Skip to content

Commit e5594e6

Browse files
committed
Avoid bogus circularity error on context sensitive constructor property assignments
1 parent e53f19f commit e5594e6

File tree

5 files changed

+140
-0
lines changed

5 files changed

+140
-0
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25248,12 +25248,46 @@ namespace ts {
2524825248
}
2524925249
}
2525025250

25251+
/**
25252+
* Try to find a resolved symbol for an expression without also resolving it's type, as
25253+
* getSymbolAtLocation would (as that could be reentrant into contextual typing)
25254+
*/
25255+
function getSymbolForExpression(e: Expression) {
25256+
if (e.symbol) {
25257+
return e.symbol;
25258+
}
25259+
if (isIdentifier(e)) {
25260+
return getResolvedSymbol(e);
25261+
}
25262+
if (isPropertyAccessExpression(e)) {
25263+
const lhsType = getTypeOfExpression(e.expression);
25264+
const prop = isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText);
25265+
return prop;
25266+
}
25267+
return undefined;
25268+
25269+
function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) {
25270+
const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id);
25271+
const prop = lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol);
25272+
return prop;
25273+
}
25274+
}
25275+
2525125276
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
2525225277
// Don't do this for assignment declarations unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
2525325278
function getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined {
2525425279
const kind = getAssignmentDeclarationKind(binaryExpression);
2525525280
switch (kind) {
2525625281
case AssignmentDeclarationKind.None:
25282+
const lhsSymbol = getSymbolForExpression(binaryExpression.left);
25283+
const decl = lhsSymbol && lhsSymbol.valueDeclaration;
25284+
// Unannotated, uninitialized property declarations have a type implied by their usage in the constructor.
25285+
// We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case.
25286+
if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) {
25287+
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
25288+
return (overallAnnotation && getTypeFromTypeNode(overallAnnotation)) ||
25289+
(decl.initializer && getTypeOfExpression(binaryExpression.left));
25290+
}
2525725291
return getTypeOfExpression(binaryExpression.left);
2525825292
case AssignmentDeclarationKind.ThisProperty:
2525925293
return getContextualTypeForThisPropertyAssignment(binaryExpression);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [classAttributeInferenceTemplate.ts]
2+
class MyClass {
3+
property;
4+
property2;
5+
6+
constructor() {
7+
const variable = 'something'
8+
9+
this.property = `foo`; // Correctly inferred as `string`
10+
this.property2 = `foo-${variable}`; // Causes an error
11+
12+
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
13+
}
14+
}
15+
16+
//// [classAttributeInferenceTemplate.js]
17+
"use strict";
18+
var MyClass = /** @class */ (function () {
19+
function MyClass() {
20+
var variable = 'something';
21+
this.property = "foo"; // Correctly inferred as `string`
22+
this.property2 = "foo-" + variable; // Causes an error
23+
var localProperty = "foo-" + variable; // Correctly inferred as `string`
24+
}
25+
return MyClass;
26+
}());
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
=== tests/cases/compiler/classAttributeInferenceTemplate.ts ===
2+
class MyClass {
3+
>MyClass : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
4+
5+
property;
6+
>property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
7+
8+
property2;
9+
>property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
10+
11+
constructor() {
12+
const variable = 'something'
13+
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
14+
15+
this.property = `foo`; // Correctly inferred as `string`
16+
>this.property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
17+
>this : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
18+
>property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
19+
20+
this.property2 = `foo-${variable}`; // Causes an error
21+
>this.property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
22+
>this : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
23+
>property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
24+
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
25+
26+
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
27+
>localProperty : Symbol(localProperty, Decl(classAttributeInferenceTemplate.ts, 10, 13))
28+
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
29+
}
30+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/classAttributeInferenceTemplate.ts ===
2+
class MyClass {
3+
>MyClass : MyClass
4+
5+
property;
6+
>property : string
7+
8+
property2;
9+
>property2 : string
10+
11+
constructor() {
12+
const variable = 'something'
13+
>variable : "something"
14+
>'something' : "something"
15+
16+
this.property = `foo`; // Correctly inferred as `string`
17+
>this.property = `foo` : "foo"
18+
>this.property : string
19+
>this : this
20+
>property : string
21+
>`foo` : "foo"
22+
23+
this.property2 = `foo-${variable}`; // Causes an error
24+
>this.property2 = `foo-${variable}` : string
25+
>this.property2 : string
26+
>this : this
27+
>property2 : string
28+
>`foo-${variable}` : string
29+
>variable : "something"
30+
31+
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
32+
>localProperty : string
33+
>`foo-${variable}` : string
34+
>variable : "something"
35+
}
36+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @strict: true
2+
class MyClass {
3+
property;
4+
property2;
5+
6+
constructor() {
7+
const variable = 'something'
8+
9+
this.property = `foo`; // Correctly inferred as `string`
10+
this.property2 = `foo-${variable}`; // Causes an error
11+
12+
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
13+
}
14+
}

0 commit comments

Comments
 (0)