Skip to content

Commit 1ec71f0

Browse files
authored
Bind alias ThisProperty assignment declarations (#39908)
* Bind alias ThisProperty assignment declarations This is a quick prototype that does the wrong thing at the wrong time with the wrong technique. * Preliminary checker handling for aliases Duplicative and untested, but I think I updated all the places that need updating. * new is error; old one should not have been removed * I don't even know what's happening with this test * cleanup and testing in the checker * binder: use lookupSymbolForNameWorker instead of mutable This should have about the same behaviour and is much easier to understand. Also refactor common code a bit. * Shorter name of lookupSymbolForName Once upon a time there was a parent/worker function, but now it's just a single function again. No need for the -Worker suffix. * remove oodate comment * fix switch-case-break lint * Refactor and move functions * Rename and improve type of getContextualTypeForAssignmentDeclaration
1 parent 668bbc6 commit 1ec71f0

24 files changed

+637
-111
lines changed

src/compiler/binder.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2462,7 +2462,7 @@ namespace ts {
24622462
if (isInJSFile(expr) &&
24632463
file.commonJsModuleIndicator &&
24642464
isModuleExportsAccessExpression(expr) &&
2465-
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
2465+
!lookupSymbolForName(blockScopeContainer, "module" as __String)) {
24662466
declareSymbol(file.locals!, /*parent*/ undefined, expr.expression,
24672467
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
24682468
}
@@ -2486,6 +2486,14 @@ namespace ts {
24862486
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
24872487
break;
24882488
case AssignmentDeclarationKind.Property:
2489+
const expression = ((node as BinaryExpression).left as AccessExpression).expression;
2490+
if (isIdentifier(expression)) {
2491+
const symbol = lookupSymbolForName(blockScopeContainer, expression.escapedText);
2492+
if (isThisInitializedDeclaration(symbol?.valueDeclaration)) {
2493+
bindThisPropertyAssignment(node as BindablePropertyAssignmentExpression);
2494+
break;
2495+
}
2496+
}
24892497
bindSpecialPropertyAssignment(node as BindablePropertyAssignmentExpression);
24902498
break;
24912499
case AssignmentDeclarationKind.None:
@@ -3104,7 +3112,7 @@ namespace ts {
31043112

31053113
function lookupSymbolForPropertyAccess(node: BindableStaticNameExpression, lookupContainer: Node = container): Symbol | undefined {
31063114
if (isIdentifier(node)) {
3107-
return lookupSymbolForNameWorker(lookupContainer, node.escapedText);
3115+
return lookupSymbolForName(lookupContainer, node.escapedText);
31083116
}
31093117
else {
31103118
const symbol = lookupSymbolForPropertyAccess(node.expression);
@@ -3404,7 +3412,7 @@ namespace ts {
34043412
return true;
34053413
}
34063414
else if (isIdentifier(node)) {
3407-
const symbol = lookupSymbolForNameWorker(sourceFile, node.escapedText);
3415+
const symbol = lookupSymbolForName(sourceFile, node.escapedText);
34083416
if (!!symbol && !!symbol.valueDeclaration && isVariableDeclaration(symbol.valueDeclaration) && !!symbol.valueDeclaration.initializer) {
34093417
const init = symbol.valueDeclaration.initializer;
34103418
q.push(init);
@@ -3418,7 +3426,7 @@ namespace ts {
34183426
return false;
34193427
}
34203428

3421-
function lookupSymbolForNameWorker(container: Node, name: __String): Symbol | undefined {
3429+
function lookupSymbolForName(container: Node, name: __String): Symbol | undefined {
34223430
const local = container.locals && container.locals.get(name);
34233431
if (local) {
34243432
return local.exportSymbol || local;

src/compiler/checker.ts

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7896,9 +7896,10 @@ namespace ts {
78967896
if (symbol.valueDeclaration && isBinaryExpression(symbol.valueDeclaration)) {
78977897
const links = getSymbolLinks(symbol);
78987898
if (links.isConstructorDeclaredProperty === undefined) {
7899+
links.isConstructorDeclaredProperty = false;
78997900
links.isConstructorDeclaredProperty = !!getDeclaringConstructor(symbol) && every(symbol.declarations, declaration =>
79007901
isBinaryExpression(declaration) &&
7901-
getAssignmentDeclarationKind(declaration) === AssignmentDeclarationKind.ThisProperty &&
7902+
isPossiblyAliasedThisProperty(declaration) &&
79027903
(declaration.left.kind !== SyntaxKind.ElementAccessExpression || isStringOrNumericLiteralLike((<ElementAccessExpression>declaration.left).argumentExpression)) &&
79037904
!getAnnotatedTypeForAssignmentDeclaration(/*declaredType*/ undefined, declaration, symbol, declaration));
79047905
}
@@ -7975,7 +7976,7 @@ namespace ts {
79757976
const kind = isAccessExpression(expression)
79767977
? getAssignmentDeclarationPropertyAccessKind(expression)
79777978
: getAssignmentDeclarationKind(expression);
7978-
if (kind === AssignmentDeclarationKind.ThisProperty) {
7979+
if (kind === AssignmentDeclarationKind.ThisProperty || isBinaryExpression(expression) && isPossiblyAliasedThisProperty(expression, kind)) {
79797980
if (isDeclarationInConstructor(expression)) {
79807981
definedInConstructor = true;
79817982
}
@@ -9637,7 +9638,7 @@ namespace ts {
96379638
for (const member of decls) {
96389639
const assignmentKind = getAssignmentDeclarationKind(member as BinaryExpression | CallExpression);
96399640
const isInstanceMember = assignmentKind === AssignmentDeclarationKind.PrototypeProperty
9640-
|| assignmentKind === AssignmentDeclarationKind.ThisProperty
9641+
|| isBinaryExpression(member) && isPossiblyAliasedThisProperty(member, assignmentKind)
96419642
|| assignmentKind === AssignmentDeclarationKind.ObjectDefinePrototypeProperty
96429643
|| assignmentKind === AssignmentDeclarationKind.Prototype; // A straight `Prototype` assignment probably can never have a computed name
96439644
if (isStatic === !isInstanceMember && hasLateBindableName(member)) {
@@ -23125,14 +23126,7 @@ namespace ts {
2312523126
case SyntaxKind.AmpersandAmpersandEqualsToken:
2312623127
case SyntaxKind.BarBarEqualsToken:
2312723128
case SyntaxKind.QuestionQuestionEqualsToken:
23128-
if (node !== right) {
23129-
return undefined;
23130-
}
23131-
const contextSensitive = getIsContextSensitiveAssignmentOrContextType(binaryExpression);
23132-
if (!contextSensitive) {
23133-
return undefined;
23134-
}
23135-
return contextSensitive === true ? getTypeOfExpression(left) : contextSensitive;
23129+
return node === right ? getContextualTypeForAssignmentDeclaration(binaryExpression) : undefined;
2313623130
case SyntaxKind.BarBarToken:
2313723131
case SyntaxKind.QuestionQuestionToken:
2313823132
// When an || expression has a contextual type, the operands are contextually typed by that type, except
@@ -23153,24 +23147,27 @@ namespace ts {
2315323147

2315423148
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
2315523149
// Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
23156-
function getIsContextSensitiveAssignmentOrContextType(binaryExpression: BinaryExpression): boolean | Type {
23150+
function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined {
2315723151
const kind = getAssignmentDeclarationKind(binaryExpression);
2315823152
switch (kind) {
2315923153
case AssignmentDeclarationKind.None:
23160-
return true;
23154+
return getTypeOfExpression(binaryExpression.left);
2316123155
case AssignmentDeclarationKind.Property:
2316223156
case AssignmentDeclarationKind.ExportsProperty:
2316323157
case AssignmentDeclarationKind.Prototype:
2316423158
case AssignmentDeclarationKind.PrototypeProperty:
23159+
if (isPossiblyAliasedThisProperty(binaryExpression, kind)) {
23160+
return getContextualTypeForThisPropertyAssignment(binaryExpression, kind);
23161+
}
2316523162
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
2316623163
// See `bindStaticPropertyAssignment` in `binder.ts`.
23167-
if (!binaryExpression.left.symbol) {
23168-
return true;
23164+
else if (!binaryExpression.left.symbol) {
23165+
return getTypeOfExpression(binaryExpression.left);
2316923166
}
2317023167
else {
2317123168
const decl = binaryExpression.left.symbol.valueDeclaration;
2317223169
if (!decl) {
23173-
return false;
23170+
return undefined;
2317423171
}
2317523172
const lhs = cast(binaryExpression.left, isAccessExpression);
2317623173
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
@@ -23185,35 +23182,17 @@ namespace ts {
2318523182
if (annotated) {
2318623183
const nameStr = getElementOrPropertyAccessName(lhs);
2318723184
if (nameStr !== undefined) {
23188-
const type = getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
23189-
return type || false;
23185+
return getTypeOfPropertyOfContextualType(getTypeFromTypeNode(annotated), nameStr);
2319023186
}
2319123187
}
23192-
return false;
23188+
return undefined;
2319323189
}
2319423190
}
23195-
return !isInJSFile(decl);
23191+
return isInJSFile(decl) ? undefined : getTypeOfExpression(binaryExpression.left);
2319623192
}
2319723193
case AssignmentDeclarationKind.ModuleExports:
2319823194
case AssignmentDeclarationKind.ThisProperty:
23199-
if (!binaryExpression.symbol) return true;
23200-
if (binaryExpression.symbol.valueDeclaration) {
23201-
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
23202-
if (annotated) {
23203-
const type = getTypeFromTypeNode(annotated);
23204-
if (type) {
23205-
return type;
23206-
}
23207-
}
23208-
}
23209-
if (kind === AssignmentDeclarationKind.ModuleExports) return false;
23210-
const thisAccess = cast(binaryExpression.left, isAccessExpression);
23211-
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
23212-
return false;
23213-
}
23214-
const thisType = checkThisExpression(thisAccess.expression);
23215-
const nameStr = getElementOrPropertyAccessName(thisAccess);
23216-
return nameStr !== undefined && thisType && getTypeOfPropertyOfContextualType(thisType, nameStr) || false;
23195+
return getContextualTypeForThisPropertyAssignment(binaryExpression, kind);
2321723196
case AssignmentDeclarationKind.ObjectDefinePropertyValue:
2321823197
case AssignmentDeclarationKind.ObjectDefinePropertyExports:
2321923198
case AssignmentDeclarationKind.ObjectDefinePrototypeProperty:
@@ -23223,6 +23202,40 @@ namespace ts {
2322323202
}
2322423203
}
2322523204

23205+
function isPossiblyAliasedThisProperty(declaration: BinaryExpression, kind = getAssignmentDeclarationKind(declaration)) {
23206+
if (kind === AssignmentDeclarationKind.ThisProperty) {
23207+
return true;
23208+
}
23209+
if (!isInJSFile(declaration) || kind !== AssignmentDeclarationKind.Property || !isIdentifier((declaration.left as AccessExpression).expression)) {
23210+
return false;
23211+
}
23212+
const name = ((declaration.left as AccessExpression).expression as Identifier).escapedText;
23213+
const symbol = resolveName(declaration.left, name, SymbolFlags.Value, undefined, undefined, /*isUse*/ true, /*excludeGlobals*/ true);
23214+
return isThisInitializedDeclaration(symbol?.valueDeclaration);
23215+
}
23216+
23217+
function getContextualTypeForThisPropertyAssignment(binaryExpression: BinaryExpression, kind: AssignmentDeclarationKind): Type | undefined {
23218+
if (!binaryExpression.symbol) return getTypeOfExpression(binaryExpression.left);
23219+
if (binaryExpression.symbol.valueDeclaration) {
23220+
const annotated = getEffectiveTypeAnnotationNode(binaryExpression.symbol.valueDeclaration);
23221+
if (annotated) {
23222+
const type = getTypeFromTypeNode(annotated);
23223+
if (type) {
23224+
return type;
23225+
}
23226+
}
23227+
}
23228+
if (kind === AssignmentDeclarationKind.ModuleExports) return undefined;
23229+
const thisAccess = cast(binaryExpression.left, isAccessExpression);
23230+
if (!isObjectLiteralMethod(getThisContainer(thisAccess.expression, /*includeArrowFunctions*/ false))) {
23231+
return undefined;
23232+
}
23233+
const thisType = checkThisExpression(thisAccess.expression);
23234+
const nameStr = getElementOrPropertyAccessName(thisAccess);
23235+
return nameStr !== undefined && getTypeOfPropertyOfContextualType(thisType, nameStr) || undefined;
23236+
23237+
}
23238+
2322623239
function isCircularMappedProperty(symbol: Symbol) {
2322723240
return !!(getCheckFlags(symbol) & CheckFlags.Mapped && !(<MappedSymbol>symbol).type && findResolutionCycleStartIndex(symbol, TypeSystemPropertyName.Type) >= 0);
2322823241
}
@@ -25058,7 +25071,8 @@ namespace ts {
2505825071
}
2505925072

2506025073
function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
25061-
return isThisProperty(node) && (isAutoTypedProperty(prop) || isConstructorDeclaredProperty(prop)) && getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop);
25074+
return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop))
25075+
&& getThisContainer(node, /*includeArrowFunctions*/ true) === getDeclaringConstructor(prop);
2506225076
}
2506325077

2506425078
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier | PrivateIdentifier) {

src/compiler/utilities.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1614,6 +1614,10 @@ namespace ts {
16141614
&& (<PropertyAccessExpression | ElementAccessExpression>node).expression.kind === SyntaxKind.ThisKeyword;
16151615
}
16161616

1617+
export function isThisInitializedDeclaration(node: Node | undefined): boolean {
1618+
return !!node && isVariableDeclaration(node) && node.initializer?.kind === SyntaxKind.ThisKeyword;
1619+
}
1620+
16171621
export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNameExpression | undefined {
16181622
switch (node.kind) {
16191623
case SyntaxKind.TypeReference:
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
=== tests/cases/conformance/salsa/inferringClassMembersFromAssignments6.js ===
2+
function Foonly() {
3+
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
4+
5+
var self = this
6+
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 1, 7))
7+
>this : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
8+
9+
self.x = 1
10+
>self.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
11+
>self : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
12+
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
13+
14+
self.m = function() {
15+
>self.m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
16+
>self : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
17+
>m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
18+
19+
console.log(self.x)
20+
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
21+
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
22+
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
23+
>self.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
24+
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 1, 7))
25+
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
26+
}
27+
}
28+
Foonly.prototype.mreal = function() {
29+
>Foonly.prototype : Symbol(Foonly.mreal, Decl(inferringClassMembersFromAssignments6.js, 6, 1))
30+
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
31+
>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --))
32+
>mreal : Symbol(Foonly.mreal, Decl(inferringClassMembersFromAssignments6.js, 6, 1))
33+
34+
var self = this
35+
>self : Symbol(self, Decl(inferringClassMembersFromAssignments6.js, 8, 7))
36+
>this : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
37+
38+
self.y = 2
39+
>self.y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
40+
>self : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
41+
>y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
42+
}
43+
const foo = new Foonly()
44+
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
45+
>Foonly : Symbol(Foonly, Decl(inferringClassMembersFromAssignments6.js, 0, 0))
46+
47+
foo.x
48+
>foo.x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
49+
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
50+
>x : Symbol(Foonly.x, Decl(inferringClassMembersFromAssignments6.js, 1, 19))
51+
52+
foo.y
53+
>foo.y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
54+
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
55+
>y : Symbol(Foonly.y, Decl(inferringClassMembersFromAssignments6.js, 8, 19))
56+
57+
foo.m()
58+
>foo.m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
59+
>foo : Symbol(foo, Decl(inferringClassMembersFromAssignments6.js, 11, 5))
60+
>m : Symbol(Foonly.m, Decl(inferringClassMembersFromAssignments6.js, 2, 14))
61+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
=== tests/cases/conformance/salsa/inferringClassMembersFromAssignments6.js ===
2+
function Foonly() {
3+
>Foonly : typeof Foonly
4+
5+
var self = this
6+
>self : this
7+
>this : this
8+
9+
self.x = 1
10+
>self.x = 1 : 1
11+
>self.x : any
12+
>self : this
13+
>x : any
14+
>1 : 1
15+
16+
self.m = function() {
17+
>self.m = function() { console.log(self.x) } : () => void
18+
>self.m : any
19+
>self : this
20+
>m : any
21+
>function() { console.log(self.x) } : () => void
22+
23+
console.log(self.x)
24+
>console.log(self.x) : void
25+
>console.log : (...data: any[]) => void
26+
>console : Console
27+
>log : (...data: any[]) => void
28+
>self.x : number
29+
>self : this
30+
>x : number
31+
}
32+
}
33+
Foonly.prototype.mreal = function() {
34+
>Foonly.prototype.mreal = function() { var self = this self.y = 2} : () => void
35+
>Foonly.prototype.mreal : any
36+
>Foonly.prototype : any
37+
>Foonly : typeof Foonly
38+
>prototype : any
39+
>mreal : any
40+
>function() { var self = this self.y = 2} : () => void
41+
42+
var self = this
43+
>self : this
44+
>this : this
45+
46+
self.y = 2
47+
>self.y = 2 : 2
48+
>self.y : number | undefined
49+
>self : this
50+
>y : number | undefined
51+
>2 : 2
52+
}
53+
const foo = new Foonly()
54+
>foo : Foonly
55+
>new Foonly() : Foonly
56+
>Foonly : typeof Foonly
57+
58+
foo.x
59+
>foo.x : number
60+
>foo : Foonly
61+
>x : number
62+
63+
foo.y
64+
>foo.y : number | undefined
65+
>foo : Foonly
66+
>y : number | undefined
67+
68+
foo.m()
69+
>foo.m() : void
70+
>foo.m : () => void
71+
>foo : Foonly
72+
>m : () => void
73+

0 commit comments

Comments
 (0)