Thanks to visit codestin.com
Credit goes to github.com

Skip to content

TypeScript doesn't reverse-infer generic constraints from discriminated unions #61661

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

Open
gabe-cc opened this issue May 5, 2025 · 4 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@gabe-cc
Copy link

gabe-cc commented May 5, 2025

πŸ”Ž Search Terms

narrowing, discriminated types, extends, generic, constraints

πŸ•— Version & Regression Information

  • This has never worked
  • The error changed between versions 3 and 4
  • There is no relevant entry in the FAQ when searching for "discriminated union" or "narrowing"

⏯ Playground Link

https://www.typescriptlang.org/play/?ts=4.0.5#code/KYDwDg9gTgLgBDAnmYcAiiB2AVZwA82AfHALwBQAPnABTZygzCYAmAznGzFAJaYDmcAPxwA3uTgIAhoIBccAORdeAhXADcEuAH1tMGXHnK+ggGRx6mgL6G4mYADdgUAJRVa9Rs3Z2ArgFsAI2dhMS19OUVMAOCoNU1JXQjbaKCQ80tyG3l7J1dycgAzX0wAYxgeCEw4FgD-RGxCIhoQWwwcPCaXMMkeQtoQADpk0lHFY1Vu8UlJUqquHVt6UkUAGwhV+K1JKGAYXyhqhXXNjS0rLT6B4YNRlYVU2IVu3f3DxQAWACYtnb2D6qtaxAA

πŸ’» Code

export type DynType<T> =
| (T extends string ? {
  tag : 'string' ;
  __tag : string & T ;
} : never)
| (T extends number ? {
  tag : 'number' ;
  __tag : number & T ;
} : never)

function dummyT<T>(x : DynType<T>) {
  if (x.tag === 'string') {
    const _ : T = 'lol' ;
    return 'lol' ;
  }
  if (x.tag === 'number') return '42' ;
  return x ;
}

πŸ™ Actual behavior

There is a type error on const _:

Type 'string' is not assignable to type 'T'. 'T' could be instantiated with an arbitrary type which could be unrelated to 'string'.

There is no type error on return '42'.

πŸ™‚ Expected behavior

There should be no type error on the const _. T is indeed of type string in that branch, and can not be never, as it has a .tag property.

There should be a type error on return '42'. T is of type number in that branch, which is incompatible with the type "42".

Additional information about the issue

I know it's a complicated case, but it would let me express pseudo-GADTs powerful enough to do some type of Zod/Runtype-like dynamic types that I can dynamically pattern-match over. That way, I could do well-typed svelte component generation for forms or displays, among other things.

Cheers!

@MartinJohns
Copy link
Contributor

This is working as intended.

T can be typed "foo", and "lol" is not assignable to "foo".

@jcalz
Copy link
Contributor

jcalz commented May 5, 2025

And why should there be a type error on return '42'? I don't see a return type annotation on dummyT(), so how could any return value be in error?

@gabe-cc
Copy link
Author

gabe-cc commented May 6, 2025

@jcalz
I am sorry, in my local tests, I did it with the T return annotation on dummyT.

In which case, you still get the error from const _ on the two returns too.
Should I edit the issue directly or open a new one?

@MartinJohns
You get the same error if you put 'lol' as string, the problem is that it is not recognising T as string.

@MartinJohns
Copy link
Contributor

MartinJohns commented May 6, 2025

You get the same error if you put 'lol' as string, the problem is that it is not recognising T as string.

Adding as string does not help, because T can be a more specific type than string. T could be the literal type "foo", in which case assigning any string is not valid.

To give an example, this is your issue: T is typed "bar", but a random string "any string" is tried to be passed:

function foo<T>(x: T) {}

foo<"bar">("any string")

Do you think that narrowing x will cause the type T to narrow too? If so, that's not the case. Narrowing x will only narrow x. Narrowing something typed DynType<T> will not narrow T. That's not how this works, the types exist independently - DynType<T> has no effect on the type T.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label May 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

4 participants