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

Generics Extending Distributed Conditionals Cannot be Narrowed #45174

Closed
anthonyjoeseph opened this issue Jul 24, 2021 · 4 comments
Closed

Generics Extending Distributed Conditionals Cannot be Narrowed #45174

anthonyjoeseph opened this issue Jul 24, 2021 · 4 comments

Comments

@anthonyjoeseph
Copy link

anthonyjoeseph commented Jul 24, 2021

Bug Report

🔎 Search Terms

Generics, Union, Narrow, Narrowing, Distributed, Conditional, Inference

🕗 Version & Regression Information

TS v4.3.5 (and nightly build as of 7/23/2021)

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about conditional types & inference

⏯ Playground Link

Playground link with relevant code

💻 Code

type Tuplify<A> = A extends unknown ? [A] : never
type test = Tuplify<'a' | 'b'> // test = ['a'] | ['b']

declare const inferFunc: <A, R extends Tuplify<A>>(a: A, b: R) => R
declare const testval: 'a' | 'b'
const retval: ['a'] = inferFunc(testval, ['a']) // this should work (?)

🙁 Actual behavior

const retval: ['a'] | ['b']

🙂 Expected behavior

const retval: ['a']

I expected R to be inferred as a subtype of Tuplify<A>, since that's how parameterized union types work as of #13995 (I think)

Please forgive me if this is actually expected behavior or has already been reported

@RyanCavanaugh
Copy link
Member

Constraints aren't inference locations, so there isn't a sufficient hint at b to infer a literal type for the array.

You could write it this way, which works as expected:

type Tuplify<A> = A extends unknown ? [A] : never
type test = Tuplify<'a' | 'b'>;
declare function inferFunc<A, R>(a: A, b: R & Tuplify<A>): R;
declare const testval: 'a' | 'b';
const retval: ["a"] = inferFunc(testval, ['a']);

@anthonyjoeseph
Copy link
Author

anthonyjoeseph commented Jul 26, 2021

@RyanCavanaugh Thank you for such a speedy response!

I'm not sure I understand - different constraints give the correct inference:

declare const inferFunc2: <R extends ['a'] | ['b']>(b: R) => R
const retval2: ['a'] = inferFunc2(['a']) // works fine

declare const inferFunc3: <A extends 'a' | 'b' | 'c', R extends Tuplify<A>>(a: A, b: R) => R
const retval2: ['a'] = inferFunc3(testval, ['a']) // works fine

I think this is somehow related to the Tuplify type specifically, or maybe distributed conditionals (there are other counter-examples in the linked playground)

You could write it this way

I'm realizing that my acceptance criteria were poorly outlined - it looks like your inferFunc actually returns [string], which technically matches ['a'] but doesn't solve my problem. I'm looking for something that would return exactly ['a'] and give autocomplete on b

declare function inferFunc<A, R>(a: A, b: R & Tuplify<A>): R;
const retval = inferFunc(testval, ['a']); // inferred as [string]

@anthonyjoeseph
Copy link
Author

I've realized that this is a string literal inference issue:

type Tuplify<A> = A extends unknown ? readonly [A] : never
declare const inferFunc: <A extends string, R extends Tuplify<A>>(a: A, b: R) => R
declare const testval: 'a' | 'b'
const retval = inferFunc(testval, ['a'] as const) // correctly inferred as `readonly ['a']`

Thanks for your guidance!

@anthonyjoeseph
Copy link
Author

In case anyone else is reading, this works too and is a little simpler:

type Tuplify<A> = A extends unknown ? [A] : never
declare const inferFunc: <A extends string, R extends Tuplify<A>>(a: A, b: [...R]) => R
declare const testval: 'a' | 'b'
const retval = inferFunc(testval, ['a']) // correctly inferred as `['a']`

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

No branches or pull requests

2 participants