Skip to content

Add support for repetition to proc_macro::quote #141608

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 18, 2025

Conversation

moatom
Copy link
Contributor

@moatom moatom commented May 26, 2025

Progress toward: #140238

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label May 26, 2025
@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@moatom moatom changed the title WIP: Add support for repetition to proc_macro::quote Add support for repetition to proc_macro::quote May 31, 2025
@@ -1613,3 +1614,202 @@ pub mod tracked_path {
crate::bridge::client::FreeFunctions::track_path(path);
}
}

#[doc(hidden)]
#[unstable(feature = "proc_macro_quote", issue = "54722")]
Copy link
Contributor Author

@moatom moatom May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether these annotations are appropriate. (I added them just based on my speculation.)

In addition,

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

do we need to take care of its license?

Copy link
Contributor

@tgross35 tgross35 Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether these annotations are appropriate. (I added them just based on my speculation.)

You can always try removing them and see if it complains - but in general, all crate-public items need a stability gate. Try to make as little as possible actually pub of course, but that's difficult because this needs a lot of helpers to be public.

This all doesn't need to be in lib.rs though, could you move the additions here to the quote module? And then reexport only what is needed.

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

do we need to take care of its license?

quote is MIT AND Apache-2.0, which is the same as rust-lang/rust so there is no problem here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all doesn't need to be in lib.rs though, could you move the additions here to the quote module? And then reexport only what is needed.

DONE.
FYI, I used #[unstable(feature = "proc_macro_quote", issue = "54722")] instead of something like #[unstable(feature = "proc_macro_quote_span", issue = "140238")], because this PR modifies the quote function, which is already part of the "proc_macro_quote" feature.

#[unstable(feature = "proc_macro_quote", issue = "54722")]
pub mod ext {
use core::slice;
use std::collections::btree_set::{self, BTreeSet};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an alternative for alloc::collections

}

self.tokens[self.pos] = self.iter.next();
let token_opt = self.tokens[self.pos].clone();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This helper struct may require significant refactoring. (Especially, the use of .clone() might be avoidable.)
Do you have any comments or suggestions?

Copy link

@ora-0 ora-0 May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth using just a Peekable. I think it would be better to use it at the expense of some extra parsing code, than using a custom lookahead iterator

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ora-0
I think Peekable only supports single-element lookahead.

However, placing a simplified and extended version of Peekable here might be a better approach than defining the completely original lookahead iterator from scratch. 🤔

Copy link

@ora-0 ora-0 Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if a lookahead is necessary. Would this work? I can't be completely sure since I haven't tested this code.

let mut iter = stream.into_iter();
// ...
let mut sep_opt: Option<TokenTree> = None;
if let next @ Some(TokenTree::Punct(token_1)) = iter.next() {
    if token_1.as_char() != '*' {
        sep_opt = next;
        if !matches!(iter.next(), Some(TokenTree::Punct(token_2)) if token_2 == "*") {
            panic!("`$(...)` must be followed by `*` in `quote!`");
        }
    }
}

Since we are panicking at the wildcard we could just use .next()s. It is a bit less declarative but it avoids the complexity of another data structure.

Copy link

@ora-0 ora-0 Jun 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nvm, turns out it is necessary, because the separator itself may end up being a star. But I think we only need one lookahead. The code ends up being pretty similar to the current:

let mut iter = stream.into_iter().peekable();
// ...
let sep_opt: Option<TokenTree> = match (iter.next(), iter.peek()) {
    (Some(TokenTree::Punct(sep)), Some(&TokenTree::Punct(star))) 
        if sep.spacing() == Spacing::Joint && star.as_char() == '*' => 
    {
        iter.next();
        Some(TokenTree::Punct(sep))
    }
    (Some(TokenTree::Punct(star)), _) if star.as_char() == '*' => None,
    _ => panic!("`$(...)` must be followed by `*` in `quote!`"),
};

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I think we only need one lookahead.

Thank you for the great point! We must consume at least one * here.

