Skip to content

Commit 6c2ae12

Browse files
authored
Relax the constraints of isValidBaseType to allow base types to be constructor types (#33146)
* Relax the constraints of isValidBaseType to allow base types to be constructor types * Fix nit * Reduce confusion between isConstructorType and isValidBaseType * Update comment
1 parent 4f25774 commit 6c2ae12

11 files changed

+271
-25
lines changed

Diff for: src/compiler/checker.ts

+14-5
Original file line numberDiff line numberDiff line change
@@ -5985,7 +5985,9 @@ namespace ts {
59855985

59865986
function getBaseTypeVariableOfClass(symbol: Symbol) {
59875987
const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol));
5988-
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : undefined;
5988+
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType :
5989+
baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) :
5990+
undefined;
59895991
}
59905992

59915993
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
@@ -6263,12 +6265,12 @@ namespace ts {
62636265
}
62646266

62656267
function isConstructorType(type: Type): boolean {
6266-
if (isValidBaseType(type) && getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
6268+
if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
62676269
return true;
62686270
}
62696271
if (type.flags & TypeFlags.TypeVariable) {
62706272
const constraint = getBaseConstraintOfType(type);
6271-
return !!constraint && isValidBaseType(constraint) && isMixinConstructorType(constraint);
6273+
return !!constraint && isMixinConstructorType(constraint);
62726274
}
62736275
return false;
62746276
}
@@ -6429,9 +6431,16 @@ namespace ts {
64296431
return true;
64306432
}
64316433

6432-
// A valid base type is `any`, any non-generic object type or intersection of non-generic
6433-
// object types.
6434+
// A valid base type is `any`, an object type or intersection of object types.
64346435
function isValidBaseType(type: Type): type is BaseType {
6436+
if (type.flags & TypeFlags.TypeParameter) {
6437+
const constraint = getBaseConstraintOfType(type);
6438+
if (constraint) {
6439+
return isValidBaseType(constraint);
6440+
}
6441+
}
6442+
// TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed?
6443+
// There's no reason a `T` should be allowed while a `Readonly<T>` should not.
64356444
return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)) && !isGenericMappedType(type) ||
64366445
!!(type.flags & TypeFlags.Intersection) && every((<IntersectionType>type).types, isValidBaseType);
64376446
}

Diff for: src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4202,7 +4202,7 @@ namespace ts {
42024202
}
42034203

42044204
// Object type or intersection of object types
4205-
export type BaseType = ObjectType | IntersectionType;
4205+
export type BaseType = ObjectType | IntersectionType | TypeVariable; // Also `any` and `object`
42064206

42074207
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
42084208
declaredProperties: Symbol[]; // Declared members

Diff for: tests/baselines/reference/api/tsserverlibrary.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2340,7 +2340,7 @@ declare namespace ts {
23402340
localTypeParameters: TypeParameter[] | undefined;
23412341
thisType: TypeParameter | undefined;
23422342
}
2343-
export type BaseType = ObjectType | IntersectionType;
2343+
export type BaseType = ObjectType | IntersectionType | TypeVariable;
23442344
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
23452345
declaredProperties: Symbol[];
23462346
declaredCallSignatures: Signature[];

Diff for: tests/baselines/reference/api/typescript.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2340,7 +2340,7 @@ declare namespace ts {
23402340
localTypeParameters: TypeParameter[] | undefined;
23412341
thisType: TypeParameter | undefined;
23422342
}
2343-
export type BaseType = ObjectType | IntersectionType;
2343+
export type BaseType = ObjectType | IntersectionType | TypeVariable;
23442344
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
23452345
declaredProperties: Symbol[];
23462346
declaredCallSignatures: Signature[];

Diff for: tests/baselines/reference/baseConstraintOfDecorator.errors.txt

+4-13
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
tests/cases/compiler/baseConstraintOfDecorator.ts(2,5): error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
22
'typeof decoratorFunc' is assignable to the constraint of type 'TFunction', but 'TFunction' could be instantiated with a different subtype of constraint '{}'.
33
tests/cases/compiler/baseConstraintOfDecorator.ts(2,40): error TS2507: Type 'TFunction' is not a constructor function type.
4-
tests/cases/compiler/baseConstraintOfDecorator.ts(12,5): error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
5-
tests/cases/compiler/baseConstraintOfDecorator.ts(12,40): error TS2507: Type 'TFunction' is not a constructor function type.
4+
tests/cases/compiler/baseConstraintOfDecorator.ts(12,18): error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
65

