How does the nonempty assertion of TS impact the inference of function generics?

My definition of the type and the variable is as follows: the LOCALES_KEYS variable is an enum

export const resources = {
  'ja-JP': jaJP,
  'zh-TW': zhTW,
  'en-US': enUS,
  'zh-CN': zhCN,
};

export type Lng = keyof typeof resources;

export type Resources = {
  [K in Lng]: (typeof resources)[K] & {
    [P in LOCALES_KEYS]: P extends keyof (typeof resources)[K]
      ? (typeof resources)[K][P]
      : '';
  };
};

export interface AppContextData extends DefaultData {
  language?: Lng;
  t: <T extends LOCALES_KEYS>(
    key: T,
  ) => Resources[keyof typeof resources][T];
}

const t: AppContextData['t'] = (key) => {
  const lang: Lng = 'en-US';
  return (resources as Resources)[lang][key];
};

When using the t function, everything works fine. However, when using t with non-empty assertions, the generic inference doesn't work as expected

t(LOCALES_KEYS.AVATAR); // The type when hovering over the t function is: const t: <LOCALES_KEYS.AVATAR>(key: HOME_KEYS.AVATAR, options?: TOptions) => "" | "Avatar" | "头像"

// Nonempty assertion
t!(LOCALES_KEYS.AVATAR);
// The type when hovering over the t function is: const t: <T extends LOCALES_KEYS>(key: T, options?: TOptions) => (({
//   readonly argots: "Argots";
//   readonly meta_desc: "A free chat platform that encrypts conversation information throughout
// the process to protect your security and privacy. No information is collected from you and no
// permissions are required from you. End of chat Clear all records";
//     ... 19 more ...;
//     readonly Invitation_description: "Each invitation link can only invite one user, and the
// invitation is invalid after success";
// } & {
//     ...;

The reason for this behavior is unclear to me. I attempted to search for relevant information on Google without success.

This code snippet is reproducible - Using '! ' affects generic inference

const t: <T>(val: T) => T = (val) => {
  return val;
};

t('111'); //  const t: <"111">(val: "111") => "111"
t!('111'); // const t: <T>(val: T) => T

Answer №1

Type inference remains unaffected. The variation lies in how TypeScript showcases the type during the utilization of IntelliSense. This operation is executed according to plan.

Initially, let's illustrate that inference persists unchanged:

const a1 = t('111'); //  const t: <"111">(val: "111") => "111"
//    ^? const a1: "111"
const a2 = t!('111'); // const t: <T>(val: T) => T
//    ^? const a2: "111"
const a3 = (t)('111'); // const t: <T>(val: T) => T
//    ^? const a3: "111"

Throughout the above three calls, the outcome type remains consistent as the string literal type "111". This aligns with the anticipation when a function of type <T>(val: T) => T is invoked with an argument of type "111", and that's precisely what unfolds. Hence, T is deduced as "111" in all the aforementioned calls.


The sole dissimilarity lies in what is visible in your IDE upon hovering over t. This discrepancy arises due to the varying elements under observation.

When hovering over t in t('111'), you are scrutinizing a function call directly. TypeScript opts to exhibit the type of the function with the type argument denoted, such as

<"111">(val: "111") => "111"
. It's noteworthy that this type is not a valid TypeScript type. The use of "111" as a type parameter name is infeasible. Essentially, it merely demonstrates <T>(val: T) => T with "111" substituted for T.

Conversely, when inspecting t in t!('111'), we are viewing a function that is not directly invoked. TypeScript perceives t as subjected to the non-null assertion operator. Since it is not invoked directly, there is no type argument substituting for the type parameter. The display solely indicates <T>(val: T) => T. If there was a method to request TypeScript to reveal the type of t! during invocation, one would likely observe

<"111">(val: "111") => "111"
. Nonetheless, a pointer hover only reaches the leaves of the abstract syntax tree (AST), thereby restricting such an action.

This circumstance is minimally related to the non-null assertion operator. Considering that t is already established as non-nullish, t! signifies the same value and type as t. This is evident when employing (t) as well. Formulating (t)("111") positions the function call at a non-leaf node of the AST, thereby omitting the display of "111" when identifying the type of t.


To summarize: TypeScript is aligning with its predetermined behavior. The operation of type inference remains entirely uninfluenced by ! or () in the scenarios cited above. The impact is solely directed towards type presentation, which essentially emerges as an ideological maneuver enacted by IDEs.

Playground link to code

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

Ways to retrieve the document ID or address in TypeScript when using Firestore

I am currently developing a function that will send notifications to all devices where a user is logged in whenever a new order document is added to their account. Below is the code I have written to execute this function. My main query revolves around ac ...

How to implement SVG in React with the image source as a parameter?

I've been working on a React component in NextJS that displays an image inside a hexagon. The issue I'm facing is that when I try to use multiple instances of this component with different images in the HexagonWall, all hexagons end up displaying ...

