ZiiMakc
ZiiMakc

Reputation: 36876

Typescript error - could be instantiated with a different subtype of constraint

Typescript sandbox.

Sometimes based on code I know what return value will be and want to specify it using:

getSmth<string>(1)

and not

getSmth(1) as string

but not sure how to do it correctly


  1. Problem. Why there is errors if i extend and return correctly?
Type 'null' is not assignable to type 'T'.
  'null' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'string | null'.(2322)

Example

const getName = (id: number) : string | null => null

const getSmth = <T extends string | null = string | null>(id: number | null): T => {
  if (!id) {
    return null;
  }
  return getName(id);
};

const x1 = getSmth(1) // should be string | null
const x2 = getSmth<null>(1) // should be null
const x3 = getSmth<string>(1) // should be string

  1. Question. Why this assertion happening?
const getSmth2 = <T extends string | null = string | null>(id: number | null): T => {
  if (!id) {
    return null as T;
  }
  return getName(id) as T;
};

const y1: string = getSmth2(1) // why getSmth2 asserts to string when return type should bestring | null

Upvotes: 0

Views: 229

Answers (1)

Ingo B&#252;rk
Ingo B&#252;rk

Reputation: 20043

Why there is errors if i extend and return correctly?

T is not guaranteed to be nullable due to the constraint. For example, string extends string | null:

// true
type X = string extends string | null ? true : false;

So while the default you give to T is nullable, the compiler cannot ensure that this is true for all possible Ts. In fact, that's the very thing you're trying do do: passing string. But once T = string, returning null wouldn't be valid anymore.

You can type-assert in your implementation to make it work. This just shuts up the compiler about something that it is indeed correct about, though, but if that is what you want to do:

const getSmth = <T extends string | null = string | null>(id: number | null): T => {
  if (!id) {
    return null as T;
  }

  return getName(id) as T;
};
  1. Question. Why this assertion happening?

Essentially, it's the same issue. The added point here is that the compiler uses the declared type on the left to infer the generic type argument:

// T inferred to be string
const y1: string = getSmth2(1);

// T is inferred to be string | null
const y2: string | null = getSmth2(1);

You're expecting T to use the default, which happens in this case:

// string | null
const y3 = getSmth2(1);

Upvotes: 2

Related Questions