Skip to content

Function and class properties #13648

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

Merged
merged 10 commits into from
Feb 16, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ namespace ts {
return "export=";
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.Property:
// exports.x = ... or this.y = ...
return ((node as BinaryExpression).left as PropertyAccessExpression).name.text;
case SpecialPropertyAssignmentKind.PrototypeProperty:
Expand Down Expand Up @@ -1051,8 +1052,8 @@ namespace ts {
// second -> edge that represents post-finally flow.
// these edges are used in following scenario:
// let a; (1)
// try { a = someOperation(); (2)}
// finally { (3) console.log(a) } (4)
// try { a = someOperation(); (2)}
// finally { (3) console.log(a) } (4)
// (5) a

// flow graph for this case looks roughly like this (arrows show ):
Expand All @@ -1064,11 +1065,11 @@ namespace ts {
// In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account
// since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
// then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
// since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.
// However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
// final flows of these blocks without taking pre-try flow into account.
//
//
// extra edges that we inject allows to control this behavior
// 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.
const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} };
Expand Down Expand Up @@ -1959,6 +1960,9 @@ namespace ts {
case SpecialPropertyAssignmentKind.ThisProperty:
bindThisPropertyAssignment(<BinaryExpression>node);
break;
case SpecialPropertyAssignmentKind.Property:
bindStaticPropertyAssignment(<BinaryExpression>node);
break;
case SpecialPropertyAssignmentKind.None:
// Nothing to do
break;
Expand Down Expand Up @@ -2249,18 +2253,41 @@ namespace ts {
constructorFunction.parent = classPrototype;
classPrototype.parent = leftSideOfAssignment;

const funcSymbol = container.locals.get(constructorFunction.text);
if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
bindPropertyAssignment(constructorFunction.text, leftSideOfAssignment, /*isPrototypeProperty*/ true);
}

function bindStaticPropertyAssignment(node: BinaryExpression) {
// We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function.

// Look up the function in the local scope, since prototype assignments should
// follow the function declaration
const leftSideOfAssignment = node.left as PropertyAccessExpression;
const target = leftSideOfAssignment.expression as Identifier;

// Fix up parent pointers since we're going to use these nodes before we bind into them
leftSideOfAssignment.parent = node;
target.parent = leftSideOfAssignment;

bindPropertyAssignment(target.text, leftSideOfAssignment, /*isPrototypeProperty*/ false);
}

function bindPropertyAssignment(functionName: string, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
let targetSymbol = container.locals.get(functionName);
if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
}

if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class))) {
return;
}

// Set up the members collection if it doesn't exist already
if (!funcSymbol.members) {
funcSymbol.members = createMap<Symbol>();
}
const symbolTable = isPrototypeProperty ?
(targetSymbol.members || (targetSymbol.members = createMap<Symbol>())) :
(targetSymbol.exports || (targetSymbol.exports = createMap<Symbol>()));

// Declare the method/property
declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
declareSymbol(symbolTable, targetSymbol, propertyAccessExpression, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}

