Skip to content

Commit e900952

Browse files
author
Andy
authored
Merge pull request #10013 from Microsoft/resolve_entity_name
Use brand types to clear up confusion about entity name expressions
2 parents c9f62f3 + 5b9bd82 commit e900952

14 files changed

+705
-103
lines changed

src/compiler/binder.ts

Lines changed: 6 additions & 7 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1887,18 +1887,17 @@ namespace ts {
1887
}
1887
}
1888

1888

1889
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
1889
function bindExportAssignment(node: ExportAssignment | BinaryExpression) {
1890-
const boundExpression = node.kind === SyntaxKind.ExportAssignment ? (<ExportAssignment>node).expression : (<BinaryExpression>node).right;
1891
if (!container.symbol || !container.symbol.exports) {
1890
if (!container.symbol || !container.symbol.exports) {
1892
// Export assignment in some sort of block construct
1891
// Export assignment in some sort of block construct
1893
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
1892
bindAnonymousDeclaration(node, SymbolFlags.Alias, getDeclarationName(node));
1894
}
1893
}
1895-
else if (boundExpression.kind === SyntaxKind.Identifier && node.kind === SyntaxKind.ExportAssignment) {
1896-
// An export default clause with an identifier exports all meanings of that identifier
1897-
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Alias, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
1898-
}
1899
else {
1894
else {
1900-
// An export default clause with an expression exports a value
1895+
const flags = node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node)
1901-
declareSymbol(container.symbol.exports, container.symbol, node, SymbolFlags.Property, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
1896+
// An export default clause with an EntityNameExpression exports all meanings of that identifier
1897+
? SymbolFlags.Alias
1898+
// An export default clause with any other expression exports a value
1899+
: SymbolFlags.Property;
1900+
declareSymbol(container.symbol.exports, container.symbol, node, flags, SymbolFlags.PropertyExcludes | SymbolFlags.AliasExcludes);
1902
}
1901
}
1903
}
1902
}
1904

1903

src/compiler/checker.ts

Lines changed: 67 additions & 65 deletions
Large diffs are not rendered by default.

src/compiler/core.ts

