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
This commit is contained in:
Wesley Wigham
2021-02-22 14:43:28 -08:00
committed by GitHub
parent 5a1d30815b
commit 87d10eb055
235 changed files with 1230 additions and 1022 deletions

View File

@@ -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

View File

@@ -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;
@@ -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)) {
@@ -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)) {
@@ -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);
@@ -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);
}
@@ -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;
}
@@ -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) {
@@ -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) :
@@ -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;
@@ -25342,7 +25368,7 @@ 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);
}
}
@@ -25350,7 +25376,7 @@ namespace ts {
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 ||
@@ -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);
}
@@ -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.

View File

@@ -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 {

View File

@@ -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>;
@@ -2326,7 +2320,7 @@ namespace ts {
/** @internal */
export type LiteralLikeElementAccessExpression = ElementAccessExpression & Declaration & {
readonly argumentExpression: StringLiteralLike | NumericLiteral | WellKnownSymbolExpression;
readonly argumentExpression: StringLiteralLike | NumericLiteral;
};
/** @internal */

View File

@@ -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. */
@@ -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;
}
@@ -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);
@@ -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 {
@@ -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)) {
@@ -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;
}

View File

@@ -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> {

View File

@@ -5,61 +5,61 @@ interface SymbolConstructor {
* A method that determines if a constructor object recognizes an object as one of the
* constructors 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 {

View File

@@ -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, TReturn = any, TNext = undefined> {

View File

@@ -6,7 +6,7 @@ interface SymbolConstructor {
* A regular expression method that matches the regular expression against a string. Called
* by the String.prototype.matchAll method.
*/
readonly matchAll: symbol;
readonly matchAll: unique symbol;
}
interface RegExp {

View File

@@ -136,7 +136,7 @@ namespace ts.NavigationBar {
for (let i = 0; i < depth; i++) endNode();
}
function startNestedNodes(targetNode: Node, entityName: BindableStaticNameExpression) {
const names: (PropertyNameLiteral | WellKnownSymbolExpression)[] = [];
const names: PropertyNameLiteral[] = [];
while (!isPropertyNameLiteral(entityName)) {
const name = getNameOrArgument(entityName);
const nameText = getElementOrPropertyAccessName(entityName);
@@ -194,6 +194,21 @@ namespace ts.NavigationBar {
}
}
/**
* Historically, we've elided dynamic names from the nav tree (including late bound names),
* but included certain "well known" symbol names. While we no longer distinguish those well-known
* symbols from other unique symbols, we do the below to retain those members in the nav tree.
*/
function hasNavigationBarName(node: Declaration) {
return !hasDynamicName(node) ||
(
node.kind !== SyntaxKind.BinaryExpression &&
isPropertyAccessExpression(node.name.expression) &&
isIdentifier(node.name.expression.expression) &&
idText(node.name.expression.expression) === "Symbol"
);
}
/** Look for navigation bar items in node's subtree, adding them to the current `parent`. */
function addChildrenRecursively(node: Node | undefined): void {
curCancellationToken.throwIfCancellationRequested();
@@ -220,18 +235,18 @@ namespace ts.NavigationBar {
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.MethodSignature:
if (!hasDynamicName((<ClassElement | TypeElement>node))) {
if (hasNavigationBarName((<ClassElement | TypeElement>node))) {
addNodeWithRecursiveChild(node, (<FunctionLikeDeclaration>node).body);
}
break;
case SyntaxKind.PropertyDeclaration:
if (!hasDynamicName(<ClassElement>node)) {
if (hasNavigationBarName(<ClassElement>node)) {
addNodeWithRecursiveInitializer(<PropertyDeclaration>node);
}
break;
case SyntaxKind.PropertySignature:
if (!hasDynamicName(<TypeElement>node)) {
if (hasNavigationBarName(<TypeElement>node)) {
addLeafNode(node);
}
break;
@@ -358,7 +373,7 @@ namespace ts.NavigationBar {
assignmentTarget;
let depth = 0;
let className: PropertyNameLiteral | WellKnownSymbolExpression;
let className: PropertyNameLiteral;
// If we see a prototype assignment, start tracking the target as a class
// This is only done for simple classes not nested assignments.
if (isIdentifier(prototypeAccess.expression)) {