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

Failed function argument type inference inside nested object passed as argument #22715

Closed
beshanoe opened this issue Mar 20, 2018 · 9 comments
Closed

Comments

@beshanoe
Copy link

TypeScript Version: 2.7.1

Search Terms: infer

Code

interface IFooOptions<K extends string> {
  name?: string;
  value?: number;
  allFoos?: {[key in K]?: boolean }
  fn?: (allFoos: {[key in K]: string }) => void;
}

class Bar<T> {
  constructor(foos: {[key in keyof T]: IFooOptions<keyof T> }) {
    // ...
  }
}

const bar = new Bar({
  firstFoo: {
    name: "john",
    value: 3,
    allFoos: { // here allFoos infered
      firstFoo: true,
      nonexist: 'yep'
    }
  },
  secondFoo: {
    name: 'sandra',
    fn: allFoos => { // but here allFoos is any though the type of "fn" is infered correctly
      allFoos.nonexist = 2
    }
  }
});

const myFoo: IFooOptions<'one' | 'two'> = {
  name: 'adas',
  fn: allFoos => { //allFoos is infered right
    allFoos.nonexist = 2
  }
}

Expected behavior:

The allFoos argument inside fn field in the new Bar argument should have a type of

{
  firstFoo: string
  secondFoo: string
}

Actual behavior:

allFoos type is implicit any
Also it repoduces without using a class, just with plain generic function

Playground Link:
https://www.typescriptlang.org/play/index.html#src=interface%20IFooOptions%3CK%20extends%20string%3E%20%7B%0A%20%20name%3F%3A%20string%3B%0A%20%20value%3F%3A%20number%3B%0A%20%20allFoos%3F%3A%20%7B%5Bkey%20in%20K%5D%3F%3A%20boolean%20%7D%0A%20%20fn%3F%3A%20(allFoos%3A%20%7B%5Bkey%20in%20K%5D%3A%20string%20%7D)%20%3D%3E%20void%3B%0A%7D%0A%0Aclass%20Bar%3CT%3E%20%7B%0A%20%20constructor(foos%3A%20%7B%5Bkey%20in%20keyof%20T%5D%3A%20IFooOptions%3Ckeyof%20T%3E%20%7D)%20%7B%0A%20%20%20%20%2F%2F%20...%0A%20%20%7D%0A%7D%0A%0Aconst%20bar%20%3D%20new%20Bar(%7B%0A%20%20firstFoo%3A%20%7B%0A%20%20%20%20name%3A%20%22john%22%2C%0A%20%20%20%20value%3A%203%2C%0A%20%20%20%20allFoos%3A%20%7B%20%2F%2F%20here%20allFoos%20infered%0A%20%20%20%20%20%20firstFoo%3A%20true%2C%0A%20%20%20%20%20%20nonexist%3A%20'yep'%0A%20%20%20%20%7D%0A%20%20%7D%2C%0A%20%20secondFoo%3A%20%7B%0A%20%20%20%20name%3A%20'sandra'%2C%0A%20%20%20%20fn%3A%20allFoos%20%3D%3E%20%7B%20%2F%2F%20but%20here%20allFoos%20is%20any%20though%20the%20type%20of%20%22fn%22%20is%20infered%20correctly%0A%20%20%20%20%20%20allFoos.nonexist%20%3D%202%0A%20%20%20%20%7D%0A%20%20%7D%0A%7D)%3B%0A%0Aconst%20myFoo%3A%20IFooOptions%3C'one'%20%7C%20'two'%3E%20%3D%20%7B%0A%20%20name%3A%20'adas'%2C%0A%20%20fn%3A%20allFoos%20%3D%3E%20%7B%20%2F%2FallFoos%20is%20infered%20right%0A%20%20%20%20allFoos.nonexist%20%3D%202%0A%20%20%7D%0A%7D

@RyanCavanaugh
Copy link
Member

@sandersn is this a duplicate of #22362 ?

@ghost
Copy link

ghost commented Mar 22, 2018

This looks like a separate bug. The type parameter is escaping!

declare function f<T>(obj: { [key in keyof T]: { x?: number } }): T;
const t = f({ a: "a" }); // T is of type `T`

@beshanoe To fix the issue for now you should provide an explicit type argument. I'm don't think we should be able to infer T if we only ever see its keys anyway. At the comment at // here allFoos infered, there actually was no type argument inference, but if you hover over it you will still see a type -- that is just telling you the type of that object literal.

@ghost ghost added the Bug A bug in TypeScript label Mar 22, 2018
@beshanoe
Copy link
Author

