-
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
Feature Request: Macros #4892
Comments
duplicate of #3136? |
@mhegazy I don't think so. Support for macros as AST->AST functions would let us do anything you could do with a type provider (just return an |
+1 |
1 similar comment
+1 |
+1 (I almost expected it to work with sweet.js.) |
+1 |
1 similar comment
+1 |
You can also look at the haxe macro system how they implemented it. http://haxe.org/manual/macro.html |
+1 |
+1 👍 |
Question Possible Benefits
Of course, I could be missing some fundamental concept of macros and compilers, rather than just attempting to break up the traditional view. |
+1 |
+1 |
I need this. My needed use-case right now would be to make sort-of an "inline-function", or similar to a C-preprocessor-like parametric macro. That code would be inlined to avoid the function call on the JavaScript output. C Macro Example: #define ADD(x, y) ((x) + (y)) C inline Example: inline int ADD(int x, int y) {
return x + y;
} I'd write something similar in TypeScript (let's assume the keyword inline will work only with functions that can be inlined). In TypeScript the inline function ADD(x: number, y: number) {
return x + y;
} UPDATE: Looks like a similar issue was already here #661 |
I would enjoy some form of macros for sure. As great as typescript is, it's still crappy old JS underneath. |
👍 |
migrated from #11536 (which was closed in favor of this one) situation:
problem: currently my options are
solution:
|
This would be huge. In something like Scala, macros are a way for the community to implement and test out new language features that are not yet (or will never be) supported by the core language. After adding macro support, TS would have a large laboratory of potential features to draw on when implementing new ones, and could gauge support and feasibility of a feature before implementing it. Features like pattern matching could first be implemented as macros, and then either moved into a standard macro lib, or into TS core if they are broadly useful and popular. This takes a burden off TS maintainers and authors, and gives the community freedom to experiment without forking the TS compiler. |
FWIW, I think that a more promising direction is for a macro facility to accommodate TS. The obvious example would be to extend sweet.js so it accepts the TS syntax, and expands into TS code. This way, TS doesn't need to know about macros at all. This leads to something very similar to Typed Racket (for anyone who knows that), including the minor disadvantage of not being able to write macros that depend on types. |
@elibarzilay With that approach, would macros be typesafe? If the whole point of TS is to be a typesafe layer on top of JS, macros should ideally also be typesafe. Again comparing to Scala macros, their safety is a huge win. Otherwise you end up shooting in the dark without IDE/compiler support until you get something that compiles. |
@bcherny: The macro code itself wouldn't be typed. But that's minor IMO (since at that level it's all ASTs in and out). (Compared to random scala macros that I've seen after a few seconds of grepping the web, you get only The code that macros produce might not be well typed, but it still goes through the type checker which does verify that the result is safe. |
I think this is something similar to c/c++ perprocessor maybe, with type check? But i really want to write something like that: #IfFlagSet("DEVELOPMENT") {
macro assert(cond: any, message?: string) {
if (!cond) { throw new Error("...") }
}
} else {
macro assert(...x: any[]) // or something similar, and in this case dont emit code for this macro call
} |
(Similar, but a proper macro system compared to CPP is like comparing JS to machine code...) |
+1 |
Hygienic macro. https://en.wikipedia.org/wiki/Hygienic_macro |
@19h If you are willing to open source said plugin I would be very interested to use it. |
if there were macro support, writing log with filename will be so much easier |
@Sytten @fullofcaffeine I'm afraid I can't share that code -- but @GoogleFeud just pushed a new version of ts-macros that implements exactly that! |
If we examine the use case within TypeScript to support compile time code unpacking/generation through source defined macros, I believe we will find a compelling case to add support. TypeScript has a macroFirst off, TypeScript already has one built in marco and that is its first class support for JSX "syntax sugar" // The JSX
const App = () => <div>Hello World</div>
// unpacks into
const App = () => React.createElement('div', {}, ['Hello World']) Obviously this is extremely useful but it caters very specifically to React use cases, something that's not exactly a TypeScript concern. Looking at other examples, Rust and YewLooking at shared use cases within Rust and how Rust macros support those use cases can inform us of what macros might do for TypeScript and its users. Take the Rust based, React inspired wasm framework Yew. Rust has no idea about JSX however the language implementation of macros allows the framework developer to add support for JSX by supplying its customers a macro. See this example. Below you see a Yew component #[function_component]
fn App() -> Yew.Html {
let hello_world = use_state(|| "Hello World");
// hmtl!{} is a custom macro used to add custom syntax to Rust
// Rust uses foo!{} rather than foo() to define a function for the compiler and not runtime
return Yew.html!{<div>{ hello_world }</div>}
} Giving framework developers an API which can be used to transform custom syntax at compile time by the native compiler while also being syntactically valid for the language server ensures the language itself is never burderend by the use cases of its consumers while also offering the ability for novel framework approaches to be experimented with at minimal cost. To translate Rust macros into TypeScript JSX, we might see a syntax like the following which is maintained by the React team externally to the TypeScript project: // The JSX
const App = () => React.jsx@(<div>Hello World</div>)
// unpacks into
const App = () => React.createElement('div', {}, ['Hello World']) Who else could take advantage of compile time macros?If we pull on this thread a bit more, we can ask the question - what other frameworks might want to use compile time macros to offer support? While React enjoys first class TypeScript support; frameworks like Angular, Vue and Svelte must use custom file types and dedicated compilers to offer support for their syntax. Perhaps introducing compile time source transformation would help framework developers to build more within TypeScript as macros - allowing for better runtime performance, easier framework development and better support for new TypeScript releases. Hypothetical Web FrameworkFor example, let's devise a hypothetical reactive on mutation framework that compiles to W3C custom elements. It uses compile time macros to take a class and expand it into a custom element where properties are statically defined as getters and setters. import { component, reactive } from 'my-web-framework'
component@({
tag: 'app-foo'
html: '<div>{bar}</div>'
})
class Foo {
reactive@
bar: string
} Transforms at compile time to something like class Foo extends HTMLElement {
#bar: string
set bar(value: string) {
this.#bar= value
this.render()
}
get bar(): string {
return this.#bar
}
constructor() {
super()
}
render() {
this.innerHTML = '<div>' + this.#bar + '</div>'
}
}
window.customElement.define('app-foo', Foo) note: I don't know what syntax would make sense for macros within TS, but I am just writing some pseudo code to help illustrate the cases which could benefit |
@alshdavid your proposal is very interesting, I will try to fork tsc-macro or use it as base to implement your syntax 💪 Hopefully, the official typescript will adopt similar syntax over time |
I think I pretty much solved it, and also solved @19h's issue of repeatedly checking result https://github.com/hazae41/result Macro support would still make this even less verbose and won't erase the error type to make it even safer |
For those looking for AST based code manipulation with typed API, ts-morph is a useful package, I recently re-implement a regex-based transformer from it, the DX is great. The transformer I worked on scan nestjs controller, and generate angular service acting as http client. The functionalities I got from ts-morph are parsing source code, rename some import specifiers, remove some named imports, add decorator on class, change constructor and controller methods implementation. |
I'm very surprised there's been little activity on this for a whole year. What's needed to make this happen? An example implementation? |
@ariccio
While we have macros in bun I do not find a macro system in any ECMAScript proposal.
Go ahead and start an ECMAScript proposal? :-) |
Bun "macros" are not functions that can deeply modify the code but just regular JS functions evaluated at compile-time (they must output JS values) What most people need is a function that can be called anywhere, and that will be output some arbitrary code (classes, functions, React components, code blocks) For example, instead of creating a high-order-class, a macro would generate all the classes you need |
I have built something that can solve many use cases, without using AST https://github.com/hazae41/saumon Macros are like normal functions, but the preprocessor will replace their call by the string value they return function $log$(x: string) {
return `console.log("${x}")`
}
$log$("hello world")
function $random$(): number {
return `${Math.random()}` as any
}
const x = $random$() * 100
function $fetch$<T>(url: string): T {
return (async () => {
const response = await fetch(url)
const object = await response.json()
return JSON.stringify(object)
})() as any
}
export const data = $fetch$<{ id: number }>("https://dummyjson.com/products/1")
function $log$(x: string) {
return `log() {
console.log("${x}")
}`
}
class Something {
/**
* @macro
* $log$("hello world")
*/
} |
This comment is correct; macros are definitely not in scope with our current definition of what we would/would not do in TypeScript. |
Is this something that could be implemented by the community as an extension to tsc and the TypeScript language server? More generally, does the TypeScript compiler and TypeScript language server have the ability to be "exteneded" to include custom syntax - like the inclusion of compile time macros? |
No |
Why no? What about typescript tranformers? ttypescript as example |
An extension like what's described here would require extending the compiler codebase itself. ttypescript and ts-patch allow you to add transformers to the compilation process, but they don't allow you to transform the compiler — not yet, anyway. |
I was asked a factual question and answered it 🤷♂️ |
So then sorry. It seems I have misunderstood the question) Anyway, my though is that macroses potentially can be implemented via transformers api 🤔 |
One thought I had was to implement Rust style macros as an swc plugin expressed as magic comments. Consider the following examples:
Another case is proc macros (not a real case but as an example)
Both can be described with magic-comment style notation however the content within the macro function parameters would be considered normal JavaScript and custom syntax (like jsx) would break type checking and the procedural macro would not have types const value = react/**!*/(<div>Hi</div>) /** #[derive(Default)] */
type Foo = {
bar: string
} Without type checking, the feature is DOA. This is sad because it's a very valuable and flexible way to expand the language without putting the burden on the TypeScript team to maintain e.g. React JSX should be maintained by the React team, styled components could be a macro, Angular decorators could probably be a custom procedural macro and frameworks like Svelte and Vue that use custom file extensions would have a better alternative for expressing their templating syntax After working in Rust for the last 2 years, I am craving macros in TypeScript. We already have a pseudo macro - jsx. So close yet so far haha |
I'm sorry for reopening this but I really feel it would make a big difference to how TypeScript is used |
I mean, let's be real, as you mentioned above, if even rust has macros it's goofy that typescript doesn't. I might expect it to be a reasonable fight-on-this-hill for a high end very high level proprietary functional language like the Wolfram/mathematica language, but typescript isn't at that extreme level of first class metaprogramming just yet |
This one might be waaaaaay out of scope, but I think it is worth proposing. The idea here is to add support for macros, functions that run at compile time, taking one or more AST nodes and returning an AST node or array of AST nodes. Examples/use cases: (Syntax off the top of my head, open to better ideas)
Interface-Based Validation
Type Providers
Conditional Compilation
Notes
tsc
on unknown code as dangerous as running unknown code. It might be good to require a--unsafeAllowMacros
argument, not settable from atsconfig.json
.macro
keyword would probably compile to a function, followed by ats.registerMacro(function, argumentTypes, returnType
call.kind
property special treatment inmacros.ts
files.#Foo(interface Bar{})
is valid syntax, as long as there is a macro namedFoo
that takes an interface.1 + 1
, but decorating Interfaces, interface items, functions, etc. should be fine.The text was updated successfully, but these errors were encountered: