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

ReturnType<> of function intersection inconsistent. #31601

Closed
AnyhowStep opened this issue May 26, 2019 · 7 comments
Closed

ReturnType<> of function intersection inconsistent. #31601

AnyhowStep opened this issue May 26, 2019 · 7 comments
Labels
Duplicate An existing issue was already created

Comments

@AnyhowStep
Copy link
Contributor

AnyhowStep commented May 26, 2019

TypeScript Version: TS Playground version (3.4.1), 3.5.0-dev.20190523

Search Terms: ReturnType, function, intersection

Code

declare const f: & (() => { x: 4 }) & (() => { y: 3 });
//OK
//z is of type {x:4}
const z = f();
//OK
const hasX: { x: 4 } = z;
//Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
const hasY: { y: 3 } = z;

/**
 * Expected:
 * OK
 * z is of type {x:4}
 * 
 * Actual:
 * Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
 * z is of type {y:3}
 */
const z2: ReturnType<typeof f> = f();
/**
 * Expected: OK
 * Actual: Property 'x' is missing in type '{ y: 3; }' but required in type '{ x: 4; }'.
*/
const hasX2: { x: 4 } = z2;
/**
 * Expected: Property 'y' is missing in type '{ x: 4; }' but required in type '{ y: 3; }'.
 * Actual: OK
*/
const hasY2: { y: 3 } = z2;

Expected behavior:

Since function intersections are implemented as overloads,

always return the first return type.

Actual behavior:

Inconsistent return type.

Playground Link: Playground

Related Issues:

@jack-williams
Copy link
Collaborator

I think this is working as intended. Conditional type inference is done from the last overload not the first:

#21496:

When inferring from a type with multiple call signatures (such as the type of an overloaded function), inferences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not possible to perform overload resolution based on a list of argument types (this would require us to support typeof for arbitrary expressions, as suggested in #6606, or something similar).

@AnyhowStep
Copy link
Contributor Author

Hmmm,

If the intention is to get the "most permissive catch-all case", wouldn't using the union of return types of each individual overload be better?

I'm pretty sure I've written overloads where each overload returned different types, and the last one wasn't a "permissive catch-all". Especially for generic functions, where allowing such an overload would makes type checks useless, because the result would be something like unknown or unknown[].


I guess I'll just treat this as a gotcha' and be careful with how I structure overloads from now on

@fatcerberus
Copy link

I’m assuming by the “last overload”, it means the implementation signature, which is the most permissive case by definition.

@AnyhowStep
Copy link
Contributor Author

AnyhowStep commented May 27, 2019

I just tested it,

function foo(x : 1): number;
function foo(x : 2): string;
function foo(x : 1|2): number | string {
    throw new Error("Not implemented");
}

//This is `string`
type r = ReturnType<typeof foo>;

type ReturnType_1<T> = (
    T extends (x: 1) => infer R ?
    R :
    never
);

/*
    Kind of sad this is `never` and not `number`.
    If we really are talking overloads here,
    then shouldn't ReturnType_1<> try to find the
    overload that matches the type `(x : 1) => any`?
*/
//never
type rt_1 = ReturnType_1<typeof foo>;

type ReturnType_2<T> = (
    T extends (x: 2) => infer R ?
    R :
    never
);

//string
type rt_2 = ReturnType_2<typeof foo>;

Playground

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label May 28, 2019
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@AnyhowStep
Copy link
Contributor Author

Wait, what about this?

#31601 (comment)

Surely it's worth considering =/

It would be nice if ReturnType_1<> was number and not never.
Is there a reason to not make ReturnType_1<> return number?

@AnyhowStep
Copy link
Contributor Author

I randomly came across this,
image

It made me think of how TS implements function overloads using intersection types.
It made me chuckle.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

5 participants