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

Generic not inferred from contextual types #25293

Closed
OliverJAsh opened this issue Jun 28, 2018 · 12 comments
Closed

Generic not inferred from contextual types #25293

OliverJAsh opened this issue Jun 28, 2018 · 12 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@OliverJAsh
Copy link
Contributor

TypeScript Version: 2.9.2

Search Terms: generic infer contextual type

Code

const baz: (n: number) => number = (
    // param `n` is correctly inferred to be `number` :-)
    n => n
);

const identity = <T>(t: T) => t;

const bar: (n: number) => number = identity(
    // param `n` is inferred as `any`, expected `number` :-(
    x => x
);

Expected behavior: TypeScript should infer the generic T in identity from the contextual type.

Actual behavior: TypeScript infers the generic T in identity as any!

Playground Link:

Related Issues:

@Hotell
Copy link

Hotell commented Jun 28, 2018

hmm I don't think this is valid.

What you are after is following:

// inferred bar 👉 (x: number) => number
const bar = identity((x: number) => x)

by doing

const bar: (n: number) => number = identity(
    x => x
);

you're telling compiler that bar should be type of (n: number) => number, but your are assigning to it identity<any>((x:any): any => x) which is inferred to type (x:any) => any and therefore you got correct error

@OliverJAsh
Copy link
Contributor Author

I thought TypeScript would use bars type annotation as a "contextual type" and use it to infer the generic to identity as (n: number) => number. 🤔

@Hotell
Copy link

Hotell commented Jun 28, 2018

I might be wrong but I don't think so, that would mean that Generics can flow deeply to function calls.

but again explicitly annotating a variable is expressing what type should be on the right side of assignment, not to infer

@ahejlsberg
Copy link
Member

This is working as intended. Function and arrow expression parameters without type annotations are assigned contextual types only when the contextual type is known (before instantiation) to be a function type. In your example the contextual type is simply T, so we infer from the argument (x: any) => any to T. Had the contextual type been something like (x: T) => T, we would fix the inferences we've made so far for T (which would include inferences from the return type) and then assign that type to the x parameter.

@ahejlsberg ahejlsberg added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 28, 2018
@Hotell
Copy link

Hotell commented Jun 28, 2018

yup what @ahejlsberg and (myself partially) said :)

@OliverJAsh
Copy link
Contributor Author

Thanks for the explanation @ahejlsberg and @Hotell.

To justify why I would like this with a real world use case:

const numberPromise = Promise.resolve(1);

numberPromise.then(
  // x is inferred as number, no type annotation required
  x => x + 1
);

// Later, when I need to expand on this function, if I use `pipe` I'm
// forced to add a type annotation to the parameter when it wasn't
// needed before.

numberPromise.then(pipe(
  // x is inferred as any, requiring me to add a type
  // annotation to the param that wasn't needed before
  x => x + 1,
  x => x * 2,
));

numberPromise.then(pipe(
  (x: number) => x + 1,
  x => x * 2,
));

@ahejlsberg
Copy link
Member

@OliverJAsh How is pipe defined?

@OliverJAsh
Copy link
Contributor Author

@Hotell
Copy link

Hotell commented Jun 28, 2018

Hmm interesting, those definitions might be wrong ? this works

declare const pipe: <A, R1, R2>(a: (arg: A) => R1, b: (args: R1) => R2) => (resolved: A) => R2

// $Expect Promise<number>
const numberPromise = Promise.resolve(1)

numberPromise.then(
  // $Expect (x:number)=>number
  (x) => x + 1
)

// $Expect Promise<number>
const result = numberPromise.then(
  pipe(
    // $Expect (x:number)=>number
    (x) => x + 1,
    // $Expect (x:number)=>number
    (x) => x * 2
  )
)

@ahejlsberg
Copy link
Member

The issue appears to be that the flow wall of overloads starts with a series of declarations that have zero parameters for the first function. In the process of matching against those, we assign type any to the x parameter of the lambda (because there is no matching contextual parameter). However, once we've made that assignment it sticks and for reasons of consistency we can't undo it. It all works if you eliminate all but the one-parameter-first-function declarations of flow. Not sure there is much we can do to change this.

@OliverJAsh
Copy link
Contributor Author

OliverJAsh commented Jun 29, 2018

Ahh, that's good to know, so we can at least workaround it by patching the flow types to remove that overload (which we don't use).

I realised my original example was off the mark. Here is a correct example which does show the actual problem, as you described:

// https://github.com/DefinitelyTyped/DefinitelyTyped/blob/64104e9760065bd498a3d62d98bb05ea26500dc2/types/lodash/common/util.d.ts#L209
declare const pipe: {
    // 0-argument first function
    <R1, R2>(f1: () => R1, f2: (a: R1) => R2): () => R2;
    // 1-argument first function
    <A1, R1, R2>(f1: (a1: A1) => R1, f2: (a: R1) => R2): (a1: A1) => R2;
}

const bar: (n: number) => number = pipe(
  // param `n` is inferred as `any`, expected `number` :-(
  x => x + 1,
  x => x * 2
);

If you remove the first overload, the param n is inferred as expected.

@OliverJAsh
Copy link
Contributor Author

I guess this can be labelled as "won't fix"?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

3 participants