Skip to content

Deprioritize inferences made from implicit never arrays #54006

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
5 changes: 5 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25237,6 +25237,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (candidate === blockedStringType) {
return;
}
// without strictNullChecks an inference from an array of undefinedWideningType is still better than an inference from a widening null/undefined
// so we avoid deprioritization of those inferences when strictNullChecks are not enabled
if (strictNullChecks && isEmptyArrayLiteralType(candidate)) {
priority |= InferencePriority.ImplicitNever;
}
if (inference.priority === undefined || priority < inference.priority) {
inference.candidates = undefined;
inference.contraCandidates = undefined;
Expand Down
11 changes: 6 additions & 5 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6817,11 +6817,12 @@ export const enum InferencePriority {
PartialHomomorphicMappedType = 1 << 4, // Partial reverse inference for homomorphic mapped type
MappedTypeConstraint = 1 << 5, // Reverse inference for mapped type
ContravariantConditional = 1 << 6, // Conditional type in contravariant position
ReturnType = 1 << 7, // Inference made from return type of generic function
LiteralKeyof = 1 << 8, // Inference made from a string literal to a keyof T
NoConstraints = 1 << 9, // Don't infer from constraints of instantiable types
AlwaysStrict = 1 << 10, // Always use strict rules for contravariant inferences
MaxValue = 1 << 11, // Seed for inference priority tracking
ImplicitNever = 1 << 7, // Inference made from an implicit never type
ReturnType = 1 << 8, // Inference made from return type of generic function
LiteralKeyof = 1 << 9, // Inference made from a string literal to a keyof T
NoConstraints = 1 << 10, // Don't infer from constraints of instantiable types
AlwaysStrict = 1 << 11, // Always use strict rules for contravariant inferences
MaxValue = 1 << 12, // Seed for inference priority tracking

PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
Circularity = -1, // Inference circularity (value less than all other priorities)
Expand Down
13 changes: 7 additions & 6 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7360,12 +7360,13 @@ declare namespace ts {
PartialHomomorphicMappedType = 16,
MappedTypeConstraint = 32,
ContravariantConditional = 64,
ReturnType = 128,
LiteralKeyof = 256,
NoConstraints = 512,
AlwaysStrict = 1024,
MaxValue = 2048,
PriorityImpliesCombination = 416,
ImplicitNever = 128,
ReturnType = 256,
LiteralKeyof = 512,
NoConstraints = 1024,
AlwaysStrict = 2048,
MaxValue = 4096,
PriorityImpliesCombination = 800,
Circularity = -1,
}
interface FileExtensionInfo {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//// [tests/cases/compiler/mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts] ////

=== mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts ===
// repro from https://github.com/microsoft/TypeScript/issues/54000

function foo<T>(record: Record<string, T>, entity: T) {}
>foo : Symbol(foo, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 0, 0))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 13))
>record : Symbol(record, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 16))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 13))
>entity : Symbol(entity, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 42))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 13))

type StringArrayRecord = Record<string, string[]>;
>StringArrayRecord : Symbol(StringArrayRecord, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 56))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

function test() {
>test : Symbol(test, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 4, 50))

const working: Record<string, string[]> = {};
>working : Symbol(working, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 7, 7))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))

foo(working, []);
>foo : Symbol(foo, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 0, 0))
>working : Symbol(working, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 7, 7))

const working2: StringArrayRecord = {};
>working2 : Symbol(working2, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 10, 7))
>StringArrayRecord : Symbol(StringArrayRecord, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 2, 56))

foo(working2, []);
>foo : Symbol(foo, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 0, 0))
>working2 : Symbol(working2, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 10, 7))
}

// showcase the same behavior with index signature

function bar<T>(record: { [k: string]: T }, entity: T) {}
>bar : Symbol(bar, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 12, 1))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 13))
>record : Symbol(record, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 16))
>k : Symbol(k, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 27))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 13))
>entity : Symbol(entity, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 43))
>T : Symbol(T, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 13))