Mocking a promise rejection in Jest to ensure that the calling function properly handles rejections

How can I effectively test the get function in Jest, specifically by mocking Promise rejection in localForage.getItem to test the catch block? async get<T>(key: string): Promise<T | null> { if (!key) { return Promise.reject(new Error(&apo ...

Adjust the specific data type to match its relevant category

Is there a method to alter literal types in TypeScript, for instance. type T1 = ""; type T2 = 1 I am interested in obtaining string for T1 and number for T2. Regarding collections, I am unsure, but I assume it would involve applying it to the generic typ ...

Creating a new endpoint within the Angular2 framework using typescript

I am brand new to Angular2 and I would like to streamline my API endpoints by creating a single class that can be injected into all of my services. What is the most optimal approach for achieving this in Angular2? Should I define an @Injectable class sim ...

Unexpected date format displayed by the flat picker calendar

The expected date format is "DD-MM-YYYY" but the shown date format in the UI is "YYYY-MM-DD". Click here to view the UI image Initially, before opening the date picker, the date is displayed in the expected format as "DD-MM-YYYY". Upon opening the date p ...

Using parameters and data type in Typescript

When I remove <IFirst extends {}, ISecond extends {}> from the declaration of this function, the compiler generates an error. Isn't the return value supposed to be the type after the double dot? What does <IFirst extends {}, ISecond extends { ...

Encountering an error in Angular 8 with the plugin: Unhandled ReferenceError for SystemJS

I recently followed a tutorial on integrating plugins into my Angular application at this link. I'm trying to create a component in my Angular app that can execute and display an external component. However, I encountered the following error: Uncaugh ...

When trying to upload a file with ng-upload in Angular, the error 'TypeError: Cannot read properties of undefined (reading 'memes')' is encountered

Struggling with an issue for quite some time now. Attempting to upload an image using ng-upload in angular, successfully saving the file in the database, but encountering a 'Cannot read properties of undefined' error once the upload queue is comp ...

The issue of HTTP parameters not being appended to the GET request was discovered

app.module.ts getHttpParams = () => { const httpParamsInstance = new HttpParams(); console.log(this.userForm.controls) Object.keys(this.userForm.controls).forEach(key => { console.log(this.userForm.get(key).value) const v ...

How is it that in TypeScript, a potential numeric value in an interface can be transformed into an impossible numeric value in a class implementation?

Encountered a surprising behavior from the TypeScript compiler today. Unsure if it's a bug or intentional feature. If it is indeed intentional, I would like to understand the reasoning behind it. The issue arises when declaring an interface method wi ...

Is there a way to sort through nested objects with unspecified keys?

I'm looking to extract specific information from a nested object with unknown keys and create a new array with it. This data is retrieved from the CUPS API, where printer names act as keys. I want to filter based on conditions like 'printer-stat ...

Experiencing an Issue with NGINX Loading Vue 3 Vite SPA as a Blank White Page

I currently have the following NGINX configuration set up: events { worker_connections 1024; } http { server { listen 80; server_name localhost; location / { root C:/test; index index.html; ...

Is OnPush Change Detection failing to detect state changes?

Curious about the issue with the OnPush change detection strategy not functioning properly in this demonstration. My understanding is that OnPush change detection should activate when a property reference changes. To ensure this, a new array must be set e ...

Encountering a TypeScript error when attempting to utilize indexOf on a Typed Array, leading to restriction

I have been working with an Interface, where I created an array of type Interface. I am currently facing some IDE error complaints when trying to use the .indexOf method on the Array. These errors seem confusing to me, and I'm hoping someone here migh ...

The mat-spinner is continuously spinning without stopping

I am facing an issue with a component (dialog-component) that references another component (DATA-component). In the dialog-component-ts file, when I set isLoaded = false and then use this.isLoaded.emit(true); in DATA-component, the isLoaded value in dial ...

Exploring objects nested within other types in Typescript is a powerful tool for

My journey with TypeScript is still in its early stages, and I find myself grappling with a specific challenge. The structure I am working with is as follows: I have a function that populates data for a timeline component. The data passed to this function ...

What is the reason behind eslint not permitting the rule option @typescript-eslint/consistent-type-imports?

Upon implementing the eslint rule, I configured it like this. module.exports = { rules: { "@typescript-eslint/consistent-type-imports": [ "error", { fixStyle: "inline-type-imports" ...

What is the reason why modifying a nested array within an object does not cause the child component to re-render?

Within my React app, there is a page that displays a list of item cards, each being a separate component. On each item card, there is a table generated from the nested array objects of the item. However, when I add an element to the nested array within an ...

Error in Typescript: Attempting to access the property 'set' of an undefined value

Currently, I am in the process of setting up a basic example of push notifications on Android using Nativescript and Typescript. Although my code may seem a bit messy, I am struggling with properly rewriting "var Observable = require("data/observable");" a ...