Skip to content
Open
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
36 changes: 33 additions & 3 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ import {
tryCast,
TypeAliasDeclaration,
TypeNode,
TypeOperatorNode,
TypeParameterDeclaration,
TypeReferenceNode,
unescapeLeadingUnderscores,
Expand All @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -1468,15 +1483,23 @@ 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 <name>
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);
if (isNonContextualKeywordName) {
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));
Expand Down Expand Up @@ -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);
}
Expand Down
105 changes: 105 additions & 0 deletions tests/baselines/reference/uniqueSymbolReassignment.js
Original file line number Diff line number Diff line change
@@ -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 };

81 changes: 81 additions & 0 deletions tests/baselines/reference/uniqueSymbolReassignment.symbols
Original file line number Diff line number Diff line change
@@ -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))

Loading
Loading