1

Closed

TS 0.9.1 - How to declare a module that's a generic class that implements an imported interface?

description

how can you export a module that's a generic class? I'd think something like this might work:
declare module 'someModule' {
    module someModule {
        interface GenClass<T> {
            prop:T;
        }
        interface GenClassStatic<T> {
            ():GenClass<T>;
            new():GenClass<T>;
        }
    }

    // this wont work :(, since we need a type for the generic arg;
    // var someModule:GenClassStatic<???>;

    export = GenClassStatic;
}
some other file:
import someModule = require('someModule');
// compile error TS2088: Cannot invoke an expression whose type lacks a call signature
var x = new someModule<string>();
Is there a way to declare module "someModule" that is a generic class?
Closed Aug 23, 2013 at 6:43 PM by paulb
Closing this as this is not a bug, but a discussion much better served by the discussion forum.

We'll take your request for auto-implementing interfaces in d.ts files into consideration for a future version.

comments

billti wrote Aug 22, 2013 at 5:53 PM

You can simply export= a generic class declared within the module. Unless I am misunderstanding the question, the below should work.
declare module 'someGenClass' {
    class Foo<T> {
        static someStatic: string;
        someMember: T;
    }

    export = Foo;
}

import mod = require('someGenClass');
var i = new mod<Date>();
i.someMember; // type is Date
Note that the name you give the class is irrelevant. It is just an identifier used to indicate the module value on the 'export =' assignment.

Please reopen the issue if I've misunderstood.

billti wrote Aug 22, 2013 at 5:53 PM

** Closed by billti 08/22/2013 9:53AM

phestermcs wrote Aug 22, 2013 at 6:53 PM

Hi Bill. I was trying to simplify the problem. The issue is that the class I want to export implements an external interface (the interface is being imported), which itself inherits from other interfaces; I was trying to avoid when declaring the class having to redeclare the signatures of all its inherited interfaces. A better representation of the problem is:
// this is imported, but put inline here for simplicity
interface IExternal {
    externMethod():void;
}
declare module 'myModule' {
    module myModule {
        export interface IMyClass<T> extends IExternal {
            myMethod():T;
        }
        export interface IMyClassStatic<T> {
            new():IMyClass<T>;
        }
    }

    // can't do this because IMyClassStatic needs generic type arg, but the
    // importer of this module needs to pass the type arg as part of new statement
    var myModule:IMyClassStatic;

    // don't want to do this because then I have to redeclare signatures of all
    // inherited interfaces
    class myModule<T> implements IMyClass<T> {
        // uhg, have to redeclare signature of IMyClass and its inherited members
    }
    export = myModule;
}

phestermcs wrote Aug 22, 2013 at 6:59 PM

Uhg I wish we could edit comments. In prior comment:
var myModule:IMyClassStatic;

class myModule<T> implements IMyClass<T> {
should have been
var myModule:myModule.IMyClassStatic;

class myModule<T> implements myModule.IMyClass<T> {
Obviously both wouldn't be there (and compiling will produce errors), but I put both declarations in there so you could better understand the problem.

phestermcs wrote Aug 22, 2013 at 7:17 PM

Frankly, I don't understand when DECLARING a class in a .d.ts file that implements an interface, why the signature of that interface must be redeclared. In other words, in a .d.ts declared class, why does this throw a compiler error?
declare module mod {
    export interface IInterface {
        bunchOfMethods():void;
    }

    // this throws error TS2137: Class mod.MyClass declares interface
    // mod.IInterface but does not implement it: Type 'mod.MyClass' is 
    // missing property 'bunchOfMethods' from type 'mod.IInterface'
    //
    // Why the compile error? this isn't a class implementation, just a 
    // class declaration but compiler error says '..but does not implement it'.
    // can't the compiler understand 'implement IInteface' means that
    // the class implements the signature somewhere else? If I don't
    // redeclare all the interface's method signatures here I get the compile 
    // error, so whats the purpose of having to redeclare all the signatures?
    // what am I conceptually missing here?
    export class MyClass implements IInterface {
        constructor();
        myClassMethods():void;
    }
}

danquirk wrote Aug 22, 2013 at 10:43 PM

To your last question about .d.ts files: implementing an interface is merely claiming to the compiler that you implement a contract, then you have to actually implement it. When extending a class there is actually a concrete implementation to inherit from automatically. Consider that your MyClass could implement bunchOfMethods with a different return type and this would satisfy the constraint imposed by implementing the interface.

To your generics question: I don't think the code you have is really doing what you think. You're attempting to export an instance of an open generic type. That's not even a new-able thing, even if open generics were available. I think you mean to want to export= IMyClass. That said, it's also not clear from your example why you want to use export= at all rather than just exporting all those elements which you want available. Generally export= is only necessary when you're actually merging declarations to create a single identifier with multiple meanings (ie a class and a function).

danquirk wrote Aug 22, 2013 at 10:44 PM

Please re-open or start a discussion in the forums if you're still unclear of the semantics here.

** Closed by danquirk 08/22/2013 2:44PM

phestermcs wrote Aug 22, 2013 at 11:57 PM

First, I still have no answer to the original question of the post 'How to declare a module that's a generic class that implements an external, imported interface?'. So whatever module imports it can use:
import genClass = require('mod');
var x = new genClass<string>();
The following code does what I want (and what I know, not think, I want) without generics:
// READ THIS COMMENT!!!
// this is imported, but put inline here for simplicity
interface IExternal {
    externMethod():void;
}
declare module 'myModule' {
    module myModule {
        // NOTICE HOW IMyClass IS EXTENDING AN IMPORTED INTERFACE
        export interface IMyClass extends IExternal {
            myMethod():void;
        }
        export interface IMyClassStatic {
            new():IMyClass;
        }
    }

    var myModule:IMyClassStatic;

    export = myModule;
}
The meat of the question, do the above but with a generic arg to the class:
// this is imported, but put inline here for simplicity
interface IExternal {
    externMethod():void;
}
declare module 'myModule' {
    module myModule {
        export interface IMyClass<T> extends IExternal {
            myMethod():T;
        }
        export interface IMyClassStatic<T> {
            new():IMyClass<T>;
        }
    }

    // what do I replace this line with??? THIS IS THE QUESTION?!?!?!? 
    var myModule:IMyClassStatic;

    // OBVIOUSLY this doesn't work, and where you may have been
    // confused stating 'I don't think the code you have is really
    // doing what you think'. The code isn't doing ANYTHING because 
    // it's not valid code!!!, which is exactly what I think, and the heart
    // of the issue

    export = myModule;
}
Regarding your first comment about interface implementation, you're not making any sense to me. I realize you're probably much smarter than I am, so maybe you can dumb it down a little for my feeble mind. Here's where you're confusing me:

You said:
"To your last question about .d.ts files: implementing an interface is merely claiming to the compiler that you implement a contract, then you have to actually implement it"

In pure typescript (using no .d.ts files), the declaration, or 'claim' as you say, to the compiler that you implement an interface is combined with defining the implementation to the compiler. i.e in a .ts file:
interface IInterface {
    method():number;
}
// this line is the 'claim', or DECLARATION, to the compiler that I implement an interface
class Class implements IInterface
{
    // this is the 'claim', or DEFINITION, to the compiler of the actual implementation
    method():number { return 0; }

    // this does NOT implement the interface, and if it was the only method in the class
    // the compiler would error with TS2137 because of differing return types
    method():string { return ''; }
}
In a .d.ts, the ONLY THING YOU CAN DO is DECLARE, not DEFINE, an interface implementation
export interface IInterface {
    method():number
}
// Here, I'm ONLY DECLARING, or 'claiming' to the compiler 'Class'
// implements IInterface, which exactly IMPLIES every method is 
// implemented, i.e. DEFINED, somewhere else outside of this
// .d.ts DECLARATIONS file
declare class Class implements IInterface {
    // so WHY does the compiler require I do this
    method():number;

