Skip to content

Inconsistent type checking when using the spread operator #38657

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
manast-apsis opened this issue May 19, 2020 · 8 comments
Closed

Inconsistent type checking when using the spread operator #38657

manast-apsis opened this issue May 19, 2020 · 8 comments
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug

Comments

@manast-apsis
Copy link

manast-apsis commented May 19, 2020

TypeScript Version: 3.9.2, 3.8.3

Search Terms:
spread operator loose typing

Code

class MyPayload {
  foo: string;
}

class MyClass {
  constructor(payload: MyPayload) {}
}

const someProps = {
  a: 'hello',
  b: 'super',
};

const a = new MyClass({ ...someProps, foo: 'bar' }); // Compiles fine

const b = new MyClass({ a: 'hello', ...someProps, foo: 'bar' }); // Compiles fine

const c = new MyClass({ ...someProps, a: 'hello', foo: 'bar' }); // Compile error

const d = new MyClass({ a: 'hello', foo: 'bar' }); // Compile error

Error message:

 error TS2345: Argument of type '{ a: string; foo: string; }' is not assignable to parameter of type 'MyPayload'.
  Object literal may only specify known properties, and 'a' does not exist in type 'MyPayload'.

Expected behavior:
All 4 last statements should either result in an compile error or none of them.

Actual behavior:
Two gives errors, the other two do not.

Discussion
I tried to find in older issues and discussions something that could explain this behaviour but I could not find it. Intuitively I picture the spread operator in the examples above as a shorthand instead of specifying all the object properties one by one, but surprisingly tsc does not threat it like that. If this really is working as design please provide some explanation for the layman.

Thanks!.

@jcalz
Copy link
Contributor

jcalz commented May 19, 2020

Likely duplicate of #32495.

I get an error in your b line also, where you say it "compiles fine"; see Playground.

@manast-apsis
Copy link
Author

@jcalz yes you are right that in latest version you get an error in b (not in 3.8.x though). However it is a different error, it is complaining that property "a" will be overwritten by the spreaded object.

@RyanCavanaugh RyanCavanaugh added the Working as Intended The behavior described is the intended behavior; this is not a bug label Jun 8, 2020
@RyanCavanaugh
Copy link
Member

I don't really want to go in to 100.0% of the reasoning behind excess property checking and how spreads are treated. These four lines are different and it's insufficient to say that spreads should behave exactly like writing their individual operands, because spreading is not the same as writing the individual operands (e.g. the possibility of aliasing, the behavior of duplicate keys, etc). If you want to make an argument for any individual line to behave different I can speak to that, but behaving differently in the presence of different code is par for the course.

@typescript-bot
Copy link
Collaborator

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

@manast-apsis
Copy link
Author

@RyanCavanaugh I think it is pretty confusing that this line compiles without any error:

const a = new MyClass({ ...someProps, foo: 'bar' }); // Compiles fine

I would love if you could point out to some explanation on why that is the case.

@RyanCavanaugh
Copy link
Member

@manast-apsis the provided object literal is a subtype of MyPayload because it has a foo property of type string, therefore the call is valid

@manast-apsis
Copy link
Author

But it includes properties that are not part of MyPayload, why would this one give a compiler error then?

const d = new MyClass({ a: 'hello', foo: 'bar' }); // Compile error

@luisgrases
Copy link

luisgrases commented Jul 20, 2023

3 years have passed and I am still having the same issue:

type Type = {
  a: string
}

const foo = ({ a }: Type) => {
  console.log(a)
}

foo({ a: 'hello', b: 'world' }) // Compile error

foo({ ...{ a: 'hello', b: 'world' } }) // No compile error

How is this intended behavior?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Working as Intended The behavior described is the intended behavior; this is not a bug
Projects
None yet
Development

No branches or pull requests

5 participants