diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs index dbb55cd9fb300..8ff691adb9c1f 100644 --- a/library/proc_macro/src/quote.rs +++ b/library/proc_macro/src/quote.rs @@ -4,6 +4,8 @@ //! This quasiquoter uses macros 2.0 hygiene to reliably access //! items from `proc_macro`, to build a `proc_macro::TokenStream`. +use core::iter::Peekable; + use crate::{ BitOr, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree, }; @@ -283,89 +285,26 @@ pub fn quote(stream: TokenStream) -> TokenStream { let proc_macro_crate = minimal_quote!(crate); let mut after_dollar = false; - let mut tokens = crate::TokenStream::new(); - let mut iter = stream.into_iter().peekable(); + let mut tokens = TokenStream::new(); + let mut iter: Peekable<::IntoIter> = 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 = 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()); + TokenTree::Group(ref tt) if tt.delimiter() == Delimiter::Parenthesis => { + consume_dollar_group_sep_star(tt.stream(), &mut iter).to_tokens(&mut tokens); + continue; + } + TokenTree::Group(_) => { minimal_quote!( - use crate::ext::*; - (@ if sep_opt.is_some() { - minimal_quote!(let mut _i = 0usize;) - } else { - minimal_quote!(();) - }) - let has_iter = crate::ThereIsNoIteratorInRepetition; + crate::ToTokens::to_tokens(&TokenTree::from(Punct::new('$', Spacing::Joint)), &mut ts); ) - .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 tokens); + minimal_quote!((@ + quote(TokenStream::from(tree)) + ).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); + .to_tokens(&mut tokens); continue; } TokenTree::Ident(_) => { @@ -373,7 +312,7 @@ pub fn quote(stream: TokenStream) -> TokenStream { .to_tokens(&mut tokens); continue; } - TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} + TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} // Escape `$` via `$$`. _ => panic!( "`$` must be followed by an ident or `$` or a repetition group in `quote!`" ), @@ -450,20 +389,274 @@ pub fn quote(stream: TokenStream) -> TokenStream { } } -/// Helper function to support macro repetitions like `$( CONTENTS ) SEP_OPT *` in `quote!`. +/// Consume a `$( CONTENTS ) SEP *` accordingly. +fn consume_dollar_group_sep_star( + content_stream: TokenStream, + sep_iter: &mut Peekable<::IntoIter>, +) -> TokenStream { + let mut tokens = TokenStream::new(); + + let mut current_contents: TokenStream = content_stream; + let mut sep_cand = Vec::new(); + + loop { + match sep_iter.peek() { + // Check for repetition `*` + Some(TokenTree::Punct(star)) if star.as_char() == '*' => { + let star_clone = star.clone(); + + // Verify it's actually a repetition star, not part of `*=` + let mut peek_iter = sep_iter.clone(); + peek_iter.next(); // Skip the `*` + let is_repetition = !matches!( + (star_clone.spacing(), peek_iter.peek()), + (Spacing::Joint, Some(TokenTree::Punct(eq))) if eq.as_char() == '=' + ); + + if is_repetition && is_valid_sep(&sep_cand) { + sep_iter.next(); // Consume the `*` + expand_dollar_group_sep_star( + &mut tokens, + current_contents, + TokenStream::from_iter(sep_cand), + ); + return tokens; + } + } + + // Check for repeated `$( CONTENTS )` pattern + Some(TokenTree::Punct(dollar)) if dollar.as_char() == '$' => { + let mut peek_iter = sep_iter.clone(); + peek_iter.next(); // Skip the `$` + if let Some(TokenTree::Group(next_contents)) = peek_iter.peek() + && next_contents.delimiter() == Delimiter::Parenthesis + // NOTE: Only a separator of a single `$` does not match this `is_valid_sep` because it is escaped via `$$`. + && is_valid_sep(&sep_cand) + { + // Output the current `$( CONTENTS ) SEP` without expansion + output_dollar_group_sep(&mut tokens, current_contents, sep_cand); + + // Move to the next `$( CONTENTS ) SEP *` + sep_iter.next(); // Consume the `$` + sep_iter.next(); // Consume the `next_contents` + current_contents = next_contents.stream(); + sep_cand = Vec::new(); + continue; + } + } + + _ => {} + } + + // Try to extend separator + if let Some(token) = sep_iter.peek() { + sep_cand.push(token.clone()); + if is_valid_sep_prefix(&sep_cand) { + sep_iter.next(); + } else { + // Can't extend separator further, backtrack and exit + sep_cand.pop(); + break; + } + } + } + + // No repetition found, output `$( CONTENTS ) SEP` without expansion + output_dollar_group_sep(&mut tokens, current_contents, sep_cand); + tokens +} + +/// Output `$( CONTENTS ) SEP` without expansion. +fn output_dollar_group_sep( + tokens: &mut TokenStream, + contents: TokenStream, + sep_cand: Vec, +) { + minimal_quote!( + crate::ToTokens::to_tokens(&TokenTree::from(Punct::new('$', Spacing::Joint)), &mut ts); + ) + .to_tokens(tokens); + + minimal_quote!( + (@ quote(TokenStream::from(TokenTree::Group(Group::new(Delimiter::Parenthesis, contents))))).to_tokens(&mut ts); + ) + .to_tokens(tokens); + + let sep = TokenStream::from_iter(sep_cand); + if !sep.is_empty() { + minimal_quote!( + (@ quote(sep)).to_tokens(&mut ts); + ) + .to_tokens(tokens); + } +} + +/// Check if a token sequence could be a valid separator prefix (for greedy matching). +fn is_valid_sep_prefix(ts: &[TokenTree]) -> bool { + // First check if it's a complete valid separator + if is_valid_sep(ts) { + return true; + } + + // Then check if it could be extended to a longer separator + match ts { + // Single punctuation that could be extended + [TokenTree::Punct(t1)] if t1.spacing() == Spacing::Joint => { + matches!( + t1.as_char(), + '.' | '<' | '>' | ':' | '+' | '&' | '^' | + '/' | '=' | '*' | '!' | '|' | '-' | '%' | '\'' | + // Escape `$` via `$$`. + '$' + ) + } + + // Two punctuation that could be extended to three + [TokenTree::Punct(t1), TokenTree::Punct(t2)] + if t1.spacing() == Spacing::Joint && t2.spacing() == Spacing::Joint => + { + matches!([t1.as_char(), t2.as_char()], ['.', '.'] | ['<', '<'] | ['>', '>']) + } + + _ => false, + } +} + +/// Determine if the given token sequence is a complete valid separator. +fn is_valid_sep(ts: &[TokenTree]) -> bool { + match ts { + [TokenTree::Punct(t1), TokenTree::Punct(t2), TokenTree::Punct(t3)] + if t1.spacing() == Spacing::Joint && t2.spacing() == Spacing::Joint => + { + matches!( + [t1.as_char(), t2.as_char(), t3.as_char()], + ['.', '.', '.'] | ['.', '.', '='] | ['<', '<', '='] | ['>', '>', '='] + ) + } + [TokenTree::Punct(t1), TokenTree::Punct(t2)] + if t1.spacing() == Spacing::Joint => + { + matches!( + [t1.as_char(), t2.as_char()], + [':', ':'] | ['+', '='] | ['&', '&'] | ['&', '='] | ['^', '='] | ['/', '='] + | ['.', '.'] | ['=', '='] | ['>', '='] | ['<', '='] | ['*', '='] | ['!', '='] + | ['|', '='] | ['|', '|'] | ['-', '>'] | ['<', '-'] | ['%', '='] | ['=', '>'] + | ['<', '<'] | ['>', '>'] | ['-', '='] | + // Escape `$` via `$$`. + ['$', '$'] + ) + } + [TokenTree::Punct(single_quote), TokenTree::Ident(_)] // lifetime + if single_quote.as_char() == '\'' && single_quote.spacing() == Spacing::Joint => + { + true + } + [TokenTree::Punct(t1)] => + { + matches!( + t1.as_char(), + // The LEGAL_CHARS except '\'' are available here. + '#' | ',' | '.' | ';' | ':' | '+' | '@' | '!' | '^' | '&' | '/' | + '=' | '>' | '<' | '|' | '?' | '%' | '*' | '-' | '_' | '~' + ) + } + [TokenTree::Ident(_)] | [TokenTree::Group(_)] | [TokenTree::Literal(_)] => true, + [] => true, // Empty separator is valid + _ => false, + } +} + +/// Handle repetition by expanding `$( CONTENTS ) SEP *` to `{ REP_EXPANDED }`. +fn expand_dollar_group_sep_star(tokens: &mut TokenStream, contents: TokenStream, sep: TokenStream) { + let mut rep_expanded = TokenStream::new(); + let meta_vars = collect_meta_vars(contents.clone()); + + // Construct setup code for the `while_loop` where recursively quoted `CONTENTS` and `SEP` are repeatedly processed + minimal_quote!( + use crate::ext::*; + (@ if !sep.is_empty() { + Some(minimal_quote!(let mut _i = 0usize;)) + } else { + None + }) + 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); + + // Construct the `while_loop` + 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 !sep.is_empty() { + Some(minimal_quote!( + if _i > 0 { + (@ + quote(TokenTree::Group(Group::new(Delimiter::None, sep)).into()) // Adjust spacing by Delimiter::None + ).to_tokens(&mut ts); + } + _i += 1; + )) + } else { + None + }) + (@ quote(contents)).to_tokens(&mut ts); + ) + .to_tokens(&mut while_body); + let while_loop = 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)), + ]; + + rep_expanded.extend(while_loop); + minimal_quote!((@ TokenTree::Group(Group::new(Delimiter::Brace, rep_expanded)))) + .to_tokens(tokens); +} + +/// Helper function to support macro repetitions like `$( CONTENTS ) SEP *` 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 { fn helper(stream: TokenStream, out: &mut Vec) { + let mut after_dollar = false; + 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() { + while let Some(ref tree) = iter.next() { + if after_dollar { + after_dollar = false; + match tree { + TokenTree::Ident(id) => { out.push(id.clone()); - iter.next(); + continue; } + // TokenTree::Punct(tt) if tt.as_char() == '$' => {} // Escape `$` via `$$`. + _ => {} } + } else if let TokenTree::Punct(tt) = tree + && tt.as_char() == '$' + { + after_dollar = true; + continue; + } + + match tree { TokenTree::Group(tt) => { helper(tt.stream(), out); } diff --git a/tests/ui/proc-macro/quote/auxiliary/basic.rs b/tests/ui/proc-macro/quote/auxiliary/basic.rs index c50bb964eab06..3b200e6db3984 100644 --- a/tests/ui/proc-macro/quote/auxiliary/basic.rs +++ b/tests/ui/proc-macro/quote/auxiliary/basic.rs @@ -11,6 +11,15 @@ use proc_macro::*; #[proc_macro] pub fn run_tests(_: TokenStream) -> TokenStream { + test_sep1(); + test_sep2(); + test_sep_group1(); + test_sep_group2(); + test_dollar_dollar1(); + test_dollar_dollar2(); + test_dollar_dollar3(); + test_dollar_dollar4(); + test_quote_impl(); test_substitution(); test_iter(); @@ -50,6 +59,75 @@ pub fn run_tests(_: TokenStream) -> TokenStream { TokenStream::new() } +fn test_sep1() { + let iter = ["a", "b"].into_iter(); + let tokens = quote!($($iter) << *); + + let expected = "\"a\" << \"b\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_sep2() { + let iter1 = ["a", "b"].into_iter(); + let iter2 = [1, 2, 3]; + let tokens = quote!($($iter1) ($($iter1) $($iter2),* A) *); + + let expected = "\"a\" ($(\"b\") 1i32 , 2i32 , 3i32 A) \"b\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_sep_group1() { + let x = "X"; + let iter = ["a", "b"].into_iter(); + let tokens = quote!($($x) >> $($iter) << *); + + let expected = "$(\"X\") >> \"a\" << \"b\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_sep_group2() { + let x = "X"; + let iter = ["a", "b"].into_iter(); + let tokens = quote!($($x) >> > $($iter) << *); + + let expected = "$(\"X\") >> > \"a\" << \"b\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_dollar_dollar1() { + let iter = ["a", "b"].into_iter(); + let tokens = quote!($$ x $($$ x $iter)*); + + let expected = "$x $x \"a\" $x \"b\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_dollar_dollar2() { + let iter = ["a", "b", "c"].into_iter(); + let tokens = quote!($($iter) $$ *); + + let expected = "\"a\" $ \"b\" $ \"c\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_dollar_dollar3() { + let x = "X"; + let iter = ["a", "b", "c"].into_iter(); + let tokens = quote!($($x)$$($x),*); + + let expected = "$(\"X\") $ (\"X\"),*"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_dollar_dollar4() { + let x = "X"; + let iter = ["a", "b", "c"].into_iter(); + let tokens = quote!($($x)$$y$($iter)*); + + let expected = "$(\"X\") $y \"a\" \"b\" \"c\""; + assert_eq!(expected, tokens.to_string()); +} + // Based on https://github.com/dtolnay/quote/blob/0245506323a3616daa2ee41c6ad0b871e4d78ae4/tests/test.rs // // FIXME(quote): @@ -106,7 +184,7 @@ fn test_iter() { 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() { @@ -320,7 +398,7 @@ fn test_fancy_repetition() { $($foo: $bar),* }; - let expected = r#""a" : true, "b" : false"#; + let expected = r#""a" : true , "b" : false"#; assert_eq!(expected, tokens.to_string()); } @@ -333,7 +411,7 @@ fn test_nested_fancy_repetition() { ),* }; - let expected = "'a' 'b' 'c', 'x' 'y' 'z'"; + let expected = "'a' 'b' 'c' , 'x' 'y' 'z'"; assert_eq!(expected, tokens.to_string()); } @@ -345,7 +423,7 @@ fn test_duplicate_name_repetition() { $($foo: $foo),* }; - let expected = r#""a" : "a", "b" : "b" "a" : "a", "b" : "b""#; + let expected = r#""a" : "a" , "b" : "b" "a" : "a" , "b" : "b""#; assert_eq!(expected, tokens.to_string()); } @@ -356,7 +434,7 @@ fn test_duplicate_name_repetition_no_copy() { $($foo: $foo),* }; - let expected = r#""a" : "a", "b" : "b""#; + let expected = r#""a" : "a" , "b" : "b""#; assert_eq!(expected, tokens.to_string()); } @@ -369,7 +447,7 @@ fn test_btreeset_repetition() { $($set: $set),* }; - let expected = r#""a" : "a", "b" : "b""#; + let expected = r#""a" : "a" , "b" : "b""#; assert_eq!(expected, tokens.to_string()); } @@ -378,7 +456,7 @@ fn test_variable_name_conflict() { // fine, if a little confusing when debugging. let _i = vec!['a', 'b']; let tokens = quote! { $($_i),* }; - let expected = "'a', 'b'"; + let expected = "'a' , 'b'"; assert_eq!(expected, tokens.to_string()); } @@ -390,7 +468,7 @@ fn test_nonrep_in_repetition() { $($rep $rep : $nonrep $nonrep),* }; - let expected = r#""a" "a" : "c" "c", "b" "b" : "c" "c""#; + let expected = r#""a" "a" : "c" "c" , "b" "b" : "c" "c""#; assert_eq!(expected, tokens.to_string()); } diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.rs b/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.rs new file mode 100644 index 0000000000000..0d988d6cc954b --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.rs @@ -0,0 +1,10 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::quote; + +fn main() { + let arr = [1, 2, 3]; + quote! { ${$arr}* }; //~ ERROR the trait bound `[{integer}; 3]: ToTokens` is not satisfied [E0277] +} diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.stderr b/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.stderr new file mode 100644 index 0000000000000..a175ca8401dcc --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-brace.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `[{integer}; 3]: ToTokens` is not satisfied + --> $DIR/not-quotable-repetition-group-brace.rs:9:5 + | +LL | quote! { ${$arr}* }; + | ^^^^^^^^^^^^^^^^^^^ + | | + | the trait `ToTokens` is not implemented for `[{integer}; 3]` + | required by a bound introduced by this call + | + = help: the following other types implement trait `ToTokens`: + &T + &mut T + Box + CString + Cow<'_, T> + Option + Rc + bool + and 24 others + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.rs b/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.rs new file mode 100644 index 0000000000000..ccfb91132ed04 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.rs @@ -0,0 +1,10 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::quote; + +fn main() { + let arr = [1, 2, 3]; + quote! { $[$arr]* }; //~ ERROR the trait bound `[{integer}; 3]: ToTokens` is not satisfied [E0277] +} diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.stderr b/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.stderr new file mode 100644 index 0000000000000..2ea626a70978d --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-bracket.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `[{integer}; 3]: ToTokens` is not satisfied + --> $DIR/not-quotable-repetition-group-bracket.rs:9:5 + | +LL | quote! { $[$arr]* }; + | ^^^^^^^^^^^^^^^^^^^ + | | + | the trait `ToTokens` is not implemented for `[{integer}; 3]` + | required by a bound introduced by this call + | + = help: the following other types implement trait `ToTokens`: + &T + &mut T + Box + CString + Cow<'_, T> + Option + Rc + bool + and 24 others + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.rs b/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.rs new file mode 100644 index 0000000000000..3440247da899f --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.rs @@ -0,0 +1,16 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::quote; + +macro_rules! do_quote { + ($dollar:tt $content:expr) => { + proc_macro::quote!($dollar $content *) //~ ERROR the trait bound `[{integer}; 3]: ToTokens` is not satisfied [E0277] + }; +} + +fn main() { + let arr = [1, 2, 3]; + do_quote!($ f!($arr)); +} diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.stderr b/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.stderr new file mode 100644 index 0000000000000..f6d5cf9597e81 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-group-none.stderr @@ -0,0 +1,27 @@ +error[E0277]: the trait bound `[{integer}; 3]: ToTokens` is not satisfied + --> $DIR/not-quotable-repetition-group-none.rs:9:9 + | +LL | proc_macro::quote!($dollar $content *) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `ToTokens` is not implemented for `[{integer}; 3]` +... +LL | do_quote!($ f!($arr)); + | --------------------- + | | + | required by a bound introduced by this call + | in this macro invocation + | + = help: the following other types implement trait `ToTokens`: + &T + &mut T + Box + CString + Cow<'_, T> + Option + Rc + bool + and 24 others + = note: this error originates in the macro `proc_macro::quote` which comes from the expansion of the macro `do_quote` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.rs b/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.rs new file mode 100644 index 0000000000000..6796f7a5ca704 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.rs @@ -0,0 +1,10 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use proc_macro::quote; + +fn main() { + let arr = [1, 2, 3]; + quote! { $($arr) $$ x .. false [] * }; //~ ERROR the trait bound `[{integer}; 3]: ToTokens` is not satisfied [E0277] +} diff --git a/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.stderr b/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.stderr new file mode 100644 index 0000000000000..717012c0b1281 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable-repetition-invalid-separator.stderr @@ -0,0 +1,23 @@ +error[E0277]: the trait bound `[{integer}; 3]: ToTokens` is not satisfied + --> $DIR/not-quotable-repetition-invalid-separator.rs:9:5 + | +LL | quote! { $($arr) $$ x .. false [] * }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | the trait `ToTokens` is not implemented for `[{integer}; 3]` + | required by a bound introduced by this call + | + = help: the following other types implement trait `ToTokens`: + &T + &mut T + Box + CString + Cow<'_, T> + Option + Rc + bool + and 24 others + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`.