0

I have the following (example-)array:

[
  'test-string', 
  foo => ({ foo }), 
  'other-string', 
  bar => ({ bar })
]

with the (example-)interface

interface MyValues { foo: string; bar: string; }

A function that expects this array type must ensure that the unification of the results of all functions implements the complete interface. Is that possible? Currently, I have:

type Fn<Values> = (part: string) => { [key in keyof Values]?: string }
type Item<Values> = string | Fn<Values>;

function extract<Values>(items: Item<Values>[]) {
  const values = {} as Values;
  // (...)
  return values;
}

However, this typing only checks that all functions return objects that match the keys of Values and not that all keys are finally present.

I am not quite sure if this check is even possible with TypeScript, I have found this answer which also goes towards "calculations" of types, but I am not sure if this is applicable for this use-case.

Putzi San
  • 5,566
  • 3
  • 19
  • 35

1 Answers1

0

I'm pretty confident there's no type you can write whose members are arrays of the form you want. If you let TypeScript infer the element type of the array as the union of the types of its members, you can then validate that the element type appears to cover the complete interface:

interface MyValues { foo: string; bar: string; }

// From jcalz, https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type Fn<Values> = (part: string) => { [key in keyof Values]?: string }
type Item<Values> = string | Fn<Values>;
type CheckComplete<Values, T extends Item<Values>> =
  // note: ReturnType is distributive.
  UnionToIntersection<ReturnType<Exclude<T, string>>> extends
  { [key in keyof Values]: string } ? unknown : never;

function extractFactory<Values>() {
  return function<T extends Item<Values>>(items: T[] & CheckComplete<Values, T>) {
    const values = {} as Values;
    // (...)
    return values;  
  };
}

const extract = extractFactory<MyValues>();

const a = [
  'test-string', 
  foo => ({ foo }), 
  'other-string', 
  bar => ({ bar })
];
extract(a);  // OK

const b = [
  'test-string', 
  foo => ({ foo }), 
  'other-string'
];
extract(b);  // Error

However, the check is easily bypassed with an annotation that sets the element type to a union, not all of whose members actually appear in the array:

extract<string
  | ((arg: string) => {foo: string})
  | ((arg: string) => {bar: string})>(b);  // Wrong but no error
Matt McCutchen
  • 28,856
  • 2
  • 68
  • 75