-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Use brand types to clear up confusion about entity name expressions #10013
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
97bbbd7
f9fd496
c50ccbf
91c9d76
db44a71
dc19223
6814a9f
7908257
5b9bd82
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 |
|---|---|---|
|
|
@@ -968,29 +968,37 @@ namespace ts { | |
|
|
||
|
|
||
| function checkAndReportErrorForExtendingInterface(errorLocation: Node): boolean { | ||
| let parentClassExpression = errorLocation; | ||
| while (parentClassExpression) { | ||
| const kind = parentClassExpression.kind; | ||
| if (kind === SyntaxKind.Identifier || kind === SyntaxKind.PropertyAccessExpression) { | ||
| parentClassExpression = parentClassExpression.parent; | ||
| continue; | ||
| } | ||
| if (kind === SyntaxKind.ExpressionWithTypeArguments) { | ||
| break; | ||
| } | ||
| return false; | ||
| } | ||
| if (!parentClassExpression) { | ||
| return false; | ||
| } | ||
| const expression = (<ExpressionWithTypeArguments>parentClassExpression).expression; | ||
| if (resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)) { | ||
| const expression = climbToEntityNameOfExpressionWithTypeArguments(errorLocation); | ||
| const isError = !!(expression && resolveEntityName(expression, SymbolFlags.Interface, /*ignoreErrors*/ true)); | ||
| if (isError) { | ||
| error(errorLocation, Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements, getTextOfNode(expression)); | ||
| return true; | ||
| } | ||
| return false; | ||
| return isError; | ||
| } | ||
| /** | ||
| * Climbs up parents to an ExpressionWithTypeArguments, and returns its expression, | ||
| * but returns undefined if that expression is not an EntityNameExpression. | ||
| */ | ||
| function climbToEntityNameOfExpressionWithTypeArguments(node: Node): EntityNameExpression | undefined { | ||
|
||
| switch (node.kind) { | ||
|
||
| case SyntaxKind.Identifier: | ||
| case SyntaxKind.PropertyAccessExpression: | ||
| if (node.parent) { | ||
| node = node.parent; | ||
| } | ||
| else { | ||
| return undefined; | ||
| } | ||
| break; | ||
| case SyntaxKind.ExpressionWithTypeArguments: | ||
| Debug.assert(isEntityNameExpression((<ExpressionWithTypeArguments>node).expression)); | ||
| return <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression; | ||
| default: | ||
| return undefined; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| function checkResolvedBlockScopedVariable(result: Symbol, errorLocation: Node): void { | ||
| Debug.assert((result.flags & SymbolFlags.BlockScopedVariable) !== 0); | ||
| // Block-scoped variables cannot be used before their definition | ||
|
|
@@ -1033,7 +1041,7 @@ namespace ts { | |
| } | ||
|
|
||
| function getDeclarationOfAliasSymbol(symbol: Symbol): Declaration { | ||
| return forEach(symbol.declarations, d => isAliasSymbolDeclaration(d) ? d : undefined); | ||
| return find(symbol.declarations, d => isAliasSymbolDeclaration(d) ? d : undefined); | ||
| } | ||
|
|
||
| function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration): Symbol { | ||
|
|
@@ -1164,7 +1172,7 @@ namespace ts { | |
| } | ||
|
|
||
| function getTargetOfExportAssignment(node: ExportAssignment): Symbol { | ||
| return resolveEntityName(<Identifier>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace); | ||
| return resolveEntityName(<EntityNameExpression>node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace); | ||
| } | ||
|
|
||
| function getTargetOfAliasDeclaration(node: Declaration): Symbol { | ||
|
|
@@ -1274,7 +1282,7 @@ namespace ts { | |
| } | ||
|
|
||
| // Resolves a qualified name and any involved aliases | ||
| function resolveEntityName(name: EntityName | Expression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol { | ||
| function resolveEntityName(name: EntityNameOrEntityNameExpression, meaning: SymbolFlags, ignoreErrors?: boolean, dontResolveAlias?: boolean): Symbol | undefined { | ||
| if (nodeIsMissing(name)) { | ||
| return undefined; | ||
| } | ||
|
|
@@ -1289,7 +1297,7 @@ namespace ts { | |
| } | ||
| } | ||
| else if (name.kind === SyntaxKind.QualifiedName || name.kind === SyntaxKind.PropertyAccessExpression) { | ||
| const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessExpression>name).expression; | ||
| const left = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).left : (<PropertyAccessEntityNameExpression>name).expression; | ||
| const right = name.kind === SyntaxKind.QualifiedName ? (<QualifiedName>name).right : (<PropertyAccessExpression>name).name; | ||
|
|
||
| const namespace = resolveEntityName(left, SymbolFlags.Namespace, ignoreErrors); | ||
|
|
@@ -1845,7 +1853,7 @@ namespace ts { | |
| } | ||
| } | ||
|
|
||
| function isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult { | ||
| function isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult { | ||
| // get symbol of the first identifier of the entityName | ||
| let meaning: SymbolFlags; | ||
| if (entityName.parent.kind === SyntaxKind.TypeQuery || isExpressionWithTypeArgumentsInClassExtendsClause(entityName.parent)) { | ||
|
|
@@ -3675,7 +3683,7 @@ namespace ts { | |
| const baseTypeNodes = getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration); | ||
| if (baseTypeNodes) { | ||
| for (const node of baseTypeNodes) { | ||
| if (isSupportedExpressionWithTypeArguments(node)) { | ||
| if (isEntityNameExpression(node.expression)) { | ||
| const baseSymbol = resolveEntityName(node.expression, SymbolFlags.Type, /*ignoreErrors*/ true); | ||
| if (!baseSymbol || !(baseSymbol.flags & SymbolFlags.Interface) || getDeclaredTypeOfClassOrInterface(baseSymbol).thisType) { | ||
| return false; | ||
|
|
@@ -5022,7 +5030,7 @@ namespace ts { | |
| return getDeclaredTypeOfSymbol(symbol); | ||
| } | ||
|
|
||
| function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): LeftHandSideExpression | EntityName { | ||
| function getTypeReferenceName(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference): EntityNameOrEntityNameExpression | undefined { | ||
| switch (node.kind) { | ||
| case SyntaxKind.TypeReference: | ||
| return (<TypeReferenceNode>node).typeName; | ||
|
|
@@ -5031,8 +5039,9 @@ namespace ts { | |
| case SyntaxKind.ExpressionWithTypeArguments: | ||
| // We only support expressions that are simple qualified names. For other | ||
| // expressions this produces undefined. | ||
| if (isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node)) { | ||
| return (<ExpressionWithTypeArguments>node).expression; | ||
| const expr = (<ExpressionWithTypeArguments>node).expression; | ||
| if (isEntityNameExpression(expr)) { | ||
| return expr; | ||
| } | ||
|
|
||
| // fall through; | ||
|
|
@@ -5043,7 +5052,7 @@ namespace ts { | |
|
|
||
| function resolveTypeReferenceName( | ||
| node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, | ||
| typeReferenceName: LeftHandSideExpression | EntityName) { | ||
| typeReferenceName: EntityNameExpression | EntityName) { | ||
|
|
||
| if (!typeReferenceName) { | ||
| return unknownSymbol; | ||
|
|
@@ -5084,15 +5093,14 @@ namespace ts { | |
| const typeReferenceName = getTypeReferenceName(node); | ||
| symbol = resolveTypeReferenceName(node, typeReferenceName); | ||
| type = getTypeReferenceType(node, symbol); | ||
|
|
||
| links.resolvedSymbol = symbol; | ||
| links.resolvedType = type; | ||
| } | ||
| else { | ||
| // We only support expressions that are simple qualified names. For other expressions this produces undefined. | ||
| const typeNameOrExpression = node.kind === SyntaxKind.TypeReference ? (<TypeReferenceNode>node).typeName : | ||
| isSupportedExpressionWithTypeArguments(<ExpressionWithTypeArguments>node) ? (<ExpressionWithTypeArguments>node).expression : | ||
| undefined; | ||
| const typeNameOrExpression: EntityNameOrEntityNameExpression = node.kind === SyntaxKind.TypeReference | ||
| ? (<TypeReferenceNode>node).typeName | ||
| : isEntityNameExpression((<ExpressionWithTypeArguments>node).expression) | ||
| ? <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression | ||
| : undefined; | ||
| symbol = typeNameOrExpression && resolveEntityName(typeNameOrExpression, SymbolFlags.Type) || unknownSymbol; | ||
| type = symbol === unknownSymbol ? unknownType : | ||
| symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) ? getTypeFromClassOrInterfaceReference(node, symbol) : | ||
|
|
@@ -16244,7 +16252,7 @@ namespace ts { | |
| const implementedTypeNodes = getClassImplementsHeritageClauseElements(node); | ||
| if (implementedTypeNodes) { | ||
| for (const typeRefNode of implementedTypeNodes) { | ||
| if (!isSupportedExpressionWithTypeArguments(typeRefNode)) { | ||
| if (!isEntityNameExpression(typeRefNode.expression)) { | ||
| error(typeRefNode.expression, Diagnostics.A_class_can_only_implement_an_identifier_Slashqualified_name_with_optional_type_arguments); | ||
| } | ||
| checkTypeReferenceNode(typeRefNode); | ||
|
|
@@ -16486,7 +16494,7 @@ namespace ts { | |
| checkObjectTypeForDuplicateDeclarations(node); | ||
| } | ||
| forEach(getInterfaceBaseTypeNodes(node), heritageElement => { | ||
| if (!isSupportedExpressionWithTypeArguments(heritageElement)) { | ||
| if (!isEntityNameExpression(heritageElement.expression)) { | ||
| error(heritageElement.expression, Diagnostics.An_interface_can_only_extend_an_identifier_Slashqualified_name_with_optional_type_arguments); | ||
| } | ||
| checkTypeReferenceNode(heritageElement); | ||
|
|
@@ -16951,20 +16959,21 @@ namespace ts { | |
| } | ||
| } | ||
|
|
||
| function getFirstIdentifier(node: EntityName | Expression): Identifier { | ||
| while (true) { | ||
| if (node.kind === SyntaxKind.QualifiedName) { | ||
| node = (<QualifiedName>node).left; | ||
| } | ||
| else if (node.kind === SyntaxKind.PropertyAccessExpression) { | ||
| node = (<PropertyAccessExpression>node).expression; | ||
| } | ||
| else { | ||
| break; | ||
| } | ||
| function getFirstIdentifier(node: EntityNameOrEntityNameExpression): Identifier { | ||
| switch (node.kind) { | ||
| case SyntaxKind.Identifier: | ||
| return <Identifier>node; | ||
| case SyntaxKind.QualifiedName: | ||
| do { | ||
| node = (<QualifiedName>node).left; | ||
| } while (node.kind !== SyntaxKind.Identifier); | ||
| return <Identifier>node; | ||
| case SyntaxKind.PropertyAccessExpression: | ||
| do { | ||
| node = (<PropertyAccessEntityNameExpression>node).expression; | ||
| } while (node.kind !== SyntaxKind.Identifier); | ||
| return <Identifier>node; | ||
| } | ||
| Debug.assert(node.kind === SyntaxKind.Identifier); | ||
| return <Identifier>node; | ||
| } | ||
|
|
||
| function checkExternalImportOrExportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration): boolean { | ||
|
|
@@ -17663,7 +17672,7 @@ namespace ts { | |
| return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; | ||
| } | ||
|
|
||
| function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol { | ||
| function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined { | ||
| if (isDeclarationName(entityName)) { | ||
| return getSymbolOfNode(entityName.parent); | ||
| } | ||
|
|
@@ -17682,22 +17691,20 @@ namespace ts { | |
| } | ||
| } | ||
|
|
||
| if (entityName.parent.kind === SyntaxKind.ExportAssignment) { | ||
| return resolveEntityName(<Identifier>entityName, | ||
| if (entityName.parent.kind === SyntaxKind.ExportAssignment && isEntityNameExpression(<Identifier | PropertyAccessExpression>entityName)) { | ||
| return resolveEntityName(<EntityNameExpression>entityName, | ||
| /*all meanings*/ SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace | SymbolFlags.Alias); | ||
| } | ||
|
|
||
| if (entityName.kind !== SyntaxKind.PropertyAccessExpression) { | ||
| if (isInRightSideOfImportOrExportAssignment(<EntityName>entityName)) { | ||
| // Since we already checked for ExportAssignment, this really could only be an Import | ||
| const importEqualsDeclaration = <ImportEqualsDeclaration>getAncestor(entityName, SyntaxKind.ImportEqualsDeclaration); | ||
| Debug.assert(importEqualsDeclaration !== undefined); | ||
| return getSymbolOfPartOfRightHandSideOfImportEquals(<EntityName>entityName, importEqualsDeclaration, /*dontResolveAlias*/ true); | ||
| } | ||
| if (entityName.kind !== SyntaxKind.PropertyAccessExpression && isInRightSideOfImportOrExportAssignment(<EntityName>entityName)) { | ||
| // Since we already checked for ExportAssignment, this really could only be an Import | ||
| const importEqualsDeclaration = <ImportEqualsDeclaration>getAncestor(entityName, SyntaxKind.ImportEqualsDeclaration); | ||
| Debug.assert(importEqualsDeclaration !== undefined); | ||
| return getSymbolOfPartOfRightHandSideOfImportEquals(<EntityName>entityName, importEqualsDeclaration, /*dontResolveAlias*/ true); | ||
| } | ||
|
|
||
| if (isRightSideOfQualifiedNameOrPropertyAccess(entityName)) { | ||
| entityName = <QualifiedName | PropertyAccessExpression>entityName.parent; | ||
| entityName = <QualifiedName | PropertyAccessEntityNameExpression>entityName.parent; | ||
| } | ||
|
|
||
| if (isHeritageClauseElementIdentifier(<EntityName>entityName)) { | ||
|
|
@@ -18410,7 +18417,7 @@ namespace ts { | |
| }; | ||
|
|
||
| // defined here to avoid outer scope pollution | ||
| function getTypeReferenceDirectivesForEntityName(node: EntityName | PropertyAccessExpression): string[] { | ||
| function getTypeReferenceDirectivesForEntityName(node: EntityNameOrEntityNameExpression): string[] { | ||
| // program does not have any files with type reference directives - bail out | ||
| if (!fileToDirective) { | ||
| return undefined; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these actually for default clauses, or just for
export =?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export defaultandexport =are both instances ofExportAssignment.