Skip to content

Add support for repetition to proc_macro::quote #140238

Description

@tgross35

Part 1: complete in #141608

I'm opening a standalone issue to have something with "help wanted" labels in case anyone is interested in picking this up.

Our proc_macro::quote does not support repetition, unlike quote from the quote crate. As mentioned many times on the tracking issue, this is something we should support or at least account for before proc_macro::quote can be stabilized.

This should use the syntax:

proc_macro::quote! {
    $( CONTENTS )SEP*
}

Where CONTENTS is the thing to be repeated and SEP is an optional single-character separator. Expansion should work for anything that implements IntoIterator. This matches the quote crate's logic (except quote::quote uses # rather than $).

It's probably easiest to just copy quote's logic here, which uses an extension trait to facilitate this.

Part 2: incomplete

From @dtolnay's comment at #141608 (comment), there are some more minor issues to be resolved before stabilization:

#![feature(proc_macro_quote)]

use proc_macro::TokenStream;

macro_rules! decl {
    ($($iter:tt)*) => {
        stringify!($($iter) << *)
    };
}

#[proc_macro]
pub fn repro(input: TokenStream) -> TokenStream {
    // macro_rules macro
    let tokens = decl!(a b c);
    eprintln!("{}", tokens);

    // quote crate
    let input2 = proc_macro2::TokenStream::from(input.clone());
    let iter2 = input2.into_iter();
    let tokens = quote::quote!(#(#iter2) << *);
    eprintln!("{}", tokens);

    // libproc_macro
    let iter = input.into_iter();
    let tokens = proc_macro::quote!($($iter) << *);
    eprintln!("{}", tokens);

    TokenStream::new()
}

Macro_rules macro: a << b << c
Quote crate: a << b << c
Libproc_macro:

error: proc macro panicked
  --> src/lib.rs:25:18
   |
25 |     let tokens = proc_macro::quote!($($iter) << *);
   |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: message: `$(...)` must be followed by `*` in `quote!`

Another example:

error[E0425]: cannot find value `j` in this scope
  --> src/lib.rs:25:47
   |
25 |     let tokens = proc_macro::quote!($$ j $($$ j $iter)*);
   |                                               ^ not found in this scope

The parsing logic will need some more scrutiny in followup PRs before the macro can be stabilized.

Activity

  1. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Apr 24, 2025
  2. added
    C-feature-requestCategory: A feature request, i.e: not implemented / a PR.
    T-libsRelevant to the library team, which will review and decide on the PR/issue.
    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
    WG-macrosWorking group: Macros
    and removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Apr 24, 2025
  3. tgross35 commented on Apr 24, 2025

    @tgross35
    MemberAuthor

    Cc @dtolnay in case you have any notes here

  4. added
    E-help-wantedCall for participation: Help is requested to fix this issue.
    E-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.
    on Apr 24, 2025
  5. tgross35 commented on Apr 24, 2025

    @tgross35
    MemberAuthor

    Also fyi @SpriteOvO if you might be interested in this, since you have done a lot of other work with quote!

  6. dtolnay commented on Apr 24, 2025

    @dtolnay
    Member

    I recommend copying from the quote crate. It is tricky to get something that has all the behaviors that people expect are "obvious". If you see $($thing)*,—

    • You cannot turn it into for x in $thing {...} because this is incompatible with $($thing)* $($thing)* where thing is something non-copyable like a Vec, a ubiquitous use case.

    • You cannot turn it into for x in &$thing {...} because this is incompatible with thing being an Iterator, a ubiquitous use case.

    • You cannot turn it into for x in &mut $thing {...} because this requires let mut thing in the caller's code which is highly unexpected.

    • On top of all the above, in $($thing1 $thing2)* the correct behavior depends on which one of these is an iterator/collection and which one is a single token (Ident) or TokenStream. For a TokenStream, even though TokenStream has an IntoIterator impl, using that here produces an extremely unexpected result.

  7. moatom commented on May 15, 2025

    @moatom
    Contributor

    Hi there!

    Excuse me for interrupting, but I’d like to try working on this PR when I have time — as long as it’s not an urgent task. (I’m not very familiar with Rust macros, but I have solid experience with meta-programming in OCaml and Scala, so I believe I can handle it given enough time.)

    The main task seems to be porting the logic from declarative macros to procedural macros:
    https://github.com/dtolnay/quote/blob/ab1e92c27a492e6077e203a6f85496bb4b1522e8/src/lib.rs#L888-L937

    The most challenging part for me is likely the parsing.
    In the declarative macro logic, parsing is done using a sliding window with 3-token(-tree) lookahead and lookback. To replicate this in procedural macros, I'll need to implement the logic in a stateful way.
    However, I don’t have much experience with hand-written stateful parsers, so it may take me some time. (More importantly, I’ll first need to fully understand the overall BNF.)

  8. ora-0 commented on May 26, 2025

    @ora-0

    @moatom I don't see why you have to replicate the parsing logic of the crate. I'm sure it won't interfere with the quoting behavior. The code for the quote crate I feel is unnecessarily complex by virtue of it being a declarative macro, with one of the comments also saying that it is written that way to avoid using a muncher. I think recursive descent (or a simpler method) would suffice.

    As for the grammar, I think I have got it here, though I'm not 100% sure I got everything correct. It is quite simple:

    <content> ::= <tokentree> | <tokentree> <quote> <content> | ε
    <quote> ::= "$" <ident> | "$" <repetition>
    <repetition> ::= "(" <content> ")" <quantifier>
    <quantifier> ::= "*" | <punct> "*"
  9. tgross35 commented on Jun 16, 2025

    @tgross35
    MemberAuthor

    Most of the logic here is landing in #141608. There are a few follow up items David mentioned that I added to the top post.

  10. added 2 commits that reference this issue on Jun 17, 2025
  11. added a commit that references this issue on Jun 18, 2025
  12. added a commit that references this issue on Jul 3, 2025
  13. moatom commented on Oct 21, 2025

    @moatom
    Contributor

    @rustbot claim

  14. moatom commented on Oct 21, 2025

    @moatom
    Contributor

    @tgross35 @dtolnay Hi!, May I ask about the first error:

    25 |     let tokens = proc_macro::quote!($($iter) << *);
       |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

    I'm considering preparing a test that checks all the operators defined in compiler/rustc_ast/src/ast.rs (specifically BinOpKind, AssignOpKind, and UnOp, as far as I understand).
    Would that be sufficient?

    [Need investigation]
    It may not be an error related to operator support in minimal_quote_ts, but rather an issue with parsing this part:

    let sep_opt: Option<Punct> = match (iter.next(), iter.peek()) {
                            (Some(TokenTree::Punct(sep)), Some(TokenTree::Punct(star)))
                                if sep.spacing() == Spacing::Joint && star.as_char() == '*' =>
                            {
                                iter.next();
                                Some(sep)
                            }
    ...
  15. kohsine commented on Sep 10, 2026

    @kohsine
    Contributor

    I see that #148209 is no longer being worked on. Can I work on this or should I hold off?

  16. moatom commented on Sep 11, 2026

    @moatom
    Contributor

    @kohsine
    I haven't received any further feedback in #148209 for a while. I suspect this is because the reviewer was concerned about the compatibility of my parsing logic, so I was planning to verify the compatibility and explain it when I have time.

    However, it may take me until around the end of the year to get to it. So if you are able to take this issue on in the meantime, please feel free to do so!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-proc-macrosArea: Procedural macrosC-feature-requestCategory: A feature request, i.e: not implemented / a PR.E-help-wantedCall for participation: Help is requested to fix this issue.E-mediumCall for participation: Medium difficulty. Experience needed to fix: Intermediate.F-proc_macro_quote`#![feature(proc_macro_quote)]`T-libsRelevant to the library team, which will review and decide on the PR/issue.WG-macrosWorking group: Macros

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions