Skip to content

Commit 1f5f9f3

Browse files
authored
Fix crash in mixin checking (#62928)
1 parent 2dfdbba commit 1f5f9f3

File tree

9 files changed

+291
-20
lines changed

9 files changed

+291
-20
lines changed

src/compiler/checker.ts

Lines changed: 9 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13212,6 +13212,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1321213212
}
1321313213

1321413214
function getBaseTypes(type: InterfaceType): BaseType[] {
13215+
if (!(getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference))) {
13216+
return emptyArray;
13217+
}
1321513218
if (!type.baseTypesResolved) {
1321613219
if (pushTypeResolution(type, TypeSystemPropertyName.ResolvedBaseTypes)) {
1321713220
if (type.objectFlags & ObjectFlags.Tuple) {
@@ -35053,28 +35056,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
3505335056
* In that case we won't consider it used before its declaration, because it gets its value from the superclass' declaration.
3505435057
*/
3505535058
function isPropertyDeclaredInAncestorClass(prop: Symbol): boolean {
35056-
if (!(prop.parent!.flags & SymbolFlags.Class)) {
35057-
return false;
35058-
}
35059-
let classType: InterfaceType | undefined = getTypeOfSymbol(prop.parent!) as InterfaceType;
35060-
while (true) {
35061-
classType = classType.symbol && getSuperClass(classType) as InterfaceType | undefined;
35062-
if (!classType) {
35063-
return false;
35064-
}
35065-
const superProperty = getPropertyOfType(classType, prop.escapedName);
35066-
if (superProperty && superProperty.valueDeclaration) {
35067-
return true;
35059+
if (prop.parent && prop.parent.flags & SymbolFlags.Class) {
35060+
const baseTypes = getBaseTypes(getDeclaredTypeOfSymbol(prop.parent) as InterfaceType);
35061+
if (baseTypes.length) {
35062+
const superProperty = getPropertyOfType(baseTypes[0], prop.escapedName);
35063+
return !!(superProperty && superProperty.valueDeclaration);
3506835064
}
3506935065
}
35070-
}
35071-
35072-
function getSuperClass(classType: InterfaceType): Type | undefined {
35073-
const x = getBaseTypes(classType);
35074-
if (x.length === 0) {
35075-
return undefined;
35076-
}
35077-
return getIntersectionType(x);
35066+
return false;
3507835067
}
3507935068

3508035069
function reportNonexistentProperty(propNode: Identifier | PrivateIdentifier, containingType: Type, isUncheckedJS: boolean) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
checkInheritedProperty.ts(7,14): error TS2729: Property 'b' is used before its initialization.
2+
3+
4+
==== checkInheritedProperty.ts (1 errors) ====
5+
class Base {
6+
}
7+
8+
declare const BaseFactory: new() => Base & { c: string }
9+
10+
class Derived extends BaseFactory {
11+
a = this.b
12+
~
13+
!!! error TS2729: Property 'b' is used before its initialization.
14+
!!! related TS2728 checkInheritedProperty.ts:8:5: 'b' is declared here.
15+
b = "abc"
16+
}
17+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//// [tests/cases/compiler/checkInheritedProperty.ts] ////
2+
3+
=== checkInheritedProperty.ts ===
4+
class Base {
5+
>Base : Symbol(Base, Decl(checkInheritedProperty.ts, 0, 0))
6+
}
7+
8+
declare const BaseFactory: new() => Base & { c: string }
9+
>BaseFactory : Symbol(BaseFactory, Decl(checkInheritedProperty.ts, 3, 13))
10+
>Base : Symbol(Base, Decl(checkInheritedProperty.ts, 0, 0))
11+
>c : Symbol(c, Decl(checkInheritedProperty.ts, 3, 44))
12+
13+
class Derived extends BaseFactory {
14+
>Derived : Symbol(Derived, Decl(checkInheritedProperty.ts, 3, 56))
15+
>BaseFactory : Symbol(BaseFactory, Decl(checkInheritedProperty.ts, 3, 13))
16+
17+
a = this.b
18+
>a : Symbol(Derived.a, Decl(checkInheritedProperty.ts, 5, 35))
19+
>this.b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14))
20+
>this : Symbol(Derived, Decl(checkInheritedProperty.ts, 3, 56))
21+
>b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14))
22+
23+
b = "abc"
24+
>b : Symbol(Derived.b, Decl(checkInheritedProperty.ts, 6, 14))
25+
}
26+
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//// [tests/cases/compiler/checkInheritedProperty.ts] ////
2+
3+
=== checkInheritedProperty.ts ===
4+
class Base {
5+
>Base : Base
6+
> : ^^^^
7+
}
8+
9+
declare const BaseFactory: new() => Base & { c: string }
10+
>BaseFactory : new () => Base & { c: string; }
11+
> : ^^^^^^^^^^
12+
>c : string
13+
> : ^^^^^^
14+
15+
class Derived extends BaseFactory {
16+
>Derived : Derived
17+
> : ^^^^^^^
18+
>BaseFactory : Base & { c: string; }
19+
> : ^^^^^^^^^^^^ ^^^
20+
21+
a = this.b
22+
>a : string
23+
> : ^^^^^^
24+
>this.b : string
25+
> : ^^^^^^
26+
>this : this
27+
> : ^^^^
28+
>b : string
29+
> : ^^^^^^
30+
31+
b = "abc"
32+
>b : string
33+
> : ^^^^^^
34+
>"abc" : "abc"
35+
> : ^^^^^
36+
}
37+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
noCrashOnMixin2.ts(11,33): error TS2370: A rest parameter must be of an array type.
2+
noCrashOnMixin2.ts(11,40): error TS1047: A rest parameter cannot be optional.
3+
noCrashOnMixin2.ts(14,12): error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
4+
noCrashOnMixin2.ts(23,9): error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration.
5+
6+
7+
==== noCrashOnMixin2.ts (4 errors) ====
8+
// https://github.com/microsoft/TypeScript/issues/62921
9+
10+
class Abstract {
11+
protected constructor() {
12+
}
13+
}
14+
15+
class Concrete extends Abstract {
16+
}
17+
18+
type Constructor<T = {}> = new (...args?: any[]) => T;
19+
~~~~~~~~~~~~~~~
20+
!!! error TS2370: A rest parameter must be of an array type.
21+
~
22+
!!! error TS1047: A rest parameter cannot be optional.
23+
24+
function Mixin<TBase extends Constructor>(Base: TBase) {
25+
return class extends Base {
26+
~~~~~
27+
!!! error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
28+
};
29+
}
30+
31+
class Empty {
32+
}
33+
34+
class CrashTrigger extends Mixin(Empty) {
35+
public trigger() {
36+
new Concrete();
37+
~~~~~~~~~~~~~~
38+
!!! error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration.
39+
}
40+
}
41+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [tests/cases/compiler/noCrashOnMixin2.ts] ////
2+
3+
=== noCrashOnMixin2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/62921
5+
6+
class Abstract {
7+
>Abstract : Symbol(Abstract, Decl(noCrashOnMixin2.ts, 0, 0))
8+
9+
protected constructor() {
10+
}
11+
}
12+
13+
class Concrete extends Abstract {
14+
>Concrete : Symbol(Concrete, Decl(noCrashOnMixin2.ts, 5, 1))
15+
>Abstract : Symbol(Abstract, Decl(noCrashOnMixin2.ts, 0, 0))
16+
}
17+
18+
type Constructor<T = {}> = new (...args?: any[]) => T;
19+
>Constructor : Symbol(Constructor, Decl(noCrashOnMixin2.ts, 8, 1))
20+
>T : Symbol(T, Decl(noCrashOnMixin2.ts, 10, 17))
21+
>args : Symbol(args, Decl(noCrashOnMixin2.ts, 10, 32))
22+
>T : Symbol(T, Decl(noCrashOnMixin2.ts, 10, 17))
23+
24+
function Mixin<TBase extends Constructor>(Base: TBase) {
25+
>Mixin : Symbol(Mixin, Decl(noCrashOnMixin2.ts, 10, 54))
26+
>TBase : Symbol(TBase, Decl(noCrashOnMixin2.ts, 12, 15))
27+
>Constructor : Symbol(Constructor, Decl(noCrashOnMixin2.ts, 8, 1))
28+
>Base : Symbol(Base, Decl(noCrashOnMixin2.ts, 12, 42))
29+
>TBase : Symbol(TBase, Decl(noCrashOnMixin2.ts, 12, 15))
30+
31+
return class extends Base {
32+
>Base : Symbol(Base, Decl(noCrashOnMixin2.ts, 12, 42))
33+
34+
};
35+
}
36+
37+
class Empty {
38+
>Empty : Symbol(Empty, Decl(noCrashOnMixin2.ts, 15, 1))
39+
}
40+
41+
class CrashTrigger extends Mixin(Empty) {
42+
>CrashTrigger : Symbol(CrashTrigger, Decl(noCrashOnMixin2.ts, 18, 1))
43+
>Mixin : Symbol(Mixin, Decl(noCrashOnMixin2.ts, 10, 54))
44+
>Empty : Symbol(Empty, Decl(noCrashOnMixin2.ts, 15, 1))
45+
46+
public trigger() {
47+
>trigger : Symbol(CrashTrigger.trigger, Decl(noCrashOnMixin2.ts, 20, 41))
48+
49+
new Concrete();
50+
>Concrete : Symbol(Concrete, Decl(noCrashOnMixin2.ts, 5, 1))
51+
}
52+
}
53+
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//// [tests/cases/compiler/noCrashOnMixin2.ts] ////
2+
3+
=== noCrashOnMixin2.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/62921
5+
6+
class Abstract {
7+
>Abstract : Abstract
8+
> : ^^^^^^^^
9+
10+
protected constructor() {
11+
}
12+
}
13+
14+
class Concrete extends Abstract {
15+
>Concrete : Concrete
16+
> : ^^^^^^^^
17+
>Abstract : Abstract
18+
> : ^^^^^^^^
19+
}
20+
21+
type Constructor<T = {}> = new (...args?: any[]) => T;
22+
>Constructor : Constructor<T>
23+
> : ^^^^^^^^^^^^^^
24+
>args : any[] | undefined
25+
> : ^^^^^^^^^^^^^^^^^
26+
27+
function Mixin<TBase extends Constructor>(Base: TBase) {
28+
>Mixin : <TBase extends Constructor>(Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
29+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30+
>Base : TBase
31+
> : ^^^^^
32+
33+
return class extends Base {
34+
>class extends Base { } : { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
35+
> : ^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
36+
>Base : {}
37+
> : ^^
38+
39+
};
40+
}
41+
42+
class Empty {
43+
>Empty : Empty
44+
> : ^^^^^
45+
}
46+
47+
class CrashTrigger extends Mixin(Empty) {
48+
>CrashTrigger : CrashTrigger
49+
> : ^^^^^^^^^^^^
50+
>Mixin(Empty) : Mixin<typeof Empty>.(Anonymous class)
51+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52+
>Mixin : <TBase extends Constructor>(Base: TBase) => { new (...args?: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
53+
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54+
>Empty : typeof Empty
55+
> : ^^^^^^^^^^^^
56+
57+
public trigger() {
58+
>trigger : () => void
59+
> : ^^^^^^^^^^
60+
61+
new Concrete();
62+
>new Concrete() : any
63+
> : ^^^
64+
>Concrete : typeof Concrete
65+
> : ^^^^^^^^^^^^^^^
66+
}
67+
}
68+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
class Base {
5+
}
6+
7+
declare const BaseFactory: new() => Base & { c: string }
8+
9+
class Derived extends BaseFactory {
10+
a = this.b
11+
b = "abc"
12+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @strict: true
2+
// @noEmit: true
3+
4+
// https://github.com/microsoft/TypeScript/issues/62921
5+
6+
class Abstract {
7+
protected constructor() {
8+
}
9+
}
10+
11+
class Concrete extends Abstract {
12+
}
13+
14+
type Constructor<T = {}> = new (...args?: any[]) => T;
15+
16+
function Mixin<TBase extends Constructor>(Base: TBase) {
17+
return class extends Base {
18+
};
19+
}
20+
21+
class Empty {
22+
}
23+
24+
class CrashTrigger extends Mixin(Empty) {
25+
public trigger() {
26+
new Concrete();
27+
}
28+
}

0 commit comments

Comments
 (0)