Estus Flask
Estus Flask

Reputation: 222344

Generic method decorator typing

I'm trying to make foo generic decorator to conform MethodDecorator type:

const foo: MethodDecorator = function <T extends Function = Function>(
    target: object,
    propertyKey: string | symbol,
    descriptor: TypedPropertyDescriptor<T>
): TypedPropertyDescriptor<T> {
    return {
        configurable: true,
        enumerable: false,
        value: descriptor.value!.bind(null)
    };
}

foo may be used directly as helper function, I would prefer to have an option to additionally type it as a generic.

Here is the error:

Type '<T extends Function = Function>(target: object, propertyKey: string | symbol, descriptor: TypedPr...' is not assignable to type 'MethodDecorator'.
  Types of parameters 'descriptor' and 'descriptor' are incompatible.
    Type 'TypedPropertyDescriptor<T>' is not assignable to type 'TypedPropertyDescriptor<Function>'.
      Types of property 'value' are incompatible.
        Type 'T | undefined' is not assignable to type 'Function | undefined'.
          Type 'T' is not assignable to type 'Function | undefined'.
            Type 'T' is not assignable to type 'Function'.

How can foo conform MethodDecorator?

Upvotes: 2

Views: 2638

Answers (1)

Seems like MethodDecorator is not a generic type alias, so T type cannot be narrowed.

type Foo<T> = (x: T) => T; // this one is generic
type Bar = (x: T) => T; // and this is not

Which make sense, since the assumptions that are made in foo (i.e. descriptor.value has a bind property) are not true for MethodDecorator in general.

Since type is a mere alias, we can define our own.

type TypedMethodDecorator = <T extends Function>(
    target: Object,
    propertyKey: string | symbol,
    descriptor: TypedPropertyDescriptor<T>
) => TypedPropertyDescriptor<T> | void;

const foo: TypedMethodDecorator = function (target, propertyKey, descriptor) {
    return {
        configurable: true,
        enumerable: false,
        value: descriptor.value!.bind(null)
    };
}

class C {
    @foo
    m() {
        console.log('this:', this);
    }
}

new C().m(); // 'this: null'

Due to type inference the compiler understands that foo is a valid decorator.

Upvotes: 1

Related Questions