From ad0632a42a156b18e16c596de0aff184f51c5fff Mon Sep 17 00:00:00 2001 From: Arnav Nagzirkar <113314200+arnavnagzirkar@users.noreply.github.com> Date: Sun, 31 May 2026 20:18:40 -0700 Subject: [PATCH] fix: Incorrect report of self-referencing type for static fields Fixes microsoft/TypeScript#62552 --- src/compiler/checker.ts | 19 +++ ...nClassExpressionPassedToGenericFunction.js | 67 ++++++++++ ...sExpressionPassedToGenericFunction.symbols | 71 +++++++++++ ...assExpressionPassedToGenericFunction.types | 119 ++++++++++++++++++ ...nClassExpressionPassedToGenericFunction.ts | 35 ++++++ 5 files changed, 311 insertions(+) create mode 100644 tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.js create mode 100644 tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.symbols create mode 100644 tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.types create mode 100644 tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0567712f11da3..f3c3f6dd9ace7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11512,6 +11512,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return -1; } + // Checks the entire resolution stack (including entries hidden by resolutionStart) for a symbol. + // Used to prevent false circularity errors when a contextual type lookup would attempt to resolve + // a symbol whose type is already being resolved in an outer scope. + function isSymbolTypeResolutionInProgress(symbol: Symbol): boolean { + for (let i = resolutionTargets.length - 1; i >= 0; i--) { + if (resolutionPropertyNames[i] === TypeSystemPropertyName.Type && resolutionTargets[i] === symbol) { + return true; + } + } + return false; + } + function resolutionTargetHasProperty(target: TypeSystemEntity, propertyName: TypeSystemPropertyName): boolean { switch (propertyName) { case TypeSystemPropertyName.Type: @@ -32452,6 +32464,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!prop || isCircularMappedProperty(prop)) { return; } + // If the property's type resolution is already in progress (possibly hidden by resolutionStart + // due to an enclosing getResolvedSignature call), avoid calling getTypeOfSymbol. Doing so would + // cause a false circularity error for static fields of class expressions passed to generic functions + // (see GH#62552). + if (isSymbolTypeResolutionInProgress(prop)) { + return; + } return removeMissingType(getTypeOfSymbol(prop), !!(prop.flags & SymbolFlags.Optional)); } diff --git a/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.js b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.js new file mode 100644 index 0000000000000..609cd2280d237 --- /dev/null +++ b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.js @@ -0,0 +1,67 @@ +//// [tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts] //// + +//// [staticFieldInClassExpressionPassedToGenericFunction.ts] +// Repro from #62552: static fields in class expressions passed to generic functions +// should not incorrectly report TS7022 (implicitly has type 'any' because it does +// not have a type annotation and is referenced directly or indirectly in its own initializer) + +function id(x: T): T { + return x; +} + +// Should not error (was incorrectly reporting TS7022 on 'foo') +const Foo = id(class { + static readonly foo = id(42); +}); + +// Confirm the inferred type is correct +const Foo2 = id(class { + static count = 0; + static name2 = "test"; +}); + +// Variants with multiple static fields +const Foo3 = id(class { + static a = 1; + static b = "hello"; + static c = true; +}); + +// No error without generic wrapper +const Ok = class { + static readonly foo = id(42); +}; + +// Static field referencing another static field of the same class (real circularity, should still error) +// (This is a true self-reference, not the false positive from #62552) + + +//// [staticFieldInClassExpressionPassedToGenericFunction.js] +"use strict"; +// Repro from #62552: static fields in class expressions passed to generic functions +// should not incorrectly report TS7022 (implicitly has type 'any' because it does +// not have a type annotation and is referenced directly or indirectly in its own initializer) +function id(x) { + return x; +} +// Should not error (was incorrectly reporting TS7022 on 'foo') +const Foo = id(class { + static foo = id(42); +}); +// Confirm the inferred type is correct +const Foo2 = id(class { + static count = 0; + static name2 = "test"; +}); +// Variants with multiple static fields +const Foo3 = id(class { + static a = 1; + static b = "hello"; + static c = true; +}); +// No error without generic wrapper +const Ok = class { + static foo = id(42); +}; +// Static field referencing another static field of the same class (real circularity, should still error) +// (This is a true self-reference, not the false positive from #62552) diff --git a/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.symbols b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.symbols new file mode 100644 index 0000000000000..3b4fbc30c6bb5 --- /dev/null +++ b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.symbols @@ -0,0 +1,71 @@ +//// [tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts] //// + +=== staticFieldInClassExpressionPassedToGenericFunction.ts === +// Repro from #62552: static fields in class expressions passed to generic functions +// should not incorrectly report TS7022 (implicitly has type 'any' because it does +// not have a type annotation and is referenced directly or indirectly in its own initializer) + +function id(x: T): T { +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) +>T : Symbol(T, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 4, 12)) +>x : Symbol(x, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 4, 15)) +>T : Symbol(T, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 4, 12)) +>T : Symbol(T, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 4, 12)) + + return x; +>x : Symbol(x, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 4, 15)) +} + +// Should not error (was incorrectly reporting TS7022 on 'foo') +const Foo = id(class { +>Foo : Symbol(Foo, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 9, 5)) +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) + + static readonly foo = id(42); +>foo : Symbol((Anonymous class).foo, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 9, 22)) +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) + +}); + +// Confirm the inferred type is correct +const Foo2 = id(class { +>Foo2 : Symbol(Foo2, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 14, 5)) +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) + + static count = 0; +>count : Symbol((Anonymous class).count, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 14, 23)) + + static name2 = "test"; +>name2 : Symbol((Anonymous class).name2, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 15, 21)) + +}); + +// Variants with multiple static fields +const Foo3 = id(class { +>Foo3 : Symbol(Foo3, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 20, 5)) +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) + + static a = 1; +>a : Symbol((Anonymous class).a, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 20, 23)) + + static b = "hello"; +>b : Symbol((Anonymous class).b, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 21, 17)) + + static c = true; +>c : Symbol((Anonymous class).c, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 22, 23)) + +}); + +// No error without generic wrapper +const Ok = class { +>Ok : Symbol(Ok, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 27, 5)) + + static readonly foo = id(42); +>foo : Symbol(Ok.foo, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 27, 18)) +>id : Symbol(id, Decl(staticFieldInClassExpressionPassedToGenericFunction.ts, 0, 0)) + +}; + +// Static field referencing another static field of the same class (real circularity, should still error) +// (This is a true self-reference, not the false positive from #62552) + diff --git a/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.types b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.types new file mode 100644 index 0000000000000..f3e5b0a6d84d1 --- /dev/null +++ b/tests/baselines/reference/staticFieldInClassExpressionPassedToGenericFunction.types @@ -0,0 +1,119 @@ +//// [tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts] //// + +=== staticFieldInClassExpressionPassedToGenericFunction.ts === +// Repro from #62552: static fields in class expressions passed to generic functions +// should not incorrectly report TS7022 (implicitly has type 'any' because it does +// not have a type annotation and is referenced directly or indirectly in its own initializer) + +function id(x: T): T { +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>x : T +> : ^ + + return x; +>x : T +> : ^ +} + +// Should not error (was incorrectly reporting TS7022 on 'foo') +const Foo = id(class { +>Foo : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id(class { static readonly foo = id(42);}) : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>class { static readonly foo = id(42);} : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + + static readonly foo = id(42); +>foo : 42 +> : ^^ +>id(42) : 42 +> : ^^ +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>42 : 42 +> : ^^ + +}); + +// Confirm the inferred type is correct +const Foo2 = id(class { +>Foo2 : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id(class { static count = 0; static name2 = "test";}) : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>class { static count = 0; static name2 = "test";} : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + + static count = 0; +>count : number +> : ^^^^^^ +>0 : 0 +> : ^ + + static name2 = "test"; +>name2 : string +> : ^^^^^^ +>"test" : "test" +> : ^^^^^^ + +}); + +// Variants with multiple static fields +const Foo3 = id(class { +>Foo3 : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id(class { static a = 1; static b = "hello"; static c = true;}) : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>class { static a = 1; static b = "hello"; static c = true;} : typeof (Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^ + + static a = 1; +>a : number +> : ^^^^^^ +>1 : 1 +> : ^ + + static b = "hello"; +>b : string +> : ^^^^^^ +>"hello" : "hello" +> : ^^^^^^^ + + static c = true; +>c : boolean +> : ^^^^^^^ +>true : true +> : ^^^^ + +}); + +// No error without generic wrapper +const Ok = class { +>Ok : typeof Ok +> : ^^^^^^^^^ +>class { static readonly foo = id(42);} : typeof Ok +> : ^^^^^^^^^ + + static readonly foo = id(42); +>foo : 42 +> : ^^ +>id(42) : 42 +> : ^^ +>id : (x: T) => T +> : ^ ^^ ^^ ^^^^^ +>42 : 42 +> : ^^ + +}; + +// Static field referencing another static field of the same class (real circularity, should still error) +// (This is a true self-reference, not the false positive from #62552) + diff --git a/tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts b/tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts new file mode 100644 index 0000000000000..724ca9b563321 --- /dev/null +++ b/tests/cases/compiler/staticFieldInClassExpressionPassedToGenericFunction.ts @@ -0,0 +1,35 @@ +// @noImplicitAny: true + +// Repro from #62552: static fields in class expressions passed to generic functions +// should not incorrectly report TS7022 (implicitly has type 'any' because it does +// not have a type annotation and is referenced directly or indirectly in its own initializer) + +function id(x: T): T { + return x; +} + +// Should not error (was incorrectly reporting TS7022 on 'foo') +const Foo = id(class { + static readonly foo = id(42); +}); + +// Confirm the inferred type is correct +const Foo2 = id(class { + static count = 0; + static name2 = "test"; +}); + +// Variants with multiple static fields +const Foo3 = id(class { + static a = 1; + static b = "hello"; + static c = true; +}); + +// No error without generic wrapper +const Ok = class { + static readonly foo = id(42); +}; + +// Static field referencing another static field of the same class (real circularity, should still error) +// (This is a true self-reference, not the false positive from #62552)