TypeScript functions with Generic Constraints return specific values rather than just types

function createGenericCoordinates<Type extends number | string>(
  x: Type,
  y: Type
) {
  return { x, y };
}

const genericCoordinates = createGenericCoordinates(1, 2);

// Error (TS2322)
// Type 3 is not assignable to type 1 | 2
genericCoordinates.x = 3;

Is there a way to modify this function so that both x and y can only be of type number or string regardless of the values passed as arguments (like in the scenario of 1|2 here)? Removing "extends number | string" allows it to work, but then other types might be permitted. This was tested using TS version 5.1.6.

Answer №1

TypeScript employs a variety of heuristic rules to determine whether a string or numeric literal like "a" or 2 should be assigned a specific literal type such as "a" or 2, or if it should receive an equivalent widened type like string or number. These rules have been proven effective in numerous real-world coding scenarios, although they may not always align perfectly with everyone's expectations.

These rules were predominantly featured and explained in the microsoft/TypeScript#10676 resource. Specifically, "during type argument inference for a call expression, the inferred type for a type parameter T is widened to its widened literal type when [...] T has no constraint or its constraint does not encompass primitive or literal types". Thus, in the context of a generic function:

function createGenericCoordinates<T extends number | string>(
    x: T, y: T
) { return { x, y }; }

the type parameter T carries a constraint that includes the primitive types string and number; thus, the type argument for T will not undergo widening.

To modify this behavior, one potential approach would involve eliminating the constraint from T and structuring the remainder of your call signature to enforce the desired constraint. For example:

function createGenericCoordinates<T>(
    x: T & (string | number), y: T & (string | number)) {
    return { x, y };
}

By doing so, T becomes unconstrained leading to widening upon inference of T. When invoking createGenericCoordinates(x, y), the compiler infers T from the type of x, ensuring that both x and y are assignable to the intersection T & (string | number). Any inconsistency will result in an error:

createGenericCoordinates(true, false); // error!
// --------------------> ~~~~
// function createGenericCoordinates<boolean>(x: never, y: never);

createGenericCoordinates({}, {}); // error!
// --------------------> ~~
// function createGenericCoordinates<{}>(x: string | number, y: string | number);

The compiler rejects true due to

boolean & (string | number)</code reducing to <code>never
, and {} being rejected because of
{} & (string | number)</code which reduces to <code>string | number
.

When calling the function with two strings or two numbers, there won't be any errors, and the lack of widening provides the intended outcomes:

createGenericCoordinates(1, 2);
// function createGenericCoordinates<number>(x: number, y: number);

createGenericCoordinates("a", "b");
// function createGenericCoordinates<string>(x: string, y: string);

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

Validate that the input is an array

Looking for a way to determine if a function parameter is an array or not, and then process it accordingly. If the parameter is not an array, convert it into an array before performing the desired function. For example: interface employee { first: st ...

Ways to require semicolons in a Typescript interface

When declaring a TypeScript interface, both , (comma) and ; (semicolon) are considered valid syntax. For example, the following declarations are all valid: export interface IUser { name: string; email: string; id: number; } export interface IUser { ...

Encountering a 404 error for core.js and browser.js while loading an Angular 2 app through system.src.js

I am new to Angular2 and have followed the Angular2 quickstart and tutorial to get started. Just to provide some context, when a user clicks on a link in the top navigation bar of my webapp, it triggers a server side request. The resulting page returned t ...

Using TypeScript with Angular: encountering a ReferenceError stating that the System object is not defined in the System

I attempted to follow a tutorial to set up Angular 2 with TypeScript from the following link: https://angular.io/guide/quickstart However, I encountered the following error: ReferenceError: System is not defined System.config I am uncertain why this e ...

What is the best way to send multiple values from the view to a method without using two-way binding?

When I change the dropdown value for the route type in my code, I need to pass both the gender value and the route type ID to my data retrieval method. Currently in my HTML file, I have only written a change event. I attempted two-way binding but encounte ...

Can we leverage Angular service styles in scss?

I am working on a change-color.service.ts file that contains the following code snippet: public defaultStyles = { firstDesignBackgroundColor: '#a31329', firstDesignFontColor: '#ffffff', secondDesignBackgroundColor: '#d1 ...

Discovering the position of an element within an array and leveraging that position to retrieve a corresponding value from a separate array

const STATE = ["TEXAS","CALIFORNIA","FLORIDA","NEW YORK"] const STATE_CODE = ["TX","CA","FL","NY"] With two arrays provided, the first array is displayed in a dropdown menu. The challenge is to retrieve the corresponding state code from the second array ...

Trouble displaying object in template using Angular's ngrx

Within my Typescript interface, I have declared the following structure: export interface FolderList { ADMIN: [Folder[], string, string]; SUPERADMIN: [Folder[], string, string]; USER: [Folder[], string, string]; } The 'Folder' interface is de ...

Display the default text using ngx-translate if a key is not found or while the translation file is loading

Currently in the process of setting up a brand new Angular 7 application. I am interested in establishing a default text for translation purposes. Specifically, when utilizing the translation {{ 'wait' | translate }}, I would like to ensure that ...

What is the best way to change multiple parameters using ngModelChange?

I have a requirement in my application to update 3 values using a single ngModelChange event. The component structure is as follows: model: any = {}; images: any; public input = true; public dropdown = false; images : any; constructor(...services) { } ...

Moving from Http to HttpClient in Angular4Changeover your Angular4

I recently migrated my Angular app to use the new HttpClient, but I'm encountering some challenges in achieving the same results as before with Http. Can anyone help me out? Here's what I was doing with Http: getAll() { return this.http.get ...

Immediate update: Receiving a status of 400 "Bad request" when trying to make an HTTPPOST

After tirelessly searching through various online resources like Google and Stack Overflow to troubleshoot my code without success, I find myself resorting to posting a question here. The issue at hand involves two nearly identical functions in my service ...

Is there a substitute for useState in a Next.js server component?

With my static site at , the only interactive feature being the dark mode toggle, I understand that using useState is not feasible in a server component. export default function RootLayout({ children }: { children: React.ReactNode }) { const [darkMode, ...

What is causing TypeScript to compile and remove local variables in my Angular base controller?

I am trying to develop a base controller in Typescript/Angular for accessing form data, but I'm encountering an issue where the form member seems to be getting removed during compilation and is not present in the generated JavaScript code. Could you ...

Angular 2 lacks compatibility with SVG

I have a website where I want to include SVG images inline with my HTML using Angular 2. However, when I try to load the SVG icons within a component, they do not display correctly. Instead, I see the following: https://i.sstatic.net/Ozo6E.png You can vi ...

Ways to ensure that your Angular component.ts file is executed only after the body has completely loaded without relying on any external

I am attempting to add an event listener to an element created with a unique identifier using uuid, but I keep getting an error that states "Cannot read properties of null (reading 'addEventListener')" export class CommentItemComponent implements ...

Can the return type of a function be determined dynamically at runtime by analyzing a function parameter value?

Let's delve into this concept with an illustrative example! interface Query { foo: any; } interface Mutation { bar: any; } type OperationName = keyof Query | keyof Mutation; const request = async <T>(args, operationName: OperationName): P ...

"Learn the trick of converting a stream into an array seamlessly with RxJs.toArray function without the need to finish the

In order to allow users to filter data by passing IDs, I have created a subject that can send an array of GUIDs: selectedVacancies: Subject<string[]> = new Subject(); selectedVacancies.next(['a00652cd-c11e-465f-ac09-aa4d3ab056c9', ...

MUI version 5 with styled-components and ListItemButton: The specified property 'to'/'component' is not recognized

While transitioning from MUI v4 to v5, I encountered a hurdle: in v4, I utilized makeStyles(), but now aim to fully switch to styled(). Unfortunately, I am struggling to get Typescript to recognize a styled(ListItemButton)(...) with to= and component= attr ...

Best approach for managing Union Types in Angular 16 Templates / Utilizing Type Inference?

Currently, I'm immersed in a project using Angular 16 where my focus lies on applying a reactive declarative method. Oftentimes, I find myself working with Observables that emit varying data types - either successful data or an error object. Each ret ...