TypeScript compiler doesn't complain if a getter is not implemented on a class that inherit a mixin abstract class

Viewed 139

Edit: Following the comment from @artur-grzesiak below, We've modified the playground for a simpler version, without a badly named interface method. where we would still expect the compiler to throw an error for the not implemented getInterface, but it does not do so:

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",
    //     }
    // }
}

Country Class should enforce the abstract contract inherited from the AbstractBaseObject Class which declares an abstract property interface and the Class Country should implements BaseDataObjectInterface which requires the same property/accessor but typescript compiler doesn't raise any error as you can see on this typescript playground.

playground

Am I missing something here?

1 Answers

If you remove everything not related to the error itself, the problem will simplify 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 try to verbosely add the types compiler should guess:

// 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';
    // }
}

So here's the reason, compiler could not guess the type

Let's try to help compiler to guess the type correctly

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>) { }

Now check if it works in 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 does


Generally in such cases try to provide some type parameter out of the box,

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>) { }

works perfectly as is for example


I didn't know you could put T type parameter after its usage until now BTW

If you know how to split off a type parameter without double function please comment, I was looking for that

Related