Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expunge the concept of well-known symbols from the checker #24738

Closed
wants to merge 10 commits into from
6 changes: 3 additions & 3 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,9 +262,9 @@ namespace ts {
if (isStringOrNumericLiteral(nameExpression)) {
return escapeLeadingUnderscores(nameExpression.text);
}

Debug.assert(isWellKnownSymbolSyntactically(nameExpression));
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
else {
Debug.fail("Only computed properties with literal names have declaration names");
}
}
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
Expand Down
99 changes: 39 additions & 60 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,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 @@ -3531,9 +3532,11 @@ namespace ts {
context.enclosingDeclaration = undefined;
if (getCheckFlags(propertySymbol) & CheckFlags.Late) {
const decl = first(propertySymbol.declarations);
const name = hasLateBindableName(decl) && resolveEntityName(decl.name.expression, SymbolFlags.Value);
if (name && context.tracker.trackSymbol) {
context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value);
if (hasLateBindableName(decl)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a copy of the primary change from #24668 - it's required to make the symbols get looked up correctly.

const name = checkExpression(decl.name.expression) && getNodeLinks(decl.name.expression).resolvedSymbol;
if (name && context.tracker.trackSymbol) {
context.tracker.trackSymbol(name, saveEnclosingDeclaration, SymbolFlags.Value);
}
}
}
const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true);
Expand Down Expand Up @@ -4439,8 +4442,7 @@ namespace ts {
// Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form)
const name = declaration.propertyName || <Identifier>declaration.name;
const isLate = isLateBindableName(name);
const isWellKnown = isComputedPropertyName(name) && isWellKnownSymbolSyntactically(name.expression);
if (!isLate && !isWellKnown && isComputedNonLiteralName(name)) {
if (!isLate && isComputedNonLiteralName(name)) {
const exprType = checkExpression((name as ComputedPropertyName).expression);
if (isTypeAssignableToKind(exprType, TypeFlags.ESSymbolLike)) {
if (noImplicitAny) {
Expand All @@ -4463,9 +4465,7 @@ namespace ts {
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
// or otherwise the type of the string index signature.
const nameType = isLate ? checkComputedPropertyName(name as ComputedPropertyName) as LiteralType | UniqueESSymbolType : undefined;
const text = isLate ? getLateBoundNameFromType(nameType!) :
isWellKnown ? getPropertyNameForKnownSymbolName(idText(((name as ComputedPropertyName).expression as PropertyAccessExpression).name)) :
getTextOfPropertyName(name);
const text = isLate ? getLateBoundNameFromType(nameType!) : getTextOfPropertyName(name);

// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
if (strictNullChecks && declaration.flags & NodeFlags.Ambient && isParameterDeclaration(declaration)) {
Expand Down Expand Up @@ -4852,6 +4852,12 @@ namespace ts {
: getTypeFromArrayBindingPattern(pattern, includePatternInType, reportErrors);
}

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

// Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type
// specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it
// is a bit more involved. For example:
Expand All @@ -4864,6 +4870,10 @@ namespace ts {
function getWidenedTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, reportErrors?: boolean): Type {
let type = getTypeForVariableLikeDeclaration(declaration, /*includeOptionality*/ true);
if (type) {
// TODO: Remove the following SymbolConstructor special case when back compat with pre-3.0 libs isn't required
if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) {
type = getESSymbolLikeTypeForNode(declaration);
}
if (reportErrors) {
reportErrorsFromWidening(declaration, type);
}
Expand Down Expand Up @@ -8153,6 +8163,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 @@ -8780,10 +8794,7 @@ namespace ts {

function getPropertyTypeForIndexType(objectType: Type, indexType: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode | undefined, cacheSymbol: boolean) {
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
const propName = isTypeUsableAsLateBoundName(indexType) ? getLateBoundNameFromType(indexType) :
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
undefined;
const propName = isTypeUsableAsLateBoundName(indexType) ? getLateBoundNameFromType(indexType) : undefined;
if (propName !== undefined) {
const prop = getPropertyOfType(objectType, propName);
if (prop) {
Expand Down Expand Up @@ -15941,9 +15952,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 @@ -15998,7 +16006,7 @@ namespace ts {
for (let i = 0; i < node.properties.length; i++) {
const memberDecl = node.properties[i];
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 @@ -16088,7 +16096,10 @@ namespace ts {
}

if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) {
if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
if (isTypeAny(computedNameType)) {
hasComputedStringProperty = true; // string is the closest to a catch-all index signature we have
}
else if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) {
if (isTypeAssignableTo(computedNameType, numberType)) {
hasComputedNumberProperty = true;
}
Expand All @@ -16101,6 +16112,10 @@ namespace ts {
}
}
else {
if (computedNameType && propertiesTable.get(member.escapedName) && !isGetOrSetAccessorDeclaration(memberDecl)) {
// The binder issues error for normal duplicate props, but for computed names we must do so here
error(memberDecl.name, Diagnostics.Duplicate_declaration_0, getNameOfSymbolAsWritten(member));
}
propertiesTable.set(member.escapedName, member);
}
propertiesArray.push(member);
Expand Down Expand Up @@ -17540,48 +17555,6 @@ namespace ts {
return checkIndexedAccessIndexType(getIndexedAccessType(objectType, indexType, node), 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 {
// TODO: Also include tagged templates (https://github.com/Microsoft/TypeScript/issues/11947)
return isCallOrNewExpression(node);
Expand Down Expand Up @@ -23633,6 +23606,12 @@ namespace ts {
return arrayElementType;
}

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

/**
* We want to treat type as an iterable, and get the type it is an iterable of. The iterable
* must have the following structure (annotated with the names of the variables below):
Expand Down Expand Up @@ -23855,7 +23834,7 @@ namespace ts {

function isGetAccessorWithAnnotatedSetAccessor(node: SignatureDeclaration) {
return node.kind === SyntaxKind.GetAccessor
&& getEffectiveSetAccessorTypeAnnotationNode(getDeclarationOfKind<SetAccessorDeclaration>(node.symbol, SyntaxKind.SetAccessor)!) !== undefined;
&& getEffectiveSetAccessorTypeAnnotationNode(getDeclarationOfKind<SetAccessorDeclaration>(getSymbolOfNode(node), SyntaxKind.SetAccessor)!) !== undefined;
}

function isUnwrappedReturnTypeVoidOrAny(func: SignatureDeclaration, returnType: Type): boolean {
Expand Down
3 changes: 1 addition & 2 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2097,8 +2097,7 @@ namespace ts {
* any such locations
*/
function isSimpleInlineableExpression(expression: Expression) {
return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
isWellKnownSymbolSyntactically(expression);
return !isIdentifier(expression) && isSimpleCopiableExpression(expression);
}

/**
Expand Down
21 changes: 2 additions & 19 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2396,17 +2396,7 @@ namespace ts {

export function isDynamicName(name: DeclarationName): boolean {
return name.kind === SyntaxKind.ComputedPropertyName &&
!isStringOrNumericLiteral(name.expression) &&
!isWellKnownSymbolSyntactically(name.expression);
}

/**
* 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: Expression): boolean {
return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression);
!isStringOrNumericLiteral(name.expression);
}

export function getPropertyNameForPropertyNameNode(name: DeclarationName): __String | undefined {
Expand All @@ -2418,10 +2408,7 @@ namespace ts {
}
if (name.kind === SyntaxKind.ComputedPropertyName) {
const nameExpression = name.expression;
if (isWellKnownSymbolSyntactically(nameExpression)) {
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
else if (nameExpression.kind === SyntaxKind.StringLiteral || nameExpression.kind === SyntaxKind.NumericLiteral) {
if (nameExpression.kind === SyntaxKind.StringLiteral || nameExpression.kind === SyntaxKind.NumericLiteral) {
return escapeLeadingUnderscores((<LiteralExpression>nameExpression).text);
}
}
Expand Down Expand Up @@ -2449,10 +2436,6 @@ namespace ts {
return node.kind === SyntaxKind.Identifier ? node.escapedText : escapeLeadingUnderscores(node.text);
}

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

export function isKnownSymbol(symbol: Symbol): boolean {
return startsWith(symbol.escapedName as string, "__@");
}
Expand Down
4 changes: 1 addition & 3 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2303,9 +2303,7 @@ Actual: ${stringify(fullActual)}`);

private verifyFileContent(fileName: string, text: string) {
const actual = this.getFileContent(fileName);
if (actual !== text) {
throw new Error(`verifyFileContent failed:\n${showTextDiff(text, actual)}`);
}
assert.equal(actual, text, "verifyFileContent failed");
}

public verifyTextAtCaretIs(text: 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 IteratorResult<T> {
Expand Down
20 changes: 10 additions & 10 deletions src/lib/es2015.symbol.wellknown.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,61 @@ interface SymbolConstructor {
* A method that determines if a constructor object recognizes an object as one of the
* constructor’s instances. Called by the semantics of the instanceof operator.
*/
readonly hasInstance: symbol;
readonly hasInstance: unique symbol;

/**
* A Boolean value that if true indicates that an object should flatten to its array elements
* by Array.prototype.concat.
*/
readonly isConcatSpreadable: symbol;
readonly isConcatSpreadable: unique symbol;

/**
* A regular expression method that matches the regular expression against a string. Called
* by the String.prototype.match method.
*/
readonly match: symbol;
readonly match: unique symbol;

/**
* A regular expression method that replaces matched substrings of a string. Called by the
* String.prototype.replace method.
*/
readonly replace: symbol;
readonly replace: unique symbol;

/**
* A regular expression method that returns the index within a string that matches the
* regular expression. Called by the String.prototype.search method.
*/
readonly search: symbol;
readonly search: unique symbol;

/**
* A function valued property that is the constructor function that is used to create
* derived objects.
*/
readonly species: symbol;
readonly species: unique symbol;

/**
* A regular expression method that splits a string at the indices that match the regular
* expression. Called by the String.prototype.split method.
*/
readonly split: symbol;
readonly split: unique symbol;

/**
* A method that converts an object to a corresponding primitive value.
* Called by the ToPrimitive abstract operation.
*/
readonly toPrimitive: symbol;
readonly toPrimitive: unique symbol;

/**
* A String value that is used in the creation of the default string description of an object.
* Called by the built-in method Object.prototype.toString.
*/
readonly toStringTag: symbol;
readonly toStringTag: unique symbol;

/**
* An Object whose own property names are property names that are excluded from the 'with'
* environment bindings of the associated objects.
*/
readonly unscopables: symbol;
readonly unscopables: unique symbol;
}

interface Symbol {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/esnext.asynciterable.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface SymbolConstructor {
* A method that returns the default async iterator for an object. Called by the semantics of
* the for-await-of statement.
*/
readonly asyncIterator: symbol;
readonly asyncIterator: unique symbol;
}

interface AsyncIterator<T> {
Expand Down
Loading