Skip to content

Unable to infer generic for a Pick / indexed-access type from a union of compatible types #16756

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

Closed
fongandrew opened this issue Jun 27, 2017 · 2 comments
Labels
Needs Investigation This issue needs a team member to investigate its status.

Comments

@fongandrew
Copy link

TypeScript Version: 2.3.4
Code

interface Test {
  cat: number;
  dog: number;
}

declare function doTest<T extends keyof Test>(x: Pick<Test, T>): void;

declare var cat: { cat: number };
doTest(cat);

declare var dog: { dog: number };
doTest(dog);

declare var catOrDog: { cat: number } | { dog: number };
doTest(catOrDog); // Error

Expected behavior: Last line should not error

Actual behavior: Property 'dog' is missing in type '{ cat: number; }'.

I understand this might be working as intended insofar that, in the last invocation of doTest, it's unclear what generic T to infer for the function. If T is 'cat'|'dog', then Pick<Test, 'cat'|'dog'> would require both cat and dog, which the union type for catOrDog does not match.

On the other hand, it seems very odd for a function to accept both an argument of type { cat: number } and type { dog: number } but not type { cat: number }|{ dog: number }.

As a work-around, it is possible to type doTest with additional generics, like so:

declare function doTest<T extends keyof CatAndDog, V extends keyof CatAndDog>(
  x: Pick<CatAndDog, T>|Pick<CatAndDog, V>
): void;
doTest<"cat", "dog">(catOrDog); // No eror

But this feels like a less than ideal solution.

@jcalz
Copy link
Contributor

jcalz commented Jun 27, 2017

I feel like I've seen this sort of thing crop up for different people and I wonder if there's a known issue that tracks the general case of reduction of unions/intersections of overloaded/generic functions.

You want TypeScript to notice that doTest is the intersection of a function from Pick<Test,'cat'> to void and a function from Pick<Test,'dog'> to void, and treat it as a function from Pick<Test,'cat'> | Pick<Test,'dog'> to void. You can kind of force TypeScript to notice this without touching your declaration of doTest declaration, in the following ugly way:

function intersectFunction<A1, R1, A2, R2>(f: ((a: A1) => R1) & ((a: A2) => R2)): (a: A1|A2) => R1|R2 {
  return f;
}
var doTestCatOrDog = intersectFunction<Pick<Test, 'cat'>, void, Pick<Test, 'dog'>, void>(doTest); 
doTestCatOrDog(catOrDog); // success!

Similarly, there's the flip side where one wants TypeScript to notice that you have a function which is the union of a function from A1 to R1 and a function from A2 to R2, and you want to be able to give it a parameter of type A1 & A2 and get a value of type R1 | R2:

var g = Math.random() < 0.5 ? ((x:string) => 1) : ((x:'dog') => 'dog')
g('dog'); //error

function uniteFunction<A1, R1, A2, R2>(f: ((a: A1) => R1) | ((a: A2) => R2)): (a: A1&A2) => R1|R2 {
  return f;
}

var gDog = uniteFunction<string, number, 'dog', string>(g);
gDog('dog'); // success!

It's probably a difficult problem to solve properly and lots of creeping unsoundness happens. But I wonder if it's tracked anywhere. See #16644 and #16716 for recent similar examples.

@mhegazy mhegazy added the Needs Investigation This issue needs a team member to investigate its status. label Aug 29, 2017
@fongandrew
Copy link
Author

Closing. I think this is a duplicate of #14107.

@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs Investigation This issue needs a team member to investigate its status.
Projects
None yet
Development

No branches or pull requests

3 participants