Skip to content

Commit 89b72ac

Browse files
committed
Support mixin classes
1 parent feb08b8 commit 89b72ac

File tree

2 files changed

+76
-14
lines changed

2 files changed

+76
-14
lines changed

src/compiler/checker.ts

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2465,7 +2465,8 @@ namespace ts {
24652465
const symbol = type.symbol;
24662466
if (symbol) {
24672467
// Always use 'typeof T' for type of class, enum, and module objects
2468-
if (symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
2468+
if (symbol.flags & SymbolFlags.Class && !getBaseTypeVariableOfClass(symbol) ||
2469+
symbol.flags & (SymbolFlags.Enum | SymbolFlags.ValueModule)) {
24692470
writeTypeOfSymbol(type, flags);
24702471
}
24712472
else if (shouldWriteTypeOfFunctionSymbol()) {
@@ -3639,6 +3640,11 @@ namespace ts {
36393640
return links.type;
36403641
}
36413642

3643+
function getBaseTypeVariableOfClass(symbol: Symbol) {
3644+
const baseConstructorType = getBaseConstructorTypeOfClass(getDeclaredTypeOfClassOrInterface(symbol));
3645+
return baseConstructorType.flags & TypeFlags.TypeVariable ? baseConstructorType : undefined;
3646+
}
3647+
36423648
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
36433649
const links = getSymbolLinks(symbol);
36443650
if (!links.type) {
@@ -3647,8 +3653,13 @@ namespace ts {
36473653
}
36483654
else {
36493655
const type = createObjectType(ObjectFlags.Anonymous, symbol);
3650-
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ?
3651-
includeFalsyTypes(type, TypeFlags.Undefined) : type;
3656+
if (symbol.flags & SymbolFlags.Class) {
3657+
const baseTypeVariable = getBaseTypeVariableOfClass(symbol);
3658+
links.type = baseTypeVariable ? getIntersectionType([type, baseTypeVariable]) : type;
3659+
}
3660+
else {
3661+
links.type = strictNullChecks && symbol.flags & SymbolFlags.Optional ? includeFalsyTypes(type, TypeFlags.Undefined) : type;
3662+
}
36523663
}
36533664
}
36543665
return links.type;
@@ -3812,8 +3823,26 @@ namespace ts {
38123823
return concatenate(getOuterTypeParametersOfClassOrInterface(symbol), getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol));
38133824
}
38143825

3826+
// A type is a mixin constructor if it has a single construct signature taking no type parameters and a single
3827+
// rest parameter of type any[].
3828+
function isMixinConstructorType(type: Type) {
3829+
const signatures = getSignaturesOfType(type, SignatureKind.Construct);
3830+
if (signatures.length === 1) {
3831+
const s = signatures[0];
3832+
return !s.typeParameters && s.parameters.length === 1 && s.hasRestParameter && getTypeOfParameter(s.parameters[0]) === anyArrayType;
3833+
}
3834+
return false;
3835+
}
3836+
38153837
function isConstructorType(type: Type): boolean {
3816-
return isValidBaseType(type) && getSignaturesOfType(type, SignatureKind.Construct).length > 0;
3838+
if (isValidBaseType(type) && getSignaturesOfType(type, SignatureKind.Construct).length > 0) {
3839+
return true;
3840+
}
3841+
if (type.flags & TypeFlags.TypeVariable) {
3842+
const constraint = getBaseConstraintOfType(<TypeVariable>type);
3843+
return isValidBaseType(constraint) && isMixinConstructorType(constraint);
3844+
}
3845+
return false;
38173846
}
38183847

38193848
function getBaseTypeNodeOfClass(type: InterfaceType): ExpressionWithTypeArguments {
@@ -3892,7 +3921,7 @@ namespace ts {
38923921

38933922
function resolveBaseTypesOfClass(type: InterfaceType): void {
38943923
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
3895-
const baseConstructorType = <ObjectType>getBaseConstructorTypeOfClass(type);
3924+
const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type));
38963925
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
38973926
return;
38983927
}
@@ -4547,11 +4576,32 @@ namespace ts {
45474576
// intersection type use getPropertiesOfType (only the language service uses this).
45484577
let callSignatures: Signature[] = emptyArray;
45494578
let constructSignatures: Signature[] = emptyArray;
4550-
let stringIndexInfo: IndexInfo = undefined;
4551-
let numberIndexInfo: IndexInfo = undefined;
4552-
for (const t of type.types) {
4579+
let stringIndexInfo: IndexInfo;
4580+
let numberIndexInfo: IndexInfo;
4581+
let mixinTypes: Type[];
4582+
const count = type.types.length;
4583+
for (let i = 0; i < count; i++) {
4584+
const t = type.types[i];
4585+
// When a type T is preceded by a mixin constructor type, the return type of each construct signature
4586+
// in T is intersected with the return type of the mixin constructor, and the mixin construct signature
4587+
// is removed. For example, the intersection '{ new(...args: any[]) => A } & { new(s: string) => B }'
4588+
// has a single construct signature 'new(s: string) => A & B'.
4589+
let signatures = getSignaturesOfType(t, SignatureKind.Construct);
4590+
if (i < count - 1 && isMixinConstructorType(t)) {
4591+
(mixinTypes || (mixinTypes = [])).push(getReturnTypeOfSignature(signatures[0]));
4592+
}
4593+
else {
4594+
if (mixinTypes) {
4595+
signatures = map(signatures, s => {
4596+
const clone = cloneSignature(s);
4597+
clone.resolvedReturnType = getIntersectionType([...mixinTypes, getReturnTypeOfSignature(s)]);
4598+
return clone;
4599+
});
4600+
mixinTypes = undefined;
4601+
}
4602+
constructSignatures = concatenate(constructSignatures, signatures);
4603+
}
45534604
callSignatures = concatenate(callSignatures, getSignaturesOfType(t, SignatureKind.Call));
4554-
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(t, SignatureKind.Construct));
45554605
stringIndexInfo = intersectIndexInfos(stringIndexInfo, getIndexInfoOfType(t, IndexKind.String));
45564606
numberIndexInfo = intersectIndexInfos(numberIndexInfo, getIndexInfoOfType(t, IndexKind.Number));
45574607
}
@@ -4593,7 +4643,7 @@ namespace ts {
45934643
constructSignatures = getDefaultConstructSignatures(classType);
45944644
}
45954645
const baseConstructorType = getBaseConstructorTypeOfClass(classType);
4596-
if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection)) {
4646+
if (baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.TypeVariable)) {
45974647
members = createSymbolTable(getNamedMembers(members));
45984648
addInheritedMembers(members, getPropertiesOfType(baseConstructorType));
45994649
}
@@ -4890,6 +4940,7 @@ namespace ts {
48904940

48914941
function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: string): Symbol {
48924942
const types = containingType.types;
4943+
const excludeModifiers = containingType.flags & TypeFlags.Union ? ModifierFlags.Private | ModifierFlags.Protected : 0;
48934944
let props: Symbol[];
48944945
// Flags we want to propagate to the result if they exist in all source symbols
48954946
let commonFlags = (containingType.flags & TypeFlags.Intersection) ? SymbolFlags.Optional : SymbolFlags.None;
@@ -4899,7 +4950,7 @@ namespace ts {
48994950
const type = getApparentType(current);
49004951
if (type !== unknownType) {
49014952
const prop = getPropertyOfType(type, name);
4902-
if (prop && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))) {
4953+
if (prop && !(getDeclarationModifierFlagsFromSymbol(prop) & excludeModifiers)) {
49034954
commonFlags &= prop.flags;
49044955
if (!props) {
49054956
props = [prop];
@@ -6965,9 +7016,12 @@ namespace ts {
69657016
result.properties = resolved.properties;
69667017
result.callSignatures = emptyArray;
69677018
result.constructSignatures = emptyArray;
6968-
type = result;
7019+
return result;
69697020
}
69707021
}
7022+
else if (type.flags & TypeFlags.Intersection) {
7023+
return getIntersectionType(map((<IntersectionType>type).types, getTypeWithoutSignatures));
7024+
}
69717025
return type;
69727026
}
69737027

@@ -18387,7 +18441,8 @@ namespace ts {
1838718441
const baseTypes = getBaseTypes(type);
1838818442
if (baseTypes.length && produceDiagnostics) {
1838918443
const baseType = baseTypes[0];
18390-
const staticBaseType = getBaseConstructorTypeOfClass(type);
18444+
const baseConstructorType = getBaseConstructorTypeOfClass(type);
18445+
const staticBaseType = getApparentType(baseConstructorType);
1839118446
checkBaseTypeAccessibility(staticBaseType, baseTypeNode);
1839218447
checkSourceElement(baseTypeNode.expression);
1839318448
if (baseTypeNode.typeArguments) {
@@ -18401,6 +18456,9 @@ namespace ts {
1840118456
checkTypeAssignableTo(typeWithThis, getTypeWithThisArgument(baseType, type.thisType), node.name || node, Diagnostics.Class_0_incorrectly_extends_base_class_1);
1840218457
checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node,
1840318458
Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1);
18459+
if (baseConstructorType.flags & TypeFlags.TypeVariable && !isMixinConstructorType(staticType)) {
18460+
error(node.name || node, Diagnostics.A_mixin_class_must_have_a_constructor_with_a_single_rest_parameter_of_type_any);
18461+
}
1840418462

1840518463
if (baseType.symbol && baseType.symbol.valueDeclaration &&
1840618464
!isInAmbientContext(baseType.symbol.valueDeclaration) &&
@@ -18410,7 +18468,7 @@ namespace ts {
1841018468
}
1841118469
}
1841218470

18413-
if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class)) {
18471+
if (!(staticBaseType.symbol && staticBaseType.symbol.flags & SymbolFlags.Class) && !(baseConstructorType.flags & TypeFlags.TypeVariable)) {
1841418472
// When the static base type is a "class-like" constructor function (but not actually a class), we verify
1841518473
// that all instantiated base constructor signatures return the same type. We can simply compare the type
1841618474
// references (as opposed to checking the structure of the types) because elsewhere we have already checked

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1783,6 +1783,10 @@
17831783
"category": "Error",
17841784
"code": 2544
17851785
},
1786+
"A mixin class must have a constructor with a single rest parameter of type 'any[]'.": {
1787+
"category": "Error",
1788+
"code": 2545
1789+
},
17861790
"JSX element attributes type '{0}' may not be a union type.": {
17871791
"category": "Error",
17881792
"code": 2600

0 commit comments

Comments
 (0)