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

[2.8.0-dev.20180216] Arguments with mixin type? #21994

Closed
canonic-epicure opened this issue Feb 16, 2018 · 12 comments
Closed

[2.8.0-dev.20180216] Arguments with mixin type? #21994

canonic-epicure opened this issue Feb 16, 2018 · 12 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@canonic-epicure
Copy link

canonic-epicure commented Feb 16, 2018

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

class Some {}

export const Mixin1 = (T : Constructable) => class OtherModel extends T {
    method1() {}
}

export const Mixin2 = (T : Constructable) => class DiffableModel extends T {
    method2() {}
}

class Test extends Mixin2(Mixin1(Some)) {
    some () {
        this.method1()
    }

}

let obj = new Test()

const func = (m : InstanceType<ReturnType<typeof Mixin1>>) => false;

func(obj) // fails here 

The code above does not compile. Any advise how to express this functionality in types?

The intention is to use mixins, and to declare an argument to the function, that is of type, that includes certain mixin. Now that works, but when passing a value, that implements a mixin, a call fails.

@canonic-epicure
Copy link
Author

The goal is to emulate the haskell typeclasses.

It seems the problem is that Test class does not "understand" that it "inherit" from the "Mixin1". But it does know about the "Mixin2" though.

@ghost
Copy link

ghost commented Feb 16, 2018

See https://blog.mariusschulz.com/2017/05/26/typescript-2-2-mixin-classes for an example -- the mixin functions need to be generic or else they just return typeof OtherModel and typeof DiffableModel.

@ghost ghost added the Question An issue which isn't directly actionable in code label Feb 16, 2018
@canonic-epicure
Copy link
Author

canonic-epicure commented Feb 16, 2018

No, thats not related, same thing:

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

class Some {}

function Mixin1<T extends Constructable>(T : Constructable) {
    return class OtherModel extends T {
        method1() {}
    }
}

function Mixin2<T extends Constructable>(T : Constructable) { 
    return class DiffableModel extends T {
        method2() {}
    }
}

class Test extends Mixin2(Mixin1(Some)) {
    some () {
        this.method1()
    }

}

let obj = new Test()

const func = (m : InstanceType<ReturnType<typeof Mixin1>>) => false;

func(obj)

@ghost
Copy link

ghost commented Feb 16, 2018

You need to write T: T not T: Constructable.

@canonic-epicure
Copy link
Author

Ah, indeed!

Thank you for the help!

@ghost
Copy link

ghost commented Feb 16, 2018

Welcome. Also helped me discover an issue #21995. (Also microsoft/TypeScript-Handbook#15)

@canonic-epicure
Copy link
Author

Can you perhaps elaborate a bit on the mixin functions need to be generic or else they just return typeof OtherModel and typeof DiffableModel?

In my initial attempt it was:
(Base : Constructable) => class OtherModel extends Base

And correct way is
function Mixin1<T extends Constructable>(Base : T) {

The difference is quite subtle for me, Base : T = Constructable vs Base : T extends Constructable.

@ghost
Copy link

ghost commented Feb 16, 2018

If Base is just Constructable, we don't know any thing about the particular type of Base. So, we don't know what methods a class inheriting from it will inherit. So you just get the methods defined in the mixin and no inherited methods.
But if you extend from T, once you fill in a particular value of T, we can correctly fill in the type of the class extending from it, so it will have both the methods of T and the methods of the mixin.

@canonic-epicure
Copy link
Author

Ok, I see.

Now one more question if you don't mind. So this code works fine:

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

class Some {}

function Mixin1<T extends Constructable>(Base : T) {
    return class OtherModel extends Base {
        method1() {}
    }
}

function Mixin2<T extends Constructable>(Base : T) {
    return class DiffableModel extends Base {
        method2() {}
    }
}

class Test extends Mixin2(Mixin1(Some)) {
    some () {
        this.method1()
    }

}

let obj = new Test()

const func = (m : InstanceType<ReturnType<typeof Mixin1>>) => false;

func(obj)

Now, I want to define an alias, like:

export type Mixin<T> = InstanceType<ReturnType<T>>

But

error TS2344: Type 'T' does not satisfy the constraint '(...args: any[]) => any'

Trying

export type Mixin<T extends (...args:any[]) => any> = InstanceType<ReturnType<T>>

solves the above, but:

const func1 = (m : Mixin<typeof Mixin1>) => false;

func1(obj)

fails with:

error TS2345: Argument of type 'Test' is not assignable to parameter of type 'never'.

@canonic-epicure
Copy link
Author

I would expect the alias to behave more like a "macros"?

@ghost
Copy link

ghost commented Feb 16, 2018

@samuraijack That looks like a bug: #21997. You can work around it with:

// Fixed, see https://github.com/Microsoft/TypeScript/issues/21997
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : never;
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : never;
export type Mixin<T extends (...args:any[]) => any> = InstanceType<ReturnType<T>>;

@canonic-epicure
Copy link
Author

Thanks!

@microsoft microsoft locked and limited conversation to collaborators Jul 3, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

1 participant