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

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

Closed
wbt opened this issue Jan 26, 2023 · 1 comment · Fixed by #52456
Labels
Bug A bug in TypeScript Help Wanted You can do this
Milestone

Comments

@wbt
Copy link

wbt commented Jan 26, 2023

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.
@wbt wbt changed the title typeof ... === "undefined" check on mapped object member doesn't narrow correctly typeof ... === "undefined" check on mapped object member doesn't narrow correctly, for only some purposes Jan 26, 2023
@RyanCavanaugh
Copy link
Member

I would also expect TS to be consistent between the use of typeof attributeValue !== 'undefined' and attributeValue !== undefined.

Agreed. Shorter repro:

declare function takeArray(arr: Array<unknown>): void;
function fn<T extends Array<unknown> | undefined>(arg: T) {
    // if (arg !== undefined) { // <- works
    if (typeof arg !== "undefined") {
        takeArray(arg); // no problem
        const n: Array<unknown> = arg; // ok

        for (const p of arg) {  } //  not ok
        const m = [...arg]; // also not ok
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript Help Wanted You can do this
Projects
None yet
2 participants