devanshj/sthir

Nested `?` doesn't narrow

devanshj opened this issue · 3 comments

import { p, pa } from "@sthir/predicate"

declare let x: { a: { b: string } | undefined }

if (pa(x, p(".a?.b"))) {
  x.a.b // should compile, doesn't
}

Originally posted by @VinSpee in #1 (comment)

here's a minimal repro:

https://tsplay.dev/N7KL4N

import { p } from "@sthir/predicate";

interface Image {
  height: number;
  width: number;
  url: string;
  name: string;
}

interface Variants {
  "square-small2x": Image;
  "square-small": Image;
  "landscape-crop": Image;
  "landscape-crop2x": Image;
}

export type Profile = {
  id: string;
  images: Variants | null | undefined;
  displayName: string;
  jobTitle: string;
};

const data: Profile[] = [
  {
    id: "618c4a28-925c-42df-a655-30552ea53607",
    images: {
      "square-small2x": {
        height: 480,
        width: 480,
        url: "https://avatars.dicebear.com/api/adventurer-neutral/ts.svg",
        name: "square-small2x",
      },
      "square-small": {
        height: 240,
        width: 240,
        url: "https://avatars.dicebear.com/api/adventurer-neutral/ts.svg",
        name: "square-small",
      },
      "landscape-crop": {
        height: 267,
        width: 400,
        url: "https://avatars.dicebear.com/api/adventurer-neutral/ts.svg",
        name: "landscape-crop",
      },
      "landscape-crop2x": {
        height: 533,
        width: 800,
        url: "https://avatars.dicebear.com/api/adventurer-neutral/ts.svg",
        name: "landscape-crop2x",
      },
    },
    displayName: "Jennifer Smith",
    jobTitle: "Marketing Executive",
  },
];

data.filter(p("?.images?.square-small2x"));

Expression produces a union type that is too complex to represent.

Argument of type '["?.images?.square-small2x"]' is not assignable to parameter of type '[] | [Comparator] | [Operator]'.
Type '["?.images?.square-small2x"]' is not assignable to type '[Comparator]'.
Type '"?.images?.square-small2x"' is not assignable to type 'Comparator'.

@VinSpee The error is correct that you don't need the leading ? because profile is always defined. The problem is this should compile but it doesn't...

// https://tsplay.dev/NrG7zm

data.filter(p(".images?.square-small2x")).map(profile => profile.images["square-small2x"]);

A more minimal repro is this...

import { p, pa } from "@sthir/predicate"

declare let x: { a: { b: string } | undefined }

if (pa(x, p(".a?.b"))) {
  x.a.b // should compile, doesn't
}

Will look into this soon!

Also in meantime you can use this instead...

// https://tsplay.dev/mMVZbW

let urls: string[] =
  data
  .filter(p(".images?.square-small2x typeof ===", "object"))
  .map(profile => profile.images["square-small2x"].url); // compiles

Fixed it, now the following compiles. I hope that solves your issue.

let urls: string[] =
  data
  .filter(p(".images?.square-small2x"))
  .map(profile => profile.images["square-small2x"].url);

The playground still picks v0.0.8 instead of latest v0.0.9 because of cache, but you can try it locally.

Thanks again for reporting and giving @sthir/predicate a try!