type-challenges / type-challenges

Collection of TypeScript type challenges with online judge
https://tsch.js.org/
MIT License
43.59k stars 4.73k forks source link

9 - Deep Readonly (🎥 Video Explanation and Solution) #21546

Open dimitropoulos opened 1 year ago

dimitropoulos commented 1 year ago

9 - Deep Readonly

Some of the prior challenges are solvable without recursion, but this one absolutely requires it. If that scares you, don't worry, it's not so bad. Recursion is more-or-less the only mechanism we have for iteration in TypeScript, so we'll be using it a lot in the future.

🎥 Video Explanation

Release Date: 2023-01-18 19:00 UTC

Deep Readonly

🔢 Code

// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'

type A1 = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
};

type B1 = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
      readonly l: readonly [
        'hi',
        {
          readonly m: readonly ['hey']
        },
      ]
    }
  }
};

type C1 = Expect<Equal<DeepReadonly<A1>, B1>>;

// ============= Your Code Here =============
type DeepReadonly<T> =
  keyof T extends never
  ? T
  : {
    readonly [P in keyof T]: DeepReadonly<T[P]>;
  };

type DeepReadonly<T> = {
  readonly [P in keyof T]:
    keyof T[P] extends never
    ? T[P]
    : DeepReadonly<T[P]>
};

type DeepReadonly<T> =
  T extends object & { call?(): never } // exclude functions from `object` type, equivalent to `T extends Function`
  ? {
      readonly [P in keyof T]: DeepReadonly<T[P]>
    }
  : T;

/*

1. You can assign anything to `{}`, *except* for `null` and `undefined`.
2. You can assign anything that's *not a primitive* to `object`.
  - Primitives are strings, booleans, numbers, bigints, symbols, `null` and `undefined`
3. You can only assign *objects* to `Record<string, unknown>`

| Value is assignable to | {}  | object | Record<string, unknown> |
| ---------------------- | --- | ------ | ----------------------- |
| null                   | No  | No     | No                      |
| undefined              | No  | No     | No                      |
| "string"               | Yes | No     | No                      |
| 42                     | Yes | No     | No                      |
| 42n                    | Yes | No     | No                      |
| Symbol()               | Yes | No     | No                      |
| true                   | Yes | No     | No                      |
| () => {}               | Yes | Yes    | No                      |
| [1, 2]                 | Yes | Yes    | No                      |
| []                     | Yes | Yes    | No                      |
| {foo: "bar"}           | Yes | Yes    | Yes                     |
| {}                     | Yes | Yes    | Yes                     |

*/

âž• More Solutions

For more video solutions to other challenges: see the umbrella list! https://github.com/type-challenges/type-challenges/issues/21338

adoin commented 1 year ago

Not passed when check expected3.