Home > Back-end >  Generic infers literal type when there is a union and conditional type is involved
Generic infers literal type when there is a union and conditional type is involved

Time:02-06

I stumbled upon this imho inconsistant behaviour:

A conditional alone works fine:

class FormControl<T> {
  constructor(t:T extends undefined ? never : T) {}
}
const a = new FormControl('');
//    ^? FormControl<string> // OK 

A union alone works also fine:

class FormControl2<T> {
  constructor(t:T|string) {}
}
const b = new FormControl2("");
//    ^? FormControl<string> OK 

When both union conditional are combined :

class FormControl3<T> {
  constructor(t:T extends undefined ? never : T|string) {}
}
const c = new FormControl3(""); 
//    ^? FormControl<""> // Too narrow :( 

How could a get the wider string infered type in that 3rd case ? I'm looking for the widen inference not specifying each time time the correct type.

Playground

CodePudding user response:

After a bit of reading, it probably has to do with usage of literal types by typescript as presented in : https://github.com/microsoft/TypeScript/pull/10676

During type argument inference for a call expression the type inferred for a type parameter T is widened to its widened literal type if:

  • all inferences for T were made to top-level occurrences of T within the particular parameter type, and
  • T has no constraint or its constraint does not include primitive or literal types, and
  • T was fixed during inference or T does not occur at top-level in the return type.

CodePudding user response:

This is a very interesting question!

I'm afraid I don't know why this happens despite I've spent way more time than I'd like to admit trying to figure it out.

As per your last question, you can force an string by doing this:

const c = new FormControl3("" as string); 
//    ^? FormControl<string>

Alternatively, you can also do this:

const c = new FormControl3<string>(""); 
//    ^? FormControl<string>
  • Related