Skip to content

Commit 64b3086

Browse files
authored
Merge pull request #21157 from Microsoft/fixEmptyArrayInference
Fix empty array inference
2 parents 154c614 + b6b936f commit 64b3086

File tree

8 files changed

+144
-39
lines changed

8 files changed

+144
-39
lines changed

src/compiler/checker.ts

+32-18
Original file line numberDiff line numberDiff line change
@@ -10917,6 +10917,7 @@ namespace ts {
1091710917
return {
1091810918
typeParameter,
1091910919
candidates: undefined,
10920+
contraCandidates: undefined,
1092010921
inferredType: undefined,
1092110922
priority: undefined,
1092210923
topLevel: true,
@@ -10928,6 +10929,7 @@ namespace ts {
1092810929
return {
1092910930
typeParameter: inference.typeParameter,
1093010931
candidates: inference.candidates && inference.candidates.slice(),
10932+
contraCandidates: inference.contraCandidates && inference.contraCandidates.slice(),
1093110933
inferredType: inference.inferredType,
1093210934
priority: inference.priority,
1093310935
topLevel: inference.topLevel,
@@ -11041,6 +11043,7 @@ namespace ts {
1104111043
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
1104211044
let symbolStack: Symbol[];
1104311045
let visited: Map<boolean>;
11046+
let contravariant = false;
1104411047
inferFromTypes(originalSource, originalTarget);
1104511048

1104611049
function inferFromTypes(source: Type, target: Type) {
@@ -11108,18 +11111,20 @@ namespace ts {
1110811111
const inference = getInferenceInfoForType(target);
1110911112
if (inference) {
1111011113
if (!inference.isFixed) {
11111-
// We give lowest priority to inferences of implicitNeverType (which is used as the
11112-
// element type for empty array literals). Thus, inferences from empty array literals
11113-
// only matter when no other inferences are made.
11114-
const p = priority | (source === implicitNeverType ? InferencePriority.NeverType : 0);
11115-
if (!inference.candidates || p < inference.priority) {
11116-
inference.candidates = [source];
11117-
inference.priority = p;
11114+
if (inference.priority === undefined || priority < inference.priority) {
11115+
inference.candidates = undefined;
11116+
inference.contraCandidates = undefined;
11117+
inference.priority = priority;
1111811118
}
11119-
else if (p === inference.priority) {
11120-
inference.candidates.push(source);
11119+
if (priority === inference.priority) {
11120+
if (contravariant) {
11121+
inference.contraCandidates = append(inference.contraCandidates, source);
11122+
}
11123+
else {
11124+
inference.candidates = append(inference.candidates, source);
11125+
}
1112111126
}
11122-
if (!(p & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
11127+
if (!(priority & InferencePriority.ReturnType) && target.flags & TypeFlags.TypeParameter && !isTypeParameterAtTopLevel(originalTarget, <TypeParameter>target)) {
1112311128
inference.topLevel = false;
1112411129
}
1112511130
}
@@ -11142,15 +11147,15 @@ namespace ts {
1114211147
}
1114311148
}
1114411149
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
11145-
priority ^= InferencePriority.Contravariant;
11150+
contravariant = !contravariant;
1114611151
inferFromTypes((<IndexType>source).type, (<IndexType>target).type);
11147-
priority ^= InferencePriority.Contravariant;
11152+
contravariant = !contravariant;
1114811153
}
1114911154
else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) {
1115011155
const empty = createEmptyObjectTypeFromStringLiteral(source);
11151-
priority ^= InferencePriority.Contravariant;
11156+
contravariant = !contravariant;
1115211157
inferFromTypes(empty, (target as IndexType).type);
11153-
priority ^= InferencePriority.Contravariant;
11158+
contravariant = !contravariant;
1115411159
}
1115511160
else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) {
1115611161
inferFromTypes((<IndexedAccessType>source).objectType, (<IndexedAccessType>target).objectType);
@@ -11219,9 +11224,9 @@ namespace ts {
1121911224

1122011225
function inferFromContravariantTypes(source: Type, target: Type) {
1122111226
if (strictFunctionTypes) {
11222-
priority ^= InferencePriority.Contravariant;
11227+
contravariant = !contravariant;
1122311228
inferFromTypes(source, target);
11224-
priority ^= InferencePriority.Contravariant;
11229+
contravariant = !contravariant;
1122511230
}
1122611231
else {
1122711232
inferFromTypes(source, target);
@@ -11400,10 +11405,19 @@ namespace ts {
1140011405
// If all inferences were made from contravariant positions, infer a common subtype. Otherwise, if
1140111406
// union types were requested or if all inferences were made from the return type position, infer a
1140211407
// union type. Otherwise, infer a common supertype.
11403-
const unwidenedType = inference.priority & InferencePriority.Contravariant ? getCommonSubtype(baseCandidates) :
11404-
context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ? getUnionType(baseCandidates, UnionReduction.Subtype) :
11408+
const unwidenedType = context.flags & InferenceFlags.InferUnionTypes || inference.priority & InferencePriority.ReturnType ?
11409+
getUnionType(baseCandidates, UnionReduction.Subtype) :
1140511410
getCommonSupertype(baseCandidates);
1140611411
inferredType = getWidenedType(unwidenedType);
11412+
// If we have inferred 'never' but have contravariant candidates. To get a more specific type we
11413+
// infer from the contravariant candidates instead.
11414+
if (inferredType.flags & TypeFlags.Never && inference.contraCandidates) {
11415+
inferredType = getCommonSubtype(inference.contraCandidates);
11416+
}
11417+
}
11418+
else if (inference.contraCandidates) {
11419+
// We only have contravariant inferences, infer the best common subtype of those
11420+
inferredType = getCommonSubtype(inference.contraCandidates);
1140711421
}
1140811422
else if (context.flags & InferenceFlags.NoDefault) {
1140911423
// We use silentNeverType as the wildcard that signals no inferences.

src/compiler/types.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -3826,20 +3826,19 @@ namespace ts {
38263826
export type TypeMapper = (t: TypeParameter) => Type;
38273827

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

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

38453844
export const enum InferenceFlags {

tests/baselines/reference/api/tsserverlibrary.d.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -2178,15 +2178,14 @@ declare namespace ts {
21782178
declaration?: SignatureDeclaration;
21792179
}
21802180
enum InferencePriority {
2181-
Contravariant = 1,
2182-
NakedTypeVariable = 2,
2183-
MappedType = 4,
2184-
ReturnType = 8,
2185-
NeverType = 16,
2181+
NakedTypeVariable = 1,
2182+
MappedType = 2,
2183+
ReturnType = 4,
21862184
}
21872185
interface InferenceInfo {
21882186
typeParameter: TypeParameter;
21892187
candidates: Type[];
2188+
contraCandidates: Type[];
21902189
inferredType: Type;
21912190
priority: InferencePriority;
21922191
topLevel: boolean;

tests/baselines/reference/api/typescript.d.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -2178,15 +2178,14 @@ declare namespace ts {
21782178
declaration?: SignatureDeclaration;
21792179
}
21802180
enum InferencePriority {
2181-
Contravariant = 1,
2182-
NakedTypeVariable = 2,
2183-
MappedType = 4,
2184-
ReturnType = 8,
2185-
NeverType = 16,
2181+
NakedTypeVariable = 1,
2182+
MappedType = 2,
2183+
ReturnType = 4,
21862184
}
21872185
interface InferenceInfo {
21882186
typeParameter: TypeParameter;
21892187
candidates: Type[];
2188+
contraCandidates: Type[];
21902189
inferredType: Type;
21912190
priority: InferencePriority;
21922191
topLevel: boolean;

tests/baselines/reference/strictFunctionTypes1.js

+18
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ const x1 = f1(fo, fs); // (x: string) => void
1515
const x2 = f2("abc", fo, fs); // "abc"
1616
const x3 = f3("abc", fo, fx); // "abc" | "def"
1717
const x4 = f4(fo, fs); // Func<string>
18+
19+
declare const never: never;
20+
21+
const x10 = f2(never, fo, fs); // string
22+
const x11 = f3(never, fo, fx); // "def"
23+
24+
// Repro from #21112
25+
26+
declare function foo<T>(a: ReadonlyArray<T>): T;
27+
let x = foo([]); // never
1828

1929

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

2740

2841
//// [strictFunctionTypes1.d.ts]
@@ -40,3 +53,8 @@ declare const x1: (x: string) => void;
4053
declare const x2 = "abc";
4154
declare const x3: string;
4255
declare const x4: Func<string>;
56+
declare const never: never;
57+
declare const x10: string;
58+
declare const x11: "def";
59+
declare function foo<T>(a: ReadonlyArray<T>): T;
60+
declare let x: never;

tests/baselines/reference/strictFunctionTypes1.symbols

+31
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,34 @@ const x4 = f4(fo, fs); // Func<string>
9494
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
9595
>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37))
9696

97+
declare const never: never;
98+
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
99+
100+
const x10 = f2(never, fo, fs); // string
101+
>x10 : Symbol(x10, Decl(strictFunctionTypes1.ts, 19, 5))
102+
>f2 : Symbol(f2, Decl(strictFunctionTypes1.ts, 0, 79))
103+
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
104+
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
105+
>fs : Symbol(fs, Decl(strictFunctionTypes1.ts, 8, 37))
106+
107+
const x11 = f3(never, fo, fx); // "def"
108+
>x11 : Symbol(x11, Decl(strictFunctionTypes1.ts, 20, 5))
109+
>f3 : Symbol(f3, Decl(strictFunctionTypes1.ts, 1, 74))
110+
>never : Symbol(never, Decl(strictFunctionTypes1.ts, 17, 13))
111+
>fo : Symbol(fo, Decl(strictFunctionTypes1.ts, 6, 58))
112+
>fx : Symbol(fx, Decl(strictFunctionTypes1.ts, 9, 37))
113+
114+
// Repro from #21112
115+
116+
declare function foo<T>(a: ReadonlyArray<T>): T;
117+
>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30))
118+
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))
119+
>a : Symbol(a, Decl(strictFunctionTypes1.ts, 24, 24))
120+
>ReadonlyArray : Symbol(ReadonlyArray, Decl(lib.d.ts, --, --))
121+
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))
122+
>T : Symbol(T, Decl(strictFunctionTypes1.ts, 24, 21))
123+
124+
let x = foo([]); // never
125+
>x : Symbol(x, Decl(strictFunctionTypes1.ts, 25, 3))
126+
>foo : Symbol(foo, Decl(strictFunctionTypes1.ts, 20, 30))
127+

tests/baselines/reference/strictFunctionTypes1.types

+35
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,38 @@ const x4 = f4(fo, fs); // Func<string>
100100
>fo : (x: Object) => void
101101
>fs : (x: string) => void
102102

103+
declare const never: never;
104+
>never : never
105+
106+
const x10 = f2(never, fo, fs); // string
107+
>x10 : string
108+
>f2(never, fo, fs) : string
109+
>f2 : <T>(obj: T, f1: (x: T) => void, f2: (x: T) => void) => T
110+
>never : never
111+
>fo : (x: Object) => void
112+
>fs : (x: string) => void
113+
114+
const x11 = f3(never, fo, fx); // "def"
115+
>x11 : "def"
116+
>f3(never, fo, fx) : "def"
117+
>f3 : <T>(obj: T, f1: (x: T) => void, f2: (f: (x: T) => void) => void) => T
118+
>never : never
119+
>fo : (x: Object) => void
120+
>fx : (f: (x: "def") => void) => void
121+
122+
// Repro from #21112
123+
124+
declare function foo<T>(a: ReadonlyArray<T>): T;
125+
>foo : <T>(a: ReadonlyArray<T>) => T
126+
>T : T
127+
>a : ReadonlyArray<T>
128+
>ReadonlyArray : ReadonlyArray<T>
129+
>T : T
130+
>T : T
131+
132+
let x = foo([]); // never
133+
>x : never
134+
>foo([]) : never
135+
>foo : <T>(a: ReadonlyArray<T>) => T
136+
>[] : never[]
137+

tests/cases/compiler/strictFunctionTypes1.ts

+10
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,13 @@ const x1 = f1(fo, fs); // (x: string) => void
1717
const x2 = f2("abc", fo, fs); // "abc"
1818
const x3 = f3("abc", fo, fx); // "abc" | "def"
1919
const x4 = f4(fo, fs); // Func<string>
20+
21+
declare const never: never;
22+
23+
const x10 = f2(never, fo, fs); // string
24+
const x11 = f3(never, fo, fx); // "def"
25+
26+
// Repro from #21112
27+
28+
declare function foo<T>(a: ReadonlyArray<T>): T;
29+
let x = foo([]); // never

0 commit comments

Comments
 (0)