Can the narrowing of types impact a generic parameter within a TypeScript function?

Is there a way to make TypeScript infer the type of the callback parameter inside the funcAorB function correctly? While TypeScript can deduce the type of the callback parameter when calling funcAorB, it fails to do so within the body of funcAorB. I was expecting TypeScript to use the same type as inferred for the data parameter throughout the function, given that it accurately determines the type of the data parameter based on the key member in the switch statement.

I appreciate that TypeScript infers the type of callback correctly when calling funcAorB, but I wish this inference extended to the internals of funcAorB.

TypeScript Playground

type DataA = {
  readonly key: 'a',
};

type DataB = {
  readonly key: 'b',
};

function funcA(data: DataA, callback: (data: DataA) => void) {}
function funcB(data: DataB, callback: (data: DataB) => void) {}

function funcAorB<T extends DataA | DataB>(data: T, callback: (data: T) => void) {
  switch (data.key) {
    case 'a': {
      // TypeScript believes that data is of type DataA now.
      // However, I need to use "as" because of TypeScript's assumption that
      // callback could be ((data: DataA) => void | (data: DataB) => void).
      funcA(data, callback as (data: DataA) => void);
      break;
    }
    case 'b': {
      // TypeScript believes that data is of type DataB now.
      // However, I need to use "as" because of TypeScript's assumption that
      // callback could be ((data: DataA) => void | (data: DataB) => void).
      funcB(data, callback as (data: DataB) => void);
      break;
    }
  }
}

// The callback type is inferred properly at the call site.
funcAorB({ key: 'a' }, (data) => {
  // This works because data is inferred to be DataA.
  const a: 'a' = data.key;
});

// The callback type is inferred properly at the call site.
funcAorB({ key: 'b' }, (data) => {
  // This works because data is inferred to be DataB.
  const b: 'b' = data.key;
});

Answer №1

Currently, TypeScript lacks the ability to utilize control flow analysis to narrow or restrict a generic type parameter. While it can narrow the apparent type of a value of a generic type, the generic type parameter itself remains unchanged. This means that even if data is narrowed to DataA or DataB, T extends DataA | DataB will not be altered.

One of the reasons for this limitation is the inability of TypeScript's type system to express the necessary changes needed for T. For instance, consider a scenario where:

const dA: DataA = { key: "a" };
const dB: DataB = { key: "b" };
const dAorB = Math.random() < 0.5 ? dA : dB;
funcAorB(dAorB, data => { data.key /* "a" | "b" */ });
// function funcAorB<DataA | DataB>(
//   data: DataA | DataB, callback: (data: DataA | DataB) => void
// ): void

In this case, T is defined as the full union type

Data | DataB</code. Therefore, narrowing <code>T
to just DataA or DataB, or constraining T to T extends DataA or T extends DataB would be incorrect. While you may only be able to add a lower-bound restriction on
T</code as suggested in the feature request <a href="https://github.com/microsoft/TypeScript/issues/14520" rel="nofollow noreferrer">microsoft/TypeScript#14520</a>.</p>
<p>If TypeScript had the capability to perform these actions, it could deduce that the <code>callback
passed to funcA should be between (data: DataA | DataB) => void and (data: DataA) => void, leading to smooth compilation without errors. However, due to current limitations, this functionality is not supported.


Several open feature requests are seeking support for control-flow-based generic re-constraining. One such request can be found at microsoft/TypeScript#33014, which aims to allow indexing with a generic index while lower-bounding the type parameter.

Another notable request is documented at microsoft/TypeScript#27808, proposing the explicit prohibition of unions in the specified type parameter to improve type inference. By addressing these issues, you can define T more accurately based on your requirements.

If you wish to show your support for these enhancements, consider contributing to the related GitHub issues mentioned above. However, it is advisable to find workarounds until these features are officially implemented.


An easy workaround involves using type assertions as previously done. A more complex solution entails refactoring code to leverage generics effectively, as described in microsoft/TypeScript#47109. This approach may involve using an appropriately generic function within funcAorB to align with the compiler's expectations. If this method does not suit your needs, exploring alternative strategies might be beneficial.

To experiment with the code and explore different approaches, you can access the Playground link provided.

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

Exploring the module and module resolution options in the TypeScript compiler

It seems that many tsconfig.json examples include the compilerOptions section with entries like this: "module": "commonjs", "moduleResolution": "node" Setting both values to commonjs and node respectively may seem r ...

The 'admin' attribute is not found in the 'Object' data type

I have been facing this issue for quite some time now. The backend API response is indicating that a certain property does not exist, even though it clearly does. My Angular application suddenly started showing 18 errors today, and I am at a loss on how ...

