Question:
I am working on a question from type-challenges.This is the requirements: Implement a generic MyReadonly2<T, K> which takes two type argument T and K.
K specify the set of properties of T that should set to Readonly. When K is not provided, it should make all properties readonly just like the normal Readonly. The solution works fine in v4.4+, not in v4.5+.
The solution in v4.7.2 : here
To fix this problem:
I just can’t figure out the reason why the solution 1 doesn’t work.
Answer:
The original behavior was considered a bug in TypeScript, filed at microsoft/TypeScript#45122, fixed in microsoft/TypeScript#45263, which was released in TypeScript 4.5.How should TypeScript represent the intersections of object types with a property of the same key where some, but not all of these are
readonly
properties? Should the resulting property be readonly
or not? For example, should {readonly a: string} & {a: string}
be equivalent to {readonly a: string}
or {a: string}
?Conceptually, and despite the name,
readonly
means “this is readable” while non-readonly
means “this is readable and writable”. That is why you can treat an Array<string>
as a ReadonlyArray<string>
, but not vice versa. Intersections conceptually mean “and”, so {readonly a: string} & {a: string}
should be an object with a readable a
property, and an object with a readable and writable a
property. That implies the final a
property is readable and writable, and so the final object type should be equivalent to {a: string}
, without the readonly
modifier. By that logic, a final object property should only be readonly
if it is readonly
in every intersection member in which it appears. Intersections cannot be used to add a readonly
modifier to a property that is not readonly
.Before TypeScript 4.5, TypeScript had the opposite and incorrect behavior, where a final object property was
readonly
if it was readonly
in some intersection member. This was wrong, and it was fixed.And that’s why your code changed in TypeScript 4.5. Something like
T & Readonly<Pick<T, K>>
shouldn’t be able to add a readonly
modifier to any of the keys in K
, because those keys are also present in keyof T
. But Omit<T, K> & Readonly<Pick<T, K>>
can add readonly
modifiers because none of the keys in K
are present in keyof Omit<T, K>
. You should get the same effect with Omit<T, K> & Readonly<T>
, since you don’t need to exclude the other keys.If you have better answer, please add a comment about this, thank you!