diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 66c2dbc00ebd9..2cd9c96242c9d 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -203,6 +203,7 @@ import { tryCast, TypeAliasDeclaration, TypeNode, + TypeOperatorNode, TypeParameterDeclaration, TypeReferenceNode, unescapeLeadingUnderscores, @@ -216,7 +217,12 @@ import { visitNodes, VisitResult, } from "../_namespaces/ts.js"; - +import { + idText, + isIdentifier, + PrivateIdentifier, +} from "../_namespaces/ts.js"; +import { collectExternalModuleInfo } from "./utilities.js"; /** @internal */ export function getDeclarationDiagnostics( host: EmitHost, @@ -1459,6 +1465,15 @@ export function transformDeclarations(context: TransformationContext): Transform fakespace.locals = createSymbolTable(props); fakespace.symbol = props[0].parent!; const exportMappings: [Identifier, string][] = []; + + // Before emitting expando properties, get all exported names for the file + const moduleInfo = collectExternalModuleInfo(context, input.parent as SourceFile); + const topLevelExportNames = new Set( + (moduleInfo.exportedNames ?? []) + .filter(n => isIdentifier(n) || isPrivateIdentifier(n)) + .map(n => idText(n as Identifier | PrivateIdentifier)), + ); + let declarations: (VariableStatement | ExportDeclaration)[] = mapDefined(props, p => { if (!isExpandoPropertyDeclaration(p.valueDeclaration)) { return undefined; @@ -1468,7 +1483,12 @@ export function transformDeclarations(context: TransformationContext): Transform return undefined; // unique symbol or non-identifier name - omit, since there's no syntax that can preserve it } getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNode(p.valueDeclaration); - const type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags | InternalNodeBuilderFlags.NoSyntacticPrinter, symbolTracker); + let type = resolver.createTypeOfDeclaration(p.valueDeclaration, fakespace, declarationEmitNodeBuilderFlags, declarationEmitInternalNodeBuilderFlags | InternalNodeBuilderFlags.NoSyntacticPrinter, symbolTracker); + const wasUniqueSymbol = isUniqueSymbolType(type); + // If this is a unique symbol and also exported at the top level, emit typeof + if (wasUniqueSymbol && topLevelExportNames.has(nameStr)) { + type = factory.createTypeQueryNode(factory.createIdentifier(nameStr)); + } getSymbolAccessibilityDiagnostic = oldDiag; const isNonContextualKeywordName = isStringANonContextualKeyword(nameStr); const name = isNonContextualKeywordName ? factory.getGeneratedNameForNode(p.valueDeclaration) : factory.createIdentifier(nameStr); @@ -1476,7 +1496,10 @@ export function transformDeclarations(context: TransformationContext): Transform exportMappings.push([name, nameStr]); } const varDecl = factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); - return factory.createVariableStatement(isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)], factory.createVariableDeclarationList([varDecl])); + const modifiers = isNonContextualKeywordName ? undefined : [factory.createToken(SyntaxKind.ExportKeyword)]; + const isConst = wasUniqueSymbol; + const variableDeclarationList = factory.createVariableDeclarationList([varDecl], isConst ? NodeFlags.Const : NodeFlags.None); + return factory.createVariableStatement(modifiers, variableDeclarationList); }); if (!exportMappings.length) { declarations = mapDefined(declarations, declaration => factory.replaceModifiers(declaration, ModifierFlags.None)); @@ -1814,6 +1837,13 @@ export function transformDeclarations(context: TransformationContext): Transform return isExportAssignment(node) || isExportDeclaration(node); } + function isUniqueSymbolType(type: TypeNode | undefined): boolean { + return !!type && + type.kind === SyntaxKind.TypeOperator && + (type as TypeOperatorNode).operator === SyntaxKind.UniqueKeyword && + (type as TypeOperatorNode).type.kind === SyntaxKind.SymbolKeyword; + } + function hasScopeMarker(statements: readonly Statement[]) { return some(statements, isScopeMarker); } diff --git a/tests/baselines/reference/uniqueSymbolReassignment.js b/tests/baselines/reference/uniqueSymbolReassignment.js new file mode 100644 index 0000000000000..5a0cfcc01afde --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolReassignment.js @@ -0,0 +1,105 @@ +//// [tests/cases/compiler/uniqueSymbolReassignment.ts] //// + +//// [uniqueSymbolReassignment.ts] +// ------------------------- +// Explicit unique symbols (should emit `const` / `typeof` when exported) +// ------------------------- +const mySymbol = Symbol('Symbols.mySymbol'); +const anotherUnique = Symbol('symbols.anotherUnique'); + +function myFunction() {} + +// Attach the unique ones +myFunction.mySymbol = mySymbol; +myFunction.anotherUnique = anotherUnique; + +// ------------------------- +// Non-unique symbols (should stay `var`) +// ------------------------- +let nonUnique1 = Symbol('nonUnique1'); +let nonUnique2 = Symbol('nonUnique2'); + +myFunction.nonUnique1 = nonUnique1; +myFunction.nonUnique2 = nonUnique2; + +// ------------------------- +// Normal variables (should stay `var`/string) +// ------------------------- +const normalVar = "just a string"; +const symbolName = "this contains symbol but is not one"; + +myFunction.normalVar = normalVar; +myFunction.symbolName = symbolName; + +// ------------------------- +// Export symbols along with function to test `typeof` behavior +// ------------------------- +export { myFunction, anotherUnique }; + + +//// [uniqueSymbolReassignment.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.anotherUnique = void 0; +exports.myFunction = myFunction; +// ------------------------- +// Explicit unique symbols (should emit `const` / `typeof` when exported) +// ------------------------- +var mySymbol = Symbol('Symbols.mySymbol'); +var anotherUnique = Symbol('symbols.anotherUnique'); +exports.anotherUnique = anotherUnique; +function myFunction() { } +// Attach the unique ones +myFunction.mySymbol = mySymbol; +myFunction.anotherUnique = anotherUnique; +// ------------------------- +// Non-unique symbols (should stay `var`) +// ------------------------- +var nonUnique1 = Symbol('nonUnique1'); +var nonUnique2 = Symbol('nonUnique2'); +myFunction.nonUnique1 = nonUnique1; +myFunction.nonUnique2 = nonUnique2; +// ------------------------- +// Normal variables (should stay `var`/string) +// ------------------------- +var normalVar = "just a string"; +var symbolName = "this contains symbol but is not one"; +myFunction.normalVar = normalVar; +myFunction.symbolName = symbolName; + + +//// [uniqueSymbolReassignment.d.ts] +declare const anotherUnique: unique symbol; +declare function myFunction(): void; +declare namespace myFunction { + const mySymbol: unique symbol; + const anotherUnique: typeof anotherUnique; + var nonUnique1: symbol; + var nonUnique2: symbol; + var normalVar: string; + var symbolName: string; +} +export { myFunction, anotherUnique }; + + +//// [DtsFileErrors] + + +uniqueSymbolReassignment.d.ts(5,11): error TS2502: 'anotherUnique' is referenced directly or indirectly in its own type annotation. + + +==== uniqueSymbolReassignment.d.ts (1 errors) ==== + declare const anotherUnique: unique symbol; + declare function myFunction(): void; + declare namespace myFunction { + const mySymbol: unique symbol; + const anotherUnique: typeof anotherUnique; + ~~~~~~~~~~~~~ +!!! error TS2502: 'anotherUnique' is referenced directly or indirectly in its own type annotation. + var nonUnique1: symbol; + var nonUnique2: symbol; + var normalVar: string; + var symbolName: string; + } + export { myFunction, anotherUnique }; + \ No newline at end of file diff --git a/tests/baselines/reference/uniqueSymbolReassignment.symbols b/tests/baselines/reference/uniqueSymbolReassignment.symbols new file mode 100644 index 0000000000000..bd56f3a7ffd90 --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolReassignment.symbols @@ -0,0 +1,81 @@ +//// [tests/cases/compiler/uniqueSymbolReassignment.ts] //// + +=== uniqueSymbolReassignment.ts === +// ------------------------- +// Explicit unique symbols (should emit `const` / `typeof` when exported) +// ------------------------- +const mySymbol = Symbol('Symbols.mySymbol'); +>mySymbol : Symbol(mySymbol, Decl(uniqueSymbolReassignment.ts, 3, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +const anotherUnique = Symbol('symbols.anotherUnique'); +>anotherUnique : Symbol(anotherUnique, Decl(uniqueSymbolReassignment.ts, 4, 5)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +function myFunction() {} +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) + +// Attach the unique ones +myFunction.mySymbol = mySymbol; +>myFunction.mySymbol : Symbol(myFunction.mySymbol, Decl(uniqueSymbolReassignment.ts, 6, 24)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>mySymbol : Symbol(myFunction.mySymbol, Decl(uniqueSymbolReassignment.ts, 6, 24)) +>mySymbol : Symbol(mySymbol, Decl(uniqueSymbolReassignment.ts, 3, 5)) + +myFunction.anotherUnique = anotherUnique; +>myFunction.anotherUnique : Symbol(myFunction.anotherUnique, Decl(uniqueSymbolReassignment.ts, 9, 31)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>anotherUnique : Symbol(myFunction.anotherUnique, Decl(uniqueSymbolReassignment.ts, 9, 31)) +>anotherUnique : Symbol(anotherUnique, Decl(uniqueSymbolReassignment.ts, 4, 5)) + +// ------------------------- +// Non-unique symbols (should stay `var`) +// ------------------------- +let nonUnique1 = Symbol('nonUnique1'); +>nonUnique1 : Symbol(nonUnique1, Decl(uniqueSymbolReassignment.ts, 15, 3)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +let nonUnique2 = Symbol('nonUnique2'); +>nonUnique2 : Symbol(nonUnique2, Decl(uniqueSymbolReassignment.ts, 16, 3)) +>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --)) + +myFunction.nonUnique1 = nonUnique1; +>myFunction.nonUnique1 : Symbol(myFunction.nonUnique1, Decl(uniqueSymbolReassignment.ts, 16, 38)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>nonUnique1 : Symbol(myFunction.nonUnique1, Decl(uniqueSymbolReassignment.ts, 16, 38)) +>nonUnique1 : Symbol(nonUnique1, Decl(uniqueSymbolReassignment.ts, 15, 3)) + +myFunction.nonUnique2 = nonUnique2; +>myFunction.nonUnique2 : Symbol(myFunction.nonUnique2, Decl(uniqueSymbolReassignment.ts, 18, 35)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>nonUnique2 : Symbol(myFunction.nonUnique2, Decl(uniqueSymbolReassignment.ts, 18, 35)) +>nonUnique2 : Symbol(nonUnique2, Decl(uniqueSymbolReassignment.ts, 16, 3)) + +// ------------------------- +// Normal variables (should stay `var`/string) +// ------------------------- +const normalVar = "just a string"; +>normalVar : Symbol(normalVar, Decl(uniqueSymbolReassignment.ts, 24, 5)) + +const symbolName = "this contains symbol but is not one"; +>symbolName : Symbol(symbolName, Decl(uniqueSymbolReassignment.ts, 25, 5)) + +myFunction.normalVar = normalVar; +>myFunction.normalVar : Symbol(myFunction.normalVar, Decl(uniqueSymbolReassignment.ts, 25, 57)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>normalVar : Symbol(myFunction.normalVar, Decl(uniqueSymbolReassignment.ts, 25, 57)) +>normalVar : Symbol(normalVar, Decl(uniqueSymbolReassignment.ts, 24, 5)) + +myFunction.symbolName = symbolName; +>myFunction.symbolName : Symbol(myFunction.symbolName, Decl(uniqueSymbolReassignment.ts, 27, 33)) +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 4, 54), Decl(uniqueSymbolReassignment.ts, 6, 24), Decl(uniqueSymbolReassignment.ts, 9, 31), Decl(uniqueSymbolReassignment.ts, 16, 38), Decl(uniqueSymbolReassignment.ts, 18, 35) ... and 2 more) +>symbolName : Symbol(myFunction.symbolName, Decl(uniqueSymbolReassignment.ts, 27, 33)) +>symbolName : Symbol(symbolName, Decl(uniqueSymbolReassignment.ts, 25, 5)) + +// ------------------------- +// Export symbols along with function to test `typeof` behavior +// ------------------------- +export { myFunction, anotherUnique }; +>myFunction : Symbol(myFunction, Decl(uniqueSymbolReassignment.ts, 33, 8)) +>anotherUnique : Symbol(anotherUnique, Decl(uniqueSymbolReassignment.ts, 33, 20)) + diff --git a/tests/baselines/reference/uniqueSymbolReassignment.types b/tests/baselines/reference/uniqueSymbolReassignment.types new file mode 100644 index 0000000000000..81ac76dabdfa6 --- /dev/null +++ b/tests/baselines/reference/uniqueSymbolReassignment.types @@ -0,0 +1,150 @@ +//// [tests/cases/compiler/uniqueSymbolReassignment.ts] //// + +=== uniqueSymbolReassignment.ts === +// ------------------------- +// Explicit unique symbols (should emit `const` / `typeof` when exported) +// ------------------------- +const mySymbol = Symbol('Symbols.mySymbol'); +>mySymbol : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol('Symbols.mySymbol') : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ +>'Symbols.mySymbol' : "Symbols.mySymbol" +> : ^^^^^^^^^^^^^^^^^^ + +const anotherUnique = Symbol('symbols.anotherUnique'); +>anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol('symbols.anotherUnique') : unique symbol +> : ^^^^^^^^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ +>'symbols.anotherUnique' : "symbols.anotherUnique" +> : ^^^^^^^^^^^^^^^^^^^^^^^ + +function myFunction() {} +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ + +// Attach the unique ones +myFunction.mySymbol = mySymbol; +>myFunction.mySymbol = mySymbol : unique symbol +> : ^^^^^^^^^^^^^ +>myFunction.mySymbol : unique symbol +> : ^^^^^^^^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>mySymbol : unique symbol +> : ^^^^^^^^^^^^^ +>mySymbol : unique symbol +> : ^^^^^^^^^^^^^ + +myFunction.anotherUnique = anotherUnique; +>myFunction.anotherUnique = anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ +>myFunction.anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ +>anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ + +// ------------------------- +// Non-unique symbols (should stay `var`) +// ------------------------- +let nonUnique1 = Symbol('nonUnique1'); +>nonUnique1 : symbol +> : ^^^^^^ +>Symbol('nonUnique1') : symbol +> : ^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ +>'nonUnique1' : "nonUnique1" +> : ^^^^^^^^^^^^ + +let nonUnique2 = Symbol('nonUnique2'); +>nonUnique2 : symbol +> : ^^^^^^ +>Symbol('nonUnique2') : symbol +> : ^^^^^^ +>Symbol : SymbolConstructor +> : ^^^^^^^^^^^^^^^^^ +>'nonUnique2' : "nonUnique2" +> : ^^^^^^^^^^^^ + +myFunction.nonUnique1 = nonUnique1; +>myFunction.nonUnique1 = nonUnique1 : symbol +> : ^^^^^^ +>myFunction.nonUnique1 : symbol +> : ^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>nonUnique1 : symbol +> : ^^^^^^ +>nonUnique1 : symbol +> : ^^^^^^ + +myFunction.nonUnique2 = nonUnique2; +>myFunction.nonUnique2 = nonUnique2 : symbol +> : ^^^^^^ +>myFunction.nonUnique2 : symbol +> : ^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>nonUnique2 : symbol +> : ^^^^^^ +>nonUnique2 : symbol +> : ^^^^^^ + +// ------------------------- +// Normal variables (should stay `var`/string) +// ------------------------- +const normalVar = "just a string"; +>normalVar : "just a string" +> : ^^^^^^^^^^^^^^^ +>"just a string" : "just a string" +> : ^^^^^^^^^^^^^^^ + +const symbolName = "this contains symbol but is not one"; +>symbolName : "this contains symbol but is not one" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>"this contains symbol but is not one" : "this contains symbol but is not one" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +myFunction.normalVar = normalVar; +>myFunction.normalVar = normalVar : "just a string" +> : ^^^^^^^^^^^^^^^ +>myFunction.normalVar : string +> : ^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>normalVar : string +> : ^^^^^^ +>normalVar : "just a string" +> : ^^^^^^^^^^^^^^^ + +myFunction.symbolName = symbolName; +>myFunction.symbolName = symbolName : "this contains symbol but is not one" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>myFunction.symbolName : string +> : ^^^^^^ +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>symbolName : string +> : ^^^^^^ +>symbolName : "this contains symbol but is not one" +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +// ------------------------- +// Export symbols along with function to test `typeof` behavior +// ------------------------- +export { myFunction, anotherUnique }; +>myFunction : typeof myFunction +> : ^^^^^^^^^^^^^^^^^ +>anotherUnique : unique symbol +> : ^^^^^^^^^^^^^ + diff --git a/tests/cases/compiler/uniqueSymbolReassignment.ts b/tests/cases/compiler/uniqueSymbolReassignment.ts new file mode 100644 index 0000000000000..48618096e90b9 --- /dev/null +++ b/tests/cases/compiler/uniqueSymbolReassignment.ts @@ -0,0 +1,38 @@ +// @filename: uniqueSymbolReassignment.ts +// @declaration: true +// @lib: esnext + +// ------------------------- +// Explicit unique symbols (should emit `const` / `typeof` when exported) +// ------------------------- +const mySymbol = Symbol('Symbols.mySymbol'); +const anotherUnique = Symbol('symbols.anotherUnique'); + +function myFunction() {} + +// Attach the unique ones +myFunction.mySymbol = mySymbol; +myFunction.anotherUnique = anotherUnique; + +// ------------------------- +// Non-unique symbols (should stay `var`) +// ------------------------- +let nonUnique1 = Symbol('nonUnique1'); +let nonUnique2 = Symbol('nonUnique2'); + +myFunction.nonUnique1 = nonUnique1; +myFunction.nonUnique2 = nonUnique2; + +// ------------------------- +// Normal variables (should stay `var`/string) +// ------------------------- +const normalVar = "just a string"; +const symbolName = "this contains symbol but is not one"; + +myFunction.normalVar = normalVar; +myFunction.symbolName = symbolName; + +// ------------------------- +// Export symbols along with function to test `typeof` behavior +// ------------------------- +export { myFunction, anotherUnique };