Combine a constant interface with a generic function to create a unique generic interface

When dealing with legacy code that utilizes a const in the following pattern:

const fnUsedInSetPrototypeOf = {
    equalityComparer<T>(a: T, b: T) { return a === b },
    otherFn<T> (this: T) { /*...*/ },
    // ... other things, all along the lines of `method<T> (...)`
} as const

I am interested in merging the typeof fnUsedInSetPrototypeOf into a generic interface that collapses all the functions to the generic type of the extending interface. For example:

type Proto = typeof fnUsedInSetPrototypeOf

interface SuperProto<T> extends Proto {
    equalityComparer (a: T, b: T): boolean
}

This approach would streamline and expedite the process of adding types to an existing JavaScript codebase like Knockout/TKO.

However, I encountered this error:

Type (a: T, b: T) => boolean is not assignable to type

<T>(a: T, b: T) => boolean
.

Various attempts have been made to resolve this issue, such as defining mappings for types like:

type Proto<T> = { [key in keyof typeof fnUsedInSetPrototypeOf]: fnUsedInSetPrototypeOf<T>[key]?? }
, but no solution has proved effective so far.

The underlying challenge I am trying to address is somewhat more complex, involving multiple inheritances of fnUsedInSetPrototypeOf and SuperProto<T>, as seen in the Knockout Observable type within the Knockout/TKO library.

A Playground has been provided to showcase the problem, along with various attempted solutions to rectify it.

Edit:

In a second playground, the challenge persists:

const fnUsedInSetPrototypeOf = {
    equalityComparer<T>(a: T, b: T) { return a === b },
    otherFn<T> (this: SuperProto<T>, v: T) { /*...*/ },
    otherFn2<T, U> (this: T, v: U) { /*...*/ },
} as const

type Proto = typeof fnUsedInSetPrototypeOf

interface SuperProto<T> extends Omit<Proto, keyof SuperProto<T>> {
    equalityComparer (a: T, b: T): boolean
    // equalityComparer<T> (a: T, b: T): boolean
}

// This should not give an error
const x = (v: SuperProto<number>) => v.otherFn(123)
const x2 = (v: SuperProto<number>) => v.otherFn2(123)

// This should error with argument to `otherFn` not being a number
const y = (v: SuperProto<number>) => v.otherFn('abc')
const y2 = (v: SuperProto<number>) => v.otherFn2('abc')

Answer №1

Consider using this definition for Proto:

const fnUsedInSetPrototypeOf = {
    equalityComparer<T>(a: T, b: T) { return a === b },
    otherFn<T>(this: SuperProto<T>, v: T) { /*...*/ },
    otherFn2<T, U>(this: T, v: U) { /*...*/ },
} as const

type Proto = typeof fnUsedInSetPrototypeOf

The goal here is to define SuperProto<T> in a way that moves the generic parameter from the methods up into SuperProto<T> itself. This creates a single generic type where each method is specific instead of having independently generic methods with a specific type. An example could be:

interface SuperProto<T> {
    equalityComparer(a: T, b: T): boolean
    otherFn(v: T): void
    otherFn2(v: T): void;
}

When defining SuperProto<T>, do not include extends Proto. Instead, let the compiler verify structural compatibility between the types. For instance:

const x = (v: SuperProto<number>) => v.otherFn(123) // okay
const x2 = (v: SuperProto<number>) => v.otherFn2(123) // okay

const y = (v: SuperProto<number>) => v.otherFn('abc') // error
const y2 = (v: SuperProto<number>) => v.otherFn2('abc') // error

If Proto has specific methods or properties that should remain unchanged, you can use an interface extension like:

interface SuperProto<T> extends Omit<Proto, keyof SuperProto<T>> {
  equalityComparer(a: T, b: T): boolean
  otherFn(v: T): void
  otherFn2(v: T): void; 
}

In this case, since there are no unchanged properties/methods from Proto, simply leave it out.


Currently, TypeScript does not offer a concise way to represent and translate the relationship between Proto and SuperProto<T>. While suggestions exist to support "generic values", they are not fully implemented yet. For now, explicitly writing out the types is the best approach.


Hopefully this explanation helps! Good luck!

Playground link with 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

OpenTok Angular 6 encountered an error with code TS2314 stating that the generic type 'Promise<T>' needs to have 1 type argument specified

Issue in opentok.d.ts File: Error TS2314 npm version: 6.2.0 node: v8.10.0 Angular CLI: 6.2.3 Operating System: Linux x64 Angular Version: 7.0.0-beta.5 @opentok/client": "^2.14.8 ...

What is the best method to locate an element<T> within an Array[T] when <T> is an enum?

I've recently started learning TypeScript and exploring its functionalities. I could use some assistance in deepening my understanding. Within our angular2 application, we have defined an enum for privileges as follows: export enum UserPrivileges{ ...

