diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bff0c137e24..e9430a058f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2904,16 +2904,17 @@ module ts { } function getPropertiesOfType(type: Type): Symbol[] { - if (type.flags & TypeFlags.Union) { - return getPropertiesOfUnionType(type); - } - return getPropertiesOfObjectType(getApparentType(type)); + type = getApparentType(type); + return type.flags & TypeFlags.Union ? getPropertiesOfUnionType(type) : getPropertiesOfObjectType(type); } // For a type parameter, return the base constraint of the type parameter. For the string, number, // boolean, and symbol primitive types, return the corresponding object types. Otherwise return the // type itself. Note that the apparent type of a union type is the union type itself. function getApparentType(type: Type): Type { + if (type.flags & TypeFlags.Union) { + type = getReducedTypeOfUnionType(type); + } if (type.flags & TypeFlags.TypeParameter) { do { type = getConstraintOfTypeParameter(type); @@ -2986,27 +2987,27 @@ module ts { // necessary, maps primitive types and type parameters are to their apparent types, and augments with properties from // Object and Function as appropriate. function getPropertyOfType(type: Type, name: string): Symbol { + type = getApparentType(type); + if (type.flags & TypeFlags.ObjectType) { + let resolved = resolveObjectOrUnionTypeMembers(type); + if (hasProperty(resolved.members, name)) { + let symbol = resolved.members[name]; + if (symbolIsValue(symbol)) { + return symbol; + } + } + if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { + let symbol = getPropertyOfObjectType(globalFunctionType, name); + if (symbol) { + return symbol; + } + } + return getPropertyOfObjectType(globalObjectType, name); + } if (type.flags & TypeFlags.Union) { return getPropertyOfUnionType(type, name); } - if (!(type.flags & TypeFlags.ObjectType)) { - type = getApparentType(type); - if (!(type.flags & TypeFlags.ObjectType)) { - return undefined; - } - } - let resolved = resolveObjectOrUnionTypeMembers(type); - if (hasProperty(resolved.members, name)) { - let symbol = resolved.members[name]; - if (symbolIsValue(symbol)) { - return symbol; - } - } - if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { - let symbol = getPropertyOfObjectType(globalFunctionType, name); - if (symbol) return symbol; - } - return getPropertyOfObjectType(globalObjectType, name); + return undefined; } function getSignaturesOfObjectOrUnionType(type: Type, kind: SignatureKind): Signature[] { @@ -3581,6 +3582,10 @@ module ts { } } + // The noSubtypeReduction flag is there because it isn't possible to always do subtype reduction. The flag + // is true when creating a union type from a type node and when instantiating a union type. In both of those + // cases subtype reduction has to be deferred to properly support recursive union types. For example, a + // type alias of the form "type Item = string | (() => Item)" cannot be reduced during its declaration. function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type { if (types.length === 0) { return emptyObjectType; @@ -3605,10 +3610,19 @@ module ts { if (!type) { type = unionTypes[id] = createObjectType(TypeFlags.Union | getWideningFlagsOfTypes(sortedTypes)); type.types = sortedTypes; + type.reducedType = noSubtypeReduction ? undefined : type; } return type; } + function getReducedTypeOfUnionType(type: UnionType): Type { + // If union type was created without subtype reduction, perform the deferred reduction now + if (!type.reducedType) { + type.reducedType = getUnionType(type.types, /*noSubtypeReduction*/ false); + } + return type.reducedType; + } + function getTypeFromUnionTypeNode(node: UnionTypeNode): Type { let links = getNodeLinks(node); if (!links.resolvedType) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 43c210331a5..bb0909f5252 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1512,6 +1512,8 @@ module ts { export interface UnionType extends Type { types: Type[]; // Constituent types /* @internal */ + reducedType: Type; // Reduced union type (all subtypes removed) + /* @internal */ resolvedProperties: SymbolTable; // Cache of resolved properties } diff --git a/tests/baselines/reference/unionTypeReduction.js b/tests/baselines/reference/unionTypeReduction.js new file mode 100644 index 00000000000..1c914489ecc --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction.js @@ -0,0 +1,25 @@ +//// [unionTypeReduction.ts] +interface I2 { + (): number; + (q): boolean; +} + +interface I3 { + (): number; +} + +var i2: I2, i3: I3; + +var e1: I2 | I3; +var e2 = i2 || i3; // Type of e2 immediately reduced to I3 + +var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature +var r2 = e2(); + + +//// [unionTypeReduction.js] +var i2, i3; +var e1; +var e2 = i2 || i3; // Type of e2 immediately reduced to I3 +var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature +var r2 = e2(); diff --git a/tests/baselines/reference/unionTypeReduction.types b/tests/baselines/reference/unionTypeReduction.types new file mode 100644 index 00000000000..e4dffff8174 --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction.types @@ -0,0 +1,42 @@ +=== tests/cases/conformance/types/union/unionTypeReduction.ts === +interface I2 { +>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0)) + + (): number; + (q): boolean; +>q : any, Symbol(q, Decl(unionTypeReduction.ts, 2, 5)) +} + +interface I3 { +>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1)) + + (): number; +} + +var i2: I2, i3: I3; +>i2 : I2, Symbol(i2, Decl(unionTypeReduction.ts, 9, 3)) +>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0)) +>i3 : I3, Symbol(i3, Decl(unionTypeReduction.ts, 9, 11)) +>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1)) + +var e1: I2 | I3; +>e1 : I2 | I3, Symbol(e1, Decl(unionTypeReduction.ts, 11, 3)) +>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0)) +>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1)) + +var e2 = i2 || i3; // Type of e2 immediately reduced to I3 +>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3)) +>i2 || i3 : I3 +>i2 : I2, Symbol(i2, Decl(unionTypeReduction.ts, 9, 3)) +>i3 : I3, Symbol(i3, Decl(unionTypeReduction.ts, 9, 11)) + +var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature +>r1 : number, Symbol(r1, Decl(unionTypeReduction.ts, 14, 3)) +>e1() : number +>e1 : I2 | I3, Symbol(e1, Decl(unionTypeReduction.ts, 11, 3)) + +var r2 = e2(); +>r2 : number, Symbol(r2, Decl(unionTypeReduction.ts, 15, 3)) +>e2() : number +>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3)) + diff --git a/tests/cases/conformance/types/union/unionTypeReduction.ts b/tests/cases/conformance/types/union/unionTypeReduction.ts new file mode 100644 index 00000000000..8bc3d1cdc8c --- /dev/null +++ b/tests/cases/conformance/types/union/unionTypeReduction.ts @@ -0,0 +1,16 @@ +interface I2 { + (): number; + (q): boolean; +} + +interface I3 { + (): number; +} + +var i2: I2, i3: I3; + +var e1: I2 | I3; +var e2 = i2 || i3; // Type of e2 immediately reduced to I3 + +var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature +var r2 = e2(); diff --git a/tests/cases/fourslash/completionEntryForPropertyFromUnionOfModuleType.ts b/tests/cases/fourslash/completionEntryForPropertyFromUnionOfModuleType.ts index 1e072acd1fa..a9205b97f1f 100644 --- a/tests/cases/fourslash/completionEntryForPropertyFromUnionOfModuleType.ts +++ b/tests/cases/fourslash/completionEntryForPropertyFromUnionOfModuleType.ts @@ -2,9 +2,11 @@ ////module E { //// export var n = 1; +//// export var x = 0; ////} ////module F { //// export var n = 1; +//// export var y = 0; ////} ////var q: typeof E | typeof F; ////var j = q./*1*/ diff --git a/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts b/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts index 674d5f4b623..0e18e543ec3 100644 --- a/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts +++ b/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts @@ -19,8 +19,8 @@ goTo.marker("propertyReference"); verify.definitionCountIs(2); goTo.definition(0); -verify.caretAtMarker("propertyDefinition2"); +verify.caretAtMarker("propertyDefinition1"); goTo.marker("propertyReference"); goTo.definition(1); -verify.caretAtMarker("propertyDefinition1"); +verify.caretAtMarker("propertyDefinition2");