Skip to content

Bind alias ThisProperty assignment declarations #39908

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 15 commits into from
Aug 10, 2020
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
16 changes: 12 additions & 4 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2462,7 +2462,7 @@ namespace ts {
if (isInJSFile(expr) &&
file.commonJsModuleIndicator &&
isModuleExportsAccessExpression(expr) &&
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
!lookupSymbolForName(blockScopeContainer, "module" as __String)) {
declareSymbol(file.locals!, /*parent*/ undefined, expr.expression,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
Expand All @@ -2486,6 +2486,14 @@ namespace ts {
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.Property:
const expression = ((node as BinaryExpression).left as AccessExpression).expression;
if (isIdentifier(expression)) {
const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText);
if (isThisInitializedDeclaration(symbol?.valueDeclaration)) {
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
}
}
bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression);
break;
case AssignmentDeclarationKind.None:
Expand Down Expand Up @@ -3104,7 +3112,7 @@ namespace ts {

function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined {
if (isIdentifier(node)) {
return lookupSymbolForNameWorker(lookupContainer, node.escapedText);
return lookupSymbolForName(lookupContainer, node.escapedText);
}
else {
const symbol = lookupSymbolForPropertyAccess(node.expression);
Expand Down Expand Up @@ -3404,7 +3412,7 @@ namespace ts {
return true;
}
else if (isIdentifier(node)) {
const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText);
const symbol = lookupSymbolForName(sourceFile, node.escapedText);
if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) {
const init = symbol.valueDeclaration.initializer;
q.push(init);
Expand All @@ -3418,7 +3426,7 @@ namespace ts {
return false;
}

function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined {
function lookupSymbolForName(container: Node, name: __String): Symbol | undefined {
const local = container.locals && container.locals.get(name);
if (local) {
return local.exportSymbol || local;
Expand Down
92 changes: 53 additions & 39 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7896,9 +7896,10 @@ namespace ts {
if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) {
const links = getSymbolLinks(symbol);
if (links.isConstructorDeclaredProperty === undefined) {
links.isConstructorDeclaredProperty = false;
links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration =>
isBinaryExpression(declaration) &&
getAssignmentDeclarationKind(declaration) === AssignmentDeclarationKind.ThisProperty &&
isPossiblyAliasedThisProperty(declaration) &&
(declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((<ElementAccessExpression>declaration.left).argumentExpression)) &&
!getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration));
}
Expand Down Expand Up @@ -7975,7 +7976,7 @@ namespace ts {
const kind = isAccessExpression(expression)
? getAssignmentDeclarationPropertyAccessKind(expression)
: getAssignmentDeclarationKind(expression);
if (kind === AssignmentDeclarationKind.ThisProperty) {
if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) {
if (isDeclarationInConstructor(expression)) {
definedInConstructor = true;
}
Expand Down Expand Up @@ -9637,7 +9638,7 @@ namespace ts {
for (const member of decls) {
const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression);
const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty
|| assignmentKind === AssignmentDeclarationKind.ThisProperty
|| isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind)
|| assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
|| assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name
if (isStatic === !isInstanceMember && hasLateBindableName(member)) {
Expand Down Expand Up @@ -23125,14 +23126,7 @@ namespace ts {
case SyntaxKind.AmpersandAmpersandEqualsToken:
case SyntaxKind.BarBarEqualsToken:
case SyntaxKind.QuestionQuestionEqualsToken:
if (node !== right) {
return undefined;
}
const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression);
if (!contextSensitive) {
return undefined;
}
return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined;
case SyntaxKind.BarBarToken:
case SyntaxKind.QuestionQuestionToken:
// When an || expression has a contextual type, the operands are contextually typed by that type, except
Expand All @@ -23153,24 +23147,27 @@ namespace ts {

// In an assignment expression, the right operand is contextually typed by the type of the left operand.
// Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type {
function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined {
const kind = getAssignmentDeclarationKind(binaryExpression);
switch (kind) {
case AssignmentDeclarationKind.None:
return true;
return getTypeOfExpression(binaryExpression.left);
case AssignmentDeclarationKind.Property:
case AssignmentDeclarationKind.ExportsProperty:
case AssignmentDeclarationKind.Prototype:
case AssignmentDeclarationKind.PrototypeProperty:
if (isPossiblyAliasedThisProperty(binaryExpression, kind)) {
return getContextualTypeForThisPropertyAssignment(binaryExpression, kind);
}
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
// See `bindStaticPropertyAssignment` in `binder.ts`.
if (!binaryExpression.left.symbol) {
return true;
else if (!binaryExpression.left.symbol) {
return getTypeOfExpression(binaryExpression.left);
}
else {
const decl = binaryExpression.left.symbol.valueDeclaration;
if (!decl) {
return false;
return undefined;
}
const lhs = cast(binaryExpression.left, isAccessExpression);
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
Expand All @@ -23185,35 +23182,17 @@ namespace ts {
if (annotated) {
const nameStr = getElementOrPropertyAccessName(lhs);
if (nameStr !== undefined) {
const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
return type || false;
return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
}
}
return false;
return undefined;
}
}
return !isInJSFile(decl);
return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left);
}
case AssignmentDeclarationKind.ModuleExports:
case AssignmentDeclarationKind.ThisProperty:
if (!binaryExpression.symbol) return true;
if (binaryExpression.symbol.valueDeclaration) {
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
if (annotated) {
const type = getTypeFromTypeNode(annotated);
if (type) {
return type;
}
}
}
if (kind === AssignmentDeclarationKind.ModuleExports) return false;
const thisAccess = cast(binaryExpression.left, isAccessExpression);
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
return false;
}
const thisType = checkThisExpression(thisAccess.expression);
const nameStr = getElementOrPropertyAccessName(thisAccess);
return nameStr !== undefined && thisType && getTypeOfPropertyOfContextualType(thisType, nameStr) || false;
return getContextualTypeForThisPropertyAssignment(binaryExpression, kind);
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
Expand All @@ -23223,6 +23202,40 @@ namespace ts {
}
}

function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) {
if (kind === AssignmentDeclarationKind.ThisProperty) {
return true;
}
if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) {
return false;
}
const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText;
const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true);
return isThisInitializedDeclaration(symbol?.valueDeclaration);
}

function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined {
if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left);
if (binaryExpression.symbol.valueDeclaration) {
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
if (annotated) {
const type = getTypeFromTypeNode(annotated);
if (type) {
return type;
}
Copy link
Member

Choose a reason for hiding this comment

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

This string of words doesn’t grok for me, maybe because “get” and ”is” are usually mutually exclusive (though I see this is inherited from the caller), and ContextSensitive parses as “context-sensitive” (an adjective), but the “of” makes me think “context” is the noun subject and “sensitive” is the adjective we’re asking about?

Copy link
Member Author

Choose a reason for hiding this comment

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

This led to an in-person discussion in which we noticed that the parent getIsContextSensitive... function is a bad member of the getContextualTypeFor... family. I just pushed a commit that makes the whole thing return Type | undefined like the rest.

}
}
if (kind === AssignmentDeclarationKind.ModuleExports) return undefined;
const thisAccess = cast(binaryExpression.left, isAccessExpression);
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
return undefined;
}
const thisType = checkThisExpression(thisAccess.expression);
const nameStr = getElementOrPropertyAccessName(thisAccess);
return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined;

}

function isCircularMappedProperty(symbol: Symbol) {
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(<MappedSymbol>symbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
}
Expand Down Expand Up @@ -25058,7 +25071,8 @@ namespace ts {
}

function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
return isThisProperty(node) && (isAutoTypedProperty(prop) || isConstructorDeclaredProperty(prop)) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop);
return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop))
&& getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop);
}

function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1614,6 +1614,10 @@ namespace ts {
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.ThisKeyword;
}

export function isThisInitializedDeclaration(node: Node | undefined): boolean {
return !!node && isVariableDeclaration(node) && node.initializer?.kind === SyntaxKind.ThisKeyword;
}

export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression | undefined {
switch (node.kind) {
case SyntaxKind.TypeReference:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
=== tests/cases/conformance/salsa/inferringClassMembersFromAssignments6.js ===
function Foonly() {
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))

var self = this
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 1, 7))
>this : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))