    // when thats EXACTLY the declared signature from IInterface
}
Continuing trying to understand your comment, you say:
"When extending a class there is actually a concrete implementation to inherit from automatically":
I'm not extending a base class. There is a difference between EXTENDING and base class, where you statement is true but has nothing to do with the original question, and IMPLEMENTING an interface.

And finally you say:
"Consider that your MyClass could implement bunchOfMethods with a different return type and this would satisfy the constraint imposed by implementing the interface."
Here is a playground link that patently refutes your statement; a different return type for the same method name does not satisfy the interface implementation, bringing me back to my second question: in a '.d.ts' file, when DECLARING a class implements an interface, why must the interface's signature be explicitly DECLARED (not implemented) within the class declaration

http://www.typescriptlang.org/Playground/#src=interface%20IInterface%20%7B%0A%09method()%3Anumber%3B%0A%7D%0A%0Aclass%20Class%20implements%20IInterface%20%7B%0A%09method()%3Astring%20%7B%20return%20''%3B%20%7D%0A%7D

danquirk wrote Aug 23, 2013 at 12:39 AM

You said:

'How to declare a module that's a generic class that implements an external, imported interface?'.
The following code does what I want (and what I know, not think, I want) without generics:
..
The meat of the question, do the above but with a generic arg to the class:
..

But the non-generic example you gave is not exporting a class or interface, it's exporting a single instance of that class or interface. That was where I was confused about your intentions vs the question that was asked. Bill's initial reply showed how to export the generic class so you could later construct it with whatever type parameter you want (which is then fed through to its base classes/interfaces). It is not possible to write an instance of a generic type with an open type parameter. The fact that you're extending an imported interface isn't relevant to the problem there. It's not clear to me what a consumer of myModule is doing that this isn't sufficient:
declare module 'external' {
    interface IExternal {
        externMethod(): void;
    }
}
declare module 'myModule' {
    import e = require('external');
    export module myModule {
        export interface IMyClass<T> extends e.IExternal {
            myMethod(): T;
        }
        export interface IMyClassStatic<T> {
            new (): IMyClass<T>;
        }
    }
}

import m = require('myModule');
var x: m.myModule.IMyClass<number>;
var y: m.myModule.IMyClass<string>;
x.externMethod();
var r1: number = x.myMethod();
var r2: string = y.myMethod();
Your second interface example is meaningfully different from the first you gave by virtue of the types involved. That's why you're seeing different behavior, your .d.ts and .ts are fundamentally different code. Consider:
interface A {
    doStuff(): void;
}

interface B extends A {
    doStuff(): string;
}

var a: A;
var b: B;
a = b;
a.doStuff();
This is acceptable because the contract that A's version of doStuff specifies says 'I take no arguments, and I don't return anything useful to a consumer of my API.' So a user of the API does not have a return value of any type that they have relied on. So if instead their variable of type A is actually an instance of type B it still satisfies the contract of A.doStuff because we can ignore the return type in this case (since the base class said it just returns void).

But if A.doStuff does not return void, like in your second example where the base interface returns number, then the derived class must not violate that constraint by returning an incompatible type. That is, this is a compiler error as you'd expect:
interface A {
    doStuff(): number;
}

interface B extends A {
    doStuff(): string;
}

var a: A;
var b: B;
a = b;
var x: number = a.doStuff();
because if it was allowed then as is clear from the last 2 lines, I've just written unsound code with a type error.

phestermcs wrote Aug 23, 2013 at 3:45 AM

In a '.d.ts' file, regarding signatures of classes that implement interfaces, you're stating because of the one special case that an interface may declare a method that returns void, and that a class can satisfy implementing that method with one that only differs in return type, that I therefore must always re-declare all of the interface's methods? Probably 99.99999% of the time the class's implementation of the interface's void-returning method will also have a void return type. On the one hand, I can understand allowing for a non-void-returning implementation satisfying the void-returning interface method, but in a practical sense, i.e. in every day use, that seems like a very flimsy reason, only in a .d.ts, to require always having to re-declare the signatures of all the interfaces methods to allow for the one special case that will rarely occur, and while technically consumers of things implementing the interface wouldn't expect a return value, for me conceptually if my implementation returns something when the interface says it shouldn't, I'm not precisely implementing the interface. It would seem far more practical to have the compiler assume, ONLY in a '.d.ts' mind you, that IF there is a void-returning interface method and ONLY IF I declare a method in the class declaration differing only in return type, THEN it should be considered implemented with a class specific method that returns something, and the interface's method signature should be excluded in the class signature; in all other cases the compiler should automatically include the interface's signature in the class signature. i.e:

in a .d.ts:
export interface IInterface {
   method():void;
}

class ClassOne implements IInterface {
} 

class ClassTwo implements IInterface {
    method():string;
}
The compiler knows ClassOne implements 'method():void' somewhere else, and automatically includes 'method():void' as part of class signature. For ClassTwo, compiler sees there's a class method only differing to IInterface's method in that it returns a value while the interface's does not, and in that case does NOT include method():void as part of class's signature. so:
var a = new ClassTwo();
var y = a.method(); // y gets typed to string;
var b = <IInterface>a;
var x = b.method() // compiler throws error
Now, I really do love TS and what it brings to JS development; absolute kudos to you guys. But this one special case, allowing a class to satisfy a void returning method from an interface with a type-returning method identical in every other way, you should really rethink that one. In 'pure' .ts, its not an issue because the 'implements interface' is always DECLARED and DEFINED together. But in a '.d.ts', where you're only declaring signatures, it just doesn't make sense whatsoever.


Back to the original post's question. In the non-generic example, you say I'm exporting a single instance of a class, rather than the class constructor function itself, and I beg to differ:
// this is imported, but put inline here for simplicity
interface IExternal {
    externMethod():void;
}
declare module 'myModule' {
    module myModule {
        export interface IMyClass extends IExternal {
            myMethod():void;
        }
        export interface IMyClassStatic {
            new():IMyClass;
        }
    }

