Home > Software design >  Typescript calling conditional typed function within another function
Typescript calling conditional typed function within another function

Time:05-06

I was trying to make a conditional-type function, and come across this stackoverflow question, but unfortunately it doesn't seem to work well with default value (doesn't matter where the default value sits on), so I followed the advice there and use overload instead.

Things do seem to work...until I need to call that function in a wrapper. Here is a strip-downed version:

// This works:
// Conditional type function with default value
// according to https://stackoverflow.com/questions/57057697
type UnionType="one"|"two"|"three";
// function check(t?:"one"):1; // Does not matter
function check(t:"one"):1;
function check(t:"two"):2;
function check(t:"three"):3;
function check(t:UnionType="one") {
  if(t=="one"){
    return 1;
  }else if(t=="two"){
    return 2;
  }else if(t=="three"){
    return 3;
  }
  return 1;
}

// This gives error:
// Calling that conditional type function within another function,
// using the same union type, but only one call
function check2(t:UnionType):1|2|3 {
  return check(t);// <--- complain
}

// This works though:
// Calling that conditional type fucntion within another function,
// using multiply identical call (redundant type check)
function check3(t:UnionType):1|2|3 {
  if(t=="one"){
    return check(t);
  }else if(t=="two"){
    return check(t);
  }else if(t=="three"){
    return check(t);
  }
  return check(t);
}

TypeScript playground

This code does compile, but you can see in the playground that compiler complains check(t) line in check2() do not match any overload, while in check3() it has no problem, even though we are doing exactly the same thing in those two functions, with just redundant type check.

Is this behavior intended? What is the correct way to do conditional type function with default value?

CodePudding user response:

There is a different duplicate question for this that I believe answers your issue.

Here's the core issue as quoted from the accepted answer on that thread:

Passing Unions to An Overload Typescript is not able to "split up" the union before checking it against your overload signatures. It is checking your variable of type Promise | string against each overload individually. The union is not assignable to either of its members so there is no overload signature that accepts Promise | string.

This is a known behavior and some of the GitHub issues about this date back years.

Essentially, typescript can't compare your UnionType to each of the overloads as you would expect. It works in the last function because you've guarded the type with the redundant value checks, thus telling typescript that t will ONLY be those specific values before going into check(t), for which overloads exist.

When you do check(t:UnionType) without those redundant checks, it won't work because it's comparing "one"|"two"|"three" against "one", and then against "two", and then against "three". Those comparisons don't equate and hence it fails.

CodePudding user response:

This behaviour is described in Function Overloads section of TS handbook:

The signature of the implementation is not visible from the outside. When writing an overloaded function, you should always have two or more signatures above the implementation of the function.

You can add yet another overload with signature matching the implementation:

function check(t:"one"):1;
function check(t:"two"):2;
function check(t:"three"):3;
function check(t:UnionType):1|2|3;
function check(t:UnionType="one") {
  if(t=="one"){
    return 1;
  }else if(t=="two"){
    return 2;
  }else if(t=="three"){
    return 3;
  }
  return 1;
}

playground link

  • Related