about summary refs log tree commit diff
diff options
context:
space:
mode:
authorTomoaki Kobayashi <tomoaki.kobayashi.t3@alumni.tohoku.ac.jp>2025-05-27 02:30:53 +0900
committerTomoaki Kobayashi <tomoaki.kobayashi.t3@alumni.tohoku.ac.jp>2025-06-17 01:36:54 +0900
commit23e35c6bd371485eff7c5f58cb59ee28da59ba62 (patch)
treeaaa44078931b2ec19f52c780b633114fadc82b47
parent68ac5abb067806a88464ddbfbd3c7eec877b488d (diff)
downloadrust-23e35c6bd371485eff7c5f58cb59ee28da59ba62.tar.gz
rust-23e35c6bd371485eff7c5f58cb59ee28da59ba62.zip
Add support for repetition to `proc_macro::quote`
-rw-r--r--library/proc_macro/src/lib.rs3
-rw-r--r--library/proc_macro/src/quote.rs328
-rw-r--r--tests/ui/proc-macro/quote/auxiliary/basic.rs152
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs6
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr11
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs6
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr11
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-separated.rs6
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr10
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter.rs6
-rw-r--r--tests/ui/proc-macro/quote/does-not-have-iter.stderr10
-rw-r--r--tests/ui/proc-macro/quote/not-quotable.stderr4
-rw-r--r--tests/ui/proc-macro/quote/not-repeatable.rs6
-rw-r--r--tests/ui/proc-macro/quote/not-repeatable.stderr23
14 files changed, 519 insertions, 63 deletions
diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs
index 32c306be94e..b0084edf2f8 100644
--- a/library/proc_macro/src/lib.rs
+++ b/library/proc_macro/src/lib.rs
@@ -45,6 +45,7 @@ mod diagnostic;
 mod escape;
 mod to_tokens;
 
+use core::ops::BitOr;
 use std::ffi::CStr;
 use std::ops::{Range, RangeBounds};
 use std::path::PathBuf;
@@ -237,7 +238,7 @@ impl Default for TokenStream {
 }
 
 #[unstable(feature = "proc_macro_quote", issue = "54722")]
