3

Given this JavaScript function:

function foo({ a, b, c = a + b }) {
  return c * 2;
}

When I try to annotate it with types in typescript like this:

function foo({ a, b, c = a + b }: { a?: number, b?: number, c: number }): number {
  return c * 2;
}

I get the error: TS2532 Object is possibly undefined which makes sense, because I haven't told it yet that a and b are not optional when c is not specified.

However any attempts to specify this didn't work:

  1. Overloading (1):
function foo({ a, b, c = a + b }: { a: number; b: number; c: number }): number;
function foo({ c }: { c: number }): number {
  return c * 2;
}

TS2371: A parameter initializer is only allowed in a function or constructor implementation.

  1. Overloading (2):
function foo({ c }: { c: number }): number;
function foo({ a, b, c = a + b }: { a: number; b: number; c: number }): number {
  return c * 2;
}

TS2394: This overload signature is not compatible with its implementation signature.

This doesn't make sense to me, I don't see hhy TypeScript thinks that they are not compatible.

  1. Conditionals
function foo({
  a,
  b,
  c = a + b,
}: {
  a: typeof c extends number ? void : number;
  b: typeof c extends number ? void : number;
  c: typeof a extends number ? void : typeof b extends number ? void : number;
}): number {
  return c * 2;
}

TS2502: 'a' is referenced directly or indirectly in its own type annotation. TS2502: 'c' is referenced directly or indirectly in its own type annotation.

This makes sense, given that TypeScript is not able to solve for the recursion.

Does anyone know how this function could be strong-typed?

Note: I am very much aware that you can just change the prototype itself to have a different argument structure, but that would defeat the point of this exercise.

3
  • 1
    Is it good enough to be strongly typed on consumer side? When you define overloads - the last one is always implementation and not visible to callers. Playground Commented Apr 7, 2020 at 13:28
  • 1
    @AlekseyL. that seems to work too and is arguably easier to read. It will not catch type errors in the implementation side as the argument is cast to any. So this may not be a good choice for more complex implementations. So depending on how complex the implementation is we now have two options to choose from. Thanks! Commented Apr 8, 2020 at 11:25
  • 1
    A bit safer workaround :) typescriptlang.org/play/index.html#code/… Commented Apr 8, 2020 at 14:26

1 Answer 1

2

Here is a proposition, using union types :

type AB_Defined = {
    a: number,
    b: number,
    c?: number,
}

type AB_Nullable = {
    a?: never,
    b?: never,
    c: number,
}

type FunctionParams = AB_Defined | AB_Nullable

// here I need to cheat using a! and b! to tell it cannot be undefined
function foo({ a, b, c = a! + b! }: FunctionParams) {
  return c * 2;
}

foo({ a: 3, b: 2  });
Sign up to request clarification or add additional context in comments.

1 Comment

That works. Thanks. (+1) I'll leave this open for a while in case there is someone with a more elegant solution.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.