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
Update 26-04-10: Added the sequence diagram for extension and deriver.
Summary
I'd like to propose a native macro system for ReScript.
The goal is to provide a ReScript-native replacement path for custom extension and deriver workflows that currently rely on PPX-style rewriting. Instead of writing transforms in OCaml against the legacy PPX surface, macro authors would write handlers in ReScript and work against a structured public AST.
The proposed user-facing forms are:
expression extensions: %ns.name(...)
structure/signature extensions: %%ns.name(...)
native derivers on type declarations: @ns.name and @ns.name(...)
Motivation
ReScript currently has extensibility through existing PPX-compatible infrastructure, but the authoring model is still inherited from the OCaml world.
That has a few drawbacks for ReScript users:
custom transforms are not written in ReScript
the public surface is not designed around ReScript ergonomics
extension/deriver authoring feels disconnected from the rest of the toolchain
it is harder to build a ReScript-first ecosystem of compile-time transforms
A native macro system would let us design this around ReScript itself.
This is meant to be the preferred model for new ReScript-authored transforms.
In other words:
macro authors write ReScript, not OCaml
the public API is designed for ReScript
extension points and derivers use ReScript-native syntax
this reduces the need to introduce new PPX-style rewriting layers for ReScript-specific use cases
Execution Model
A reasonable implementation model is:
compile macro sources to JavaScript
run them in Node.js during compilation
serialize payloads/type declarations across the process boundary
expose a structured AST API to macro authors
decode the result back into compiler AST and continue compilation
The transport format can be JSON, while the public API remains structured AST.
Initial Scope
A first version does not need the full compiler AST.
A useful subset would already cover many cases:
payloads
PStr
PSig
PTyp
PPat
expressions
Pexp_ident
Pexp_constant
Pexp_apply
Pexp_fun
Pexp_construct
Pexp_let
Pexp_sequence
Pexp_record
Pexp_field
patterns
Ppat_any
Ppat_var
Ppat_record
structure/signature items
Pstr_eval
Pstr_value
Pstr_type
Psig_value
Psig_type
core types
Ptyp_constr
Ptyp_arrow
Ptyp_tuple
Unsupported nodes can fail explicitly at the macro call site.
Sequence Diagrams
Extension
sequenceDiagram
participant Src as User source
participant Parser as Parser
participant NM as Native_macro
participant Node as Node runner
participant Macro as expand
Src->>Parser: Parse source module
Parser->>NM: Encounter expression extension
NM->>NM: Resolve macro registration
NM->>Node: request JSON
Node->>Macro: Call expand handler
Macro-->>Node: Return public AST result
Node-->>NM: response JSON
NM->>NM: Decode to compiler AST
NM-->>Parser: Replace original expression
Loading
Deriver
sequenceDiagram
participant Src as User source
participant Parser as Parser
participant NM as Native_macro
participant Node as Node runner
participant Macro as registered deriver
Src->>Parser: Parse source module
Parser->>NM: Encounter type declaration with deriver
NM->>NM: Resolve deriver registration
NM->>NM: Preserve original type and strip macro attrs
NM->>Node: request JSON
Node->>Macro: Call registered deriver handler
Macro-->>Node: Return generated structure and signature items
Node-->>NM: response JSON
NM->>NM: Decode to compiler AST
NM-->>Parser: Append generated items after original type group
The current PoC only covers project-local macros under macros/.
A future design step should define how macros can be published by npm packages and consumed through normal package dependencies. In practice, that means designing the interface for:
exposing macro entry points from a package
resolving macro namespaces from dependencies
using dependency-provided macros from an application project
I think this is an important missing piece for a complete macro design, even though it is intentionally out of scope for the current prototype.
Update 26-04-10: Added the sequence diagram for extension and deriver.
Summary
I'd like to propose a native macro system for ReScript.
The goal is to provide a ReScript-native replacement path for custom extension and deriver workflows that currently rely on PPX-style rewriting. Instead of writing transforms in OCaml against the legacy PPX surface, macro authors would write handlers in ReScript and work against a structured public AST.
The proposed user-facing forms are:
%ns.name(...)%%ns.name(...)@ns.nameand@ns.name(...)Motivation
ReScript currently has extensibility through existing PPX-compatible infrastructure, but the authoring model is still inherited from the OCaml world.
That has a few drawbacks for ReScript users:
A native macro system would let us design this around ReScript itself.
Proposal
1. Project-level macro source set
Enable macros from
rescript.json:{ "name": "@foo/a", "sources": { "dir": "src", "subdirs": true }, "dependencies": ["ppxlib_res"], "macros": { "dir": "macros", "namespace": "foo.a", "node": "node" } }Proposed fields:
If namespace is omitted, it can default from the package name.
2. Macro registration in ReScript
Macro handlers live under
macros/and are registered with attributes on top-level let bindings:3. ReScript-native call-site syntax
Example usage:
4. Structured public AST
Instead of a string-in/string-out protocol, macros should work against a structured public AST API, for example:
Example extension API:
Example deriver API:
Suggested handler shapes:
Intended Direction
This is meant to be the preferred model for new ReScript-authored transforms.
In other words:
Execution Model
A reasonable implementation model is:
Initial Scope
A first version does not need the full compiler AST.
A useful subset would already cover many cases:
payloads
expressions
patterns
structure/signature items
core types
Unsupported nodes can fail explicitly at the macro call site.
Sequence Diagrams
Extension
Deriver
Example Outcomes
A simple extension could turn:
into something like:
A simple deriver could turn:
into generated bindings such as:
while preserving the original type declaration.
PoC
There is already a working reference implementation and design write-up in my fork:
Follow-up Design Questions
The current PoC only covers project-local macros under
macros/.A future design step should define how macros can be published by npm packages and consumed through normal package dependencies. In practice, that means designing the interface for:
I think this is an important missing piece for a complete macro design, even though it is intentionally out of scope for the current prototype.