From e310261e0d78005fcc375e2f7bed080ca77a6e25 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 24 Dec 2025 11:26:55 -1000 Subject: [PATCH 1/5] Fix issue and back-port typescript-go #2376 --- src/compiler/checker.ts | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 51c9493b61721..f3823f5fffd96 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13212,6 +13212,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getBaseTypes(type: InterfaceType): BaseType[] { + if (!(getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference))) { + return emptyArray + } if (!type.baseTypesResolved) { if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { if (type.objectFlags & ObjectFlags.Tuple) { @@ -35053,28 +35056,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration. */ function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { - if (!(prop.parent!.flags & SymbolFlags.Class)) { - return false; - } - let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType; - while (true) { - classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined; - if (!classType) { - return false; - } - const superProperty = getPropertyOfType(classType, prop.escapedName); - if (superProperty && superProperty.valueDeclaration) { - return true; + if (prop.parent && prop.parent.flags & SymbolFlags.Class) { + const baseTypes = getBaseTypes(getDeclaredTypeOfSymbol(prop.parent) as InterfaceType) + if (baseTypes.length) { + const superProperty = getPropertyOfType(baseTypes[0], prop.escapedName) + return !!(superProperty && superProperty.valueDeclaration) } } - } - - function getSuperClass(classType: InterfaceType): Type | undefined { - const x = getBaseTypes(classType); - if (x.length === 0) { - return undefined; - } - return getIntersectionType(x); + return false } function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) { From 339a3d73c9c936650ed88c2793272257c53a97df Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 24 Dec 2025 11:27:26 -1000 Subject: [PATCH 2/5] Add regression test from typescript-go #2376 --- tests/cases/compiler/checkInheritedProperty.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 tests/cases/compiler/checkInheritedProperty.ts diff --git a/tests/cases/compiler/checkInheritedProperty.ts b/tests/cases/compiler/checkInheritedProperty.ts new file mode 100644 index 0000000000000..12960b990c5cc --- /dev/null +++ b/tests/cases/compiler/checkInheritedProperty.ts @@ -0,0 +1,12 @@ +// @strict: true +// @noEmit: true + +class Base { +} + +declare const BaseFactory: new() => Base & { c: string } + +class Derived extends BaseFactory { + a = this.b + b = "abc" +} From 5f6af5a70a9b843ab16c11b781a0904f82d846ba Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 24 Dec 2025 11:27:48 -1000 Subject: [PATCH 3/5] Accept new baselines --- .../checkInheritedProperty.errors.txt | 17 +++++++++ .../reference/checkInheritedProperty.symbols | 26 +++++++++++++ .../reference/checkInheritedProperty.types | 37 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/baselines/reference/checkInheritedProperty.errors.txt create mode 100644 tests/baselines/reference/checkInheritedProperty.symbols create mode 100644 tests/baselines/reference/checkInheritedProperty.types diff --git a/tests/baselines/reference/checkInheritedProperty.errors.txt b/tests/baselines/reference/checkInheritedProperty.errors.txt new file mode 100644 index 0000000000000..86c262303dd4d --- /dev/null +++ b/tests/baselines/reference/checkInheritedProperty.errors.txt @@ -0,0 +1,17 @@ +checkInheritedProperty.ts(7,14): error TS2729: Property 'b' is used before its initialization. + + +==== checkInheritedProperty.ts (1 errors) ==== + class Base { + } + + declare const BaseFactory: new() => Base & { c: string } + + class Derived extends BaseFactory { + a = this.b + ~ +!!! error TS2729: Property 'b' is used before its initialization. +!!! related TS2728 checkInheritedProperty.ts:8:5: 'b' is declared here. + b = "abc" + } + \ No newline at end of file diff --git a/tests/baselines/reference/checkInheritedProperty.symbols b/tests/baselines/reference/checkInheritedProperty.symbols new file mode 100644 index 0000000000000..c977d078ae24b --- /dev/null +++ b/tests/baselines/reference/checkInheritedProperty.symbols @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/checkInheritedProperty.ts] //// + +=== checkInheritedProperty.ts === +class Base { +>Base : Symbol(Base, Decl(checkInheritedProperty.ts, 0, 0)) +} + +declare const BaseFactory: new() => Base & { c: string } +>BaseFactory : Symbol(BaseFactory, Decl(checkInheritedProperty.ts, 3, 13)) +>Base : Symbol(Base, Decl(checkInheritedProperty.ts, 0, 0)) +>c : Symbol(c, Decl(checkInheritedProperty.ts, 3, 44)) + +class Derived extends BaseFactory { +>Derived : Symbol(Derived, Decl(checkInheritedProperty.ts, 3, 56)) +>BaseFactory : Symbol(BaseFactory, Decl(checkInheritedProperty.ts, 3, 13)) + + a = this.b +>a : Symbol(Derived.a, Decl(checkInheritedProperty.ts, 5, 35)) +>this.b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14)) +>this : Symbol(Derived, Decl(checkInheritedProperty.ts, 3, 56)) +>b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14)) + + b = "abc" +>b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14)) +} + diff --git a/tests/baselines/reference/checkInheritedProperty.types b/tests/baselines/reference/checkInheritedProperty.types new file mode 100644 index 0000000000000..5abc92638b5a5 --- /dev/null +++ b/tests/baselines/reference/checkInheritedProperty.types @@ -0,0 +1,37 @@ +//// [tests/cases/compiler/checkInheritedProperty.ts] //// + +=== checkInheritedProperty.ts === +class Base { +>Base : Base +> : ^^^^ +} + +declare const BaseFactory: new() => Base & { c: string } +>BaseFactory : new () => Base & { c: string; } +> : ^^^^^^^^^^ +>c : string +> : ^^^^^^ + +class Derived extends BaseFactory { +>Derived : Derived +> : ^^^^^^^ +>BaseFactory : Base & { c: string; } +> : ^^^^^^^^^^^^ ^^^ + + a = this.b +>a : string +> : ^^^^^^ +>this.b : string +> : ^^^^^^ +>this : this +> : ^^^^ +>b : string +> : ^^^^^^ + + b = "abc" +>b : string +> : ^^^^^^ +>"abc" : "abc" +> : ^^^^^ +} + From b6a6c781188ab2b882c1fe6db5cfb3acf11f68c0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 25 Dec 2025 17:13:55 -1000 Subject: [PATCH 4/5] Add regression test --- .../reference/noCrashOnMixin2.errors.txt | 41 +++++++++++ .../reference/noCrashOnMixin2.symbols | 53 +++++++++++++++ .../baselines/reference/noCrashOnMixin2.types | 68 +++++++++++++++++++ tests/cases/compiler/noCrashOnMixin2.ts | 28 ++++++++ 4 files changed, 190 insertions(+) create mode 100644 tests/baselines/reference/noCrashOnMixin2.errors.txt create mode 100644 tests/baselines/reference/noCrashOnMixin2.symbols create mode 100644 tests/baselines/reference/noCrashOnMixin2.types create mode 100644 tests/cases/compiler/noCrashOnMixin2.ts diff --git a/tests/baselines/reference/noCrashOnMixin2.errors.txt b/tests/baselines/reference/noCrashOnMixin2.errors.txt new file mode 100644 index 0000000000000..d7e28ae49b003 --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin2.errors.txt @@ -0,0 +1,41 @@ +noCrashOnMixin2.ts(11,33): error TS2370: A rest parameter must be of an array type. +noCrashOnMixin2.ts(11,40): error TS1047: A rest parameter cannot be optional. +noCrashOnMixin2.ts(14,12): error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'. +noCrashOnMixin2.ts(23,9): error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration. + + +==== noCrashOnMixin2.ts (4 errors) ==== + // https://github.com/microsoft/TypeScript/issues/62921 + + class Abstract { + protected constructor() { + } + } + + class Concrete extends Abstract { + } + + type Constructor = new (...args?: any[]) => T; + ~~~~~~~~~~~~~~~ +!!! error TS2370: A rest parameter must be of an array type. + ~ +!!! error TS1047: A rest parameter cannot be optional. + + function Mixin(Base: TBase) { + return class extends Base { + ~~~~~ +!!! error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'. + }; + } + + class Empty { + } + + class CrashTrigger extends Mixin(Empty) { + public trigger() { + new Concrete(); + ~~~~~~~~~~~~~~ +!!! error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration. + } + } + \ No newline at end of file diff --git a/tests/baselines/reference/noCrashOnMixin2.symbols b/tests/baselines/reference/noCrashOnMixin2.symbols new file mode 100644 index 0000000000000..428e2e5de280f --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin2.symbols @@ -0,0 +1,53 @@ +//// [tests/cases/compiler/noCrashOnMixin2.ts] //// + +=== noCrashOnMixin2.ts === +// https://github.com/microsoft/TypeScript/issues/62921 + +class Abstract { +>Abstract : Symbol(Abstract, Decl(noCrashOnMixin2.ts, 0, 0)) + + protected constructor() { + } +} + +class Concrete extends Abstract { +>Concrete : Symbol(Concrete, Decl(noCrashOnMixin2.ts, 5, 1)) +>Abstract : Symbol(Abstract, Decl(noCrashOnMixin2.ts, 0, 0)) +} + +type Constructor = new (...args?: any[]) => T; +>Constructor : Symbol(Constructor, Decl(noCrashOnMixin2.ts, 8, 1)) +>T : Symbol(T, Decl(noCrashOnMixin2.ts, 10, 17)) +>args : Symbol(args, Decl(noCrashOnMixin2.ts, 10, 32)) +>T : Symbol(T, Decl(noCrashOnMixin2.ts, 10, 17)) + +function Mixin(Base: TBase) { +>Mixin : Symbol(Mixin, Decl(noCrashOnMixin2.ts, 10, 54)) +>TBase : Symbol(TBase, Decl(noCrashOnMixin2.ts, 12, 15)) +>Constructor : Symbol(Constructor, Decl(noCrashOnMixin2.ts, 8, 1)) +>Base : Symbol(Base, Decl(noCrashOnMixin2.ts, 12, 42)) +>TBase : Symbol(TBase, Decl(noCrashOnMixin2.ts, 12, 15)) + + return class extends Base { +>Base : Symbol(Base, Decl(noCrashOnMixin2.ts, 12, 42)) + + }; +} + +class Empty { +>Empty : Symbol(Empty, Decl(noCrashOnMixin2.ts, 15, 1)) +} + +class CrashTrigger extends Mixin(Empty) { +>CrashTrigger : Symbol(CrashTrigger, Decl(noCrashOnMixin2.ts, 18, 1)) +>Mixin : Symbol(Mixin, Decl(noCrashOnMixin2.ts, 10, 54)) +>Empty : Symbol(Empty, Decl(noCrashOnMixin2.ts, 15, 1)) + + public trigger() { +>trigger : Symbol(CrashTrigger.trigger, Decl(noCrashOnMixin2.ts, 20, 41)) + + new Concrete(); +>Concrete : Symbol(Concrete, Decl(noCrashOnMixin2.ts, 5, 1)) + } +} + diff --git a/tests/baselines/reference/noCrashOnMixin2.types b/tests/baselines/reference/noCrashOnMixin2.types new file mode 100644 index 0000000000000..e568789976804 --- /dev/null +++ b/tests/baselines/reference/noCrashOnMixin2.types @@ -0,0 +1,68 @@ +//// [tests/cases/compiler/noCrashOnMixin2.ts] //// + +=== noCrashOnMixin2.ts === +// https://github.com/microsoft/TypeScript/issues/62921 + +class Abstract { +>Abstract : Abstract +> : ^^^^^^^^ + + protected constructor() { + } +} + +class Concrete extends Abstract { +>Concrete : Concrete +> : ^^^^^^^^ +>Abstract : Abstract +> : ^^^^^^^^ +} + +type Constructor = new (...args?: any[]) => T; +>Constructor : Constructor +> : ^^^^^^^^^^^^^^ +>args : any[] | undefined +> : ^^^^^^^^^^^^^^^^^ + +function Mixin(Base: TBase) { +>Mixin : (Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : TBase +> : ^^^^^ + + return class extends Base { +>class extends Base { } : { new (...args?: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +> : ^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Base : {} +> : ^^ + + }; +} + +class Empty { +>Empty : Empty +> : ^^^^^ +} + +class CrashTrigger extends Mixin(Empty) { +>CrashTrigger : CrashTrigger +> : ^^^^^^^^^^^^ +>Mixin(Empty) : Mixin.(Anonymous class) +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Mixin : (Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin.(Anonymous class); } & TBase +> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>Empty : typeof Empty +> : ^^^^^^^^^^^^ + + public trigger() { +>trigger : () => void +> : ^^^^^^^^^^ + + new Concrete(); +>new Concrete() : any +> : ^^^ +>Concrete : typeof Concrete +> : ^^^^^^^^^^^^^^^ + } +} + diff --git a/tests/cases/compiler/noCrashOnMixin2.ts b/tests/cases/compiler/noCrashOnMixin2.ts new file mode 100644 index 0000000000000..f6ddccf9480d2 --- /dev/null +++ b/tests/cases/compiler/noCrashOnMixin2.ts @@ -0,0 +1,28 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/62921 + +class Abstract { + protected constructor() { + } +} + +class Concrete extends Abstract { +} + +type Constructor = new (...args?: any[]) => T; + +function Mixin(Base: TBase) { + return class extends Base { + }; +} + +class Empty { +} + +class CrashTrigger extends Mixin(Empty) { + public trigger() { + new Concrete(); + } +} From f7a6b20cf2db6d334bc656a7e93cc4ab0608afdd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 26 Dec 2025 06:02:30 -1000 Subject: [PATCH 5/5] Formatting --- src/compiler/checker.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f3823f5fffd96..73fb7af6a4609 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13213,7 +13213,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getBaseTypes(type: InterfaceType): BaseType[] { if (!(getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference))) { - return emptyArray + return emptyArray; } if (!type.baseTypesResolved) { if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) { @@ -35057,13 +35057,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { */ function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean { if (prop.parent && prop.parent.flags & SymbolFlags.Class) { - const baseTypes = getBaseTypes(getDeclaredTypeOfSymbol(prop.parent) as InterfaceType) + const baseTypes = getBaseTypes(getDeclaredTypeOfSymbol(prop.parent) as InterfaceType); if (baseTypes.length) { - const superProperty = getPropertyOfType(baseTypes[0], prop.escapedName) - return !!(superProperty && superProperty.valueDeclaration) + const superProperty = getPropertyOfType(baseTypes[0], prop.escapedName); + return !!(superProperty && superProperty.valueDeclaration); } } - return false + return false; } function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) {