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
This commit is contained in:
Wesley Wigham
2019-09-23 15:58:03 -07:00
committed by GitHub
parent 4f25774785
commit 6c2ae12559
11 changed files with 271 additions and 25 deletions

View File

@@ -5985,7 +5985,9 @@ namespace ts {
function getBaseTypeVariableOfClass(symbol: Symbol) {
const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol));
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : undefined;
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType :
baseConstructorType.flags & TypeFlags.Intersection ? find((baseConstructorType as IntersectionType).types, t => !!(t.flags & TypeFlags.TypeVariable)) :
undefined;
}
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
@@ -6263,12 +6265,12 @@ namespace ts {
}
function isConstructorType(type: Type): boolean {
if (isValidBaseType(type) && getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
if (getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
return true;
}
if (type.flags & TypeFlags.TypeVariable) {
const constraint = getBaseConstraintOfType(type);
return !!constraint && isValidBaseType(constraint) && isMixinConstructorType(constraint);
return !!constraint && isMixinConstructorType(constraint);
}
return false;
}
@@ -6429,9 +6431,16 @@ namespace ts {
return true;
}
// A valid base type is `any`, any non-generic object type or intersection of non-generic
// object types.
// A valid base type is `any`, an object type or intersection of object types.
function isValidBaseType(type: Type): type is BaseType {
if (type.flags & TypeFlags.TypeParameter) {
const constraint = getBaseConstraintOfType(type);
if (constraint) {
return isValidBaseType(constraint);
}
}
// TODO: Given that we allow type parmeters here now, is this `!isGenericMappedType(type)` check really needed?
// There's no reason a `T` should be allowed while a `Readonly<T>` should not.
return !!(type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)) && !isGenericMappedType(type) ||
!!(type.flags & TypeFlags.Intersection) && every((<IntersectionType>type).types, isValidBaseType);
}

View File

@@ -4202,7 +4202,7 @@ namespace ts {
}
// Object type or intersection of object types
export type BaseType = ObjectType | IntersectionType;
export type BaseType = ObjectType | IntersectionType | TypeVariable; // Also `any` and `object`
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
declaredProperties: Symbol[]; // Declared members

View File

@@ -2340,7 +2340,7 @@ declare namespace ts {
localTypeParameters: TypeParameter[] | undefined;
thisType: TypeParameter | undefined;
}
export type BaseType = ObjectType | IntersectionType;
export type BaseType = ObjectType | IntersectionType | TypeVariable;
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
declaredProperties: Symbol[];
declaredCallSignatures: Signature[];

View File

@@ -2340,7 +2340,7 @@ declare namespace ts {
localTypeParameters: TypeParameter[] | undefined;
thisType: TypeParameter | undefined;
}
export type BaseType = ObjectType | IntersectionType;
export type BaseType = ObjectType | IntersectionType | TypeVariable;
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
declaredProperties: Symbol[];
declaredCallSignatures: Signature[];

View File

