Skip to content

Commit 6d36dd5

Browse files
committed
Merge pull request microsoft#2729 from Microsoft/reducedUnionTypes
Consistently reduce union types
2 parents d1bd164 + 9a2846e commit 6d36dd5

7 files changed

Lines changed: 124 additions & 23 deletions

File tree

src/compiler/checker.ts

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2904,16 +2904,17 @@ module ts {
29042904
}
29052905

29062906
function getPropertiesOfType(type: Type): Symbol[] {
2907-
if (type.flags & TypeFlags.Union) {
2908-
return getPropertiesOfUnionType(<UnionType>type);
2909-
}
2910-
return getPropertiesOfObjectType(getApparentType(type));
2907+
type = getApparentType(type);
2908+
return type.flags & TypeFlags.Union ? getPropertiesOfUnionType(<UnionType>type) : getPropertiesOfObjectType(type);
29112909
}
29122910

29132911
// For a type parameter, return the base constraint of the type parameter. For the string, number,
29142912
// boolean, and symbol primitive types, return the corresponding object types. Otherwise return the
29152913
// type itself. Note that the apparent type of a union type is the union type itself.
29162914
function getApparentType(type: Type): Type {
2915+
if (type.flags & TypeFlags.Union) {
2916+
type = getReducedTypeOfUnionType(<UnionType>type);
2917+
}
29172918
if (type.flags & TypeFlags.TypeParameter) {
29182919
do {
29192920
type = getConstraintOfTypeParameter(<TypeParameter>type);
@@ -2986,27 +2987,27 @@ module ts {
29862987
// necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from
29872988
// Object and Function as appropriate.
29882989
function getPropertyOfType(type: Type, name: string): Symbol {
2989-
if (type.flags & TypeFlags.Union) {
2990-
return getPropertyOfUnionType(<UnionType>type, name);
2991-
}
2992-
if (!(type.flags & TypeFlags.ObjectType)) {
2993-
type = getApparentType(type);
2994-
if (!(type.flags & TypeFlags.ObjectType)) {
2995-
return undefined;
2990+
type = getApparentType(type);
2991+
if (type.flags & TypeFlags.ObjectType) {
2992+
let resolved = resolveObjectOrUnionTypeMembers(type);
2993+
if (hasProperty(resolved.members, name)) {
2994+
let symbol = resolved.members[name];
2995+
if (symbolIsValue(symbol)) {
2996+
return symbol;
2997+
}
29962998
}
2997-
}
2998-
let resolved = resolveObjectOrUnionTypeMembers(type);
2999-
if (hasProperty(resolved.members, name)) {
3000-
let symbol = resolved.members[name];
3001-
if (symbolIsValue(symbol)) {
3002-
return symbol;
2999+
if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) {
3000+
let symbol = getPropertyOfObjectType(globalFunctionType, name);
3001+
if (symbol) {
3002+
return symbol;
3003+
}
30033004
}
3005+
return getPropertyOfObjectType(globalObjectType, name);
30043006
}
3005-
if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) {
3006-
let symbol = getPropertyOfObjectType(globalFunctionType, name);
3007-
if (symbol) return symbol;
3007+
if (type.flags & TypeFlags.Union) {
3008+
return getPropertyOfUnionType(<UnionType>type, name);
30083009
}
3009-
return getPropertyOfObjectType(globalObjectType, name);
3010+
return undefined;
30103011
}
30113012

30123013
function getSignaturesOfObjectOrUnionType(type: Type, kind: SignatureKind): Signature[] {
@@ -3581,6 +3582,10 @@ module ts {
35813582
}
35823583
}
35833584

3585+
// The noSubtypeReduction flag is there because it isn't possible to always do subtype reduction. The flag
3586+
// is true when creating a union type from a type node and when instantiating a union type. In both of those
3587+
// cases subtype reduction has to be deferred to properly support recursive union types. For example, a
3588+
// type alias of the form "type Item = string | (() => Item)" cannot be reduced during its declaration.
35843589
function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type {
35853590
if (types.length === 0) {
35863591
return emptyObjectType;
@@ -3605,10 +3610,19 @@ module ts {
36053610
if (!type) {
36063611
type = unionTypes[id] = <UnionType>createObjectType(TypeFlags.Union | getWideningFlagsOfTypes(sortedTypes));
36073612
type.types = sortedTypes;
3613+
type.reducedType = noSubtypeReduction ? undefined : type;
36083614
}
36093615
return type;
36103616
}
36113617

3618+
function getReducedTypeOfUnionType(type: UnionType): Type {
3619+
// If union type was created without subtype reduction, perform the deferred reduction now
3620+
if (!type.reducedType) {
3621+
type.reducedType = getUnionType(type.types, /*noSubtypeReduction*/ false);
3622+
}
3623+
return type.reducedType;
3624+
}
3625+
36123626
function getTypeFromUnionTypeNode(node: UnionTypeNode): Type {
36133627
let links = getNodeLinks(node);
36143628
if (!links.resolvedType) {

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1512,6 +1512,8 @@ module ts {
15121512
export interface UnionType extends Type {
15131513
types: Type[]; // Constituent types
15141514
/* @internal */
1515+
reducedType: Type; // Reduced union type (all subtypes removed)
1516+
/* @internal */
15151517
resolvedProperties: SymbolTable; // Cache of resolved properties
15161518
}
15171519

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [unionTypeReduction.ts]
2+
interface I2 {
3+
(): number;
4+
(q): boolean;
5+
}
6+
7+
interface I3 {
8+
(): number;
9+
}
10+
11+
var i2: I2, i3: I3;
12+
13+
var e1: I2 | I3;
14+
var e2 = i2 || i3; // Type of e2 immediately reduced to I3
15+
16+
var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature
17+
var r2 = e2();
18+
19+
20+
//// [unionTypeReduction.js]
21+
var i2, i3;
22+
var e1;
23+
var e2 = i2 || i3; // Type of e2 immediately reduced to I3
24+
var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature
25+
var r2 = e2();
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/conformance/types/union/unionTypeReduction.ts ===
2+
interface I2 {
3+
>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0))
4+
5+
(): number;
6+
(q): boolean;
7+
>q : any, Symbol(q, Decl(unionTypeReduction.ts, 2, 5))
8+
}
9+
10+
interface I3 {
11+
>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1))
12+
13+
(): number;
14+
}
15+
16+
var i2: I2, i3: I3;
17+
>i2 : I2, Symbol(i2, Decl(unionTypeReduction.ts, 9, 3))
18+
>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0))
19+
>i3 : I3, Symbol(i3, Decl(unionTypeReduction.ts, 9, 11))
20+
>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1))
21+
22+
var e1: I2 | I3;
23+
>e1 : I2 | I3, Symbol(e1, Decl(unionTypeReduction.ts, 11, 3))
24+
>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0))
25+
>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1))
26+
27+
var e2 = i2 || i3; // Type of e2 immediately reduced to I3
28+
>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3))
29+
>i2 || i3 : I3
30+
>i2 : I2, Symbol(i2, Decl(unionTypeReduction.ts, 9, 3))
31+
>i3 : I3, Symbol(i3, Decl(unionTypeReduction.ts, 9, 11))
32+
33+
var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature
34+
>r1 : number, Symbol(r1, Decl(unionTypeReduction.ts, 14, 3))
35+
>e1() : number
36+
>e1 : I2 | I3, Symbol(e1, Decl(unionTypeReduction.ts, 11, 3))
37+
38+
var r2 = e2();
39+
>r2 : number, Symbol(r2, Decl(unionTypeReduction.ts, 15, 3))
40+
>e2() : number
41+
>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3))
42+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
interface I2 {
2+
(): number;
3+
(q): boolean;
4+
}
5+
6+
interface I3 {
7+
(): number;
8+
}
9+
10+
var i2: I2, i3: I3;
11+
12+
var e1: I2 | I3;
13+
var e2 = i2 || i3; // Type of e2 immediately reduced to I3
14+
15+
var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature
16+
var r2 = e2();

tests/cases/fourslash/completionEntryForPropertyFromUnionOfModuleType.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
////module E {
44
//// export var n = 1;
5+
//// export var x = 0;
56
////}
67
////module F {
78
//// export var n = 1;
9+
//// export var y = 0;
810
////}
911
////var q: typeof E | typeof F;
1012
////var j = q./*1*/

tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
goTo.marker("propertyReference");
2020
verify.definitionCountIs(2);
2121
goTo.definition(0);
22-
verify.caretAtMarker("propertyDefinition2");
22+
verify.caretAtMarker("propertyDefinition1");
2323

2424
goTo.marker("propertyReference");
2525
goTo.definition(1);
26-
verify.caretAtMarker("propertyDefinition1");
26+
verify.caretAtMarker("propertyDefinition2");

0 commit comments

Comments
 (0)