Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TS cannot infer a class instance method prop name in a static method

Tags:

typescript

Trying to make a static method that accept only instance method prop names, unfortunately out of ideas how to make this work with the following case. Any thoughts (please no type assertion in the call signature)?

Playground

type MethodKeys<T extends object> = keyof { [K in keyof T as T[K] extends (...args: any) => any ? K : never]: unknown };

class Model{ 
    static chainMethod<C extends typeof Model, T extends InstanceType<C>, K extends MethodKeys<T>>
        (this: C, name: K, method: T[K]) {
        
        }
    method(){

    }
}

class Party{
    static addProps<C extends typeof Model>(model: C){
        model.chainMethod('method', function(){ // error

        })
    }
}

Another try:

Playground

class Model{ 
    static chainMethod<C extends typeof Model, T extends InstanceType<C>, K extends keyof T, F extends T[K] extends (...args: any) => any ? T[K]: never>
        (this: C, name: K, method: F) {
        
        }
    method(){

    }
}

class Party{
    static addProps<C extends typeof Model>(model: C){
        model.chainMethod('method', function(){

        })
    }
}

UPDATE
Matt Kantor has solved the second solution, but I would still prefer the first one where autocomplete would show only method names

like image 506
Alexander Nenashev Avatar asked Dec 05 '25 15:12

Alexander Nenashev


1 Answers

I believe this has the behavior you want:

Playground

class Model{ 
    static chainMethod<C extends typeof Model, K extends keyof InstanceType<C>, F extends InstanceType<C>[K]>
        (this: C, name: K, method: F & ((...args: never) => unknown)) {
        
        }
    nonMethod?: string
    method(){

    }
}

class Party{
    static addProps<C extends typeof Model>(model: C){
        model.chainMethod('method', function(){})

        // these should all have errors:
        model.chainMethod('method', function(x: string){})
        model.chainMethod('nonMethod', 'test')
        model.chainMethod('nonExistentKey', function(){})
    }
}

It's a good rule of thumb to use as few type parameters as possible in generic function types. I also replaced your conditional type (to check method-ness) with an intersection, as that's simpler for the type checker to reason about.


As per your update, here's another version with different autocomplete behavior:

Playground

type MethodKeys<T> = keyof {
    [K in keyof T as T[K] extends (...args: never) => unknown ? K : never]: unknown
}

class Model{ 
    static chainMethod<T extends Model, K extends MethodKeys<T>, F extends T[K]>
        (this: abstract new (...args: never) => T, name: K, method: F) {
        
        }
}
like image 183
Matt Kantor Avatar answered Dec 11 '25 03:12

Matt Kantor



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!