Bring back item-like macros

Manually forked from

This is about potentially allowing item-like procedural macros.

Option A:

The macro_rules! style of item-like macro, roughly $path ! $ident $block.

  • Probably want to add visibility somewhere.
  • Limited to just one identifier of information outside the block.
  • Other unknown unknowns?

Option B:

Allow a "macro keyword" as the first keyword in the decl header of an item (fn decl, struct decl, etc). If the "macro keyword" is present, any bracketed blocks of the item (args list of fn, body of struct, etc) are not constrained to be syntactically correct for that item, just to be lexically correct (~create a TokenStream with matched brackets).

  • Much more complicated, and splits every item grammar into two copies.
  • Limits item like macros to truly item like (Rust item like) rather than a more arbitrary item like design.
  • Other unknown unknowns?

Option C: the heavy hammer

A macro parses roughly like the following algorithm:

  • If immediately followed by a tree (matched brackets), functionlike macro that takes just that tree;
  • Otherwise, take all input until the first {} tree or (unnested) ;.

This has the most significant drawbacks as well for being so sillily powerful:

  • Basically ruins error messages with functionlike macros.
  • When all you have is a hammer, ...
  • Other unknown unknowns?
2 Likes

As I suggested in original topic my option is B:

#[proc_macro_keyword]
pub fn py_object(attr: TokenStream, item: TokenStream) -> impl Iterator {
    // ...
}

py_object! MyPyObject {
    item = 2
    def send_report():
        pass
}

It will allow developers to create any feature (even domain specific) in language without waiting the update of compiler

Does it have to be limited to a single $ident or known $items?

How about some_macro! any number of tokens here until {block}. Similar to grammar of CSS selectors + declarations: keep consuming tokens (with paired parens) until a block start, and then consume the block.

For example, it'd be super cool if this syntax worked:

for! item in streaming_iterator {…}

edit: oh, it's ambiguous with for! (x,y) in streaming_iterator {…}. So maybe:

macro! for (x,y) in streaming_iterator {…}

where macro! $ident selects the macro, and then feeds the rest of the construct to the macro.

4 Likes

I'm personally glad that macros are delimited by {}/[]/(). It makes reading and mentally parsing code way easier.

Sorry for not having a more substantial comment here. I'd just like to make sure this discussion includes the added complexity of reading this code.

8 Likes

This could be very confusing with variadic macros:

println! "Hello";
println!("Hello, {}", name);
1 Like

That's in the OP as Option C.

I think it's an interesting concept, but it's problematic for one major reason:

It means that if I typo a macro call, the error handling for it is basically impossible due to how different parsing is based on whether I have a !. That said,

This seems like a very practical idea for allowing "macro keywords" that take a less bounded token stream. (Though the best you'd get is macro! r#for since for is a keyword.)

I still am quite wary to fully back this solution, though, primarily because of how heavy handed it is.

Perhaps we could extend Option B from just items to any "block like statement" (that is, an expression that terminates with a block and thus doesn't need a semicolon) as well as items, to cover the stream loop case.


That looks like Option A to me. The grammar fits $path ! $ident $block and there is no $item in Rust currently that doesn't start with a keyword.