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

Cannot infer Template Literal Type containing ${number} / ${string} #43060

Closed
kotarella1110 opened this issue Mar 3, 2021 · 2 comments · Fixed by #43361
Closed

Cannot infer Template Literal Type containing ${number} / ${string} #43060

kotarella1110 opened this issue Mar 3, 2021 · 2 comments · Fixed by #43361
Assignees
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@kotarella1110
Copy link

kotarella1110 commented Mar 3, 2021

Bug Report

Cannot infer Template Literal Type containing ${number} / ${string}

🔎 Search Terms

  • Template Literal Types number infer
  • Template Literal Types string infer

🕗 Version & Regression Information

  • This is the behavior of version 4.1 or higher.

⏯ Playground Link

Playground link with relevant code

💻 Code

// ${number} issue
type A<T> = T extends `${infer U}.${infer V}` ? U | V : never
type B = A<`test.1024`>; // This will be "test" | "1024" as expected
type C = A<`test.${number}`>; // Expected "test" | `${number}` or "test" | number but this will be never
type D<T> = T extends `${infer U}.${number}` ? U : never
type E = D<`test.1024`>; // This will be "test" as expected
type F = D<`test.${number}`>; // Expected "test" but this will be never
// ${string} issue
type G<T> = T extends `${infer U}.${infer V}` ? U | V : never
type H = G<`test.hoge`>; // This will be "test" | "hoge" as expected
type I = G<`test.${string}`>; // Expected "test" | `${string}` or "test" | string but this will be never
type J<T> = T extends `${infer U}.${string}` ? U : never
type K = J<`test.hoge`>; // This will be "test" as expected
type L = J<`test.${string}`>; // Expected "test" but this will be never

🙁 Actual behavior

  • C type is never.
  • F type is never.
  • I type is never.
  • L type is never.

🙂 Expected behavior

  • Expected C type to be "test" | `${number}` or "test" | number.
  • Expected F type to be "test".
  • Expected I type to be "test" | `${string}` or "test" | string.
  • Expected L type to be "test".
@jcalz
Copy link
Contributor

jcalz commented Mar 22, 2021

I'm sure this goes without saying, but the issue here is not specific to ${number}, but occurs for all "pattern" template literals (as implemented in #40598) like ${string}.

@kotarella1110 kotarella1110 changed the title Cannot infer Template Literal Type containing ${number} Cannot infer Template Literal Type containing ${number} / ${string} Mar 22, 2021
@siefkenj
Copy link

I just came across this issue when trying to type a REST API. I made a type using templates that could successfully determine the appropriate return type of get("/sessions/43/posts"), however, my API is most commonly called as get(`/sessions/${sessionId}/posts` as const), which TypeScript failed to recognize.

For my purposes, I would be happy if there were some way to tell TypeScript to interpret a template string as a string literal. E.g., `abc${x}` === "abc${x}".

Some confusion I have with the current implementation is that type type of a const template seems to be inaccessible.

When doing

const x = 4.5;
const y = `${x}abc` as const

y will match against "4.5abc". However, in

const x:number = 4.5;
const y = `${x}abc` as const

will not match against anything more specific than string (correct me if I'm wrong...), and yet TypeScript will show the type as `${number}abc`.

Perhaps tagged-template syntax could be used to specify a template as abstract or weakly inferred?

For example

type A<T> = T extends `${infer U}.${infer V}` ? U | V : never
type B<T> = T extends weak`${infer U}.${infer V}` ? U | V : never

const x = 4.5
const y:number = 4.5
type X = A<`${typeof x}.abc`>  // "4" | "5.abc"
type Y = A<`${typeof y}.abc`>  // never
type WeakX = B<`${typeof x}.abc`>  // 4.5 | "abc"  OR  `${4.5}` | "abc"
type WeakY = B<`${typeof y}.abc`>  // number | "abc"  OR  `${number}` | "abc"
type WeakXMovedDecimal = B<`${typeof x}a.bc`>  // "4.5a" | "bc"  OR  `${4.5}a` | "bc"
type WeakYMovedDecimal = B<`${typeof y}a.bc`>  // string | "bc"  OR  `${number}a` | "bc"

const z1:`${typeof x}abc` = `${x}abc`  // Okay
const z2:`${typeof y}abc` = `${y}abc`  // Fails typechecking
const z3:weak`${typeof x}abc` = `${x}abc` // Okay
const z4:weak`${typeof y}abc` = `${y}abc` // Okay

I'm not sure if these rules would be consistent, but the basic idea would be to not expand const types to strings and allow weak template types to match substitutions directly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants