The reason Typescript is able to accurately determine the value when providing a Generics that extends specific types

Exploring Different Generics in TypeScript:

When using generics in TypeScript, the type of data you receive can vary based on how you define your functions.

// Example with string generic type
function getResult<T>(...v: T[]) {
  return v
}

const str = getResult('str', 'str2') // const str: string[]

// Another example with explicit string generic type
const str = getResult<string>('str' as const, 'str2') // const str: string[]

Different Results with Extending Generics:

// Using generics that extend specific types
function getResultByExtendsString<T extends string>(...v: T[]) {
  return v
}

const str2 = getResultByExtendsString('str', 'str2') // const str2: ("str" | "str2")[]

// Extending for number type
function getResultByExtendsNumber<T extends number>(...v: T[]) {
  return v
}

const num2 = getResultByExtendsNumber(123, 456) // const num2: (123 | 456)[]

Mixed Type Examples:

Generics can also be used with mixed types to create arrays of different possible data types.

type Types = string | number

function getAllResults<P extends Types, Q extends Types>(p: P, v: Q): (P | Q)[] {
  return [p, v]
}

const res = getAllResults('str', 123) // const res: ("str" | 123)[]

Understanding the Behavior:

If you're curious about why this behavior occurs, you can explore more by checking out TypeScript Playground.

Answer №1

The compiler's ability to use contextual typing allows for easy determination of data types in TypeScript. With this feature, the compiler can infer the type even when explicit type annotations are only provided on one side of an expression. Generic classes, functions, and variables are commonly utilized as "complex" types when the specific data type is unknown or intricate.

For more information:

https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html#types-by-inference

https://www.typescriptlang.org/docs/handbook/functions.html#inferring-the-types

Answer №2

This particular outcome is a direct result of microsoft/TypeScript#10676, titled "Always use literal types". In general, the compiler's initial inference for string, numeric, or boolean values tends to be the narrowest, which means that "str" is initially classified as the type "str".

Subsequently, the compiler may choose to broaden or "widen" the type depending on its usage. This means that while in some cases "str" might expand to string, in others it will remain as "str". The pull request mentioned earlier highlights various scenarios where this widening behavior occurs (e.g., const str = "str"; remains unchanged but let str = "str" gets widened). As a rule of thumb, types are usually expanded unless the compiler expects a narrower type.

Of specific relevance to this issue is how the compiler infers a type parameter when invoking a generic function:

When inferring type arguments for a function call, the inferred type for a type parameter T is widened to its broader literal form if:

  • All inferences for T pertain solely to top-level occurrences within the parameter type,
  • T lacks constraints or its constraints do not involve primitive or literal types,
  • And either T was fixed during inference or T does not surface at the top level in the return type.

Hence, T would undergo expansion if no constraints exist, as seen in getResult<T>(...), or if the constraint excludes primitive or literal types, exemplified by

getSomethingElse<T extends Date>(...)
. These instances yield string or number rather than string or number literals.

In contrast, if a constraint includes primitive types like in

getResultByExtendsString<T extends string>(...)
or
getResultByExtendsNumber<T extends number>(...)
, or even
getAllResults<P extends Types, Q extends Types>(...)
(given that Types encompasses string and number), no widening takes place. Consequently, "str" or 123 will retain their original forms as "str" and 123.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Unable to use innerHTML function on Blogger platform