type StringArrayIndexSignature = { [k: string]: string[] };
>StringArrayIndexSignature : Symbol(StringArrayIndexSignature, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 57))
>k : Symbol(k, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 18, 36))

function test2() {
>test2 : Symbol(test2, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 18, 59))

const working: { [k: string]: string[] } = {};
>working : Symbol(working, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 21, 7))
>k : Symbol(k, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 21, 20))

bar(working, []);
>bar : Symbol(bar, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 12, 1))
>working : Symbol(working, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 21, 7))

const working2: StringArrayIndexSignature = {};
>working2 : Symbol(working2, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 24, 7))
>StringArrayIndexSignature : Symbol(StringArrayIndexSignature, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 16, 57))

bar(working2, []);
>bar : Symbol(bar, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 12, 1))
>working2 : Symbol(working2, Decl(mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts, 24, 7))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//// [tests/cases/compiler/mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts] ////

=== mappedTypeWithPrimitiveTypeParameterConstraintInferencePriority.ts ===
// repro from https://github.com/microsoft/TypeScript/issues/54000

function foo<T>(record: Record<string, T>, entity: T) {}
>foo : <T>(record: Record<string, T>, entity: T) => void
>record : Record<string, T>
>entity : T

type StringArrayRecord = Record<string, string[]>;
>StringArrayRecord : { [x: string]: string[]; }

function test() {
>test : () => void

const working: Record<string, string[]> = {};
>working : Record<string, string[]>
>{} : {}

foo(working, []);
>foo(working, []) : void
>foo : <T>(record: Record<string, T>, entity: T) => void
>working : Record<string, string[]>
>[] : never[]

const working2: StringArrayRecord = {};
>working2 : StringArrayRecord
>{} : {}

foo(working2, []);
>foo(working2, []) : void
>foo : <T>(record: Record<string, T>, entity: T) => void
>working2 : StringArrayRecord
>[] : never[]
}

// showcase the same behavior with index signature

function bar<T>(record: { [k: string]: T }, entity: T) {}
>bar : <T>(record: { [k: string]: T; }, entity: T) => void
>record : { [k: string]: T; }
>k : string
>entity : T

type StringArrayIndexSignature = { [k: string]: string[] };
>StringArrayIndexSignature : { [k: string]: string[]; }
>k : string

function test2() {
>test2 : () => void

const working: { [k: string]: string[] } = {};
>working : { [k: string]: string[]; }
>k : string
>{} : {}

bar(working, []);
>bar(working, []) : void
>bar : <T>(record: { [k: string]: T; }, entity: T) => void
>working : { [k: string]: string[]; }
>[] : never[]

const working2: StringArrayIndexSignature = {};
>working2 : StringArrayIndexSignature
>{} : {}

bar(working2, []);
>bar(working2, []) : void
>bar : <T>(record: { [k: string]: T; }, entity: T) => void
>working2 : StringArrayIndexSignature
>[] : never[]
}

2 changes: 0 additions & 2 deletions tests/baselines/reference/unionOfClassCalls.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ arr.map((a: number | string, index: number) => {
return index
})

// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
return []
}, [])
Expand Down Expand Up @@ -92,7 +91,6 @@ var arr2 = [];
arr.map(function (a, index) {
return index;
});
// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce(function (acc, a, index) {
return [];
}, []);
Expand Down
85 changes: 42 additions & 43 deletions tests/baselines/reference/unionOfClassCalls.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,14 @@ arr.map((a: number | string, index: number) => {

})

// This case still doesn't work because `reduce` has multiple overloads :(
arr.reduce((acc: Array<string>, a: number | string, index: number) => {
>arr.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --) ... and 1 more)
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 27, 12))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 26, 12))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 27, 31))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 27, 51))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 26, 31))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 26, 51))

return []
}, [])
Expand All @@ -84,69 +83,69 @@ arr.forEach((a: number | string, index: number) => {
>arr.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr : Symbol(arr, Decl(unionOfClassCalls.ts, 18, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 31, 13))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 31, 32))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 30, 13))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 30, 32))

