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

pipe loses generics (2) #35167

Closed
OliverJAsh opened this issue Nov 18, 2019 · 6 comments
Closed

pipe loses generics (2) #35167

OliverJAsh opened this issue Nov 18, 2019 · 6 comments
Labels
Duplicate An existing issue was already created

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Nov 18, 2019

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:

#30727

@karol-majewski
Copy link

The missing signature:

declare function isString(candidate: any): candidate is string;

Full TypeScript Playground

@OliverJAsh
Copy link
Contributor Author

Thanks @karol-majewski, updated my description.

@jack-williams
Copy link
Collaborator

jack-williams commented Nov 18, 2019

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.

@OliverJAsh
Copy link
Contributor Author

@jack-williams Thanks for your explanation! Should we label this as a suggestion?

@andrewbranch
Copy link
Member

andrewbranch commented Dec 11, 2019

Worth noting that a kinda gross workaround exists 👀


Edit: actually, the type parameter doesn’t even have to be used. (I was surprised that T | unknown was meaningful to inference—turns out it’s 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.

@andrewbranch andrewbranch added the Duplicate An existing issue was already created label Dec 11, 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.

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