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

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

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 11
  • Comments: 15 (4 by maintainers)

Most upvoted comments

...{} 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.

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

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.

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.

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.

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

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

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?