function bindCallExpression(node: CallExpression) {
Expand Down
40 changes: 26 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2169,13 +2169,15 @@ namespace ts {
return type.flags & TypeFlags.StringLiteral ? `"${escapeString((<LiteralType>type).text)}"` : (<LiteralType>type).text;
}


function getNameOfSymbol(symbol: Symbol): string {
if (symbol.declarations && symbol.declarations.length) {
const declaration = symbol.declarations[0];
if (declaration.name) {
return declarationNameToString(declaration.name);
}
if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) {
return declarationNameToString((<VariableDeclaration>declaration.parent).name);
}
switch (declaration.kind) {
case SyntaxKind.ClassExpression:
return "(Anonymous class)";
Expand Down Expand Up @@ -4663,7 +4665,7 @@ namespace ts {
// Combinations of function, class, enum and module
let members = emptySymbols;
let constructSignatures: Signature[] = emptyArray;
if (symbol.flags & SymbolFlags.HasExports) {
if (symbol.exports) {
members = getExportsOfSymbol(symbol);
}
if (symbol.flags & SymbolFlags.Class) {
Expand Down Expand Up @@ -14128,10 +14130,13 @@ namespace ts {
// in a JS file
// Note:JS inferred classes might come from a variable declaration instead of a function declaration.
// In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration.
const funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
let funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
getResolvedSymbol(node.expression as Identifier) :
checkExpression(node.expression).symbol;
if (funcSymbol && funcSymbol.members && (funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
}
if (funcSymbol && funcSymbol.members && funcSymbol.flags & SymbolFlags.Function) {
return getInferredClassType(funcSymbol);
}
else if (compilerOptions.noImplicitAny) {
Expand Down Expand Up @@ -20117,22 +20122,29 @@ namespace ts {
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
}

function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) {
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
switch (specialPropertyAssignmentKind) {
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.PrototypeProperty:
return getSymbolOfNode(entityName.parent);
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.Property:
return getSymbolOfNode(entityName.parent.parent);
}
}

function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined {
if (isDeclarationName(entityName)) {
return getSymbolOfNode(entityName.parent);
}

if (isInJavaScriptFile(entityName) && entityName.parent.kind === SyntaxKind.PropertyAccessExpression) {
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
switch (specialPropertyAssignmentKind) {
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.PrototypeProperty:
return getSymbolOfNode(entityName.parent);
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
return getSymbolOfNode(entityName.parent.parent);
default:
// Fall through if it is not a special property assignment
// Check if this is a special property assignment
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName);
if (specialPropertyAssignmentSymbol) {
return specialPropertyAssignmentSymbol;
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3167,7 +3167,9 @@
/// className.prototype.name = expr
PrototypeProperty,
/// this.name = expr
ThisProperty
ThisProperty,
// F.name = expr
Property
}

export interface JsFileExtensionInfo {
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1402,10 +1402,10 @@ namespace ts {
* Returns true if the node is a variable declaration whose initializer is a function expression.
* This function does not test if the node is in a JavaScript file or not.
*/
export function isDeclarationOfFunctionExpression(s: Symbol) {
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment is not updated on line 1402

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
const declaration = s.valueDeclaration as VariableDeclaration;
return declaration.initializer && declaration.initializer.kind === SyntaxKind.FunctionExpression;
return declaration.initializer && (declaration.initializer.kind === SyntaxKind.FunctionExpression || declaration.initializer.kind === SyntaxKind.ClassExpression);
}
return false;
}
Expand Down Expand Up @@ -1434,6 +1434,10 @@ namespace ts {
// module.exports = expr
return SpecialPropertyAssignmentKind.ModuleExports;
}
else {
// F.x = expr
return SpecialPropertyAssignmentKind.Property;
}
}
else if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
return SpecialPropertyAssignmentKind.ThisProperty;
Expand All @@ -1453,6 +1457,7 @@ namespace ts {
}
}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't need this extra whitespace

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope

return SpecialPropertyAssignmentKind.None;
}

Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/classExpression3.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>C : Symbol(C, Decl(classExpression3.ts, 0, 3))
>a : Symbol((Anonymous class).a, Decl(classExpression3.ts, 0, 43))
>b : Symbol((Anonymous class).b, Decl(classExpression3.ts, 0, 53))
>c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
>c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))

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

c.c;
>c.c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
>c.c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))
>c : Symbol(c, Decl(classExpression3.ts, 1, 3))
>c : Symbol((Anonymous class).c, Decl(classExpression3.ts, 0, 63))
>c : Symbol(C.c, Decl(classExpression3.ts, 0, 63))

16 changes: 8 additions & 8 deletions tests/baselines/reference/classExpression3.types
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/conformance/classes/classExpressions/classExpression3.ts ===
let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>C : typeof (Anonymous class)
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof (Anonymous class)
>C : typeof C
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof C
>class extends class { a = 1 } { b = 2 } : (Anonymous class)
>class { a = 1 } : (Anonymous class)
>a : number
Expand All @@ -12,22 +12,22 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>3 : 3

let c = new C();
>c : (Anonymous class)
>new C() : (Anonymous class)
>C : typeof (Anonymous class)
>c : C
>new C() : C
>C : typeof C

c.a;
>c.a : number
>c : (Anonymous class)
>c : C
>a : number

c.b;
>c.b : number
>c : (Anonymous class)
>c : C
>b : number

c.c;
>c.c : number
>c : (Anonymous class)
>c : C
>c : number

6 changes: 3 additions & 3 deletions tests/baselines/reference/classExpression4.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ let C = class {
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))

foo() {
>foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
>foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))

return new C();
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))
}
};
let x = (new C).foo();
>x : Symbol(x, Decl(classExpression4.ts, 5, 3))
>(new C).foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
>(new C).foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))
>C : Symbol(C, Decl(classExpression4.ts, 0, 3))
>foo : Symbol((Anonymous class).foo, Decl(classExpression4.ts, 0, 15))
>foo : Symbol(C.foo, Decl(classExpression4.ts, 0, 15))