-pub use quote::{quote, quote_span};
+pub use quote::{HasIterator, RepInterp, ThereIsNoIteratorInRepetition, ext, quote, quote_span};
 
 fn tree_to_bridge_tree(
     tree: TokenTree,
diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs
index bcb15912bb6..dbb55cd9fb3 100644
--- a/library/proc_macro/src/quote.rs
+++ b/library/proc_macro/src/quote.rs
@@ -5,9 +5,183 @@
 //! items from `proc_macro`, to build a `proc_macro::TokenStream`.
 
 use crate::{
-    Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
+    BitOr, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
 };
 
+#[doc(hidden)]
+pub struct HasIterator; // True
+#[doc(hidden)]
+pub struct ThereIsNoIteratorInRepetition; // False
+
+impl BitOr<ThereIsNoIteratorInRepetition> for ThereIsNoIteratorInRepetition {
+    type Output = ThereIsNoIteratorInRepetition;
+    fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition {
+        ThereIsNoIteratorInRepetition
+    }
+}
+
+impl BitOr<ThereIsNoIteratorInRepetition> for HasIterator {
+    type Output = HasIterator;
+    fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator {
+        HasIterator
+    }
+}
+
+impl BitOr<HasIterator> for ThereIsNoIteratorInRepetition {
+    type Output = HasIterator;
+    fn bitor(self, _rhs: HasIterator) -> HasIterator {
+        HasIterator
+    }
+}
+
+impl BitOr<HasIterator> for HasIterator {
+    type Output = HasIterator;
+    fn bitor(self, _rhs: HasIterator) -> HasIterator {
+        HasIterator
+    }
+}
+
+/// Extension traits used by the implementation of `quote!`. These are defined
+/// in separate traits, rather than as a single trait due to ambiguity issues.
+///
+/// These traits expose a `quote_into_iter` method which should allow calling
+/// whichever impl happens to be applicable. Calling that method repeatedly on
+/// the returned value should be idempotent.
+#[doc(hidden)]
+pub mod ext {
+    use core::slice;
+    use std::collections::btree_set::{self, BTreeSet};
+
+    use super::{
+        HasIterator as HasIter, RepInterp, ThereIsNoIteratorInRepetition as DoesNotHaveIter,
+    };
+    use crate::ToTokens;
+
+    /// Extension trait providing the `quote_into_iter` method on iterators.
+    #[doc(hidden)]
+    pub trait RepIteratorExt: Iterator + Sized {
+        fn quote_into_iter(self) -> (Self, HasIter) {
+            (self, HasIter)
+        }
+    }
+
+    impl<T: Iterator> RepIteratorExt for T {}
+
+    /// Extension trait providing the `quote_into_iter` method for
+    /// non-iterable types. These types interpolate the same value in each
+    /// iteration of the repetition.
+    #[doc(hidden)]
+    pub trait RepToTokensExt {
+        /// Pretend to be an iterator for the purposes of `quote_into_iter`.
+        /// This allows repeated calls to `quote_into_iter` to continue
+        /// correctly returning DoesNotHaveIter.
+        fn next(&self) -> Option<&Self> {
+            Some(self)
+        }
+
+        fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) {
+            (self, DoesNotHaveIter)
+        }
+    }
+
+    impl<T: ToTokens + ?Sized> RepToTokensExt for T {}
+
+    /// Extension trait providing the `quote_into_iter` method for types that
+    /// can be referenced as an iterator.
+    #[doc(hidden)]
+    pub trait RepAsIteratorExt<'q> {
+        type Iter: Iterator;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter);
+    }
+
+    impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &T {
+        type Iter = T::Iter;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            <T as RepAsIteratorExt>::quote_into_iter(*self)
+        }
+    }
+
+    impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &mut T {
+        type Iter = T::Iter;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            <T as RepAsIteratorExt>::quote_into_iter(*self)
+        }
+    }
+
+    impl<'q, T: 'q> RepAsIteratorExt<'q> for [T] {
+        type Iter = slice::Iter<'q, T>;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            (self.iter(), HasIter)
+        }
+    }
+
+    impl<'q, T: 'q, const N: usize> RepAsIteratorExt<'q> for [T; N] {
+        type Iter = slice::Iter<'q, T>;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            (self.iter(), HasIter)
+        }
+    }
+
+    impl<'q, T: 'q> RepAsIteratorExt<'q> for Vec<T> {
+        type Iter = slice::Iter<'q, T>;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            (self.iter(), HasIter)
+        }
+    }
+
+    impl<'q, T: 'q> RepAsIteratorExt<'q> for BTreeSet<T> {
+        type Iter = btree_set::Iter<'q, T>;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            (self.iter(), HasIter)
+        }
+    }
+
+    impl<'q, T: RepAsIteratorExt<'q>> RepAsIteratorExt<'q> for RepInterp<T> {
+        type Iter = T::Iter;
+
+        fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
+            self.0.quote_into_iter()
+        }
+    }
+}
+
+// Helper type used within interpolations to allow for repeated binding names.
+// Implements the relevant traits, and exports a dummy `next()` method.
+#[derive(Copy, Clone)]
+#[doc(hidden)]
+pub struct RepInterp<T>(pub T);
+
+impl<T> RepInterp<T> {
+    // This method is intended to look like `Iterator::next`, and is called when
+    // a name is bound multiple times, as the previous binding will shadow the
+    // original `Iterator` object. This allows us to avoid advancing the
+    // iterator multiple times per iteration.
+    pub fn next(self) -> Option<T> {
+        Some(self.0)
+    }
+}
+
+impl<T: Iterator> Iterator for RepInterp<T> {
+    type Item = T::Item;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        self.0.next()
+    }
+}
+
+impl<T: ToTokens> ToTokens for RepInterp<T> {
+    fn to_tokens(&self, tokens: &mut TokenStream) {
+        self.0.to_tokens(tokens);
+    }
+}
+
 macro_rules! minimal_quote_tt {
     (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) };
     ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) };
@@ -20,7 +194,13 @@ macro_rules! minimal_quote_tt {
     (>) => { Punct::new('>', Spacing::Alone) };
     (&) => { Punct::new('&', Spacing::Alone) };
     (=) => { Punct::new('=', Spacing::Alone) };
+    (#) => { Punct::new('#', Spacing::Alone) };
+    (|) => { Punct::new('|', Spacing::Alone) };
+    (:) => { Punct::new(':', Spacing::Alone) };
+    (*) => { Punct::new('*', Spacing::Alone) };
+    (_) => { Ident::new("_", Span::def_site()) };
     ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
+    ($lit:literal) => { stringify!($lit).parse::<Literal>().unwrap() };
 }
 
 macro_rules! minimal_quote_ts {
@@ -36,6 +216,39 @@ macro_rules! minimal_quote_ts {
             [c.0, c.1].into_iter().collect::<TokenStream>()
         }
     };
+    (=>) => {
+        {
+            let mut c = (
+                TokenTree::from(Punct::new('=', Spacing::Joint)),
+                TokenTree::from(Punct::new('>', Spacing::Alone))
+            );
+            c.0.set_span(Span::def_site());
+            c.1.set_span(Span::def_site());
+            [c.0, c.1].into_iter().collect::<TokenStream>()
+        }
+    };
+    (+=) => {
+        {
+            let mut c = (
+                TokenTree::from(Punct::new('+', Spacing::Joint)),
+                TokenTree::from(Punct::new('=', Spacing::Alone))
+            );
+            c.0.set_span(Span::def_site());
+            c.1.set_span(Span::def_site());
+            [c.0, c.1].into_iter().collect::<TokenStream>()
+        }
+    };
+    (!=) => {
+        {
+            let mut c = (
+                TokenTree::from(Punct::new('!', Spacing::Joint)),
+                TokenTree::from(Punct::new('=', Spacing::Alone))
+            );
+            c.0.set_span(Span::def_site());
+            c.1.set_span(Span::def_site());
+            [c.0, c.1].into_iter().collect::<TokenStream>()
+        }
+    };
     ($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) };
 }
 
