David Upsdale
David Upsdale

Reputation: 205

TS incorrectly inferring intersection type

We hit a TS error that we weren't able to resolve, and I was wondering if anyone knows how to fix it. TS seems to infer a an intersection type on combo.input, which seems odd (a union type makes more sense to me).

I boiled down an example below.

We ended up just using a different method, but I feel there should be a way to type the functions to fix the error.

const stringFunc = (input: string): string => input;
const numFunc = (input: number): number => input;

const stringCombo = { funct: stringFunc, input: 'hello' };
const numCombo = { funct: numFunc, input: 4 };

type Combo = typeof stringCombo | typeof numCombo;

const processCombo = (combo: Combo) => {
  return combo.funct(combo.input);  
     // Argument of type 'string | number' is not assignable to parameter of type 'never'.
};

Upvotes: 2

Views: 250

Answers (2)

Lesiak
Lesiak

Reputation: 25976

On top of answer by Titian Cernicova-Dragomir, you could also model your types as generics:

type FunTtoT<T> = (t: T) => T;

const stringFunc: FunTtoT<string> = (input: string): string => input;
const numFunc: FunTtoT<number> = (input: number): number => input;

type Combo<T extends string | number> =  { funct: FunTtoT<T>, input: T };

const stringCombo = { funct: stringFunc, input: 'hello' };
const numCombo = { funct: numFunc, input: 4 };

function processCombo<T extends string | number>(combo: Combo<T>) {
  return combo.funct(combo.input);  
};

const s = processCombo(stringCombo);  // inferred to string
const n = processCombo(numCombo);     // inferred to number

I prefer this approach both to discriminated union and type assertion, as the output type is inferred to a single type, not to an union.

Playground

Upvotes: 3

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249676

This is not wrong, more a design limitation. combo.funct is a union of functions. This means that it can be a either a function that accepts a string or a function that accepts a number, but at the the time of the call there is no way to know which it is. Consequently the only safe way to call combo.funct is with a parameter that can satisfy BOTH function signatures. In this case that type would be number & string which reduces to never (if these were functions accepting different object types, there could be a value that satisfies both object types)

In this particular case the function and the argument are actually corelated, but typescript doesn't track this. All it can see is that the type of combo.input (which is string | number) does not work as am argument for the union of functions combo.funct.

You could use a discriminated union:

const stringFunc = (input: string): string => input;
const numFunc = (input: number): number => input;

const stringCombo = { funct: stringFunc, input: 'hello', type: "string" as const };
const numCombo = { funct: numFunc, input: 4 , type: "number" as const};

type Combo = typeof stringCombo | typeof numCombo;

const processCombo = (combo: Combo) => {
  return combo.type === "string"?
      combo.funct(combo.input):
      combo.funct(combo.input);
};

Playground Link

Or since this a case where we clearly know more than the compiler we can use a type assertion:

const processCombo = (combo: Combo) => {
  return combo.funct(combo.input as never);  
};

Playground Link

Upvotes: 3

Related Questions