Description
🔎 Search Terms
"infer predicate object types", "infer predicate empty object", "infer predicate empty object null", "{} | null"
🕗 Version & Regression Information
- This is a type narrowing issue
- This is the behavior in every version I tried
⏯ Playground Link
💻 Code
const emptyNull = [{}, null].filter(s => !!s); // expected {}[]
// ^? ({} | null)[]
const emptyUndefined = [{}, undefined].filter(s => !!s); // expected {}[]
// ^? ({} | undefined)
const emptyTruthy = [{}].filter(s => !!s); // expected {}[]
// ^? {}[]
const emptyFalsy = [{}].filter(s => !s); // expected never[]
// ^? {}[]
const nullish = [null, undefined].filter(s => !!s); // expected never[]
// ^? (null | undefined)[]
const explicitNullish = [{}, null].filter(s => s !== null); // ok
// ^? { foo: {}; }[]
const nonEmptyNull = [{ foo: {} }, null].filter(s => !!s); // ok
// ^? { foo: {}; }[]
const emptyNumber = [{}, 0].filter(s => !!s); // ok
// ^? {}[]
🙁 Actual behavior
Predicate inference fails for the cases where {}
is declared along with a nullish value, using a predicate to test for truthiness or falsiness (!!x
/ !x
). It infers a type that includes the nullishness.
It also fails to infer the predicate for cases where array only contains nullish values.
It works if the object has at least one property, or using explicit bullishness check.
🙂 Expected behavior
The return type should reflect the truthiness of {}
, as it is neither a nullish or falsy value and should pass the truthiness test.
Additional information about the issue
The section "This is a gentle nudge away from truthiness footguns" in #57465 explains that object types should have no footguns:
If you're working with object types, on the other hand, there is no footgun
and a truthiness test will infer a predicate:const datesTruthy = [new Date(), null, new Date(), null].filter(d => !!d); // ^? const datesTruthy: Date[]
That's why I expected the same for {}
and the opposite with nullish-only values.