@@ -71,10 +160,97 @@ pub fn quote(stream: TokenStream) -> TokenStream {
let mut after_dollar = false;

let mut tokens = crate::TokenStream::new();
for tree in stream {
let mut iter = LookaheadIter::new(stream);
while let Some(tree) = iter.next() {
if after_dollar {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after_dollar can be combined with LookaheadIter, if you prefer.

Comment on lines +107 to +109
assert_eq!("X, X, X, X,", quote!($($primes,)*).to_string());

assert_eq!("X, X, X, X", quote!($($primes),*).to_string());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I adjusted the expected spacing around SEP in the original test code. Is it appropriate?
Cf. https://github.com/dtolnay/quote/blob/62fd385a800f7398ab416c00100664479261a86e/tests/test.rs#L84

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proc_macro and proc_macro2 unquote whitespace slightly differently, is this what you are referring to? If so, I don't think there is any problem.

@moatom moatom marked this pull request as ready for review June 1, 2025 00:48
@rustbot
Copy link
Collaborator

rustbot commented Jun 1, 2025

r? @petrochenkov

rustbot has assigned @petrochenkov.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jun 1, 2025
@rustbot

This comment has been minimized.

@moatom
Copy link
Contributor Author

moatom commented Jun 1, 2025

@tgross35 @dtolnay @ora-0
Hi! Although it still needs some refactoring, it passes all the updated tests.
I'd be grateful for your review and feedback.

@moatom
Copy link
Contributor Author

moatom commented Jun 1, 2025

r? @tgross35

@rustbot rustbot assigned tgross35 and unassigned petrochenkov Jun 1, 2025
@tgross35
Copy link
Contributor

tgross35 commented Jun 1, 2025

I’ll leave a review but David knows this area much better, so

r? dtolnay

@rustbot rustbot assigned dtolnay and unassigned tgross35 Jun 1, 2025
@rust-log-analyzer

This comment has been minimized.

Comment on lines +107 to +109
assert_eq!("X, X, X, X,", quote!($($primes,)*).to_string());

assert_eq!("X, X, X, X", quote!($($primes),*).to_string());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

proc_macro and proc_macro2 unquote whitespace slightly differently, is this what you are referring to? If so, I don't think there is any problem.

if after_dollar {
after_dollar = false;
match tree {
TokenTree::Group(inner) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add comments in this section about what is happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added them, but

https://github.com/rust-lang/rust/pull/141608/files#diff-6ebaa6be050b7cea1d542579c11cde3cba0942a3417c53298001f9db02a8d06dR310-R311

// Append setup code for a while, where recursively quoted CONTENTS
// and SEP_OPT are repeatedly processed, to REP_EXPANDED.

the word choice of "setup code" may be ambiguous.
Moreover, it would be better to carefully distinguish the terms "expanded", "processed", and "quoted".

@@ -155,6 +276,30 @@ pub fn quote(stream: TokenStream) -> TokenStream {
}
}

fn collect_meta_vars(stream: TokenStream) -> Vec<Ident> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a doc comment?

@tgross35
Copy link
Contributor

You should also rebase when you update this, I think the CI failure was from something spurious.

@moatom moatom force-pushed the proc_macro-140238 branch 3 times, most recently from dcca29e to e6cb946 Compare June 16, 2025 16:33
@rustbot rustbot added the A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. label Jun 16, 2025
@moatom moatom force-pushed the proc_macro-140238 branch from e6cb946 to 23e35c6 Compare June 16, 2025 16:40
@moatom
Copy link
Contributor Author

moatom commented Jun 16, 2025

@tgross35 @dtolnay
I've addressed all the points. Please review it at your convenience.

@rustbot rustbot added the A-LLVM label Jun 17, 2025

This label was added because I mistakenly pushed incorrectly rebased commits. Feel free to remove it if you'd like.

@dtolnay dtolnay added A-proc-macros Area: Procedural macros and removed A-LLVM Area: Code generation parts specific to LLVM. Both correctness bugs and optimization-related issues. labels Jun 16, 2025
@dtolnay
Copy link
Member

dtolnay commented Jun 16, 2025

Thanks for working on this feature!

I see some parsing differences here compared to what the quote crate does. I think this PR is a good start and is fine to land in nightly, but not all of these differences seem like improvements to me. Example:

#![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.

@bors r+

@bors
Copy link
Collaborator

bors commented Jun 16, 2025

📌 Commit 23e35c6 has been approved by dtolnay

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 16, 2025
@tgross35
Copy link
Contributor

I see some parsing differences here compared to what the quote crate does. I think this PR is a good start and is fine to land in nightly, but not all of these differences seem like improvements to me. Example:

I removed the tag to close #140238 and added your comment as a todo item there so we don't lose this follow up.

jhpratt added a commit to jhpratt/rust that referenced this pull request Jun 17, 2025
Add support for repetition to `proc_macro::quote`

Progress toward: rust-lang#140238
bors added a commit that referenced this pull request Jun 17, 2025
Rollup of 11 pull requests

Successful merges:

 - #140809 (Reduce special casing for the panic runtime)
 - #141608 (Add support for repetition to `proc_macro::quote`)
 - #141864 (Handle win32 separator for cygwin paths)
 - #142216 (Miscellaneous RefCell cleanups)
 - #142517 (Windows: Use anonymous pipes in Command)
 - #142570 (Reject union default field values)
 - #142584 (Handle same-crate macro for borrowck semicolon suggestion)
 - #142585 (Update books)
 - #142586 (Fold unnecessary `visit_struct_field_def` in AstValidator)
 - #142595 (Revert overeager warning for misuse of `--print native-static-libs`)
 - #142598 (Set elf e_flags on ppc64 targets according to abi)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit that referenced this pull request Jun 18, 2025
Rollup of 14 pull requests

Successful merges:

 - #141574 (impl `Default` for `array::IntoIter`)
 - #141608 (Add support for repetition to `proc_macro::quote`)
 - #142100 (rustdoc: make srcIndex no longer a global variable)
 - #142371 (avoid `&mut P<T>` in `visit_expr` etc methods)
 - #142517 (Windows: Use anonymous pipes in Command)
 - #142520 (alloc: less static mut + some cleanup)
 - #142588 (Generic ctx imprv)
 - #142605 (Don't unwrap in enzyme builds in case of missing llvm-config)
 - #142608 (Refresh module-level docs for `rustc_target::spec`)
 - #142618 (Lint about `console` calls in rustdoc JS)
 - #142620 (Remove a panicking branch in `BorrowedCursor::advance`)
 - #142631 (Dont suggest remove semi inside macro expansion for redundant semi lint)
 - #142632 (Update cargo)
 - #142635 (Temporarily add back -Zwasm-c-abi=spec)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 17ab49a into rust-lang:master Jun 18, 2025
10 checks passed
@rustbot rustbot added this to the 1.89.0 milestone Jun 18, 2025
rust-timer added a commit that referenced this pull request Jun 18, 2025
Rollup merge of #141608 - moatom:proc_macro-140238, r=dtolnay

Add support for repetition to `proc_macro::quote`

Progress toward: #140238
lnicola pushed a commit to lnicola/rust-analyzer that referenced this pull request Jun 18, 2025
Rollup of 14 pull requests

Successful merges:

 - rust-lang/rust#141574 (impl `Default` for `array::IntoIter`)
 - rust-lang/rust#141608 (Add support for repetition to `proc_macro::quote`)
 - rust-lang/rust#142100 (rustdoc: make srcIndex no longer a global variable)
 - rust-lang/rust#142371 (avoid `&mut P<T>` in `visit_expr` etc methods)
 - rust-lang/rust#142517 (Windows: Use anonymous pipes in Command)
 - rust-lang/rust#142520 (alloc: less static mut + some cleanup)
 - rust-lang/rust#142588 (Generic ctx imprv)
 - rust-lang/rust#142605 (Don't unwrap in enzyme builds in case of missing llvm-config)
 - rust-lang/rust#142608 (Refresh module-level docs for `rustc_target::spec`)
 - rust-lang/rust#142618 (Lint about `console` calls in rustdoc JS)
 - rust-lang/rust#142620 (Remove a panicking branch in `BorrowedCursor::advance`)
 - rust-lang/rust#142631 (Dont suggest remove semi inside macro expansion for redundant semi lint)
 - rust-lang/rust#142632 (Update cargo)
 - rust-lang/rust#142635 (Temporarily add back -Zwasm-c-abi=spec)

r? `@ghost`
`@rustbot` modify labels: rollup
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-proc-macros Area: Procedural macros S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants