Skip to content

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

Closed
@beshanoe

Description

@beshanoe

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

Activity

RyanCavanaugh

RyanCavanaugh commented on Mar 20, 2018

@RyanCavanaugh
Member

@sandersn is this a duplicate of #22362 ?

ghost

ghost commented on Mar 22, 2018

@ghost

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 added
BugA bug in TypeScript
on Mar 22, 2018
beshanoe

beshanoe commented on Mar 22, 2018

@beshanoe
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

ghost commented on Mar 22, 2018

@ghost

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 removed
BugA bug in TypeScript
on Mar 22, 2018
ghost added
Needs InvestigationThis issue needs a team member to investigate its status.
on Mar 22, 2018
beshanoe

beshanoe commented on Apr 16, 2018

@beshanoe
Author

@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

ghost commented on Apr 16, 2018

@ghost

@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

beshanoe commented on Apr 16, 2018

@beshanoe
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

ghost commented on Apr 16, 2018

@ghost

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

RyanCavanaugh commented on Sep 16, 2019

@RyanCavanaugh
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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @beshanoe@RyanCavanaugh

        Issue actions

          Failed function argument type inference inside nested object passed as argument · Issue #22715 · microsoft/TypeScript