Lines changed: 12 additions & 1 deletion
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -81,7 +81,7 @@ namespace ts {
81
* returns a truthy value, then returns that value.
81
* returns a truthy value, then returns that value.
82
* If no such value is found, the callback is applied to each element of array and undefined is returned.
82
* If no such value is found, the callback is applied to each element of array and undefined is returned.
83
*/
83
*/
84-
export function forEach<T, U>(array: T[], callback: (element: T, index: number) => U): U {
84+
export function forEach<T, U>(array: T[] | undefined, callback: (element: T, index: number) => U | undefined): U | undefined {
85
if (array) {
85
if (array) {
86
for (let i = 0, len = array.length; i < len; i++) {
86
for (let i = 0, len = array.length; i < len; i++) {
87
const result = callback(array[i], i);
87
const result = callback(array[i], i);
@@ -93,6 +93,17 @@ namespace ts {
93
return undefined;
93
return undefined;
94
}
94
}
95

95

96+
/** Like `forEach`, but assumes existence of array and fails if no truthy value is found. */
97+
export function find<T, U>(array: T[], callback: (element: T, index: number) => U | undefined): U {
98+
for (let i = 0, len = array.length; i < len; i++) {
99+
const result = callback(array[i], i);
100+
if (result) {
101+
return result;
102+
}
103+
}
104+
Debug.fail();
105+
}
106+
96
export function contains<T>(array: T[], value: T): boolean {
107
export function contains<T>(array: T[], value: T): boolean {
97
if (array) {
108
if (array) {
98
for (const v of array) {
109
for (const v of array) {

src/compiler/declarationEmitter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -441,7 +441,7 @@ namespace ts {
441
}
441
}
442
}
442
}
443

443

444-
function emitEntityName(entityName: EntityName | PropertyAccessExpression) {
444+
function emitEntityName(entityName: EntityNameOrEntityNameExpression) {
445
const visibilityResult = resolver.isEntityNameVisible(entityName,
445
const visibilityResult = resolver.isEntityNameVisible(entityName,
446
// Aliases can be written asynchronously so use correct enclosing declaration
446
// Aliases can be written asynchronously so use correct enclosing declaration
447
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration ? entityName.parent : enclosingDeclaration);
447
entityName.parent.kind === SyntaxKind.ImportEqualsDeclaration ? entityName.parent : enclosingDeclaration);
@@ -452,9 +452,9 @@ namespace ts {
452
}
452
}
453

453

454
function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) {
454
function emitExpressionWithTypeArguments(node: ExpressionWithTypeArguments) {
455-
if (isSupportedExpressionWithTypeArguments(node)) {
455+
if (isEntityNameExpression(node.expression)) {
456
Debug.assert(node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression);
456
Debug.assert(node.expression.kind === SyntaxKind.Identifier || node.expression.kind === SyntaxKind.PropertyAccessExpression);
457-
emitEntityName(<Identifier | PropertyAccessExpression>node.expression);
457+
emitEntityName(node.expression);
458
if (node.typeArguments) {
458
if (node.typeArguments) {
459
write("<");
459
write("<");
460
emitCommaList(node.typeArguments, emitType);
460
emitCommaList(node.typeArguments, emitType);
@@ -1019,7 +1019,7 @@ namespace ts {
1019
}
1019
}
1020

1020

1021
function emitTypeOfTypeReference(node: ExpressionWithTypeArguments) {
1021
function emitTypeOfTypeReference(node: ExpressionWithTypeArguments) {
1022-
if (isSupportedExpressionWithTypeArguments(node)) {
1022+
if (isEntityNameExpression(node.expression)) {
1023
emitTypeWithNewGetSymbolAccessibilityDiagnostic(node, getHeritageClauseVisibilityError);
1023
emitTypeWithNewGetSymbolAccessibilityDiagnostic(node, getHeritageClauseVisibilityError);
1024
}
1024
}
1025
else if (!isImplementsList && node.expression.kind === SyntaxKind.NullKeyword) {
1025
else if (!isImplementsList && node.expression.kind === SyntaxKind.NullKeyword) {

src/compiler/types.ts

Lines changed: 10 additions & 4 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -982,13 +982,19 @@ namespace ts {
982
multiLine?: boolean;
982
multiLine?: boolean;
983
}
983
}
984

984

985+
export type EntityNameExpression = Identifier | PropertyAccessEntityNameExpression;
986+
export type EntityNameOrEntityNameExpression = EntityName | EntityNameExpression;
987+
985
// @kind(SyntaxKind.PropertyAccessExpression)
988
// @kind(SyntaxKind.PropertyAccessExpression)
986
export interface PropertyAccessExpression extends MemberExpression, Declaration {
989
export interface PropertyAccessExpression extends MemberExpression, Declaration {
987
expression: LeftHandSideExpression;
990
expression: LeftHandSideExpression;
988
name: Identifier;
991
name: Identifier;
989
}
992
}
990-
993+
/** Brand for a PropertyAccessExpression which, like a QualifiedName, consists of a sequence of identifiers separated by dots. */
991-
export type IdentifierOrPropertyAccess = Identifier | PropertyAccessExpression;
994+
export interface PropertyAccessEntityNameExpression extends PropertyAccessExpression {
995+
_propertyAccessExpressionLikeQualifiedNameBrand?: any;
996+
expression: EntityNameExpression;
997+
}
992

998

993
// @kind(SyntaxKind.ElementAccessExpression)
999
// @kind(SyntaxKind.ElementAccessExpression)
994
export interface ElementAccessExpression extends MemberExpression {
1000
export interface ElementAccessExpression extends MemberExpression {
@@ -2031,7 +2037,7 @@ namespace ts {
2031
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
2037
writeTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
2032
writeBaseConstructorTypeOfClass(node: ClassLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
2038
writeBaseConstructorTypeOfClass(node: ClassLikeDeclaration, enclosingDeclaration: Node, flags: TypeFormatFlags, writer: SymbolWriter): void;
2033
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessibilityResult;
2039
isSymbolAccessible(symbol: Symbol, enclosingDeclaration: Node, meaning: SymbolFlags): SymbolAccessibilityResult;
2034-
isEntityNameVisible(entityName: EntityName | Expression, enclosingDeclaration: Node): SymbolVisibilityResult;
2040+
isEntityNameVisible(entityName: EntityNameOrEntityNameExpression, enclosingDeclaration: Node): SymbolVisibilityResult;
2035
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
2041
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
2036
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
2042
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number;
2037
getReferencedValueDeclaration(reference: Identifier): Declaration;
2043
getReferencedValueDeclaration(reference: Identifier): Declaration;
@@ -2040,7 +2046,7 @@ namespace ts {
2040
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
2046
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
2041
isArgumentsLocalBinding(node: Identifier): boolean;
2047
isArgumentsLocalBinding(node: Identifier): boolean;
2042
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
2048
getExternalModuleFileFromDeclaration(declaration: ImportEqualsDeclaration | ImportDeclaration | ExportDeclaration | ModuleDeclaration): SourceFile;
2043-
getTypeReferenceDirectivesForEntityName(name: EntityName | PropertyAccessExpression): string[];
2049+
getTypeReferenceDirectivesForEntityName(name: EntityNameOrEntityNameExpression): string[];
2044
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
2050
getTypeReferenceDirectivesForSymbol(symbol: Symbol, meaning?: SymbolFlags): string[];
2045
}
2051
}
2046

2052

src/compiler/utilities.ts

Lines changed: 13 additions & 22 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -1033,14 +1033,14 @@ namespace ts {
1033
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
1033
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.SuperKeyword;
1034
}
1034
}
1035

1035

1036-
1036+
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression {
1037-
export function getEntityNameFromTypeNode(node: TypeNode): EntityName | Expression {
1038
if (node) {
1037
if (node) {
1039
switch (node.kind) {
1038
switch (node.kind) {
1040
case SyntaxKind.TypeReference:
1039
case SyntaxKind.TypeReference:
1041
return (<TypeReferenceNode>node).typeName;
1040
return (<TypeReferenceNode>node).typeName;
1042
case SyntaxKind.ExpressionWithTypeArguments:
1041
case SyntaxKind.ExpressionWithTypeArguments:
1043-
return (<ExpressionWithTypeArguments>node).expression;
1042+
Debug.assert(isEntityNameExpression((<ExpressionWithTypeArguments>node).expression));
1043+
return <EntityNameExpression>(<ExpressionWithTypeArguments>node).expression;
1044
case SyntaxKind.Identifier:
1044
case SyntaxKind.Identifier:
1045
case SyntaxKind.QualifiedName:
1045
case SyntaxKind.QualifiedName:
1046
return (<EntityName><Node>node);
1046
return (<EntityName><Node>node);
@@ -1694,16 +1694,20 @@ namespace ts {
1694
// import * as <symbol> from ...
1694
// import * as <symbol> from ...
1695
// import { x as <symbol> } from ...
1695
// import { x as <symbol> } from ...
1696
// export { x as <symbol> } from ...
1696
// export { x as <symbol> } from ...
1697-
// export = ...
1697+
// export = <EntityNameExpression>
1698-
// export default ...
1698+
// export default <EntityNameExpression>
1699
export function isAliasSymbolDeclaration(node: Node): boolean {
1699
export function isAliasSymbolDeclaration(node: Node): boolean {
1700
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
1700
return node.kind === SyntaxKind.ImportEqualsDeclaration ||
1701
node.kind === SyntaxKind.NamespaceExportDeclaration ||
1701
node.kind === SyntaxKind.NamespaceExportDeclaration ||
1702
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
1702
node.kind === SyntaxKind.ImportClause && !!(<ImportClause>node).name ||
1703
node.kind === SyntaxKind.NamespaceImport ||
1703
node.kind === SyntaxKind.NamespaceImport ||
1704
node.kind === SyntaxKind.ImportSpecifier ||
1704
node.kind === SyntaxKind.ImportSpecifier ||
1705
node.kind === SyntaxKind.ExportSpecifier ||
1705
node.kind === SyntaxKind.ExportSpecifier ||
1706-
node.kind === SyntaxKind.ExportAssignment && (<ExportAssignment>node).expression.kind === SyntaxKind.Identifier;
1706+
node.kind === SyntaxKind.ExportAssignment && exportAssignmentIsAlias(<ExportAssignment>node);
1707+
}
1708+
1709+
export function exportAssignmentIsAlias(node: ExportAssignment): boolean {
1710+
return isEntityNameExpression(node.expression);
1707
}
1711
}
1708

1712

1709
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
1713
export function getClassExtendsHeritageClauseElement(node: ClassLikeDeclaration | InterfaceDeclaration) {
@@ -2681,22 +2685,9 @@ namespace ts {
2681
isClassLike(node.parent.parent);
2685
isClassLike(node.parent.parent);
2682
}
2686
}
2683

2687

2684-
// Returns false if this heritage clause element's expression contains something unsupported
2688+
export function isEntityNameExpression(node: Expression): node is EntityNameExpression {
2685-
// (i.e. not a name or dotted name).
2689+
return node.kind === SyntaxKind.Identifier ||
2686-
export function isSupportedExpressionWithTypeArguments(node: ExpressionWithTypeArguments): boolean {
2690+
node.kind === SyntaxKind.PropertyAccessExpression && isEntityNameExpression((<PropertyAccessExpression>node).expression);
2687-
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
2688-
}
2689-
2690-
function isSupportedExpressionWithTypeArgumentsRest(node: Expression): boolean {
2691-
if (node.kind === SyntaxKind.Identifier) {
2692-
return true;
2693-
}
2694-
else if (isPropertyAccessExpression(node)) {
2695-
return isSupportedExpressionWithTypeArgumentsRest(node.expression);
2696-
}
2697-
else {
2698-
return false;
2699-
}
2700
}
2691
}
2701

2692

2702
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
2693
export function isRightSideOfQualifiedNameOrPropertyAccess(node: Node) {
Lines changed: 76 additions & 0 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//// [tests/cases/compiler/exportDefaultProperty.ts] ////
2+
3+
//// [declarations.d.ts]
4+
// This test is just like exportEqualsProperty, but with `export default`.
5+
6+
declare namespace foo.bar {
7+
export type X = number;
8+
export const X: number;
9+
}
10+
11+
declare module "foobar" {
12+
export default foo.bar;
13+
}
14+
15+
declare module "foobarx" {
16+
export default foo.bar.X;
17+
}
18+
19+
//// [a.ts]
20+
namespace A {
21+
export class B { constructor(b: number) {} }
22+
export namespace B { export const b: number = 0; }
23+
}
24+
export default A.B;
25+
26+
//// [b.ts]
27+
export default "foo".length;
28+
29+
//// [index.ts]
30+
/// <reference path="declarations.d.ts" />
31+
import fooBar from "foobar";
32+
import X = fooBar.X;
33+
import X2 from "foobarx";
34+
const x: X = X;
35+
const x2: X2 = X2;
36+
37+
import B from "./a";
38+
const b: B = new B(B.b);
39+
40+
import fooLength from "./b";
41+
fooLength + 1;
42+
43+
44+
//// [a.js]
45+
"use strict";
46+
var A;
47+
(function (A) {
48+
var B = (function () {
49+
function B(b) {
50+
}
51+
return B;
52+
}());
53+
A.B = B;
54+
var B;
55+
(function (B) {
56+
B.b = 0;
57+
})(B = A.B || (A.B = {}));
58+
})(A || (A = {}));
59+
exports.__esModule = true;
60+
exports["default"] = A.B;
61+
//// [b.js]
62+
"use strict";
63+
exports.__esModule = true;
64+
exports["default"] = "foo".length;
65+
//// [index.js]
66+
"use strict";
67+
/// <reference path="declarations.d.ts" />
68+
var foobar_1 = require("foobar");
69+
var X = foobar_1["default"].X;
70+
var foobarx_1 = require("foobarx");
71+
var x = X;
72+
var x2 = foobarx_1["default"];
73+
var a_1 = require("./a");
74+
var b = new a_1["default"](a_1["default"].b);
75+
var b_1 = require("./b");
76+
b_1["default"] + 1;
Lines changed: 92 additions & 0 deletions
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
=== tests/cases/compiler/index.ts ===
2+
/// <reference path="declarations.d.ts" />
3+
import fooBar from "foobar";
4+
>fooBar : Symbol(fooBar, Decl(index.ts, 1, 6))
5+
6+
import X = fooBar.X;
7+
>X : Symbol(X, Decl(index.ts, 1, 28))
8+
>fooBar : Symbol(fooBar, Decl(index.ts, 1, 6))
9+
>X : Symbol(fooBar.X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
10+
11+
import X2 from "foobarx";
12+
>X2 : Symbol(X2, Decl(index.ts, 3, 6))
13+
14+
const x: X = X;
15+
>x : Symbol(x, Decl(index.ts, 4, 5))
16+
>X : Symbol(X, Decl(index.ts, 1, 28))
17+
>X : Symbol(X, Decl(index.ts, 1, 28))
18+
19+
const x2: X2 = X2;
20+
>x2 : Symbol(x2, Decl(index.ts, 5, 5))
21+
>X2 : Symbol(X2, Decl(index.ts, 3, 6))
22+
>X2 : Symbol(X2, Decl(index.ts, 3, 6))
23+
24+
import B from "./a";
25+
>B : Symbol(B, Decl(index.ts, 7, 6))
26+
27+
const b: B = new B(B.b);
28+
>b : Symbol(b, Decl(index.ts, 8, 5))
29+
>B : Symbol(B, Decl(index.ts, 7, 6))
30+
>B : Symbol(B, Decl(index.ts, 7, 6))
31+
>B.b : Symbol(B.b, Decl(a.ts, 2, 37))
32+
>B : Symbol(B, Decl(index.ts, 7, 6))
33+
>b : Symbol(B.b, Decl(a.ts, 2, 37))
34+
35+
import fooLength from "./b";
36+
>fooLength : Symbol(fooLength, Decl(index.ts, 10, 6))
37+
38+
fooLength + 1;
39+
>fooLength : Symbol(fooLength, Decl(index.ts, 10, 6))
40+
41+
=== tests/cases/compiler/declarations.d.ts ===
42+
// This test is just like exportEqualsProperty, but with `export default`.
43+
44+
declare namespace foo.bar {
45+
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
46+
>bar : Symbol(bar, Decl(declarations.d.ts, 2, 22))
47+
48+
export type X = number;
49+
>X : Symbol(X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
50+
51+
export const X: number;
52+
>X : Symbol(X, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
53+
}
54+
55+
declare module "foobar" {
56+
export default foo.bar;
57+
>foo.bar : Symbol(default, Decl(declarations.d.ts, 2, 22))
58+
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
59+
>bar : Symbol(default, Decl(declarations.d.ts, 2, 22))
60+
}
61+
62+
declare module "foobarx" {
63+
export default foo.bar.X;
64+
>foo.bar.X : Symbol(default, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
65+
>foo.bar : Symbol(foo.bar, Decl(declarations.d.ts, 2, 22))
66+
>foo : Symbol(foo, Decl(declarations.d.ts, 0, 0))
67+
>bar : Symbol(foo.bar, Decl(declarations.d.ts, 2, 22))
68+
>X : Symbol(default, Decl(declarations.d.ts, 2, 27), Decl(declarations.d.ts, 4, 16))
69+
}
70+
71+
=== tests/cases/compiler/a.ts ===
72+
namespace A {
73+
>A : Symbol(A, Decl(a.ts, 0, 0))
74+
75+
export class B { constructor(b: number) {} }
76+
>B : Symbol(B, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
77+
>b : Symbol(b, Decl(a.ts, 1, 33))
78+
79+
export namespace B { export const b: number = 0; }
80+
>B : Symbol(B, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
81+
>b : Symbol(b, Decl(a.ts, 2, 37))
82+
}
83+
export default A.B;
84+
>A.B : Symbol(default, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
85+
>A : Symbol(A, Decl(a.ts, 0, 0))
86+
>B : Symbol(default, Decl(a.ts, 0, 13), Decl(a.ts, 1, 48))
87+
88+
=== tests/cases/compiler/b.ts ===
89+
export default "foo".length;
90+
>"foo".length : Symbol(String.length, Decl(lib.d.ts, --, --))
91+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
92+

0 commit comments

Comments
 (0)