Skip to content

Commit 87d10eb

Browse files
authored
Eliminate well known symbols as a concept in the checker and rely on unique symbols (#42543)
* Eliminate well-known symbols in the checker: 2021 edition * Actually update the lib text to say unique symbol, too (this is unneeded with compat code in place, but this makes goto-def make more sense) * Add test showing mismatched symbol constructor type interop * Add more test cases for some other related issues this fixes * Revert computed name change * Style comments
1 parent 5a1d308 commit 87d10eb

File tree

235 files changed

+1230
-1022
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

235 files changed

+1230
-1022
lines changed

src/compiler/binder.ts

+3-6
Original file line numberDiff line numberDiff line change
@@ -344,12 +344,9 @@ namespace ts {
344344
if (isSignedNumericLiteral(nameExpression)) {
345345
return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String;
346346
}
347-
348-
Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
349-
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
350-
}
351-
if (isWellKnownSymbolSyntactically(name)) {
352-
return getPropertyNameForKnownSymbolName(idText(name.name));
347+
else {
348+
Debug.fail("Only computed properties with literal names have declaration names");
349+
}
353350
}
354351
if (isPrivateIdentifier(name)) {
355352
// containingClass exists because private names only allowed inside classes

src/compiler/checker.ts

+49-59
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ namespace ts {
898898
// This allows users to just specify library files they want to used through --lib
899899
// and they will not get an error from not having unrelated library files
900900
let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined;
901+
let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined;
901902
let deferredGlobalESSymbolType: ObjectType;
902903
let deferredGlobalTypedPropertyDescriptorType: GenericType;
903904
let deferredGlobalPromiseType: GenericType;
@@ -3677,11 +3678,30 @@ namespace ts {
36773678
const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer);
36783679
const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration);
36793680
const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning);
3680-
if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) {
3681+
if (
3682+
enclosingDeclaration &&
3683+
container.flags & getQualifiedLeftMeaning(meaning) &&
3684+
getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)
3685+
) {
36813686
return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope
36823687
}
3683-
const res = append(append(additionalContainers, container), objectLiteralContainer);
3684-
return concatenate(res, reexportContainers);
3688+
// we potentially have a symbol which is a member of the instance side of something - look for a variable in scope with the container's type
3689+
// which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`)
3690+
const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning))
3691+
&& container.flags & SymbolFlags.Type
3692+
&& getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object
3693+
&& meaning === SymbolFlags.Value
3694+
? forEachSymbolTableInScope(enclosingDeclaration, t => {
3695+
return forEachEntry(t, s => {
3696+
if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) {
3697+
return s;
3698+
}
3699+
});
3700+
}) : undefined;
3701+
let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container];
3702+
res = append(res, objectLiteralContainer);
3703+
res = addRange(res, reexportContainers);
3704+
return res;
36853705
}
36863706
const candidates = mapDefined(symbol.declarations, d => {
36873707
if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) {
@@ -5107,8 +5127,9 @@ namespace ts {
51075127
}
51085128
}
51095129
}
5110-
context.enclosingDeclaration = saveEnclosingDeclaration;
5130+
context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration;
51115131
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
5132+
context.enclosingDeclaration = saveEnclosingDeclaration;
51125133
context.approximateLength += (symbolName(propertySymbol).length + 1);
51135134
const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined;
51145135
if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) {
@@ -5852,9 +5873,6 @@ namespace ts {
58525873
if (fromNameType) {
58535874
return fromNameType;
58545875
}
5855-
if (isKnownSymbol(symbol)) {
5856-
return factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), (symbol.escapedName as string).substr(3)));
5857-
}
58585876
const rawName = unescapeLeadingUnderscores(symbol.escapedName);
58595877
const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed);
58605878
return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote);
@@ -8707,8 +8725,18 @@ namespace ts {
87078725
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors);
87088726
}
87098727

8728+
function isGlobalSymbolConstructor(node: Node) {
8729+
const symbol = getSymbolOfNode(node);
8730+
const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false);
8731+
return globalSymbol && symbol && symbol === globalSymbol;
8732+
}
8733+
87108734
function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) {
87118735
if (type) {
8736+
// TODO: If back compat with pre-3.0/4.0 libs isn't required, remove the following SymbolConstructor special case transforming `symbol` into `unique symbol`
8737+
if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) {
8738+
type = getESSymbolLikeTypeForNode(declaration);
8739+
}
87128740
if (reportErrors) {
87138741
reportErrorsFromWidening(declaration, type);
87148742
}
@@ -12859,6 +12887,10 @@ namespace ts {
1285912887
return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors));
1286012888
}
1286112889

