'Is there way to narrow function parameters witch have same union types signature to exactly same subtypes by generic type

// this is what i what for function parameters type
type Combinable = string | number;

function isString(param: unknown): param is string {
  return typeof param === "string";
}

// i expect function parameters a and b to have exactly the same subtype of Combinable like both are string or number(e.g. a: string and b: number is not i wanted),and finally the return type of this function must be the same subtype of a and b

function add<T extends Combinable>(a: T, b: T): T {
  if (isString(a)) {
    //and i get this following error
    //1st question: how can i fix this

    /* Type 'string' is not assignable to type 'T'.
  'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Combinable'.ts(2322) */

  // my 2nd question is here: why TS can not infer b must be type "string" since i narrow type of a to "string"?
    //type ta = typeof a  // answer is  string & T === string
    //type tb = typeof b  // answer is T     why T not string?
    return a + b;
  } else {
    return (a as number) + (b as number); //same error
  }
}

let a: Combinable = "123";
let b: Combinable = 123;

//3rd question here: i want to know that why TS can infer a and b must be the same subtype of Combinable here but it can not do this in the function body above?
add(a, b);  //constraint a and b in same sub type is what i expected when function called

My Questions:

  1. How can i fix this
  2. Why TS can not infer b must be type "string" since i narrow type of a to "string"?
  3. Why TS can infer a and b must be the same subtype of Combinable when function called but it can not do this in the function body above?

Appreciate your comments and answers.



Solution 1:[1]

If it's just about making sure the typescript compiler does his job, you can achieve it like this:

type Combinable = string | number;

function add<T extends Combinable>(a: T, b: T): T {
  return (a as number) + (b as number) as T;
}

let a: Combinable = "123";
let b: Combinable = 123;
const sumOfAB = add(a, b); // Argument of type 'number' is not assignable to parameter of type 'string'.(2345)

let c: Combinable = 123;
let d: Combinable = "123";
const sumOfCD = add(c, d); // Argument of type 'string' is not assignable to parameter of type 'number'.(2345)

let e: Combinable = 123;
let f: Combinable = 123;
const sumOfEF = add(e, f); // const sumOfEF: number

let g: Combinable = "123";
let h: Combinable = "123";
const sumOfGH = add(g, h); // const sumOfGH: string

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 W.S.