From 5430ba6f1537772bfff5f098ea211220fb556136 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 21:56:09 +0000 Subject: [PATCH 1/4] Initial plan From 629a7104cc521052635adae927e2ab8841801e00 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:04:20 +0000 Subject: [PATCH 2/4] Add test case for symbol property key import in declaration emit Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- .../declarationEmitSymbolPropertyKey.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/cases/compiler/declarationEmitSymbolPropertyKey.ts diff --git a/tests/cases/compiler/declarationEmitSymbolPropertyKey.ts b/tests/cases/compiler/declarationEmitSymbolPropertyKey.ts new file mode 100644 index 0000000000000..7e7d1047f072f --- /dev/null +++ b/tests/cases/compiler/declarationEmitSymbolPropertyKey.ts @@ -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 = { + [lostSymbol]: T; +}; + +declare function fn(): SomeGeneric; + +export { + lostSymbol, + fn +}; + +// @filename: index.ts +import { fn } from 'test-pkg'; +export const value = fn(); From 55892141650380501fc61339b4ae0d8e5ab89f17 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 22:54:50 +0000 Subject: [PATCH 3/4] 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> --- src/compiler/checker.ts | 21 +++++++- ...eclarationEmitSymbolPropertyKey.errors.txt | 24 +++++++++ .../declarationEmitSymbolPropertyKey.js | 28 ++++++++++ .../declarationEmitSymbolPropertyKey.symbols | 42 +++++++++++++++ .../declarationEmitSymbolPropertyKey.types | 53 +++++++++++++++++++ 5 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/declarationEmitSymbolPropertyKey.errors.txt create mode 100644 tests/baselines/reference/declarationEmitSymbolPropertyKey.js create mode 100644 tests/baselines/reference/declarationEmitSymbolPropertyKey.symbols create mode 100644 tests/baselines/reference/declarationEmitSymbolPropertyKey.types diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 75cf8829b2c21..7ed2eb0c72063 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7777,7 +7777,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); } } - context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; + // For unique symbol property keys, preserve the original enclosing declaration + // to enable proper symbol accessibility checking and import tracking + const propNameType = getSymbolLinks(propertySymbol).nameType; + const useOriginalEnclosing = propNameType && (propNameType.flags & TypeFlags.UniqueESSymbol); + context.enclosingDeclaration = useOriginalEnclosing + ? saveEnclosingDeclaration + : (propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration); const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context); context.enclosingDeclaration = saveEnclosingDeclaration; context.approximateLength += symbolName(propertySymbol).length + 1; @@ -9015,7 +9021,18 @@ 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; + // Check if the unique symbol is directly accessible as a value from the enclosing declaration + // Use isSymbolAccessibleByFlags with allowModules=false to ensure the symbol itself is accessible + // (not just via parent module import type). If not directly accessible as a value, + // report as non-serializable to trigger proper error message. + if (context.tracker.canTrackSymbol && context.enclosingDeclaration) { + const directlyAccessible = isSymbolAccessibleByFlags(uniqueSymbol, context.enclosingDeclaration, SymbolFlags.Value); + if (!directlyAccessible && context.tracker.reportNonSerializableProperty) { + context.tracker.reportNonSerializableProperty(symbolToString(uniqueSymbol)); + } + } + return factory.createComputedPropertyName(symbolToExpression(uniqueSymbol, context, SymbolFlags.Value)); } } } diff --git a/tests/baselines/reference/declarationEmitSymbolPropertyKey.errors.txt b/tests/baselines/reference/declarationEmitSymbolPropertyKey.errors.txt new file mode 100644 index 0000000000000..97be0292d90e3 --- /dev/null +++ b/tests/baselines/reference/declarationEmitSymbolPropertyKey.errors.txt @@ -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 = { + [lostSymbol]: T; + }; + + declare function fn(): SomeGeneric; + + 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. + \ No newline at end of file diff --git a/tests/baselines/reference/declarationEmitSymbolPropertyKey.js b/tests/baselines/reference/declarationEmitSymbolPropertyKey.js new file mode 100644 index 0000000000000..cfe51e183c9a6 --- /dev/null +++ b/tests/baselines/reference/declarationEmitSymbolPropertyKey.js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/declarationEmitSymbolPropertyKey.ts] //// + +//// [index.d.ts] +declare const lostSymbol: unique symbol; +type lostSymbol = typeof lostSymbol; + +type SomeGeneric = { + [lostSymbol]: T; +}; + +declare function fn(): SomeGeneric; + +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)(); diff --git a/tests/baselines/reference/declarationEmitSymbolPropertyKey.symbols b/tests/baselines/reference/declarationEmitSymbolPropertyKey.symbols new file mode 100644 index 0000000000000..c66b0c914c17b --- /dev/null +++ b/tests/baselines/reference/declarationEmitSymbolPropertyKey.symbols @@ -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 = { +>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; +>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)) + diff --git a/tests/baselines/reference/declarationEmitSymbolPropertyKey.types b/tests/baselines/reference/declarationEmitSymbolPropertyKey.types new file mode 100644 index 0000000000000..bf048c9f5a43d --- /dev/null +++ b/tests/baselines/reference/declarationEmitSymbolPropertyKey.types @@ -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 = { +>SomeGeneric : SomeGeneric +> : ^^^^^^^^^^^^^^ + + [lostSymbol]: T; +>[lostSymbol] : T +> : ^ +>lostSymbol : unique symbol +> : ^^^^^^^^^^^^^ + +}; + +declare function fn(): SomeGeneric; +>fn : () => SomeGeneric +> : ^^^^^^ + +export { + lostSymbol, +>lostSymbol : unique symbol +> : ^^^^^^^^^^^^^ + + fn +>fn : () => SomeGeneric +> : ^^^^^^ + +}; + +=== index.ts === +import { fn } from 'test-pkg'; +>fn : () => { [lostSymbol]: unknown; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +export const value = fn(); +>value : { [lostSymbol]: unknown; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fn() : { [lostSymbol]: unknown; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^ +>fn : () => { [lostSymbol]: unknown; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + From 8198701a44446e185e6ecad23554ba7a5666d2ad Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 23:15:38 +0000 Subject: [PATCH 4/4] Refine fix to report error for inaccessible unique symbol property keys Add accessibility check in getPropertyNameNodeForSymbolFromNameType to detect when a unique symbol from an external module cannot be directly accessed as a value. Reports a non-serializable property error to prevent generating broken declaration files with undefined symbol references. Co-authored-by: DanielRosenwasser <972891+DanielRosenwasser@users.noreply.github.com> --- src/compiler/checker.ts | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7ed2eb0c72063..874103578d2b7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7777,10 +7777,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { context.tracker.reportNonSerializableProperty(symbolToString(propertySymbol)); } } - // For unique symbol property keys, preserve the original enclosing declaration - // to enable proper symbol accessibility checking and import tracking + // 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; - const useOriginalEnclosing = propNameType && (propNameType.flags & TypeFlags.UniqueESSymbol); + 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); @@ -9022,14 +9030,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } if (nameType.flags & TypeFlags.UniqueESSymbol) { const uniqueSymbol = (nameType as UniqueESSymbolType).symbol; - // Check if the unique symbol is directly accessible as a value from the enclosing declaration - // Use isSymbolAccessibleByFlags with allowModules=false to ensure the symbol itself is accessible - // (not just via parent module import type). If not directly accessible as a value, - // report as non-serializable to trigger proper error message. - if (context.tracker.canTrackSymbol && context.enclosingDeclaration) { - const directlyAccessible = isSymbolAccessibleByFlags(uniqueSymbol, context.enclosingDeclaration, SymbolFlags.Value); - if (!directlyAccessible && context.tracker.reportNonSerializableProperty) { - context.tracker.reportNonSerializableProperty(symbolToString(uniqueSymbol)); + // 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));