Skip to content

Commit 7f6608b

Browse files
committed
Merge pull request #4537 from Microsoft/unionSubtypeReduction
Union subtype reduction
2 parents 9f8de0f + 603eb86 commit 7f6608b

File tree

44 files changed

+660
-546
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+660
-546
lines changed

src/compiler/checker.ts

Lines changed: 43 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3119,7 +3119,7 @@ namespace ts {
31193119
}
31203120

31213121
function resolveTupleTypeMembers(type: TupleType) {
3122-
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noDeduplication*/ true)));
3122+
let arrayType = resolveStructuredTypeMembers(createArrayType(getUnionType(type.elementTypes, /*noSubtypeReduction*/ true)));
31233123
let members = createTupleTypeMemberSymbols(type.elementTypes);
31243124
addInheritedMembers(members, arrayType.properties);
31253125
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
@@ -3451,29 +3451,6 @@ namespace ts {
34513451
return undefined;
34523452
}
34533453

3454-
// Check if a property with the given name is known anywhere in the given type. In an object
3455-
// type, a property is considered known if the object type is empty, if it has any index
3456-
// signatures, or if the property is actually declared in the type. In a union or intersection
3457-
// type, a property is considered known if it is known in any constituent type.
3458-
function isKnownProperty(type: Type, name: string): boolean {
3459-
if (type.flags & TypeFlags.ObjectType && type !== globalObjectType) {
3460-
const resolved = resolveStructuredTypeMembers(type);
3461-
return !!(resolved.properties.length === 0 ||
3462-
resolved.stringIndexType ||
3463-
resolved.numberIndexType ||
3464-
getPropertyOfType(type, name));
3465-
}
3466-
if (type.flags & TypeFlags.UnionOrIntersection) {
3467-
for (let t of (<UnionOrIntersectionType>type).types) {
3468-
if (isKnownProperty(t, name)) {
3469-
return true;
3470-
}
3471-
}
3472-
return false;
3473-
}
3474-
return true;
3475-
}
3476-
34773454
function getSignaturesOfStructuredType(type: Type, kind: SignatureKind): Signature[] {
34783455
if (type.flags & TypeFlags.StructuredType) {
34793456
let resolved = resolveStructuredTypeMembers(<ObjectType>type);
@@ -4103,73 +4080,20 @@ namespace ts {
41034080
}
41044081
}
41054082

4106-
function isObjectLiteralTypeDuplicateOf(source: ObjectType, target: ObjectType): boolean {
4107-
let sourceProperties = getPropertiesOfObjectType(source);
4108-
let targetProperties = getPropertiesOfObjectType(target);
4109-
if (sourceProperties.length !== targetProperties.length) {
4110-
return false;
4111-
}
4112-
for (let sourceProp of sourceProperties) {
4113-
let targetProp = getPropertyOfObjectType(target, sourceProp.name);
4114-
if (!targetProp ||
4115-
getDeclarationFlagsFromSymbol(targetProp) & (NodeFlags.Private | NodeFlags.Protected) ||
4116-
!isTypeDuplicateOf(getTypeOfSymbol(sourceProp), getTypeOfSymbol(targetProp))) {
4117-
return false;
4118-
}
4119-
}
4120-
return true;
4121-
}
4122-
4123-
function isTupleTypeDuplicateOf(source: TupleType, target: TupleType): boolean {
4124-
let sourceTypes = source.elementTypes;
4125-
let targetTypes = target.elementTypes;
4126-
if (sourceTypes.length !== targetTypes.length) {
4127-
return false;
4128-
}
4129-
for (var i = 0; i < sourceTypes.length; i++) {
4130-
if (!isTypeDuplicateOf(sourceTypes[i], targetTypes[i])) {
4131-
return false;
4132-
}
4133-
}
4134-
return true;
4135-
}
4136-
4137-
// Returns true if the source type is a duplicate of the target type. A source type is a duplicate of
4138-
// a target type if the the two are identical, with the exception that the source type may have null or
4139-
// undefined in places where the target type doesn't. This is by design an asymmetric relationship.
4140-
function isTypeDuplicateOf(source: Type, target: Type): boolean {
4141-
if (source === target) {
4142-
return true;
4143-
}
4144-
if (source.flags & TypeFlags.Undefined || source.flags & TypeFlags.Null && !(target.flags & TypeFlags.Undefined)) {
4145-
return true;
4146-
}
4147-
if (source.flags & TypeFlags.ObjectLiteral && target.flags & TypeFlags.ObjectType) {
4148-
return isObjectLiteralTypeDuplicateOf(<ObjectType>source, <ObjectType>target);
4149-
}
4150-
if (isArrayType(source) && isArrayType(target)) {
4151-
return isTypeDuplicateOf((<TypeReference>source).typeArguments[0], (<TypeReference>target).typeArguments[0]);
4152-
}
4153-
if (isTupleType(source) && isTupleType(target)) {
4154-
return isTupleTypeDuplicateOf(<TupleType>source, <TupleType>target);
4155-
}
4156-
return isTypeIdenticalTo(source, target);
4157-
}
4158-
4159-
function isTypeDuplicateOfSomeType(candidate: Type, types: Type[]): boolean {
4160-
for (let type of types) {
4161-
if (candidate !== type && isTypeDuplicateOf(candidate, type)) {
4083+
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
4084+
for (let i = 0, len = types.length; i < len; i++) {
4085+
if (candidate !== types[i] && isTypeSubtypeOf(candidate, types[i])) {
41624086
return true;
41634087
}
41644088
}
41654089
return false;
41664090
}
41674091

4168-
function removeDuplicateTypes(types: Type[]) {
4092+
function removeSubtypes(types: Type[]) {
41694093
let i = types.length;
41704094
while (i > 0) {
41714095
i--;
4172-
if (isTypeDuplicateOfSomeType(types[i], types)) {
4096+
if (isSubtypeOfAny(types[i], types)) {
41734097
types.splice(i, 1);
41744098
}
41754099
}
@@ -4194,12 +4118,14 @@ namespace ts {
41944118
}
41954119
}
41964120

4197-
// We always deduplicate the constituent type set based on object identity, but we'll also deduplicate
4198-
// based on the structure of the types unless the noDeduplication flag is true, which is the case when
4199-
// creating a union type from a type node and when instantiating a union type. In both of those cases,
4200-
// structural deduplication has to be deferred to properly support recursive union types. For example,
4201-
// a type of the form "type Item = string | (() => Item)" cannot be deduplicated during its declaration.
4202-
function getUnionType(types: Type[], noDeduplication?: boolean): Type {
4121+
// We reduce the constituent type set to only include types that aren't subtypes of other types, unless
4122+
// the noSubtypeReduction flag is specified, in which case we perform a simple deduplication based on
4123+
// object identity. Subtype reduction is possible only when union types are known not to circularly
4124+
// reference themselves (as is the case with union types created by expression constructs such as array
4125+
// literals and the || and ?: operators). Named types can circularly reference themselves and therefore
4126+
// cannot be deduplicated during their declaration. For example, "type Item = string | (() => Item" is
4127+
// a named type that circularly references itself.
4128+
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
42034129
if (types.length === 0) {
42044130
return emptyObjectType;
42054131
}
@@ -4208,12 +4134,12 @@ namespace ts {
42084134
if (containsTypeAny(typeSet)) {
42094135
return anyType;
42104136
}
4211-
if (noDeduplication) {
4137+
if (noSubtypeReduction) {
42124138
removeAllButLast(typeSet, undefinedType);
42134139
removeAllButLast(typeSet, nullType);
42144140
}
42154141
else {
4216-
removeDuplicateTypes(typeSet);
4142+
removeSubtypes(typeSet);
42174143
}
42184144
if (typeSet.length === 1) {
42194145
return typeSet[0];
@@ -4230,7 +4156,7 @@ namespace ts {
42304156
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
42314157
let links = getNodeLinks(node);
42324158
if (!links.resolvedType) {
4233-
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noDeduplication*/ true);
4159+
links.resolvedType = getUnionType(map(node.types, getTypeFromTypeNode), /*noSubtypeReduction*/ true);
42344160
}
42354161
return links.resolvedType;
42364162
}
@@ -4526,7 +4452,7 @@ namespace ts {
45264452
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
45274453
}
45284454
if (type.flags & TypeFlags.Union) {
4529-
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noDeduplication*/ true);
4455+
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*noSubtypeReduction*/ true);
45304456
}
45314457
if (type.flags & TypeFlags.Intersection) {
45324458
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType));
@@ -4813,6 +4739,30 @@ namespace ts {
48134739
return Ternary.False;
48144740
}
48154741

4742+
// Check if a property with the given name is known anywhere in the given type. In an object type, a property
4743+
// is considered known if the object type is empty and the check is for assignability, if the object type has
4744+
// index signatures, or if the property is actually declared in the object type. In a union or intersection
4745+
// type, a property is considered known if it is known in any constituent type.
4746+
function isKnownProperty(type: Type, name: string): boolean {
4747+
if (type.flags & TypeFlags.ObjectType) {
4748+
const resolved = resolveStructuredTypeMembers(type);
4749+
if (relation === assignableRelation && (type === globalObjectType || resolved.properties.length === 0) ||
4750+
resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) {
4751+
return true;
4752+
}
4753+
return false;
4754+
}
4755+
if (type.flags & TypeFlags.UnionOrIntersection) {
4756+
for (let t of (<UnionOrIntersectionType>type).types) {
4757+
if (isKnownProperty(t, name)) {
4758+
return true;
4759+
}
4760+
}
4761+
return false;
4762+
}
4763+
return true;
4764+
}
4765+
48164766
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
48174767
for (let prop of getPropertiesOfObjectType(source)) {
48184768
if (!isKnownProperty(target, prop.name)) {
@@ -5594,7 +5544,7 @@ namespace ts {
55945544
return getWidenedTypeOfObjectLiteral(type);
55955545
}
55965546
if (type.flags & TypeFlags.Union) {
5597-
return getUnionType(map((<UnionType>type).types, getWidenedType));
5547+
return getUnionType(map((<UnionType>type).types, getWidenedType), /*noSubtypeReduction*/ true);
55985548
}
55995549
if (isArrayType(type)) {
56005550
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));

tests/baselines/reference/arrayBestCommonTypes.types

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,8 @@ module EmptyTypes {
294294

295295
// Order matters here so test all the variants
296296
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
297-
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
298-
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
297+
>a1 : { x: any; y: string; }[]
298+
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
299299
>{ x: 0, y: 'a' } : { x: number; y: string; }
300300
>x : number
301301
>0 : number
@@ -313,8 +313,8 @@ module EmptyTypes {
313313
>'a' : string
314314

315315
var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
316-
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
317-
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
316+
>a2 : { x: any; y: string; }[]
317+
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
318318
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
319319
>x : any
320320
>anyObj : any
@@ -332,8 +332,8 @@ module EmptyTypes {
332332
>'a' : string
333333

334334
var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
335-
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
336-
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
335+
>a3 : { x: any; y: string; }[]
336+
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
337337
>{ x: 0, y: 'a' } : { x: number; y: string; }
338338
>x : number
339339
>0 : number
@@ -639,7 +639,7 @@ module NonEmptyTypes {
639639
>x : number
640640
>y : base
641641
>base : base
642-
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : ({ x: number; y: derived; } | { x: number; y: base; })[]
642+
>[{ x: 7, y: new derived() }, { x: 5, y: new base() }] : { x: number; y: base; }[]
643643
>{ x: 7, y: new derived() } : { x: number; y: derived; }
644644
>x : number
645645
>7 : number
@@ -658,7 +658,7 @@ module NonEmptyTypes {
658658
>x : boolean
659659
>y : base
660660
>base : base
661-
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : ({ x: boolean; y: derived; } | { x: boolean; y: base; })[]
661+
>[{ x: true, y: new derived() }, { x: false, y: new base() }] : { x: boolean; y: base; }[]
662662
>{ x: true, y: new derived() } : { x: boolean; y: derived; }
663663
>x : boolean
664664
>true : boolean
@@ -697,8 +697,8 @@ module NonEmptyTypes {
697697

698698
// Order matters here so test all the variants
699699
var a1 = [{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }];
700-
>a1 : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
701-
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : ({ x: number; y: string; } | { x: string; y: string; } | { x: any; y: string; })[]
700+
>a1 : { x: any; y: string; }[]
701+
>[{ x: 0, y: 'a' }, { x: 'a', y: 'a' }, { x: anyObj, y: 'a' }] : { x: any; y: string; }[]
702702
>{ x: 0, y: 'a' } : { x: number; y: string; }
703703
>x : number
704704
>0 : number
@@ -716,8 +716,8 @@ module NonEmptyTypes {
716716
>'a' : string
717717

718718
var a2 = [{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }];
719-
>a2 : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
720-
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: any; y: string; } | { x: number; y: string; } | { x: string; y: string; })[]
719+
>a2 : { x: any; y: string; }[]
720+
>[{ x: anyObj, y: 'a' }, { x: 0, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
721721
>{ x: anyObj, y: 'a' } : { x: any; y: string; }
722722
>x : any
723723
>anyObj : any
@@ -735,8 +735,8 @@ module NonEmptyTypes {
735735
>'a' : string
736736

737737
var a3 = [{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }];
738-
>a3 : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
739-
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : ({ x: number; y: string; } | { x: any; y: string; } | { x: string; y: string; })[]
738+
>a3 : { x: any; y: string; }[]
739+
>[{ x: 0, y: 'a' }, { x: anyObj, y: 'a' }, { x: 'a', y: 'a' }] : { x: any; y: string; }[]
740740
>{ x: 0, y: 'a' } : { x: number; y: string; }
741741
>x : number
742742
>0 : number
@@ -769,29 +769,29 @@ module NonEmptyTypes {
769769
>base2 : typeof base2
770770

771771
var b1 = [baseObj, base2Obj, ifaceObj];
772-
>b1 : (base | base2 | iface)[]
773-
>[baseObj, base2Obj, ifaceObj] : (base | base2 | iface)[]
772+
>b1 : iface[]
773+
>[baseObj, base2Obj, ifaceObj] : iface[]
774774
>baseObj : base
775775
>base2Obj : base2
776776
>ifaceObj : iface
777777

778778
var b2 = [base2Obj, baseObj, ifaceObj];
779-
>b2 : (base2 | base | iface)[]
780-
>[base2Obj, baseObj, ifaceObj] : (base2 | base | iface)[]
779+
>b2 : iface[]
780+
>[base2Obj, baseObj, ifaceObj] : iface[]
781781
>base2Obj : base2
782782
>baseObj : base
783783
>ifaceObj : iface
784784

785785
var b3 = [baseObj, ifaceObj, base2Obj];
786-
>b3 : (base | iface | base2)[]
787-
>[baseObj, ifaceObj, base2Obj] : (base | iface | base2)[]
786+
>b3 : iface[]
787+
>[baseObj, ifaceObj, base2Obj] : iface[]
788788
>baseObj : base
789789
>ifaceObj : iface
790790
>base2Obj : base2
791791

792792
var b4 = [ifaceObj, baseObj, base2Obj];
793-
>b4 : (iface | base | base2)[]
794-
>[ifaceObj, baseObj, base2Obj] : (iface | base | base2)[]
793+
>b4 : iface[]
794+
>[ifaceObj, baseObj, base2Obj] : iface[]
795795
>ifaceObj : iface
796796
>baseObj : base
797797
>base2Obj : base2

tests/baselines/reference/arrayLiteralWithMultipleBestCommonTypes.types

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ var cs = [a, b, c]; // { x: number; y?: number };[]
3636
>c : { x: number; a?: number; }
3737

3838
var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
39-
>ds : (((x: Object) => number) | ((x: string) => number))[]
40-
>[(x: Object) => 1, (x: string) => 2] : (((x: Object) => number) | ((x: string) => number))[]
39+
>ds : ((x: Object) => number)[]
40+
>[(x: Object) => 1, (x: string) => 2] : ((x: Object) => number)[]
4141
>(x: Object) => 1 : (x: Object) => number
4242
>x : Object
4343
>Object : Object
@@ -47,8 +47,8 @@ var ds = [(x: Object) => 1, (x: string) => 2]; // { (x:Object) => number }[]
4747
>2 : number
4848

4949
var es = [(x: string) => 2, (x: Object) => 1]; // { (x:string) => number }[]
50-
>es : (((x: string) => number) | ((x: Object) => number))[]
51-
>[(x: string) => 2, (x: Object) => 1] : (((x: string) => number) | ((x: Object) => number))[]
50+
>es : ((x: string) => number)[]
51+
>[(x: string) => 2, (x: Object) => 1] : ((x: string) => number)[]
5252
>(x: string) => 2 : (x: string) => number
5353
>x : string
5454
>2 : number

tests/baselines/reference/arrayLiteralsWithRecursiveGenerics.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ var myDerivedList: DerivedList<number>;
7777
>DerivedList : DerivedList<U>
7878

7979
var as = [list, myDerivedList]; // List<number>[]
80-
>as : (List<number> | DerivedList<number>)[]
81-
>[list, myDerivedList] : (List<number> | DerivedList<number>)[]
80+
>as : List<number>[]
81+
>[list, myDerivedList] : List<number>[]
8282
>list : List<number>
8383
>myDerivedList : DerivedList<number>
8484

0 commit comments

Comments
 (0)