2

Intention

I'm trying to add immutability-by-default everywhere I can in my code.

I use generics because I don't want to:

  1. write twice every getters (that's the critical reason, otherwise a DeepWritable type would do)
  2. write twice every data-types
  3. write more stuff to declare an immutable parameter than a mutable one, that's what I mean by immutable-by-default.

Current situation

I've got a deep-readonly type:

// Simplified: Only handle types used in this example
type DeepReadonly<T> = T extends number
  ? T
  : T extends ReadonlyArray<infer V>
  ? ReadonlyArray<DeepReadonly<V>>
  : { readonly [K in keyof T]: DeepReadonly<T[K]> };

And my data types can be mutable or immutable with a type parameter:

type Mutability = 'const' | 'mut';

interface PlayerMut {
  life: number;
}

type Player<M extends Mutability = 'const'> = M extends 'const' ? DeepReadonly<PlayerMut> : PlayerMut;

interface GameStateMut {
  players: Player[];
}

type GameState<M extends Mutability = 'const'> = M extends 'const' ? DeepReadonly<GameStateMut> : GameStateMut;

I can use GameState as immutable (thus, by default) and GameState<'mut'> as mutable.

Also, GameState<'mut'> extends GameState is valid. So I can pass mutable states to a function expecting an immutable one (.i.e it won't modify it), but not the contrary (.i.e it would try to modify it).

So far so good...

Problem

Then, I am trying to write a getter function which would return an immutable part of the state by default, but could return a mutable one if we wish (and providing a mutable GameState).

This fails:

function getFirstPlayer<M extends Mutability = 'const'>(gameState: GameState<M>): Player<M> {
  // error: Type 'Readonly<PlayerMut>' is not assignable to type 'Player<M>'
  return gameState.players[0]; // dirty workaround: ...as Player<M>
}

In my head, this should work. But TypeScript doesn't understand it (gameState type in the function is equivalent to GameState<'mut'> | GameState<'const'>, always).

Question

Is there a way to make the above work without resorting to as?

as works but feels like cheating. It is putting a hole in the type checking, which is making the whole program less future-proof.

Typescript playground

Drestin
  • 326
  • 2
  • 9
  • Yes indeed. It is a known problem in TypeScript: https://github.com/microsoft/TypeScript/issues/13995 – Drestin Dec 09 '20 at 16:42

0 Answers0