Modifying app aesthetics on-the-fly in Angular

I am currently working on implementing various color schemes to customize our app, and I want Angular to dynamically apply one based on user preferences. In our scenario, the UI will be accessed by multiple clients, each with their own preferred color sch ...

React: Avoid unnecessary re-rendering of child components caused by a bloated tree structure

I am dealing with a tree/directory structured data containing approximately 14k nodes. The issue I am facing is that every time a node is expanded or minimized by clicking a button, causing it to be added to an 'expanded' Set in the Redux state, ...

Difficulty Converting Array of Objects to Proper Type with Q.Promise and KO.mapping

I have encountered an issue while trying to filter an observable array. It seems that the ko.utils.arrayFilter method is converting all my model's field names to lowercase, causing unexpected behavior. I should mention that this project involves Types ...

What causes tsc to generate different json files based on its execution location?

Scenario: You have a typescript project set up to generate JSON files. The tsconfig.json is properly configured and all dependencies are in place. You've even referred to this related Q&A and ensured that your typescript files are importing the json f ...

Enhanced VS code typings for Nuxt injected properties

My approach to injecting properties into the Nuxt TS context is as follows: ~/plugins/services.ts import Vue from 'vue'; import { errorService } from '~/services/error'; import { Plugin } from '@nuxt/types' const services: Pl ...

Create a flexible string for refining a REST request

I am currently working on constructing a dynamic string and I've encountered an issue, so I humbly seek assistance from the community. I have a string for creating a rest call filter, but I am struggling with the use of and's This query only fu ...

What is a more efficient method for incorporating optional values into an object?

Currently, I am utilizing the optional addition feature in this way: ...(!!providerId && { providerId }), ...(!!practiceId && { practiceId }), Is there a more elegant shorthand method to replace this logic, such as: yield createRemark ...

Testing the Angular router-outlet using Jasmine

When testing web-app navigation using Jasmine spec with RouterTestingModule, I am facing challenges with nested fixture.whenStable().then(() => {}). For instance: After clicking on multiple links where the router-outlet changes the displayed component ...

An effective method for excluding null values with an Angular pipe

I am currently working on an Angular pipe that filters results based on user input. The problem I'm encountering is that some of the results do not have a value, resulting in this error message: Cannot read property 'toLocaleLowerCase' o ...

Excessive wait times during the construction of a moderately sized application (over 2 minutes) using TypeScript and loaders like Vue-Loader and TS-Loader

I'm currently utilizing Nuxt 2 with TypeScript and the most up-to-date dependency versions available. Even though my app is of medium size, the compilation time seems excessively slow. Here are my PC specs: Ryzen 7 2700X (8 Cores/16 Threads) 16 GB D ...

Enhance your PowerBI dashboards with the ChatGPT Custom Visual!

I am currently working on developing a custom visual for Power BI using TypeScript. This visual includes an input field for user prompts and another input field for ChatGPT answers. The main goal is to allow users to ask questions about the data in their r ...

TypeScript: The class results in an empty object being returned

I'm encountering an issue with a Typescript Class that I'm attempting to use. Even after instantiating it, I'm not getting the correct class instance. Class GamesService export interface IGame { name: string; online: number; likes: n ...

Error: The function list.forEach does not exist within service.buildList [as project]

Today, I've been grappling with a challenging issue. As someone new to Typescript and Angular, I'm attempting to make a call to my backend API. However, when trying to populate an array for display, I keep encountering an error that says rawRegis ...

Type guards do not work properly on a union of enum types in TypeScript

Recently delved into the concept of Type Guards Chapter within the realm of Typescript However, I encountered an issue where my basic type guards failed to differentiate a union of enums. Why is this happening? enum A { COMMA = ',', PLUS = & ...

Locating a class variable using a string chosen from a DropDown menu

In my Tv class, I have several string variables. One requirement is for the user to select an option from a DropDown list and input a value. This entered value should then be stored in the Tv class under a variable with a similar name to the selected optio ...

Updating the main window in Angular after the closure of a popup window

Is it possible in Angular typescript to detect the close event of a popup window and then refresh the parent window? I attempted to achieve this by including the following script in the component that will be loaded onto the popup window, but unfortunatel ...

Neglect variables that have not been declared (TypeScript)

Currently, I am working on developing a WebExtension using TypeScript that will be later compiled into JavaScript. This extension relies on one of the browser APIs offered by Firefox, specifically the extension API. An example of this is my use of the get ...

Bug in timezone calculation on Internet Explorer 11

I've spent hours researching the issue but haven't been able to find any effective workarounds or solutions. In our Angular 7+ application, we are using a timezone interceptor that is defined as follows: import { HttpInterceptor, HttpRequest, H ...