Repository navigation
ReturnType<F> in parameter of generic function breaks inference when F has one implicitly typed param #33042
Copy link
Copy link
Closed
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Description
TypeScript Version: 3.5.1
Search Terms:
- ReturnType
- Implicit type annotation
- Generic function
- Parameter
Code
This is me opening a new issue for #29133
I have three separate code snippets, please bear with me =(
//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;
declare function noReturnTypeInParam<
F extends Callback<3|5>
>(f: F): ReturnType<F>
//Expected: const noArg: 3
//Actual : const noArg: 3
const noArg = noReturnTypeInParam(
//Return type is 3
() => 3
);
//Expected: const withArg: 3
//Actual : const withArg: 3
const withArg = noReturnTypeInParam(
//Return type is 3
t => 3
);//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;
//Forgive the contrived example
declare function withReturnTypeInParam<
F extends Callback<3|5>,
>(
f: F & (
ReturnType<F> extends 3 ?
unknown :
[
"Only 3 allowed, received",
ReturnType<F>
]
)
): ReturnType<F>
//Expected: const noArg: 3
//Actual : const noArg: 3
const noArg = withReturnTypeInParam(
//Return type is 3
() => 3
);
//Expected: const withArg: 3
//Actual : const withArg: 3|5
const withArg = withReturnTypeInParam(
/*
Expected: No error
Actual :
Argument of type
'(t: any) => 3'
is not assignable to parameter of type
'Callback<3 | 5> & ["Only 3 allowed, received", 3 | 5]'.
*/
//Return type is `3`
//function(t: any): 3
t => 3
);
//Expected: const withArgAndExplicitReturnTypeAnnotation: 3
//Actual : const withArgAndExplicitReturnTypeAnnotation: 3|5
const withArgAndExplicitReturnTypeAnnotation = withReturnTypeInParam(
/*
Expected: No error
Actual :
Argument of type
'(t: any) => 3'
is not assignable to parameter of type
'Callback<3 | 5> & ["Only 3 allowed, received", 3 | 5]'.
*/
//We *force* the return type to be 3
//Return type is `3`
//function(t: any): 3
(t) : 3 => (3 as 3)
);
//Expected: const withExplicitlyTypedArg: 3
//Actual : const withExplicitlyTypedArg: 3
const withExplicitlyTypedArg = withReturnTypeInParam(
/*
Expected: No error
Actual : No error
*/
//Return type is `3`
//function(t: any): 3
(t : any) => 3
);//Innocent enough.
//Returns a T
type Callback<T> = (...args : any[]) => T;
//Forgive the contrived example
declare function returnTypeAsTypeParam<
T extends 3|5,
>(
f: Callback<
& T
& (
T extends 3 ?
unknown :
[
"Only 3 allowed, received",
T
]
)
>
): T
//Expected: const noArg: 3
//Actual : const noArg: 3
const noArg = returnTypeAsTypeParam(
//Return type is 3
() => 3
);
//Expected: const withArg: 3
//Actual : const withArg: 3
const withArg = returnTypeAsTypeParam(
/*
Expected: No error
Actual : No error
*/
//Return type is `3`
//function(t: any): 3
t => 3
);
//Expected: const withArgAndExplicitReturnTypeAnnotation: 3
//Actual : const withArgAndExplicitReturnTypeAnnotation: 3
const withArgAndExplicitReturnTypeAnnotation = returnTypeAsTypeParam(
/*
Expected: No error
Actual : No error
*/
//We *force* the return type to be 3
//Return type is `3`
//function(t: any): 3
(t) : 3 => (3 as 3)
);Related Issues:
I hope this is concise enough ><
I've commented all the relevant locations with what is expected and what actually happens.
Snippets 2 (withReturnTypeInParam) and 3 (returnTypeAsTypeParam) are expected to have the same behaviour because they mean the same thing semantically (to me).
They are just expressed differently syntactically.
I just have trouble understanding why snippet 2 breaks unless I have an explicit type annotation. I'm 99% sure it has to be a bug.
And I have trouble understanding why snippet 3 is OK
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Working as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
Activity
AnyhowStep commentedon Aug 23, 2019
There's a reason I want to use snippet 2 and not snippet 3,
#32442
For snippet 2,
Playground
For snippet 3,
Playground
The conditional type must be in the return type of
Callback<>.We want
Callback<T & /*myConditionalType*/>.We do not want
Callback<T> & /*myConditionalType*/because I have seen it break type inference (especially over here: #32540 (comment))ahejlsberg commentedon Sep 1, 2019
This is working as intended. In the call
withReturnTypeInParam(t => 3), thetparameter is contextually typed. This means we defer inferences from thet => 3argument and first infer from other arguments such that we can produce the best possible contextual type. However, there are no other arguments, so we make no inferences forF. Then, the use ofReturnType<F>in the contextual parameter type causes us to apply the inferences we've made so far, and since there aren't any we default to the constraint3 | 5. That subsequently produces an incompatible parameter type.The core issue here is that you can't simultaneously infer from an argument and depend on the argument.
AnyhowStep commentedon Sep 1, 2019
Doesn't
returnTypeAsTypeParambasically do the same thing aswithReturnTypeInParam, but in a different way?What's the reason
returnTypeAsTypeParamworks? Can I rely on it working in the future as well?Wait-
I'm guessing it's because it doesn't have to infer the type of
tforreturnTypeAsTypeParam. So, we don't run into that problem.That makes sense.
AnyhowStep commentedon Sep 1, 2019
I guess I'll close this issue.
I have seen cases where TS is able to infer the type of the argument, depend on it, and also correctly infer the
ReturnType<>But it probably only worked in those cases out of dumb luck, I guess