rune
rune copied to clipboard
feature: support user-defined attributes
Support rust style attributes;
#[hello]
fn test() {
}
#[hello]
struct Foo {
}
This will be invoked as macros with the ast of the thing that it annotates as input, and anything produced by the macro will be appended to the source (to work the same as Rust procedural macros).
- [x] Implement parsing (#83).
- [ ] Add compiler support to process attributes.
Implementation note: If you need inspiration for how to structure the AST, look into Attribute in the syn
project.
FYI, this is one of the main things I dislike in Rust's syntax because of the typing overhead and the fact that pretty much any tokens are valid in #[here]
. Using @Foo(param=foo)
(or #[Foo(param=foo)]
if you like typing braces) and supporting exactly this style of calling a macro would reduce complexity quite a bit.
FYI, this is one of the main things I dislike in Rust's syntax because of the typing overhead and the fact that pretty much any tokens are valid in
#[here]
. Using@Foo(param=foo)
(or#[Foo(param=foo)]
if you like typing braces) and supporting exactly this style of calling a macro would reduce complexity quite a bit.
I appreciate the point of view. But this is another instance when I want to maintain feature parity with Rust to make it possible to more easily transfer ideas. A note is that attributes are pretty much macro calls with different call syntax (and are associated with the AST they are tacked on to). You can see this in syn
, with the Macro
(macro call) and Attribute
. They both take a Path
and a raw TokenStream
.
syn
also provides a convenience function to parse its content as meta items. Something like this can be done to unobtrusively enforce more opinion. Or something like darling
can be ported to Rune.
Might look into this in the next few days, what should be the input of a function handling a macro? everything after the path? or should it only be the "input" as it is in rust: https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros i.e. if you call #[attribute(some_input)] the macro implementation would only get some_input
and not (some_input)
.
Doing it in the same way as Rust sounds good to me.
this would mean, that a macro could not detect whether they got #[path(...)]
, #[path[...]]
, #[path={...}
or #[path = ...]
. Though I guess that should be fine, as it would be strange to have a different behavior depending on the exact syntax used to invoke (similar to how normal macros also don't differentiate`.
I think so, all though I must admit I'm not sure what happens with the #[path = ..]
variant 😅. I'm guessing ..
is what's being fed in? The =
is mentioned in the reference as part of meta syntax. Or is it not supported for proc-macro's?
IIRC it is not supported for the actual macro, only for it's helper macros.
I guess this information could also just be added to the AttributeMacroContext
.
I.e. outer vs inner, what kind of delimiter was used etc., though I could also see an argument be made that this is just preference of the macro user, and the macro dev shouldn't try to enforce anything.
AFAICT we need to have a bunch of seperate types for the attribute macros anyway, as they e.g. in the AttributeMacroContext
would need to have input_span
and item_span
instead of only stream_span
.
As we have builtin ast which is different from rust's proc-macros, we could also have the attribute macro input be something like syn's https://docs.rs/syn/latest/syn/enum.Meta.html instead of a plain token_stream.
As we have builtin ast which is different from rust's proc-macros, we could also have the attribute macro input be something like syn's https://docs.rs/syn/latest/syn/enum.Meta.html instead of a plain token_stream.
I'd be a bit careful with that. Even in Rust a meta item is only defined to the extent that it is used as a convention for most built-in macros. I frequently include things which is not valid meta
type syntax in macros (types, patterns, bounds, ...). If a macro wants the meta syntax, they can opt to parse it out and there's really no difference in diagnostics.
If I'm being honest, I'd probably prefer specialized AST constructs derived in many cases instead of using generic containers such as syn::Meta
(which you'd need to decompose / parse further anyway). Many attribute parsing needs can then be done by derives, like:
// Parses `= "<string>"` in an a `#[path]` attribute.
#[derive(Parse, ToTokens, Spanned)]
pub struct PathValue(T![=], LitStr);
The benefit here is that instead of having to match out syn::Meta::NameValue
and match out its expression to a literal string or raise an error, that happens during the first round of parsing instead.
I'd be a bit careful with that. Even in Rust a meta item is only defined to the extent that it is used as a convention for most built-in macros. I frequently include things which is not valid
meta
type syntax in macros (types, patterns, bounds, ...). If a macro wants the meta syntax, they can opt to parse it out and there's really no difference in diagnostics.
Actually in syn2, the MetaList variant contains unparsed tokenstreams, so it would only enforce that the top level macro matches meta which (at least in rust) is enforced by the syntax itself, but I agree that having the derived Parse is quite nice, and you would lose that when preparsed in some way.
Another place where this question arises is with the item, as an attribute macro can be applied only to valid items (and attributes themselves also to modules, statements and fields/params) the item could also be forwarded in it's parsed form, though the same argument could be applied here, that one could want to call ItemFn::parse directly instead of matching on the Item.
One future question is, should macros be able to have helper attributes, like rust derive macros have? I.e. attributes that will be ignored when not in scope instead of raising an error.
In rust, only derive macros can have them, attribute macros need to clean them up themselves.
Derive macros should definitely declare and have their helper attributes cleaned up by the compiler. The reason there would be that other than removing those attributes the original item remains untouched. Anything added is added as separate items after it. It can safely be punted on, since I don't think there's any particular infrastructure overlap in the compiler between the two, but I might be wrong! Unless you want to implement derives as well.
For attribute macros, do what Rust does basically 😄 (unless there's a really good reason). Which I think amounts to "follow a top to bottom expansion order, unless nested macros are present". But no helper attributes.