Skip to content

Incorrect type inference when using object index #17011

Closed

Description

@zlepper

TypeScript Version: 2.4.0 / nightly (2.5.0-dev.201xxxxx)
2.4.1

Code

function test(paramsObject: { [key: string]: string | Array<string> }) {
    for (let key in paramsObject) {
        if (paramsObject.hasOwnProperty(key)) {
            if (Array.isArray(paramsObject[key])) {
                doArray(paramsObject[key]);
            } else {
                doString(paramsObject[key]);
            }
        }
    }
}


function doString(s: string) {
    console.log(s);
}

function doArray<T>(a: Array<T>) {
    console.log(a);
}

Expected behavior:
Typescript should be able to infer that paramsObject[key] is still a string or string array after Array.isArray has been called. It can't change.
It works correct if I assign paramsObject[key] to a variable beforehand, and then use that variable in both the check and the doArray and doString.

Actual behavior:
Argument of type 'string | string[]' is not assignable to parameter of type 'string[]' for doArray
and
Argument of type 'string | string[]' is not assignable to parameter of type 'string'. for doString

Activity

  1. zlepper commented on Jul 7, 2017

    @zlepper
    Author

    I made the example in the OP in the playground, so i'm pretty sure typescript complains, and I have seen it in my local code, that's why i noticed in the first place.

  2. kitsonk commented on Jul 7, 2017

    @kitsonk
    Contributor

    Rasmus Hansen (@zlepper) Andy reduced your example to a more minimum reproducible which exhibits the behaviour. In other words, he is suspecting it is an issue.

  3. ahejlsberg commented on Jul 7, 2017

    @ahejlsberg
    Member

    Andy (Andrewkraft) (@Andy-MS) Your example is a duplicate of #10530, but isn't the same as the OP.

    The OP example is working as intended. In the example the index expresssion is a variable and not a string literal. I don't think it is likely we'll ever do control flow based narrowing of obj[x] expressions where x is a variable since we'd also have to track that there are no intervening assignments to x. I suggest changing the code to:

    function test(paramsObject: { [key: string]: string | Array<string> }) {
        for (let key in paramsObject) {
            if (paramsObject.hasOwnProperty(key)) {
                const value = paramsObject[key];
                if (Array.isArray(value) {
                    doArray(value);
                } else {
                    doString(value);
                }
            }
        }
    }

    This behaves as expected and is also more efficient.

  4. zlepper commented on Jul 7, 2017

    @zlepper
    Author

    That is also what i wrote in the OP

    It works correct if I assign paramsObject[key] to a variable beforehand

    I just wanted to make sure it was intended. Thank you!

    And thanks for the Great language :)

  5. ahejlsberg commented on Jul 7, 2017

    @ahejlsberg
    Member

    Rasmus Hansen (@zlepper) Right, I missed that. And you're welcome!

  6. locked and limited conversation to collaborators on Jun 14, 2018
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

    Working as IntendedThe behavior described is the intended behavior; this is not a bug

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions