Skip to content

Spreading object with optional property causes incorrect type inference #57086

Closed as not planned
@zeke-earthling

Description

@zeke-earthling

🔎 Search Terms

javascript typescript spread spreading undefined optional property object type

🕗 Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about spreading objects with optional properties.

⏯ Playground Link

https://www.typescriptlang.org/play?ts=5.3.3&ssl=2&ssc=24&pln=2&pc=8#code/MYewdgzgLgBAHjAvDA3gQwFwwIwF8BQoksAnlijGgPxZgCuAtgEYCmATjLkqgUdDAC9ylGjHrN2XZBUww6YACYsAZgEswLBZ3yFw-OCW4oAdKbgAaGKeMlee2AbTcDxpwHo3Yxqza7IIABsWYwCQAHMACkcAShgPHBgAWkoAgJgwkBAFHT4HASNrCytTATtieAEnZDgBVzjPcR8-CEDg0MiatFj4+SU1DS1kpjQFIA

💻 Code

const x = {a: 1}
const y: { a?: number } = {}
const z: { a?: number} = { a: undefined }

const xy = {...x, ...y}
const xya = xy.a // number
console.log(xya) // 1 - all good

const xz = {...x, ...z}
const xza = xz.a // number
console.log(xza) // undefined - bad

🙁 Actual behavior

When you spread two objects together that have the same property set, the property gets the last value assigned. This holds true even when the property is assigned undefined. However, TypeScript doesn't differentiate between a property that is not set and a property that is set to undefined. This causes problems when you spread an object with a property set to undefined—TypeScript assumes the property is not set at all, so it skips it and infers from whatever other (earlier in the spread) values the property could take.

In the example above, y and z are indifferentiable to TypeScript, but when you spread them the resulting objects are different. xy.a is in fact a number, as TypeScript thinks it is, because {...x, ...y} uses the value of a from x. xz.a, on the other hand, is undefined, but TypeScript still assumes it's a number because it thinks {...x, ...z} used the value of a from x.

🙂 Expected behavior

I'm really not sure how TypeScript could deal with this case best. It could give a type number | undefined in both scenarios above, but then that's annoying when a really is not set on the object (y in the example).

Ideally I think there would be a difference in types between y and z. TypeScript already does this if the type is inferred—if you remove : { a?: number} from the third line above, x gets type { a: undefined } and xza gets type undefined, which is what it should be. The issue seems to come from the property being explicitly optional but set to undefined.

Additional information about the issue

Closely related to #41418.

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions