typescripttypescript-typingsmixinstsc.d.ts

Return type in emitted .d.ts files is any instead of this


I have created mixins as described here. When I'm editing the source files, the types are correct. However, the emitted .d.ts files have any instead of (in this case) PreComp types:

export class LottieItem {
  ...
}
export function Layers<TBase extends Constructor<LottieItem>>(
  Base: TBase
) {
  return class Layers extends Base {
    layers: Layer[] = [];

    addLayerFront(layer: Layer) {
      this.layers.unshift(layer);
      return this;
    }
    ...
  };
}
export class PreComp extends Layers(LottieItem) {
  ...
  // if I were to use this.addLayerBack() in here, it would return PreComp
}

declaration file after running tsc:

declare const PreComp_base: {
    new (...args: any[]): {
        [x: string]: any;
        layers: import("./Layer").Layer[];
        addLayerFront(layer: import("./Layer").Layer): any; // return value should not be any, but PreComp
    };
} & typeof LottieItem;
export declare class PreComp extends PreComp_base {
    ...
}

I also tried to use the alternative mixin pattern at the bottom of the page, but that caused properties of mixins not being initialized and methods being overridden (and thus not being callable using super).


Solution

  • A solution would be to declare the type of the Layers constructor:

    interface Layers { // <- new declaration
        layers: Layer[];
        addLayerFront(layer: Layer): this;
    }
    
    export function Layers<TBase extends Constructor<LottieItem>>(
        Base: TBase
    ): TBase & Constructor<Layers> { // <- explicit type
        return class Layers extends Base {
            layers: Layer[] = [];
    
            addLayerFront(layer: Layer) {
                this.layers.unshift(layer);
                return this;
            }
        };
    }
    

    The resulting declaration would be the following:

    interface Layers {
        layers: Layer[];
        addLayerFront(layer: Layer): this;
    }
    declare const PreComp_base: typeof LottieItem & Constructor<Layers>;
    export declare class PreComp extends PreComp_base {}
    

    The type of addLayerFront is preserved because it is now declared as part of an interface.

    This solution is not perfectly optimal because it forces you to write the signatures of your methods twice, but it is type safe (no casts needed) and solves the problem.