Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generic class interface for an abstract class in TS1.6? #4693

Closed
qc00 opened this issue Sep 8, 2015 · 12 comments
Closed

Generic class interface for an abstract class in TS1.6? #4693

qc00 opened this issue Sep 8, 2015 · 12 comments
Labels
Needs More Info The issue still hasn't been fully clarified Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@qc00
Copy link

qc00 commented Sep 8, 2015

I use the following definition in some generic code to capture the type of a class argument:

interface IClass<T> extends Function {
    new (...args: any[]): T;
}

However, this does not work if the class in question is abstract. Is there any alternative for this? If not, can some syntax be added to support this?

@DanielRosenwasser DanielRosenwasser added Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Sep 8, 2015
@DanielRosenwasser
Copy link
Member

@mhegazy and I had a recent conversation about this and the question we were wrestling with was whether it is the class constructor that is aware of abstractness or if it is the prototype that is abstract because it doesn't implement expected methods.

@mhegazy
Copy link
Contributor

mhegazy commented Sep 8, 2015

@billccn can you shed some light on how this is used, and how you want to use abstract classes with them?

@mhegazy mhegazy added the Needs More Info The issue still hasn't been fully clarified label Sep 8, 2015
@qc00
Copy link
Author

qc00 commented Sep 8, 2015

Hi,

I use the IClass<T> interface in two places: a checked cast function (if (x instanceof cls) then return <T>x else throw) and a manual sub-class function (mainly used to augment instance creation at run time).

What I really want is a typing syntax that expresses the relationship between an expression that evaluates to a Function and objects with the prototype of that Function.

In TS1.6, the expression does not have to be new-able. For example, classes with private constructors, abstract classes or everything that is allowed on the RHS of extends. So we can say the {new (): T} syntax is no longer fit for purpose regardless of how we interpret abstractness in abstract classes.

Personally, I see three possible solutions (can all be combined):

  1. Make typeof X sub-typeable. Currently, interface Clazz<T> extends typeof T does not work, but this is artificial as manually enumerating typeof T in a differnet interface would be sub-typeable so why can't the type checker do it for us? (I think this would also add a nice symmetry with extending class expressions in ES6.)
  2. A new keyword like new in {new (): T} to state the instance type when sub-classed. This is more generic than 1 and carries all the benefits of the original new syntax. Especially, this allows protected constructors to be typed and maybe even allow overloaded constructors to return different types (e.g. to model that different overloads set different fields or the fact that some constructor can replace proto or return completely different object).
  3. Treat the prototype property as first class citizen in the type system. So instead of new ():T, people can say interface X<T> {prototype: T}. I think this already works, but maybe it should be advertised more in the documentations instead of the new() syntax.

@DanielRosenwasser
Copy link
Member

@mhegazy, on #4709 (comment), @OlegDokuka was interested in specifying a catch-all type for classes to decorate, but not being able to specify that the type could be abstract limited the approach.

@OlegDokuka
Copy link

In my opinion, the best thing that would be is create in native code. It should provide two main things:

Base idea

  1. It can be generic
  2. it can be empty

The first one should be something like this:

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

it can be used something like this

function newInstance<T>(clazz:Class<T>):<T>{
}

The second one should looks like this:

type Class = {
        new (...args: any[]): any; //may be it should be Object instead of any
 };

it can be used like that

function newInstance(clazz:Class):any{
}

Both cases should be in the TS system. But in generic case The type of Class is static and equals T.
In the second case type of Class is hidden and equals any or Object.

var a:Class;
var b:Class<Something> = Something;

a = b; // here we hide type

var c:Something = new a() //here we can get compiler exception that it should be up cast

Using with Abstract class

In this case if we support code above the TSs compiler can check in compile time what type is passed - for example:

abstract class TestWithAbstractKW{
}

var clazz :Class<TestWithAbstractKW> = TestWithAbstractKW;

var a = new clazz() //here we get compiler error that abstract class cannot be directly instantiated

function testInGeneric<T>(clazz:Class<T>){
     var a = new clazz();
}

testInGeneric(clazz); // everything is okay, but in runtime we get exception that abstract class can not be directly instantiated.

As you can see I haven`t proposed any kind of JS code yet, because it just compiler improvement here.

The last one idea

I heard that TypeScript 2.0 (AtScript) will give us many features like types manipulations at runtime. It would be cool if Class would be something like in Java or C# and give us more reflection than we have ever imagined about JS language.

It would be cool if we would do something like

var clazz:Class<Something>;

var abstract:boolean = clazz.isAbstract()
var implementedInsterfaces:Class[] = clazz.listInterfaces();
var fields: PropertyDescriptor[] = abstractclazz.fields();
var methods: PropertyDescriptor[] = clazz.methods();

That is all.
What do you think about it?

P.S. I hope my idea will be useful

@mhegazy
Copy link
Contributor

mhegazy commented Sep 9, 2015

@billccn, just to make sure i understand the issue..

function checkedCast<T>(x: any, cls: IClass<T>): T {
    if (x instanceof cls)
        return <T>x;
    else
        throw new Error("not cls");
}

class C {
}

abstract class A {
}

var c = checkedCast({}, C);  // OK
var a = checkedCast({}, A);  // Error, A is abstract

are there other use cases?

@qc00
Copy link
Author

qc00 commented Sep 14, 2015

@mhegazy (Sorry, lost track of this thread.)

I have two uses for it, one as you showed above and another one which creats a sub-class of the given argument at run time. (The latter is a replacement of the __extend function TS inserts.)

@mhegazy
Copy link
Contributor

mhegazy commented Sep 14, 2015

so why not just use Function. this to me seems to be the accurate representation of what you want to do. if you also want to get the type inference in the above example, define cls as IClass<T>|Function.

@weswigham
Copy link
Member

We choke if you actually try to drop something of that type into an extends block:

interface IClass<T> extends Function {
    new (...args: any[]): T;
}

type Extendsable<T> = IClass<T>|Function;

function makeClass<T>(a: Extendsable<T>) {
    return class extends a {}
}

When it's a union we say it's not a constructor function type, and when it's just IClass<T> we get that the return type of the extends clause is not a class or interface type.

So I guess the simple question would be to ask what is the broadest way to type a?

@mhegazy
Copy link
Contributor

mhegazy commented Sep 14, 2015

but this is not the scenario @billccn is describing. we are talking about generic helper functions that can reason about classes. extends clause is a bit of a special case.

@mhegazy
Copy link
Contributor

mhegazy commented Nov 13, 2015

@billccn does #4693 (comment) addresses the issue?

@mhegazy
Copy link
Contributor

mhegazy commented Feb 20, 2016

please reopen if there is more information to consider.

@mhegazy mhegazy closed this as completed Feb 20, 2016
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs More Info The issue still hasn't been fully clarified Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

5 participants