Skip to content

typeof ... === "undefined" check on mapped object member doesn't narrow correctly, for only some purposes #52440

Closed
@wbt

Description

@wbt

Bug Report

🔎 Search Terms

undefined type guard narrowing fails

🕗 Version & Regression Information

⏯ Playground Link

Playground link with relevant code

💻 Code

type MappedType = {[T in ('Alpha' | 'Beta')]: {[index: string] : number[]};}
declare function takesArray(arg0: number[]) : boolean;
export const demoFn = function<T extends keyof MappedType>(
    key: T, //clarifying that this is known/specified
    row: Partial<MappedType[T]>
) {
    for(let attribute in row) {
        const attributeValue = row[attribute];
        //attributeValue could be undefined here, that's OK
        takesArray(attributeValue); //err as expected
        //Use of this syntax required by https://eslint.org/docs/latest/rules/no-undefined
        //but the error doesn't appear when using `if(attributeValue !== undefined) {`
        if(typeof attributeValue !== 'undefined') {
            takesArray(attributeValue); //attributeValue is number[]
            // with undefined narrowed out, but next line has error saying
            //'attributeValue' is possibly 'undefined'. ts(18048)
            for(let member of attributeValue) {
                console.log('This array includes '+member+'.');
            }
        }
    }
}

🙁 Actual behavior

Error 'attributeValue' is possibly 'undefined' (for the purposes of a for loop but not for the purposes of a function call) inside a type-guard conditional checking for undefined, where attributeValue is a constant originally copied from a mapped object type.

🙂 Expected behavior

  • Only the first error, on the line with the comment "err as expected".
  • I would also expect TS to be consistent about whether attributeValue can be undefined within the two immediate children lines in the conditional (function call and for loop).
  • I would also expect TS to be consistent between the use of typeof attributeValue !== 'undefined' and attributeValue !== undefined.
  • I would also expect TS to behave consistently whether the same nominal type originally comes from a mapped type or not. Note that the sample code above uses a mildly complex derived type, which is still much simpler than the inspiring example, and the motivation for this was left out of the example to get it closer to minimal with focus on the failing type guard. The "workaround" of using simpler non-derived types would NOT solve the issue here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugA bug in TypeScriptHelp WantedYou can do this

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions