Introducing pmutil
swc
uses proc macros extensively. I use a procedural macro if possible to reduce boilerplates and to avoid rewriting boilerplates when something is changed. Behind the scene, there's a hero crate called pmutil. It provides useful stuffs for proc macros.
#
Features#
smart_quote!It's a rustfmt-friendly quasi quotter. It exploits the fact that rustfmt
can format function-call-like macros.
It takes two arguments. The first argument is to list variables, and should be wrapped with Vars { }
. You can think this as constructing a struct named Vars
. It's really the same, as shorthand notation is supported. It means, you can pass
Currently, Vars{}
is required even if no variable is passed in. This restriction may be removed in the future.
The second argument is a template, wrapped with a group (()
, []
, {}
). In most cases, wrapping with {
and }
is best.
where the method is ident named len
, i is an arbitrary value (we use 10
at here) and TypeName is the name of DeriveInput
(we use Enum
at here), expands to
As rustfmt is happy with
- function-call-like macro invokation
- a block containing multiple items
you can do something like without breaking rustfmt.
#
Naming conventionI recommend to follow the naming convention of template argument. It means, identifier of a type should be passed in PascalCase although it is passed as a field initialization.
#
Quote.parse() and error reportingYou might think quasi quoting is enough and parsing quoted tokens to ast type is just waisting compile time. It's true if your project is small. But debugging proc macro is hard. I made 10+ proc macros and you should trust me. It's really hard.
Let's start with a simple wrong code.
As you generated a wrong token (impl block is not an expression), Quote.parse()
will panic, reports the line of smart_quote!
invocations and tell you to rerun after setting an environment variable named DBG_DUMP
to 1. When you run with it, Quote.parse()
will print the tokens you quoted.
Yes, I broke code to show the message. With DBG_DUMP=1
,
You can see the code, which is clearly not an expression, and fix it.
#
ToTokensExtIf you have an experience of writing a proc macro, you may know proc_macro::TokenStream
and proc_macro2::TokenStream
interop badly, as name is equal to each other. ToTokensExt
helps you.
It provides a .dump()
method, which returns proc_macro2::TokenStream
without bringing the name into scope.
The method is also useful for debugging as proc_macro2::TokenStream
implements Display while ast types from syn do not implement it.
#
Macros built with pmutilLots of macros are built over pmutil.
If you want a macro below to be a standalone crate, please ping me on slack (slackin for swc) or file an issue at github.
(This is one of the reasons why I'm writing this section)
#
FromVariant (swc)It uses pmutil like
where VariantType
is replaced with the type of the only field of the variant, Variant
is replaced with the name of the variant, and Type is replaced with the name of the enum itself.
#
ast_node (swc)- doc:
#[ast_node]
Alias for #[derive(Spanned, Fold, Clone, Debug, PartialEq)]
for a struct and #[derive(Spanned, Fold, Clone, Debug, PartialEq, FromVariant)]
for an enum.
#
Fold, Visit (swc)- doc:
#[derive(Fold)]
With specialization, which requires nightly, it becomes an automagical weapon.
You can collect all identifiers in ast node with 20 lines of code however it is stored (unless you are using #[fold(ignore)]
).
#
Spanned (swc)- doc:
#[derive(Spanned)]
#
enum_kind (swc)- doc:
#[derive(Kind)]
#
string_enum (swc)#
swc_ecma_parser_macros (swc)- doc:
#[parser]
I'll show you the code and then tell how it works.
Did you get it? As Input
is stored in Parser
, you should pass an instance of Parser
into macro, so code to consume a token should be
But #[parser]
injects self to the code automatically. It knows the list of macros that accept Parser
as a first argument and inject self into the macro call.
#
swc_ecma_codegen_macros (swc)- doc:
#[emitter]
becomes
Thus emit!
works for any emittable types. emit!
is defined as
As the macro creates an impl block from a method, it reduced the amount of code drastically. (Writing impl Node for T every time is cumbersome.)
#
is-macro- Repository: https://github.com/kdy1/is-macro
is-macro
is a proc macro to generate methods for enum to allow using it like Option
/ Result
.
#
st-map- Repository: https://github.com/kdy1/rust-static-map
This macro adds .iter()
, .iter_mut()
, .into_iter()
, .map(|k, v| {})
, .map_values(|v| {})
to a map-like structs.
Map-like means that all fields have the same type.