-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Eliminate well known symbols as a concept in the checker and rely on unique symbols #42543
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
Changes from 4 commits
31dc466
a3fc74e
bd49b81
4c808c4
ebac382
b954507
e99c05a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,25 @@ 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 | ||
| } | ||
| // we potentially a symbol which is a member of the instance side of something - look for a variable in scope with the container's type | ||
|
weswigham marked this conversation as resolved.
Outdated
|
||
| // which may be acting like a namespace | ||
|
weswigham marked this conversation as resolved.
Outdated
|
||
| const firstVariableMatch = !(container.flags & getQualifiedLeftMeaning(meaning)) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll call this out since it's a new bit compared to the older PR. This bit and the following modify our symbol container lookup to prefer, eg, |
||
| && 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; | ||
| const res = append(append(additionalContainers, container), objectLiteralContainer); | ||
| return concatenate(res, reexportContainers); | ||
| const resWithReexports = concatenate(res, reexportContainers); | ||
| return firstVariableMatch ? [firstVariableMatch, ...resWithReexports] : resWithReexports; | ||
|
weswigham marked this conversation as resolved.
Outdated
|
||
| } | ||
| const candidates = mapDefined(symbol.declarations, d => { | ||
| if (!isAmbientModule(d) && d.parent && hasNonGlobalAugmentationExternalModuleSymbol(d.parent)) { | ||
|
|
@@ -5104,8 +5119,9 @@ namespace ts { | |
| } | ||
| } | ||
| } | ||
| context.enclosingDeclaration = saveEnclosingDeclaration; | ||
| context.enclosingDeclaration = propertySymbol.valueDeclaration || propertySymbol.declarations?.[0] || saveEnclosingDeclaration; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we don't set a specific
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's an example where
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| 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)) { | ||
|
|
@@ -5849,9 +5865,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); | ||
|
|
@@ -8704,8 +8717,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: Remove the following SymbolConstructor special case when back compat with pre-3.0 libs isn't required | ||
|
weswigham marked this conversation as resolved.
Outdated
|
||
| if (type.flags & TypeFlags.ESSymbol && isGlobalSymbolConstructor(declaration.parent)) { | ||
| type = getESSymbolLikeTypeForNode(declaration); | ||
| } | ||
| if (reportErrors) { | ||
| reportErrorsFromWidening(declaration, type); | ||
| } | ||
|
|
@@ -12835,6 +12858,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; | ||
| } | ||
|
|
@@ -13919,13 +13946,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); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps confusingly,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It won't be confusing once well-known symbols are gone as a concept, right? Seems fine to me. |
||
| } | ||
| } | ||
| if (type && type.flags & include) { | ||
|
|
@@ -14153,11 +14180,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) : | ||
|
|
@@ -25151,9 +25175,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; | ||
|
|
@@ -25214,15 +25235,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 || | ||
|
|
@@ -25321,7 +25342,10 @@ namespace ts { | |
| } | ||
|
|
||
| if (computedNameType && !(computedNameType.flags & TypeFlags.StringOrNumberLiteralOrUnique)) { | ||
| if (isTypeAssignableTo(computedNameType, stringNumberSymbolType)) { | ||
| if (isTypeAny(computedNameType)) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically, I can push this change through without this change, if we'd prefer. However, that means when you write, eg const obj = {
[Symbol.whatever]: 0
};
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From the comment here, it sounds like this mostly to cater to the "degenerate" case of That does loosen the behavior of this by quite a bit. I think I would be willing to go the opposite way and take a break on this behavior.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You definitely don't wanna go the opposite way, because the numeric index signature we otherwise make makes very little sense, IMO. I actually think
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I get that, but I think the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ???? That flag is about accessing keys and the types accessed keys are assigned, where here the issue is solely weather or not we make a key at all, and what slot that key should go in; that seems wholly unrelated to that flag.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But const s = {["str" as any]: "str"}can now be indexed by a wider variety of nonsense that would never work either, like
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And in fact, I think that's why the numeric index signature was chosen - because at the time we also felt the behavior it supported was more desirable even if it wasn't good. The point of the numeric index signature isn't to support indexing with
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the point of
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At best this isn't something we need to change with this feature. I don't find the "well known symbol that doesn't exist" thing to be a use-case that should fundamentally change what happens when you use
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alright 🤷♂️ |
||
| 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; | ||
| } | ||
|
|
@@ -26879,48 +26903,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); | ||
| } | ||
|
|
@@ -35198,6 +35180,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; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you give a little more context on the significance? Are you saying this is handled well in the existing code, that other code needs to be careful, that there's a fundamental architectural issue, or that this now has a user-facing impact? (or all?)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm saying that properties with a name like that can no longer be constructed, so when the proper name cannot be found, returning a name like that will always make a follow-up property lookup fail, which has the desired outcome. |
||
| } | ||
|
|
||
| /** | ||
| * Gets the *yield*, *return*, and *next* types of an `Iterable`-like or `AsyncIterable`-like | ||
| * type from its members. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change is pretty much single-handedly responsible for the .js emit changes in this PR.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @rbuckton is probably the best judge of that. |
||
| } | ||
|
|
||
| export function isCompoundAssignment(kind: BinaryOperator): kind is CompoundAssignmentOperator { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.