    var myModule:IMyClassStatic;

    export = myModule;
}
Here I've exported a variable that implements IMyStaticClass, which has the 'new()' method declared. that means, for all practical purposes regarding importers, I AM exporting a class constructor function, i.e a class, and not a class instance! Because importers can do this, and only this:
import asClass = require('myModule');
var x = new asClass(); 
// compiler sees the exported var myModule', i.e. 'asClass', as a constructor function
// and NOT as an instance of IMyClass.. its an instance of IMyClassStatic
// which is a constructor function because it has 'new():IMyClass;' as a method
The above declaration is the only practical way to declare a module as essentially a class constructor function when the class implements an externally imported interface, 'IExternal' in the above example. In Bill's original response, his class doesn't implement an external interface. And in your example, the module itself is not a constructor function, as evidenced by the dotted notations and the lack of a 'new' statement. A sample .js to go along with the declaration would look 'something' like this:
var IExternal = require('externalModAsClassCtorFunc');
var function myClass() { //constructor stuff here }
_.extend( myClass.prototype, IExternal );
module.exports = myClass;
And really whats happening is IExternal is a base class that myClass is deriving from, but the '.d.ts' file that goes along with module 'externalModAsClass' has exported it as an interface, and I have no control over changing it.

Now, I've really been trying to keep my original question simple, as even after all this my situation is still slightly more complex. Ideally, IExternal would have been defined as a class, like CExternal, and then I could have just used:
declare module 'myModule' {
    class myClass<T> extends CExternal {
        // CExternal can change its signature
        // without breaking my .d.ts 
    }
    export = myClass;
}
But in fact IExternal is also 2 levels down from a root base class (via _.extend()), and they've all been defined as interfaces. So I could do this:
declare module 'myModule' {
    class myClass<T> implements IExternal {
        // ok, go through all the source and .d.ts's for IExternal
        // and it's 2 ancestors, and redeclare all their method
        // signatures here because the TS compiler wants us
        // to, for the super rare case when some other class
        // needs to implement a void returning interface method
        // with a method identical in every way except it returns
        // a value.
    }
}
But that's just not at all practical! As soon as IExternal's signature changes, or its ancestors, (which technically are classes) the .d.ts breaks. In my non-generic sample, I deal with this by creating an interface IClass that extends IExternal, and a separate interface for IClassStatic with a 'new():IClass' declaration. All that works until I want to use a generic, which leads my to the original question of this issue, and to my secondary question of the necessity, only in '.d.ts' files, to have to completely redeclare the method signatures of the interfaces implemented by a class, in the class's declaration. You've said that's to handle the super rare case when an interface method returns void, and the class wants to implement with a method that returns something. I don't agree with that technically, practically, or philosophically, only in a '.d.ts' file.

So, this is all still an issue for me; maybe not for you guys, but it should be.

phestermcs wrote Aug 24, 2013 at 10:33 PM

ANSWER TO ORIGINAL QUESTION:

...for the 2 of you out there who may have had the same question and actually read everything to this point.


First to restate the question and why it was asked....

Without generics, the following works to declare a preexisting javascript module thats actually a class that derives (prototypal inheritance) from another javascript class, when the base class has been declared
as an interface in some other .d.ts file:
// external interface in module you have no control over
declare module 'extMod'
{
    export interface IExternClass {
        extMethod():void;
    }
}

// ----- PATTERN 1: -----
// .d.ts for preexisting javascript module thats a class that 'derives'
// from a class in another javascript lib
declare module 'myMod'
{
    import ext = require('ext');
    module myMod {
        export interface IClass extends ext.IExternClass {
            method():string;
        }
        export interface IClassStatic {
            new():IClass;
        }
    }
    // important: using 0.9.x 'module merging feature' here.
    var myMod:IClassStatic;
    export = myMod;
}

//-------------------
// used in some typescript
import AsClass = import('myMod');
var inst = new AsClass();
This represents preexisting javascript 'classes' as interfaces, and enables extending them without having to redeclare the inherited interface methods.

In other words, the following is the same as above but tedious (when the base IExternalClass has lots of methods that aren't actually implemented in the derived class, but inherited via prototypal inheritance):
// ----- PATTERN 2: -----
declare module 'myMod'
{
    import ext = require('ext');

    class myClass implements ext.IExternClass {
        // have to redeclare all of IExternClass's methods,
        // as well as any ancestor extended interfaces.
        // tedious because if IExternClass was declared
        // as a class (which in actuality it is) instead of an interface,
        // extending (vs. implementing) wouldn't require this.
        extMethod():void;

        // class methods
        method():string;
    }
    export = myClass;
}

//-------------------
// used in exact same way as PATTERN 1
import AsClass = import('myMod');
var inst = new AsClass();
PATTERN 1 has been used in several '.d.ts' files the community has created. My question was how to do PATTERN 1 but with a generic class.

THE ANSWER
(And for the life of me I can't understand why the MSFT guys closed this issue three times without providing this answer as it was so simple, although not immediately obvious...)
declare module 'myMod'
{
    import ext = require('ext');
    module myMod {
        // the generic class
        export interface IClass<T> extends ext.IExternClass {
            method():T;
        }
        export interface IClassStatic {
            // THIS IS THE ANSWER RIGHT HERE!
            // which is to include a type param
            // to the 'new' declaration
            new<T>():IClass<T>;
        }
    }
    // important: using 0.9.x 'module merging feature' here.
    var myMod:IClassStatic;
    export = myMod;
}

//-------------------
// used in some typescript
import AsClass = import('myMod');
var inst = new AsClass<string>();