Skip to content

Wrong declaration file for generic class factory #18545

Closed

Description

@damian-lewkowicz

TypeScript Version: 2.5.2

Code
I created some code to ensure that developer will create only one class with specific 'kind' property used in discriminated union:

const kindCache: { [kind: string]: boolean } = {};

function register(kind: string): void | never {
  if (kindCache[kind]) {
    throw new Error(`Class with kind "${kind}" is already registered.`);
  }
  kindCache[kind] = true;
}

function ClassFactory<TKind extends string>(kind: TKind) {
  register(kind);

  return class {
    static readonly THE_KIND: TKind = kind;
    readonly kind: TKind = kind;
  };
}

class Kinds {
  static readonly A = "A";
  static readonly B = "B";
  static readonly C = "C";
}

export class AKind extends ClassFactory(Kinds.A) {
}

export class BKind extends ClassFactory(Kinds.B) {
}

export class CKind extends ClassFactory(Kinds.C) {
}

Locally it works as expected. Compilation of:

const x: AKind = new BKind();

causes error:

 error TS2322: Type 'BKind' is not assignable to type 'AKind'.
  Types of property 'kind' are incompatible.
    Type '"B"' is not assignable to type '"A"'.

Expected behavior:
I expected that it will generate d.ts like this:

declare const AKind_base: {
    new (): {
        readonly kind: "A";
    };
    readonly THE_KIND: "A";
};
export declare class AKind extends AKind_base {
}
declare const BKind_base: {
    new (): {
        readonly kind: "B";
    };
    readonly THE_KIND: "B";
};
export declare class BKind extends BKind_base {
}
declare const CKind_base: {
    new (): {
        readonly kind: "C";
    };
    readonly THE_KIND: "C";
};
export declare class CKind extends CKind_base {
}

Actual behavior:
But type of non-static field is invalid (TKind)

declare const AKind_base: {
    new (): {
        readonly kind: TKind;
    };
    readonly THE_KIND: "A";
};
export declare class AKind extends AKind_base {
}
declare const BKind_base: {
    new (): {
        readonly kind: TKind;
    };
    readonly THE_KIND: "B";
};
export declare class BKind extends BKind_base {
}
declare const CKind_base: {
    new (): {
        readonly kind: TKind;
    };
    readonly THE_KIND: "C";
};
export declare class CKind extends CKind_base {
}

Activity

  1. damian-lewkowicz commented on Sep 18, 2017

    @damian-lewkowicz
    Author

    Aluan Haddad (@aluanhaddad) it will produce the wrong d.ts as well

    export declare type Kind = 'A' | 'B' | 'C';
    export declare function ClassFactory<TKind extends Kind>(kind: TKind): {
        new (): {
            readonly kind: TKind;
        };
        readonly THE_KIND: TKind;
    };
    declare const AKind_base: {
        new (): {
            readonly kind: TKind;
        };
        readonly THE_KIND: "A";
    };
    export declare class AKind extends AKind_base {
    }
    declare const BKind_base: {
        new (): {
            readonly kind: TKind;
        };
        readonly THE_KIND: "B";
    };
    export declare class BKind extends BKind_base {
    }
    declare const CKind_base: {
        new (): {
            readonly kind: TKind;
        };
        readonly THE_KIND: "C";
    };
    export declare class CKind extends CKind_base {
    }
  2. appsforartists commented on Sep 28, 2017

    @appsforartists

    Here's a simpler reproduction:

    export function withMixin<S>(superclass: S): S {
      return superclass;
    }
    
    export class Base<T> {
      constructor(value: T) {
      }
    }
    
    export class MixedTogether<T> extends withMixin<new (value: T) => Base<T>>(Base){}

    and the resulting (broken) d.ts:

    export declare function withMixin<S>(superclass: S): S;
    export declare class Base<T> {
        constructor(value: T);
    }
    declare const MixedTogether_base: new (value: T) => Base<T>;
    export declare class MixedTogether<T> extends MixedTogether_base {
    }
    // Any code that depends on this module will now throw:
    //     error TS2304: Cannot find name 'T'.
    // even though the module itself built cleanly
  3. added 2 commits that reference this issue on Oct 11, 2017
    0383ae2
    50a5ea5
  4. added a commit that references this issue on Oct 16, 2017
    aea7e9a
  5. added a commit that references this issue on Oct 16, 2017
    a704915
  6. locked and limited conversation to collaborators on Jun 14, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

BugA bug in TypeScriptDomain: Declaration EmitThe issue relates to the emission of d.ts filesFixedA PR has been merged for this issueHigh Priority

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions