Skip to content

Relax the constraints of isValidBaseType to allow base types to be constructor types #33146

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Sep 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5984,7 +5984,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 {
Expand Down Expand Up @@ -6262,12 +6264,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;
}
Expand Down Expand Up @@ -6428,9 +6430,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);
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
Expand Down
17 changes: 4 additions & 13 deletions tests/baselines/reference/baseConstraintOfDecorator.errors.txt
Original file line number Diff line number Diff line change
@@ -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 {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand All @@ -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'.
}

Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 4 additions & 4 deletions tests/baselines/reference/baseConstraintOfDecorator.types
Original file line number Diff line number Diff line change
Expand Up @@ -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[]

Expand Down
77 changes: 77 additions & 0 deletions tests/baselines/reference/mixinIntersectionIsValidbaseType.js
Original file line number Diff line number Diff line change
@@ -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)));
};
74 changes: 74 additions & 0 deletions tests/baselines/reference/mixinIntersectionIsValidbaseType.symbols
Original file line number Diff line number Diff line change
@@ -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))

};
};
67 changes: 67 additions & 0 deletions tests/baselines/reference/mixinIntersectionIsValidbaseType.types
Original file line number Diff line number Diff line change
@@ -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

};
};
27 changes: 27 additions & 0 deletions tests/cases/compiler/mixinIntersectionIsValidbaseType.ts
Original file line number Diff line number Diff line change
@@ -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) {
};
};