1

I have the following minimal viable example:

export type Left<E> = {
    readonly _tag: 'Left'
    readonly left: E
}

export type Right<A> = {
    readonly _tag: 'Right'
    readonly right: A
}

export type Either<E, A> = Left<E> | Right<A>

export const left = <E = never, A = never>(e: E): Either<E, A> => ({_tag: 'Left', left: e})

type A = Either<{type: 1}, {type: 2}>

const f = (a: A): void => {}

f(left({type: 1, extraKey: 2})) // Expect this to throw a type error because of the extra key, but it does not on TS 4.6.3

The Either type is strongly inspired from the Either type in the fp-ts library.

I expected the last line to throw a type error because of the presence of the extra key, but it does not on TypeScript 4.6.3.

Is this a bug, or a feature?

Bertrand Caron
  • 2,525
  • 2
  • 22
  • 49
  • It's a feature. Extra keys do not violate a type (see [this example](https://tsplay.dev/WGPVXw)); they are only considered errors in particular circumstances (being written in an object literal that is directly assigned to something that immediately throws away the property, which `left()` does not do). Does the question really have much to do with unions and `Either`? Or is it primarily about whether extra properties should or should not violate a type? – jcalz Apr 08 '22 at 02:33
  • I felt it had something to do with `Either`, because the following throws an error, but I'd love your opinion on that: `type A = {type: 1}; const f = (a: A): void => {}; f({type: 1, extraKey: 2})` – Bertrand Caron Apr 08 '22 at 02:40
  • `left()` is a generic function that specifically infers the type parameter `E` based on what's passed into it, so as far as `left()` is concerned it's impossible for there to be an "extra" property in `{type: 1, extraKey: 2}`. On the other hand, your second `f()` function is not generic, and accepts an `A`, and the object literal `{type: 1, extraKey:2}` has a property that's immediately thrown away. Seems like you want info about [excess property checking](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-6.html#stricter-object-literal-assignment-checks). – jcalz Apr 08 '22 at 02:48
  • See [this q/a](https://stackoverflow.com/questions/68478462/typescript-interfaces-not-enforcing-properties-when-an-object-is-assigned) for more information; does that address your situation or am I missing something? – jcalz Apr 08 '22 at 02:51
  • Well, the example you are linking is using `interface`; I was hoping that `type` would have a way to check this more strictly (the presence of extra keys matters a lot if you are for instance splatting the parameter into another object, or just inserting it raw in a database). If not, are there any "tricks" to help enforce it? – Bertrand Caron Apr 08 '22 at 03:18
  • There's no difference between `type` and `interface` when it comes to excess property checking. There are various existing Q/As about dealing with extra properties in TypeScript and how to simulate/emulate so-called "exact types". Like [this one](https://stackoverflow.com/a/69666350/2887218) for example. I'm inclined to close this question as a duplicate of these other ones unless there's something else going on here that I haven't addressed. – jcalz Apr 08 '22 at 03:53
  • I guess it does sound like a duplicate. This might sound more like a philosophical question, but does that mean that the splat operator in inherently dangerous on objects that are type-checked by TS, because the compiler checks what they DO contain, but not what they do NOT? – Bertrand Caron Apr 08 '22 at 04:00
  • What's the "splat" operator? Is that from another programming language? Presumably you mean "spread" like `{...foo, ...bar}`. – jcalz Apr 08 '22 at 12:58
  • The possible presence of extra properties is not itself inherently dangerous; there is no guarantee that such properties are either present or absent; the type `{a: number}` says nothing whatsoever about a property named `"b"`, for example. But TypeScript is not always type safe; there are indeed places where the compiler assumes that no extra properties exist, for productivity. The spread operator and `Object.assign()` are like this, where in `{...foo, ...bar}` it is presumed that there are no extra properties in `bar` which would overwrite properties from `foo`. – jcalz Apr 08 '22 at 13:00
  • Ugh the comment section on a question is not the right place to answer this, so I'm going to stop now. You might want to read https://www.typescriptlang.org/docs/handbook/type-compatibility.html#a-note-on-soundness and https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals for more information. – jcalz Apr 08 '22 at 13:02

0 Answers0