return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 31, 32))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 30, 32))

})

arr1.map((a: number, index: number) => {
>arr1.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 35, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 35, 20))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 34, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 34, 20))

return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 35, 20))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 34, 20))

})

arr1.reduce((acc: number[], a: number, index: number) => {
>arr1.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 39, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 39, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 39, 38))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 38, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 38, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 38, 38))

return [a]
>a : Symbol(a, Decl(unionOfClassCalls.ts, 39, 27))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 38, 27))

}, [])

arr1.forEach((a: number, index: number) => {
>arr1.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>arr1 : Symbol(arr1, Decl(unionOfClassCalls.ts, 19, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 43, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 43, 24))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 42, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 42, 24))

return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 43, 24))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 42, 24))

})
arr2.map((a: string, index: number) => {
>arr2.map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>map : Symbol(Array.map, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 46, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 46, 21))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 45, 10))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 45, 21))

return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 46, 21))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 45, 21))

})

arr2.reduce((acc: string[], a: string, index: number) => {
>arr2.reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 50, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 50, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 50, 38))
>acc : Symbol(acc, Decl(unionOfClassCalls.ts, 49, 13))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 49, 27))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 49, 38))

return []
}, [])
Expand All @@ -155,54 +154,54 @@ arr2.forEach((a: string, index: number) => {
>arr2.forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>arr2 : Symbol(arr2, Decl(unionOfClassCalls.ts, 20, 5))
>forEach : Symbol(Array.forEach, Decl(lib.es5.d.ts, --, --))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 54, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 54, 24))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 53, 14))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 53, 24))

return index
>index : Symbol(index, Decl(unionOfClassCalls.ts, 54, 24))
>index : Symbol(index, Decl(unionOfClassCalls.ts, 53, 24))

})

// from https://github.com/microsoft/TypeScript/issues/36307

declare class Foo {
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 55, 2))

doThing(): Promise<this>
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19))
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 59, 19))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
}

declare class Bar extends Foo {
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 61, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 55, 2))

bar: number;
>bar : Symbol(Bar.bar, Decl(unionOfClassCalls.ts, 64, 31))
>bar : Symbol(Bar.bar, Decl(unionOfClassCalls.ts, 63, 31))
}
declare class Baz extends Foo {
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 56, 2))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 65, 1))
>Foo : Symbol(Foo, Decl(unionOfClassCalls.ts, 55, 2))

baz: number;
>baz : Symbol(Baz.baz, Decl(unionOfClassCalls.ts, 67, 31))
>baz : Symbol(Baz.baz, Decl(unionOfClassCalls.ts, 66, 31))
}

declare var a: Bar | Baz;
>a : Symbol(a, Decl(unionOfClassCalls.ts, 71, 11))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 70, 11))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 61, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 65, 1))

// note, you must annotate `result` for now
a.doThing().then((result: Bar | Baz) => {
>a.doThing().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>a.doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19), Decl(unionOfClassCalls.ts, 60, 19))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 71, 11))
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 60, 19), Decl(unionOfClassCalls.ts, 60, 19))
>a.doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 59, 19), Decl(unionOfClassCalls.ts, 59, 19))
>a : Symbol(a, Decl(unionOfClassCalls.ts, 70, 11))
>doThing : Symbol(Foo.doThing, Decl(unionOfClassCalls.ts, 59, 19), Decl(unionOfClassCalls.ts, 59, 19))
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>result : Symbol(result, Decl(unionOfClassCalls.ts, 73, 18))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 62, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 66, 1))
>result : Symbol(result, Decl(unionOfClassCalls.ts, 72, 18))
>Bar : Symbol(Bar, Decl(unionOfClassCalls.ts, 61, 1))
>Baz : Symbol(Baz, Decl(unionOfClassCalls.ts, 65, 1))

// whatever
});
Expand Down
Loading