Skip to content

Predicate inference fails for empty object and nullish values #58967

Closed as not planned
@fnky

Description

@fnky

🔎 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

https://www.typescriptlang.org/play/?ts=5.5.2#code/MYewdgzgLgBApgWwA5QJ4DkCuAbbMC8MA2gN4C+ANDGDtgLoB0AZgJbZRwBOAFBAQHwwAhEIgBKANwwA9NPgAPJHGAcAJjHJE6AKBh7ZMAHoB+baEixEKVAFUwquKzBx1hUpRiZ7jls9WNWdi5eAWFRSRk5OEVlNQ0yLV19ORMzcGgFJGwWYBYoLFwWCAALAmJyKhpcALYOHj58QT4hfEIq7AiDEABrJMijU3MMqzQsBAAjLjL3KgAGGqD60JFxKS7evX7UoctkNAAVTkwoYtRp8gW6kMaw1f7opRUXeMTNg2303esAMQBDbAgZzcF2YtWCDUE4TWURiT3UzgAblxXskBmkLNRwABRPYYWjnGBMEAgABc8RgHnal3Byyh-R6fXeg0+1FoRVKbnaVC8DicLmpSxuK06MMecURyJ0bxSpiAA

💻 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions