Repository navigation
[Regression] ReturnType<> Inference works on 3.3.3, breaks on 3.4.1, 3.5.1 #31814
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.4.1, 3.5.1
Search Terms: regression, ReturnType, parameter, inference
Code
interface Mapper<HandledInputT, OutputT> {
(name : string, mixed : HandledInputT) : OutputT,
}
export type IsPipeable<
FromF extends Mapper<any, any>,
ToF extends Mapper<any, any>
> = (
ToF extends Mapper<infer HandledInputT, any> ?
(
ReturnType<FromF> extends HandledInputT ?
true :
false
) :
false
);
export type AssertPipeable<
FromF extends Mapper<any, any>,
ToF extends Mapper<any, any>
> = (
IsPipeable<FromF, ToF> extends true ?
ToF :
[ReturnType<FromF>, "is not pipeable"]
);
declare function pipe<
F0 extends Mapper<unknown, any>,
F1 extends Mapper<any, any>
> (
f0 : F0,
f1 : AssertPipeable<F0, F1>
): void;
declare function literal<ArrT extends (string | number | boolean | bigint | null | undefined)[]>(
...arr: ArrT
): (
Mapper<unknown, ArrT[Extract<keyof ArrT, number>]>
);
declare const b: Mapper<"0" | "1" | 0 | 1 | "false" | "true", boolean>
//TS suddenly thinks the first arg is Mapper<unknown, any>.
//It is actually Mapper<unknown, "0" | "1" | 0 | 1 | "false" | "true">.
pipe(
//TS knows this is Mapper<unknown, "0" | "1" | 0 | 1 | "false" | "true">
literal("0", "1", 0, 1, "false", "true"),
/*
Expected: works.
Actual:
Argument of type 'Mapper<0 | "0" | "1" | 1 | "false" | "true", boolean>'
is not assignable to parameter of type '[any, "is not pipeable"]'.
Succeeds with 3.3.3,
Fails with 3.4.1,
Fails with 3.5.1
*/
b
);
const a = literal("0", "1", 0, 1, "false", "true");
//TS now knows the first arg is Mapper<unknown, "0" | "1" | 0 | 1 | "false" | "true">
pipe(
a,
/*
This works.
`a` is literally the same type as
`literal("0", "1", 0, 1, "false", "true")`.
Why would the first pipe() call fail,
but the second one succeed?
Succeeds with 3.3.3,
Succeeds with 3.4.1,
Succeeds with 3.5.1
*/
b
);Expected behavior:
The below should work,
pipe(
literal("0", "1", 0, 1, "false", "true"),
b
);Actual behavior:
It used to work but does not work now.
Playground Link: Playground
Related Issues:
I feel like it is related to #29133 somehow. Because I have ReturnType<> being used in a parameter again. But it's somewhat different this time in that the first argument is not an anonymous function "literal" I created. It's a function given by another function.
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 Jun 7, 2019
This is a simplified repro. My actual use case is more complicated in that
IsPipeable<>has way more rules. It's not as simple as "Output ofAmust match input ofB" becausepipe()may do other things behind the scenes to make the data acceptable toB, orBmay have additional properties that make it accept a larger range of inputs than its parameters may suggest.So, the repro might not make much sense here =x
RyanCavanaugh commentedon Jun 25, 2019
Anders Hejlsberg (@ahejlsberg) looks like another instance of contextual return type inference messing things up, though it's not completely obvious how in this example
ahejlsberg commentedon Jul 1, 2019
This is an effect of #30215 and I'm not sure there's much we can do to change it. With #30215 we sometimes defer processing of arguments in order to make better inferences (but we didn't before 3.4.1). This is captured here in my comment in
resolveCallExpression:In the
pipe(literal(...), b)example, we defer processing of theliteral(...)call for the reasons explained above. This means we initially make no inferences forF0, which in turn means theAssertPipeableconditional type isn't happy. Basically, the way thepipefunction is declared, type inference only succeeds when inferences are made forF0andF1in the same phase. Things work fine whenAssertPipeable<F0, F1>is replaced with justF1because we can then make independent inferences forF0andF1in different phases of the type inference process.