简体   繁体   中英

Arguments and return type not inferred properly in function typed as union

In the following code, I'm defining a Function union type GetThing which I'm then using for the getThing function.

However when I check the type of either name or thing , they're both any. I would expect name to be string and thing to be Thing1 | Thing2 Thing1 | Thing2

Additionally I would expect the return type of the getThing function to depend on the type of the thing argument, which should be inferred by that switch , but we're not even getting that far.

interface Thing1 {
    type: "thing1";
}

interface Thing2 {
    type: "thing2";
}


type GetThing1 = (name: string, thing: Thing1) => string;
type GetThing2 = (name: string, thing: Thing2) => boolean;
type GetThing = GetThing1 | GetThing2;

const getThing: GetThing = (name, thing) => {
   switch (thing.type) {
      case "thing1": {
          return "thingy";
      }
      case "thing2": {
          return true;
      }
   }
}

Is this a bug in Typescript, is this just too many layers of inference deep for Typescript to handle, or is there something I'm missing?

This is currently a limitation of Typescript.

(For your initial question, they're both any because it can't identify that function as being of type GetThing1 or GetThing2. But, you'd still have an issue even if you defined them manually to be (name: string, thing: Thing1 | Thing2) ).

I simplified your code a little, to:

type Foo = (v: number) => string;
type Bar = (v: string) => boolean;
type FooBar = Foo | Bar;

const fooBar: FooBar = (v: number | string) => {

   switch (typeof v) {
      case "number": {
          return "thingy";
      }
      case "string": {
          return true;
      }
   }
}

console.log(fooBar('Bob'));

Which may be a little clearer.

Your snippet doesn't type the inputs to getThing, but they're implied to be:

(name: string, thing: Thing1|Thing2)

So (using the above example for simplicity), FooBar is defined to be either a Foo , or a Bar . That is, either a (number)=>string , or a (string)=>bool .

From Typescript's pedantic point of view, (number|string) => string | bool (number|string) => string | bool is just not the same as either of those - it doesn't care that you've written it correctly and the types would work out.

I believe the core issue in this feature-request is the same, with a couple work-arounds given further down . It's apparently not a trivial thing for Typescript to handle, conceptually.

I'd recommend one of the following:

type Foo = (v: number) => string;
type Bar = (v: string) => boolean;
type FooBar = Foo | Bar;

let foo: Foo = (v: number) => {
    return "heyo";
}
let bar: Bar = (v: string) => {
    return true;
}

type FooBarDiscriminator = (v: number | string) => string | boolean;
const fooBar1: FooBarDiscriminator = (v: number | string) => {
    switch (typeof v) {
        case "number": {
            return foo(v);
        }
        case "string": {
            return bar(v);
        }
    }
}

function fooBar2(v: number | string) {
    switch (typeof v) {
        case "number": {
            return foo(v);
        }
        case "string": {
            return bar(v);
        }
    }
}

console.log(fooBar1(0));
console.log(fooBar1('Bob'));
console.log(fooBar2(0));
console.log(fooBar2('Bob'));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM