From 9d99b47b5131cb98b996be851c036ef30e1c3314 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2014 10:24:34 -0800 Subject: [PATCH 1/5] Improved type inferences involving union types --- src/compiler/checker.ts | 31 +++++++++++++++++++++---------- src/compiler/types.ts | 18 +++++++++++------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 9f612e11673..be7497c89c5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3995,8 +3995,10 @@ module ts { } function createInferenceContext(typeParameters: TypeParameter[], inferUnionTypes: boolean): InferenceContext { - var inferences: Type[][] = []; - for (var i = 0; i < typeParameters.length; i++) inferences.push([]); + var inferences: TypeInferences[] = []; + for (var i = 0; i < typeParameters.length; i++) { + inferences.push({ primary: undefined, secondary: undefined }); + } return { typeParameters: typeParameters, inferUnionTypes: inferUnionTypes, @@ -4010,6 +4012,7 @@ module ts { var sourceStack: Type[]; var targetStack: Type[]; var depth = 0; + var inferiority = 0; inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -4038,9 +4041,11 @@ module ts { var typeParameters = context.typeParameters; for (var i = 0; i < typeParameters.length; i++) { if (target === typeParameters[i]) { - context.inferenceCount++; var inferences = context.inferences[i]; - if (!contains(inferences, source)) inferences.push(source); + var candidates = inferiority ? + inferences.secondary || (inferences.secondary = []) : + inferences.primary || (inferences.primary = []); + if (!contains(candidates, source)) candidates.push(source); break; } } @@ -4055,7 +4060,6 @@ module ts { } else if (target.flags & TypeFlags.Union) { var targetTypes = (target).types; - var startCount = context.inferenceCount; var typeParameterCount = 0; var typeParameter: TypeParameter; // First infer to each type in union that isn't a type parameter @@ -4069,9 +4073,12 @@ module ts { inferFromTypes(source, t); } } - // If no inferences were produced above and union contains a single naked type parameter, infer to that type parameter - if (context.inferenceCount === startCount && typeParameterCount === 1) { + // If no inferences were produced above and union contains a single naked type parameter, + // make a secondary inference to that type parameter + if (typeParameterCount === 1) { + inferiority++; inferFromTypes(source, typeParameter); + inferiority--; } } else if (source.flags & TypeFlags.Union) { @@ -4141,10 +4148,15 @@ module ts { } } + function getInferenceCandidates(context: InferenceContext, index: number): Type[]{ + var inferences = context.inferences[index]; + return inferences.primary || inferences.secondary || emptyArray; + } + function getInferredType(context: InferenceContext, index: number): Type { var inferredType = context.inferredTypes[index]; if (!inferredType) { - var inferences = context.inferences[index]; + var inferences = getInferenceCandidates(context, index); if (inferences.length) { // Infer widened union or supertype, or the undefined type for no common supertype var unionOrSuperType = context.inferUnionTypes ? getUnionType(inferences) : getCommonSupertype(inferences); @@ -4154,7 +4166,6 @@ module ts { // Infer the empty object type when no inferences were made inferredType = emptyObjectType; } - if (inferredType !== inferenceFailureType) { var constraint = getConstraintOfTypeParameter(context.typeParameters[index]); inferredType = constraint && !isTypeAssignableTo(inferredType, constraint) ? constraint : inferredType; @@ -5387,7 +5398,7 @@ module ts { else { Debug.assert(resultOfFailedInference.failedTypeParameterIndex >= 0); var failedTypeParameter = candidateForTypeArgumentError.typeParameters[resultOfFailedInference.failedTypeParameterIndex]; - var inferenceCandidates = resultOfFailedInference.inferences[resultOfFailedInference.failedTypeParameterIndex]; + var inferenceCandidates = getInferenceCandidates(resultOfFailedInference, resultOfFailedInference.failedTypeParameterIndex); var diagnosticChainHead = chainDiagnosticMessages(/*details*/ undefined, // details will be provided by call to reportNoCommonSupertypeError Diagnostics.The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ead84957731..416223823c5 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1037,14 +1037,18 @@ module ts { (t: Type): Type; } + export interface TypeInferences { + primary: Type[]; // Inferences made directly to a type parameter + secondary: Type[]; // Inferences made to a type parameter in a union type + } + 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 - failedTypeParameterIndex?: number; // Index of type parameter for which inference failed - // It is optional because in contextual signature instantiation, nothing fails + typeParameters: TypeParameter[]; // Type parameters for which inferences are made + inferUnionTypes: boolean; // Infer union types for disjoint candidates (otherwise undefinedType) + inferences: TypeInferences[]; // Inferences made for each type parameter + inferredTypes: Type[]; // Inferred type for each type parameter + failedTypeParameterIndex?: number; // Index of type parameter for which inference failed + // It is optional because in contextual signature instantiation, nothing fails } export interface DiagnosticMessage { From 6b61422e077c531f0f8bebad9c8b6b45ab93a004 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2014 11:14:23 -0800 Subject: [PATCH 2/5] Adding tests --- .../typeInference/unionTypeInference.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts new file mode 100644 index 00000000000..a4b5edba9eb --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -0,0 +1,20 @@ +// Verify that inferences made *to* a type parameter in a union type are secondary +// to inferences made directly to that type parameter + +function f(x: T, y: string|T): T { + return x; +} +function g(value: [string, T]): T { + return value[1]; +} + +var a: number; +var a = f(1, 2); +var b: number; +var b = f(1, "hello"); +var c: number; +var c = f(1, a || "hello"); +var d: any; +var d = f(undefined, "abc"); +var e: boolean; +var e = g(["string", true]); From edb6543354fdb5efeb45d4e9e6ebc148fc121490 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 2 Nov 2014 15:21:10 -0800 Subject: [PATCH 3/5] Accepting new baselines --- .../baselines/reference/unionTypeInference.js | 42 +++++++++++ .../reference/unionTypeInference.types | 72 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 tests/baselines/reference/unionTypeInference.js create mode 100644 tests/baselines/reference/unionTypeInference.types diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js new file mode 100644 index 00000000000..ec59f2c7593 --- /dev/null +++ b/tests/baselines/reference/unionTypeInference.js @@ -0,0 +1,42 @@ +//// [unionTypeInference.ts] +// Verify that inferences made *to* a type parameter in a union type are secondary +// to inferences made directly to that type parameter + +function f(x: T, y: string|T): T { + return x; +} +function g(value: [string, T]): T { + return value[1]; +} + +var a: number; +var a = f(1, 2); +var b: number; +var b = f(1, "hello"); +var c: number; +var c = f(1, a || "hello"); +var d: any; +var d = f(undefined, "abc"); +var e: boolean; +var e = g(["string", true]); + + +//// [unionTypeInference.js] +// Verify that inferences made *to* a type parameter in a union type are secondary +// to inferences made directly to that type parameter +function f(x, y) { + return x; +} +function g(value) { + return value[1]; +} +var a; +var a = f(1, 2); +var b; +var b = f(1, "hello"); +var c; +var c = f(1, a || "hello"); +var d; +var d = f(undefined, "abc"); +var e; +var e = g(["string", true]); diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types new file mode 100644 index 00000000000..dc7fed2aced --- /dev/null +++ b/tests/baselines/reference/unionTypeInference.types @@ -0,0 +1,72 @@ +=== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts === +// Verify that inferences made *to* a type parameter in a union type are secondary +// to inferences made directly to that type parameter + +function f(x: T, y: string|T): T { +>f : (x: T, y: string | T) => T +>T : T +>x : T +>T : T +>y : string | T +>T : T +>T : T + + return x; +>x : T +} +function g(value: [string, T]): T { +>g : (value: [string, T]) => T +>T : T +>value : [string, T] +>T : T +>T : T + + return value[1]; +>value[1] : T +>value : [string, T] +} + +var a: number; +>a : number + +var a = f(1, 2); +>a : number +>f(1, 2) : number +>f : (x: T, y: string | T) => T + +var b: number; +>b : number + +var b = f(1, "hello"); +>b : number +>f(1, "hello") : number +>f : (x: T, y: string | T) => T + +var c: number; +>c : number + +var c = f(1, a || "hello"); +>c : number +>f(1, a || "hello") : number +>f : (x: T, y: string | T) => T +>a || "hello" : string | number +>a : number + +var d: any; +>d : any + +var d = f(undefined, "abc"); +>d : any +>f(undefined, "abc") : any +>f : (x: T, y: string | T) => T +>undefined : undefined + +var e: boolean; +>e : boolean + +var e = g(["string", true]); +>e : boolean +>g(["string", true]) : boolean +>g : (value: [string, T]) => T +>["string", true] : [string, boolean] + From b3ef9622a7c7a47a5b070b5e834e8625c7def653 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 3 Nov 2014 13:50:41 -0800 Subject: [PATCH 4/5] Addressing CR feedback --- src/compiler/checker.ts | 3 +- .../typeInference/unionTypeInference.ts | 31 +++++++++++++------ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index be7497c89c5..5cec8987384 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4073,8 +4073,7 @@ module ts { inferFromTypes(source, t); } } - // If no inferences were produced above and union contains a single naked type parameter, - // make a secondary inference to that type parameter + // If union contains a single naked type parameter, make a secondary inference to that type parameter if (typeParameterCount === 1) { inferiority++; inferFromTypes(source, typeParameter); diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index a4b5edba9eb..39def706622 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -4,17 +4,28 @@ function f(x: T, y: string|T): T { return x; } + +var a1: number; +var a1 = f(1, 2); +var a2: number; +var a2 = f(1, "hello"); +var a3: number; +var a3 = f(1, a1 || "hello"); +var a4: any; +var a4 = f(undefined, "abc"); + function g(value: [string, T]): T { return value[1]; } -var a: number; -var a = f(1, 2); -var b: number; -var b = f(1, "hello"); -var c: number; -var c = f(1, a || "hello"); -var d: any; -var d = f(undefined, "abc"); -var e: boolean; -var e = g(["string", true]); +var b1: boolean; +var b1 = g(["string", true]); + +function h(x: string|boolean|T): T { + return typeof x === "string" || typeof x === "boolean" ? undefined : x; +} + +var c1: number; +var c1 = h(5); +var c2: string; +var c2 = h("abc"); From 4c4484be05221ea2ede81327c0c238834d4bcc94 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 3 Nov 2014 14:29:23 -0800 Subject: [PATCH 5/5] Accepting new baselines --- .../baselines/reference/unionTypeInference.js | 58 ++++++--- .../reference/unionTypeInference.types | 115 ++++++++++++------ 2 files changed, 114 insertions(+), 59 deletions(-) diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index ec59f2c7593..bdaa340f6ea 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -5,20 +5,31 @@ function f(x: T, y: string|T): T { return x; } + +var a1: number; +var a1 = f(1, 2); +var a2: number; +var a2 = f(1, "hello"); +var a3: number; +var a3 = f(1, a1 || "hello"); +var a4: any; +var a4 = f(undefined, "abc"); + function g(value: [string, T]): T { return value[1]; } -var a: number; -var a = f(1, 2); -var b: number; -var b = f(1, "hello"); -var c: number; -var c = f(1, a || "hello"); -var d: any; -var d = f(undefined, "abc"); -var e: boolean; -var e = g(["string", true]); +var b1: boolean; +var b1 = g(["string", true]); + +function h(x: string|boolean|T): T { + return typeof x === "string" || typeof x === "boolean" ? undefined : x; +} + +var c1: number; +var c1 = h(5); +var c2: string; +var c2 = h("abc"); //// [unionTypeInference.js] @@ -27,16 +38,23 @@ var e = g(["string", true]); function f(x, y) { return x; } +var a1; +var a1 = f(1, 2); +var a2; +var a2 = f(1, "hello"); +var a3; +var a3 = f(1, a1 || "hello"); +var a4; +var a4 = f(undefined, "abc"); function g(value) { return value[1]; } -var a; -var a = f(1, 2); -var b; -var b = f(1, "hello"); -var c; -var c = f(1, a || "hello"); -var d; -var d = f(undefined, "abc"); -var e; -var e = g(["string", true]); +var b1; +var b1 = g(["string", true]); +function h(x) { + return typeof x === "string" || typeof x === "boolean" ? undefined : x; +} +var c1; +var c1 = h(5); +var c2; +var c2 = h("abc"); diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index dc7fed2aced..d685fc4ec6b 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -14,6 +14,42 @@ function f(x: T, y: string|T): T { return x; >x : T } + +var a1: number; +>a1 : number + +var a1 = f(1, 2); +>a1 : number +>f(1, 2) : number +>f : (x: T, y: string | T) => T + +var a2: number; +>a2 : number + +var a2 = f(1, "hello"); +>a2 : number +>f(1, "hello") : number +>f : (x: T, y: string | T) => T + +var a3: number; +>a3 : number + +var a3 = f(1, a1 || "hello"); +>a3 : number +>f(1, a1 || "hello") : number +>f : (x: T, y: string | T) => T +>a1 || "hello" : string | number +>a1 : number + +var a4: any; +>a4 : any + +var a4 = f(undefined, "abc"); +>a4 : any +>f(undefined, "abc") : any +>f : (x: T, y: string | T) => T +>undefined : undefined + function g(value: [string, T]): T { >g : (value: [string, T]) => T >T : T @@ -26,47 +62,48 @@ function g(value: [string, T]): T { >value : [string, T] } -var a: number; ->a : number +var b1: boolean; +>b1 : boolean -var a = f(1, 2); ->a : number ->f(1, 2) : number ->f : (x: T, y: string | T) => T - -var b: number; ->b : number - -var b = f(1, "hello"); ->b : number ->f(1, "hello") : number ->f : (x: T, y: string | T) => T - -var c: number; ->c : number - -var c = f(1, a || "hello"); ->c : number ->f(1, a || "hello") : number ->f : (x: T, y: string | T) => T ->a || "hello" : string | number ->a : number - -var d: any; ->d : any - -var d = f(undefined, "abc"); ->d : any ->f(undefined, "abc") : any ->f : (x: T, y: string | T) => T ->undefined : undefined - -var e: boolean; ->e : boolean - -var e = g(["string", true]); ->e : boolean +var b1 = g(["string", true]); +>b1 : boolean >g(["string", true]) : boolean >g : (value: [string, T]) => T >["string", true] : [string, boolean] +function h(x: string|boolean|T): T { +>h : (x: string | boolean | T) => T +>T : T +>x : string | boolean | T +>T : T +>T : T + + return typeof x === "string" || typeof x === "boolean" ? undefined : x; +>typeof x === "string" || typeof x === "boolean" ? undefined : x : T +>typeof x === "string" || typeof x === "boolean" : boolean +>typeof x === "string" : boolean +>typeof x : string +>x : string | boolean | T +>typeof x === "boolean" : boolean +>typeof x : string +>x : boolean | T +>undefined : undefined +>x : T +} + +var c1: number; +>c1 : number + +var c1 = h(5); +>c1 : number +>h(5) : number +>h : (x: string | boolean | T) => T + +var c2: string; +>c2 : string + +var c2 = h("abc"); +>c2 : string +>h("abc") : string +>h : (x: string | boolean | T) => T +