1

I have a key that can either be of type number or type [number] or type [number, number];

I created a type for each and a union type that can be either:

type Foo    = [number, number?];
type Bar    = number;
type FooBar = Foo | Bar;

I then wrote a compare method that would analyze two of these keys.

My issue is, I want to limit the compare so that the two parameters must be of the same type. IE compare of Foo and Foo or Bar and Bar but not Foo and Bar.

If I make the compare method as follows:

compare(key1: FooBar, key2: FooBar)

then it will accept this kind of miss matched compare: compare([1], 1)

If I define some more types as such:

type FooFoo = [Foo, Foo];
type BarBar = [Bar, Bar];

and change the compare signature to:

compare(keys: FooFoo|BarBar)

It will correctly limit the two keys to be the same type but that means that you have to pass the compare parameter as an array, which is ugly. So I decided to try and use the spread operator ...

compare(...keys: FooFoo|BarBar)

But it returns an error saying that FooFoo needs to be an array. If I use compare(...keys: FooFoo) it doesn't complain (but is limited to only FooFoo). And if I use compare(...keys: BarBar) it also doesn't complain (but is limited to only BarBar).

Is there any way to correctly overload this method to be equivalent to:

//an example of what I want.
//Using Java or C#'s overloading style for illustration

function compare(k1: Bar, k2: Bar): boolean {
   return compare([key1], [key2]);
}

function compare(k1: Foo, k2: Foo): boolean {
  if (k1 === k2) { return true; }
  if (Array.isArray(k1) && Array.isArray(k2)) {
      return k1[0] === k2[0] && (k1[1] === k2[1] ||
        (!isSet(k1[1]) && !isSet(k2[1])));
  }
  return false;
}

//this would result in allowing and not allowing the following:
compare(1, 2); //valid
compare([1], [2]); //valid
compare([1, 2], [3, 4]); //valid
compare([1, 2], [3]); //valid
compare([1], [2, 3]); //valid

compare([1], 2); //not valid
compare([1, 2], 3); //not valid
compare(1, [2]); //not valid
compare(1, [2, 3]); //not valid

I've set up a stackblitz example with everything laid out. I hope what I am trying to do is possible. Thanks for any suggestions.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Curtis
  • 3,170
  • 7
  • 28
  • 44
  • You can use TypeScript overloading. – SLaks Oct 11 '18 at 18:18
  • Have you tried with TypeScript 3.1? I think this issue has been [fixed](https://github.com/Microsoft/TypeScript/pull/26676). – jcalz Oct 11 '18 at 18:38
  • @SLaks I did try that. I hadn't realized I needed 3 method signatures though. So I got that part working. But ran into a new problem... When the variables being based in are set directly it works, but if they are set through a JSON parse (as they are in my use case - the data is coming from an API) it gives an error. https://stackblitz.com/edit/typescript-spread-issue1?file=index.ts Is there any way around this without needing to check the type before calling compare? – Curtis Oct 11 '18 at 18:54
  • @jcalz yes I am using version 3.1.1 and was still getting the error with the spread operator – Curtis Oct 11 '18 at 19:00
  • I cannot reproduce your error in 3.1.1: `function f(...x: [string, string] | [number, number]): void {}` compiles fine in 3.1.1, but fails in 3.0 and earlier. How sure are you that you're using 3.1.1? – jcalz Oct 11 '18 at 19:21
  • @jcalz sorry my mistake, sorry. Stackblitz is running an older version so it doesnt work there. And when I moved the code over to VC which is running 3.1.1 I had copied the code incorrectly. So yes, now I have it working with the spread operator, thanks. But I'm now running into the issue I mentioned above about it complaining when I use a variable that has been parsed from JSON and not been explicitly typed. Any ideas? – Curtis Oct 11 '18 at 19:36
  • 1
    I don't think people are going to come to these comments looking for an answer to your followup question, so please ask it separately. – jcalz Oct 11 '18 at 19:40

1 Answers1

0

This was fixed in Typescript version 3.1 (thanks jcalz)

For a work around in older versions you can use TypeScript overloading. (thanks SLaks). A note when using TypeScript overloading: You have to make a signature for each overload and a generic signature that will support all of the overloads together. See here. This initially tripped me up a bit so I thought it was worth repeating.

Curtis
  • 3,170
  • 7
  • 28
  • 44