Skip to content

Narrowed variable types become widened in functions defined after narrowing #50580

Description

Bug Report

🔎 Search Terms

possibly undefined array methods predicate mutable variables narrowing

🕗 Version & Regression Information

4.5.4

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about functions and method "bugs" that aren't bugs

⏯ Playground Link

Playground link with relevant code

💻 Code

function test(param?: number): number {
    let a: number | undefined;

    // Some code that could make a defined goes here

    if (a === undefined || param === undefined) {
        return 0;
    }

    const b = () => a + param; // a is not narrowed. param is narrowed

    function c() {
        // Neither is narrowed
        return a + param;
    }

    [1, 2, 3].map(elem => elem + a + param); // a is not narrowed

    return a + param; // Both narrowed
}

🙁 Actual behavior

The variables are not narrowed when used in functions. param is narrowed when using an arrow function. This causes friction when trying to use an array method that takes a function as an argument

🙂 Expected behavior

Both variables are narrowed to number in all cases

Activity

  1. guillaumebrunerie commented on Sep 1, 2022

    @guillaumebrunerie

    This is by design, see #9998 and the template "Types not correct in/with callbacks" when creating a new issue. The basic reason is that the narrowing could have become invalid by the time the function gets invoked.

  2. mattrunyon commented on Sep 1, 2022

    @mattrunyon
    Author

    Ahh that makes sense. The behavior still seems inconsistent with parameters and variables in arrow functions. Why is param narrowed in the arrow function definition? Shouldn't it also be its original type? They're both possibly undefined in the regular function definition.

  3. guillaumebrunerie commented on Sep 1, 2022

    @guillaumebrunerie

    Indeed, not sure why it behaves differently for parameters and variables.

  4. andrewbranch commented on Sep 7, 2022

    @andrewbranch
    Member

    For const variables, we know the value can never change, so if narrowing applies in one place, it’s safe to keep that narrowing inside functions too. If you replace let a with const a = undefined as number | undefined, you’ll see that a keeps its narrowing in the arrow function. We assume that let variables will change, so that narrowing gets undone. Because parameters are not annotated with let or const, we do some quick syntactic analysis to determine whether a parameter is ever reassigned, and if not, we are safe to treat it like a const.

    Since function declarations get hoisted, we defer checking them, so I think it may just be challenging to figure out where to put the body of c into the control flow graph of the function it’s in. I’m not 100% sure why the narrowing doesn’t or can’t apply in there, but given that we don’t protect you from TDZ issues either (try adding a call to c() as the first line of test), the limitation seems kind of reasonable. I’m sure there’s an issue somewhere digging more into that particular case.

  5. typescript-bot commented on Sep 9, 2022

    @typescript-bot
    Contributor

    This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

  6. locked as resolved and limited conversation to collaborators on Oct 22, 2025
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

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions