TypeScript Field Type Dependent on Other Field

Viewed 103
type Formula =
  | {
      fn: (a: number) => number;
      arg: number;
    }
  | {
      fn: (a: () => number) => number;
      arg: () => number;
    };

In this example, how can I type Formula so that fn always accepts arg as a parameter? I still need something like the Formula type to represent both kinds of formulas in case I want to mix them in a list or create a function that can return either type.

formula1.fn(formula1.arg) // should always work.

In other words:

  1. If arg is a number, fn should be a (number) => number
  2. If arg is a () => number, fn should be a (() => number) => number
  3. There exists one type which covers both types of formula.
// Just applies formula.fn to formula.arg
const calc = (
  formula: Formula
) => formula.fn(formula.arg);  // formula.arg fails to type check

/*
Argument of type 'number | (() => number)' is not assignable to parameter of type 'number & (() => number)'.
Type 'number' is not assignable to type 'number & (() => number)'.
    Type 'number' is not assignable to type '() => number'.ts(2345)
*/


// Should succeed (fn takes a number and arg is a number)
const num1 = calc({
  fn: (a) => a + 1,
  arg: 3
})

// Should succeed (fn takes a () => number and arg is a () => number)
const num2 = calc({
  fn: (a) => a() + 1,
  arg: () => 3
})

// Should fail because arg is a number and fn expects a () => number parameter
const num3 = calc({
  fn: (a) => a() + 1,
  arg: 4
})

// Should fail because arg is a () => number and fn expects a number parameter
const num4 = calc({
  fn: (a) => a + 1,
  arg: () => 4
})

// It should be possible to build an array
// containing types of formulas.
const formulas: Formula = [
   // plain number formula
  {
    fn: a => a + 3,
    arg: 4
  },
   // () => number formula
  {
    fn: a => a() * 2,
    arg: () => 11
  }
]

const answers = formulas.map(calc)

Thanks!

1 Answers

You need a generic function.

type Formula = {
   fn: <T extends (() => number) | number>(a: T) => number;
}

Or, maybe you would prefer the second one:

type Formula<Arg> = {
      fn: (a: Arg) => number;
      arg: Arg
}

const calc = <T extends (() => number) | number>(
  formula: Formula<T>
) => formula.fn(formula.arg);

Read more about generic types here

Did I understand you correctly?

Related