mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
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:
@@ -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)) {
|
||||
|
||||
26
tests/baselines/reference/classAttributeInferenceTemplate.js
Normal file
26
tests/baselines/reference/classAttributeInferenceTemplate.js
Normal 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;
|
||||
}());
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
14
tests/cases/compiler/classAttributeInferenceTemplate.ts
Normal file
14
tests/cases/compiler/classAttributeInferenceTemplate.ts
Normal 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`
|
||||
}
|
||||
}
|
||||
17
tests/cases/compiler/classAttributeInferenceTemplateJS.ts
Normal file
17
tests/cases/compiler/classAttributeInferenceTemplateJS.ts
Normal 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`
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user