Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 32 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7777,7 +7777,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol));
}
}
context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration;
// For unique symbol property keys from external modules, preserve the original enclosing declaration
// to enable proper symbol accessibility checking and import tracking.
// Built-in symbols (like Symbol.iterator) should use the normal enclosing declaration handling.
const propNameType = getSymbolLinks(propertySymbol).nameType;
let useOriginalEnclosing = false;
if (propNameType && (propNameType.flags & TypeFlags.UniqueESSymbol) && saveEnclosingDeclaration) {
const symDecl = (propNameType as UniqueESSymbolType).symbol.declarations;
const symbolSourceFile = symDecl && symDecl[0] && getSourceFileOfNode(symDecl[0]);
const enclosingSourceFile = getSourceFileOfNode(saveEnclosingDeclaration);
// Only use original enclosing for symbols from different files (external modules)
useOriginalEnclosing = !!(symbolSourceFile && enclosingSourceFile && symbolSourceFile !== enclosingSourceFile);
}
context.enclosingDeclaration = useOriginalEnclosing
? saveEnclosingDeclaration
: (propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration);
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
context.enclosingDeclaration = saveEnclosingDeclaration;
context.approximateLength += symbolName(propertySymbol).length + 1;
Expand Down Expand Up @@ -9015,7 +9029,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return createPropertyNameNodeForIdentifierOrLiteral(name, getEmitScriptTarget(compilerOptions), singleQuote, stringNamed, isMethod);
}
if (nameType.flags & TypeFlags.UniqueESSymbol) {
return factory.createComputedPropertyName(symbolToExpression((nameType as UniqueESSymbolType).symbol, context, SymbolFlags.Value));
const uniqueSymbol = (nameType as UniqueESSymbolType).symbol;
// For unique symbol property keys from external modules, verify value accessibility
// and report error if the symbol cannot be named without an import
if (context.tracker.canTrackSymbol && context.enclosingDeclaration && uniqueSymbol.declarations?.length) {
const symSourceFile = getSourceFileOfNode(uniqueSymbol.declarations[0]);
const enclosingFile = getSourceFileOfNode(context.enclosingDeclaration);
// Only check for symbols from different source files (external modules)
if (symSourceFile && enclosingFile && symSourceFile !== enclosingFile) {
// Check if symbol is directly accessible as a value (not via parent module)
if (!isSymbolAccessibleByFlags(uniqueSymbol, context.enclosingDeclaration, SymbolFlags.Value)) {
if (context.tracker.reportNonSerializableProperty) {
context.tracker.reportNonSerializableProperty(symbolToString(uniqueSymbol));
}
}
}
}
return factory.createComputedPropertyName(symbolToExpression(uniqueSymbol, context, SymbolFlags.Value));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
index.ts(2,14): error TS4118: The type of this node cannot be serialized because its property 'lostSymbol' cannot be serialized.


==== node_modules/test-pkg/index.d.ts (0 errors) ====
declare const lostSymbol: unique symbol;
type lostSymbol = typeof lostSymbol;

type SomeGeneric<T> = {
[lostSymbol]: T;
};

declare function fn(): SomeGeneric<unknown>;

export {
lostSymbol,
fn
};

==== index.ts (1 errors) ====
import { fn } from 'test-pkg';
export const value = fn();
~~~~~
!!! error TS4118: The type of this node cannot be serialized because its property 'lostSymbol' cannot be serialized.

28 changes: 28 additions & 0 deletions tests/baselines/reference/declarationEmitSymbolPropertyKey.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////

//// [index.d.ts]
declare const lostSymbol: unique symbol;
type lostSymbol = typeof lostSymbol;

type SomeGeneric<T> = {
[lostSymbol]: T;
};

declare function fn(): SomeGeneric<unknown>;

export {
lostSymbol,
fn
};

//// [index.ts]
import { fn } from 'test-pkg';
export const value = fn();


//// [index.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.value = void 0;
const test_pkg_1 = require("test-pkg");
exports.value = (0, test_pkg_1.fn)();
42 changes: 42 additions & 0 deletions tests/baselines/reference/declarationEmitSymbolPropertyKey.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////

=== node_modules/test-pkg/index.d.ts ===
declare const lostSymbol: unique symbol;
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))

type lostSymbol = typeof lostSymbol;
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))

type SomeGeneric<T> = {
>SomeGeneric : Symbol(SomeGeneric, Decl(index.d.ts, 1, 36))
>T : Symbol(T, Decl(index.d.ts, 3, 17))

[lostSymbol]: T;
>[lostSymbol] : Symbol([lostSymbol], Decl(index.d.ts, 3, 23))
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 0, 13), Decl(index.d.ts, 0, 40))
>T : Symbol(T, Decl(index.d.ts, 3, 17))

};

declare function fn(): SomeGeneric<unknown>;
>fn : Symbol(fn, Decl(index.d.ts, 5, 2))
>SomeGeneric : Symbol(SomeGeneric, Decl(index.d.ts, 1, 36))

export {
lostSymbol,
>lostSymbol : Symbol(lostSymbol, Decl(index.d.ts, 9, 8))

fn
>fn : Symbol(fn, Decl(index.d.ts, 10, 15))

};

=== index.ts ===
import { fn } from 'test-pkg';
>fn : Symbol(fn, Decl(index.ts, 0, 8))

export const value = fn();
>value : Symbol(value, Decl(index.ts, 1, 12))
>fn : Symbol(fn, Decl(index.ts, 0, 8))

53 changes: 53 additions & 0 deletions tests/baselines/reference/declarationEmitSymbolPropertyKey.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] ////

=== node_modules/test-pkg/index.d.ts ===
declare const lostSymbol: unique symbol;
>lostSymbol : unique symbol
> : ^^^^^^^^^^^^^

type lostSymbol = typeof lostSymbol;
>lostSymbol : unique symbol
> : ^^^^^^^^^^^^^
>lostSymbol : unique symbol
> : ^^^^^^^^^^^^^

type SomeGeneric<T> = {
>SomeGeneric : SomeGeneric<T>
> : ^^^^^^^^^^^^^^

[lostSymbol]: T;
>[lostSymbol] : T
> : ^
>lostSymbol : unique symbol
> : ^^^^^^^^^^^^^

};

declare function fn(): SomeGeneric<unknown>;
>fn : () => SomeGeneric<unknown>
> : ^^^^^^

export {
lostSymbol,
>lostSymbol : unique symbol
> : ^^^^^^^^^^^^^

fn
>fn : () => SomeGeneric<unknown>
> : ^^^^^^

};

=== index.ts ===
import { fn } from 'test-pkg';
>fn : () => { [lostSymbol]: unknown; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

export const value = fn();
>value : { [lostSymbol]: unknown; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>fn() : { [lostSymbol]: unknown; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^
>fn : () => { [lostSymbol]: unknown; }
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

22 changes: 22 additions & 0 deletions tests/cases/compiler/declarationEmitSymbolPropertyKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @target: es2015
// @declaration: true
// @module: commonjs

// @filename: node_modules/test-pkg/index.d.ts
declare const lostSymbol: unique symbol;
type lostSymbol = typeof lostSymbol;

type SomeGeneric<T> = {
[lostSymbol]: T;
};

declare function fn(): SomeGeneric<unknown>;

export {
lostSymbol,
fn
};

// @filename: index.ts
import { fn } from 'test-pkg';
export const value = fn();