2

I am creating a boardgame in Typescript. In it I declare the following:

export enum PieceType {
    PAWN,
    KNIGHT2,
    KNIGHT4,
    WIZARD,
    KING
}

export class Board {
    // ...
    private takeRules: Map<PieceType, Set<PieceType>> = new Map([
        [PieceType.PAWN, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD])],
        [PieceType.KNIGHT2, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KNIGHT4, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KING, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])]
    ]);
    private enchantRules: Map<PieceType, Set<PieceType>> = new Map([
        [PieceType.WIZARD, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])],
        [PieceType.KING, new Set([PieceType.WIZARD])]
    ]);
    // ...
}

This errors as follows:

[tsl] ERROR in ./src/board.ts(28,60)
      TS2769: No overload matches this call.
  Overload 1 of 3, '(iterable: Iterable<readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>]>): Map<PieceType.WIZARD | PieceType.KING, Set<...>>', gave the following error.
    Argument of type '([PieceType.WIZARD, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>] | [PieceType.KING, Set<PieceType.WIZARD>])[]' is not assignable to parameter of type 'Iterable<readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>]>'.
      The types returned by '[Symbol.iterator]().next(...)' are incompatible between these types.
        Type 'IteratorResult<[PieceType.WIZARD, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>] | [PieceType.KING, Set<PieceType.WIZARD>], any>' is not assignable to type 'IteratorResult<readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>], any>'.
          Type 'IteratorYieldResult<[PieceType.WIZARD, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>] | [PieceType.KING, Set<PieceType.WIZARD>]>' is not assignable to type 'IteratorResult<readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>], any>'.
            Type 'IteratorYieldResult<[PieceType.WIZARD, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>] | [PieceType.KING, Set<PieceType.WIZARD>]>' is not assignable to type 'IteratorYieldResult<readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>]>'.
              Type '[PieceType.WIZARD, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>] | [PieceType.KING, Set<PieceType.WIZARD>]' is not assignable to type 'readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>]'.
                Type '[PieceType.KING, Set<PieceType.WIZARD>]' is not assignable to type 'readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>]'.
                  Types of property '1' are incompatible.
                    Type 'Set<PieceType.WIZARD>' is not assignable to type 'Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>'.
                      Type 'PieceType.WIZARD' is not assignable to type 'PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4'.
  Overload 2 of 3, '(entries?: readonly (readonly [PieceType.WIZARD | PieceType.KING, Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>])[]): Map<PieceType.WIZARD | PieceType.KING, Set<...>>', gave the following error.
    Type 'Set<PieceType.WIZARD>' is not assignable to type 'Set<PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4>'.

If I put the second map in comments: no problem. If I ommit the wizard enum in the last line of the second map: no problem. I can even put the ommitted value in using the constructor without a problem. E.g.

    private takeRules: Map<PieceType, Set<PieceType>> = new Map([
        [PieceType.PAWN, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD])],
        [PieceType.KNIGHT2, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KNIGHT4, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KING, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])]
    ]);
    private enchantRules: Map<PieceType, Set<PieceType>> = new Map([
        [PieceType.WIZARD, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])],
        [PieceType.KING, new Set([])]
    ]);

   // ...

    constructor() {
        // ...
        this.enchantRules.get(PieceType.KING).add(PieceType.WIZARD)
    }

I really don't understand how this is possible?

MartenBE
  • 744
  • 1
  • 5
  • 20

1 Answers1

3

Very interesting question. Here is solution:

export enum PieceType {
    PAWN,
    KNIGHT2,
    KNIGHT4,
    WIZARD,
    KING
}

export class Board {
    // ...
    private takeRules = new Map<PieceType, Set<PieceType>>([
        [PieceType.PAWN, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD])],
        [PieceType.KNIGHT2, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KNIGHT4, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4, PieceType.WIZARD, PieceType.KING])],
        [PieceType.KING, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])]
    ]);
    private enchantRules = new Map<PieceType, Set<PieceType>>([
        [PieceType.WIZARD, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])],
        [PieceType.KING, new Set([PieceType.WIZARD])]
    ])
}

I just defined generics for Map instead of defining whole type : new Map<PieceType, Set<PieceType>>.

But main question, why the error occured?

Let's make some analyze of :

   private enchantRules: Map<PieceType, Set<PieceType>> = new Map([
        [PieceType.WIZARD, new Set([PieceType.PAWN, PieceType.KNIGHT2, PieceType.KNIGHT4])],
        [PieceType.KING, new Set([PieceType.WIZARD])]
    ]);

If you put type definition Map<PieceType, Set<PieceType>> before equal sign, TS will try to infer these type. Hence, if you put some PieceType enum value, which don't exists in previous entity, it will throw an error.

Take a look on simplier example:

type Fst = PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4
type Scd = PieceType.WIZARD

let fst: Fst = PieceType.PAWN;
let scd: Scd = PieceType.WIZARD;

scd = fst; // error

You can't assign fst to scd, but, if you add PAWN enum type to Scd type, it will work:

type Fst = PieceType.PAWN | PieceType.KNIGHT2 | PieceType.KNIGHT4
type Scd = PieceType.WIZARD | PieceType.PAWN

let fst: Fst = PieceType.PAWN;
let scd: Scd = PieceType.WIZARD;

scd = fst; // ok

Let's go back to our problem. Because WIZARD was not defined in first Map value V, you are unable to use WIZARD at all.

SO, I believe you should stick with explicit generic type definition instead of defininf your type directly near variable definition.

P.S. You may find this answer also interesting

I'm open to criticizm, so feel free to point my mistakes. Thanks