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

Spread of optional properties can result in unexpected runtime errors #44432

Closed
issacgerges opened this issue Jun 4, 2021 · 4 comments
Closed
Labels
Question An issue which isn't directly actionable in code

Comments

@issacgerges
Copy link

issacgerges commented Jun 4, 2021

Bug Report

I believe this may just be a gap in how optional interface properties work under the hood, but it's possible to (using spread or assign) produce values that don't match the type systems expectations. You can see that in this example, but in short: Spread/assign are internally check [[GetOwnProperty]] to overwrite a value, so undefined properties can overwrite ones with concrete values. This results in values that don't represent the calculated Intersection.

I think this is caused by how optional properties are represent internally as (at times) T | undefined. Although this holds true for object lookup foo[prop], it does mean that we can create some incorrect values such as this.

interface OptionalFoo {
    foo?: Record<string, number>
}

const undefinedFoo: OptionalFoo = {
    foo: undefined
};

Resulting in errors like this

const spread = {
    foo: {},
    ...undefinedFoo
};

const assigned = Object.assign({}, {foo: {}}, undefinedFoo);

// TS has typed these as `number` but at runtime will produce "Cannot read property 'a' of undefined"
spread.foo['a'];
assigned.foo['a'];

🔎 Search Terms

interface types, optional properties, intersection, spread, assign

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about optional properties and spread/assign

⏯ Playground Link

Playground link with relevant code

💻 Code

(see in description)

🙁 Actual behavior

The type system does not report any errors although the code is guaranteed to produce an error

🙂 Expected behavior

Type system should prevent an optional property from being assigned to undefined unless it was defined as | undefined or the Intersection should contain | undefined

@fatcerberus
Copy link

This is the type of thing --strictOptionalProperties was recently introduced to deal with.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jun 4, 2021
@RyanCavanaugh
Copy link
Member

With strictOptionalProperties, there's an error on line 6:

interface OptionalFoo {
    foo?: Record<string, number>
}

const undefinedFoo: OptionalFoo = {
    foo: undefined
    ~~~
};

@issacgerges
Copy link
Author

issacgerges commented Jun 4, 2021

Ah, thanks for pointing in the direction @fatcerberus and @RyanCavanaugh . It looks like exactly the gap I believed I found was fixed! I tested against nightly but didn't think to recheck the options. I'll close this one.

@RyanCavanaugh
Copy link
Member

You can definitely be forgiven for not knowing about a feature that got checked in a week ago! 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

3 participants