Skip to content

Type checking stops after spread operator #47995

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

Closed
Oram opened this issue Feb 22, 2022 · 10 comments
Closed

Type checking stops after spread operator #47995

Oram opened this issue Feb 22, 2022 · 10 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@Oram
Copy link

Oram commented Feb 22, 2022

Bug Report

🔎 Search Terms

spread
spread operator
spread any
spread object assign

🕗 Version & Regression Information

This is the behavior in every version I tried, and I reviewed the FAQ for entries about the spread operator

⏯ Playground Link

Playground link with relevant code

💻 Code

const x: any = { a: 5 }

interface I {
  b?: string
}

let spread: I;
spread = {
  ...x,
  b: 5
}

🙁 Actual behavior

Type mismatch is missed after use of spread operator.

🙂 Expected behavior

I expect a "Type 'number' is not assignable to type 'string | undefined'.(2322)" error for b: 5.

Similar bug #46128 indicates that after spread the type is not kept.
The main difference between the bugs is that in this case, the type is declared beforehand.


Edit: removed a line of code and changed some wording to make things clearer
@MartinJohns
Copy link
Contributor

MartinJohns commented Feb 22, 2022

You're spreading an object typed any, that automatically makes your resulting object also any, which is assignable to the type I. any is the escape-hatch from the type system, it basically turns type checking off when involved. Why not use a less permissive type, like Record<string, unknown>?

@Oram
Copy link
Author

Oram commented Feb 22, 2022

I'm using Angular forms which are not typed.
Now that I'm aware of this issue, I can add an extra step to the process and add a type to the object I'm spreading.
Maybe I'm wrong, but I still expect the rest of the assignments to be checked.

@MartinJohns
Copy link
Contributor

You have two assignments, and both are checked. { ...x, b: 5 } is typed any, and is assignable to I. Assigning any to a typed variable does not change the type of the variable. So spread is typed I, which is not assignable to never.

@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Feb 22, 2022
@Oram
Copy link
Author

Oram commented Feb 22, 2022

The never part is obvious. It just demonstrates the type is of spread is I (as expected). I removed that part to make the issue clearer.

My expectation is that { ...x, b: 5 } will be checked as two assignments: ...x which will be treated as any and b: 5 which will be treated as number.
That is the behavior when you do something like this:

const x: any = { a: 5 }

interface I {
  a: string;
  b?: string;
}

let spread: I;
spread = {
  a: x.a,
  b: 5
}

The fact that any values are assigned does not interfere with following type checks.

@fatcerberus
Copy link

What it looks like is that by using spread syntax, the object literal as a whole is inferred as typeof x & { b: number } (similar to the typing of Object.assign). Since typeof x is any, the whole type collapses to any before the usual assignability check can catch it. The non-spread version is inferred as { a: any, b: number }, so the any doesn’t take over the type in that case.

@Oram
Copy link
Author

Oram commented Feb 24, 2022

@fatcerberus this really explains it.
I guess the questions now are:

  1. Can it be considered a bug?
  2. Can the process be changed so it will offer more type safety?
  3. Should I open a feature request instead of this bug?
  4. I failed to find any documentation regarding the type inference of the spread operator (only this).
    Is there any documentation about this?
    Maybe adding it, or moving it to a more visible place will be sufficient.

@fatcerberus
Copy link

I would say less “bug” and more “design limitation” - TS has to pick a type for the type of the object literal in order to check it (assignability checks are done between types, not values; one assignment = one typecheck), and intersecting anything with any gives back any. There’s no mechanism for typechecking individual properties of an object assignment, even with a spread.

One could make the case that T & any should be T for all T, but this would actually prevent your spread entirely - it would be treated as { b: number }. So IMO the current behavior is the lesser of two evils given that any is meant to be the “shut up and leave me alone” type.

@Oram
Copy link
Author

Oram commented Feb 24, 2022

Treating it as { b: number } is actually not a bad idea IMHO. Because if you want to get any, you can always cast to it.

spread = {
  ...x,
  b: 5
} as any

@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. If you're still waiting on a response, questions are usually better suited to stackoverflow or the TypeScript Discord community.

@leppaott
Copy link

It's just unexpected when there's no "temporary" variable that could be thought of as an any type.

let x: { foo: number } = { foo: 1, baz: 2 }; // Error, excess property `baz`

will error while following works:

let baz = { baz: 2 }
let x: { foo: number } = { foo: 1, ...baz}; // OK

Why would object with spreading be any even if it uses types?

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

6 participants