Skip to content
Open
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
42 changes: 36 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11360,6 +11360,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
!declaration.initializer && (noImplicitAny || isInJSFile(declaration));
}

function isThisPropertyAccessInAutoContainer(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
const declaration = Debug.checkDefined(prop.valueDeclaration);
if (hasStaticModifier(declaration)) {
if (!isThisProperty(node) || !isAutoTypedProperty(prop)) {
return false;
}
const autoContainer = getAutoContainer(node)!;
return isClassStaticBlockDeclaration(autoContainer) && (prop.parent && getClassLikeDeclarationOfSymbol(prop.parent)) === autoContainer.parent;
}
if (!isConstructorDeclaredProperty(prop) && (!isThisProperty(node) || !isAutoTypedProperty(prop))) {
return false;
}
const autoContainer = getAutoContainer(node)!;
return isConstructorDeclaration(autoContainer) && (prop.parent && getClassLikeDeclarationOfSymbol(prop.parent)) === autoContainer.parent || autoContainer === getDeclaringConstructor(prop);
}

function getAutoContainer(node: Node) {
let container = getContainingFunctionOrClassStaticBlock(node);
while (container) {
if (getImmediatelyInvokedFunctionExpression(container)) {
container = getContainingFunctionOrClassStaticBlock(container);
continue;
}
return container;
}
}

function getDeclaringConstructor(symbol: Symbol) {
if (!symbol.declarations) {
return;
Expand Down Expand Up @@ -18624,7 +18651,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (accessFlags & AccessFlags.CacheSymbol) {
getNodeLinks(accessNode!).resolvedSymbol = prop;
}
if (isThisPropertyAccessInConstructor(accessExpression, prop)) {
if (isThisPropertyAccessInDeclaringConstructor(accessExpression, prop)) {
return autoType;
}
}
Expand Down Expand Up @@ -31602,12 +31629,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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.
// Unannotated, uninitialized property declarations have a type implied by their usage in the constructor or static blocks.
// We avoid calling back into `getTypeOfExpression` and reentering contextual typing to avoid a bogus circularity error in that case when the assignment declaration is in the respective auto container
if (decl && (isPropertyDeclaration(decl) || isPropertySignature(decl))) {
const overallAnnotation = getEffectiveTypeAnnotationNode(decl);
return (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) ||
const type = (overallAnnotation && instantiateType(getTypeFromTypeNode(overallAnnotation), getSymbolLinks(lhsSymbol).mapper)) ||
(isPropertyDeclaration(decl) ? decl.initializer && getTypeOfExpression(binaryExpression.left) : undefined);
if (type || isAccessExpression(binaryExpression.left) && isThisPropertyAccessInAutoContainer(binaryExpression.left, lhsSymbol)) {
return type;
}
}
if (kind === AssignmentDeclarationKind.None) {
return getTypeOfExpression(binaryExpression.left);
Expand Down Expand Up @@ -34171,7 +34201,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return false;
}

function isThisPropertyAccessInConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
function isThisPropertyAccessInDeclaringConstructor(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol) {
return (isConstructorDeclaredProperty(prop) || isThisProperty(node) && isAutoTypedProperty(prop))
&& getThisContainer(node, /*includeArrowFunctions*/ true, /*includeClassComputedPropertyName*/ false) === getDeclaringConstructor(prop);
}
Expand Down Expand Up @@ -34291,7 +34321,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return errorType;
}

propType = isThisPropertyAccessInConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop);
propType = isThisPropertyAccessInDeclaringConstructor(node, prop) ? autoType : writeOnly || isWriteOnlyAccess(node) ? getWriteTypeOfSymbol(prop) : getTypeOfSymbol(prop);
}

return getFlowTypeOfAccessExpression(node, prop, propType, right, checkMode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ export class Cls {
this[0] = [seed];
}
}

export class Cls2 {
accessor x;
accessor y;
accessor z;

accessor 0;

constructor(seed: number) {
(() => {
this['x'] = [seed];
this['y'] = { seed };
this['z'] = `${seed}`;

this[0] = [seed];
})();
}
}


//// [classAccessorInitializationInferenceWithElementAccess1.js]
Expand All @@ -31,6 +49,20 @@ export class Cls {
this[0] = [seed];
}
}
export class Cls2 {
accessor x;
accessor y;
accessor z;
accessor 0;
constructor(seed) {
(() => {
this['x'] = [seed];
this['y'] = { seed };
this['z'] = `${seed}`;
this[0] = [seed];
})();
}
}


//// [classAccessorInitializationInferenceWithElementAccess1.d.ts]
Expand All @@ -43,3 +75,12 @@ export declare class Cls {
accessor 0: number[];
constructor(seed: number);
}
export declare class Cls2 {
accessor x: number[];
accessor y: {
seed: number;
};
accessor z: string;
accessor 0: number[];
constructor(seed: number);
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,46 @@ export class Cls {
}
}

export class Cls2 {
>Cls2 : Symbol(Cls2, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 14, 1))

accessor x;
>x : Symbol(Cls2.x, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 16, 19))

accessor y;
>y : Symbol(Cls2.y, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 17, 15))

accessor z;
>z : Symbol(Cls2.z, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 18, 15))

accessor 0;
>0 : Symbol(Cls2[0], Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 19, 15))

constructor(seed: number) {
>seed : Symbol(seed, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 23, 16))

