Merge pull request #1035 from Microsoft/improvedTypeInference

Improved type inference (fixes #1011)
This commit is contained in:
Anders Hejlsberg 2014-11-04 17:09:31 -08:00
commit d8080a0b46
5 changed files with 231 additions and 17 deletions

View File

@ -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 = (<UnionType>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,

View File

@ -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 {

View File

@ -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<T>(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<T>(value: [string, T]): T {
return value[1];
}
var b1: boolean;
var b1 = g(["string", true]);
function h<T>(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");

View File

@ -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<T>(x: T, y: string|T): T {
>f : <T>(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 : <T>(x: T, y: string | T) => T
var a2: number;
>a2 : number
var a2 = f(1, "hello");
>a2 : number
>f(1, "hello") : number
>f : <T>(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 : <T>(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 : <T>(x: T, y: string | T) => T
>undefined : undefined
function g<T>(value: [string, T]): T {
>g : <T>(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 : <T>(value: [string, T]) => T
>["string", true] : [string, boolean]
function h<T>(x: string|boolean|T): T {
>h : <T>(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 : <T>(x: string | boolean | T) => T
var c2: string;
>c2 : string
var c2 = h("abc");
>c2 : string
>h("abc") : string
>h : <T>(x: string | boolean | T) => T

View File

@ -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<T>(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<T>(value: [string, T]): T {
return value[1];
}
var b1: boolean;
var b1 = g(["string", true]);
function h<T>(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");