Typescript: Object spread can lead to unknown properties added [Regression v3.1.6]

Created on 20 Jul 2019  路  13Comments  路  Source: microsoft/TypeScript


TypeScript Version: 3.6.0-dev.20190719


Search Terms:
spread
type check

Code

// Error as expected, b2 is not a valid key
const foo: {a?: string; b?: string} = {
    //a: "",
    ...{b2: ""}
}
// I meant to overwrite b, but somehow this is accepted
const bar: {a?: string; b?: string} = {
    a: "",
    ...{b2: ""}
}

Expected behavior:
bar should not accept ...{b2: ""}

Actual behavior:
bar has type {a?: string; b?: string}, but at runtime actually has extra property b2

Playground Link:
No problem v3.0.1
Regression v3.1.6

Related Issues:

30129

Bug Working as Intended

Most helpful comment

Would love too ! This code should definitely not be correct: const v: { a: string } = { a: '', ...{ b: '' } };

All 13 comments

I've run into this problem as well. It's still present in 3.7.2

Other example

I've created a PR that will fix this issue.
Could someone check for it please?

37392

Not sure but seems to me that this issue should have quite a big impact in angular apps that use ngrx, where spread operator is commonly used to copy state properties from one action to the next in the reducers... basically it allows you to put any data on the state without being aware of it.

Would love to see this fixed, since it can lead to very undesirable outcomes (inadvertently polluting strictly typed store state, values of forms, data for saving to db, request bodies).

Would love too ! This code should definitely not be correct: const v: { a: string } = { a: '', ...{ b: '' } };

We decided to track excess properties only for actual properties, not for spreads, back in 2017. See the design meeting notes on Spread object literal freshness at #14853.

Eh, this bug is "working as intended"? Well, I guess I question your "intentions" if you are allowing values to mismatch types in a statically typed language. But only sometimes, other times excess property check is working as users expect it to.

var c = {
a: 0,
d: 0
}
var o: { a: number } = { a: 0, ...c}

Seems arguable about whether there should be an error.

I don't see how it is arguable. In my statically typed point of view it definitely should be an error, or at the very least a user should be able to enable a compiler option for it being an error.

We probably will have to use properly typed lenses (and forbid object spreads) to overcome this "working as intended" blunder, which already caused a bunch of bugs in our code which could have been caught early if the compiler would have done its job.

The meeting notes from #14853 were written 3 years ago. Isn't type inference better nowadays, which would allow TypeScript to _really_ know which properties the spread object has?

I can't believe it. It would avoid so many bugs...

So I think it is quite important to know why they do not support this, according to the link above:

var c = { d: 0 };
var o: { a: number, b: string } = {
    a: 0,
    b: "",
    ...c,
    d: 100
};

When you spread in c, you don't know what properties it really has.
So TypeScript doesn't really know if you have excess properties in some cases."

Which makes sense, and I guess this problem is just surfacing from the fact that typescript type system is not sound, and this is just a manifestation of that limitation...
I started to look into hegeljs which has some interesting properties though: https://github.com/JSMonk/hegel

I question your "intentions" if you are allowing values to mismatch types in a statically typed language

Typescript, along with any language with subtyping (including Hegel), allows this:

class A { a }
class B extends A { b }
const a: A = new B()
const ab = { ...a } // contains b at runtime -- even though the static type of `a: A`.

I don't like subtyping, but it's unavoidably part of Javascript, and nothing to do with whether Typescript is sound or not. If you want to avoid this problem, use a language without subtyping.

I don't like subtyping, but it's unavoidably part of Javascript, and nothing to do with whether Typescript is sound or not. If you want to avoid this problem, use a language without subtyping.

Believe me, I would love to, but I am stuck at work with TypeScript.

Typescript, along with any language with subtyping (including Hegel), allows this:

But what if we don't use subtyping?

interface A { a: null }

const good: A = { a: null, invalid: true }; // correctly reports error

const bad: A = { a: null, ...{ invalid: true } }; // incorrectly doesn't report error

TypeScript here can statically verify it's an error. Why both cases aren't reported as errors or at least cannot be enabled in compiler to report them as errors? This looks to me like pretty bad inconsistency.

...{} is really rare, so checking spreads of object literals differently from other spreads would complicate the compiler with no real benefit.

I grepped for ...{ in all the .js/.ts files in our user and, besides tests in prettier and flow, found one use, which looks like it was introduced by a mechanical upgrade from Object.assign.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

weswigham picture weswigham  路  3Comments

siddjain picture siddjain  路  3Comments

manekinekko picture manekinekko  路  3Comments

DanielRosenwasser picture DanielRosenwasser  路  3Comments

Roam-Cooper picture Roam-Cooper  路  3Comments