@andy-ms regarding argument, I was saying about this place:

secondFoo: {
    name: 'sandra',
    fn: allFoos => { // but here allFoos is any though the type of "fn" is infered correctly
      allFoos.nonexist = 2
    }
  }

if you remove error line "nonexistent": "yep", you will see that fn's type is inferred when I hover it, however allFoos argument is any
image

@ghost
Copy link

ghost commented Mar 22, 2018

I was confused by not getting errors (#22790). Now that I look at the original example again, there is a type error at nonexist: 'yep', so this may not actually be a bug.

@ghost ghost added Needs Investigation This issue needs a team member to investigate its status. and removed Bug A bug in TypeScript labels Mar 22, 2018
@beshanoe
Copy link
Author

beshanoe commented Apr 16, 2018

@andy-ms could you please explain your latest statement? Is it a bug? To me it looks like it is :)
I've encountered the same thing again, and it really prevents me from doing good typing in my app.
It's reproduced in 2.8.1 version
Here's is another example:
https://www.typescriptlang.org/play/index.html#src=class%20RouterProps%3CP%3E%20%7B%0D%0A%20%20props%3A%20P%0D%0A%7D%0D%0A%0D%0Aclass%20Component%3CP%3E%20%7B%0D%0A%20%20props%3A%20P%0D%0A%7D%0D%0A%0D%0A%2F%2F%20in%20'resolve'%20field%20I%20want%20to%20pass%20an%20object%20with%20keys%20from%20passed%20component's%20props%20and%20with%20functions%20as%20values%0D%0Ainterface%20IRoute%3CP%20%3D%20any%2C%20RP%20%3D%20any%3E%20%7B%0D%0A%20%20name%3A%20string%0D%0A%20%20component%3A%20Component%3CP%20%26%20RouterProps%3CRP%3E%3E%2C%0D%0A%20%20resolve%3F%3A%20%7B%20%5Bname%20in%20keyof%20P%5D%3F%3A%20(params%3A%20RP)%20%3D%3E%20Promise%3CP%5Bname%5D%3E%20%7D%0D%0A%7D%0D%0A%0D%0Afunction%20route%3CP%2C%20RP%3E(route%3A%20IRoute%3CP%2C%20RP%3E)%20%7B%0D%0A%20%20return%20route%0D%0A%7D%0D%0A%0D%0Aconst%20cmp%20%3D%20new%20Component%3C%7B%20foo%3A%20number%2C%20bar%3A%20string%20%7D%20%26%20RouterProps%3C%7B%20id%3A%20string%20%7D%3E%3E()%0D%0A%0D%0A%0D%0A%2F%2F%20if%20you%20hover%20'resolve'%20or%20'foo'%20object's%20field%2C%20the%20type%20is%20inferred%20correctly%2C%20saying%20that%20'params'%20arg%20is%20of%20type%20%7Bid%3A%20string%7D%0D%0A%2F%2F%20but%20when%20you%20hover%20'params'%2C%20it%20says%20that%20it's%20'any'%0D%0Aroute(%7B%0D%0A%20%20name%3A%20'sample-route'%2C%0D%0A%20%20component%3A%20cmp%2C%0D%0A%20%20resolve%3A%20%7B%0D%0A%20%20%20%20foo%3A%20params%20%3D%3E%20%7B%0D%0A%20%20%20%20%20%20alert(params.nonexistent)%20%2F%2F%20no%20error%20here%2C%20but%20should%20be%0D%0A%20%20%20%20%20%20return%20Promise.resolve(10)%20%2F%2F%20notice%20that%20the%20type%20of%20return%20value%20is%20enforced%2C%20namely%20change%2010%20to%20%22somestring%22%20and%20there%20will%20be%20an%20error%0D%0A%20%20%20%20%7D%0D%0A%20%20%7D%0D%0A%7D)

@ghost
Copy link

ghost commented Apr 16, 2018

@beshanoe Try turning on --noImplicitAny -- no type was inferred for params. It looks like you need to explicitly provide type arguments to route. However, I did file an issue based on this at #23429.

@beshanoe
Copy link
Author

@andy-ms cool thanks, do you think this issue is a "good first issue"? Or it's too complicated? I just want to try to start contributing to TS :)

@ghost
Copy link

ghost commented Apr 16, 2018

That seems like a pretty tough issue to me, since it involves type inference, and contextual typing of lambdas, and mapped types, all together.

@RyanCavanaugh
Copy link
Member

This has been fixed (verified in 3.6)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants