#bizarre

169 messages · Page 1 of 1 (latest)

placid tide
#
function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: { [key: string]: (...args: any[]) => any }
       run<Key extends keyof this['map']>(
           key: Key, args: this['map'][Key]
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) // 3 is a number, so it should fit (n: number)
 /*
        Argument of type 'number' is not assignable to parameter of type 'this["map"]["test"]'.
        Type 'number' is not assignable to type '(n: number) => string'.ts(2345) */
    }
}

I want the second argument of this.run() to have to be whatever the parameter is of the function this.map['test']

latent panther
forest ivy
#

yeah

       run<Key extends keyof this['map']>(
           key: Key, args: Parameters<this['map'][Key]>
       ) { }
placid tide
#

I knew that was necessary

#

but it does not work for some resaon

#

look at this

naive elkBOT
placid tide
#
function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: { [key: string]: (...args: any[]) => any }
       run<Key extends keyof this['map']>(
           key: Key, args: Parameters<this['map'][Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) /*
        Argument of type 'number' is not assignable to parameter of type 'Parameters<this["map"]["test"]>'.ts(2345) */
    }
}```
forest ivy
#

it's not possible to call it I think, since it could be defined more narrowly

placid tide
#

it is able to see that this['map'][Key] is (n: number) => string

so why does that info dissappear when you wrap it in Parameters<>?

fossil ivy
#

Parameters<T> gives back a tuple, so it would be [number]

#

You want Parameters<this['map'][Key]>[0].

placid tide
#

I thought that too already

#

but when I wrap 3 in []

#

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: { [key: string]: (...args: any[]) => any }
       run<Key extends keyof this['map']>(
           key: Key, args: Parameters<this['map'][Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', [3]) /*
        Argument of type '[number]' is not assignable to parameter of type 'Parameters<this["map"]["test"]>'.ts(2345) */
    }
}```
#

still fail

naive elkBOT
#
nonspicyburrito#0

