Skip to content

Commit 5589214

Browse files
Fix declaration emit for symbol property keys from external modules
When a type uses a unique symbol from an external module as a property key, check if the symbol is directly accessible as a value. If not, report a non-serializable property error instead of silently emitting a broken declaration file with an undefined reference. Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com>
1 parent 629a710 commit 5589214

File tree

5 files changed

+166
-2
lines changed

5 files changed

+166
-2
lines changed

src/compiler/checker.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7777,7 +7777,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
77777777
context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol));
77787778
}
77797779
}
7780-
context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration;
7780+
// For unique symbol property keys, preserve the original enclosing declaration
7781+
// to enable proper symbol accessibility checking and import tracking
7782+
const propNameType = getSymbolLinks(propertySymbol).nameType;
7783+
const useOriginalEnclosing = propNameType && (propNameType.flags & TypeFlags.UniqueESSymbol);
7784+
context.enclosingDeclaration = useOriginalEnclosing
7785+
? saveEnclosingDeclaration
7786+
: (propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration);
77817787
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
77827788
context.enclosingDeclaration = saveEnclosingDeclaration;
77837789
context.approximateLength += symbolName(propertySymbol).length + 1;
@@ -9015,7 +9021,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
90159021
return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod);
90169022
}
90179023
if (nameType.flags & TypeFlags.UniqueESSymbol) {
9018-
return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value));
9024+
const uniqueSymbol = (nameType as UniqueESSymbolType).symbol;
9025+
// Check if the unique symbol is directly accessible as a value from the enclosing declaration
9026+
// Use isSymbolAccessibleByFlags with allowModules=false to ensure the symbol itself is accessible
9027+
// (not just via parent module import type). If not directly accessible as a value,
9028+
// report as non-serializable to trigger proper error message.
9029+
if (context.tracker.canTrackSymbol && context.enclosingDeclaration) {
9030+
const directlyAccessible = isSymbolAccessibleByFlags(uniqueSymbol, context.enclosingDeclaration, SymbolFlags.Value);
9031+
if (!directlyAccessible && context.tracker.reportNonSerializableProperty) {
9032+
context.tracker.reportNonSerializableProperty(symbolToString(uniqueSymbol));
9033+
}
9034+
}
9035+
return factory.createComputedPropertyName(symbolToExpression(uniqueSymbol, context, SymbolFlags.Value));
90199036
}
90209037
}
90219038
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
index.ts(2,14): error TS4118: The type of this node cannot be serialized because its property 'lostSymbol' cannot be serialized.
2+
3+
4+
==== node_modules/test-pkg/index.d.ts (0 errors) ====
5+
declare const lostSymbol: unique symbol;
6+
type lostSymbol = typeof lostSymbol;
7+
8+
type SomeGeneric<T> = {
9+
[lostSymbol]: T;
10+
};
11+
12+
declare function fn(): SomeGeneric<unknown>;
13+
14+
export {
15+
lostSymbol,
16+
fn
17+
};
18+
19+
==== index.ts (1 errors) ====
20+
import { fn } from 'test-pkg';
21+
export const value = fn();
22+
~~~~~
23+
!!! error TS4118: The type of this node cannot be serialized because its property 'lostSymbol' cannot be serialized.
24+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////
2+
3+
//// [index.d.ts]
4+
declare const lostSymbol: unique symbol;
5+
type lostSymbol = typeof lostSymbol;
6+
7+
type SomeGeneric<T> = {
8+
[lostSymbol]: T;
9+
};
10+
11+
declare function fn(): SomeGeneric<unknown>;
12+
13+
export {
14+
lostSymbol,
15+
fn
16+
};
17+
18+
//// [index.ts]
19+
import { fn } from 'test-pkg';
20+
export const value = fn();
21+
22+
23+
//// [index.js]
24+
"use strict";
25+
Object.defineProperty(exports, "__esModule", { value: true });
26+
exports.value = void 0;
27+
const test_pkg_1 = require("test-pkg");
28+
exports.value = (0, test_pkg_1.fn)();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////
2+
3+
=== node_modules/test-pkg/index.d.ts ===
4+
declare const lostSymbol: unique symbol;
5+
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
6+
7+
type lostSymbol = typeof lostSymbol;
8+
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
9+
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
10+
11+
type SomeGeneric<T> = {
12+
>SomeGeneric : Symbol(SomeGeneric, Decl(index.d.ts, 1, 36))
13+
>T : Symbol(T, Decl(index.d.ts, 3, 17))
14+
15+
[lostSymbol]: T;
16+
>[lostSymbol] : Symbol([lostSymbol], Decl(index.d.ts, 3, 23))
17+
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
18+
>T : Symbol(T, Decl(index.d.ts, 3, 17))
19+
20+
};
21+
22+
declare function fn(): SomeGeneric<unknown>;
23+
>fn : Symbol(fn, Decl(index.d.ts, 5, 2))
24+
>SomeGeneric : Symbol(SomeGeneric, Decl(index.d.ts, 1, 36))
25+
26+
export {
27+
lostSymbol,
28+
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 9, 8))
29+
30+
fn
31+
>fn : Symbol(fn, Decl(index.d.ts, 10, 15))
32+
33+
};
34+
35+
=== index.ts ===
36+
import { fn } from 'test-pkg';
37+
>fn : Symbol(fn, Decl(index.ts, 0, 8))
38+
39+
export const value = fn();
40+
>value : Symbol(value, Decl(index.ts, 1, 12))
41+
>fn : Symbol(fn, Decl(index.ts, 0, 8))
42+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////
2+
3+
=== node_modules/test-pkg/index.d.ts ===
4+
declare const lostSymbol: unique symbol;
5+
>lostSymbol : unique symbol
6+
> : ^^^^^^^^^^^^^
7+
8+
type lostSymbol = typeof lostSymbol;
9+
>lostSymbol : unique symbol
10+
> : ^^^^^^^^^^^^^
11+
>lostSymbol : unique symbol
12+
> : ^^^^^^^^^^^^^
13+
14+
type SomeGeneric<T> = {
15+
>SomeGeneric : SomeGeneric<T>
16+
> : ^^^^^^^^^^^^^^
17+
18+
[lostSymbol]: T;
19+
>[lostSymbol] : T
20+
> : ^
21+
>lostSymbol : unique symbol
22+
> : ^^^^^^^^^^^^^
23+
24+
};
25+
26+
declare function fn(): SomeGeneric<unknown>;
27+
>fn : () => SomeGeneric<unknown>
28+
> : ^^^^^^
29+
30+
export {
31+
lostSymbol,
32+
>lostSymbol : unique symbol
33+
> : ^^^^^^^^^^^^^
34+
35+
fn
36+
>fn : () => SomeGeneric<unknown>
37+
> : ^^^^^^
38+
39+
};
40+
41+
=== index.ts ===
42+
import { fn } from 'test-pkg';
43+
>fn : () => { [lostSymbol]: unknown; }
44+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
45+
46+
export const value = fn();
47+
>value : { [lostSymbol]: unknown; }
48+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
49+
>fn() : { [lostSymbol]: unknown; }
50+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
51+
>fn : () => { [lostSymbol]: unknown; }
52+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
53+

0 commit comments

Comments
 (0)