12890+
function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean) {
12891+
return deferredGlobalESSymbolConstructorTypeSymbol || (deferredGlobalESSymbolConstructorTypeSymbol = getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors));
12892+
}
12893+
1286212894
function getGlobalESSymbolType(reportErrors: boolean) {
1286312895
return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
1286412896
}
@@ -13941,13 +13973,13 @@ namespace ts {
1394113973
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) {
1394213974
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
1394313975
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
13944-
if (!type && !isKnownSymbol(prop)) {
13976+
if (!type) {
1394513977
if (prop.escapedName === InternalSymbolName.Default) {
1394613978
type = getLiteralType("default");
1394713979
}
1394813980
else {
1394913981
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
13950-
type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop));
13982+
type = name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getLiteralType(symbolName(prop)) : undefined);
1395113983
}
1395213984
}
1395313985
if (type && type.flags & include) {
@@ -14174,11 +14206,8 @@ namespace ts {
1417414206
}
1417514207

1417614208
function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | PrivateIdentifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
14177-
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
1417814209
return isTypeUsableAsPropertyName(indexType) ?
1417914210
getPropertyNameFromType(indexType) :
14180-
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
14181-
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
1418214211
accessNode && isPropertyName(accessNode) ?
1418314212
// late bound names are handled in the first branch, so here we only need to handle normal names
1418414213
getPropertyNameForPropertyNameNode(accessNode) :
@@ -25279,9 +25308,6 @@ namespace ts {
2527925308
!isTypeAssignableTo(links.resolvedType, stringNumberSymbolType)) {
2528025309
error(node, Diagnostics.A_computed_property_name_must_be_of_type_string_number_symbol_or_any);
2528125310
}
25282-
else {
25283-
checkThatExpressionIsProperSymbolReference(node.expression, links.resolvedType, /*reportError*/ true);
25284-
}
2528525311
}
2528625312

2528725313
return links.resolvedType;
@@ -25342,15 +25368,15 @@ namespace ts {
2534225368
// As otherwise they may not be checked until exports for the type at this position are retrieved,
2534325369
// which may never occur.
2534425370
for (const elem of node.properties) {
25345-
if (elem.name && isComputedPropertyName(elem.name) && !isWellKnownSymbolSyntactically(elem.name)) {
25371+
if (elem.name && isComputedPropertyName(elem.name)) {
2534625372
checkComputedPropertyName(elem.name);
2534725373
}
2534825374
}
2534925375

2535025376
let offset = 0;
2535125377
for (const memberDecl of node.properties) {
2535225378
let member = getSymbolOfNode(memberDecl);
25353-
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
25379+
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ?
2535425380
checkComputedPropertyName(memberDecl.name) : undefined;
2535525381
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
2535625382
memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ||
@@ -27006,48 +27032,6 @@ namespace ts {
2700627032
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
2700727033
}
2700827034

27009-
function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
27010-
if (expressionType === errorType) {
27011-
// There is already an error, so no need to report one.
27012-
return false;
27013-
}
27014-
27015-
if (!isWellKnownSymbolSyntactically(expression)) {
27016-
return false;
27017-
}
27018-
27019-
// Make sure the property type is the primitive symbol type
27020-
if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) {
27021-
if (reportError) {
27022-
error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression));
27023-
}
27024-
return false;
27025-
}
27026-
27027-
// The name is Symbol.<someName>, so make sure Symbol actually resolves to the
27028-
// global Symbol object
27029-
const leftHandSide = <Identifier>(<PropertyAccessExpression>expression).expression;
27030-
const leftHandSideSymbol = getResolvedSymbol(leftHandSide);
27031-
if (!leftHandSideSymbol) {
27032-
return false;
27033-
}
27034-
27035-
const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true);
27036-
if (!globalESSymbol) {
27037-
// Already errored when we tried to look up the symbol
27038-
return false;
27039-
}
27040-
27041-
if (leftHandSideSymbol !== globalESSymbol) {
27042-
if (reportError) {
27043-
error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object);
27044-
}
27045-
return false;
27046-
}
27047-
27048-
return true;
27049-
}
27050-
2705127035
function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
2705227036
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
2705327037
}
@@ -35355,6 +35339,12 @@ namespace ts {
3535535339
}
3535635340
}
3535735341

35342+
function getPropertyNameForKnownSymbolName(symbolName: string): __String {
35343+
const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false);
35344+
const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName));
35345+
return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String;
35346+
}
35347+
3535835348
/**
3535935349
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like
3536035350
* type from its members.

src/compiler/transformers/utilities.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,7 @@ namespace ts {
269269
* any such locations
270270
*/
271271
export function isSimpleInlineableExpression(expression: Expression) {
272-
return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
273-
isWellKnownSymbolSyntactically(expression);
272+
return !isIdentifier(expression) && isSimpleCopiableExpression(expression);
274273
}
275274

