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
Adds a data modifier for decorator declarations. Data decorators are simple metadata annotations that auto-store their arguments — no JavaScript implementation needed.
data declabel(target:Model, value:valueofstring);
@label("my-model")
modelFoo {}
Motivation
A large number of decorators in the TypeSpec ecosystem follow the same pattern: accept arguments, store them in a state map, expose a getter. This requires boilerplate across three files (.tsp declaration, .ts implementation, .ts state accessors). Data decorators eliminate this entirely.
Design
Syntax
data is a new modifier keyword on dec declarations, mutually exclusive with extern:
data decmyFlag(target:Model); // boolean flag
data decmyLabel(target:Model, label:valueofstring); // single value
data decmyMeta(target:Model, name:valueofstring, v:valueofint32); // multi-arg
internal data decmyInternal(target:Model); // combinable with internal
Storage
The compiler auto-generates an implementation that stores args in program.stateMap keyed by Symbol.for("data-dec:<fqn>"):
Parameters (beyond target)
Stored value
None
true
One
The value directly
Multiple
{ paramName1: val1, paramName2: val2 }
Compiler API
Generic functions to read data decorator state by FQN string — no generated code needed:
tspd gen-extern-signature generates typed accessors for data decorators:
// For: data dec myFlag(target: Model)exportfunctionisMyFlag(program: Program,target: Model): boolean;// For: data dec myLabel(target: Model, label: valueof string)exportfunctiongetMyLabel(program: Program,target: Model): string|undefined;
Decorator type
The Decorator runtime type gains a declarationKind: "extern" | "data" property (extensible for future kinds).
data as a contextual keyword
data is a modifier keyword but remains usable as an identifier (decorator names, property names, etc.) to avoid breaking existing code like @data in @typespec/events.
Open Questions
Modifier name: Is data the right keyword? Alternatives considered: metadata, declare, attr, pure. data was chosen for brevity and precedent (Kotlin data class), but open to alternatives.
Multiple data decorators on the same node: Should multiple different data decorators be allowed on the same target? Currently there's no restriction — each stores independently. Should we add validation or a way to declare exclusivity?
Behavior with model is / op is: When a model or operation copies from another via is, should data decorator metadata be inherited by the copy? Currently decorators applied via is are re-applied, so data decorators would also be re-applied. Is this the desired behavior, or should data decorators have different inheritance semantics?
Migration Examples
Several existing decorators across the TypeSpec ecosystem are pure "store args in state" and could be migrated to data dec:
Added data decorator modifier for declaring decorators that auto-store their arguments as metadata without requiring a JavaScript implementation.,> ,> typespec,> data dec label(target: Model, value: valueof string);,> ,> @label("my-model"),> model Foo {},> ,> ,> Added compiler API hasDataDecorator, getDataDecoratorValue, and getDataDecoratorTargets for reading data decorator values by FQN.
I generally like the idea of the feature but I have a couple of reservations:
I generally think that relying on constructing a symbol by stringly-typed convention is just too brittle for the compiler API boundary. The generic read APIs for data decorators take FQN as a string, the symbol for storage in the program state map is data-dec:${fqn}. That's super simple, and it does seem stable for now since we have no way to alias decorators. But imagine if we had a way to alias a decorator, just moving a decorator from one namespace to another and preserving a backcompat alias would be an "ABI" breaking change to the metadata interface for that decorator. I think it would probably be best if the symbol for the decorator was embedded into the Decorator Type instance as well, and we could use the Decorator identity rather than its fully-qualified name to extract bound metadata. For visibility, I used Enum identity for a similar purpose, rather than the enum FQN. That said, it's much easier to get a reference to an enum than it is to get a reference to a decorator in library code.
I think three storage shapes depending on the decorator's arity is a real problem for programming contract stability. The arity-based thing does seem like it would be convenient, but I think a regular shape would age better and be easier to analyze with tooling. If the storage shape changes by arity, and you add an argument, even an optional one, suddenly you are changing the ABI for accessing that decorator's metadata which could break a lot of callers.
I'm really not so sure about data as a modifier name. I do see the similarity to data class, though it is still quite different. Is there something that more clearly communicates the idea of "compiler-managed metadata binding". Maybe just auto, but that also comes with its own baggage from other languages.
On the questions:
See no. 3 above for modifier name.
I don't see any reason that data decorators specifically should have any particular exclusivity criteria. If we wanted to have some way to declaratively apply exclusivity I think it would apply just as well to other kinds of decorators, so IMO that question is orthogonal to the design of data decorators except that it would be the only way to do it for data decorators, while manually implemented decorators can of course check exclusivity themselves. Furthermore, this seems likely to be a library-policy concern, so i don't think we could come up with any particular rule that would make sense to enforce at the language level, so it would have to be declarative syntax (decorators of decorators) used to configure exclusivity behavior.
For the same reason, I think the question about applicability through is inheritance is also orthogonal. If we had a way to declaratively state that a decorator does not apply by transition through is relationships, then we would want that to apply to extern decorators as well. So I think as long as we're talking just about the specifics of data decorators, I don't think we want them to do anything unique or special compared to extern decorators from an applicability perspective.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Data Decorators
Summary
Adds a
datamodifier for decorator declarations. Data decorators are simple metadata annotations that auto-store their arguments — no JavaScript implementation needed.Motivation
A large number of decorators in the TypeSpec ecosystem follow the same pattern: accept arguments, store them in a state map, expose a getter. This requires boilerplate across three files (
.tspdeclaration,.tsimplementation,.tsstate accessors). Data decorators eliminate this entirely.Design
Syntax
datais a new modifier keyword ondecdeclarations, mutually exclusive withextern:Storage
The compiler auto-generates an implementation that stores args in
program.stateMapkeyed bySymbol.for("data-dec:<fqn>"):true{ paramName1: val1, paramName2: val2 }Compiler API
Generic functions to read data decorator state by FQN string — no generated code needed:
tspd generation
tspd gen-extern-signaturegenerates typed accessors for data decorators:Decorator type
The
Decoratorruntime type gains adeclarationKind: "extern" | "data"property (extensible for future kinds).dataas a contextual keyworddatais a modifier keyword but remains usable as an identifier (decorator names, property names, etc.) to avoid breaking existing code like@datain@typespec/events.Open Questions
Modifier name: Is
datathe right keyword? Alternatives considered:metadata,declare,attr,pure.datawas chosen for brevity and precedent (Kotlindata class), but open to alternatives.Multiple data decorators on the same node: Should multiple different data decorators be allowed on the same target? Currently there's no restriction — each stores independently. Should we add validation or a way to declare exclusivity?
Behavior with
model is/op is: When a model or operation copies from another viais, should data decorator metadata be inherited by the copy? Currently decorators applied viaisare re-applied, so data decorators would also be re-applied. Is this the desired behavior, or should data decorators have different inheritance semantics?Migration Examples
Several existing decorators across the TypeSpec ecosystem are pure "store args in state" and could be migrated to
data dec:Compiler —
@discriminatorBefore:
After:
No JS needed.
HTTP —
@body,@bodyRoot,@bodyIgnore,@statusCode,@multipartBodyThese are all boolean flags — the decorator just marks the target:
Before:
After:
Same pattern applies to
@bodyRoot,@bodyIgnore,@statusCode,@multipartBody.OpenAPI —
@operationIdBefore:
After:
Not candidates
Decorators that do validation, normalization, or side effects beyond storage are not candidates. Examples:
@doc(template string rewriting),@format(uniqueness validation),@pattern(regex validation),@route(shared route handling),@server(URL parameter parsing).