I am currently working on a basic voting system. It functions perfectly when the two files are in the same location (locally). However, once I publish it on my blogger platform, the system fails to display the results. (When a user clicks to vote, it regi ...

Leverage the power of multiline JavaScript expressions within your React components

I am facing a situation where I have the following React function component source code: return ( result.map(item => ( <tr key={item.id}> <td> {new Date(item.pub_date).getFullYear()} / {new Date(item.pub_date).getMont ...

Is there a way to incorporate multiple rules from data into a text component using Vuetify?

I'm looking to establish specific rules within a mixin for my components. Allow me to provide a straightforward example of my request: Example Link The code snippet: <v-text-field :rules="[nbRules, requiredRules]" outlined v-model="name" label ...

Splitting the div into two columns

I've encountered various solutions to this issue, but when I integrate an Angular2 component inside the divs, it fails to function properly. Here is my progress so far: https://i.stack.imgur.com/qJ8a9.jpg Code: <div id="container"> <div ...

Warning: Be cautious of title array issues when implementing _document and page-specific next/head in Next.js

I've recently worked on setting up a basic _document file following the guidelines to add a Favicon and utilize Next/Head to define the title of each page (with plans to include more metadata). However, I encountered a warning message after making the ...

Revealing an Angular directive's functionality to a different module

Imagine having a module with a directive structured as follows (this is a rough, untested implementation) There are 3 fundamental requirements that need to be fulfilled: Set up configuration for the element display Add event listeners accessible by the b ...

Modifying the <TypescriptModuleKind> setting for typescript transpilation in project.csproj is not supported in Visual Studio 2017

I recently encountered an issue with changing the module kind used by the transpiler in Visual Studio. Despite updating the <TypescriptModuleKind> in the project's project.csproj file from commonjs to AMD, the transpiler still defaults to using ...

How can dynamically added data be retained in a table after reloading the page?

I have a piece of JavaScript code that is functioning properly, but the dynamically inserted data in the table gets lost after reloading the page. I am also implementing server-side validation and need to ensure that the dynamically added data persists e ...

Displaying related objects information from a single object in AngularFire2 can be achieved by following these steps

As a newcomer to Angular and Firebase, I apologize if my question seems basic. I'm seeking guidance on how to display related object information from one object in angularfire2. Specifically, I want to show the role names assigned to a user. Here is ...

Exploring the integration of angular-ui-select into an angular seed project

I set up a new project using the starter template from https://github.com/angular/angular-seed and now I'm attempting to integrate angular-ui-select for dropdown menus. I've added select.js and select.css files to my index.html as well as install ...

Managing the rendering of charts in Angular with Directives

I'm in the process of creating an admin page with multiple elements, each revealing more information when clicked - specifically, a high chart graph. However, I've encountered a challenge with the rendering of these charts using a directive. Curr ...

Submit information in two different formats; the initial format will not transmit any data until the second one is completed

At registration, I have divided the process into two forms. The user fills out the first set of information and then clicks "next" to proceed to the second set. Both sets of POST information are submitted upon clicking the final register button. This is m ...

What causes the Element to be null in Vue.js?

Could someone please clarify why the console.log output is showing as null, and provide guidance on how to resolve this issue? <template v-for="day in getMonthLength()"> <td> <input :id="day" type=number :value=&qu ...

Using the "this" keyword within a debounce function will result in an undefined value

It seems that the this object becomes undefined when using a debounce function. Despite trying to bind it, the issue persists and it's difficult to comprehend what is going wrong here... For instance, in this context this works fine and returns: Vu ...

I am looking to dynamically fill my form fields with data retrieved from my database through an AJAX call to another PHP file

I am attempting to dynamically populate my form fields with data retrieved from a database row using ajax. The goal is to send the id of the row I need when a specific button is clicked. Although I have managed to successfully fetch the desired row in the ...

JavaScript for returning a boolean output

I have been tasked with creating a webpage based on the requirements below Here is the current code that I am working with function validate() { var form = document.getElementsByTagName('form')[0]; if (form.tickets.value <= form.childr ...

Accessing nested objects within a JavaScript array for an Express API

My current data sample looks like this: var data = [{ articles : [{ id : '0', url : 'foo', title : 'Foo', body : 'some foo bar', category : 'foo', tags : ...

I possess an array with a specific structure that I wish to substitute the keys as demonstrated below

The data array is currently structured in this way. I have attempted various methods but have not been able to find a suitable solution. I need to send the JSON below via an AJAX request, however, I do not want the keys 0 and 1 in both child arrays. [sch ...

PHP contact form with AJAX functionality without page redirection

I'm currently working on a page that includes a contact form. The issue I'm facing is that when I click on "submit," the form redirects me to another page. What I actually want is for the page not to redirect, but instead display the success mes ...

Uploading standard image when selecting a file

I have been searching and struggling to find a solution on how to automatically attach a default image when the user clicks "Choose File" using JavaScript or jQuery. My intention is that if the user forgets to attach an image, a default image URL will be ...