Skip to content

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

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

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
@@ -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;
}
91 changes: 34 additions & 57 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -452,6 +452,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;
@@ -4420,8 +4421,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) {
@@ -4444,9 +4444,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)) {
@@ -4839,6 +4837,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:
@@ -4854,6 +4858,10 @@ namespace ts {

function widenTypeForVariableLikeDeclaration(type: Type | undefined, declaration: any, reportErrors?: boolean) {
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);
}
@@ -8235,6 +8243,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;
}
@@ -8894,10 +8906,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) {
@@ -16359,9 +16368,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;
@@ -16403,7 +16409,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 ||
@@ -16493,7 +16499,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;
}
@@ -16506,6 +16515,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);
@@ -17980,48 +17993,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);
@@ -24320,6 +24291,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):
@@ -24542,7 +24519,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 {
3 changes: 1 addition & 2 deletions src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
@@ -2108,8 +2108,7 @@ namespace ts {
* any such locations
*/
function isSimpleInlineableExpression(expression: Expression) {
return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
isWellKnownSymbolSyntactically(expression);
return !isIdentifier(expression) && isSimpleCopiableExpression(expression);
}

/**
21 changes: 2 additions & 19 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
@@ -2566,17 +2566,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 {
@@ -2588,10 +2578,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);
}
}
@@ -2619,10 +2606,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, "__@");
}
4 changes: 1 addition & 3 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
@@ -2300,9 +2300,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) {
2 changes: 1 addition & 1 deletion src/lib/es2015.iterable.d.ts
Original file line number Diff line number Diff line change
@@ -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> {
20 changes: 10 additions & 10 deletions src/lib/es2015.symbol.wellknown.d.ts
Original file line number Diff line number Diff line change
@@ -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 {
2 changes: 1 addition & 1 deletion src/lib/esnext.asynciterable.d.ts
Original file line number Diff line number Diff line change
@@ -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> {
11 changes: 4 additions & 7 deletions src/services/navigationBar.ts
Original file line number Diff line number Diff line change
@@ -171,16 +171,12 @@ namespace ts.NavigationBar {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.MethodSignature:
if (!hasDynamicName((<ClassElement | TypeElement>node))) {
addNodeWithRecursiveChild(node, (<FunctionLikeDeclaration>node).body);
}
addNodeWithRecursiveChild(node, (<FunctionLikeDeclaration>node).body);
break;

case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
if (!hasDynamicName((<ClassElement | TypeElement>node))) {
addLeafNode(node);
}
addLeafNode(node);
break;

case SyntaxKind.ImportClause:
@@ -410,7 +406,8 @@ namespace ts.NavigationBar {

const declName = getNameOfDeclaration(<Declaration>node);
if (declName) {
return unescapeLeadingUnderscores(getPropertyNameForPropertyNameNode(declName)!); // TODO: GH#18217
const candidate = getPropertyNameForPropertyNameNode(declName);
return candidate ? unescapeLeadingUnderscores(candidate) : getTextOfNode(declName);
}
switch (node.kind) {
case SyntaxKind.FunctionExpression:
Loading