@@ -71,17 +284,99 @@ pub fn quote(stream: TokenStream) -> TokenStream {
     let mut after_dollar = false;
 
     let mut tokens = crate::TokenStream::new();
-    for tree in stream {
+    let mut iter = stream.into_iter().peekable();
+    while let Some(tree) = iter.next() {
         if after_dollar {
             after_dollar = false;
             match tree {
+                TokenTree::Group(tt) => {
+                    // Handles repetition by expanding `$( CONTENTS ) SEP_OPT *` to `{ REP_EXPANDED }`.
+                    let contents = tt.stream();
+
+                    // The `*` token is also consumed here.
+                    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)
+                        }
+                        (Some(TokenTree::Punct(star)), _) if star.as_char() == '*' => None,
+                        _ => panic!("`$(...)` must be followed by `*` in `quote!`"),
+                    };
+
+                    let mut rep_expanded = TokenStream::new();
+
+                    // Append setup code for a `while`, where recursively quoted `CONTENTS`
+                    // and `SEP_OPT` are repeatedly processed, to `REP_EXPANDED`.
+                    let meta_vars = collect_meta_vars(contents.clone());
+                    minimal_quote!(
+                        use crate::ext::*;
+                        (@ if sep_opt.is_some() {
+                            minimal_quote!(let mut _i = 0usize;)
+                        } else {
+                            minimal_quote!(();)
+                        })
+                        let has_iter = crate::ThereIsNoIteratorInRepetition;
+                    )
+                    .to_tokens(&mut rep_expanded);
+                    for meta_var in &meta_vars {
+                        minimal_quote!(
+                            #[allow(unused_mut)]
+                            let (mut (@ meta_var), i) = (@ meta_var).quote_into_iter();
+                            let has_iter = has_iter | i;
+                        )
+                        .to_tokens(&mut rep_expanded);
+                    }
+                    minimal_quote!(let _: crate::HasIterator = has_iter;)
+                        .to_tokens(&mut rep_expanded);
+
+                    // Append the `while` to `REP_EXPANDED`.
+                    let mut while_body = TokenStream::new();
+                    for meta_var in &meta_vars {
+                        minimal_quote!(
+                            let (@ meta_var) = match (@ meta_var).next() {
+                                Some(_x) => crate::RepInterp(_x),
+                                None => break,
+                            };
+                        )
+                        .to_tokens(&mut while_body);
+                    }
+                    minimal_quote!(
+                        (@ if let Some(sep) = sep_opt {
+                            minimal_quote!(
+                                if _i > 0 {
+                                    (@ minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(
+                                        (@ TokenTree::from(Literal::character(sep.as_char()))),
+                                        (@ minimal_quote!(crate::Spacing::Alone)),
+                                    )), &mut ts);))
+                                }
+                                _i += 1;
+                            )
+                        } else {
+                            minimal_quote!(();)
+                        })
+                        (@ quote(contents.clone())).to_tokens(&mut ts);
+                    )
+                        .to_tokens(&mut while_body);
+                    rep_expanded.extend(vec![
+                        TokenTree::Ident(Ident::new("while", Span::call_site())),
+                        TokenTree::Ident(Ident::new("true", Span::call_site())),
+                        TokenTree::Group(Group::new(Delimiter::Brace, while_body)),
+                    ]);
+
+                    minimal_quote!((@ TokenTree::Group(Group::new(Delimiter::Brace, rep_expanded)))).to_tokens(&mut tokens);
+                    continue;
+                }
                 TokenTree::Ident(_) => {
                     minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);)
                         .to_tokens(&mut tokens);
                     continue;
                 }
                 TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
