From 87b562c5dc81ee44c68b8852562e33d2c6ce6cf0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Oct 2014 10:30:58 -0700 Subject: [PATCH 1/3] Union types in contextual signature instantiations --- src/compiler/checker.ts | 14 +++++++------- src/compiler/types.ts | 1 + 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0341d76190c..4437ecef933 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3746,11 +3746,12 @@ module ts { } } - function createInferenceContext(typeParameters: TypeParameter[]): InferenceContext { + function createInferenceContext(typeParameters: TypeParameter[], inferUnionTypes: boolean): InferenceContext { var inferences: Type[][] = []; for (var i = 0; i < typeParameters.length; i++) inferences.push([]); return { typeParameters: typeParameters, + inferUnionTypes: inferUnionTypes, inferenceCount: 0, inferences: inferences, inferredTypes: new Array(typeParameters.length), @@ -3897,10 +3898,9 @@ module ts { if (!result) { var inferences = context.inferences[index]; if (inferences.length) { - // Find type that is supertype of all others - var supertype = getCommonSupertype(inferences); - // Infer widened supertype, or the undefined type for no common supertype - var inferredType = supertype ? getWidenedType(supertype) : undefinedType; + // Infer widened union or supertype, or the undefined type for no common supertype + var unionOrSuperType = context.inferUnionTypes ? getUnionType(inferences) : getCommonSupertype(inferences); + var inferredType = unionOrSuperType ? getWidenedType(unionOrSuperType) : undefinedType; } else { // Infer the empty object type when no inferences were made @@ -4941,7 +4941,7 @@ module ts { // Instantiate a generic signature in the context of a non-generic signature (section 3.8.5 in TypeScript spec) function instantiateSignatureInContextOf(signature: Signature, contextualSignature: Signature, contextualMapper: TypeMapper): Signature { - var context = createInferenceContext(signature.typeParameters); + var context = createInferenceContext(signature.typeParameters, /*inferUnionTypes*/ true); forEachMatchingParameterType(contextualSignature, signature, (source, target) => { // Type parameters from outer context referenced by source type are fixed by instantiation of the source type inferTypes(context, instantiateType(source, contextualMapper), target); @@ -4951,7 +4951,7 @@ module ts { function inferTypeArguments(signature: Signature, args: Expression[], excludeArgument?: boolean[]): Type[] { var typeParameters = signature.typeParameters; - var context = createInferenceContext(typeParameters); + var context = createInferenceContext(typeParameters, /*inferUnionTypes*/ false); var mapper = createInferenceMapper(context); // First infer from arguments that are not context sensitive for (var i = 0; i < args.length; i++) { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 7bd855eafcb..945b09ed7f8 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1006,6 +1006,7 @@ module ts { export interface InferenceContext { typeParameters: TypeParameter[]; // Type parameters for which inferences are made + inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType) inferenceCount: number; // Incremented for every inference made (whether new or not) inferences: Type[][]; // Inferences made for each type parameter inferredTypes: Type[]; // Inferred type for each type parameter From c00ee42b1182cf89da8c4513cbc74a9cb92f34c2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 16 Oct 2014 20:52:36 -0700 Subject: [PATCH 2/3] Adding tests --- .../contextualSignatureInstantiation.js | 39 +++++++++ .../contextualSignatureInstantiation.types | 86 +++++++++++++++++++ .../contextualSignatureInstantiation.ts | 22 +++++ 3 files changed, 147 insertions(+) create mode 100644 tests/baselines/reference/contextualSignatureInstantiation.js create mode 100644 tests/baselines/reference/contextualSignatureInstantiation.types create mode 100644 tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts diff --git a/tests/baselines/reference/contextualSignatureInstantiation.js b/tests/baselines/reference/contextualSignatureInstantiation.js new file mode 100644 index 00000000000..fd3bd78dd01 --- /dev/null +++ b/tests/baselines/reference/contextualSignatureInstantiation.js @@ -0,0 +1,39 @@ +//// [contextualSignatureInstantiation.ts] +// TypeScript Spec, section 4.12.2: +// If e is an expression of a function type that contains exactly one generic call signature and no other members, +// and T is a function type with exactly one non - generic call signature and no other members, then any inferences +// made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed +// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). + +declare function foo(cb: (x: number, y: string) => T): T; +declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; + +declare function f(x: number, y: string): boolean; +declare function g(x: T, y: T): T; + +var a: boolean; +var a = foo(f); // Should be boolean + +var b: number | string; +var b = foo(g); // Should be number | string +var b = bar(1, "one", g); // Should be number | string +var b = bar("one", 1, g); // Should be number | string + +var c: number; +var c = bar(1, 1, g); // Should be number + + +//// [contextualSignatureInstantiation.js] +// TypeScript Spec, section 4.12.2: +// If e is an expression of a function type that contains exactly one generic call signature and no other members, +// and T is a function type with exactly one non - generic call signature and no other members, then any inferences +// made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed +// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). +var a; +var a = foo(f); // Should be boolean +var b; +var b = foo(g); // Should be number | string +var b = bar(1, "one", g); // Should be number | string +var b = bar("one", 1, g); // Should be number | string +var c; +var c = bar(1, 1, g); // Should be number diff --git a/tests/baselines/reference/contextualSignatureInstantiation.types b/tests/baselines/reference/contextualSignatureInstantiation.types new file mode 100644 index 00000000000..225761024b6 --- /dev/null +++ b/tests/baselines/reference/contextualSignatureInstantiation.types @@ -0,0 +1,86 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts === +// TypeScript Spec, section 4.12.2: +// If e is an expression of a function type that contains exactly one generic call signature and no other members, +// and T is a function type with exactly one non - generic call signature and no other members, then any inferences +// made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed +// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). + +declare function foo(cb: (x: number, y: string) => T): T; +>foo : (cb: (x: number, y: string) => T) => T +>T : T +>cb : (x: number, y: string) => T +>x : number +>y : string +>T : T +>T : T + +declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>T : T +>U : U +>V : V +>x : T +>T : T +>y : U +>U : U +>cb : (x: T, y: U) => V +>x : T +>T : T +>y : U +>U : U +>V : V +>V : V + +declare function f(x: number, y: string): boolean; +>f : (x: number, y: string) => boolean +>x : number +>y : string + +declare function g(x: T, y: T): T; +>g : (x: T, y: T) => T +>T : T +>x : T +>T : T +>y : T +>T : T +>T : T + +var a: boolean; +>a : boolean + +var a = foo(f); // Should be boolean +>a : boolean +>foo(f) : boolean +>foo : (cb: (x: number, y: string) => T) => T +>f : (x: number, y: string) => boolean + +var b: number | string; +>b : string | number + +var b = foo(g); // Should be number | string +>b : string | number +>foo(g) : string | number +>foo : (cb: (x: number, y: string) => T) => T +>g : (x: T, y: T) => T + +var b = bar(1, "one", g); // Should be number | string +>b : string | number +>bar(1, "one", g) : string | number +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>g : (x: T, y: T) => T + +var b = bar("one", 1, g); // Should be number | string +>b : string | number +>bar("one", 1, g) : string | number +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>g : (x: T, y: T) => T + +var c: number; +>c : number + +var c = bar(1, 1, g); // Should be number +>c : number +>bar(1, 1, g) : number +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>g : (x: T, y: T) => T + diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts b/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts new file mode 100644 index 00000000000..b5408da606d --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts @@ -0,0 +1,22 @@ +// TypeScript Spec, section 4.12.2: +// If e is an expression of a function type that contains exactly one generic call signature and no other members, +// and T is a function type with exactly one non - generic call signature and no other members, then any inferences +// made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed +// to a function type with e's call signature instantiated in the context of T’s call signature (section 3.8.5). + +declare function foo(cb: (x: number, y: string) => T): T; +declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; + +declare function f(x: number, y: string): boolean; +declare function g(x: T, y: T): T; + +var a: boolean; +var a = foo(f); // Should be boolean + +var b: number | string; +var b = foo(g); // Should be number | string +var b = bar(1, "one", g); // Should be number | string +var b = bar("one", 1, g); // Should be number | string + +var c: number; +var c = bar(1, 1, g); // Should be number From e5872b48241628df2107cfcf4eae723bd8bfcb9e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 18 Oct 2014 08:37:33 -0700 Subject: [PATCH 3/3] Addressing CR feedback --- .../contextualSignatureInstantiation.js | 31 ++++--- .../contextualSignatureInstantiation.types | 90 +++++++++++++++---- .../contextualSignatureInstantiation.ts | 18 ++-- 3 files changed, 106 insertions(+), 33 deletions(-) diff --git a/tests/baselines/reference/contextualSignatureInstantiation.js b/tests/baselines/reference/contextualSignatureInstantiation.js index fd3bd78dd01..11e19b3262e 100644 --- a/tests/baselines/reference/contextualSignatureInstantiation.js +++ b/tests/baselines/reference/contextualSignatureInstantiation.js @@ -3,24 +3,30 @@ // If e is an expression of a function type that contains exactly one generic call signature and no other members, // and T is a function type with exactly one non - generic call signature and no other members, then any inferences // made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed -// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). +// to a function type with e's call signature instantiated in the context of T's call signature (section 3.8.5). declare function foo(cb: (x: number, y: string) => T): T; declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; +declare function baz(x: T, y: T, cb: (x: T, y: T) => U): U; -declare function f(x: number, y: string): boolean; declare function g(x: T, y: T): T; +declare function h(x: T, y: U): T[] | U[]; -var a: boolean; -var a = foo(f); // Should be boolean +var a: number; +var a = bar(1, 1, g); // Should be number +var a = baz(1, 1, g); // Should be number var b: number | string; var b = foo(g); // Should be number | string var b = bar(1, "one", g); // Should be number | string var b = bar("one", 1, g); // Should be number | string +var b = baz(b, b, g); // Should be number | string -var c: number; -var c = bar(1, 1, g); // Should be number +var d: number[] | string[]; +var d = foo(h); // Should be number[] | string[] +var d = bar(1, "one", h); // Should be number[] | string[] +var d = bar("one", 1, h); // Should be number[] | string[] +var d = baz(d, d, g); // Should be number[] | string[] //// [contextualSignatureInstantiation.js] @@ -28,12 +34,17 @@ var c = bar(1, 1, g); // Should be number // If e is an expression of a function type that contains exactly one generic call signature and no other members, // and T is a function type with exactly one non - generic call signature and no other members, then any inferences // made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed -// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). +// to a function type with e's call signature instantiated in the context of T's call signature (section 3.8.5). var a; -var a = foo(f); // Should be boolean +var a = bar(1, 1, g); // Should be number +var a = baz(1, 1, g); // Should be number var b; var b = foo(g); // Should be number | string var b = bar(1, "one", g); // Should be number | string var b = bar("one", 1, g); // Should be number | string -var c; -var c = bar(1, 1, g); // Should be number +var b = baz(b, b, g); // Should be number | string +var d; +var d = foo(h); // Should be number[] | string[] +var d = bar(1, "one", h); // Should be number[] | string[] +var d = bar("one", 1, h); // Should be number[] | string[] +var d = baz(d, d, g); // Should be number[] | string[] diff --git a/tests/baselines/reference/contextualSignatureInstantiation.types b/tests/baselines/reference/contextualSignatureInstantiation.types index 225761024b6..4363272622a 100644 --- a/tests/baselines/reference/contextualSignatureInstantiation.types +++ b/tests/baselines/reference/contextualSignatureInstantiation.types @@ -3,7 +3,7 @@ // If e is an expression of a function type that contains exactly one generic call signature and no other members, // and T is a function type with exactly one non - generic call signature and no other members, then any inferences // made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed -// to a function type with e's call signature instantiated in the context of T�s call signature (section 3.8.5). +// to a function type with e's call signature instantiated in the context of T's call signature (section 3.8.5). declare function foo(cb: (x: number, y: string) => T): T; >foo : (cb: (x: number, y: string) => T) => T @@ -31,10 +31,21 @@ declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; >V : V >V : V -declare function f(x: number, y: string): boolean; ->f : (x: number, y: string) => boolean ->x : number ->y : string +declare function baz(x: T, y: T, cb: (x: T, y: T) => U): U; +>baz : (x: T, y: T, cb: (x: T, y: T) => U) => U +>T : T +>U : U +>x : T +>T : T +>y : T +>T : T +>cb : (x: T, y: T) => U +>x : T +>T : T +>y : T +>T : T +>U : U +>U : U declare function g(x: T, y: T): T; >g : (x: T, y: T) => T @@ -45,14 +56,31 @@ declare function g(x: T, y: T): T; >T : T >T : T -var a: boolean; ->a : boolean +declare function h(x: T, y: U): T[] | U[]; +>h : (x: T, y: U) => T[] | U[] +>T : T +>U : U +>x : T +>T : T +>y : U +>U : U +>T : T +>U : U -var a = foo(f); // Should be boolean ->a : boolean ->foo(f) : boolean ->foo : (cb: (x: number, y: string) => T) => T ->f : (x: number, y: string) => boolean +var a: number; +>a : number + +var a = bar(1, 1, g); // Should be number +>a : number +>bar(1, 1, g) : number +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>g : (x: T, y: T) => T + +var a = baz(1, 1, g); // Should be number +>a : number +>baz(1, 1, g) : number +>baz : (x: T, y: T, cb: (x: T, y: T) => U) => U +>g : (x: T, y: T) => T var b: number | string; >b : string | number @@ -75,12 +103,40 @@ var b = bar("one", 1, g); // Should be number | string >bar : (x: T, y: U, cb: (x: T, y: U) => V) => V >g : (x: T, y: T) => T -var c: number; ->c : number +var b = baz(b, b, g); // Should be number | string +>b : string | number +>baz(b, b, g) : string | number +>baz : (x: T, y: T, cb: (x: T, y: T) => U) => U +>b : string | number +>b : string | number +>g : (x: T, y: T) => T -var c = bar(1, 1, g); // Should be number ->c : number ->bar(1, 1, g) : number +var d: number[] | string[]; +>d : string[] | number[] + +var d = foo(h); // Should be number[] | string[] +>d : string[] | number[] +>foo(h) : string[] | number[] +>foo : (cb: (x: number, y: string) => T) => T +>h : (x: T, y: U) => T[] | U[] + +var d = bar(1, "one", h); // Should be number[] | string[] +>d : string[] | number[] +>bar(1, "one", h) : string[] | number[] >bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>h : (x: T, y: U) => T[] | U[] + +var d = bar("one", 1, h); // Should be number[] | string[] +>d : string[] | number[] +>bar("one", 1, h) : string[] | number[] +>bar : (x: T, y: U, cb: (x: T, y: U) => V) => V +>h : (x: T, y: U) => T[] | U[] + +var d = baz(d, d, g); // Should be number[] | string[] +>d : string[] | number[] +>baz(d, d, g) : string[] | number[] +>baz : (x: T, y: T, cb: (x: T, y: T) => U) => U +>d : string[] | number[] +>d : string[] | number[] >g : (x: T, y: T) => T diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts b/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts index b5408da606d..393d0e10d37 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/contextualSignatureInstantiation.ts @@ -2,21 +2,27 @@ // If e is an expression of a function type that contains exactly one generic call signature and no other members, // and T is a function type with exactly one non - generic call signature and no other members, then any inferences // made for type parameters referenced by the parameters of T's call signature are fixed, and e's type is changed -// to a function type with e's call signature instantiated in the context of T’s call signature (section 3.8.5). +// to a function type with e's call signature instantiated in the context of T's call signature (section 3.8.5). declare function foo(cb: (x: number, y: string) => T): T; declare function bar(x: T, y: U, cb: (x: T, y: U) => V): V; +declare function baz(x: T, y: T, cb: (x: T, y: T) => U): U; -declare function f(x: number, y: string): boolean; declare function g(x: T, y: T): T; +declare function h(x: T, y: U): T[] | U[]; -var a: boolean; -var a = foo(f); // Should be boolean +var a: number; +var a = bar(1, 1, g); // Should be number +var a = baz(1, 1, g); // Should be number var b: number | string; var b = foo(g); // Should be number | string var b = bar(1, "one", g); // Should be number | string var b = bar("one", 1, g); // Should be number | string +var b = baz(b, b, g); // Should be number | string -var c: number; -var c = bar(1, 1, g); // Should be number +var d: number[] | string[]; +var d = foo(h); // Should be number[] | string[] +var d = bar(1, "one", h); // Should be number[] | string[] +var d = bar("one", 1, h); // Should be number[] | string[] +var d = baz(d, d, g); // Should be number[] | string[]