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

never extends ${infer P} yield a different result than never extends ${infer P}/ #50215

Open
tylim88 opened this issue Aug 8, 2022 · 7 comments
Labels
Bug A bug in TypeScript
Milestone

Comments

@tylim88
Copy link

tylim88 commented Aug 8, 2022

Bug Report

πŸ”Ž Search Terms

never extends ${infer P} yield different result than never extends ${infer P}/

πŸ•— Version & Regression Information

TS 4.7

type is not what expected

⏯ Playground Link

playground

πŸ’» Code

type a = never extends `${infer P}` ? P : 1  // never
//   ^?
type b = never extends `${infer P}/` ? P : 2 // string
//   ^?

type a1 = never extends `${infer P extends string}` ? P : 1 // never
//   ^?
type b1 = never extends `${infer P extends string}/` ? P : 2 // string
//   ^?

type a2 = never extends `${infer P extends number}` ? P : 1 // never
//   ^?
type b2 = never extends `${infer P extends number}/` ? P : 2 // number
//   ^?

type a3 = never extends `${infer P extends null}` ? P : 1 // never
//   ^?
type b3 = never extends `${infer P extends null}/` ? P : 2 // null
//   ^?

type a4 = never extends `${infer P extends undefined}` ? P : 1 // never
//   ^?
type b4 = never extends `${infer P extends undefined}/` ? P : 2 // undefined
//   ^?

type a5 = never extends `${infer P extends boolean}` ? P : 1 // never
//   ^?
type b5 = never extends `${infer P extends boolean}/` ? P : 2 // boolean
//   ^?

type a6 = never extends `${infer P extends bigint}` ? P : 1 // never
//   ^?
type b6 = never extends `${infer P extends bigint}/` ? P : 2 // bigint
//   ^?

πŸ™ Actual behavior

  1. never extends ${infer P}/ yield the same result as never extends ${infer P}
  2. never extends ${infer P extends X}/ yield X but never extends ${infer P extends X} always yield never

πŸ™‚ Expected behavior

  1. never extends ${infer P}/ should yield never like never extends ${infer P}
  2. never extends ${infer P extends X}/ yield never like never extends ${infer P extends X}

are these intended behaviors? kind of unintuitive

@RyanCavanaugh
Copy link
Member

This would seem to follow from the PR text

#40336

Type inference supports inferring from a string literal type to a template literal type. For inference to succeed the starting and ending literal character spans (if any) of the target must exactly match the starting and ending spans of the source. Inference proceeds by matching each placeholder to a substring in the source from left to right: A placeholder followed by a literal character span is matched by inferring zero or more characters from the source until the first occurrence of that literal character span in the source. A placeholder immediately followed by another placeholder is matched by inferring a single character from the source.

I don't really see a defect here; zero-length strings are weird to reason about and you could make an argument for changing either of these behaviors. Without a motivating use case to drive which one, changing it for the sake of changing it is just risk without benefit.

@RyanCavanaugh RyanCavanaugh added the Not a Defect This behavior is one of several equally-correct options label Aug 8, 2022
@jcalz
Copy link
Contributor

jcalz commented Aug 8, 2022

What zero-length strings are we looking at here? Are you saying that we could interpret `${infer P}` as having a zero-length string between `${infer P} and `?

Would it be fair to say that one should have no particular expectations about what will come out of a pathological case like never extends F<infer T> ? T : never in general?

@tylim88
Copy link
Author

tylim88 commented Aug 8, 2022

update:
written some silly stuff, dont bother

@RyanCavanaugh
Copy link
Member

it is simply counter intuitive, because with never, we always expecting negative case

What? never is defined to be the subtype of all types.

type M = never extends string ? true : false; // true

@tylim88
Copy link
Author

tylim88 commented Aug 8, 2022

@RyanCavanaugh lmao, sorry, had a brain fart

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript and removed Not a Defect This behavior is one of several equally-correct options labels Aug 8, 2022
@RyanCavanaugh RyanCavanaugh added this to the Backlog milestone Aug 8, 2022
@RyanCavanaugh
Copy link
Member

RyanCavanaugh commented Aug 8, 2022

Thinking about it more, I think the correct behavior is actually fairly straightforward when comparing this to object types (demonstrated below). Probably what's going wrong here is that we have an assumption somewhere that for any T legal to be there in the first place, ${T} === T. That's true for string subtypes, but isn't true for never.

// A = true
type A = never extends { x: string } ? true : false;
// B = unknown
type B = never extends { x: infer T } ? T : "no";
// C = unknown
type C = never extends [infer T, unknown] ? T : "no";
// D = unknown
type D = never extends [unknown, infer T, unknown] ? T : "no";
// E = string
type E = never extends `${infer X}#` ? X : "no";
// F = never, ?. Should be 'string'
type F = never extends `${infer X}` ? X : "no";

@tylim88
Copy link
Author

tylim88 commented Aug 8, 2022

@RyanCavanaugh

sorry for that silly post, it is morning and my brain still being dumb

ok long thing short

I think it is normal to expect the P from never extends ${infer P}/? P : 1 and never extends ${infer P extends X}/? P : 1 is never

update:
judging from the last post, seem like it is going to be P from never extends ${infer P}? P : 1 is string and never extends ${infer P extends X}? P : 1 is X instead

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants