Skip to content

Commit 6c58938

Browse files
authored
Merge pull request #13648 from Microsoft/functionAndClassProperties
Function and class properties
2 parents 2e466c6 + db0e376 commit 6c58938

25 files changed

+238
-125
lines changed

src/compiler/binder.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ namespace ts {
265265
return "export=";
266266
case SpecialPropertyAssignmentKind.ExportsProperty:
267267
case SpecialPropertyAssignmentKind.ThisProperty:
268+
case SpecialPropertyAssignmentKind.Property:
268269
// exports.x = ... or this.y = ...
269270
return ((node as BinaryExpression).left as PropertyAccessExpression).name.text;
270271
case SpecialPropertyAssignmentKind.PrototypeProperty:
@@ -1051,8 +1052,8 @@ namespace ts {
10511052
// second -> edge that represents post-finally flow.
10521053
// these edges are used in following scenario:
10531054
// let a; (1)
1054-
// try { a = someOperation(); (2)}
1055-
// finally { (3) console.log(a) } (4)
1055+
// try { a = someOperation(); (2)}
1056+
// finally { (3) console.log(a) } (4)
10561057
// (5) a
10571058

10581059
// flow graph for this case looks roughly like this (arrows show ):
@@ -1064,11 +1065,11 @@ namespace ts {
10641065
// In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account
10651066
// since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
10661067
// then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
1067-
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
1068+
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
10681069
// since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.
10691070
// However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
10701071
// final flows of these blocks without taking pre-try flow into account.
1071-
//
1072+
//
10721073
// extra edges that we inject allows to control this behavior
10731074
// if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
10741075
const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} };
@@ -1969,6 +1970,9 @@ namespace ts {
19691970
case SpecialPropertyAssignmentKind.ThisProperty:
19701971
bindThisPropertyAssignment(<BinaryExpression>node);
19711972
break;
1973+
case SpecialPropertyAssignmentKind.Property:
1974+
bindStaticPropertyAssignment(<BinaryExpression>node);
1975+
break;
19721976
case SpecialPropertyAssignmentKind.None:
19731977
// Nothing to do
19741978
break;
@@ -2265,18 +2269,41 @@ namespace ts {
22652269
constructorFunction.parent = classPrototype;
22662270
classPrototype.parent = leftSideOfAssignment;
22672271

2268-
const funcSymbol = container.locals.get(constructorFunction.text);
2269-
if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
2272+
bindPropertyAssignment(constructorFunction.text, leftSideOfAssignment, /*isPrototypeProperty*/ true);
2273+
}
2274+
2275+
function bindStaticPropertyAssignment(node: BinaryExpression) {
2276+
// We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function.
2277+
2278+
// Look up the function in the local scope, since prototype assignments should
2279+
// follow the function declaration
2280+
const leftSideOfAssignment = node.left as PropertyAccessExpression;
2281+
const target = leftSideOfAssignment.expression as Identifier;
2282+
2283+
// Fix up parent pointers since we're going to use these nodes before we bind into them
2284+
leftSideOfAssignment.parent = node;
2285+
target.parent = leftSideOfAssignment;
2286+
2287+
bindPropertyAssignment(target.text, leftSideOfAssignment, /*isPrototypeProperty*/ false);
2288+
}
2289+
2290+
function bindPropertyAssignment(functionName: string, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
2291+
let targetSymbol = container.locals.get(functionName);
2292+
if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
2293+
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
2294+
}
2295+
2296+
if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class))) {
22702297
return;
22712298
}
22722299

22732300
// Set up the members collection if it doesn't exist already
2274-
if (!funcSymbol.members) {
2275-
funcSymbol.members = createMap<Symbol>();
2276-
}
2301+
const symbolTable = isPrototypeProperty ?
2302+
(targetSymbol.members || (targetSymbol.members = createMap<Symbol>())) :
2303+
(targetSymbol.exports || (targetSymbol.exports = createMap<Symbol>()));
22772304

22782305
// Declare the method/property
2279-
declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
2306+
declareSymbol(symbolTable, targetSymbol, propertyAccessExpression, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
22802307
}
22812308