24 changes: 12 additions & 12 deletions tests/baselines/reference/classExpression4.types
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
=== tests/cases/conformance/classes/classExpressions/classExpression4.ts ===
let C = class {
>C : typeof (Anonymous class)
>class { foo() { return new C(); }} : typeof (Anonymous class)
>C : typeof C
>class { foo() { return new C(); }} : typeof C

foo() {
>foo : () => (Anonymous class)
>foo : () => C

return new C();
>new C() : (Anonymous class)
>C : typeof (Anonymous class)
>new C() : C
>C : typeof C
}
};
let x = (new C).foo();
>x : (Anonymous class)
>(new C).foo() : (Anonymous class)
>(new C).foo : () => (Anonymous class)
>(new C) : (Anonymous class)
>new C : (Anonymous class)
>C : typeof (Anonymous class)
>foo : () => (Anonymous class)
>x : C
>(new C).foo() : C
>(new C).foo : () => C
>(new C) : C
>new C : C
>C : typeof C
>foo : () => C

6 changes: 3 additions & 3 deletions tests/baselines/reference/classExpressionES63.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>C : Symbol(C, Decl(classExpressionES63.ts, 0, 3))
>a : Symbol((Anonymous class).a, Decl(classExpressionES63.ts, 0, 43))
>b : Symbol((Anonymous class).b, Decl(classExpressionES63.ts, 0, 53))
>c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
>c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))

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

c.c;
>c.c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
>c.c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))
>c : Symbol(c, Decl(classExpressionES63.ts, 1, 3))
>c : Symbol((Anonymous class).c, Decl(classExpressionES63.ts, 0, 63))
>c : Symbol(C.c, Decl(classExpressionES63.ts, 0, 63))

16 changes: 8 additions & 8 deletions tests/baselines/reference/classExpressionES63.types
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
=== tests/cases/conformance/es6/classExpressions/classExpressionES63.ts ===
let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>C : typeof (Anonymous class)
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof (Anonymous class)
>C : typeof C
>class extends class extends class { a = 1 } { b = 2 } { c = 3 } : typeof C
>class extends class { a = 1 } { b = 2 } : (Anonymous class)
>class { a = 1 } : (Anonymous class)
>a : number
Expand All @@ -12,22 +12,22 @@ let C = class extends class extends class { a = 1 } { b = 2 } { c = 3 };
>3 : 3

let c = new C();
>c : (Anonymous class)
>new C() : (Anonymous class)
>C : typeof (Anonymous class)
>c : C
>new C() : C
>C : typeof C

c.a;
>c.a : number
>c : (Anonymous class)
>c : C
>a : number

c.b;
>c.b : number
>c : (Anonymous class)
>c : C
>b : number

c.c;
>c.c : number
>c : (Anonymous class)
>c : C
>c : number

Loading