The TypeScript compiler is tolerant when a subclass inherits a mixin abstract class without implementing all its getters

Update: In response to the feedback from @artur-grzesiak below, we have made changes to the playground to simplify it. We removed a poorly named interface method and now expect the compiler to throw an error for the unimplemented getInterface. However, the error is not being thrown:

New playground


type GConstructor<T = {}> = abstract new (...args: any[]) => T;

// Raw objects interfaces
interface IBaseDataObject {
    readonly id: string;
}
// Name Pattern
interface Name {
    name: string;
}

// interfaces that classes must implement
interface BaseDataObjectInterface<T extends IBaseDataObject> {
    readonly id: string;
    readonly interface: T;
}

abstract class AbstractBaseObject<T extends IBaseDataObject> {
    readonly id: string;
    abstract readonly interface: T;

    constructor(
        iBaseDataObject: T
    ) {
        this.id = iBaseDataObject.id;
    }
}

type AbstractBaseObjectCtor<T extends IBaseDataObject> = GConstructor<AbstractBaseObject<T>>;

// Country interface, class and instances
interface ICountry extends IBaseDataObject, Name {}

function NameAbstractMixin<TBase extends AbstractBaseObjectCtor<T>, T extends IBaseDataObject & Name>(Base: TBase) {
    abstract class NamedBase extends Base implements Name {
        readonly name: string;

        constructor(...args: any[]) {
            super(...args)
            this.name = args[0].name;
        }

    }
    return NamedBase;
}


class Country extends NameAbstractMixin(AbstractBaseObject<ICountry>) implements BaseDataObjectInterface<ICountry> {
    // get interface(): ICountry {
    //     return {
    //         id : "hello",
    //         name: "France",
    //     }
    // }
}

The Country Class should enforce the abstract contract inherited from the AbstractBaseObject Class, which declares an abstract property interface. The Class Country should implement the BaseDataObjectInterface, which requires the same property/accessor, but the TypeScript compiler does not raise any error as shown in this TypeScript playground.

playground

Could there be something I am overlooking here?

Answer №1

When focusing solely on the error itself and removing extraneous details, the core problem simplifies to this:

type GConstructor<T = {}> = abstract new (...args: any[]) => T;
type ICountry = 'ICountry' | 'IAnotherCountry'

abstract class AGetInterface<T> {
    abstract readonly getValue: T
}

function NameAbstractMixin1<TBase extends GConstructor<AGetInterface<T>>, T>(Base: TBase) {
    abstract class NamedBase extends Base { }
    return NamedBase;
}

class Country1 extends NameAbstractMixin1(AGetInterface<ICountry>) {
    // get getValue(): ICountry {
    //     return 'ICountry';
    // }
}

Let's provide more explicit type information for the compiler:

// Non-abstract class 'Country' does not implement inherited abstract member
class Country2 extends NameAbstractMixin1<GConstructor<AGetInterface<ICountry>>, ICountry>(AGetInterface<ICountry>) {
    // get getValue(): ICountry {
    //     return 'ICountry';
    // }
}

The issue lies in the compiler's inability to deduce the correct type

We can assist the compiler in determining the type accurately

type GuessType3<C> = C extends GConstructor<AGetInterface<infer T>> ? T : never;

function NameAbstractMixin3<TBase extends GConstructor<AGetInterface<T>>, T = GuessType3<TBase>>(Base: TBase) {
    abstract class NamedBase extends Base { }
    return NamedBase;
}
// Non-abstract class 'Country3' does not implement inherited abstract member 'getValue'
class Country3 extends NameAbstractMixin3(AGetInterface<ICountry>) { }

Let's confirm if the solution works with the initial code

type GuessType<TBase> = TBase extends AbstractBaseObjectCtor<infer T> ? T : never
function NameAbstractMixin<TBase extends AbstractBaseObjectCtor<T>, T extends IBaseDataObject & Name = GuessType<TBase>>(Base: TBase) { ... }
// Non-abstract class 'Country' does not implement inherited abstract member 'getInterface'
class Country extends NameAbstractMixin(AbstractBaseObject<ICountry>) { ... }

It works


In situations like this, consider providing a type parameter upfront,

function NameAbstractMixin4<T>() {
    return function <TBase extends GConstructor<AGetInterface<T>>>(Base: TBase) {
        abstract class NamedBase extends Base { }
        return NamedBase;
    }
}
// Non-abstract class 'Country4' does not implement inherited abstract member 'getValue' 
class Country4 extends NameAbstractMixin4<ICountry>()(AGetInterface<ICountry>) { }

It works seamlessly in this scenario


Interestingly, I recently discovered the ability to place the T type parameter after its usage

If you have insights on separating a type parameter without nesting functions, feel free to share

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

No matter the circumstances, the "Unexpected end of form" error consistently appears when attempting to upload files in Express

I'm facing a challenge in implementing a file upload API endpoint for my Express+no-stress+Typescript application. Initially, I attempted to use the express-fileupload library, but I quickly realized that it didn't integrate well with Typescript ...

Error message: "The property is not found within the specified type when using the OR operator with