22822309
function bindCallExpression(node: CallExpression) {

src/compiler/checker.ts

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,13 +2184,15 @@ namespace ts {
21842184
return type.flags & TypeFlags.StringLiteral ? `"${escapeString((<LiteralType>type).text)}"` : (<LiteralType>type).text;
21852185
}
21862186

2187-
21882187
function getNameOfSymbol(symbol: Symbol): string {
21892188
if (symbol.declarations && symbol.declarations.length) {
21902189
const declaration = symbol.declarations[0];
21912190
if (declaration.name) {
21922191
return declarationNameToString(declaration.name);
21932192
}
2193+
if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) {
2194+
return declarationNameToString((<VariableDeclaration>declaration.parent).name);
2195+
}
21942196
switch (declaration.kind) {
21952197
case SyntaxKind.ClassExpression:
21962198
return "(Anonymous class)";
@@ -4692,7 +4694,7 @@ namespace ts {
46924694
// Combinations of function, class, enum and module
46934695
let members = emptySymbols;
46944696
let constructSignatures: Signature[] = emptyArray;
4695-
if (symbol.flags & SymbolFlags.HasExports) {
4697+
if (symbol.exports) {
46964698
members = getExportsOfSymbol(symbol);
46974699
}
46984700
if (symbol.flags & SymbolFlags.Class) {
@@ -14577,10 +14579,13 @@ namespace ts {
1457714579
// in a JS file
1457814580
// Note:JS inferred classes might come from a variable declaration instead of a function declaration.
1457914581
// In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration.
14580-
const funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
14582+
let funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
1458114583
getResolvedSymbol(node.expression as Identifier) :
1458214584
checkExpression(node.expression).symbol;
14583-
if (funcSymbol && funcSymbol.members && (funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
14585+
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
14586+
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
14587+
}
14588+
if (funcSymbol && funcSymbol.members && funcSymbol.flags & SymbolFlags.Function) {
1458414589
return getInferredClassType(funcSymbol);
1458514590
}
1458614591
else if (compilerOptions.noImplicitAny) {
@@ -20629,22 +20634,29 @@ namespace ts {
2062920634
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
2063020635
}
2063120636

20637+
function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) {
20638+
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
20639+
switch (specialPropertyAssignmentKind) {
20640+
case SpecialPropertyAssignmentKind.ExportsProperty:
20641+
case SpecialPropertyAssignmentKind.PrototypeProperty:
20642+
return getSymbolOfNode(entityName.parent);
20643+
case SpecialPropertyAssignmentKind.ThisProperty:
20644+
case SpecialPropertyAssignmentKind.ModuleExports:
20645+
case SpecialPropertyAssignmentKind.Property:
20646+
return getSymbolOfNode(entityName.parent.parent);
20647+
}
20648+
}
20649+
2063220650
function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined {
2063320651
if (isDeclarationName(entityName)) {
2063420652
return getSymbolOfNode(entityName.parent);
2063520653
}
2063620654

2063720655
if (isInJavaScriptFile(entityName) && entityName.parent.kind === SyntaxKind.PropertyAccessExpression) {
20638-
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
20639-
switch (specialPropertyAssignmentKind) {
20640-
case SpecialPropertyAssignmentKind.ExportsProperty:
20641-
case SpecialPropertyAssignmentKind.PrototypeProperty:
20642-
return getSymbolOfNode(entityName.parent);
20643-
case SpecialPropertyAssignmentKind.ThisProperty:
20644-
case SpecialPropertyAssignmentKind.ModuleExports:
20645-
return getSymbolOfNode(entityName.parent.parent);
20646-
default:
20647-
// Fall through if it is not a special property assignment
20656+
// Check if this is a special property assignment
20657+
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName);
20658+
if (specialPropertyAssignmentSymbol) {
20659+
return specialPropertyAssignmentSymbol;
2064820660
}
2064920661
}
2065020662

src/compiler/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3180,7 +3180,9 @@
31803180
/// className.prototype.name = expr
31813181
PrototypeProperty,
31823182
/// this.name = expr
3183-
ThisProperty
3183+
ThisProperty,
3184+
// F.name = expr
3185+
Property
31843186
}
31853187

31863188
export interface JsFileExtensionInfo {

src/compiler/utilities.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,10 +1417,10 @@ namespace ts {
14171417
* Returns true if the node is a variable declaration whose initializer is a function expression.
14181418
* This function does not test if the node is in a JavaScript file or not.
14191419
*/
1420-
export function isDeclarationOfFunctionExpression(s: Symbol) {
1420+
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
14211421
if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
14221422
const declaration = s.valueDeclaration as VariableDeclaration;
1423-
return declaration.initializer && declaration.initializer.kind === SyntaxKind.FunctionExpression;
1423+
return declaration.initializer && (declaration.initializer.kind === SyntaxKind.FunctionExpression || declaration.initializer.kind === SyntaxKind.ClassExpression);
14241424
}
14251425
return false;
14261426
}
@@ -1449,6 +1449,10 @@ namespace ts {
14491449
// module.exports = expr
14501450
return SpecialPropertyAssignmentKind.ModuleExports;
14511451
}
1452+
else {
1453+
// F.x = expr
1454+
return SpecialPropertyAssignmentKind.Property;
1455+
}
14521456
}
14531457
else if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
14541458
return SpecialPropertyAssignmentKind.ThisProperty;
@@ -1468,6 +1472,7 @@ namespace ts {
14681472
}
14691473
}
14701474

1475+
14711476
return SpecialPropertyAssignmentKind.None;
14721477
}
14731478

tests/baselines/reference/classExpression3.symbols

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
33
>C : Symbol(C, Decl(classExpression3.ts, 0, 3))
44
>a : Symbol((Anonymous class).a, Decl(classExpression3.ts, 0, 43))
55
>b : Symbol((Anonymous class).b, Decl(classExpression3.ts, 0, 53))
6-
>c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
6+
>c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))
77

88
let c = new C();
99
>c : Symbol(c, Decl(classExpression3.ts, 1, 3))
@@ -20,7 +20,7 @@ c.b;
2020
>b : Symbol((Anonymous class).b, Decl(classExpression3.ts, 0, 53))
2121

2222
c.c;
23-
>c.c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
23+
>c.c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))
2424
>c : Symbol(c, Decl(classExpression3.ts, 1, 3))
25-
>c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
25+
>c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))
2626

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
=== tests/cases/conformance/classes/classExpressions/classExpression3.ts ===
22
let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
3-
>C : typeof (Anonymous class)
4-
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof (Anonymous class)
3+
>C : typeof C
4+
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof C
55
>class extends class { a = 1 } { b = 2 } : (Anonymous class)
66
>class { a = 1 } : (Anonymous class)
77
>a : number
@@ -12,22 +12,22 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
1212
>3 : 3
1313