Updating nested interface values using React hooks

I am looking to develop an application that can seamlessly update a nested configuration file after it has been imported (similar to swagger). To achieve this, I first created a JSON configuration file and then generated corresponding interfaces using the ...

React TypeScript - creating a component with a defined interface and extra properties

I'm completely new to Typescript and I am having trouble with rendering a component and passing in an onClick function. How can I properly pass in an onClick function to the CarItem? It seems like it's treating onMenuClick as a property of ICar, ...

What is the best way to bypass using an if/else statement in TypeScript when dealing with mocha and returning undefined values

A unique spline able to be intertwined and produce a new version of itself, most of the time. export default class UniqueSpline { public intertwinedCount: number; constructor(parent?: UniqueSpline) { this.intertwinedCount = parent && pare ...

Generate an instance containing attributes that correspond to constant string values within a class

In the world of TypeScript, I have a scenario that might be a bit tricky, but I'll throw it out there anyway. Let's say I start with a base service class: export default class Service { public static readonly accessorName: string constructo ...

Recursively map elements of a TypeScript array to keys of an object

I am looking to create a structured way to specify paths for accessing objects, ensuring that the path is correctly typed based on the object type. Let me illustrate with an example. Consider the following data: const obj = { name: 'Test', ...

What is the best way to retrieve a function's response depending on the parameters provided?

I am trying to figure out how to determine the data types of copied array elements in my code. let inputArray = [ { test: 1, }, { test: 2, }, ]; function clone(array: any[]): any[] { return Array.from(inputArray); } ...

TS18047 jest error: "object may be null"

I'm currently working on a test (jtest) for an angular component, but it keeps failing due to this particular error. Any thoughts on how to resolve this? :) it("should require valid email", () => { spectator.component.email.setValue( ...

Utilizing Angular 9's inherent Ng directives to validate input components within child elements

In my current setup, I have a text control input component that serves as the input field for my form. This component is reused for various types of input data such as Name, Email, Password, etc. The component has been configured to accept properties like ...

Building React Typescript Components with Froala Editor Plugins

Attempting to integrate a custom plugin into a Froala Editor within my React application using the package react-froala-wysiwyg. Following a tutorial on incorporating a custom popup/plugin found here. Encountering an issue due to TypeScript incompatibility ...

Discover the highest value within an array of objects, along with any numerical object attributes that have a value greater than zero

Considering an array of objects structured as follows: [{ "202201": { "WO": 900, "WS": 0, "SY": 0.915, "LY": 0.98, "CT": 75 }, "202202" ...

Join the Observable in Angular2 Newsletter for the latest updates and tips

One of my functions stores the previous URL address. prevId () { let name, id, lat, lng; this.router.events .filter(event => event instanceof NavigationEnd) .subscribe(e => { console.log('prev:', this.previo ...

Adding a custom role in Angular TypeScript in Microsoft AppInsights is a straightforward process that can provide valuable

I have an angular project where I am looking to incorporate AppInsight with custom telemetry (role). The project is built in Angular using TypeScript, and I successfully integrated appinsights by following this tutorial. However, when attempting to add cus ...

Testing the receiveMessage function in SQS using Jest unit tests

Struggling to find the right approach for unit testing this function. I almost have it, but can't quite nail it down. Take a look at the function below: receiveMessage(callback: Function): any { this.sqs.receiveMessage( this.params, ...

Using Dropbox for seamless navigation

My navigation using Dropbox is not redirecting to the selected page as expected. Below, I have provided code and a demo for your reference. App Routing Module import { NgModule } from '@angular/core'; import { CommonModule } from '@angular ...

List the attributes that have different values

One of the functions I currently have incorporates lodash to compare two objects and determine if they are identical. private checkForChanges(): boolean { if (_.isEqual(this.definitionDetails, this.originalDetails) === true) { return false; ...

Remove an element from an array within objects

Need help with removing specific items from an array within objects? If you want to delete all hobbies related to dancing, you may consider using the splice method const people = [{ id: 1, documents: [{ ...

What is the best way to save data from a Firebaselistobservable into an array?

I've been attempting to transfer data from Firebase to an array using Angular 2, but I'm facing difficulties in pushing the data into the array. Below is the code snippet: Variables: uid: string = ''; agencyItems: FirebaseListObserva ...

Utilizing the polymer paper-dialog component in an Angular 2 TypeScript application

I have imported the paper-dialog from bower, but I am facing an issue with showing the dialog using the open() method. app.component.html <paper-icon-button icon="social:person-outline" data-dialog="dialog" id="sing_in_dialog" (click)="clickHandler()" ...