@@ -1,11 +1,10 @@
tests/cases/compiler/baseConstraintOfDecorator.ts(2,5): error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
'typeof decoratorFunc' is assignable to the constraint of type 'TFunction', but 'TFunction' could be instantiated with a different subtype of constraint '{}'.
tests/cases/compiler/baseConstraintOfDecorator.ts(2,40): error TS2507: Type 'TFunction' is not a constructor function type.
tests/cases/compiler/baseConstraintOfDecorator.ts(12,5): error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
tests/cases/compiler/baseConstraintOfDecorator.ts(12,40): error TS2507: Type 'TFunction' is not a constructor function type.
tests/cases/compiler/baseConstraintOfDecorator.ts(12,18): error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
==== tests/cases/compiler/baseConstraintOfDecorator.ts (4 errors) ====
==== tests/cases/compiler/baseConstraintOfDecorator.ts (3 errors) ====
export function classExtender<TFunction>(superClass: TFunction, _instanceModifier: (instance: any, args: any[]) => void): TFunction {
return class decoratorFunc extends superClass {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -29,20 +28,12 @@ tests/cases/compiler/baseConstraintOfDecorator.ts(12,40): error TS2507: Type 'TF
class MyClass { private x; }
export function classExtender2<TFunction extends new (...args: string[]) => MyClass>(superClass: TFunction, _instanceModifier: (instance: any, args: any[]) => void): TFunction {
return class decoratorFunc extends superClass {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~
!!! error TS2507: Type 'TFunction' is not a constructor function type.
!!! related TS2735 tests/cases/compiler/baseConstraintOfDecorator.ts:11:32: Did you mean for 'TFunction' to be constrained to type 'new (...args: any[]) => MyClass'?
~~~~~~~~~~~~~
!!! error TS2545: A mixin class must have a constructor with a single rest parameter of type 'any[]'.
constructor(...args: any[]) {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
super(...args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~
_instanceModifier(this, args);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
~~~~~~~~~
};
~~~~~~
!!! error TS2322: Type 'typeof decoratorFunc' is not assignable to type 'TFunction'.
}

View File

@@ -51,6 +51,7 @@ export function classExtender2<TFunction extends new (...args: string[]) => MyCl
>args : Symbol(args, Decl(baseConstraintOfDecorator.ts, 12, 20))
super(...args);
>super : Symbol(TFunction, Decl(baseConstraintOfDecorator.ts, 10, 31))
>args : Symbol(args, Decl(baseConstraintOfDecorator.ts, 12, 20))
_instanceModifier(this, args);

View File

@@ -42,16 +42,16 @@ export function classExtender2<TFunction extends new (...args: string[]) => MyCl
>args : any[]
return class decoratorFunc extends superClass {
>class decoratorFunc extends superClass { constructor(...args: any[]) { super(...args); _instanceModifier(this, args); } } : typeof decoratorFunc
>decoratorFunc : typeof decoratorFunc
>superClass : TFunction
>class decoratorFunc extends superClass { constructor(...args: any[]) { super(...args); _instanceModifier(this, args); } } : { new (...args: any[]): decoratorFunc; prototype: classExtender2<any>.decoratorFunc; } & TFunction
>decoratorFunc : { new (...args: any[]): decoratorFunc; prototype: classExtender2<any>.decoratorFunc; } & TFunction
>superClass : MyClass
constructor(...args: any[]) {
>args : any[]
super(...args);
>super(...args) : void
>super : any
>super : TFunction
>...args : any
>args : any[]

View File

@@ -0,0 +1,77 @@
//// [mixinIntersectionIsValidbaseType.ts]
export type Constructor<T extends object = object> = new (...args: any[]) => T;
export interface Initable {
init(...args: any[]): void;
}
/**
* Plain mixin where the superclass must be Initable
*/
export const Serializable = <K extends Constructor<Initable> & Initable>(
SuperClass: K
) => {
const LocalMixin = (InnerSuperClass: K) => {
return class SerializableLocal extends InnerSuperClass {
}
};
let ResultClass = LocalMixin(SuperClass);
return ResultClass;
};
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
let SomeHowOkay = class A extends SuperClass {
};
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
};
};
//// [mixinIntersectionIsValidbaseType.js]
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
exports.__esModule = true;
/**
* Plain mixin where the superclass must be Initable
*/
exports.Serializable = function (SuperClass) {
var LocalMixin = function (InnerSuperClass) {
return /** @class */ (function (_super) {
__extends(SerializableLocal, _super);
function SerializableLocal() {
return _super !== null && _super.apply(this, arguments) || this;
}
return SerializableLocal;
}(InnerSuperClass));
};
var ResultClass = LocalMixin(SuperClass);
return ResultClass;
};
var AMixin = function (SuperClass) {
var SomeHowOkay = /** @class */ (function (_super) {
__extends(A, _super);
function A() {
return _super !== null && _super.apply(this, arguments) || this;
}
return A;
}(SuperClass));
var SomeHowNotOkay = /** @class */ (function (_super) {
__extends(A, _super);
function A() {
return _super !== null && _super.apply(this, arguments) || this;
}
return A;
}(exports.Serializable(SuperClass)));
};

View File

@@ -0,0 +1,74 @@
=== tests/cases/compiler/mixinIntersectionIsValidbaseType.ts ===
export type Constructor<T extends object = object> = new (...args: any[]) => T;
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
>T : Symbol(T, Decl(mixinIntersectionIsValidbaseType.ts, 0, 24))
>args : Symbol(args, Decl(mixinIntersectionIsValidbaseType.ts, 0, 58))
>T : Symbol(T, Decl(mixinIntersectionIsValidbaseType.ts, 0, 24))
export interface Initable {
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
init(...args: any[]): void;
>init : Symbol(Initable.init, Decl(mixinIntersectionIsValidbaseType.ts, 2, 27))
>args : Symbol(args, Decl(mixinIntersectionIsValidbaseType.ts, 3, 9))
}
/**
* Plain mixin where the superclass must be Initable
*/
export const Serializable = <K extends Constructor<Initable> & Initable>(
>Serializable : Symbol(Serializable, Decl(mixinIntersectionIsValidbaseType.ts, 9, 12))
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
SuperClass: K
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 9, 73))
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
) => {
const LocalMixin = (InnerSuperClass: K) => {
>LocalMixin : Symbol(LocalMixin, Decl(mixinIntersectionIsValidbaseType.ts, 12, 9))
>InnerSuperClass : Symbol(InnerSuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 12, 24))
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 9, 29))
return class SerializableLocal extends InnerSuperClass {
>SerializableLocal : Symbol(SerializableLocal, Decl(mixinIntersectionIsValidbaseType.ts, 13, 14))
>InnerSuperClass : Symbol(InnerSuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 12, 24))
}
};
let ResultClass = LocalMixin(SuperClass);
>ResultClass : Symbol(ResultClass, Decl(mixinIntersectionIsValidbaseType.ts, 16, 7))
>LocalMixin : Symbol(LocalMixin, Decl(mixinIntersectionIsValidbaseType.ts, 12, 9))
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 9, 73))
return ResultClass;
>ResultClass : Symbol(ResultClass, Decl(mixinIntersectionIsValidbaseType.ts, 16, 7))
};
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
>AMixin : Symbol(AMixin, Decl(mixinIntersectionIsValidbaseType.ts, 20, 5))
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 20, 16))
>Constructor : Symbol(Constructor, Decl(mixinIntersectionIsValidbaseType.ts, 0, 0))
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
>Initable : Symbol(Initable, Decl(mixinIntersectionIsValidbaseType.ts, 0, 79))
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
>K : Symbol(K, Decl(mixinIntersectionIsValidbaseType.ts, 20, 16))
let SomeHowOkay = class A extends SuperClass {
>SomeHowOkay : Symbol(SomeHowOkay, Decl(mixinIntersectionIsValidbaseType.ts, 21, 7))
>A : Symbol(A, Decl(mixinIntersectionIsValidbaseType.ts, 21, 21))
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
};
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
>SomeHowNotOkay : Symbol(SomeHowNotOkay, Decl(mixinIntersectionIsValidbaseType.ts, 24, 7))
>A : Symbol(A, Decl(mixinIntersectionIsValidbaseType.ts, 24, 24))
>Serializable : Symbol(Serializable, Decl(mixinIntersectionIsValidbaseType.ts, 9, 12))
>SuperClass : Symbol(SuperClass, Decl(mixinIntersectionIsValidbaseType.ts, 20, 60))
};
};