-                _ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
+                _ => panic!(
+                    "`$` must be followed by an ident or `$` or a repetition group in `quote!`"
+                ),
             }
         } else if let TokenTree::Punct(ref tt) = tree {
             if tt.as_char() == '$' {
@@ -155,6 +450,33 @@ pub fn quote(stream: TokenStream) -> TokenStream {
     }
 }
 
+/// Helper function to support macro repetitions like `$( CONTENTS ) SEP_OPT *` in `quote!`.
+/// Recursively collects all `Ident`s (meta-variables) that follow a `$`
+/// from the given `CONTENTS` stream, preserving their order of appearance.
+fn collect_meta_vars(content_stream: TokenStream) -> Vec<Ident> {
+    fn helper(stream: TokenStream, out: &mut Vec<Ident>) {
+        let mut iter = stream.into_iter().peekable();
+        while let Some(tree) = iter.next() {
+            match &tree {
+                TokenTree::Punct(tt) if tt.as_char() == '$' => {
+                    if let Some(TokenTree::Ident(id)) = iter.peek() {
+                        out.push(id.clone());
+                        iter.next();
+                    }
+                }
+                TokenTree::Group(tt) => {
+                    helper(tt.stream(), out);
+                }
+                _ => {}
+            }
+        }
+    }
+
+    let mut vars = Vec::new();
+    helper(content_stream, &mut vars);
+    vars
+}
+
 /// Quote a `Span` into a `TokenStream`.
 /// This is needed to implement a custom quoter.
 #[unstable(feature = "proc_macro_quote", issue = "54722")]
diff --git a/tests/ui/proc-macro/quote/auxiliary/basic.rs b/tests/ui/proc-macro/quote/auxiliary/basic.rs
index ef726bbfbe3..c50bb964eab 100644
--- a/tests/ui/proc-macro/quote/auxiliary/basic.rs
+++ b/tests/ui/proc-macro/quote/auxiliary/basic.rs
@@ -4,6 +4,7 @@
 extern crate proc_macro;
 
 use std::borrow::Cow;
+use std::collections::BTreeSet;
 use std::ffi::{CStr, CString};
 
 use proc_macro::*;
@@ -12,6 +13,8 @@ use proc_macro::*;
 pub fn run_tests(_: TokenStream) -> TokenStream {
     test_quote_impl();
     test_substitution();
+    test_iter();
+    test_array();
     test_advanced();
     test_integer();
     test_floating();
@@ -24,6 +27,13 @@ pub fn run_tests(_: TokenStream) -> TokenStream {
     test_ident();
     test_underscore();
     test_duplicate();
+    test_fancy_repetition();
+    test_nested_fancy_repetition();
+    test_duplicate_name_repetition();
+    test_duplicate_name_repetition_no_copy();
+    test_btreeset_repetition();
+    test_variable_name_conflict();
+    test_nonrep_in_repetition();
     test_empty_quote();
     test_box_str();
     test_cow();
@@ -34,6 +44,7 @@ pub fn run_tests(_: TokenStream) -> TokenStream {
     test_inner_block_comment();
     test_outer_attr();
     test_inner_attr();
+    test_star_after_repetition();
     test_quote_raw_id();
 
     TokenStream::new()
@@ -49,20 +60,9 @@ pub fn run_tests(_: TokenStream) -> TokenStream {
 //   - fn test_type_inference_for_span
 //   - wrong-type-span.rs
 // - format_ident:
+//   - fn test_closure
 //   - fn test_format_ident
 //   - fn test_format_ident_strip_raw
-// - repetition:
-//   - fn test_iter
-//   - fn test_array
-//   - fn test_fancy_repetition
-//   - fn test_nested_fancy_repetition
-//   - fn test_duplicate_name_repetition
-//   - fn test_duplicate_name_repetition_no_copy
-//   - fn test_btreeset_repetition
-//   - fn test_variable_name_conflict
-//   - fn test_nonrep_in_repetition
-//   - fn test_closure
-//   - fn test_star_after_repetition
 
 struct X;
 
@@ -99,6 +99,39 @@ fn test_substitution() {
     assert_eq!(expected, tokens.to_string());
 }
 
+fn test_iter() {
+    let primes = &[X, X, X, X];
+
+    assert_eq!("X X X X", quote!($($primes)*).to_string());
+
+    assert_eq!("X, X, X, X,", quote!($($primes,)*).to_string());
+
+    assert_eq!("X, X, X, X", quote!($($primes),*).to_string());
+}
+
+fn test_array() {
+    let array: [u8; 40] = [0; 40];
+    let _ = quote!($($array $array)*);
+
+    let ref_array: &[u8; 40] = &[0; 40];
+    let _ = quote!($($ref_array $ref_array)*);
+
+    let ref_slice: &[u8] = &[0; 40];
+    let _ = quote!($($ref_slice $ref_slice)*);
+
+    let array: [X; 2] = [X, X]; // !Copy
+    let _ = quote!($($array $array)*);
+
+    let ref_array: &[X; 2] = &[X, X];
+    let _ = quote!($($ref_array $ref_array)*);
+
+    let ref_slice: &[X] = &[X, X];
+    let _ = quote!($($ref_slice $ref_slice)*);
+
+    let array_of_array: [[u8; 2]; 2] = [[0; 2]; 2];
+    let _ = quote!($($($array_of_array)*)*);
+}
+
 fn test_advanced() {
     let generics = quote!( <'a, T> );
 
@@ -279,6 +312,88 @@ fn test_duplicate() {
     assert_eq!(expected, tokens.to_string());
 }
 
+fn test_fancy_repetition() {
+    let foo = vec!["a", "b"];
+    let bar = vec![true, false];
+
+    let tokens = quote! {
+        $($foo: $bar),*
+    };
+
+    let expected = r#""a" : true, "b" : false"#;
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_nested_fancy_repetition() {
+    let nested = vec![vec!['a', 'b', 'c'], vec!['x', 'y', 'z']];
+
+    let tokens = quote! {
+        $(
+            $($nested)*
+        ),*
+    };
+
+    let expected = "'a' 'b' 'c', 'x' 'y' 'z'";
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_duplicate_name_repetition() {
+    let foo = &["a", "b"];
+
+    let tokens = quote! {
+        $($foo: $foo),*
+        $($foo: $foo),*
+    };
+
+    let expected = r#""a" : "a", "b" : "b" "a" : "a", "b" : "b""#;
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_duplicate_name_repetition_no_copy() {
+    let foo = vec!["a".to_owned(), "b".to_owned()];
+
+    let tokens = quote! {
+        $($foo: $foo),*
+    };
+
+    let expected = r#""a" : "a", "b" : "b""#;
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_btreeset_repetition() {
+    let mut set = BTreeSet::new();
+    set.insert("a".to_owned());
+    set.insert("b".to_owned());
+
+    let tokens = quote! {
+        $($set: $set),*
+    };
+
+    let expected = r#""a" : "a", "b" : "b""#;
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_variable_name_conflict() {
+    // The implementation of `#(...),*` uses the variable `_i` but it should be
+    // fine, if a little confusing when debugging.
+    let _i = vec!['a', 'b'];
+    let tokens = quote! { $($_i),* };
+    let expected = "'a', 'b'";
+    assert_eq!(expected, tokens.to_string());
+}
+
+fn test_nonrep_in_repetition() {
+    let rep = vec!["a", "b"];
+    let nonrep = "c";
+
+    let tokens = quote! {
+        $($rep $rep : $nonrep $nonrep),*
+    };
+
+    let expected = r#""a" "a" : "c" "c", "b" "b" : "c" "c""#;
+    assert_eq!(expected, tokens.to_string());
+}
+
 fn test_empty_quote() {
     let tokens = quote!();
     assert_eq!("", tokens.to_string());
@@ -355,6 +470,19 @@ fn test_inner_attr() {
     assert_eq!(expected, tokens.to_string());
 }
 
+// https://github.com/dtolnay/quote/issues/130
+fn test_star_after_repetition() {
+    let c = vec!['0', '1'];
+    let tokens = quote! {
+        $(
+            f($c);
+        )*
+        *out = None;
+    };
+    let expected = "f('0'); f('1'); * out = None;";
+    assert_eq!(expected, tokens.to_string());
+}
+
 fn test_quote_raw_id() {
     let id = quote!(r#raw_id);
     assert_eq!(id.to_string(), "r#raw_id");
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs
index 2f67ae1bc6e..418e3dd444d 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs
@@ -1,7 +1,3 @@
-// FIXME(quote): `proc_macro::quote!` doesn't support repetition at the moment, so the stderr is
-// expected to be incorrect.
-//@ known-bug: #54722
-
 #![feature(proc_macro_quote)]
 
 extern crate proc_macro;
@@ -13,5 +9,5 @@ fn main() {
 
     // Without some protection against repetitions with no iterator somewhere
     // inside, this would loop infinitely.
-    quote!($($nonrep $nonrep)*);
+    quote!($($nonrep $nonrep)*); //~ ERROR mismatched types
 }
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr
index 5f28a46f318..ecb12c1df3b 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr
@@ -1,10 +1,13 @@
-error: proc macro panicked
-  --> $DIR/does-not-have-iter-interpolated-dup.rs:16:5
+error[E0308]: mismatched types
+  --> $DIR/does-not-have-iter-interpolated-dup.rs:12:5
    |
 LL |     quote!($($nonrep $nonrep)*);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   |
-   = help: message: `$` must be followed by an ident or `$` in `quote!`
+   |     |
+   |     expected `HasIterator`, found `ThereIsNoIteratorInRepetition`
+   |     expected due to this
+   |     here the type of `has_iter` is inferred to be `ThereIsNoIteratorInRepetition`
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs
index 1efb3eac642..507936770aa 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs
@@ -1,7 +1,3 @@
-// FIXME(quote): `proc_macro::quote!` doesn't support repetition at the moment, so the stderr is
-// expected to be incorrect.
-//@ known-bug: #54722
-
 #![feature(proc_macro_quote)]
 
 extern crate proc_macro;
@@ -13,5 +9,5 @@ fn main() {
 
     // Without some protection against repetitions with no iterator somewhere
     // inside, this would loop infinitely.
-    quote!($($nonrep)*);
+    quote!($($nonrep)*); //~ ERROR mismatched types
 }
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr
index 595aa858763..093e2ebc098 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr
@@ -1,10 +1,13 @@
-error: proc macro panicked
-  --> $DIR/does-not-have-iter-interpolated.rs:16:5
+error[E0308]: mismatched types
+  --> $DIR/does-not-have-iter-interpolated.rs:12:5
    |
 LL |     quote!($($nonrep)*);
    |     ^^^^^^^^^^^^^^^^^^^
-   |
-   = help: message: `$` must be followed by an ident or `$` in `quote!`
+   |     |
+   |     expected `HasIterator`, found `ThereIsNoIteratorInRepetition`
+   |     expected due to this
+   |     here the type of `has_iter` is inferred to be `ThereIsNoIteratorInRepetition`
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-separated.rs b/tests/ui/proc-macro/quote/does-not-have-iter-separated.rs
index 5f2ddabc390..7e41b08f263 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-separated.rs
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-separated.rs
@@ -1,7 +1,3 @@
-// FIXME(quote): `proc_macro::quote!` doesn't support repetition at the moment, so the stderr is
-// expected to be incorrect.
-//@ known-bug: #54722
-
 #![feature(proc_macro_quote)]
 
 extern crate proc_macro;
@@ -9,5 +5,5 @@ extern crate proc_macro;
 use proc_macro::quote;
 
 fn main() {
-    quote!($(a b),*);
+    quote!($(a b),*); //~ ERROR mismatched types
 }
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr b/tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr
index f6f5d7e007d..937209e675e 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr
+++ b/tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr
@@ -1,10 +1,12 @@
-error: proc macro panicked
-  --> $DIR/does-not-have-iter-separated.rs:12:5
+error[E0308]: mismatched types
+  --> $DIR/does-not-have-iter-separated.rs:8:5
    |
 LL |     quote!($(a b),*);
    |     ^^^^^^^^^^^^^^^^
-   |
-   = help: message: `$` must be followed by an ident or `$` in `quote!`
+   |     |
+   |     expected `HasIterator`, found `ThereIsNoIteratorInRepetition`
+   |     expected due to this
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter.rs b/tests/ui/proc-macro/quote/does-not-have-iter.rs
index 25ffd786cc6..038851ff76e 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter.rs
+++ b/tests/ui/proc-macro/quote/does-not-have-iter.rs
@@ -1,7 +1,3 @@
-// FIXME(quote): `proc_macro::quote!` doesn't support repetition at the moment, so the stderr is
-// expected to be incorrect.
-//@ known-bug: #54722
-
 #![feature(proc_macro_quote)]
 
 extern crate proc_macro;
@@ -9,5 +5,5 @@ extern crate proc_macro;
 use proc_macro::quote;
 
 fn main() {
-    quote!($(a b)*);
+    quote!($(a b)*); //~ ERROR mismatched types
 }
diff --git a/tests/ui/proc-macro/quote/does-not-have-iter.stderr b/tests/ui/proc-macro/quote/does-not-have-iter.stderr
index 0ed1daffc8c..e74ea334899 100644
--- a/tests/ui/proc-macro/quote/does-not-have-iter.stderr
+++ b/tests/ui/proc-macro/quote/does-not-have-iter.stderr
@@ -1,10 +1,12 @@
-error: proc macro panicked
-  --> $DIR/does-not-have-iter.rs:12:5
+error[E0308]: mismatched types
+  --> $DIR/does-not-have-iter.rs:8:5
    |
 LL |     quote!($(a b)*);
    |     ^^^^^^^^^^^^^^^
-   |
-   = help: message: `$` must be followed by an ident or `$` in `quote!`
+   |     |
+   |     expected `HasIterator`, found `ThereIsNoIteratorInRepetition`
+   |     expected due to this
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0308`.
diff --git a/tests/ui/proc-macro/quote/not-quotable.stderr b/tests/ui/proc-macro/quote/not-quotable.stderr
index 62a02638e54..d1c3d06f2b6 100644
--- a/tests/ui/proc-macro/quote/not-quotable.stderr
+++ b/tests/ui/proc-macro/quote/not-quotable.stderr
@@ -15,8 +15,8 @@ LL |     let _ = quote! { $ip };
              Cow<'_, T>
              Option<T>
              Rc<T>
-             bool
-           and 24 others
+             RepInterp<T>
+           and 25 others
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/proc-macro/quote/not-repeatable.rs b/tests/ui/proc-macro/quote/not-repeatable.rs
index d115da73181..0291e4ddf88 100644
--- a/tests/ui/proc-macro/quote/not-repeatable.rs
+++ b/tests/ui/proc-macro/quote/not-repeatable.rs
@@ -1,7 +1,3 @@
-// FIXME(quote): `proc_macro::quote!` doesn't support repetition at the moment, so the stderr is
-// expected to be incorrect.
-//@ known-bug: #54722
-
 #![feature(proc_macro_quote)]
 
 extern crate proc_macro;
@@ -12,5 +8,5 @@ struct Ipv4Addr;
 
 fn main() {
     let ip = Ipv4Addr;
-    let _ = quote! { $($ip)* };
+    let _ = quote! { $($ip)* }; //~ ERROR the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied
 }
diff --git a/tests/ui/proc-macro/quote/not-repeatable.stderr b/tests/ui/proc-macro/quote/not-repeatable.stderr
index 18fbcd73798..aeda08d7de6 100644
--- a/tests/ui/proc-macro/quote/not-repeatable.stderr
+++ b/tests/ui/proc-macro/quote/not-repeatable.stderr
@@ -1,10 +1,25 @@
-error: proc macro panicked
-  --> $DIR/not-repeatable.rs:15:13
+error[E0599]: the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied
+  --> $DIR/not-repeatable.rs:11:13
    |
+LL | struct Ipv4Addr;
+   | --------------- method `quote_into_iter` not found for this struct because it doesn't satisfy `Ipv4Addr: Iterator`, `Ipv4Addr: ToTokens`, `Ipv4Addr: proc_macro::ext::RepIteratorExt` or `Ipv4Addr: proc_macro::ext::RepToTokensExt`
+...
 LL |     let _ = quote! { $($ip)* };
-   |             ^^^^^^^^^^^^^^^^^^
+   |             ^^^^^^^^^^^^^^^^^^ method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds
    |
-   = help: message: `$` must be followed by an ident or `$` in `quote!`
+   = note: the following trait bounds were not satisfied:
+           `Ipv4Addr: Iterator`
+           which is required by `Ipv4Addr: proc_macro::ext::RepIteratorExt`
+           `&Ipv4Addr: Iterator`
+           which is required by `&Ipv4Addr: proc_macro::ext::RepIteratorExt`
+           `Ipv4Addr: ToTokens`
+           which is required by `Ipv4Addr: proc_macro::ext::RepToTokensExt`
+           `&mut Ipv4Addr: Iterator`
+           which is required by `&mut Ipv4Addr: proc_macro::ext::RepIteratorExt`
+note: the traits `Iterator` and `ToTokens` must be implemented
+  --> $SRC_DIR/proc_macro/src/to_tokens.rs:LL:COL
+  --> $SRC_DIR/core/src/iter/traits/iterator.rs:LL:COL
 
 error: aborting due to 1 previous error
 
+For more information about this error, try `rustc --explain E0599`.