Skip to content

StrictNullChck with Object.assign anomaly (Object is possibly 'undefined') #43110

Description

Bug Report

With the strict null check turned on

function test(options?: { fields?: string , strict?: boolean}) {
    options = Object.assign({ fields: null }, options);
    if (options.fields) {
       //^^^^^^^ Object is possibly 'undefined'.ts(2532)
    }
    const test2 = () => {
            if (options.strict) {
               //^^^^^^^ Object is possibly 'undefined'.ts(2532)
            }
    }
}

Any of the following changes fix the first (but not the second):

function test(options?: { fields?: string|null }) {
    options = Object.assign({ fields: '' }, options);
    options = Object.assign({ a:null }, options);

🔎 Search Terms

strictNullCheck, Object.assign

🕗 Version & Regression Information

4.1.3

⏯ Playground Link

https://www.typescriptlang.org/play?#code/GYVwdgxgLglg9mABFApgZygCjgB1gtAfgC5EBvRYGFAGwBMjSMAnGMAcwBpEWZoTEAIzhwaKAIZIAvgEpyAKERLEufGDSIAvIgDyggFYpoAOnFo0MdmEwUqtBqTAgaNRFO6r46mQG5FymGBEbDwvNGM7ejQ5Mn9lZQB6BIA9VLTk3QMjKEQYDRw4cxhBGgBPRABycDoUKjAUOgrjKDRMACYAVgBmNpk4t36IAhzUDDatYLlNAD4FePmAoJC1cN5oGP6FhaT0tMzDaFz8wosS8qqwGrqGppb27t7Nral+l6kgA

💻 Code

🙁 Actual behavior

Wrongly assumes the options can be undefined

🙂 Expected behavior

Should be no error

Activity

  1. jcalz commented on Mar 6, 2021

    @jcalz
    Contributor

    There's a number of issues mixed together here:

    • The value {fields: null} is not assignable to type {fields?: string}. Optional properties can be undefined, not null. Is this a mistake?
    • Object.assign() produces an intersection type, which is only an approximation of what happens when there is property overlap, see Bug Report + Proposal: Object.assign() #31982 for why.
    • Since Reduce intersections by discriminants #36696, the intersection that comes out of Object.assign() here, {fields: null} & ({ fields?: string , strict?: boolean} | undefined), will be reduced to never. Apparently the reduction to never here makes the compiler forget about any control flow narrowing for options, and it resets to the original type of options, which includes undefined in its domain. When you make any of the changes you mention above, this reduction to never doesn't happen, because there's no longer an impossible discriminant type.
    • Closures also reset any control flow narrowing that happen; see Trade-offs in Control Flow Analysis #9998 for why.

    This is a combination of things working as intended and known design limitations. If you think there's something here that isn't explained by the above points, could you point it out?

  2. holdfenytolvaj commented on Mar 6, 2021

    @holdfenytolvaj
    Author

    Thanks for the detailed answer

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions