-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Support for design-time decorators #2900
Comments
what is the benefit of using |
For this to be useful in our scenario, we need ambient decorators to be preserved in the generated .d.ts. In our scenario, compiling a set of .ts files produces a library consumed by others via the library's .d.ts (conventional stuff so far). Those who consume the library, compile against it using a custom tool built in terms of the TypeScript compiler APIs. The tool makes use of ambient decorators in the library's .d.ts. Today, we're prototyping this tool in terms of 1.5beta decorators, but ambient decorators would allow us to expand to decorate non-classes/non-functions. |
I noticed that this is on the roadmap for 2.0. Would it be another use-case to allow augmentation of other design time features (like typing)? Something like augmenting the reflect meta-data (@rbuckton). This is part of my never ending quest to be able to get effective design time mixin/trait support while we all wait for TC39 to figure something out. |
@kitsonk can you elaborate on your proposal? |
@mhegazy I might not be the best person to expound upon the proposal, as I have only lived in JavaScript land for an extended period of time (and Delphi a long time ago) and some of the Reflection and Type handling that are part of C# and other languages I am not familiar with. I guess what I am suggesting is that at design time, in these decorators, some of the internal TypeScript constructs would be available. For example, the ability to construct and return a new type, similar to the C# TypeBuilder.CreateType But I guess from my perspective, if I had some programmatic design time access to types via these design-time decorators, I could implement language features (mixins/traits) without needing syntatical support in typescript. From a "vision" objective, I would see something like this: function mixin(target: any, ...mixins: any[]): type {
const typeTarget = typeof target;
mixins.forEach((mixin) => {
const typeMixin = typeof mixin;
for (let t in typeMixin) {
typeTarget[t] = typeMixin[t];
}
});
return typeTarget;
}
class A {
foo() { return false; }
}
class B {
bar() { return 'bar'; }
}
@@mixin(A, B)
class C {
baz() { return 1; }
}
const c = new C();
console.log(c.foo(), c.bar(), c.baz()); Of course, I could put a lot of different logic in there, but essentially I could perform design-time operations on the types. I don't know the inner workings of the compiler, but I suspect there would be good set of semantics for safely programmatically operating on types. |
Just a clarification; this example you still need to mixin |
@mhegazy yes... my specific use case would be to mirror run-time operations which are difficult/impossible to currently do in TypeScript. For example: class A {
foo: string = '';
}
class B {
foo() { return 'foo'; };
}
interface GenericClass<T> {
new (...args: any[]): T;
}
function mixin<T, U>(target: GenericClass<T>, source: GenericClass<U>): GenericClass<T&U> {
return;
}
const C = mixin(A, B);
const c = new C();
c.foo; // is of type string & (() => string) Clearly, the intent of my code was not to create a type of I am sure there are other use cases, where it is difficult/impossible to express the actual run-time types without some advanced design-time logic to mutate those types. |
Risking to derail this issue, but just wondering if you looked at the proposal in #6502 (comment), and what you think about it. |
No, I had missed it, as I mentioned there, I do think it is related to #3870 (which this is sort of in the same space as well), but I think the challenge comes in that sometimes we can (want/need/desire) to do things at runtime that require us to express some heavy design time logic. I see design time decorators too as a potential way of avoiding introducing some challenging language semantics. For example in #6502, the proposal is to place syntax that would have to both be erased but also add more to the emit, which could easily cause forward incompatibilities with ES. Property initialisers are likely to come to ES (YAY!) but what is the likelyhood that the proposed syntax in #6502 would be aligned? I would expect the TypeScript team to decline that just like #311. The beauty of design time decorators is that, as the name implies, they would be 100% erasable with almost no need to consider the emit (except when emitting meta data). |
Design time custom decorators/annotations just as in Java / C# would require the transpiler to examine external sources, which might already have been transpiled, as such these design time decorators/annotations will have already been removed from these sources. So in my personal understanding this is not an option unless one wants to go for bytecode and Java / C# like package constructs. And as far as my understanding of existing transpilers such as Babel and maybe also TypeScript goes, these only examine a single source file at a time and will rewrite that file, not considering any external files that might be imported, say using the import statement or, more hidden, the require() method. |
Not necessarily. There is a set of typing meta data that is already inferred by the time the decorator would be invoked. TypeScript makes assumptions about the structure of external files all the time, which is how it accomplishes the intellisense when integrated into IDEs such as VSCode, using a number of different methods (including ambient declarations). This would essentially allow code to interface with that information direction at design time. Most of that is already in TypeScript type structures, since TypeScript is compiled with TypeScript. |
@kitsonk you are talking about IDE integration here, which is an altogether different thing. As for decorators, please compare https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md to what is defined in here and you might see that it is actually the same. As for design time vs. compile time vs. runtime. These are very different things. And just because the typescript compiler is able to compile itself, does not necessarily mean that it is capable of loading external entities and make that part of the AST during compilation. |
@silkentrance @mhegazy is one of the core team working on TypeScript. I am pretty sure he understands the difference between design time and runtime decorators and how decorators work in TypeScript. Actually IDE integration and how TypeScript compiles and emits code aren't very different. TypeScript gathers a whole lot of information, including transforming to an AST with appropriate typing information to be able to offer up Intellisense or code emitting.
I am afraid it can and it does. |
@kitsonk so even if typescript was able to infer certain properties from an already transpiled source, it would have to annotate that source by certain means. looking at transpiled sources of typescript, I cannot find such annotations, though, it is still plain javascript. As such, VCode or whatever the name of the IDE was, requires the original sources by which it is able to infer such idioms of the TypeScript language. See the compiled source in the PLAYGROUND |
With plain JavaScript (or already compiled TypeScript) TypeScript can understand the structure of those files using ambient declarations via .d.ts files. This is how the DOM and the rest of the built in APIs are typed. There are other tools out there can can generate the appropriate information so people can continue to build on-top of the emitted code. I happen to maintain once such tool. In those cases, TypeScript doesn't even read the source files. As a side note, there is part of the TypeScript services called Salsa, which gives similar inferred typing information to plain JavaScript code being written in JavaScript. Those services can be enhanced with further data from .d.ts files as well and this is what is starting to power the intellisense in Microsoft Visual Studio Code (that "whatever" IDE I was referring to) when editing JavaScript files in that IDE. Also, just to point out, this particular issue is part of the TypeScript Roadmap for 2.0 (the Ambient Declarations). So I am pretty sure the TypeScript know what they are talking about. |
Considering ambient decorators further, there cannot be custom ambient decorators unless they are part of the same source file. As such, these need to be made part of the language. See wycats/javascript-decorators#27 |
@kitsonk you are talking about extraneous files generated by the compiler that are not part of any specification so far as I can see. So while this is a special case/behavior of the typescript compiler, it cannot be made a general feature as requested in wycats/javascript-decorators#27 |
Does this add anything that cannot be done with a jsdoc tag, like |
+1 for a decorator-based suppression mechanism. |
cc @chuckjaz |
Sorry I typed without thinking - we could generalize our solution as a
transformer. Angular (and Bazel) run typescript through our own CLI
program, not `tsc`, so we can add transformers.
You can add a transform at the beginning of the pipeline while the sources
are still TS is my understanding.
…On Thu, Apr 27, 2017 at 2:35 PM jods ***@***.***> wrote:
(things like angular @component are stripped before downleveling).
We should totally generalize that as a language service plugin.
Very interesting... It is my understanding that transforming the AST or
codegen is explicitly a non-goal of Language Service plugins, so how would
you do that?
I am very interested in being able to add/remove stuff from the generated
JS code, including when building with tsc, but that seems out of the
picture for now.
To the TS team: it's sad that we can't transform TS code, because
transforming the downlevel JS code is *much* harder after types have been
erased :(
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2900 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAC5I5cXGdHbCmIkOpF44kbREW9XEitCks5r0QoHgaJpZM4EHdy2>
.
|
Any update on this issue? |
I'm interested in implementing this (and have already started experimenting with the source code) but I'm not sure what the state of this issue is and if it's worth to open a pull request. I know I'm not supposed to open a pull request unless a issue's milestone is "Community" but in the roadmap it states "Investigate Ambient(, Deprecated, and Conditional) decorators" so I would like some clarification here. This is also mentioned in #20724 as "One day?". |
I see this issue being mentioned in the roadmap, so I hope this will get some attention soon. Just wanted to check, will design time decorators be able to define custom return types for a function? Example: #4881 (comment) |
With this feature, is it possible to write something like: @@expose({name: "sockFile", since: 2.0})
domainSocketFile: string It should be kept in |
I would also like to see this gain some traction. Here are a few use cases that we currently don't have a good solution for:
Part of the opposition to
|
My use case for this would be to annotate some of the public methods as "experimental" which means that may change in the future. class myClass {
@@experimental("This method is experimental and may change in the future versions")
doSomething() {
}
} |
I wrote an esbuild plugin today that kind of implements design-time decorators: https://github.com/jarred-sumner/decky. It uses the existing |
Now that decorators have advanced to Stage 3, I wonder if there's anything else to say about this? As I mentioned above, I'd like to see a way to get both runtime and design time effects out of a single A straw-person proposal (it's admittedly pretty ugly, but we have to start somewhere): when compiling |
TypeScript 5.0 no longer allows many decorators since switching to stage 3 decorators support. |
I feel that I have to say something. Since the stage 3 decorator change, TypeScript has removed decorator support for interface members. So instead of writing something like this:
} Some may say that it is a better way. It is more robust because I have 100% control. And young developers prefer that way. And it helps me with vendor lock-in. No one seems to lose anything. But from a purely technical point of view, it still sucks. I don't want to see a good invention just refuse to be something much bigger but choose to be a puppet of JavaScript instead. |
We've never supported decorators on ambient classes or interfaces at compile time. The only reason they existed within the AST was to report grammar errors. |
I am very well aware of that. But the fact is that it was in AST, and now it is gone. It worked perfectly for my purpose while it was on AST. The warning can be easily suppressed. I was just giving my personal opinion. I believe it was in the best interest of TypeScript as a general-purpose programming language with many unique features. |
Rather than focusing on a particular use case that wasn't well-supported (anywhere you need to suppress an error seems reasonably fair-game to be broken in the future), it's probably more constructive to think about how design-time decorators could be handled more generally. From my perspective, I see two broad possibilities:
For various reasons, I think (1) is a more promising alternative: first, because (as I've pointed out above) it's quite reasonable for a single decorator to have both runtime and design-time behavior, and second, because my impression is that the TypeScript team would be hesitant to move on (2) without upstream (TC39) consensus on JS syntax, and this seems unlikely given that it would effectively be a comment, and there doesn't seem to be much traction to adding syntax that doesn't actually affect runtime at all. I can imagine a couple ways that a decorator could be "marked" as design-time, which wouldn't require new JS syntax:
There are certainly other options, and I imagine we would do well to brainstorm it a bit. This is unlikely to gain any traction without a compelling proposal. But if there were a working use case by which decorating type-only elements (such as interfaces) would actually be meaningful (e.g. because it would affect the .d.ts emit), then it doesn't seem unreasonable that such behavior could be actually supported, rather than just a suppressed error. |
I can give a use case for decorating type-only elements (such as interfaces), which will show how much bigger TypeScript can go beyond JavaScript. Let's use the code I posted earlier as an example. The code will be transpiled into Lua instead of JavaScript. The interfaces are just declarations of data structures, which will be arguments of a main() entry function. What it does is that it allows the automatic generation of a Tree UI on any process, which I call "Apps." Those apps are not phone Apps because they don't run on phones. They are IoT Apps. That particular code defines the input structure (configuration) of a "Smart Sprinkler." It looks like this on an end-user's smartphone. It's up to the end-user to create an instance of the interface data (tree data) and use the data to create the actual process. So what does the decorator do? It provides additional information to optimize the generated Tree UI. Of course, another developer can write a more "traditional" sprinkler app with a strict time scheduler. I am also working on the App engine to power the tiny little wireless MCU chips. A chip with 256KB of RAM can run 10000 lines of TypeScript code. Yes, it's 256KB, several 100,000 times less RAM than your smartphone. TypeScript can be truly "write once, run everywhere" on trillions of devices! |
The only workaround I think of since we don't have this feature yet is... Attaching a related meta-data file to another .ts fileFor example, say you've a transformer that compiles TypeScript to WebAssembly and want to export a TypeScript function named
export const Q = {
fooFn() { return 10 }
}
{
"Q.fooFn": {"exportAs": "q_foo_fn"}
} I don't know how symbols work in the TypeScript Compiler API yet, so I don't know how resolve |
Now that decorators are supported in TypeScript (#2249), consider adding support for ambient/design-time-only decorators:
Ambient decorators can be an extensible way to declare properties on or associate special behavior to declarations; design time tools can leverage these associations to produce errors or produce documentation. For example:
Use cases:
Proposal
Design-time (Ambient) decorators are:
Application
The text was updated successfully, but these errors were encountered: