Skip to content
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

Select produces wrong types #172

Open
DeluxeOwl opened this issue Jul 10, 2023 · 3 comments
Open

Select produces wrong types #172

DeluxeOwl opened this issue Jul 10, 2023 · 3 comments

Comments

@DeluxeOwl
Copy link

DeluxeOwl commented Jul 10, 2023

Hi, I'm trying to extract useState patterns from react, but the selections object has the wrong types (some properties aren't even added):
image

import { match, P } from "ts-pattern";

const isObject = (value: unknown): value is Object =>
  Boolean(value && typeof value === "object");

const useStatePattern = {
  type: "VariableDeclaration",
  kind: "const",
  declarations: [
    {
      type: "VariableDeclarator",
      id: {
        type: "ArrayPattern",
        elements: [
          {
            type: "Identifier",
            name: P.select("stateIdentifier", P.string),
          },
          {
            type: "Identifier",
            name: P.select("setStateIdentifier", P.string),
          },
        ],
      },
      init: {
        type: "CallExpression",
        callee: {
          type: "Identifier",
          name: "useState",
        },
        // this matches the exact length
        arguments: [P.select("value", P.when(isObject))],
        typeParameters: P.optional(
          P.select("typeParameters", P.when(isObject))
        ),
      },
    },
  ],
};

// here when hovering over selections
match<any, any>({})
  .with(useStatePattern, (selections) => selections)
  .otherwise(() => null) as any;

Using the latest version as of today "ts-pattern": "^5.0.1"

@gvergnaud
Copy link
Owner

Hey,

First, here is a workaround: you can use as const after your pattern declaration, and TS-Pattern will work as expected: Playround

const useStatePattern = { ... } as const;

match<any, any>({})
      .with(useStatePattern, (selections) => 
      /* selections: {
            value: Object;
            stateIdentifier: string;
            setStateIdentifier: string;
            typeParameters: Object | undefined;
        }  */
      )
      .otherwise(() => null);

It doesn't work without it because TypeScript will infer the type of arrays contained inside useStatePattern as arrays of unions rather than tuple types by default, e.g. (A | B)[] instead of [A, B]. TS-Pattern ignores inline arrays because they aren't precise enough to perform exhaustive checking, or to really know what it's inside each index and what has been selected (when the type of input is known ahead of time). This wouldn't happen if your pattern was defined inline, inside the with clause:

match<any, any>({})
  .with({  /* ... your useStatePattern inlined */  }, (selections) => selections) // would work
  .otherwise(() => null) as any;

That said, I think we should change the Selections type to infer what it can from an array, even though the type of the selected value could be a little bit imprecise.

@DeluxeOwl
Copy link
Author

Thanks for the answer, the workaround works fine for my use case

@gvergnaud
Copy link
Owner

It turns out that part of this bug is due to what I believe is a bug in the type checker when deduplicating arrays elements that use phantom type parameters: Playground

I'm planning to open an issue on TypeScript's repository, keeping this issue open in the meantime.

@gvergnaud gvergnaud reopened this Jul 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants