Skip to content

chained generic mixins fail #28594

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

Open
prasannavl opened this issue Nov 18, 2018 · 2 comments
Open

chained generic mixins fail #28594

prasannavl opened this issue Nov 18, 2018 · 2 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@prasannavl
Copy link

prasannavl commented Nov 18, 2018

TypeScript Version: 3.2.0-dev.201xxxxx

Search Terms: chained generic mixins

Code

type Constructor<T> = new(...args: any[]) => T;

interface XInterface {
    value: string;
 }

function make<T extends Constructor<any>>(Base: T) {
    return class extends Base implements XInterface {
        value = "Hello!"
    }
}

// Great. Works.
class _X1 extends make(Function) { hello() { this.value; }}

type _T1 = Constructor<HTMLElement>;

// Oops. But changing `make(Base)`, to `make(Base as _T1)` works perfectly, 
// which makes no sense.
function make2<T extends _T1>(Base: T) {
    return class extends make(Base) { }
}

Expected behavior:
Compile.

Actual behavior:
Type '{ new (...args: any[]): make.(Anonymous class); prototype: make.(Anonymous class); } & T' is not a constructor function type.

@weswigham
Copy link
Member

I think this is the same as #13807.

@weswigham weswigham added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Nov 19, 2018
@KyleJune
Copy link

// Oops. But changing make(Base), to make(Base as _T1) works perfectly

I believe that's because _T1 is just a constructor. When you pass a generic type to another generic function, it appears to lose the ability to tell that it is a constructor function type. The error your getting is not from the arg you are passing to make, it's actually from trying to extend the result of your make function call. I believe my examples below illustrate the problem more clearly.

type Constructor<T = {}> = new (...args: any[]) => T;
class Test {}
function make1<T extends Constructor>(base: T): T {
  return class extends base {};
}
function make2<T extends Constructor>(base: T) {
  return class extends make1(base) {};
}
class Test1 extends make1(Test) {}

Both make2's anonymous class and Test1 class are able to extend make1(Test) because the generic T extends Constructor.

function make3<T extends Constructor>(base: T): Constructor {
  return class extends base {};
}
function make4<T extends Constructor>(base: T) {
  return class extends make3(base) {};
}
class Test2 extends make3(Test) {}

Both make4's anonymous class and Test2 class are able to extend make3(Test) because it is cast to the Constructor type.

Based on the above two examples, we can see that both Constructor and T are constructor type functions and it allows them to be used as such. So how about the union of T & Constructor?

function make5<T extends Constructor>(base: T): T & Constructor {
  return class extends base {};
}
function make6<T extends Constructor>(base: T) {
  return class extends make5(base) {};
}
class Test3 extends make5(Test) {}

The Test3 class is able to extend make5(Test) but make6's anonymous class is unable to extend make5(base) because of the following typescript error.

Type 'T & Constructor<{}>' is not a constructor function type. ts(2507)

Based on the fact that it works for the Test3 class but not make6's anonymous class, it looks like it doesn't recognize that T is extending Constructor. I believe that is why it refuses to accept T & Constructor as a constructor function type. After seeing that I decided to try without casting.

function make7<T extends Constructor>(base: T) {
  return class extends base {};
}
function make8<T extends Constructor>(base: T) {
  return class extends make7(base) {};
}
class Test4 extends make7(Test) {}

Just like the other example, the Test4 class is able to extend make7(Test) but make8's anonymous class is unable to extend make7(base) because of the following typescript error.

Type '{ new (...args: any[]): make7<T>.(Anonymous class); prototype: make7<any>.(Anonymous class); } & T' is not a constructor function type. ts(2507)

I suspect the reason is the same, T not being recognized as being a constructor function type. I tried finding ways to work around this issue but haven't been able to. Both of the workarounds shown above, casting to T or Constructor, work but have drawbacks.

  1. If you cast to type T, the class extending the class returned by your make function would not know about the attribute/method types on your anonymous class.
  2. If you cast to type Constructor, where AnInterface has all the type information that your anonymous class has, the class extending the class returned by your make function would not know about the attributes/method types on your base class T.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

3 participants