ts-essentials / ts-essentials

All essential TypeScript types in one place 🤙

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Bug: `DeepReadonly<T>` and `DeepWritable<T>` are not exact inverses if `T` is a type parameter

polyipseity opened this issue · comments

Versions

package version
typescript 5.0.4
ts-essentials 9.3.2

Repro Code

import type { DeepReadonly, DeepWritable } from 'ts-essentials'

declare function read<T>(value: DeepReadonly<T>): void
declare function write<T>(value: DeepWritable<T>): void
type ReadonlyWritable<T> = DeepReadonly<DeepWritable<T>>
type WriteableReadonly<T> = DeepWritable<DeepReadonly<T>>
function readwrite<T>(rw: ReadonlyWritable<T>, wr: WriteableReadonly<T>) {
    read<T>(rw)
    write<T>(wr)
}

TypeScript Playground

https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBDAnmApnA3nAIilYBKKAhgCYQB2ANogDTa5gDqUwMRARpWgL5wBmUCCDgByGAGcAtCnHiU5GMCKVxIgLAAoTSRQBjSkSho+AV3K7FFOEdIAeACoA+ABQA3ZSZQAuenkKkKagdHAEofVwhgEm09AyN+MwtgKwB3FhgUYLcPb18mdI4uYLC4CKjNJFQ4fzIqRGZWQsynOABePJrAxFscPAa2TmbHRwrkNH7iQc664La8-qaehmmgpxGNU3NLcmtiEjTWIecoFJ8V+oLB4Lo0nwmm8+KMTThX3bsnY5SQl7eDjKyaR+Gm4miAA

Expected Result

DeepReadonly<DeepWritable<T>> should be assignable to DeepReadonly<T> and DeepWritable<DeepReadonly<T>> should be assignable to DeepWritable<T>.

Actual Result

The above are not actually assignable if T depends on any type parameter (if T is fully concrete then it works as expected). It would be nice if this also works for type parameters.

However, I am not sure if this is even possible in TypeScript (the type system might not be powerful enough, or that checking for such type equality with a free parameter is undecidable), and it might be more work than what it is worth.

A more practical workaround I am using right now is manually doing the conversion using a overloaded function, which you may be interested in:

export function simplifyType<T>(value: DeepWritable<DeepReadonly<T>>): DeepWritable<T>
export function simplifyType<T>(value: DeepReadonly<DeepWritable<T>>): DeepReadonly<T>
export function simplifyType<T>(value: T): T { return value }

Hey @polyipseity

Thank you for reporting this issue

I won' be available to look at it until beginning of June, I will come back to you once I have some time

Apologies in advance

Hey @polyipseity

I don't have any ideas about how to fix it yet. To keep assignability, we have to add T & <do something else with T> which breaks both utility types

If you have any suggestions, I'm open to experiments. Have you seen it working on other type libraries?