Skip to content

Type narrowed too much #57880

Closed as not planned
Closed as not planned

Description

🔎 Search Terms

type narrowing local variable

🕗 Version & Regression Information

I don't see anything related in the FAQ. But I acknowledge that this seem too common to be unknown. Every version seem affected however.

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.4.2#code/MYewdgzgLgBMCGAbRMC8MAUAzAXJglGgHwwBuIAlgCaGolYb4DcAUC6JLFAKbRoHEYAbxYwxMRN1jw8AVzBVuWCmG5UYAHxhhZAWwBG3AE795i5aqqtxMAPS2YAFQCeAB17AjFV7AjduuhAwUCAwhnAguq6SPIjOMPLwAO7wRtzBABbwXBnppEiy3KLi9jAgWDAABvCVcPBgcFlgAObpKpnpWCDIIEkqzXXIAHTFYgjIGIyCQjDw-ACMTDAAvsyjdg4AggodaQA0MBS+UBTImSoA1pnZVTV1DeBx66WuIBAQFPpxYemVZkoqNSVEY2CgVDBzACEqHQ-wsakIIhsNlKUAyFCCiEBMFasDSACtuMAeFR1jZ4EMQgBlKBeFqMaziZYsZasIA

💻 Code

const call = (f: () => void) => f();

const test = () => {
    let a: undefined | number = undefined;
    // Typescript seems to be completely unaware that the value
    // of `a` can change in the following call.
    call(() => { a = 1; });
    // And there, it still think that `a` can only
    // possibly be `undefined`.
    if (a !== undefined) {
        // this line get rejected
        a.toString();
    }
};

🙁 Actual behavior

It seems that Typescript is not considering a function call to affect local variable. It proceed to narrow the type from this limitation, and thus reject valid code.

Not assigning undefined to a causes the error to disappear BUT this is still a problem, because I might want a default value here (could be a number initially, that later get changed to undefined for example).

For example, and this is worse:

const call = (f: () => void) => f();

const test = () => {
    let a: undefined | number = 42;
    call(() => {
        a = undefined;
    });
    a.toString();
};

This will compile fine, but it will fail at runtime!

🙂 Expected behavior

The type should not be narrowed in this case.

Typescript should detect that a closure is referencing a local variable, and thus, should avoid narrowing its type.

Additional information about the issue

No response

Activity

  1. MartinJohns commented on Mar 21, 2024

    @MartinJohns
    Contributor

    Another duplicate of #9998.

  2. fjolliton commented on Mar 21, 2024

    @fjolliton
    Author

    Is there anything to prevent to narrow such types? (a configuration option or a directive to put somewhere)

  3. MartinJohns commented on Mar 21, 2024

    @MartinJohns
    Contributor

    No. It's all written in that issue. You can use a type assertion to prevent the initial narrowing: 42 as undefined | number.

  4. craigphicks commented on Mar 23, 2024

    @craigphicks

    Note: Typescript is aware that the value of a can change in a synchronous IIFE

    const test = () => {
        let a: undefined | number = undefined;
        // Typescript is aware that the value of `a` can change in a synchronous IIFE
        (() => { a = 1; })();
        a; // number
    };
    test();
    
  5. fatcerberus commented on Mar 23, 2024

    @fatcerberus

    Craig P Hicks (@craigphicks) Yes, IIFEs are special-cased: since the compiler can readily see from the syntax that the function will always be called immediately (and exactly once), it's treated the same as if the code was written inline.

  6. typescript-bot commented on Mar 26, 2024

    @typescript-bot
    Contributor

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

  7. 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