Avoid bogus circularity error on context sensitive constructor property assignments (#44601)

* Avoid bogus circularity error on context sensitive constructor property assignments

* Add JS case and ensure its fixed
This commit is contained in:
Wesley Wigham
2021-07-15 17:06:56 -07:00
committed by GitHub
parent 87cff4e3bb
commit 8268f2adec
8 changed files with 224 additions and 1 deletions

View File

@@ -25368,14 +25368,48 @@ namespace ts {
}
}
/**
* Try to find a resolved symbol for an expression without also resolving its type, as
* getSymbolAtLocation would (as that could be reentrant into contextual typing)
*/
function getSymbolForExpression(e: Expression) {
if (e.symbol) {
return e.symbol;
}
if (isIdentifier(e)) {
return getResolvedSymbol(e);
}
if (isPropertyAccessExpression(e)) {
const lhsType = getTypeOfExpression(e.expression);
return isPrivateIdentifier(e.name) ? tryGetPrivateIdentifierPropertyOfType(lhsType, e.name) : getPropertyOfType(lhsType, e.name.escapedText);
}
return undefined;
function tryGetPrivateIdentifierPropertyOfType(type: Type, id: PrivateIdentifier) {
const lexicallyScopedSymbol = lookupSymbolForPrivateIdentifierDeclaration(id.escapedText, id);
return lexicallyScopedSymbol && getPrivateIdentifierPropertyOfType(type, lexicallyScopedSymbol);
}
}
// 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 getContextualTypeForAssignmentDeclaration(binaryExpression: BinaryExpression): Type | undefined {
const kind = getAssignmentDeclarationKind(binaryExpression);
switch (kind) {
case AssignmentDeclarationKind.None:
return getTypeOfExpression(binaryExpression.left);
case AssignmentDeclarationKind.ThisProperty:
const lhsSymbol = getSymbolForExpression(binaryExpression.left);
const decl = lhsSymbol && lhsSymbol.valueDeclaration;
// Unannotated, uninitialized property declarations have a type implied by their usage in the constructor.
// We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case.
if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) {
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
return (overallAnnotation && getTypeFromTypeNode(overallAnnotation)) ||
(decl.initializer && getTypeOfExpression(binaryExpression.left));
}
if (kind === AssignmentDeclarationKind.None) {
return getTypeOfExpression(binaryExpression.left);
}
return getContextualTypeForThisPropertyAssignment(binaryExpression);
case AssignmentDeclarationKind.Property:
if (isPossiblyAliasedThisProperty(binaryExpression, kind)) {

View File

@@ -0,0 +1,26 @@
//// [classAttributeInferenceTemplate.ts]
class MyClass {
property;
property2;
constructor() {
const variable = 'something'
this.property = `foo`; // Correctly inferred as `string`
this.property2 = `foo-${variable}`; // Causes an error
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
}
}
//// [classAttributeInferenceTemplate.js]
"use strict";
var MyClass = /** @class */ (function () {
function MyClass() {
var variable = 'something';
this.property = "foo"; // Correctly inferred as `string`
this.property2 = "foo-" + variable; // Causes an error
var localProperty = "foo-" + variable; // Correctly inferred as `string`
}
return MyClass;
}());

View File

@@ -0,0 +1,30 @@
=== tests/cases/compiler/classAttributeInferenceTemplate.ts ===
class MyClass {
>MyClass : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
property;
>property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
property2;
>property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
constructor() {
const variable = 'something'
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
this.property = `foo`; // Correctly inferred as `string`
>this.property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
>this : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
>property : Symbol(MyClass.property, Decl(classAttributeInferenceTemplate.ts, 0, 15))
this.property2 = `foo-${variable}`; // Causes an error
>this.property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
>this : Symbol(MyClass, Decl(classAttributeInferenceTemplate.ts, 0, 0))
>property2 : Symbol(MyClass.property2, Decl(classAttributeInferenceTemplate.ts, 1, 13))
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
>localProperty : Symbol(localProperty, Decl(classAttributeInferenceTemplate.ts, 10, 13))
>variable : Symbol(variable, Decl(classAttributeInferenceTemplate.ts, 5, 13))
}
}

View File

@@ -0,0 +1,36 @@
=== tests/cases/compiler/classAttributeInferenceTemplate.ts ===
class MyClass {
>MyClass : MyClass
property;
>property : string
property2;
>property2 : string
constructor() {
const variable = 'something'
>variable : "something"
>'something' : "something"
this.property = `foo`; // Correctly inferred as `string`
>this.property = `foo` : "foo"
>this.property : string
>this : this
>property : string
>`foo` : "foo"
this.property2 = `foo-${variable}`; // Causes an error
>this.property2 = `foo-${variable}` : string
>this.property2 : string
>this : this
>property2 : string
>`foo-${variable}` : string
>variable : "something"
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
>localProperty : string
>`foo-${variable}` : string
>variable : "something"
}
}

View File

@@ -0,0 +1,30 @@
=== tests/cases/compiler/index.js ===
class MyClass {
>MyClass : Symbol(MyClass, Decl(index.js, 0, 0))
property;
>property : Symbol(MyClass.property, Decl(index.js, 0, 15))
property2;
>property2 : Symbol(MyClass.property2, Decl(index.js, 1, 13))
constructor() {
const variable = 'something'
>variable : Symbol(variable, Decl(index.js, 5, 13))
this.property = `foo`; // Correctly inferred as `string`
>this.property : Symbol(MyClass.property, Decl(index.js, 0, 15))
>this : Symbol(MyClass, Decl(index.js, 0, 0))
>property : Symbol(MyClass.property, Decl(index.js, 0, 15))
this.property2 = `foo-${variable}`; // Causes an error
>this.property2 : Symbol(MyClass.property2, Decl(index.js, 1, 13))
>this : Symbol(MyClass, Decl(index.js, 0, 0))
>property2 : Symbol(MyClass.property2, Decl(index.js, 1, 13))
>variable : Symbol(variable, Decl(index.js, 5, 13))
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
>localProperty : Symbol(localProperty, Decl(index.js, 10, 13))
>variable : Symbol(variable, Decl(index.js, 5, 13))
}
}

View File

@@ -0,0 +1,36 @@
=== tests/cases/compiler/index.js ===
class MyClass {
>MyClass : MyClass
property;
>property : string
property2;
>property2 : string
constructor() {
const variable = 'something'
>variable : "something"
>'something' : "something"
this.property = `foo`; // Correctly inferred as `string`
>this.property = `foo` : "foo"
>this.property : string
>this : this
>property : string
>`foo` : "foo"
this.property2 = `foo-${variable}`; // Causes an error
>this.property2 = `foo-${variable}` : string
>this.property2 : string
>this : this
>property2 : string
>`foo-${variable}` : string
>variable : "something"
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
>localProperty : string
>`foo-${variable}` : string
>variable : "something"
}
}

View File

@@ -0,0 +1,14 @@
// @strict: true
class MyClass {
property;
property2;
constructor() {
const variable = 'something'
this.property = `foo`; // Correctly inferred as `string`
this.property2 = `foo-${variable}`; // Causes an error
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
}
}

View File

@@ -0,0 +1,17 @@
// @noEmit: true
// @checkJs: true
// @strict: true
// @filename: index.js
class MyClass {
property;
property2;
constructor() {
const variable = 'something'
this.property = `foo`; // Correctly inferred as `string`
this.property2 = `foo-${variable}`; // Causes an error
const localProperty = `foo-${variable}`; // Correctly inferred as `string`
}
}