self.x = 1
>self.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
>self : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))

self.m = function() {
>self.m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
>self : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
>m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))

console.log(self.x)
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>self.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 1, 7))
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
}
}
Foonly.prototype.mreal = function() {
>Foonly.prototype : Symbol(Foonly.mreal, Decl(inferringClassMembersFromAssignments6.js, 6, 1))
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
>mreal : Symbol(Foonly.mreal, Decl(inferringClassMembersFromAssignments6.js, 6, 1))

var self = this
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 8, 7))
>this : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))

self.y = 2
>self.y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
>self : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
>y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
}
const foo = new Foonly()
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))

foo.x
>foo.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))

foo.y
>foo.y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
>y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))

foo.m()
>foo.m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
>m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
=== tests/cases/conformance/salsa/inferringClassMembersFromAssignments6.js ===
function Foonly() {
>Foonly : typeof Foonly

var self = this
>self : this
>this : this

self.x = 1
>self.x = 1 : 1
>self.x : any
>self : this
>x : any
>1 : 1

self.m = function() {
>self.m = function() { console.log(self.x) } : () => void
>self.m : any
>self : this
>m : any
>function() { console.log(self.x) } : () => void

console.log(self.x)
>console.log(self.x) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>self.x : number
>self : this
>x : number
}
}
Foonly.prototype.mreal = function() {
>Foonly.prototype.mreal = function() { var self = this self.y = 2} : () => void
>Foonly.prototype.mreal : any
>Foonly.prototype : any
>Foonly : typeof Foonly
>prototype : any
>mreal : any
>function() { var self = this self.y = 2} : () => void

var self = this
>self : this
>this : this

self.y = 2
>self.y = 2 : 2
>self.y : number | undefined
>self : this
>y : number | undefined
>2 : 2
}
const foo = new Foonly()
>foo : Foonly
>new Foonly() : Foonly
>Foonly : typeof Foonly

foo.x
>foo.x : number
>foo : Foonly
>x : number

foo.y
>foo.y : number | undefined
>foo : Foonly
>y : number | undefined

foo.m()
>foo.m() : void
>foo.m : () => void
>foo : Foonly
>m : () => void

Loading