Preview:ts function Mix<T extends new (...args: any[]) => any>( Base: T ) { abstract class Ext extends Base { abstract map: { [key: string]: (...args: any[]) => any } run<Key extends keyof this["map"]>( key: Key, args: Parameters<this["map"][Key]>[0] ...

placid tide
#

some may have multiple arguments

#

so I can't do [0]

#
function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: { [key: string]: (...args: any[]) => any }
       run<Key extends keyof this['map']>(
           key: Key, args: Parameters<this['map'][Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = {
        'test': (n: number) => 'ok',
        'second': (n: number, y: string) => 7
    }
    ctx() {
        this.run('test', [3])
        this.run('second', [5, 'test'])
    }
}```
forest ivy
#

a sublass of Foo could re-define this["map"]["test"] more narrowly than you have there

placid tide
#

as in, I wont leave redeclaring stuff as a responsibility to consumers

fossil ivy
#

A subclass can still overwrite it.

placid tide
#

the library needs to just handle all of that underneath

#

yes and then that undercuts the point of the mixin

#

all the subclass needs to do is implmeent map

#

the mixin uses that for all the type constraints

#

via this['map']

#

which we can see works in other scenarios

#

it is bizarre that it does not work with Parameters<this['map']>

fossil ivy
forest ivy
#

redefining it like run<Key extends keyof Foo['map']>(key: Key, args: Parameters<Foo['map'][Key]>): void {}

#

fixes the issue

placid tide
#
function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: { [key: string]: (...args: any[]) => any }
       run<Key extends keyof this['map']>(
           key: Key, ...args: Parameters<this['map'][Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = {
        'test': (n: number) => 'ok',
        'second': (n: number, y: string) => 7
    }
    ctx() {
        this.run('test', 3)
        this.run('second', 5, 'test')
    }
}```
#

making it a ... spread argument doesnt fix it either

forest ivy
#
       run<T extends { map: { [key: string]: (...args: any[]) => any } }, Key extends keyof T['map']>(this: T,
           key: Key, args: Parameters<T['map'][Key]>
       ) { }

doesn't work either

naive elkBOT
latent panther
#

!ts

naive elkBOT
#
type NoInfer<T> = [T][T extends T ? 0 : never];

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: AnyMap
       run<M extends AnyMap, Key extends keyof M>(
           this: { map: M }, key: Key, ...args: Parameters<M[Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) // 3 is a number, so it should fit (n: number)
    }
}
latent panther
#

nah it works

forest ivy
#

oh I just needed to descend into this?

#

weird that works but capturing the whole type doesnt

latent panther
#

maybe

forest ivy
#

I think this might be a bug, or at least a design limitation

latent panther
#

yeah i did try what you did earlier

#

and it indeed didn't work

forest ivy
#

I'm not sure why this is refusing to be resolved

latent panther
latent panther
placid tide
#

it must be a bug since this['map'] works elsewher

fossil ivy
#

this type always acts weird because of possible subclass.

latent panther
#

if you mean without this: T

forest ivy
#

problem is that just infers T = this so it fixes nothing

forest ivy
#

so it's weird it's not working

fossil ivy
#

Well, unless TS is taking into account method bivariance.

latent panther
fossil ivy
latent panther
placid tide
# latent panther nah it works

i dont get how this is even woring ```ts
type NoInfer<T> = [T][T extends T ? 0 : never];

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
abstract class Ext extends Base {
abstract map: AnyMap
run<M extends AnyMap, Key extends keyof M>(
this: { map: M }, key: Key, ...args: Parameters<M[Key]>
) { }
}
return Ext
}

class Foo extends Mix(Object) {
map = { 'test': (n: number) => 'ok'}
ctx() {
this.run('test', 3) // works
this.run('foo', 5) // doesn't work, but I expected it too. How are you constraining it to this.map, if you are not using this but instead AnyMap?
}
}

latent panther
#

because they could easily make M resolve to this['map'] to be more correct

latent panther
#

(see the first parameter of run)

placid tide
#

yeah but how is it linked to abstract map

#

if you have them both be :numer

#

you could do 7 and 5

#

different

fossil ivy
#

It's linked via this: { map: M }.

forest ivy
#

nice

       run<M extends AnyMap, Key extends keyof M, U extends unknown[]>(
           this: { map: M & { [P in Key]: (...args: U) => unknown } }, key: Key, ...args: U
       ) { }

this gets rid of the nasty call to Parameters

placid tide
#

where is this happening

#

you mean the parameter name?

fossil ivy
#

It's in the run.

placid tide
#
           this: { map: M }, key: Key, ...args: Parameters<M[Key]>
#

that is just a parameter name...

#
type NoInfer<T> = [T][T extends T ? 0 : never];

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: AnyMap
       run<M extends AnyMap, Key extends keyof M>(
           foo: { map: M }, key: Key, ...args: Parameters<M[Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) // works
    }
}
#

breaks if i change it

#

that is weird

fossil ivy
#

this is a TS feature, it's not "just a parameter"

placid tide
#

so you cannot name parameters this?

fossil ivy
#

this tells TS the shape of this, it isn't a parameter.

fossil ivy
placid tide
#

without any magic needed

fossil ivy
#

No idea, this type is just weird I guess.

placid tide
#

so have we found a version that works? and is not broken?

fossil ivy
#

Good thing I don't use classes.

placid tide
#

an approach that works for ```ts
function Mix<T extends new (...args: any[]) => any>(Base: T) {
abstract class Ext extends Base {
abstract map: { [key: string]: (...args: any[]) => any }
run<Key extends keyof this['map']>(
key: Key, ...args: Parameters<this['map'][Key]>
) { }
}
return Ext
}

class Foo extends Mix(Object) {
map = {
'test': (n: number) => 'ok',
'second': (n: number, y: string) => 7
}
ctx() {
this.run('test', 3)
this.run('second', 5, 'test')
}
}```

fossil ivy
#

People gave you 2 solutions above already.

placid tide
#

yeah but there was talk about it being an oversight

forest ivy
#

avoiding Parameters is good because it makes it more likely that overloads and generic functions will still work, kinda

forest ivy
#

or it might be a fundemental design limitation. It's hard to know with typescript

placid tide
forest ivy
#

yeah. Parameters is a conditional type

#

conditional types over generic parameters (this is implicitly generic inside of classes) often refuse to resolve

#

it's why conditional return types are so annoying

#

by declaring this parameter in the function signature, you're removing that implicit generic from the function body

forest ivy
placid tide
#

i dont think these functions will ever have generics

#

since thyere just lambdas

forest ivy
# placid tide in this scenario, what is advantageous about that?

So if you decide to use Mix inside of a generic function like

function foo<T>() {
    const foo = new Mix<T>
    foo.run // this will never work, because conditional types involving generics can't be resolved.
}

this snippet won't work with Parameters because it's a conditional type. With my version, it's a structural type and that can sometimes work, but not always

#

anyway my own personal rule of thumb is: as far as possible avoid ReturnType and Parameters and prefer destructuring types instead

placid tide
#

and the other guys version will never work?

#

vs your sometimes

forest ivy
#

yeah, basically. Is you're choice really, so long as no overloads/generics are in play, both solutions using Parameters and not will work just fine

#

as soon as you add one of those Parameters begins to break apart

placid tide
forest ivy
# placid tide referring to this

to be absolutely clear, mine is just a slight modification of this

type NoInfer<T> = [T][T extends T ? 0 : never];

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: AnyMap
       run<M extends AnyMap, Key extends keyof M, U extends unknown[]>(
           this: { map: M & { [P in Key]: (...args: U) => unknown } }, key: Key, ...args: U
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) // 3 is a number, so it should fit (n: number)
    }
}
placid tide
#

other guys reads more obviously but I guess it doesnt matter if no one ever looks at this deeply nested architecture code

forest ivy
#

I've used his code, I've just added the generic parameter U which is used to infer the parameters of the key function

placid tide
#

thats the only line

forest ivy
#

yeah

#

its just & { [P in Key]: (...args: U) => unknown } instead which tells typescript how to infer U

#

in general, I did a bunch of frustrating work trying to make bind/curry work, I found that Parameters was to be avoided at all costs

#

but if you're not making things you expect people to use with generics i.e. forwarding a class Blarg<T> or function blarg<T>'s T parameter into Mix, its mostly wasted effort

#

I still do it anyway, but you may not appreciate my style

placid tide
#
type NoInfer<T> = [T][T extends T ? 0 : never];

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: AnyMap
       run<M extends AnyMap, Key extends keyof M>(
           this: { map: M }, key: Key, ...args: Parameters<M[Key]>
       ) { }
    }
    return Ext
}

class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', 3) // works
    }
}
#

@forest ivy is U a special keyword in generics?

        run<M extends AnyMap, Key extends keyof M, U extends unknown[]>(
            this: { map: M & { [P in Key]: (...args: U) => unknown } }, key: Key, ...args: U
        ) { }

works

But ```ts
run<M extends AnyMap, Key extends keyof M, P extends unknown[]>(
this: { map: M & { [P in Key]: (...args: P) => unknown } }, key: Key, ...args: P
) { }

fails
#

chaning U -> P breaaks it

forest ivy
#

appears to be unnecessary

#

[P in Key] collides/shadows

placid tide
#

ooh didnt even see that

#

@forest ivy @fossil ivy ```ts

type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
abstract class Ext extends Base {
abstract map: AnyMap
foo = { bar: () => 7 }
run<M extends AnyMap, Key extends keyof M>(
this: { map: M }, key: Key, ...args: Parameters<M[Key]>
) {
this.foo.bar() // Property 'foo' does not exist on type '{ map: M; }'.(2339)
}
}
return Ext
}

this is a problem with the solutions
#

my actual use case has members like foo implemented in the mixin

#

is it as simple as adding a & { foo } to fix?

#

also fails in your version webstrand

forest ivy
#

you have to add it to the definition, or you can do something like this & perhaps?

placid tide
#

this looks more intense ```ts
this: { map: M & { [P in Key]: (...args: U) => unknown } }, key: Key, ...args: U

than simply  ```ts
           this: { map: M }, key: Key, ...args: Parameters<M[Key]>
forest ivy
#

dunno tbh

#

Parameters expands to T extends (...args: infer U) => unknown ? U : never

#

so I think it should be similar cost

placid tide
#

@forest ivy interesting difference between the two versions is where and how TS errors when you pass incorrect stuff

#

red squiggle is in different location

#

and your error message is gigantic

placid tide
#

it solves the this.foo error

#

but now it allows this.run to pass with incorrect args

forest ivy
#

weird

#

wait no it doesn't?

naive elkBOT
#
webstrand#0

Preview:```ts
type AnyMap = {[key: string]: (...args: any[]) => any}

function Mix<T extends new (...args: any[]) => any>(
Base: T
) {
abstract class Ext extends Base {
abstract map: AnyMap
run<
M extends AnyMap,
Key extends keyof M,
U extends unknown[]
>(
this
...```

placid tide
#

his version ```ts
type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
abstract class Ext extends Base {
abstract map: AnyMap
run<M extends AnyMap, Key extends keyof M, U extends unknown[]>(
this: this & { map: M & { [P in Key]: (...args: U) => unknown } }, key: Key, ...args: U
) { }
}
return Ext
}

class Foo extends Mix(Object) {
map = { 'test': (n: number) => 'ok'}
ctx() {
this.run('test', "3") // 3 is a number, so it should fit (n: number)
}
}```

#

doesnt

#

this.run('test', "3") passes in his

#

so thats another advantage of yours I guess

forest ivy
#

does tsserver need a restart?

placid tide
#
type AnyMap = { [key: string]: (...args: any[]) => any }

function Mix<T extends new (...args: any[]) => any>(Base: T) {
    abstract class Ext extends Base {
       abstract map: AnyMap
        foo = { bar: () => 7 }

       run<M extends AnyMap, Key extends keyof M>(
           this: this & { map: M }, key: Key, ...args: Parameters<M[Key]>
       ) { 
        this.foo.bar() // 
       }
    }
    return Ext
}
class Foo extends Mix(Object) {
    map = { 'test': (n: number) => 'ok'}
    ctx() {
        this.run('test', "3") // SHOULD FAIL!
    }
}```
#

try this

forest ivy
#

inference is failing for just one type parameter

#

that's super weird

#

this also looks suspiciously like a bug