typeinndeling - TypeScript kan ikke utlede en delmengde av nøkler i et objekt fra bruk


Jeg jobber med algebraiske datatyper i TypeScript og sitter fast med å typifisere match-funksjonen. En forenklet versjon av problemet er vist nedenfor:

let rec = { a: 'a', b: 1 };

// just captures type of 'rec' and then reuses the type for eval
const makeEval = <Record>(rec: Record) => <
  Res,
  K extends keyof Record = keyof Record
>(
  val: Record,
  cases:
    | Cases<Record, Res>
    | (Cases<Record, Res, K> & { else: (r: Record) => Res })
): Res => (undefined as any) as Res;

export type Cases<Record, Res, K extends keyof Record = keyof Record> = {
  [T in K]: (value: Record[T]) => Res
};

const evalMyRecord = makeEval(rec);

// this is fine
const a = evalMyRecord(rec, { a: s => s, b: n => n.toString() });

// err
const b = evalMyRecord(rec, { a: s => s, else: _ => 'why err?' });
// Property 'b' is missing in type '{ a: (s: string) => string; else: (_: { a: string; b: number; }) => string; }'.

// requires explicit subset
const b_ = evalMyRecord<string, 'a'>(rec, {
  a: s => s,
  else: _ => 'but explicit a is fine'
});

// full set is fine
const c = evalMyRecord(rec, {
  a: s => s,
  b: n => 'n',
  else: _ => 'fine too'
});

Så jeg vil uttrykke en type som enten har alle nøklene til Record eller et hvilket som helst delsett av det + {else}-tilfellet.

Jeg vet at det finnes en løsning med Partial:

type EvalCases<Record, Res> =
  | Cases<Record, Res>
  | (Partial<Cases<Record, Res>> & { else: (r: Record) => Res });

const b = evalMyRecord(rec, { a: s => s, else: _ => 'works' });

const b2 = evalMyRecord(rec, {
  a: s => s,
  b: undefined,
  else: _ => 'also works but weird'
});

Men å ha: {b: undefined} ser litt rart ut. Jeg ønsker en kompilatorfeil hvis noen sak ikke er en korrekt funksjon (hvis mulig).

Noen forslag til typescript-magikk?

MERK: konseptuelt nært (men ikke det samme) som Typescript kan ikke utlede riktige argumenttyper for et objekt av funksjoner.

Svar #1

Følgende løsning lar deg ha enten Cases<Record, Res> eller et hvilket som helst delsett av Cases<Record, Res> pluss else-egenskapen.

type EnforcingPartial<T> = {
    [key in keyof T]: { [subKey in key]: T[key]; }
}[keyof T];

type EvalCases<Record, Res> = Cases<Record, Res> |
    (EnforcingPartial<Cases<Record, Res>> & { else: (r: Record) => Res });

const b = evalMyRecord(rec, { a: s => s, else: _ => 'works' });

const b2 = evalMyRecord(rec, {
    a: s => s,
    b: undefined, // ERROR HERE, b is incompatible
    else: _ => 'also works but weird'
});

Relaterte artikler