276275
export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {

src/compiler/types.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -2307,12 +2307,6 @@ namespace ts {
23072307
| CallChainRoot
23082308
;
23092309

2310-
/** @internal */
2311-
export interface WellKnownSymbolExpression extends PropertyAccessExpression {
2312-
readonly expression: Identifier & { readonly escapedText: __String & "Symbol" };
2313-
readonly name: Identifier;
2314-
}
2315-
23162310
/** @internal */
23172311
export type BindableObjectDefinePropertyCall = CallExpression & {
23182312
readonly arguments: readonly [BindableStaticNameExpression, StringLiteralLike | NumericLiteral, ObjectLiteralExpression] & Readonly<TextRange>;
@@ -2326,7 +2320,7 @@ namespace ts {
23262320

23272321
/** @internal */
23282322
export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & {
2329-
readonly argumentExpression: StringLiteralLike | NumericLiteral | WellKnownSymbolExpression;
2323+
readonly argumentExpression: StringLiteralLike | NumericLiteral;
23302324
};
23312325

23322326
/** @internal */

src/compiler/utilities.ts

+3-28
Original file line numberDiff line numberDiff line change
@@ -2241,9 +2241,7 @@ namespace ts {
22412241

22422242
/** x[0] OR x['a'] OR x[Symbol.y] */
22432243
export function isLiteralLikeElementAccess(node: Node): node is LiteralLikeElementAccessExpression {
2244-
return isElementAccessExpression(node) && (
2245-
isStringOrNumericLiteralLike(node.argumentExpression) ||
2246-
isWellKnownSymbolSyntactically(node.argumentExpression));
2244+
return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression);
22472245
}
22482246

22492247
/** Any series of property and element accesses. */
@@ -2328,9 +2326,6 @@ namespace ts {
23282326
return escapeLeadingUnderscores(name.text);
23292327
}
23302328
}
2331-
if (isElementAccessExpression(node) && isWellKnownSymbolSyntactically(node.argumentExpression)) {
2332-
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>node.argumentExpression).name));
2333-
}
23342329
return undefined;
23352330
}
23362331

@@ -3139,9 +3134,6 @@ namespace ts {
31393134
* 3. The computed name is *not* expressed as a NumericLiteral.
31403135
* 4. The computed name is *not* expressed as a PlusToken or MinusToken
31413136
* immediately followed by a NumericLiteral.
3142-
* 5. The computed name is *not* expressed as `Symbol.<name>`, where `<name>`
3143-
* is a property of the Symbol constructor that denotes a built-in
3144-
* Symbol.
31453137
*/
31463138
export function hasDynamicName(declaration: Declaration): declaration is DynamicNamedDeclaration | DynamicNamedBinaryExpression {
31473139
const name = getNameOfDeclaration(declaration);
@@ -3154,17 +3146,7 @@ namespace ts {
31543146
}
31553147
const expr = isElementAccessExpression(name) ? skipParentheses(name.argumentExpression) : name.expression;
31563148
return !isStringOrNumericLiteralLike(expr) &&
3157-
!isSignedNumericLiteral(expr) &&
3158-
!isWellKnownSymbolSyntactically(expr);
3159-
}
3160-
3161-
/**
3162-
* Checks if the expression is of the form:
3163-
* Symbol.name
3164-
* where Symbol is literally the word "Symbol", and name is any identifierName
3165-
*/
3166-
export function isWellKnownSymbolSyntactically(node: Node): node is WellKnownSymbolExpression {
3167-
return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression);
3149+
!isSignedNumericLiteral(expr);
31683150
}
31693151

31703152
export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined {
@@ -3177,10 +3159,7 @@ namespace ts {
31773159
return escapeLeadingUnderscores(name.text);
31783160
case SyntaxKind.ComputedPropertyName:
31793161
const nameExpression = name.expression;
3180-
if (isWellKnownSymbolSyntactically(nameExpression)) {
3181-
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
3182-
}
3183-
else if (isStringOrNumericLiteralLike(nameExpression)) {
3162+
if (isStringOrNumericLiteralLike(nameExpression)) {
31843163
return escapeLeadingUnderscores(nameExpression.text);
31853164
}
31863165
else if (isSignedNumericLiteral(nameExpression)) {
@@ -3218,10 +3197,6 @@ namespace ts {
32183197
return `__@${getSymbolId(symbol)}@${symbol.escapedName}` as __String;
32193198
}
32203199

3221-
export function getPropertyNameForKnownSymbolName(symbolName: string): __String {
3222-
return "__@" + symbolName as __String;
3223-
}
3224-
32253200
export function getSymbolNameForPrivateIdentifier(containingClassSymbol: Symbol, description: __String): __String {
32263201
return `__#${getSymbolId(containingClassSymbol)}@${description}` as __String;
32273202
}

src/lib/es2015.iterable.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ interface SymbolConstructor {
55
* A method that returns the default iterator for an object. Called by the semantics of the
66
* for-of statement.
77
*/
8-
readonly iterator: symbol;
8+
readonly iterator: unique symbol;
99
}
1010

1111
interface IteratorYieldResult<TYield> {

0 commit comments

Comments
 (0)