TypeScript Version: 3.6.3
Search Terms:
Code
function pipeWith<A, B>(a: A, ab: (this: void, a: A) => B): B;
type Predicate<A> = (a: A) => boolean;
declare const filter: <A>(predicate: Predicate<A>) => (fa: A) => A;
declare function isString(candidate: any): candidate is string;
declare const r: 'foo';
const r2 = pipeWith(r, filter(isString)); // expected type "foo", actual any
const r3 = pipeWith(r, filter(v => isString(v))); // expected and actual type "foo"
Expected behavior:
Actual behavior:
Playground Link:
Related Issues:
The missing signature:
declare function isString(candidate: any): candidate is string;
Thanks @karol-majewski, updated my description.
This is not really related to pipe specifically. For example:
const a: (x: "foo") => "foo" = filter(isString); // infers A as any
const b: (x: "foo") => "foo" = filter(v => isString(v)); // infers A as "foo"
The problem is that in the first case there is not floating argument lacking a type annotation, so any type inference is done by directly unifying Predicate<A> and typeof isString, which results in the unification of A with any.
In the second case the v parameter is free and subject to contextual typing which comes from the annotation in my example, or the first argument in your example. The contextual type of v is "foo" which then unifies Predicate<A> with (v: "foo") => boolean, and A with "foo".
TLDR: eta expansion of a function introduces "wiggle" room that lets contextual typing make inferences. Without the exapansion you're committing to inferences obtained from the declared type of isString.
This is similar to why in the following case:
const a1 = isString;
const b1 = v => isString(v);
The type of a1 is inferred correctly, but b1 gets an implicit any error.
I guess the argument in the original example might be that when collecting inferences for A which are "foo" and any, it should try and pick the more precise type if possible. I think that would give the expected results here, but I'm not sure how that would play out in general.
@jack-williams Thanks for your explanation! Should we label this as a suggestion?
Worth noting that a kinda gross workaround exists 馃憖
Edit: actually, the type parameter doesn鈥檛 even have to be _used_. (I was surprised that T | unknown was meaningful to inference鈥攖urns out it鈥檚 not, which is a relief.) Just making both functions generic is the key. Which makes me think this is probably not a separate issue from #30727.
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Most helpful comment
Worth noting that a kinda gross workaround exists 馃憖
Edit: actually, the type parameter doesn鈥檛 even have to be _used_. (I was surprised that
T | unknownwas meaningful to inference鈥攖urns out it鈥檚 not, which is a relief.) Just making both functions generic is the key. Which makes me think this is probably not a separate issue from #30727.