diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf00022c6f0..ae6bacb4c84 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4007,8 +4007,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, @@ -4022,6 +4024,7 @@ module ts { var sourceStack: Type[]; var targetStack: Type[]; var depth = 0; + var inferiority = 0; inferFromTypes(source, target); function isInProcess(source: Type, target: Type) { @@ -4050,9 +4053,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; } } @@ -4067,7 +4072,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 @@ -4081,9 +4085,11 @@ 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 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) { @@ -4153,10 +4159,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); @@ -4166,7 +4177,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; @@ -5404,7 +5414,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 90b0697697e..421f142c251 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1041,14 +1041,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 { diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js new file mode 100644 index 00000000000..bdaa340f6ea --- /dev/null +++ b/tests/baselines/reference/unionTypeInference.js @@ -0,0 +1,60 @@ +//// [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; +} + +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 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] +// 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; +} +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 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 new file mode 100644 index 00000000000..d685fc4ec6b --- /dev/null +++ b/tests/baselines/reference/unionTypeInference.types @@ -0,0 +1,109 @@ +=== 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 +} + +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 +>value : [string, T] +>T : T +>T : T + + return value[1]; +>value[1] : T +>value : [string, T] +} + +var b1: boolean; +>b1 : 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 + 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..39def706622 --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -0,0 +1,31 @@ +// 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; +} + +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 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");