Home > Enterprise >  Optional second argument must be defined if first argument is of a certain type
Optional second argument must be defined if first argument is of a certain type

Time:01-06

I have a function that is meant to be called as either

myFunc("")
myFunc({}, {}) // two different objects

it can also be called like

myFunc("", {})

in which case the second argument is simply ignored.

I want to prevent it from being called without a second argument if the first argument is an obj instead of a string

myFunc({}) // should type error

I implement this like this

type SomeType = { name: string }
type SomeOtherType = { config: number }

function myFunc(firstArg: string | SomeType, secondArg?: SomeOtherType): number {
  if (typeof firstArg === "string") {
    return doStringStuff(firstArg)
  }
  if (secondArg === undefined) {
    throw new Error('secondArg must be defined when firstArg is SomeType')
  }
  return doObjStuff(firstArg, secondArg)
}

(where doStringStuff() and doObjStuff() both return numbers) but is there a better way to do this? At the type level?

CodePudding user response:

Seems that function overloading works here, surprisingly even for variable argument counts (Sandbox):

function myFunc(p1: string): void;
function myFunc(p1: object, p2: object): void;
function myFunc(p1: string | object, p2: object | null = null): void { }

myFunc(''); // OK
myFunc({}, {}); // OK
myFunc({}); // Error: Argument of type '{}' is not assignable to parameter of type 'string'.
myFunc('', {}); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.
myFunc({}, ''); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.
myFunc('', ''); // Error: Argument of type 'string' is not assignable to parameter of type 'object'.

CodePudding user response:

You can use overloads to ensure that in TypeScript you can't call with a string and SomeTye:

function myFunc(firstArg: string): void
function myFunc(firstArg: SomeType, secondArg: SomeOtherType): void
function myFunc(firstArg: string | SomeType, secondArg?: SomeOtherType) {
  if (typeof firstArg === "string") {
    return doStringStuff(firstArg)
  }
  if (secondArg === undefined) {
    throw new Error('secondArg must be defined when firstArg is SomeType')
  }
  return doTypeStuff(firstArg, secondArg)
}


myFunc("", { name : ""}) // error
myFunc("") // ok

You could also use a union of tuples in rest parameters to help TypeScript understand the relationship between parameters:


function myFunc(...{ 0:firstArg, 1:secondArg, length }: 
  | [firstArg: string]
  | [firstArg: SomeType, secondArg: SomeOtherType]) {
  if (length === 1) {
    return doStringStuff(firstArg)
  }
  return doTypeStuff(firstArg, secondArg)
}


myFunc("", { name : ""}) // error
myFunc("") // ok

Playground Link

The union of tuples will act like overloads at call site so your callers aren't expose to the tuples

  • Related