镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

Type narrowing on template literal types #46045

Closed

Description

Bug Report

(I'm not 100% sure if this would count as a "bug report" or a "feature request", so feel free to correct me)

🔎 Search Terms

narrowing, type, template literal types, template strings, template literals

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about template literal types

⏯ Playground Link

Workbench Repro

💻 Code

// With template literal types
export type Action =
  | {
      type: `${string}_REQUEST`;
    }
  | {
      type: `${string}_SUCCESS`;
      response: string;
    };

export function reducer(action: Action) {
  if (action.type === 'FOO_SUCCESS') {
    console.log(action.response);
  }
}

// WITHOUT template literal types
export type Action2 =
  | {
      type: 'FOO_REQUEST';
    }
  | {
      type: 'FOO_SUCCESS';
      response: string;
    };

export function reducer2(action: Action2) {
  if (action.type === 'FOO_SUCCESS') {
    console.log(action.response);
  }
}

🙁 Actual behavior

In the example with template literal types in the reducer function, it seems like TypeScript is not able to narrow the type of action down, since accessing action.response results in the following error:

Property 'response' does not exist on type 'Action'.
  Property 'response' does not exist on type '{ type: `${string}_REQUEST`; }'.(2339)

While in the example without template literal types TypeScript is able to narrow down the type of Action correctly to the one with type: 'FOO_SUCCESS' and correctly recognizes that action.response can be safely accessed as expected.

🙂 Expected behavior

In the example with template literal types I expect TypeScript to be able to narrow the type of action down in the same way as it does in the example without template literal types, and not seeing any errors.

Activity

  1. anuraghazra commented on Sep 25, 2021

    @anuraghazra

    Interesting! This one caught my eye.

    I was just playing around with the problem trying to solve it.

    Seems like I found a solution by using a type guard

    // With template literal types
    export type Action =
      | {
          type: `${string}_REQUEST`;
          data: number;
        }
      | {
          type: `${string}_SUCCESS`;
          response: string;
        };
    
    
    type RemovePrefix<T> = T extends `${infer _}_${infer Suffix}` ? `${string}_${Suffix}` : T;
    
    function isType<T extends Action, K extends T['type']>(action: Action, type: K): action is Extract<T, { type: RemovePrefix<K> }> {
      return action.type === type;
    }
    
    export function reducer(action: Action) {
      if (isType(action, 'Hello_SUCCESS')) {
        console.log(action.response);
      }
      if (isType(action, 'World_REQUEST')) {
        console.log(action.data);
      }
    }

    https://tsplay.dev/wgLV9N

  2. martinfrancois commented on Sep 27, 2021

    @martinfrancois
    Author

    Hi Anurag Hazra (@anuraghazra)

    Thanks for your efforts, you found an interesting solution that works. Unfortunately, as soon as I change the string to have multiple underscores (which occurs a lot in our application), an error shows up again:

    if (isType(action, 'HELLO_WORLD_SUCCESS')) {
        console.log(action.response);
      }
    

    However should someone else only have the need to support one underscore, this would be fine.

    Also while I feel like while in this example the overhead / added complexity would be acceptable, I wouldn't be so sure if that would still be the case when I would use it in our application where the things are already a lot more complex (since this is just a very minimal example of what we're trying to do).

    So I still feel like if this was either easy to add to TypeScript itself so that it could be used natively, as is the case in the example without template literal types, or if this is a bug which is easy to fix it would be much better.

  3. anuraghazra commented on Sep 27, 2021

    @anuraghazra

    That is right, not a ideal solution, just a workaround.
    Yes I think TS compiler should handle this case of narrowing the type depending on the template literal type.

    Cross fingers 🤞, Hope to see this land in 4.6

  4. martinfrancois commented on Oct 1, 2021

    @martinfrancois
    Author

    Thanks Anders Hejlsberg (@ahejlsberg) for the quick implementation!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Fix AvailableA PR has been opened for this issueSuggestionAn idea for TypeScript

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions