20

If I want to define a tuple or constant object shape, I'd use as const. E.g.:

const arr = [{ a: 1 }] as const;

/* readonly [{
  readonly a: 1;
}] */

However, I don't want readonly because I'd get a lot of The type 'readonly ...' is 'readonly' and cannot be assigned to the mutable type '...'

I defined a "Mutable" generic:

type Mutable<T> = {
  -readonly [K in keyof T]: Mutable<T[K]>;
}

type Arr = Mutable<typeof arr>;

/* [Mutable<{
    readonly a: 1;
}>] */

However, TS doesn't recursively apply Mutable in VS Code's type preview. In other words, I want VS Code's type preview to show:

/* [{
    a: 1;
}] */

I don't think it's possible for VS Code to recursively evaluate Mutable, since it could be too slow.

Is there something like as const, but without adding readonly?

Leo Jiang
  • 24,497
  • 49
  • 154
  • 284

1 Answers1

5

In fact, you already made this array mutable. But you probably have noticed that type of first element is {a: 1}

const arr = [{ a: 1 }] as const;

type Mutable<T> = {
    -readonly [K in keyof T]:  Mutable<T[K]>;
}

type Arr = Mutable<typeof arr>[0]; // {a: 1}

Because a has literal type number 1, you still can't mutate the element.

Sorry, you can, but you are allowed to use only 1

const x: Arr = [{ a: 1 }]
x[0].a = 1 // ok

In order to make it fully mutable, you should map all literal types to more commont types.

1 -> number


type LiteralToCommon<T extends PropertyKey> =
    T extends number
    ? number : T extends string
    ? string : T extends symbol
    ? symbol : never;

type Mutable<T> = {
    -readonly [K in keyof T]: T[K] extends PropertyKey ? LiteralToCommon<T[K]> : Mutable<T[K]>;
}

const arr = [{ a: 1 }] as const;


type Arr = Mutable<typeof arr>;

const x: Arr = [{ a: 1 }]

x[0].a = 10 // ok

Playground

There is an alternative way:

type Mutable<T> = {
    -readonly [K in keyof T]: Mutable<T[K]>;
}

const arr = [{ a: 1 }];

type ImmutableArr = Readonly<typeof arr>

type MutableArr = Mutable<ImmutableArr>

type Arr = Mutable<typeof arr>;

const x: MutableArr = [{ a: 1 }]

x[0].a = 10 // ok

You can use just Readonly for array. This util will infer {a: 1} as {a: number}