When it comes to determining whether two types are identical, the usage of the `readonly` keyword may not directly impact their assignability, but it does play a role in establishing their identity. One approach to test type identity involves exploiting either (1) the assignability rule for conditional types, which mandates that types following `extends` must be identical, or (2) the inference mechanism for intersection types, which eliminates identical types from both ends. By utilizing mapped types, as demonstrated in Titian Cernicova-Dragomir's response, each property of `Car` can be examined individually to ascertain if it matches a mutable version of itself.
// Link: https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
type IfEquals<X, Y, A, B> =
(<T>() => T extends X ? 1 : 2) extends
(<T>() => T extends Y ? 1 : 2) ? A : B;
// Alternatively:
/*
type IfEquals<X, Y, A, B> =
[2] & [0, 1, X] extends [2] & [0, 1, Y] & [0, infer W, unknown]
? W extends 1 ? B : A
: B;
*/
type WritableKeysOf<T> = {
[P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never>
}[keyof T];
type WritablePart<T> = Pick<T, WritableKeysOf<T>>;
class Car {
engine: number;
get hp() {
return this.engine / 2;
}
get kw() {
return this.engine * 2;
}
}
function applySnapshot(
car: Car,
snapshoot: Partial<WritablePart<Car>>
) {
let key: keyof typeof snapshoot;
for (key in snapshoot) {
if (!snapshoot.hasOwnProperty(key)) continue;
car[key] = snapshoot[key];
}
}