You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Support to method decorator that change the method signature #49229
As the title said, I just started using typescript and I want to have a method decorator that is able to change the method signature and have tsc knew about the new signature.
🔍 Search Terms
I have seen a lot of works around class decorators, like #4881 but nothing specifically on supporting the behavior I have described.
✅ Viability Checklist
My suggestion meets these guidelines:
[*] This wouldn't be a breaking change in existing TypeScript/JavaScript code
[*] This wouldn't change the runtime behavior of existing JavaScript code
[*] This could be implemented without emitting different JS based on the types of the expressions
[*] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
If not for other things, I would like to understand if there is some way to set the types of decoratorto signal the new decorated function signature.
📃 Motivating Example
As a minimal example, look at
functiondecorator(_target: any,_propertyKey: string,descriptor: PropertyDescriptor): void{constmethod=descriptor.value;descriptor.value=functionwrapper(a: number,b: number,c: number): void{// here you have access to a, b, cconsole.log(a,b,c);returnmethod.call(this,a,b,c);}}classC{
@decoratormethod(a: number,b: number): void{// here you have access to a, bconsole.log(a,b);}}newC().method(1,2,3);// here you pass a, b and c
tsc sasys that there is an error in the last line, because it doesn't "know" that C.method have a new signature, after applying the decorator.
💻 Use Cases
I'm implementing a simple method wrapper that add a parameter to do pre-checks on method invocation itself
It is usually an anti-pattern as decorators are meant to be transparent - meaning that no changes to method signature must be done by the decorator and only a behavior may be added/changed (like doing validation/logging).
As for your case it seems like you are adding some extra "private" arguments that your consumer and receiver must not see/access and only your decorator can see and possibly some privileged part of the code?
In this case you can create a shortcut mapped type that adds new argument on the fly so you can cast your method in place just before invoking it. Pseudo TS could look something like this:
typeAnyFunction=(...args: any[])=>any;typeExtendedMethod<TMethodextendsAnyFunction>=(// Add extra argument of type "string" at the end
...args: [...Parameters<TMethod>,string])=>any;typeExtendedObject<T>={// Extend all methods on the object[PinkeyofT]: T[P]extendsAnyFunction ? ExtendedMethod<T[P]> : T[P];};functionasExtended<T>(obj: T){returnobjasExtendedObject<T>;}classExample{method(a: number,b: number){}}// Standard callnewExample().method(1,2);// Extended call that knows about extra argumentasExtended(newExample()).method(1,2,'3');
Reacted by Thundercraft5, Gabriel Porto, Paul Kiddle, John Wright, Szymon Bretner, runjuu, Alex Ryan, PJ Eby, Gonçalo Fernandes, Jacob Sánchez and 10 more
I don't agree that decorators should only be used for validation/logging. I think that adding methods and properties to classes via decorators is one of their main goals and capabilities and typescript should be able to access to the added signatures. If there is such wish to let decorators remain a 'hidden' thing, maybe the best option would be to have a flag in compiler options like "DecoratorsChangeTypes" ?
Reacted by Van Tigranyan, 吉凯, Dan Chu, TiPOS GmbH, Bela Bohlender, Paul Kiddle, John Wright, Szymon Bretner, Oussama Benhamed, David Ekeflod and 21 more
This feature would be a big QOL improvement for TS developers wanting to rely on composition over inheritance in their classes.
It would be very natural to allow the wrapper method to have its own signature which, to me, would substantially increase the utility of existing decorator functionality.
Reacted by AndriiDemchenko, Jialiang T., Alex Ryan, oddguan, Alex Patin, Adam, Valerie-June, PJ Eby, Segu, WillsterJohnsonAtZenesis and 14 more
I hope it's going to be implemented right after decorators reach stage 4. Currently it doesn't make a lot of sense to be able to change signatures while decorators aren't standardised, unfortunately.
To add onto the validation topic, I believe that is actually a very convenient case for changing the signature. When working with a library like Zod, it is not rare for the result of the parsing operation to have a different type than the input, even if it's just removing null or undefined from property types.
In my case, I wanted to write a decorator to do exactly that, i.e., running a method's input through a provided Zod schema first before calling the method itself, and then passing the result to the method. Since this is a use case that happens in many different places in the code, I thought that a decorator would be a great abstraction to avoid having to replicate the manual parse invocation at all usage sites.
Reacted by Thibaud Courtoison, Travis Fischer, Benedikt Strehle, Neil Baillie, Jonathan Hefner and snarbles2
It would be particularly helpful for decorators for decorators to be able to change the class type as well, for example for adding extra methods:
functionusing<This,T>(field: undefined,ctx: ClassFieldDecoratorContext<This,T>){// add disposable ctx.access to metadata}functiondisposable<Classextendsnew(...args: any)=>any>(cls: Class,ctx: ClassDecoratorContext<Class>){returnclassextendscls{[Symbol.dispose](): void{// dispose disposable fields collected in metadata}}}
@disposableclassFoo{
@usingreadonlyfield=createSomeDisposable();}// Currently a type error even though it works
using foo=newFoo();
Reacted by Sʜɪᴍᴜʀᴀ Yū, David Brito, Arthur Fontaine, Ethan Resnick and Luis
I completed a TypeScript-Go/Corsa implementation of type-changing standard method decorators. This is an experimental language extension and implementation artifact, not a claim that the semantics have been accepted for upstream TypeScript.
The body of method still uses (value: number) => number, while the observable type of Example.prototype.method is (value: string) => number.
Stage-result rules
A callable result replaces the incoming callable.
Callable unions and overloaded callable results preserve all supported call signatures and callable facets.
void, undefined, and never preserve the incoming callable.
A callable-or-preserving result exposes the union of the replacement and incoming callables.
any remains any.
Invalid non-callable and unknown results retain their diagnostics and recover to the last valid callable, allowing later decorators to continue composing.
Under strictNullChecks: false, explicit undefined can disappear during type normalization. The implementation therefore tracks fallback provenance through aliases, conditional types, inferred returns, explicitly typed identifier expressions, and uncertain indexed-access forms. Recursive analysis is bounded; indeterminate cases preserve the incoming callable rather than unsafely narrowing it away.
Supported scope
The implementation covers:
Direct decorators, decorator factories, and stacked decorators
Callable unions and callable-or-void/undefined results
Overloaded source methods and overloaded replacement callables
Generic methods and classes, explicit type arguments, aliases, and class expressions
Polymorphic this, static versus instance This, and context.static
Public, protected, TypeScript-private, and JavaScript #private methods without widening access
Callable properties, construct signatures, and string/number index signatures
Declaration emit of the final observable type
Checked JavaScript and JSDoc-authored decorators
Checker query-order determinism
Incremental declaration updates
Language-service quick-info and diagnostic refresh after edits
Declaration emit
Declaration emit uses method syntax when one ordinary call signature completely represents the final type. It uses property syntax when method syntax would lose information such as unions, overload sets, any, construct signatures, properties, or index signatures.
For checked JavaScript, a member whose public type changes does not reuse stale type-bearing JSDoc. Unchanged JavaScript members and TypeScript API documentation retain their existing documentation behavior.
Intentional exclusions
This implementation does not apply decorator-driven member-type mutation to:
Legacy decorators
Non-method decorators, including fields, accessors, classes, and parameters
Abstract, ambient, or otherwise bodyless methods
isolatedDeclarations
Unchecked JavaScript
These are explicit boundaries of this implementation, not assertions that a future language design cannot support them.
Verification
The branch includes focused checker, compiler, declaration, runtime, incremental, and Fourslash coverage for the supported behavior and exclusions. In particular, it tests overload composition, callable facets, generic/alias instantiation, both conditional-union orders, non-strict-null fallback provenance, query-order independence, incremental edits, and language-service edits.
Activity
gund commentedon Jun 1, 2022
It is usually an anti-pattern as decorators are meant to be transparent - meaning that no changes to method signature must be done by the decorator and only a behavior may be added/changed (like doing validation/logging).
As for your case it seems like you are adding some extra "private" arguments that your consumer and receiver must not see/access and only your decorator can see and possibly some privileged part of the code?
In this case you can create a shortcut mapped type that adds new argument on the fly so you can cast your method in place just before invoking it. Pseudo TS could look something like this:
soffyo commentedon Oct 3, 2022
I don't agree that decorators should only be used for validation/logging. I think that adding methods and properties to classes via decorators is one of their main goals and capabilities and typescript should be able to access to the added signatures. If there is such wish to let decorators remain a 'hidden' thing, maybe the best option would be to have a flag in compiler options like "DecoratorsChangeTypes" ?
ssalbdivad commentedon Jul 25, 2023
This feature would be a big QOL improvement for TS developers wanting to rely on composition over inheritance in their classes.
It would be very natural to allow the wrapper method to have its own signature which, to me, would substantially increase the utility of existing decorator functionality.
finom commentedon May 22, 2024
I hope it's going to be implemented right after decorators reach stage 4. Currently it doesn't make a lot of sense to be able to change signatures while decorators aren't standardised, unfortunately.
sybereal commentedon Nov 7, 2024
To add onto the validation topic, I believe that is actually a very convenient case for changing the signature. When working with a library like Zod, it is not rare for the result of the parsing operation to have a different type than the input, even if it's just removing
nullorundefinedfrom property types.In my case, I wanted to write a decorator to do exactly that, i.e., running a method's input through a provided Zod schema first before calling the method itself, and then passing the result to the method. Since this is a use case that happens in many different places in the code, I thought that a decorator would be a great abstraction to avoid having to replicate the manual parse invocation at all usage sites.
Jamesernator commentedon Jan 19, 2025
It would be particularly helpful for decorators for decorators to be able to change the class type as well, for example for adding extra methods:
Arlen22 commentedon Apr 18, 2025
Here's another example where it would be very useful. I'm surprised this isn't supported already.
ayden94 commentedon Jul 31, 2026
Completed implementation evidence
I completed a TypeScript-Go/Corsa implementation of type-changing standard method decorators. This is an experimental language extension and implementation artifact, not a claim that the semantics have been accepted for upstream TypeScript.
prototype/decorator-method-final-typeb52b692cProposed semantic model
The source method signature and the decorated member type have separate roles:
ClassMethodDecoratorContext.For example:
The body of
methodstill uses(value: number) => number, while the observable type ofExample.prototype.methodis(value: string) => number.Stage-result rules
void,undefined, andneverpreserve the incoming callable.anyremainsany.unknownresults retain their diagnostics and recover to the last valid callable, allowing later decorators to continue composing.Under
strictNullChecks: false, explicitundefinedcan disappear during type normalization. The implementation therefore tracks fallback provenance through aliases, conditional types, inferred returns, explicitly typed identifier expressions, and uncertain indexed-access forms. Recursive analysis is bounded; indeterminate cases preserve the incoming callable rather than unsafely narrowing it away.Supported scope
The implementation covers:
void/undefinedresultsthis, static versus instanceThis, andcontext.static#privatemethods without widening accessDeclaration emit
Declaration emit uses method syntax when one ordinary call signature completely represents the final type. It uses property syntax when method syntax would lose information such as unions, overload sets,
any, construct signatures, properties, or index signatures.For checked JavaScript, a member whose public type changes does not reuse stale type-bearing JSDoc. Unchanged JavaScript members and TypeScript API documentation retain their existing documentation behavior.
Intentional exclusions
This implementation does not apply decorator-driven member-type mutation to:
isolatedDeclarationsThese are explicit boundaries of this implementation, not assertions that a future language design cannot support them.
Verification
The branch includes focused checker, compiler, declaration, runtime, incremental, and Fourslash coverage for the supported behavior and exclusions. In particular, it tests overload composition, callable facets, generic/alias instantiation, both conditional-union orders, non-strict-null fallback provenance, query-order independence, incremental edits, and language-service edits.
The current implementation passes:
npx hereby testDecisions needed for an upstream design