Suggestion
TypeScript sometimes infers empty arrays as having type never[]. Usually, this doesn't cause problems, until it gets unioned with another type, and you try to call it as a function/method:
function orDefault<T, D>(x: T | null, d: D): T | D {
if (x === null) {
return d;
}
return x;
}
const xs: string[] | null = ["a", "bc", "def"];
// y: string[] | never[]
const y = orDefault(xs, []);
// ERR: This expression is not callable.
// Each member of the union type
// (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])
// | (<U>(callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])
// has signatures, but none of those signatures are compatible with each other. (2349)
const yThen = y.map(item => item.length);
const yChain = orDefault(xs, []).map(item => item.length);
In this example, since the [] is typed as never[], the .map method has a union type with two incompatible types; one possibility is a string, number, string[] argument list returning a generic U, and the other is a never, number, never[] argument list returning a generic U.
Right now, tsc doesn't distinguish between the two; since they're incompatible, it raises ts(2349). This can make it difficult to write nicely-chainable union values, since the inferred values for "empty" or "impossible" variants prevent type inference from working.
However, there's a very easy reason to see why the first overload should be preferred: values of type never shouldn't exist, so a function asking for a never will never be called.
While applying this rule in general is too extreme (since it would likely open up many soundness holes), we can add a small exception to ts(2349): before rejecting the call for having incompatible callback types, check to see if any argument is a callback expecting never as any argument. If so, replace that callback with the most-general function type, (...args: unknown[]) => never. Then attempt to type-check with this type.
We can see that the existing type machinery can handle the code after this "fix":
const func: (<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any) => U[])
| ((...args: unknown[]) => never)
= null as any;
// item: string
// res: number[]
const res = func(item => item.length);
This change is very narrow, since it only applies to code that:
- is currently rejected by
ts(2349)
- AND one of the union signature's parameters is a callback
- AND that callback has an argument with type
never
In particular, since it only affects code that today fails to compile, it can't introduce new breaking changes.
Despite being fairly narrow, it should help out inference for many common functions, especially related to empty arrays and other empty collections, including .map, .filter, .flatMap, .reduce, etc.
🔍 Search Terms
- empty array map
- "This expression is not callable"
- Each member of the union type '((callbackfn: (value: Item, index: number, array: Item[]) => U, thisArg?: any) => U[]) | ((callbackfn: (value: never, index: number, array: never[]) => U, thisArg?: any) => U[])' has signatures, but none of those signatures are compatible with each other
- ts(2349)
- never type
- incompatible callbacks
Vaguely related, but broader/different from this proposal: #40157
List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
✅ Viability Checklist
My suggestion meets these guidelines:
📃 Motivating Example
function orDefault<T, D>(x: T | null, d: D): T | D {
if (x === null) {
return d;
}
return x;
}
const xs: string[] | null = ["a", "bc", "def"];
// does not type-check today, due to ts(2349)
const fixed: number[] = orDefault(xs, []).map(item => item.length);
💻 Use Cases
Making "chaining" libraries more ergonomic, especially when dealing with results.
Improves types for various array methods.
Also applies to other collections, like new Set([]) having type Set<never>; this makes .forEach callable on a union value with new Set([]) as one possible alternative.
Suggestion
TypeScript sometimes infers empty arrays as having type
never[]. Usually, this doesn't cause problems, until it gets unioned with another type, and you try to call it as a function/method:In this example, since the
[]is typed asnever[], the.mapmethod has a union type with two incompatible types; one possibility is astring,number,string[]argument list returning a genericU, and the other is anever,number,never[]argument list returning a genericU.Right now, tsc doesn't distinguish between the two; since they're incompatible, it raises
ts(2349). This can make it difficult to write nicely-chainable union values, since the inferred values for "empty" or "impossible" variants prevent type inference from working.However, there's a very easy reason to see why the first overload should be preferred: values of type
nevershouldn't exist, so a function asking for aneverwill never be called.While applying this rule in general is too extreme (since it would likely open up many soundness holes), we can add a small exception to
ts(2349): before rejecting the call for having incompatible callback types, check to see if any argument is a callback expectingneveras any argument. If so, replace that callback with the most-general function type,(...args: unknown[]) => never. Then attempt to type-check with this type.We can see that the existing type machinery can handle the code after this "fix":
This change is very narrow, since it only applies to code that:
ts(2349)neverIn particular, since it only affects code that today fails to compile, it can't introduce new breaking changes.
Despite being fairly narrow, it should help out inference for many common functions, especially related to empty arrays and other empty collections, including
.map,.filter,.flatMap,.reduce, etc.🔍 Search Terms
Vaguely related, but broader/different from this proposal: #40157
neverUnion of subtypes should be usable as the supertype #38048List of keywords you searched for before creating this issue. Write them down here so that others can find this suggestion more easily and help provide feedback.
✅ Viability Checklist
My suggestion meets these guidelines:
📃 Motivating Example
💻 Use Cases
Making "chaining" libraries more ergonomic, especially when dealing with results.
Improves types for various array methods.
Also applies to other collections, like
new Set([])having typeSet<never>; this makes.forEachcallable on a union value withnew Set([])as one possible alternative.