(() => {
this['x'] = [seed];
>this : Symbol(Cls2, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 14, 1))
>'x' : Symbol(Cls2.x, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 16, 19))
>seed : Symbol(seed, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 23, 16))

this['y'] = { seed };
>this : Symbol(Cls2, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 14, 1))
>'y' : Symbol(Cls2.y, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 17, 15))
>seed : Symbol(seed, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 26, 25))

this['z'] = `${seed}`;
>this : Symbol(Cls2, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 14, 1))
>'z' : Symbol(Cls2.z, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 18, 15))
>seed : Symbol(seed, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 23, 16))

this[0] = [seed];
>this : Symbol(Cls2, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 14, 1))
>0 : Symbol(Cls2[0], Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 19, 15))
>seed : Symbol(seed, Decl(classAccessorInitializationInferenceWithElementAccess1.ts, 23, 16))

})();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,95 @@ export class Cls {
}
}

export class Cls2 {
>Cls2 : Cls2
> : ^^^^

accessor x;
>x : number[]
> : ^^^^^^^^

accessor y;
>y : { seed: number; }
> : ^^^^^^^^^^^^^^^^^

accessor z;
>z : string
> : ^^^^^^

accessor 0;
>0 : number[]
> : ^^^^^^^^

constructor(seed: number) {
>seed : number
> : ^^^^^^

(() => {
>(() => { this['x'] = [seed]; this['y'] = { seed }; this['z'] = `${seed}`; this[0] = [seed]; })() : void
> : ^^^^
>(() => { this['x'] = [seed]; this['y'] = { seed }; this['z'] = `${seed}`; this[0] = [seed]; }) : () => void
> : ^^^^^^^^^^
>() => { this['x'] = [seed]; this['y'] = { seed }; this['z'] = `${seed}`; this[0] = [seed]; } : () => void
> : ^^^^^^^^^^

this['x'] = [seed];
>this['x'] = [seed] : number[]
> : ^^^^^^^^
>this['x'] : number[]
> : ^^^^^^^^
>this : this
> : ^^^^
>'x' : "x"
> : ^^^
>[seed] : number[]
> : ^^^^^^^^
>seed : number
> : ^^^^^^

this['y'] = { seed };
>this['y'] = { seed } : { seed: number; }
> : ^^^^^^^^^^^^^^^^^
>this['y'] : { seed: number; }
> : ^^^^^^^^^^^^^^^^^
>this : this
> : ^^^^
>'y' : "y"
> : ^^^
>{ seed } : { seed: number; }
> : ^^^^^^^^^^^^^^^^^
>seed : number
> : ^^^^^^

this['z'] = `${seed}`;
>this['z'] = `${seed}` : string
> : ^^^^^^
>this['z'] : string
> : ^^^^^^
>this : this
> : ^^^^
>'z' : "z"
> : ^^^
>`${seed}` : string
> : ^^^^^^
>seed : number
> : ^^^^^^

this[0] = [seed];
>this[0] = [seed] : number[]
> : ^^^^^^^^
>this[0] : number[]
> : ^^^^^^^^
>this : this
> : ^^^^
>0 : 0
> : ^
>[seed] : number[]
> : ^^^^^^^^
>seed : number
> : ^^^^^^

})();
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
classAttributeInferenceContextualTypingOutsideOfConstructor1.ts(15,22): error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
classAttributeInferenceContextualTypingOutsideOfConstructor1.ts(21,20): error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
classAttributeInferenceContextualTypingOutsideOfConstructor1.ts(33,22): error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
classAttributeInferenceContextualTypingOutsideOfConstructor1.ts(39,20): error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?


==== classAttributeInferenceContextualTypingOutsideOfConstructor1.ts (4 errors) ====
// https://github.com/microsoft/TypeScript/issues/60394

type State = { type: "running"; speed: number } | { type: "stopped" };

declare const initialState: State;

class Actor1 {
private state;

constructor() {
this.state = initialState;

const localRun = (speed: number) => {
this.state = { type: "running", speed };
this.state = { type: "runnnning", speed }; // error
~~~~
!!! error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
!!! related TS6500 classAttributeInferenceContextualTypingOutsideOfConstructor1.ts:3:16: The expected type comes from property 'type' which is declared here on type 'State'
}
}

run(speed: number) {
this.state = { type: "running", speed };
this.state = { type: "runnnning", speed }; // error
~~~~
!!! error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
!!! related TS6500 classAttributeInferenceContextualTypingOutsideOfConstructor1.ts:3:16: The expected type comes from property 'type' which is declared here on type 'State'
}
}

class Actor2 {
accessor state;

constructor() {
this.state = initialState;

const localRun = (speed: number) => {
this.state = { type: "running", speed };
this.state = { type: "runnnning", speed }; // error
~~~~
!!! error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
!!! related TS6500 classAttributeInferenceContextualTypingOutsideOfConstructor1.ts:3:16: The expected type comes from property 'type' which is declared here on type 'State'
}
}

run(speed: number) {
this.state = { type: "running", speed };
this.state = { type: "runnnning", speed }; // error
~~~~
!!! error TS2820: Type '"runnnning"' is not assignable to type '"running" | "stopped"'. Did you mean '"running"'?
!!! related TS6500 classAttributeInferenceContextualTypingOutsideOfConstructor1.ts:3:16: The expected type comes from property 'type' which is declared here on type 'State'
}
}

Loading