From e8ee5005390b9434f4608c57a9a25f7d338ec848 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 12 Apr 2015 10:35:57 -0700 Subject: [PATCH 1/6] Consistently reduce union types in property access --- src/compiler/checker.ts | 51 +++++++++++-------- src/compiler/types.ts | 1 + ...onEntryForPropertyFromUnionOfModuleType.ts | 2 + .../goToDefinitionUnionTypeProperty2.ts | 4 +- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3c0bf39f1fa..1465f1ab51b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2846,16 +2846,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); @@ -2928,27 +2929,25 @@ 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[] { @@ -3526,10 +3525,18 @@ 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 (!type.reducedType) { + type.reducedType = getUnionType(type.types); + } + 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 d24ee45e21e..e5dc3dc3306 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1458,6 +1458,7 @@ module ts { export interface UnionType extends Type { types: Type[]; // Constituent types + reducedType: Type; // Reduced union type (all subtypes removed) resolvedProperties: SymbolTable; // Cache of resolved properties } 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"); From b7408fa0b4357171c874dcb91019a61cb9c4b625 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 12 Apr 2015 10:37:14 -0700 Subject: [PATCH 2/6] Accepting new baselines --- tests/baselines/reference/APISample_compile.js | 1 + tests/baselines/reference/APISample_compile.types | 4 ++++ tests/baselines/reference/APISample_linter.js | 1 + tests/baselines/reference/APISample_linter.types | 4 ++++ tests/baselines/reference/APISample_transform.js | 1 + tests/baselines/reference/APISample_transform.types | 4 ++++ tests/baselines/reference/APISample_watcher.js | 1 + tests/baselines/reference/APISample_watcher.types | 4 ++++ 8 files changed, 20 insertions(+) diff --git a/tests/baselines/reference/APISample_compile.js b/tests/baselines/reference/APISample_compile.js index 3f0ba5f18cd..c5455d26bac 100644 --- a/tests/baselines/reference/APISample_compile.js +++ b/tests/baselines/reference/APISample_compile.js @@ -1149,6 +1149,7 @@ declare module "typescript" { } interface UnionType extends Type { types: Type[]; + reducedType: Type; resolvedProperties: SymbolTable; } interface ResolvedType extends ObjectType, UnionType { diff --git a/tests/baselines/reference/APISample_compile.types b/tests/baselines/reference/APISample_compile.types index eff47a4cb7b..1df5b188d3a 100644 --- a/tests/baselines/reference/APISample_compile.types +++ b/tests/baselines/reference/APISample_compile.types @@ -3701,6 +3701,10 @@ declare module "typescript" { types: Type[]; >types : Type[] +>Type : Type + + reducedType: Type; +>reducedType : Type >Type : Type resolvedProperties: SymbolTable; diff --git a/tests/baselines/reference/APISample_linter.js b/tests/baselines/reference/APISample_linter.js index ab48f78affc..d9757de48a1 100644 --- a/tests/baselines/reference/APISample_linter.js +++ b/tests/baselines/reference/APISample_linter.js @@ -1180,6 +1180,7 @@ declare module "typescript" { } interface UnionType extends Type { types: Type[]; + reducedType: Type; resolvedProperties: SymbolTable; } interface ResolvedType extends ObjectType, UnionType { diff --git a/tests/baselines/reference/APISample_linter.types b/tests/baselines/reference/APISample_linter.types index 600bf5c6adc..99d1405a244 100644 --- a/tests/baselines/reference/APISample_linter.types +++ b/tests/baselines/reference/APISample_linter.types @@ -3847,6 +3847,10 @@ declare module "typescript" { types: Type[]; >types : Type[] +>Type : Type + + reducedType: Type; +>reducedType : Type >Type : Type resolvedProperties: SymbolTable; diff --git a/tests/baselines/reference/APISample_transform.js b/tests/baselines/reference/APISample_transform.js index d40c078a150..019ff29148b 100644 --- a/tests/baselines/reference/APISample_transform.js +++ b/tests/baselines/reference/APISample_transform.js @@ -1181,6 +1181,7 @@ declare module "typescript" { } interface UnionType extends Type { types: Type[]; + reducedType: Type; resolvedProperties: SymbolTable; } interface ResolvedType extends ObjectType, UnionType { diff --git a/tests/baselines/reference/APISample_transform.types b/tests/baselines/reference/APISample_transform.types index 4ea9c49d758..f977fbcc2be 100644 --- a/tests/baselines/reference/APISample_transform.types +++ b/tests/baselines/reference/APISample_transform.types @@ -3797,6 +3797,10 @@ declare module "typescript" { types: Type[]; >types : Type[] +>Type : Type + + reducedType: Type; +>reducedType : Type >Type : Type resolvedProperties: SymbolTable; diff --git a/tests/baselines/reference/APISample_watcher.js b/tests/baselines/reference/APISample_watcher.js index c53034c11d7..832ddc25b1f 100644 --- a/tests/baselines/reference/APISample_watcher.js +++ b/tests/baselines/reference/APISample_watcher.js @@ -1218,6 +1218,7 @@ declare module "typescript" { } interface UnionType extends Type { types: Type[]; + reducedType: Type; resolvedProperties: SymbolTable; } interface ResolvedType extends ObjectType, UnionType { diff --git a/tests/baselines/reference/APISample_watcher.types b/tests/baselines/reference/APISample_watcher.types index 3903e169b4e..8ecc20e860c 100644 --- a/tests/baselines/reference/APISample_watcher.types +++ b/tests/baselines/reference/APISample_watcher.types @@ -3970,6 +3970,10 @@ declare module "typescript" { types: Type[]; >types : Type[] +>Type : Type + + reducedType: Type; +>reducedType : Type >Type : Type resolvedProperties: SymbolTable; From c91e2855caae4e7c4cb5ab77faaa70e1a9be7df3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 12 Apr 2015 10:52:20 -0700 Subject: [PATCH 3/6] Adding test --- .../baselines/reference/unionTypeReduction.js | 25 +++++++++++ .../reference/unionTypeReduction.types | 42 +++++++++++++++++++ .../types/union/unionTypeReduction.ts | 16 +++++++ 3 files changed, 83 insertions(+) create mode 100644 tests/baselines/reference/unionTypeReduction.js create mode 100644 tests/baselines/reference/unionTypeReduction.types create mode 100644 tests/cases/conformance/types/union/unionTypeReduction.ts 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..073d690162a --- /dev/null +++ b/tests/baselines/reference/unionTypeReduction.types @@ -0,0 +1,42 @@ +=== tests/cases/conformance/types/union/unionTypeReduction.ts === +interface I2 { +>I2 : I2 + + (): number; + (q): boolean; +>q : any +} + +interface I3 { +>I3 : I3 + + (): number; +} + +var i2: I2, i3: I3; +>i2 : I2 +>I2 : I2 +>i3 : I3 +>I3 : I3 + +var e1: I2 | I3; +>e1 : I2 | I3 +>I2 : I2 +>I3 : I3 + +var e2 = i2 || i3; // Type of e2 immediately reduced to I3 +>e2 : I3 +>i2 || i3 : I3 +>i2 : I2 +>i3 : I3 + +var r1 = e1(); // Type of e1 reduced to I3 upon accessing property or signature +>r1 : number +>e1() : number +>e1 : I2 | I3 + +var r2 = e2(); +>r2 : number +>e2() : number +>e2 : I3 + 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(); From 56e0fb0b35e684d0c2db84669269c8fd4d6d464f Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Apr 2015 10:01:11 -0700 Subject: [PATCH 4/6] Addressing CR feedback --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f9815a22b8c..e1f0a53ca85 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2998,7 +2998,9 @@ module ts { } if (resolved === anyFunctionType || resolved.callSignatures.length || resolved.constructSignatures.length) { let symbol = getPropertyOfObjectType(globalFunctionType, name); - if (symbol) return symbol; + if (symbol) { + return symbol; + } } return getPropertyOfObjectType(globalObjectType, name); } @@ -3580,6 +3582,9 @@ 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. function getUnionType(types: Type[], noSubtypeReduction?: boolean): Type { if (types.length === 0) { return emptyObjectType; From f33acf8ba47d27ab125e0005ef454c9468fc2081 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Apr 2015 14:18:57 -0700 Subject: [PATCH 5/6] Accepting new baselines --- .../reference/unionTypeReduction.types | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/baselines/reference/unionTypeReduction.types b/tests/baselines/reference/unionTypeReduction.types index 073d690162a..e4dffff8174 100644 --- a/tests/baselines/reference/unionTypeReduction.types +++ b/tests/baselines/reference/unionTypeReduction.types @@ -1,42 +1,42 @@ === tests/cases/conformance/types/union/unionTypeReduction.ts === interface I2 { ->I2 : I2 +>I2 : I2, Symbol(I2, Decl(unionTypeReduction.ts, 0, 0)) (): number; (q): boolean; ->q : any +>q : any, Symbol(q, Decl(unionTypeReduction.ts, 2, 5)) } interface I3 { ->I3 : I3 +>I3 : I3, Symbol(I3, Decl(unionTypeReduction.ts, 3, 1)) (): number; } var i2: I2, i3: I3; ->i2 : I2 ->I2 : I2 ->i3 : I3 ->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 ->I2 : I2 ->I3 : 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 +>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3)) >i2 || i3 : I3 ->i2 : 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 +>r1 : number, Symbol(r1, Decl(unionTypeReduction.ts, 14, 3)) >e1() : number ->e1 : I2 | I3 +>e1 : I2 | I3, Symbol(e1, Decl(unionTypeReduction.ts, 11, 3)) var r2 = e2(); ->r2 : number +>r2 : number, Symbol(r2, Decl(unionTypeReduction.ts, 15, 3)) >e2() : number ->e2 : I3 +>e2 : I3, Symbol(e2, Decl(unionTypeReduction.ts, 12, 3)) From 9a2846ef72e7d9d14945609a3009315e1f9cee20 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 14 Apr 2015 14:51:08 -0700 Subject: [PATCH 6/6] Addressing CR feedback --- src/compiler/checker.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3f4a5b83ac4..e9430a058f8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3584,7 +3584,8 @@ 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. + // 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; @@ -3615,8 +3616,9 @@ module ts { } 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); + type.reducedType = getUnionType(type.types, /*noSubtypeReduction*/ false); } return type.reducedType; }