Skip to content

Commit

Permalink
Merge pull request #21157 from Microsoft/fixEmptyArrayInference
Browse files Browse the repository at this point in the history
Fix empty array inference
  • Loading branch information
ahejlsberg authored Jan 16, 2018
2 parents 154c614 + b6b936f commit 64b3086
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 39 deletions.
50 changes: 32 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10917,6 +10917,7 @@ namespace ts {
return {
typeParameter,
candidates: undefined,
contraCandidates: undefined,
inferredType: undefined,
priority: undefined,
topLevel: true,
Expand All @@ -10928,6 +10929,7 @@ namespace ts {
return {
typeParameter: inference.typeParameter,
candidates: inference.candidates && inference.candidates.slice(),
contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(),
inferredType: inference.inferredType,
priority: inference.priority,
topLevel: inference.topLevel,
Expand Down Expand Up @@ -11041,6 +11043,7 @@ namespace ts {
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
let symbolStack: Symbol[];
let visited: Map<boolean>;
let contravariant = false;
inferFromTypes(originalSource, originalTarget);

function inferFromTypes(source: Type, target: Type) {
Expand Down Expand Up @@ -11108,18 +11111,20 @@ namespace ts {
const inference = getInferenceInfoForType(target);
if (inference) {
if (!inference.isFixed) {
// We give lowest priority to inferences of implicitNeverType (which is used as the
// element type for empty array literals). Thus, inferences from empty array literals
// only matter when no other inferences are made.
const p = priority | (source === implicitNeverType ? InferencePriority.NeverType : 0);
if (!inference.candidates || p < inference.priority) {
inference.candidates = [source];
inference.priority = p;
if (inference.priority === undefined || priority < inference.priority) {
inference.candidates = undefined;
inference.contraCandidates = undefined;
inference.priority = priority;
}
else if (p === inference.priority) {
inference.candidates.push(source);
if (priority === inference.priority) {
if (contravariant) {
inference.contraCandidates = append(inference.contraCandidates, source);
}
else {
inference.candidates = append(inference.candidates, source);
}
}
if (!(p & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
inference.topLevel = false;
}
}
Expand All @@ -11142,15 +11147,15 @@ namespace ts {
}
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
}
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
const empty = createEmptyObjectTypeFromStringLiteral(source);
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
inferFromTypes(empty, (target as IndexType).type);
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
}
else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
inferFromTypes((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType);
Expand Down Expand Up @@ -11219,9 +11224,9 @@ namespace ts {

function inferFromContravariantTypes(source: Type, target: Type) {
if (strictFunctionTypes) {
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
inferFromTypes(source, target);
priority ^= InferencePriority.Contravariant;
contravariant = !contravariant;
}
else {
inferFromTypes(source, target);
Expand Down Expand Up @@ -11400,10 +11405,19 @@ namespace ts {
// If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if
// union types were requested or if all inferences were made from the return type position, infer a
// union type. Otherwise, infer a common supertype.
const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) :
context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, UnionReduction.Subtype) :
const unwidenedType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ?
getUnionType(baseCandidates, UnionReduction.Subtype) :
getCommonSupertype(baseCandidates);
inferredType = getWidenedType(unwidenedType);
// If we have inferred 'never' but have contravariant candidates. To get a more specific type we
// infer from the contravariant candidates instead.
if (inferredType.flags & TypeFlags.Never && inference.contraCandidates) {
inferredType = getCommonSubtype(inference.contraCandidates);
}
}
else if (inference.contraCandidates) {
// We only have contravariant inferences, infer the best common subtype of those
inferredType = getCommonSubtype(inference.contraCandidates);
}
else if (context.flags & InferenceFlags.NoDefault) {
// We use silentNeverType as the wildcard that signals no inferences.
Expand Down
21 changes: 10 additions & 11 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3826,20 +3826,19 @@ namespace ts {
export type TypeMapper = (t: TypeParameter) => Type;

export const enum InferencePriority {
Contravariant = 1 << 0, // Inference from contravariant position
NakedTypeVariable = 1 << 1, // Naked type variable in union or intersection type
MappedType = 1 << 2, // Reverse inference for mapped type
ReturnType = 1 << 3, // Inference made from return type of generic function
NeverType = 1 << 4, // Inference made from the never type
NakedTypeVariable = 1 << 0, // Naked type variable in union or intersection type
MappedType = 1 << 1, // Reverse inference for mapped type
ReturnType = 1 << 2, // Inference made from return type of generic function
}

export interface InferenceInfo {
typeParameter: TypeParameter;
candidates: Type[];
inferredType: Type;
priority: InferencePriority;
topLevel: boolean;
isFixed: boolean;
typeParameter: TypeParameter; // Type parameter for which inferences are being made
candidates: Type[]; // Candidates in covariant positions (or undefined)
contraCandidates: Type[]; // Candidates in contravariant positions (or undefined)
inferredType: Type; // Cache for resolved inferred type
priority: InferencePriority; // Priority of current inference set
topLevel: boolean; // True if all inferences are to top level occurrences
isFixed: boolean; // True if inferences are fixed
}

export const enum InferenceFlags {
Expand Down
9 changes: 4 additions & 5 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2178,15 +2178,14 @@ declare namespace ts {
declaration?: SignatureDeclaration;
}
enum InferencePriority {
Contravariant = 1,
NakedTypeVariable = 2,
MappedType = 4,
ReturnType = 8,
NeverType = 16,
NakedTypeVariable = 1,
MappedType = 2,
ReturnType = 4,
}
interface InferenceInfo {
typeParameter: TypeParameter;
candidates: Type[];
contraCandidates: Type[];
inferredType: Type;
priority: InferencePriority;
topLevel: boolean;
Expand Down
9 changes: 4 additions & 5 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2178,15 +2178,14 @@ declare namespace ts {
declaration?: SignatureDeclaration;
}
enum InferencePriority {
Contravariant = 1,
NakedTypeVariable = 2,
MappedType = 4,
ReturnType = 8,
NeverType = 16,
NakedTypeVariable = 1,
MappedType = 2,
ReturnType = 4,
}
interface InferenceInfo {
typeParameter: TypeParameter;
candidates: Type[];
contraCandidates: Type[];
inferredType: Type;
priority: InferencePriority;
topLevel: boolean;
Expand Down
18 changes: 18 additions & 0 deletions tests/baselines/reference/strictFunctionTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const x1 = f1(fo, fs); // (x: string) => void
const x2 = f2("abc", fo, fs); // "abc"
const x3 = f3("abc", fo, fx); // "abc" | "def"
const x4 = f4(fo, fs); // Func<string>

declare const never: never;

const x10 = f2(never, fo, fs); // string
const x11 = f3(never, fo, fx); // "def"

// Repro from #21112

declare function foo<T>(a: ReadonlyArray<T>): T;
let x = foo([]); // never


//// [strictFunctionTypes1.js]
Expand All @@ -23,6 +33,9 @@ var x1 = f1(fo, fs); // (x: string) => void
var x2 = f2("abc", fo, fs); // "abc"
var x3 = f3("abc", fo, fx); // "abc" | "def"
var x4 = f4(fo, fs); // Func<string>
var x10 = f2(never, fo, fs); // string
var x11 = f3(never, fo, fx); // "def"
var x = foo([]); // never


//// [strictFunctionTypes1.d.ts]
Expand All @@ -40,3 +53,8 @@ declare const x1: (x: string) => void;
declare const x2 = "abc";
declare const x3: string;
declare const x4: Func<string>;
declare const never: never;
declare const x10: string;
declare const x11: "def";
declare function foo<T>(a: ReadonlyArray<T>): T;
declare let x: never;
31 changes: 31 additions & 0 deletions tests/baselines/reference/strictFunctionTypes1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,34 @@ const x4 = f4(fo, fs); // Func<string>
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37))

declare const never: never;
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))

const x10 = f2(never, fo, fs); // string
>x10 : Symbol(x10, Decl(strictFunctionTypes1.ts, 19, 5))
>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 79))
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37))

const x11 = f3(never, fo, fx); // "def"
>x11 : Symbol(x11, Decl(strictFunctionTypes1.ts, 20, 5))
>f3 : Symbol(f3, Decl(strictFunctionTypes1.ts, 1, 74))
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
>fx : Symbol(fx, Decl(strictFunctionTypes1.ts, 9, 37))

// Repro from #21112

declare function foo<T>(a: ReadonlyArray<T>): T;
>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))
>a : Symbol(a, Decl(strictFunctionTypes1.ts, 24, 24))
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.d.ts, --, --))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))

let x = foo([]); // never
>x : Symbol(x, Decl(strictFunctionTypes1.ts, 25, 3))
>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30))