Within my Angular component, I am faced with a challenge involving an Input that can be one of two types. @Input() profile: UserProfileDetails | BusinessProfileDetails; The structure of the profile template is straightforward and I want to avoid duplicati ...

Employing async/await for efficient data retrieval

Attempting to utilize async-await in TypeScript Vue 3 to retrieve data, but encountering an issue where the function is already logging 'undefined' (or executing before the function call) private async exportDataOrder() { await this.getDataEx ...

Unable to simultaneously execute TypeScript and nodemon

Currently, I am in the process of developing a RESTful API using Node.js, Express, and TypeScript. To facilitate this, I have already installed all the necessary dependencies, including nodemon. In my TypeScript configuration file, I made a modification to ...

Strange behavior of Lambda function in Typescript

Within a larger class, I'm working with the following code snippet: array.map(seq => this.mFunction(seq)); After compiling using the tsc command, it becomes: array.map(function (seq) { return _this.mFunction(seq); }); Everything seems fine so f ...

How can I stop TypeScript from causing my builds to fail in Next.js?

Encountering numerous type errors when executing yarn next build, such as: Type error: Property 'href' does not exist on type '{ name: string; }'. This issue leads to the failure of my build process. Is there a specific command I can ...

Typescript: Removing signatures with a filter

I am encountering a TypeScript error stating that .filter has no signatures. I'm unsure of how to resolve this issue. interface IDevice { deviceId: string; deviceName?: string; } const joinRoom = ({ userId, deviceId, deviceName }: IRoomParams ...

What could be causing the "no exported member" errors to appear when trying to update Angular?

The dilemma I'm facing a challenge while attempting to upgrade from Angular V9 to V11. Here are the errors that I am encountering: Namespace node_module/@angular/core/core has no exported member ɵɵFactoryDeclaration Namespace node_module/@angular/ ...

Tips for transferring state information from a client to a server component within Nextjs?

Currently, I am working on a project where I need to read and write data from a locally stored .xml file that contains multiple <user> tags. The technology stack includes TypeScript and NextJS. The project is divided into three main components sprea ...

Why is my Angular promise unexpectedly landing in the error callback?

I am facing an issue with my Angular + Typescript client. I have developed a PHP API and need to send a post request to it. Upon receiving the request, the server fills the response body with the correct data (verified through server debugging). However, w ...

Adding a new line in the configurations of MatDialogConfig (Angular)

Here is a code snippet: private mDialog: MatDialog, const dialog = new MatDialogConfig(); msg = "I enjoy coding in Angular.\r\n I am learning TypeScript." dialog.data = { message:msg }; alert (msg); mDialog.open(AB ...

Is it possible to showcase a unique date for every item that gets added to a list?

I am new to using React, so please bear with me. I want to be able to show the date and time of each item that I add to my list (showing when it was added). I am struggling to get this functionality working with my current code. Any help or explanation o ...

The value of additionalUserInfo.isNewUser in Firebase is consistently false

In my application using Ionic 4 with Firebase/AngularFire2, I authenticate users with the signinwithemeailandpassword() method. I need to verify if it's the first time a user is logging in after registering. Firebase provides the option to check this ...

How to access the result without using subscribe in Angular?

I am facing unexpected behavior with a method in my component: private fetchExternalStyleSheet(outerHTML: string): string[] { let externalStyleSheetText: string; let match: RegExpExecArray; const matchedHrefs = []; while (match = this.hrefReg.exe ...

Stop users from inputting dates beyond the current date in Angular 4

Encountering an issue with comparing the date of birth object and today's date object using Moment.js. Even if the entered date is smaller than today's date, it still throws an error. Below is the HTML code: <div class="form-group datepicker ...

Having difficulty importing the WebRTCAdaptor from the antmedia package stored in the node modules directory into an Angular Typescript file

An error is appearing indicating that: There seems to be a problem finding a declaration file for the module '@antmedia/webrtc_adaptor/js/webrtc_adaptor.js'. The file 'D:/web/node_modules/@antmedia/webrtc_adaptor/js/webrtc_adaptor.js' ...

What is the syntax for accessing elements from an iterable?

Is it possible to create a getter that acts like a function generator? My attempts class Foo { * Test1(): IterableIterator<string> { // Works, but not a getter... yield "Hello!"; } * get Test2(): IterableIterator<string> ...

Tips for integrating external libraries (npm packages) in Ionic 4 applications

With the changes in Ionic 4, I am seeking a definitive guide on implementing third party libraries, such as "rss-parser". I have reviewed this article which seems to be the latest resource on the topic: https://ionicframework.com/docs/v3/developer-resour ...

What is the proper way to create a React Context in TypeScript that accepts both a ref and a setState function as arguments?

When encountering various errors, one of them being: Type 'Dispatch<SetStateAction<null>>' is not assignable to type '() => void' My code looks something like this: import React, { ReactElement, ReactNode, useEffec ...

What is the best way to refresh existing data retrieved by React Query without having to fetch everything again?

My current code structure requires me to refetch all the data after a successful mutation, as the client-side tasks are not updated automatically. Is there a way to update the tasks directly when I create or delete a task? const { data: sessionData } = ...