Skip to content

Fix empty array inference #21157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 32 additions & 18 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11344,6 +11344,7 @@ namespace ts {
return {
typeParameter,
candidates: undefined,
contraCandidates: undefined,
inferredType: undefined,
priority: undefined,
topLevel: true,
Expand All @@ -11355,6 +11356,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 @@ -11468,6 +11470,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 @@ -11535,18 +11538,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 @@ -11569,15 +11574,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 @@ -11646,9 +11651,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 @@ -11827,10 +11832,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 @@ -3772,20 +3772,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 @@ -2149,15 +2149,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 @@ -2149,15 +2149,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