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

Spreading object with optional property causes incorrect type inference #57086

Closed
zeke-earthling opened this issue Jan 18, 2024 · 3 comments
Closed
Labels
Question An issue which isn't directly actionable in code

Comments

@zeke-earthling
Copy link

zeke-earthling commented Jan 18, 2024

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

@MartinJohns
Copy link
Contributor

That's what exactOptionalPropertyTypes was introduced for. See also #51755 and #51253.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Jan 18, 2024
@typescript-bot
Copy link
Collaborator

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

@typescript-bot typescript-bot closed this as not planned Won't fix, can't repro, duplicate, stale Jan 21, 2024
@fmaerkl-sw
Copy link

For reference, here is another example, which type-checks by default but violates the static typing at runtime and crashes:

const a: { val?: string } = {
	val: undefined
};

const b: { val: string } = {
	val: "stub",
	...a
};

const val: string = b.val;
console.log(val.length);

Enabling exactOptionalPropertyTypes correctly fixes this issue:

index.ts:2:7 - error TS2375: Type '{ val: undefined; }' is not assignable to type '{ val?: string; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
  Types of property 'val' are incompatible.
    Type 'undefined' is not assignable to type 'string'.

2 const a: { val?: string } = {
        ~

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

5 participants