Skip to content

Commit

Permalink
Eliminate well known symbols as a concept in the checker and rely on …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
weswigham authored Feb 22, 2021
1 parent 5a1d308 commit 87d10eb
Show file tree
Hide file tree
Showing 235 changed files with 1,230 additions and 1,022 deletions.
9 changes: 3 additions & 6 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,12 +344,9 @@ namespace ts {
if (isSignedNumericLiteral(nameExpression)) {
return tokenToString(nameExpression.operator) + nameExpression.operand.text as __String;
}

Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
if (isWellKnownSymbolSyntactically(name)) {
return getPropertyNameForKnownSymbolName(idText(name.name));
else {
Debug.fail("Only computed properties with literal names have declaration names");
}
}
if (isPrivateIdentifier(name)) {
// containingClass exists because private names only allowed inside classes
Expand Down
108 changes: 49 additions & 59 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -898,6 +898,7 @@ namespace ts {
// This allows users to just specify library files they want to used through --lib
// and they will not get an error from not having unrelated library files
let deferredGlobalESSymbolConstructorSymbol: Symbol | undefined;
let deferredGlobalESSymbolConstructorTypeSymbol: Symbol | undefined;
let deferredGlobalESSymbolType: ObjectType;
let deferredGlobalTypedPropertyDescriptorType: GenericType;
let deferredGlobalPromiseType: GenericType;
Expand Down Expand Up @@ -3677,11 +3678,30 @@ namespace ts {
const additionalContainers = mapDefined(container.declarations, fileSymbolIfFileSymbolExportEqualsContainer);
const reexportContainers = enclosingDeclaration && getAlternativeContainingModules(symbol, enclosingDeclaration);
const objectLiteralContainer = getVariableDeclarationOfObjectLiteral(container, meaning);
if (enclosingDeclaration && getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)) {
if (
enclosingDeclaration &&
container.flags & getQualifiedLeftMeaning(meaning) &&
getAccessibleSymbolChain(container, enclosingDeclaration, SymbolFlags.Namespace, /*externalOnly*/ false)
) {
return append(concatenate(concatenate([container], additionalContainers), reexportContainers), objectLiteralContainer); // This order expresses a preference for the real container if it is in scope
}
const res = append(append(additionalContainers, container), objectLiteralContainer);
return concatenate(res, reexportContainers);
// 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
// which may be acting like a namespace (eg, `Symbol` acts like a namespace when looking up `Symbol.toStringTag`)
const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning))
&& container.flags & SymbolFlags.Type
&& getDeclaredTypeOfSymbol(container).flags & TypeFlags.Object
&& meaning === SymbolFlags.Value
? forEachSymbolTableInScope(enclosingDeclaration, t => {
return forEachEntry(t, s => {
if (s.flags & getQualifiedLeftMeaning(meaning) && getTypeOfSymbol(s) === getDeclaredTypeOfSymbol(container)) {
return s;
}
});
}) : undefined;
let res = firstVariableMatch ? [firstVariableMatch, ...additionalContainers, container] : [...additionalContainers, container];
res = append(res, objectLiteralContainer);
res = addRange(res, reexportContainers);
return res;
}
const candidates = mapDefined(symbol.declarations, d => {
if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) {
Expand Down Expand Up @@ -5107,8 +5127,9 @@ namespace ts {
}
}
}
context.enclosingDeclaration = saveEnclosingDeclaration;
context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration;
const propertyName = getPropertyNameNodeForSymbol(propertySymbol, context);
context.enclosingDeclaration = saveEnclosingDeclaration;
context.approximateLength += (symbolName(propertySymbol).length + 1);
const optionalToken = propertySymbol.flags & SymbolFlags.Optional ? factory.createToken(SyntaxKind.QuestionToken) : undefined;
if (propertySymbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(propertyType).length && !isReadonlySymbol(propertySymbol)) {
Expand Down Expand Up @@ -5852,9 +5873,6 @@ namespace ts {
if (fromNameType) {
return fromNameType;
}
if (isKnownSymbol(symbol)) {
return factory.createComputedPropertyName(factory.createPropertyAccessExpression(factory.createIdentifier("Symbol"), (symbol.escapedName as string).substr(3)));
}
const rawName = unescapeLeadingUnderscores(symbol.escapedName);
const stringNamed = !!length(symbol.declarations) && every(symbol.declarations, isStringNamed);
return createPropertyNameNodeForIdentifierOrLiteral(rawName, stringNamed, singleQuote);
Expand Down Expand Up @@ -8707,8 +8725,18 @@ namespace ts {
return widenTypeForVariableLikeDeclaration(getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true), declaration, reportErrors);
}

function isGlobalSymbolConstructor(node: Node) {
const symbol = getSymbolOfNode(node);
const globalSymbol = getGlobalESSymbolConstructorTypeSymbol(/*reportErrors*/ false);
return globalSymbol && symbol && symbol === globalSymbol;
}

function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) {
if (type) {
// 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`
if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) {
type = getESSymbolLikeTypeForNode(declaration);
}
if (reportErrors) {
reportErrorsFromWidening(declaration, type);
}
Expand Down Expand Up @@ -12859,6 +12887,10 @@ namespace ts {
return deferredGlobalESSymbolConstructorSymbol || (deferredGlobalESSymbolConstructorSymbol = getGlobalValueSymbol("Symbol" as __String, reportErrors));
}

function getGlobalESSymbolConstructorTypeSymbol(reportErrors: boolean) {
return deferredGlobalESSymbolConstructorTypeSymbol || (deferredGlobalESSymbolConstructorTypeSymbol = getGlobalTypeSymbol("SymbolConstructor" as __String, reportErrors));
}

function getGlobalESSymbolType(reportErrors: boolean) {
return deferredGlobalESSymbolType || (deferredGlobalESSymbolType = getGlobalType("Symbol" as __String, /*arity*/ 0, reportErrors)) || emptyObjectType;
}
Expand Down Expand Up @@ -13941,13 +13973,13 @@ namespace ts {
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags) {
if (!(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
if (!type && !isKnownSymbol(prop)) {
if (!type) {
if (prop.escapedName === InternalSymbolName.Default) {
type = getLiteralType("default");
}
else {
const name = prop.valueDeclaration && getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
type = name && getLiteralTypeFromPropertyName(name) || getLiteralType(symbolName(prop));
type = name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getLiteralType(symbolName(prop)) : undefined);
}
}
if (type && type.flags & include) {
Expand Down Expand Up @@ -14174,11 +14206,8 @@ namespace ts {
}

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

return links.resolvedType;
Expand Down Expand Up @@ -25342,15 +25368,15 @@ namespace ts {
// As otherwise they may not be checked until exports for the type at this position are retrieved,
// which may never occur.
for (const elem of node.properties) {
if (elem.name && isComputedPropertyName(elem.name) && !isWellKnownSymbolSyntactically(elem.name)) {
if (elem.name && isComputedPropertyName(elem.name)) {
checkComputedPropertyName(elem.name);
}
}

let offset = 0;
for (const memberDecl of node.properties) {
let member = getSymbolOfNode(memberDecl);
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName && !isWellKnownSymbolSyntactically(memberDecl.name.expression) ?
const computedNameType = memberDecl.name && memberDecl.name.kind === SyntaxKind.ComputedPropertyName ?
checkComputedPropertyName(memberDecl.name) : undefined;
if (memberDecl.kind === SyntaxKind.PropertyAssignment ||
memberDecl.kind === SyntaxKind.ShorthandPropertyAssignment ||
Expand Down Expand Up @@ -27006,48 +27032,6 @@ namespace ts {
return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node);
}

function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean {
if (expressionType === errorType) {
// There is already an error, so no need to report one.
return false;
}

if (!isWellKnownSymbolSyntactically(expression)) {
return false;
}

// Make sure the property type is the primitive symbol type
if ((expressionType.flags & TypeFlags.ESSymbolLike) === 0) {
if (reportError) {
error(expression, Diagnostics.A_computed_property_name_of_the_form_0_must_be_of_type_symbol, getTextOfNode(expression));
}
return false;
}

// The name is Symbol.<someName>, so make sure Symbol actually resolves to the
// global Symbol object
const leftHandSide = <Identifier>(<PropertyAccessExpression>expression).expression;
const leftHandSideSymbol = getResolvedSymbol(leftHandSide);
if (!leftHandSideSymbol) {
return false;
}

const globalESSymbol = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ true);
if (!globalESSymbol) {
// Already errored when we tried to look up the symbol
return false;
}

if (leftHandSideSymbol !== globalESSymbol) {
if (reportError) {
error(leftHandSide, Diagnostics.Symbol_reference_does_not_refer_to_the_global_Symbol_constructor_object);
}
return false;
}

return true;
}

function callLikeExpressionMayHaveTypeArguments(node: CallLikeExpression): node is CallExpression | NewExpression | TaggedTemplateExpression | JsxOpeningElement {
return isCallOrNewExpression(node) || isTaggedTemplateExpression(node) || isJsxOpeningLikeElement(node);
}
Expand Down Expand Up @@ -35355,6 +35339,12 @@ namespace ts {
}
}

function getPropertyNameForKnownSymbolName(symbolName: string): __String {
const ctorType = getGlobalESSymbolConstructorSymbol(/*reportErrors*/ false);
const uniqueType = ctorType && getTypeOfPropertyOfType(getTypeOfSymbol(ctorType), escapeLeadingUnderscores(symbolName));
return uniqueType && isTypeUsableAsPropertyName(uniqueType) ? getPropertyNameFromType(uniqueType) : `__@${symbolName}` as __String;
}

/**
* Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like
* type from its members.
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/transformers/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,7 @@ namespace ts {
* any such locations
*/
export function isSimpleInlineableExpression(expression: Expression) {
return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
isWellKnownSymbolSyntactically(expression);
return !isIdentifier(expression) && isSimpleCopiableExpression(expression);
}

export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator {
Expand Down
8 changes: 1 addition & 7 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2307,12 +2307,6 @@ namespace ts {
| CallChainRoot
;

/** @internal */
export interface WellKnownSymbolExpression extends PropertyAccessExpression {
readonly expression: Identifier & { readonly escapedText: __String & "Symbol" };
readonly name: Identifier;
}

/** @internal */
export type BindableObjectDefinePropertyCall = CallExpression & {
readonly arguments: readonly [BindableStaticNameExpression, StringLiteralLike | NumericLiteral, ObjectLiteralExpression] & Readonly<TextRange>;
Expand All @@ -2326,7 +2320,7 @@ namespace ts {

/** @internal */
export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & {
readonly argumentExpression: StringLiteralLike | NumericLiteral | WellKnownSymbolExpression;
readonly argumentExpression: StringLiteralLike | NumericLiteral;
};

/** @internal */
Expand Down
31 changes: 3 additions & 28 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,9 +2241,7 @@ namespace ts {

/** x[0] OR x['a'] OR x[Symbol.y] */
export function isLiteralLikeElementAccess(node: Node): node is LiteralLikeElementAccessExpression {
return isElementAccessExpression(node) && (
isStringOrNumericLiteralLike(node.argumentExpression) ||
isWellKnownSymbolSyntactically(node.argumentExpression));
return isElementAccessExpression(node) && isStringOrNumericLiteralLike(node.argumentExpression);
}

/** Any series of property and element accesses. */
Expand Down Expand Up @@ -2328,9 +2326,6 @@ namespace ts {
return escapeLeadingUnderscores(name.text);
}
}
if (isElementAccessExpression(node) && isWellKnownSymbolSyntactically(node.argumentExpression)) {
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>node.argumentExpression).name));
}
return undefined;
}

Expand Down Expand Up @@ -3139,9 +3134,6 @@ namespace ts {
* 3. The computed name is *not* expressed as a NumericLiteral.
* 4. The computed name is *not* expressed as a PlusToken or MinusToken
* immediately followed by a NumericLiteral.
* 5. The computed name is *not* expressed as `Symbol.<name>`, where `<name>`
* is a property of the Symbol constructor that denotes a built-in
* Symbol.
*/
export function hasDynamicName(declaration: Declaration): declaration is DynamicNamedDeclaration | DynamicNamedBinaryExpression {
const name = getNameOfDeclaration(declaration);
Expand All @@ -3154,17 +3146,7 @@ namespace ts {
}
const expr = isElementAccessExpression(name) ? skipParentheses(name.argumentExpression) : name.expression;
return !isStringOrNumericLiteralLike(expr) &&
!isSignedNumericLiteral(expr) &&
!isWellKnownSymbolSyntactically(expr);
}

/**
* Checks if the expression is of the form:
* Symbol.name
* where Symbol is literally the word "Symbol", and name is any identifierName
*/
export function isWellKnownSymbolSyntactically(node: Node): node is WellKnownSymbolExpression {
return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression);
!isSignedNumericLiteral(expr);
}

export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined {
Expand All @@ -3177,10 +3159,7 @@ namespace ts {
return escapeLeadingUnderscores(name.text);
case SyntaxKind.ComputedPropertyName:
const nameExpression = name.expression;
if (isWellKnownSymbolSyntactically(nameExpression)) {
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
else if (isStringOrNumericLiteralLike(nameExpression)) {
if (isStringOrNumericLiteralLike(nameExpression)) {
return escapeLeadingUnderscores(nameExpression.text);
}
else if (isSignedNumericLiteral(nameExpression)) {
Expand Down Expand Up @@ -3218,10 +3197,6 @@ namespace ts {
return `__@${getSymbolId(symbol)}@${symbol.escapedName}` as __String;
}

export function getPropertyNameForKnownSymbolName(symbolName: string): __String {
return "__@" + symbolName as __String;
}

export function getSymbolNameForPrivateIdentifier(containingClassSymbol: Symbol, description: __String): __String {
return `__#${getSymbolId(containingClassSymbol)}@${description}` as __String;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/es2015.iterable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface SymbolConstructor {
* A method that returns the default iterator for an object. Called by the semantics of the
* for-of statement.
*/
readonly iterator: symbol;
readonly iterator: unique symbol;
}

interface IteratorYieldResult<TYield> {
Expand Down
Loading

0 comments on commit 87d10eb

Please sign in to comment.