Relay of typescript return function types

I have created a class that utilizes the visitor design pattern:

abstract class MyNode {};
class MyNodeA extends MyNode {};
class MyNodeB extends MyNode {};

abstract class NodeVisitor {
  abstract visitMyNodeA(node: MyNodeA): unknown;
  abstract visitMyNodeB(node: MyNodeB): unknown;

  public visit(node: MyNode) {
    if(node instanceof MyNodeA) {
      return this.visitMyNodeA(node);
    } else if(node instanceof MyNodeB) {
      return this.visitMyNodeB(node);
    } else {
      throw new Error('Unknown node type on visitor');
    }
  } 
}

In my implementation of NodeVisitor, I would like to define custom return types for each visit function.

class MyNodeVisitor extends NodeVisitor {
  visitMyNodeA(node: MyNodeA): number {
    return 1;
  }
  visitMyNodeB(node: MyNodeB): number {
    return this.visit(new MyNodeA()) + 1;
  }
}

However, there is an error being generated by the TypeScript compiler. It does not recognize that calling visit with a parameter of type MyNodeA should redirect to the visitMyNodeA function, which now returns a number.

What steps can I take to successfully implement this solution?

Answer №1

Absolutely, the compiler isn't able to deduce that on its own. To assist it in this regard, you can provide some guidance, albeit at the cost of increased complexity (and reduced type safety within the visit() function). My recommendation is to furnish visit() with a generic declaration where the return type is contingent on a conditional type tied to the polymorphic nature of the this type found in subclasses:

abstract class MyNode {myNode = "myNode"}
class MyNodeA extends MyNode {a = "a"}
class MyNodeB extends MyNode {b = "b"}

abstract class NodeVisitor {
  abstract visitMyNodeA(node: MyNodeA): unknown;
  abstract visitMyNodeB(node: MyNodeB): unknown;

  // defining the call signature    
  public visit<T extends MyNode>(
    node: T
  ): T extends MyNodeA ? ReturnType<this["visitMyNodeA"]> : 
     T extends MyNodeB ? ReturnType<this["visitMyNodeB"]> : 
    never;

  // broader implementation signature
  public visit(node: MyNode): unknown {
    if (node instanceof MyNodeA) {
      return this.visitMyNodeA(node);
    } else if (node instanceof MyNodeB) {
      return this.visitMyNodeB(node);
    } else {
      throw new Error("Unknown node type encountered in visitor");
    }
  }
}    

class MyNodeVisitor extends NodeVisitor {
  visitMyNodeA(node: MyNodeA): number {
    return 1;
  }

  visitMyNodeB(node: MyNodeB): number {
    return this.visit(new MyNodeA()) + 1;
  }
}

Does this approach work for your scenario? The rationale behind it is to direct the compiler towards understanding that passing in a MyNodeA will yield the result from this.visitMyNodeA(node), and likewise for MyNodeB.

I hope this explanation proves beneficial to you! Best of luck!

Code Link

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

operating efficiently even when producing an incorrect output type

Exploring Typescript for the first time on Codepen.io has left me puzzled. I'm unsure why, despite defining the function signature and return type with an interface, I am able to return a different type without encountering any errors. Is there somet ...

Having trouble extracting parameters with TypeScript in React Router even when they are present

I am in the process of migrating an older project to utilize react and react-router. Additionally, I am fairly new to typescript, which is the language used for this particular project. Any guidance or explanations on these topics would be highly beneficia ...

Hovering over the Chart.js tooltip does not display the labels as expected