35 changes: 35 additions & 0 deletions tests/baselines/reference/strictFunctionTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,38 @@ const x4 = f4(fo, fs); // Func<string>
>fo : (x: Object) => void
>fs : (x: string) => void

declare const never: never;
>never : never

const x10 = f2(never, fo, fs); // string
>x10 : string
>f2(never, fo, fs) : string
>f2 : <T>(obj: T, f1: (x: T) => void, f2: (x: T) => void) => T
>never : never
>fo : (x: Object) => void
>fs : (x: string) => void

const x11 = f3(never, fo, fx); // "def"
>x11 : "def"
>f3(never, fo, fx) : "def"
>f3 : <T>(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T
>never : never
>fo : (x: Object) => void
>fx : (f: (x: "def") => void) => void

// Repro from #21112

declare function foo<T>(a: ReadonlyArray<T>): T;
>foo : <T>(a: ReadonlyArray<T>) => T
>T : T
>a : ReadonlyArray<T>
>ReadonlyArray : ReadonlyArray<T>
>T : T
>T : T

let x = foo([]); // never
>x : never
>foo([]) : never
>foo : <T>(a: ReadonlyArray<T>) => T
>[] : never[]

10 changes: 10 additions & 0 deletions tests/cases/compiler/strictFunctionTypes1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ const x1 = f1(fo, fs); // (x: string) => void
const x2 = f2("abc", fo, fs); // "abc"
const x3 = f3("abc", fo, fx); // "abc" | "def"
const x4 = f4(fo, fs); // Func<string>

declare const never: never;

const x10 = f2(never, fo, fs); // string
const x11 = f3(never, fo, fx); // "def"

// Repro from #21112

declare function foo<T>(a: ReadonlyArray<T>): T;
let x = foo([]); // never

0 comments on commit 64b3086

Please sign in to comment.