This was a fantastic writeup, thanks. If you don't mind an additional question...
How does this work,
function coolPeopleOnly(person: Person & { isCool: true }) {
// only cool people can enter here
}
const person = {
name: "Jerred",
isCool: true,
} satisfies Person;
coolPeopleOnly(person);
Since
- person isn't const, so person.isCool could be mutated
- coolPeopleOnly requires that it's input mean not only Person, but isCool = true.
If you ignore the `satisfies` for a moment, the type of `person` is the literal object type that you've written (so in this case, { "person": string, isCool: true }). So coolPeopleOnly(person) works, regardless of whether `satisfies` is there, because TypeScript sees an object literal that has all the person attributes and also `isCool: true`.
(You could mutate it to `isCool: false` later, but then TypeScript would complain because `isCool: false` is different to `isCool: true`. When that happens isn't always obvious, TypeScript uses a bunch of heuristics to decide when to narrow a type down to the literal value (e.g. `true` or `"Jerred"`), vs when to keep it as the more general type (e.g. `boolean` or `string`).)
What `satisfies` is doing here is adding an extra note to the compiler that says "don't change the type of `person` at all, keep it how it is, _but_ also raise an error if that type doesn't match this other type".
(This is only partially true, I believe `satisfies` does affect the heuristics I mentioned above, in that Typescript treats it a little bit like `as const` and narrows types down to their smallest value. But I forget the details of exactly how that works.)
So the `coolPeopleOnly` check will pass because the `person` literal has all the right attributes, but also we'll get an error on the literal itself if we forget an attribute that's necessary for the `Person` type.
How does this work,
Since- person isn't const, so person.isCool could be mutated
- coolPeopleOnly requires that it's input mean not only Person, but isCool = true.