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 3 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
42 changes: 40 additions & 2 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 @@ -1921,6 +1922,9 @@ namespace ts {
case SpecialPropertyAssignmentKind.ThisProperty:
bindThisPropertyAssignment(<BinaryExpression>node);
break;
case SpecialPropertyAssignmentKind.Property:
bindPropertyAssignment(<BinaryExpression>node);
break;
case SpecialPropertyAssignmentKind.None:
// Nothing to do
break;
Expand Down Expand Up @@ -2211,8 +2215,12 @@ namespace ts {
constructorFunction.parent = classPrototype;
classPrototype.parent = leftSideOfAssignment;

const funcSymbol = container.locals.get(constructorFunction.text);
if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
let funcSymbol = container.locals.get(constructorFunction.text);
Copy link
Member

Choose a reason for hiding this comment

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

bindPrototypePropertyAssignment and bindPropertyAssignment share lots of code now, so I think they should either merge or delegate to a third function that does most of the work.

if (isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
funcSymbol = (funcSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
}

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

Expand All @@ -2225,6 +2233,36 @@ namespace ts {
declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}

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

Choose a reason for hiding this comment

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

This comment should be JSDoc comment


// 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;

let funcSymbol = container.locals.get(target.text);
Copy link
Contributor

Choose a reason for hiding this comment

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

So this symbol is not necessary only for function declaration but class expression as well? if so, could you rename the variable?

if (isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
funcSymbol = (funcSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
}

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

// Set up the members collection if it doesn't exist already
if (!funcSymbol.exports) {
funcSymbol.exports = createMap<Symbol>();
Copy link
Contributor

Choose a reason for hiding this comment

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

why you are using exports ? if there is more use to exports than "module exports" as the comment suggest (types.ts Line 2695) could you fix up the comment ?

Copy link
Member

Choose a reason for hiding this comment

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

Is it because this is the static side of the class, not the instance side? Exports is a confusing name in this case, then.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

that is where we keep the static properties on classes. members has the instance properties.

}

// Declare the method/property
declareSymbol(funcSymbol.exports, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
}

function bindCallExpression(node: CallExpression) {
// We're only inspecting call expressions to detect CommonJS modules, so we can skip
// this check if we've already seen the module indicator
Expand Down
40 changes: 26 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2171,13 +2171,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 @@ -4580,7 +4582,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 @@ -13886,10 +13888,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 @@ -19871,22 +19876,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 @@ -3133,7 +3133,9 @@
/// className.prototype.name = expr
PrototypeProperty,
/// this.name = expr
ThisProperty
ThisProperty,
// F.name = expr
Property
}

export interface FileExtensionInfo {
Expand Down
9 changes: 7 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1366,10 +1366,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 @@ -1398,6 +1398,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 @@ -1417,6 +1421,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