How can I show the numberValue value as a label on hover over the bar chart? Despite trying various methods, nothing seems to appear when hovering over the bars. Below is the code snippet: getBarChart() { this.http.get(API).subscribe({ next: (d ...

The loop is returning a string element instead of the expected type from the array

I am facing an issue with looping through a TypeScript array. The following methods are being used: getNotification(evt: string, rowIndex: number) { console.log("Production order: law has changed to " + evt + " " + rowIndex); var select = document ...

Indeed verification using this in a separate constant

I'm currently working with React and TypeScript, and I need to verify if my groupID exists in an array of [2, 3, 4]. I'm unsure about the validity of my validationSchema as I am encountering issues with a keyword that seems to be missing from th ...

The rendering of the list is taking an unexpectedly long time due to random factors

I have encountered a strange issue with one of my components. The component fetches a list of objects from a service in the ngOInit(). The problem I am facing seems to occur randomly, where sometimes it takes a considerable amount of time to display this l ...

What is the best way to structure tabular data with metadata using TypeScript?

Our backend provides data in a specific format, with a data section containing tabular data and a meta section describing the columns in the table. The metadata includes information about the type of each column. For Example { meta: [ {name: "foo& ...

Updating the node startup file with Visual Studio 2015 using NodeJS/Typescript

Encountering a persistent error: Error Code: TS5055 Cannot write file C:/project/dir/server.js' because it would overwrite the input file. Project: TypeScript/JavaScript Virtual Projects Even after renaming my entry filename to nodeserver.js, the ...

Filtering FieldSelector options in react-querybuilder: A step-by-step guide

I am currently working on a task to eliminate the fields from FieldSelector that have already been utilized. In my custom FieldSelector component, let's assume there are fields A, B, C, D, E available. If A & B have been used, they should not be ...

Having difficulties incorporating a selected custom repository into a module

Issue with Dependency Injection in NestJS Currently, I am working on implementing SOLID principles in my NestJS project by decoupling my service layer from TypeOrm. One of the benefits of this approach is the ability to switch between using an InMemoryRep ...

Encountering an issue with core.js:15723 showing ERROR TypeError: Unable to access property 'toLowerCase' of an undefined value while using Angular 7

Below, I have provided my code which utilizes the lazyLoading Module. Please review my code and identify any errors. Currently facing TypeError: Cannot read property 'toLowerCase' of undefined in Angular 7. Model Class: export class C_data { ...

Issues with Vite's global import feature not functioning properly in a production build

My current setup involves loading all markdown files within a directory using a glob import. The code snippet below depicts this functionality: const useGetChangelogs = () => { const [changelogs, setChangelogs] = useState<string[]>([]); useEf ...

The 'string' data type cannot be assigned

Let me share how I define and utilize my font sizes in my custom React app: FontSizes.ts const fontSizes = { xs: 'xs', sm: 'sm', base: 'base', lg: 'lg', xl: 'xl' ...

Enhancing Apollo Cache Updates using TypeScript null checks

Currently, I am utilizing apollo codgen to automatically generate types for my graphql queries in TypeScript. However, I have noticed that the generated types contain numerous instances of null values, leading to an abundance of if checks throughout my cod ...

Select the implied type from a resolved Promise type in Typescript

I am working with a function called getStaticProps in Next.js that returns a Promise, resolving to an Object with the following structure: type StaticProps<P> = { props: P; revalidate?: number | boolean; } To generically "unwrap" the type o ...

Issue with Vue.js Typescript when setting a debounced function

Upon debouncing a function in my Vue.js application, I encountered the following error message: Type 'DebouncedFunc<(queryParams: QueryParams, page?: number) => Promise<void>>' is not assignable to type '(queryParams: QueryPa ...

What is the best way to swap out one component for another in a design?

I am working with a component that has the selector 'app-view', and I need to replace a specific part of the template: <div> content </div> The part that needs to be replaced will be substituted with another component: selector: &a ...

Utilizing interfaces, unlocking keys, and allocating assignments

Here is the code snippet in question: interface Config1 { A: string; N: number; } interface Config2 { B: string; N: number; } interface Config extends Config1, Config2 { } const config: Partial<Config> = {}; const keys: Array<keyof Config> = ...

Utilizing Node modules in TypeScript, Angular 2, and SystemJS: A Comprehensive Guide

In the process of developing a simple Angular 2 application, I am constructing a class named User. This class includes a function called validPassword() which utilizes the bcrypt library to validate user passwords: import { compareSync, genSaltSync, hashS ...

How can one use TypeScript to return a subclass instance within a static function of a base class?

Below is the code snippet: class BaseElement { public static create<T extends typeof BaseElement>(this: T ): InstanceType<T> { this.createHelper(); const r = new this(); return r; } public static createHelpe ...