Skip to content

Commit a6c5306

Browse files
committed
Allow object intersection types as class/interface base types
1 parent 4759ade commit a6c5306

File tree

4 files changed

+42
-22
lines changed

4 files changed

+42
-22
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3732,11 +3732,16 @@ namespace ts {
37323732
return getObjectFlags(type) & ObjectFlags.Reference ? (<TypeReference>type).target : type;
37333733
}
37343734

3735-
function hasBaseType(type: InterfaceType, checkBase: InterfaceType) {
3735+
function hasBaseType(type: BaseType, checkBase: BaseType) {
37363736
return check(type);
3737-
function check(type: InterfaceType): boolean {
3738-
const target = <InterfaceType>getTargetType(type);
3739-
return target === checkBase || forEach(getBaseTypes(target), check);
3737+
function check(type: BaseType): boolean {
3738+
if (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference)) {
3739+
const target = <InterfaceType>getTargetType(type);
3740+
return target === checkBase || forEach(getBaseTypes(target), check);
3741+
}
3742+
else if (type.flags & TypeFlags.Intersection) {
3743+
return forEach((<IntersectionType>type).types, check);
3744+
}
37403745
}
37413746
}
37423747

@@ -3862,7 +3867,7 @@ namespace ts {
38623867
return type.resolvedBaseConstructorType;
38633868
}
38643869

3865-
function getBaseTypes(type: InterfaceType): ObjectType[] {
3870+
function getBaseTypes(type: InterfaceType): BaseType[] {
38663871
if (!type.resolvedBaseTypes) {
38673872
if (type.objectFlags & ObjectFlags.Tuple) {
38683873
type.resolvedBaseTypes = [createArrayType(getUnionType(type.typeParameters))];
@@ -3922,11 +3927,11 @@ namespace ts {
39223927
if (baseType === unknownType) {
39233928
return;
39243929
}
3925-
if (!(getObjectFlags(getTargetType(baseType)) & ObjectFlags.ClassOrInterface)) {
3930+
if (!isValidBaseType(baseType)) {
39263931
error(baseTypeNode.expression, Diagnostics.Base_constructor_return_type_0_is_not_a_class_or_interface_type, typeToString(baseType));
39273932
return;
39283933
}
3929-
if (type === baseType || hasBaseType(<InterfaceType>baseType, type)) {
3934+
if (type === baseType || hasBaseType(<BaseType>baseType, type)) {
39303935
error(valueDecl, Diagnostics.Type_0_recursively_references_itself_as_a_base_type,
39313936
typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType));
39323937
return;
@@ -3951,15 +3956,22 @@ namespace ts {
39513956
return true;
39523957
}
39533958

3959+
// A valid base type is any non-generic object type or intersection of non-generic
3960+
// object types.
3961+
function isValidBaseType(type: Type): boolean {
3962+
return type.flags & TypeFlags.Object && !isGenericMappedType(type) ||
3963+
type.flags & TypeFlags.Intersection && !forEach((<IntersectionType>type).types, t => !isValidBaseType(t));
3964+
}
3965+
39543966
function resolveBaseTypesOfInterface(type: InterfaceType): void {
39553967
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
39563968
for (const declaration of type.symbol.declarations) {
39573969
if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration)) {
39583970
for (const node of getInterfaceBaseTypeNodes(<InterfaceDeclaration>declaration)) {
39593971
const baseType = getTypeFromTypeNode(node);
39603972
if (baseType !== unknownType) {
3961-
if (getObjectFlags(getTargetType(baseType)) & ObjectFlags.ClassOrInterface) {
3962-
if (type !== baseType && !hasBaseType(<InterfaceType>baseType, type)) {
3973+
if (isValidBaseType(baseType)) {
3974+
if (type !== baseType && !hasBaseType(<BaseType>baseType, type)) {
39633975
if (type.resolvedBaseTypes === emptyArray) {
39643976
type.resolvedBaseTypes = [<ObjectType>baseType];
39653977
}
@@ -4317,6 +4329,9 @@ namespace ts {
43174329
return createTypeReference((<TypeReference>type).target,
43184330
concatenate((<TypeReference>type).typeArguments, [thisArgument || (<TypeReference>type).target.thisType]));
43194331
}
4332+
if (type.flags & TypeFlags.Intersection) {
4333+
return getIntersectionType(map((<IntersectionType>type).types, t => getTypeWithThisArgument(t, thisArgument)));
4334+
}
43204335
return type;
43214336
}
43224337

@@ -4350,8 +4365,8 @@ namespace ts {
43504365
}
43514366
const thisArgument = lastOrUndefined(typeArguments);
43524367
for (const baseType of baseTypes) {
4353-
const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(<ObjectType>instantiateType(baseType, mapper), thisArgument) : baseType;
4354-
addInheritedMembers(members, getPropertiesOfObjectType(instantiatedBaseType));
4368+
const instantiatedBaseType = thisArgument ? getTypeWithThisArgument(instantiateType(baseType, mapper), thisArgument) : baseType;
4369+
addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType));
43554370
callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call));
43564371
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct));
43574372
stringIndexInfo = stringIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
@@ -18216,16 +18231,18 @@ namespace ts {
1821618231
return;
1821718232
}
1821818233

18234+
const propDeclaration = prop.valueDeclaration;
18235+
1821918236
// index is numeric and property name is not valid numeric literal
18220-
if (indexKind === IndexKind.Number && !isNumericName(prop.valueDeclaration.name)) {
18237+
if (indexKind === IndexKind.Number && propDeclaration && !isNumericName(propDeclaration.name)) {
1822118238
return;
1822218239
}
1822318240

1822418241
// perform property check if property or indexer is declared in 'type'
1822518242
// this allows to rule out cases when both property and indexer are inherited from the base class
1822618243
let errorNode: Node;
18227-
if (prop.valueDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) {
18228-
errorNode = prop.valueDeclaration;
18244+
if (propDeclaration && propDeclaration.name.kind === SyntaxKind.ComputedPropertyName || prop.parent === containingType.symbol) {
18245+
errorNode = propDeclaration;
1822918246
}
1823018247
else if (indexDeclaration) {
1823118248
errorNode = indexDeclaration;
@@ -18364,7 +18381,7 @@ namespace ts {
1836418381
checkTypeAssignableTo(staticType, getTypeWithoutSignatures(staticBaseType), node.name || node,
1836518382
Diagnostics.Class_static_side_0_incorrectly_extends_base_class_static_side_1);
1836618383

18367-
if (baseType.symbol.valueDeclaration &&
18384+
if (baseType.symbol && baseType.symbol.valueDeclaration &&
1836818385
!isInAmbientContext(baseType.symbol.valueDeclaration) &&
1836918386
baseType.symbol.valueDeclaration.kind === SyntaxKind.ClassDeclaration) {
1837018387
if (!isBlockScopedNameDeclaredBeforeUse(baseType.symbol.valueDeclaration, node)) {
@@ -18437,7 +18454,7 @@ namespace ts {
1843718454
return forEach(symbol.declarations, d => isClassLike(d) ? d : undefined);
1843818455
}
1843918456

18440-
function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: ObjectType): void {
18457+
function checkKindsOfPropertyMemberOverrides(type: InterfaceType, baseType: BaseType): void {
1844118458

1844218459
// TypeScript 1.0 spec (April 2014): 8.2.3
1844318460
// A derived class inherits all members from its base class it doesn't override.
@@ -18454,7 +18471,7 @@ namespace ts {
1845418471
// derived class instance member variables and accessors, but not by other kinds of members.
1845518472

1845618473
// NOTE: assignability is checked in checkClassDeclaration
18457-
const baseProperties = getPropertiesOfObjectType(baseType);
18474+
const baseProperties = getPropertiesOfType(baseType);
1845818475
for (const baseProperty of baseProperties) {
1845918476
const base = getTargetSymbol(baseProperty);
1846018477

@@ -18578,7 +18595,7 @@ namespace ts {
1857818595
let ok = true;
1857918596

1858018597
for (const base of baseTypes) {
18581-
const properties = getPropertiesOfObjectType(getTypeWithThisArgument(base, type.thisType));
18598+
const properties = getPropertiesOfType(getTypeWithThisArgument(base, type.thisType));
1858218599
for (const prop of properties) {
1858318600
const existing = seen.get(prop.name);
1858418601
if (!existing) {

src/compiler/types.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2358,7 +2358,7 @@
23582358
getIndexInfoOfType(type: Type, kind: IndexKind): IndexInfo;
23592359
getSignaturesOfType(type: Type, kind: SignatureKind): Signature[];
23602360
getIndexTypeOfType(type: Type, kind: IndexKind): Type;
2361-
getBaseTypes(type: InterfaceType): ObjectType[];
2361+
getBaseTypes(type: InterfaceType): BaseType[];
23622362
getReturnTypeOfSignature(signature: Signature): Type;
23632363
/**
23642364
* Gets the type of a parameter at a given position in a signature.
@@ -2910,9 +2910,12 @@
29102910
/* @internal */
29112911
resolvedBaseConstructorType?: Type; // Resolved base constructor type of class
29122912
/* @internal */
2913-
resolvedBaseTypes: ObjectType[]; // Resolved base types
2913+
resolvedBaseTypes: BaseType[]; // Resolved base types
29142914
}
29152915

2916+
// Object type or intersection of object types
2917+
export type BaseType = ObjectType | IntersectionType;
2918+
29162919
export interface InterfaceTypeWithDeclaredMembers extends InterfaceType {
29172920
declaredProperties: Symbol[]; // Declared members
29182921
declaredCallSignatures: Signature[]; // Declared call signatures

src/services/services.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ namespace ts {
379379
getNumberIndexType(): Type {
380380
return this.checker.getIndexTypeOfType(this, IndexKind.Number);
381381
}
382-
getBaseTypes(): ObjectType[] {
382+
getBaseTypes(): BaseType[] {
383383
return this.flags & TypeFlags.Object && this.objectFlags & (ObjectFlags.Class | ObjectFlags.Interface)
384384
? this.checker.getBaseTypes(<InterfaceType><Type>this)
385385
: undefined;

src/services/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ namespace ts {
3333
getConstructSignatures(): Signature[];
3434
getStringIndexType(): Type;
3535
getNumberIndexType(): Type;
36-
getBaseTypes(): ObjectType[];
36+
getBaseTypes(): BaseType[];
3737
getNonNullableType(): Type;
3838
}
3939

0 commit comments

Comments
 (0)