Fix crash in mixin checking (#62928)

This commit is contained in:
Anders Hejlsberg 2025-12-26 13:15:54 -10:00 committed by GitHub
parent 2dfdbbabae
commit 1f5f9f34d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 291 additions and 20 deletions

View File

@ -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) {

View File

@ -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"
}

View File

@ -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))
}

View File

@ -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"
> : ^^^^^
}

View File

@ -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<T = {}> = 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<TBase extends Constructor>(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.
}
}

View File

@ -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<T = {}> = 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<TBase extends Constructor>(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))
}
}

View File

@ -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<T = {}> = new (...args?: any[]) => T;
>Constructor : Constructor<T>
> : ^^^^^^^^^^^^^^
>args : any[] | undefined
> : ^^^^^^^^^^^^^^^^^
function Mixin<TBase extends Constructor>(Base: TBase) {
>Mixin : <TBase extends Constructor>(Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Base : TBase
> : ^^^^^
return class extends Base {
>class extends Base { } : { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
> : ^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Base : {}
> : ^^
};
}
class Empty {
>Empty : Empty
> : ^^^^^
}
class CrashTrigger extends Mixin(Empty) {
>CrashTrigger : CrashTrigger
> : ^^^^^^^^^^^^
>Mixin(Empty) : Mixin<typeof Empty>.(Anonymous class)
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Mixin : <TBase extends Constructor>(Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>Empty : typeof Empty
> : ^^^^^^^^^^^^
public trigger() {
>trigger : () => void
> : ^^^^^^^^^^
new Concrete();
>new Concrete() : any
> : ^^^
>Concrete : typeof Concrete
> : ^^^^^^^^^^^^^^^
}
}

View File

@ -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"
}

View File

@ -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<T = {}> = new (...args?: any[]) => T;
function Mixin<TBase extends Constructor>(Base: TBase) {
return class extends Base {
};
}
class Empty {
}
class CrashTrigger extends Mixin(Empty) {
public trigger() {
new Concrete();
}
}