1414
let c = new C();
15-
>c : (Anonymous class)
16-
>new C() : (Anonymous class)
17-
>C : typeof (Anonymous class)
15+
>c : C
16+
>new C() : C
17+
>C : typeof C
1818

1919
c.a;
2020
>c.a : number
21-
>c : (Anonymous class)
21+
>c : C
2222
>a : number
2323

2424
c.b;
2525
>c.b : number
26-
>c : (Anonymous class)
26+
>c : C
2727
>b : number
2828

2929
c.c;
3030
>c.c : number
31-
>c : (Anonymous class)
31+
>c : C
3232
>c : number
3333

tests/baselines/reference/classExpression4.symbols

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ let C = class {
33
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))
44

55
foo() {
6-
>foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
6+
>foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))
77

88
return new C();
99
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))
1010
}
1111
};
1212
let x = (new C).foo();
1313
>x : Symbol(x, Decl(classExpression4.ts, 5, 3))
14-
>(new C).foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
14+
>(new C).foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))
1515
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))
16-
>foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
16+
>foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))
1717

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
=== tests/cases/conformance/classes/classExpressions/classExpression4.ts ===
22
let C = class {
3-
>C : typeof (Anonymous class)
4-
>class { foo() { return new C(); }} : typeof (Anonymous class)
3+
>C : typeof C
4+
>class { foo() { return new C(); }} : typeof C
55

66
foo() {
7-
>foo : () => (Anonymous class)
7+
>foo : () => C
88

99
return new C();
10-
>new C() : (Anonymous class)
11-
>C : typeof (Anonymous class)
10+
>new C() : C
11+
>C : typeof C
1212
}
1313
};
1414
let x = (new C).foo();
15-
>x : (Anonymous class)
16-
>(new C).foo() : (Anonymous class)
17-
>(new C).foo : () => (Anonymous class)
18-
>(new C) : (Anonymous class)
19-
>new C : (Anonymous class)
20-
>C : typeof (Anonymous class)
21-
>foo : () => (Anonymous class)
15+
>x : C
16+
>(new C).foo() : C
17+
>(new C).foo : () => C
18+
>(new C) : C
19+
>new C : C
20+
>C : typeof C
21+
>foo : () => C
2222

tests/baselines/reference/classExpressionES63.symbols

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
33
>C : Symbol(C, Decl(classExpressionES63.ts, 0, 3))
44
>a : Symbol((Anonymous class).a, Decl(classExpressionES63.ts, 0, 43))
55
>b : Symbol((Anonymous class).b, Decl(classExpressionES63.ts, 0, 53))
6-
>c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
6+
>c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))
77

88
let c = new C();
99
>c : Symbol(c, Decl(classExpressionES63.ts, 1, 3))
@@ -20,7 +20,7 @@ c.b;
2020
>b : Symbol((Anonymous class).b, Decl(classExpressionES63.ts, 0, 53))
2121

2222
c.c;
23-
>c.c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
23+
>c.c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))
2424
>c : Symbol(c, Decl(classExpressionES63.ts, 1, 3))
25-
>c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
25+
>c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))
2626

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
=== tests/cases/conformance/es6/classExpressions/classExpressionES63.ts ===
22
let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
3-
>C : typeof (Anonymous class)
4-
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof (Anonymous class)
3+
>C : typeof C
4+
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof C
55
>class extends class { a = 1 } { b = 2 } : (Anonymous class)
66
>class { a = 1 } : (Anonymous class)
77
>a : number
@@ -12,22 +12,22 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
1212
>3 : 3
1313

1414
let c = new C();
15-
>c : (Anonymous class)
16-
>new C() : (Anonymous class)
17-
>C : typeof (Anonymous class)
15+
>c : C
16+
>new C() : C
17+
>C : typeof C
1818

1919
c.a;
2020
>c.a : number
21-
>c : (Anonymous class)
21+
>c : C
2222
>a : number
2323

2424
c.b;
2525
>c.b : number
26-
>c : (Anonymous class)
26+
>c : C
2727
>b : number
2828

2929
c.c;
3030
>c.c : number
31-
>c : (Anonymous class)
31+
>c : C
3232
>c : number
3333

0 commit comments

Comments
 (0)