Create Intersection Type of Multiple Generic Types

Viewed 879

I've been doing a lot of reading into variadic types and some of the new tuple stuff in TypeScript but am having trouble determining if what I'm trying to accomplish is simply not possible given the language capabilities currently.

The exact issue with the API currently can be seen here:

export type Themed<T> = { theme: T };

export type ThemedStyleAugmentationFn<P, T> = (
  allProps: Partial<P> & Themed<T>
) => string;

export function augmentComponent<
  P,
  TA extends T,
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
  T extends object,
  O extends object = {},
  A extends keyof any = never
>(
  styledComponent: StyledComponent<C, T, O, A>,
  styleAugmentation: ThemedStyleAugmentationFn<P, TA>
): StyledComponent<C, T, O & Partial<P>, A> {
  const augmented = styled(styledComponent)`
    ${styleAugmentation}
  `;

  return augmented as StyledComponent<C, T, O & Partial<P>, A>;
}

export function multiAugment<
  P1 extends {},
  TA1 extends T,
  P2 extends {},
  TA2 extends T,
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
  T extends object,
  O extends object = {},
  A extends keyof any = never
>(
  styledComponent: StyledComponent<C, T, O, A>,
  styleAugmentations: [ThemedStyleAugmentationFn<P1, TA1>, ThemedStyleAugmentationFn<P2, TA2>]
): StyledComponent<C, T, O & Partial<P1 & P2>, A> {
  const augmented = styled(styledComponent)`
    ${styleAugmentations}
  `;

  return augmented as StyledComponent<C, T, O & Partial<P1 & P2>, A>;
}

In augmentComponent the important part of the signature is here: O & Partial<P>.

In multiAugment which explicitly takes a tuple of two ThemeStyleAugmentationFun it's here: O & Partial<P1 & P2>

I would like to be able to write a version of multiAugment that can take an arbitrary list of AugmentationFn and union them all together such that it would support any arity. Something that might return a version of O & Partial<P1 & P2 & P3 ... & P100> for example.

I'd like to avoid if possible needing to have a massive number of function overloads in order to support this as I could continue writing Tupled versions of increasing length.

1 Answers

I can't be sure because I don't have the libraries you're using installed, but perhaps you could do something like this:

type PfromSA<SA extends Array<ThemedStyleAugmentationFn<any, any>>>
  = { [K in keyof SA]: SA[K] extends ThemedStyleAugmentationFn<infer P, any> ? (x: P) => void : never } extends
  { [k: number]: (x: infer P) => void } ? P : never;

type SAExtendsTProperly<SA extends Array<ThemedStyleAugmentationFn<any, any>>, T>
  = { [K in keyof SA]: SA[K] extends ThemedStyleAugmentationFn<any, infer U> ? [U] extends [T] ? SA[K] : ThemedStyleAugmentationFn<any, T> : never }

export function multiAugment<
  SA extends Array<ThemedStyleAugmentationFn<any, any>>,
  C extends keyof JSX.IntrinsicElements | React.ComponentType<any>,
  T extends object,
  O extends object = {},
  A extends keyof any = never
>(
  styledComponent: StyledComponent<C, T, O, A>,
  ...styleAugmentations: SA & SAExtendsTProperly<SA, T>
): StyledComponent<C, T, O & Partial<PfromSA<SA>>, A> {
  const augmented = styled(styledComponent)`
    ${styleAugmentations}
  `;

  return augmented as StyledComponent<C, T, O & Partial<PfromSA<SA>>, A>;
}

The idea is that you use a generic rest parameter for your styleAugmentations parameter (in the above example I made it a rest parameter instead of a tuple; TypeScript will not infer a tuple type from an array literal, but it will infer a tuple type from a rest parameter. If you need an array literal you will need to assert or otherwise coax the compiler into interpreting it as a tuple).

This type parameter SA is an array of ThemedStyleAugmentationFn<any, any>. Then the type function PfromSA<SA> uses type inference in conditional types to calculate your desired intersection of P types from SA. And the type function SAExtendsTProperly<SA> does something similar to make sure that each element's ThemedStyleAugmentationFn<P, TA> has TA properly extending T (because ThemedStyleAugmentationFn<P, TA> might be invariant in TA, you can't just make sure SA extends ThemedStyleAugmentationFn<any, T>; you have to walk through the tuple and grab the TA for each element).

This should work as you want, but again, I can't be sure without more self-contained code.

Hope that helps you. Good luck.

Related