Let\'s imagine that I have an A
decorator that is being applied on a method that returns a string. The decorator uses that string and in the end returns B
See below for 3.0 solution
A decorator can't change the structure of the type, no type of decorator can do this (class, method or parameter decorators)
Using Typescript 2.8, you can write a function that will take another function as a parameter and perform a change of the return type, but you will loose things like parameter names, optional parameters and multiple signatures. Also you should take care, as this way of doing this makes someMethod
a field that is assigned a method, instead of a class method. So the field will have to be assigned in each constructor instead of being assigned to the prototype
once, this may have performance implications.
class B<T> {
constructor(public value: T) { }
method() { return this.value; }
}
function A<T extends (...args: any[]) => any>(fn: T): ReplaceReturnType<T, B<ReturnType<T>>> {
return function (this: any, ...args: any[]) {
return new B<ReturnType<T>>(fn.apply(this, args));
} as any;
}
class X {
constructor() {
this.someMethod().method();
}
other() {}
someMethod = A(function (this: X): string {
this.other(); // We can access other members because of the explicit this parameter
return 'lala';
});
}
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;
type ReplaceReturnType<T, TNewReturn> = T extends (a: infer A, b: infer B, c: infer C, d: infer D, e: infer E, f: infer F, g: infer G, h: infer H, i: infer I, j: infer J) => infer R ? (
IsValidArg<J> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J) => TNewReturn :
IsValidArg<I> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I) => TNewReturn :
IsValidArg<H> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H) => TNewReturn :
IsValidArg<G> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F, g: G) => TNewReturn :
IsValidArg<F> extends true ? (a: A, b: B, c: C, d: D, e: E, f: F) => TNewReturn :
IsValidArg<E> extends true ? (a: A, b: B, c: C, d: D, e: E) => TNewReturn :
IsValidArg<D> extends true ? (a: A, b: B, c: C, d: D) => TNewReturn :
IsValidArg<C> extends true ? (a: A, b: B, c: C) => TNewReturn :
IsValidArg<B> extends true ? (a: A, b: B) => TNewReturn :
IsValidArg<A> extends true ? (a: A) => TNewReturn :
() => TNewReturn
) : never
Edit
Since the original question was answered typescript has improved the possible solution to this problem. With the addition of Tuples in rest parameters and spread expressions we now don't need to have all the overloads for ReplaceReturnType
:
type ArgumentTypes<T> = T extends (... args: infer U ) => infer R ? U: never;
type ReplaceReturnType<T, TNewReturn> = (...a: ArgumentTypes<T>) => TNewReturn;
Not only is this shorter but it solves a number of problems
Sample:
type WithOptional = ReplaceReturnType<(n?: number)=> string, Promise<string>>;
let x!: WithOptional; // Typed as (n?: number) => Promise<string>
x() // Valid
x(1); //Ok