76

8-
==== tests/cases/compiler/baseConstraintOfDecorator.ts (4 errors) ====
7+
==== tests/cases/compiler/baseConstraintOfDecorator.ts (3 errors) ====
98
export function classExtender<TFunction>(superClass: TFunction, _instanceModifier: (instance: any, args: any[]) => void): TFunction {
109
return class decoratorFunc extends superClass {
1110
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -29,20 +28,12 @@ tests/cases/compiler/baseConstraintOfDecorator.ts(12,40): error TS2507: Type 'TF
2928
class MyClass { private x; }
3029
export function classExtender2<TFunction extends new (...args: string[]) => MyClass>(superClass: TFunction, _instanceModifier: (instance: any, args: any[]) => void): TFunction {
3130
return class decoratorFunc extends superClass {
32-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
33-
~~~~~~~~~~
34-
!!! error TS2507: Type 'TFunction' is not a constructor function type.
35-
!!! related TS2735 tests/cases/compiler/baseConstraintOfDecorator.ts:11:32: Did you mean for 'TFunction' to be constrained to type 'new (...args: any[]) => MyClass'?
31+
~~~~~~~~~~~~~
32+
!!! error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
3633
constructor(...args: any[]) {
37-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3834
super(...args);
39-
~~~~~~~~~~~~~~~~~~~~~~~~~~~
4035
_instanceModifier(this, args);
41-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4236
}
43-
~~~~~~~~~
4437
};
45-
~~~~~~
46-
!!! error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
4738
}
4839

Diff for: tests/baselines/reference/baseConstraintOfDecorator.symbols

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function classExtender2<TFunction extends new (...args: string[]) => MyCl
5151
>args : Symbol(args, Decl(baseConstraintOfDecorator.ts, 12, 20))
5252

5353
super(...args);
54+
>super : Symbol(TFunction, Decl(baseConstraintOfDecorator.ts, 10, 31))
5455
>args : Symbol(args, Decl(baseConstraintOfDecorator.ts, 12, 20))
5556

5657
_instanceModifier(this, args);

Diff for: tests/baselines/reference/baseConstraintOfDecorator.types

+4-4
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,16 @@ export function classExtender2<TFunction extends new (...args: string[]) => MyCl
4242
>args : any[]
4343

4444
return class decoratorFunc extends superClass {
45-
>class decoratorFunc extends superClass { constructor(...args: any[]) { super(...args); _instanceModifier(this, args); } } : typeof decoratorFunc
46-
>decoratorFunc : typeof decoratorFunc
47-
>superClass : TFunction
45+
>class decoratorFunc extends superClass { constructor(...args: any[]) { super(...args); _instanceModifier(this, args); } } : { new (...args: any[]): decoratorFunc; prototype: classExtender2<any>.decoratorFunc; } & TFunction
46+
>decoratorFunc : { new (...args: any[]): decoratorFunc; prototype: classExtender2<any>.decoratorFunc; } & TFunction
47+
>superClass : MyClass
4848

4949
constructor(...args: any[]) {
5050
>args : any[]
5151

5252
super(...args);
5353
>super(...args) : void
54-
>super : any
54+
>super : TFunction
5555
>...args : any
5656
>args : any[]
5757

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//// [mixinIntersectionIsValidbaseType.ts]
2+
export type Constructor<T extends object = object> = new (...args: any[]) => T;
3+
4+
export interface Initable {
5+
init(...args: any[]): void;
6+
}
7+
8+
/**
9+
* Plain mixin where the superclass must be Initable
10+
*/
11+
export const Serializable = <K extends Constructor<Initable> & Initable>(
12+
SuperClass: K
13+
) => {
14+
const LocalMixin = (InnerSuperClass: K) => {
15+
return class SerializableLocal extends InnerSuperClass {
16+
}
17+
};
18+
let ResultClass = LocalMixin(SuperClass);
19+
return ResultClass;
20+
};
21+
22+
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
23+
let SomeHowOkay = class A extends SuperClass {
24+
};
25+
26+
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
27+
};
28+
};
29+
30+
//// [mixinIntersectionIsValidbaseType.js]
31+
"use strict";
32+
var __extends = (this && this.__extends) || (function () {
33+
var extendStatics = function (d, b) {
34+
extendStatics = Object.setPrototypeOf ||
35+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
36+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
37+
return extendStatics(d, b);
38+
};
39+
return function (d, b) {
40+
extendStatics(d, b);
41+
function __() { this.constructor = d; }
42+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
43+
};
44+
})();
45+
exports.__esModule = true;
46+
/**
47+
* Plain mixin where the superclass must be Initable
48+
*/
49+
exports.Serializable = function (SuperClass) {
50+
var LocalMixin = function (InnerSuperClass) {
51+
return /** @class */ (function (_super) {
52+
__extends(SerializableLocal, _super);
53+
function SerializableLocal() {
54+
return _super !== null && _super.apply(this, arguments) || this;
55+
}
56+
return SerializableLocal;
57+
}(InnerSuperClass));
58+
};
59+
var ResultClass = LocalMixin(SuperClass);
60+
return ResultClass;
61+
};
62+
var AMixin = function (SuperClass) {
63+
var SomeHowOkay = /** @class */ (function (_super) {
64+
__extends(A, _super);
65+
function A() {
66+
return _super !== null && _super.apply(this, arguments) || this;
67+
}
68+
return A;
69+
}(SuperClass));
70+
var SomeHowNotOkay = /** @class */ (function (_super) {
71+
__extends(A, _super);
72+
function A() {
73+
return _super !== null && _super.apply(this, arguments) || this;
74+
}
75+
return A;
76+
}(exports.Serializable(SuperClass)));
77+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
=== tests/cases/compiler/mixinIntersectionIsValidbaseType.ts ===
2+
export type Constructor<T extends object = object> = new (...args: any[]) => T;
3+
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
4+
>T : Symbol(T, Decl(mixinIntersectionIsValidbaseType.ts, 0, 24))
5+
>args : Symbol(args, Decl(mixinIntersectionIsValidbaseType.ts, 0, 58))
6+
>T : Symbol(T, Decl(mixinIntersectionIsValidbaseType.ts, 0, 24))
7+
8+
export interface Initable {
9+
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
10+
11+
init(...args: any[]): void;
12+
>init : Symbol(Initable.init, Decl(mixinIntersectionIsValidbaseType.ts, 2, 27))
13+
>args : Symbol(args, Decl(mixinIntersectionIsValidbaseType.ts, 3, 9))
14+
}
15+
16+
/**
17+
* Plain mixin where the superclass must be Initable
18+
*/
19+
export const Serializable = <K extends Constructor<Initable> & Initable>(
20+
>Serializable : Symbol(Serializable, Decl(mixinIntersectionIsValidbaseType.ts, 9, 12))
21+
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
22+
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
23+
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
24+
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
25+
26+
SuperClass: K
27+
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 9, 73))
28+
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
29+
30+
) => {
31+
const LocalMixin = (InnerSuperClass: K) => {
32+
>LocalMixin : Symbol(LocalMixin, Decl(mixinIntersectionIsValidbaseType.ts, 12, 9))
33+
>InnerSuperClass : Symbol(InnerSuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 12, 24))
34+
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
35+
36+
return class SerializableLocal extends InnerSuperClass {
37+
>SerializableLocal : Symbol(SerializableLocal, Decl(mixinIntersectionIsValidbaseType.ts, 13, 14))
38+
>InnerSuperClass : Symbol(InnerSuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 12, 24))
39+
}
40+
};
41+
let ResultClass = LocalMixin(SuperClass);
42+
>ResultClass : Symbol(ResultClass, Decl(mixinIntersectionIsValidbaseType.ts, 16, 7))
43+
>LocalMixin : Symbol(LocalMixin, Decl(mixinIntersectionIsValidbaseType.ts, 12, 9))
44+
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 9, 73))
45+
46+
return ResultClass;
47+
>ResultClass : Symbol(ResultClass, Decl(mixinIntersectionIsValidbaseType.ts, 16, 7))
48+
49+
};
50+
51+
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
52+
>AMixin : Symbol(AMixin, Decl(mixinIntersectionIsValidbaseType.ts, 20, 5))
53+
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 20, 16))
54+
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
55+
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
56+
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
57+
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
58+
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 20, 16))
59+
60+
let SomeHowOkay = class A extends SuperClass {
61+
>SomeHowOkay : Symbol(SomeHowOkay, Decl(mixinIntersectionIsValidbaseType.ts, 21, 7))
62+
>A : Symbol(A, Decl(mixinIntersectionIsValidbaseType.ts, 21, 21))
63+
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
64+
65+
};
66+
67+
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
68+
>SomeHowNotOkay : Symbol(SomeHowNotOkay, Decl(mixinIntersectionIsValidbaseType.ts, 24, 7))
69+
>A : Symbol(A, Decl(mixinIntersectionIsValidbaseType.ts, 24, 24))
70+
>Serializable : Symbol(Serializable, Decl(mixinIntersectionIsValidbaseType.ts, 9, 12))
71+
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
72+
73+
};
74+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== tests/cases/compiler/mixinIntersectionIsValidbaseType.ts ===
2+
export type Constructor<T extends object = object> = new (...args: any[]) => T;
3+
>Constructor : Constructor<T>
4+
>args : any[]
5+
6+
export interface Initable {
7+
init(...args: any[]): void;
8+
>init : (...args: any[]) => void
9+
>args : any[]
10+
}
11+
12+
/**
13+
* Plain mixin where the superclass must be Initable
14+
*/
15+
export const Serializable = <K extends Constructor<Initable> & Initable>(
16+
>Serializable : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
17+
><K extends Constructor<Initable> & Initable>( SuperClass: K) => { const LocalMixin = (InnerSuperClass: K) => { return class SerializableLocal extends InnerSuperClass { } }; let ResultClass = LocalMixin(SuperClass); return ResultClass;} : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
18+
19+
SuperClass: K
20+
>SuperClass : K
21+
22+
) => {
23+
const LocalMixin = (InnerSuperClass: K) => {
24+
>LocalMixin : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
25+
>(InnerSuperClass: K) => { return class SerializableLocal extends InnerSuperClass { } } : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
26+
>InnerSuperClass : K
27+
28+
return class SerializableLocal extends InnerSuperClass {
29+
>class SerializableLocal extends InnerSuperClass { } : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
30+
>SerializableLocal : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
31+
>InnerSuperClass : Initable
32+
}
33+
};
34+
let ResultClass = LocalMixin(SuperClass);
35+
>ResultClass : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
36+
>LocalMixin(SuperClass) : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
37+
>LocalMixin : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
38+
>SuperClass : K
39+
40+
return ResultClass;
41+
>ResultClass : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
42+
43+
};
44+
45+
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
46+
>AMixin : <K extends Constructor<Initable> & Initable>(SuperClass: K) => void
47+
><K extends Constructor<Initable> & Initable>(SuperClass: K) => { let SomeHowOkay = class A extends SuperClass { }; let SomeHowNotOkay = class A extends Serializable(SuperClass) { };} : <K extends Constructor<Initable> & Initable>(SuperClass: K) => void
48+
>SuperClass : K
49+
50+
let SomeHowOkay = class A extends SuperClass {
51+
>SomeHowOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
52+
>class A extends SuperClass { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
53+
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
54+
>SuperClass : Initable
55+
56+
};
57+
58+
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
59+
>SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
60+
>class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
61+
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
62+
>Serializable(SuperClass) : Serializable<K>.SerializableLocal & Initable
63+
>Serializable : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
64+
>SuperClass : K
65+
66+
};
67+
};
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export type Constructor<T extends object = object> = new (...args: any[]) => T;
2+
3+
export interface Initable {
4+
init(...args: any[]): void;
5+
}
6+
7+
/**
8+
* Plain mixin where the superclass must be Initable
9+
*/
10+
export const Serializable = <K extends Constructor<Initable> & Initable>(
11+
SuperClass: K
12+
) => {
13+
const LocalMixin = (InnerSuperClass: K) => {
14+
return class SerializableLocal extends InnerSuperClass {
15+
}
16+
};
17+
let ResultClass = LocalMixin(SuperClass);
18+
return ResultClass;
19+
};
20+
21+
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
22+
let SomeHowOkay = class A extends SuperClass {
23+
};
24+
25+
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
26+
};
27+
};

0 commit comments

Comments
 (0)