View File

@@ -0,0 +1,67 @@
=== tests/cases/compiler/mixinIntersectionIsValidbaseType.ts ===
export type Constructor<T extends object = object> = new (...args: any[]) => T;
>Constructor : Constructor<T>
>args : any[]
export interface Initable {
init(...args: any[]): void;
>init : (...args: any[]) => void
>args : any[]
}
/**
* Plain mixin where the superclass must be Initable
*/
export const Serializable = <K extends Constructor<Initable> & Initable>(
>Serializable : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
><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
SuperClass: K
>SuperClass : K
) => {
const LocalMixin = (InnerSuperClass: K) => {
>LocalMixin : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>(InnerSuperClass: K) => { return class SerializableLocal extends InnerSuperClass { } } : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>InnerSuperClass : K
return class SerializableLocal extends InnerSuperClass {
>class SerializableLocal extends InnerSuperClass { } : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>SerializableLocal : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>InnerSuperClass : Initable
}
};
let ResultClass = LocalMixin(SuperClass);
>ResultClass : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>LocalMixin(SuperClass) : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>LocalMixin : (InnerSuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>SuperClass : K
return ResultClass;
>ResultClass : { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
};
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
>AMixin : <K extends Constructor<Initable> & Initable>(SuperClass: K) => void
><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
>SuperClass : K
let SomeHowOkay = class A extends SuperClass {
>SomeHowOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>class A extends SuperClass { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init(...args: any[]): void; } & K
>SuperClass : Initable
};
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
>SomeHowNotOkay : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>class A extends Serializable(SuperClass) { } : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>A : { new (...args: any[]): A; prototype: AMixin<any>.A; init: (...args: any[]) => void; } & K
>Serializable(SuperClass) : Serializable<K>.SerializableLocal & Initable
>Serializable : <K extends Constructor<Initable> & Initable>(SuperClass: K) => { new (...args: any[]): SerializableLocal; prototype: Serializable<any>.SerializableLocal; init(...args: any[]): void; } & K
>SuperClass : K
};
};

View File

@@ -0,0 +1,27 @@
export type Constructor<T extends object = object> = new (...args: any[]) => T;
export interface Initable {
init(...args: any[]): void;
}
/**
* Plain mixin where the superclass must be Initable
*/
export const Serializable = <K extends Constructor<Initable> & Initable>(
SuperClass: K
) => {
const LocalMixin = (InnerSuperClass: K) => {
return class SerializableLocal extends InnerSuperClass {
}
};
let ResultClass = LocalMixin(SuperClass);
return ResultClass;
};
const AMixin = <K extends Constructor<Initable> & Initable>(SuperClass: K) => {
let SomeHowOkay = class A extends SuperClass {
};
let SomeHowNotOkay = class A extends Serializable(SuperClass) {
};
};