From 2a2e87b398c677062280254f6c5db8757e4b7ce3 Mon Sep 17 00:00:00 2001 From: Asuna Date: Wed, 1 Jan 2025 18:51:39 +0100 Subject: [PATCH 1/5] Rename the internal simpler `quote` macro to `minimal_quote` --- library/proc_macro/src/quote.rs | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs index 04fa696d5e6be..6faa41b297005 100644 --- a/library/proc_macro/src/quote.rs +++ b/library/proc_macro/src/quote.rs @@ -6,10 +6,10 @@ use crate::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; -macro_rules! quote_tt { - (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) }; - ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) }; - ({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) }; +macro_rules! minimal_quote_tt { + (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) }; + ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) }; + ({$($t:tt)*}) => { Group::new(Delimiter::Brace, minimal_quote!($($t)*)) }; (,) => { Punct::new(',', Spacing::Alone) }; (.) => { Punct::new('.', Spacing::Alone) }; (;) => { Punct::new(';', Spacing::Alone) }; @@ -21,7 +21,7 @@ macro_rules! quote_tt { ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) }; } -macro_rules! quote_ts { +macro_rules! minimal_quote_ts { ((@ $($t:tt)*)) => { $($t)* }; (::) => { [ @@ -35,7 +35,7 @@ macro_rules! quote_ts { }) .collect::() }; - ($t:tt) => { TokenTree::from(quote_tt!($t)) }; + ($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) }; } /// Simpler version of the real `quote!` macro, implemented solely @@ -46,11 +46,11 @@ macro_rules! quote_ts { /// /// Note: supported tokens are a subset of the real `quote!`, but /// unquoting is different: instead of `$x`, this uses `(@ expr)`. -macro_rules! quote { +macro_rules! minimal_quote { () => { TokenStream::new() }; ($($t:tt)*) => { [ - $(TokenStream::from(quote_ts!($t)),)* + $(TokenStream::from(minimal_quote_ts!($t)),)* ].iter().cloned().collect::() }; } @@ -62,9 +62,9 @@ macro_rules! quote { #[unstable(feature = "proc_macro_quote", issue = "54722")] pub fn quote(stream: TokenStream) -> TokenStream { if stream.is_empty() { - return quote!(crate::TokenStream::new()); + return minimal_quote!(crate::TokenStream::new()); } - let proc_macro_crate = quote!(crate); + let proc_macro_crate = minimal_quote!(crate); let mut after_dollar = false; let tokens = stream .into_iter() @@ -73,7 +73,7 @@ pub fn quote(stream: TokenStream) -> TokenStream { after_dollar = false; match tree { TokenTree::Ident(_) => { - return Some(quote!(Into::::into( + return Some(minimal_quote!(Into::::into( Clone::clone(&(@ tree))),)); } TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} @@ -86,28 +86,28 @@ pub fn quote(stream: TokenStream) -> TokenStream { } } - Some(quote!(crate::TokenStream::from((@ match tree { - TokenTree::Punct(tt) => quote!(crate::TokenTree::Punct(crate::Punct::new( + Some(minimal_quote!(crate::TokenStream::from((@ match tree { + TokenTree::Punct(tt) => minimal_quote!(crate::TokenTree::Punct(crate::Punct::new( (@ TokenTree::from(Literal::character(tt.as_char()))), (@ match tt.spacing() { - Spacing::Alone => quote!(crate::Spacing::Alone), - Spacing::Joint => quote!(crate::Spacing::Joint), + Spacing::Alone => minimal_quote!(crate::Spacing::Alone), + Spacing::Joint => minimal_quote!(crate::Spacing::Joint), }), ))), - TokenTree::Group(tt) => quote!(crate::TokenTree::Group(crate::Group::new( + TokenTree::Group(tt) => minimal_quote!(crate::TokenTree::Group(crate::Group::new( (@ match tt.delimiter() { - Delimiter::Parenthesis => quote!(crate::Delimiter::Parenthesis), - Delimiter::Brace => quote!(crate::Delimiter::Brace), - Delimiter::Bracket => quote!(crate::Delimiter::Bracket), - Delimiter::None => quote!(crate::Delimiter::None), + Delimiter::Parenthesis => minimal_quote!(crate::Delimiter::Parenthesis), + Delimiter::Brace => minimal_quote!(crate::Delimiter::Brace), + Delimiter::Bracket => minimal_quote!(crate::Delimiter::Bracket), + Delimiter::None => minimal_quote!(crate::Delimiter::None), }), (@ quote(tt.stream())), ))), - TokenTree::Ident(tt) => quote!(crate::TokenTree::Ident(crate::Ident::new( + TokenTree::Ident(tt) => minimal_quote!(crate::TokenTree::Ident(crate::Ident::new( (@ TokenTree::from(Literal::string(&tt.to_string()))), (@ quote_span(proc_macro_crate.clone(), tt.span())), ))), - TokenTree::Literal(tt) => quote!(crate::TokenTree::Literal({ + TokenTree::Literal(tt) => minimal_quote!(crate::TokenTree::Literal({ let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string()))) .parse::() .unwrap() @@ -129,7 +129,7 @@ pub fn quote(stream: TokenStream) -> TokenStream { panic!("unexpected trailing `$` in `quote!`"); } - quote!([(@ tokens)].iter().cloned().collect::()) + minimal_quote!([(@ tokens)].iter().cloned().collect::()) } /// Quote a `Span` into a `TokenStream`. @@ -137,5 +137,5 @@ pub fn quote(stream: TokenStream) -> TokenStream { #[unstable(feature = "proc_macro_quote", issue = "54722")] pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream { let id = span.save_span(); - quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id))))) + minimal_quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id))))) } From d52564118b2e3f7140e911dec4021decda6493e8 Mon Sep 17 00:00:00 2001 From: Asuna Date: Sun, 5 Jan 2025 23:47:32 +0100 Subject: [PATCH 2/5] Append `TokenTree` with `ToTokens` in `proc_macro::quote!` --- library/proc_macro/src/lib.rs | 2 +- library/proc_macro/src/quote.rs | 109 ++++++++++++++----------- tests/ui/proc-macro/quote-debug.stdout | 42 +++++----- 3 files changed, 87 insertions(+), 66 deletions(-) diff --git a/library/proc_macro/src/lib.rs b/library/proc_macro/src/lib.rs index 26a09f0daca02..b19c9cee75a0b 100644 --- a/library/proc_macro/src/lib.rs +++ b/library/proc_macro/src/lib.rs @@ -419,7 +419,7 @@ pub mod token_stream { /// Unquoting is done with `$`, and works by taking the single next ident as the unquoted term. /// To quote `$` itself, use `$$`. #[unstable(feature = "proc_macro_quote", issue = "54722")] -#[allow_internal_unstable(proc_macro_def_site, proc_macro_internals)] +#[allow_internal_unstable(proc_macro_def_site, proc_macro_internals, proc_macro_totokens)] #[rustc_builtin_macro] pub macro quote($($t:tt)*) { /* compiler built-in */ diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs index 6faa41b297005..27c8458a351ca 100644 --- a/library/proc_macro/src/quote.rs +++ b/library/proc_macro/src/quote.rs @@ -4,7 +4,9 @@ //! This quasiquoter uses macros 2.0 hygiene to reliably access //! items from `proc_macro`, to build a `proc_macro::TokenStream`. -use crate::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; +use crate::{ + Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree, +}; macro_rules! minimal_quote_tt { (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) }; @@ -24,16 +26,15 @@ macro_rules! minimal_quote_tt { macro_rules! minimal_quote_ts { ((@ $($t:tt)*)) => { $($t)* }; (::) => { - [ - TokenTree::from(Punct::new(':', Spacing::Joint)), - TokenTree::from(Punct::new(':', Spacing::Alone)), - ].iter() - .cloned() - .map(|mut x| { - x.set_span(Span::def_site()); - x - }) - .collect::() + { + 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::() + } }; ($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) }; } @@ -47,11 +48,13 @@ macro_rules! minimal_quote_ts { /// Note: supported tokens are a subset of the real `quote!`, but /// unquoting is different: instead of `$x`, this uses `(@ expr)`. macro_rules! minimal_quote { - () => { TokenStream::new() }; ($($t:tt)*) => { - [ - $(TokenStream::from(minimal_quote_ts!($t)),)* - ].iter().cloned().collect::() + { + #[allow(unused_mut)] // In case the expansion is empty + let mut ts = TokenStream::new(); + $(ToTokens::to_tokens(&minimal_quote_ts!($t), &mut ts);)* + ts + } }; } @@ -66,35 +69,39 @@ pub fn quote(stream: TokenStream) -> TokenStream { } let proc_macro_crate = minimal_quote!(crate); let mut after_dollar = false; - let tokens = stream - .into_iter() - .filter_map(|tree| { - if after_dollar { - after_dollar = false; - match tree { - TokenTree::Ident(_) => { - return Some(minimal_quote!(Into::::into( - Clone::clone(&(@ tree))),)); - } - TokenTree::Punct(ref tt) if tt.as_char() == '$' => {} - _ => panic!("`$` must be followed by an ident or `$` in `quote!`"), - } - } else if let TokenTree::Punct(ref tt) = tree { - if tt.as_char() == '$' { - after_dollar = true; - return None; + + let mut tokens = crate::TokenStream::new(); + for tree in stream { + if after_dollar { + after_dollar = false; + match tree { + 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!`"), } + } else if let TokenTree::Punct(ref tt) = tree { + if tt.as_char() == '$' { + after_dollar = true; + continue; + } + } - Some(minimal_quote!(crate::TokenStream::from((@ match tree { - TokenTree::Punct(tt) => minimal_quote!(crate::TokenTree::Punct(crate::Punct::new( + match tree { + TokenTree::Punct(tt) => { + minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new( (@ TokenTree::from(Literal::character(tt.as_char()))), (@ match tt.spacing() { Spacing::Alone => minimal_quote!(crate::Spacing::Alone), Spacing::Joint => minimal_quote!(crate::Spacing::Joint), }), - ))), - TokenTree::Group(tt) => minimal_quote!(crate::TokenTree::Group(crate::Group::new( + )), &mut ts);) + } + TokenTree::Group(tt) => { + minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Group(crate::Group::new( (@ match tt.delimiter() { Delimiter::Parenthesis => minimal_quote!(crate::Delimiter::Parenthesis), Delimiter::Brace => minimal_quote!(crate::Delimiter::Brace), @@ -102,12 +109,16 @@ pub fn quote(stream: TokenStream) -> TokenStream { Delimiter::None => minimal_quote!(crate::Delimiter::None), }), (@ quote(tt.stream())), - ))), - TokenTree::Ident(tt) => minimal_quote!(crate::TokenTree::Ident(crate::Ident::new( + )), &mut ts);) + } + TokenTree::Ident(tt) => { + minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new( (@ TokenTree::from(Literal::string(&tt.to_string()))), (@ quote_span(proc_macro_crate.clone(), tt.span())), - ))), - TokenTree::Literal(tt) => minimal_quote!(crate::TokenTree::Literal({ + )), &mut ts);) + } + TokenTree::Literal(tt) => { + minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Literal({ let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string()))) .parse::() .unwrap() @@ -120,16 +131,22 @@ pub fn quote(stream: TokenStream) -> TokenStream { } else { unreachable!() } - })) - })),)) - }) - .collect::(); - + }), &mut ts);) + } + } + .to_tokens(&mut tokens); + } if after_dollar { panic!("unexpected trailing `$` in `quote!`"); } - minimal_quote!([(@ tokens)].iter().cloned().collect::()) + minimal_quote! { + { + let mut ts = crate::TokenStream::new(); + (@ tokens) + ts + } + } } /// Quote a `Span` into a `TokenStream`. diff --git a/tests/ui/proc-macro/quote-debug.stdout b/tests/ui/proc-macro/quote-debug.stdout index d84b4e051e872..583599ff97524 100644 --- a/tests/ui/proc-macro/quote-debug.stdout +++ b/tests/ui/proc-macro/quote-debug.stdout @@ -19,25 +19,29 @@ extern crate std; extern crate proc_macro; fn main() { - [crate::TokenStream::from(crate::TokenTree::Ident(crate::Ident::new("let", - crate::Span::recover_proc_macro_span(0)))), - crate::TokenStream::from(crate::TokenTree::Ident(crate::Ident::new("hello", - crate::Span::recover_proc_macro_span(1)))), - crate::TokenStream::from(crate::TokenTree::Punct(crate::Punct::new('=', - crate::Spacing::Alone))), - crate::TokenStream::from(crate::TokenTree::Literal({ - let mut iter = - "\"world\"".parse::().unwrap().into_iter(); - if let (Some(crate::TokenTree::Literal(mut lit)), None) = - (iter.next(), iter.next()) { - lit.set_span(crate::Span::recover_proc_macro_span(2)); - lit - } else { - ::core::panicking::panic("internal error: entered unreachable code") - } - })), - crate::TokenStream::from(crate::TokenTree::Punct(crate::Punct::new(';', - crate::Spacing::Alone)))].iter().cloned().collect::() + { + let mut ts = crate::TokenStream::new(); + crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new("let", + crate::Span::recover_proc_macro_span(0))), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new("hello", + crate::Span::recover_proc_macro_span(1))), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new('=', + crate::Spacing::Alone)), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Literal({ + let mut iter = + "\"world\"".parse::().unwrap().into_iter(); + if let (Some(crate::TokenTree::Literal(mut lit)), None) = + (iter.next(), iter.next()) { + lit.set_span(crate::Span::recover_proc_macro_span(2)); + lit + } else { + ::core::panicking::panic("internal error: entered unreachable code") + } + }), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(';', + crate::Spacing::Alone)), &mut ts); + ts + } } const _: () = { From e9063c36a95f032b2f504e30fa19b22bc88c2dcd Mon Sep 17 00:00:00 2001 From: Asuna Date: Wed, 8 Jan 2025 23:36:09 +0100 Subject: [PATCH 3/5] Migrate basic tests for `proc_macro::quote!` from `quote` crate --- tests/ui/proc-macro/quote/auxiliary/basic.rs | 360 ++++++++++++++++++ tests/ui/proc-macro/quote/basic.rs | 8 + .../{quote-debug.rs => quote/debug.rs} | 0 .../debug.stdout} | 0 4 files changed, 368 insertions(+) create mode 100644 tests/ui/proc-macro/quote/auxiliary/basic.rs create mode 100644 tests/ui/proc-macro/quote/basic.rs rename tests/ui/proc-macro/{quote-debug.rs => quote/debug.rs} (100%) rename tests/ui/proc-macro/{quote-debug.stdout => quote/debug.stdout} (100%) diff --git a/tests/ui/proc-macro/quote/auxiliary/basic.rs b/tests/ui/proc-macro/quote/auxiliary/basic.rs new file mode 100644 index 0000000000000..67927e5f6a8a0 --- /dev/null +++ b/tests/ui/proc-macro/quote/auxiliary/basic.rs @@ -0,0 +1,360 @@ +#![feature(proc_macro_quote)] +#![feature(proc_macro_totokens)] + +extern crate proc_macro; + +use std::borrow::Cow; +use std::ffi::{CStr, CString}; + +use proc_macro::*; + +#[proc_macro] +pub fn run_tests(_: TokenStream) -> TokenStream { + test_quote_impl(); + test_substitution(); + test_advanced(); + test_integer(); + test_floating(); + test_char(); + test_str(); + test_string(); + test_c_str(); + test_c_string(); + test_interpolated_literal(); + test_ident(); + test_underscore(); + test_duplicate(); + test_empty_quote(); + test_box_str(); + test_cow(); + test_append_tokens(); + test_outer_line_comment(); + test_inner_line_comment(); + test_outer_block_comment(); + test_inner_block_comment(); + test_outer_attr(); + test_inner_attr(); + // test_quote_raw_id(); // FIXME: Fix it in a subsequent commit + + TokenStream::new() +} + +// Based on https://github.com/dtolnay/quote/blob/0245506323a3616daa2ee41c6ad0b871e4d78ae4/tests/test.rs +// +// FIXME(quote): +// The following tests are removed because they are not supported yet in `proc_macro::quote!` +// +// - quote_spanned: +// - fn test_quote_spanned_impl +// - fn test_type_inference_for_span +// - format_ident: +// - 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; + +impl ToTokens for X { + fn to_tokens(&self, tokens: &mut TokenStream) { + Ident::new("X", Span::call_site()).to_tokens(tokens) + } +} + +fn test_quote_impl() { + let tokens = quote! { + impl<'a, T: ToTokens> ToTokens for &'a T { + fn to_tokens(&self, tokens: &mut TokenStream) { + (**self).to_tokens(tokens) + } + } + }; + + let expected = r#"impl < 'a, T : ToTokens > ToTokens for & 'a T +{ + fn to_tokens(& self, tokens : & mut TokenStream) + { (** self).to_tokens(tokens) } +}"#; + + assert_eq!(expected, tokens.to_string()); +} + +fn test_substitution() { + let x = X; + let tokens = quote!($x <$x> ($x) [$x] {$x}); + + let expected = "X (X) [X] { X }"; + + assert_eq!(expected, tokens.to_string()); +} + +fn test_advanced() { + let generics = quote!( <'a, T> ); + + let where_clause = quote!( where T: Serialize ); + + let field_ty = quote!(String); + + let item_ty = quote!(Cow<'a, str>); + + let path = quote!(SomeTrait::serialize_with); + + let value = quote!(self.x); + + let tokens = quote! { + struct SerializeWith $generics $where_clause { + value: &'a $field_ty, + phantom: ::std::marker::PhantomData<$item_ty>, + } + + impl $generics ::serde::Serialize for SerializeWith $generics $where_clause { + fn serialize(&self, s: &mut S) -> Result<(), S::Error> + where S: ::serde::Serializer + { + $path(self.value, s) + } + } + + SerializeWith { + value: $value, + phantom: ::std::marker::PhantomData::<$item_ty>, + } + }; + + let expected = r#"struct SerializeWith < 'a, T > where T : Serialize +{ + value : & 'a String, phantom : :: std :: marker :: PhantomData >, +} impl < 'a, T > :: serde :: Serialize for SerializeWith < 'a, T > where T : +Serialize +{ + fn serialize < S > (& self, s : & mut S) -> Result < (), S :: Error > + where S : :: serde :: Serializer + { SomeTrait :: serialize_with(self.value, s) } +} SerializeWith +{ + value : self.x, phantom : :: std :: marker :: PhantomData :: >, +}"#; + + assert_eq!(expected, tokens.to_string()); +} + +fn test_integer() { + let ii8 = -1i8; + let ii16 = -1i16; + let ii32 = -1i32; + let ii64 = -1i64; + let ii128 = -1i128; + let iisize = -1isize; + let uu8 = 1u8; + let uu16 = 1u16; + let uu32 = 1u32; + let uu64 = 1u64; + let uu128 = 1u128; + let uusize = 1usize; + + let tokens = quote! { + 1 1i32 1u256 + $ii8 $ii16 $ii32 $ii64 $ii128 $iisize + $uu8 $uu16 $uu32 $uu64 $uu128 $uusize + }; + let expected = r#"1 1i32 1u256 -1i8 -1i16 -1i32 -1i64 -1i128 -1isize 1u8 1u16 1u32 1u64 1u128 +1usize"#; + assert_eq!(expected, tokens.to_string()); +} + +fn test_floating() { + let e32 = 2.345f32; + + let e64 = 2.345f64; + + let tokens = quote! { + $e32 + $e64 + }; + let expected = concat!("2.345f32 2.345f64"); + assert_eq!(expected, tokens.to_string()); +} + +fn test_char() { + let zero = '\u{1}'; + let dollar = '$'; + let pound = '#'; + let quote = '"'; + let apost = '\''; + let newline = '\n'; + let heart = '\u{2764}'; + + let tokens = quote! { + $zero $dollar $pound $quote $apost $newline $heart + }; + let expected = "'\\u{1}' '$' '#' '\"' '\\'' '\\n' '\u{2764}'"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_str() { + let s = "\u{1} a 'b \" c"; + let tokens = quote!($s); + let expected = "\"\\u{1} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_string() { + let s = "\u{1} a 'b \" c".to_string(); + let tokens = quote!($s); + let expected = "\"\\u{1} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_c_str() { + let s = CStr::from_bytes_with_nul(b"\x01 a 'b \" c\0").unwrap(); + let tokens = quote!($s); + let expected = "c\"\\u{1} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_c_string() { + let s = CString::new(&b"\x01 a 'b \" c"[..]).unwrap(); + let tokens = quote!($s); + let expected = "c\"\\u{1} a 'b \\\" c\""; + assert_eq!(expected, tokens.to_string()); +} + +fn test_interpolated_literal() { + macro_rules! m { + ($literal:literal) => { + quote!($literal) + }; + } + + let tokens = m!(1); + let expected = "1"; + assert_eq!(expected, tokens.to_string()); + + let tokens = m!(-1); + let expected = "- 1"; + assert_eq!(expected, tokens.to_string()); + + let tokens = m!(true); + let expected = "true"; + assert_eq!(expected, tokens.to_string()); + + let tokens = m!(-true); + let expected = "- true"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_ident() { + let foo = Ident::new("Foo", Span::call_site()); + let bar = Ident::new(&format!("Bar{}", 7), Span::call_site()); + let tokens = quote!(struct $foo; enum $bar {}); + let expected = "struct Foo; enum Bar7 {}"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_underscore() { + let tokens = quote!(let _;); + let expected = "let _;"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_duplicate() { + let ch = 'x'; + + let tokens = quote!($ch $ch); + + let expected = "'x' 'x'"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_empty_quote() { + let tokens = quote!(); + assert_eq!("", tokens.to_string()); +} + +fn test_box_str() { + let b = "str".to_owned().into_boxed_str(); + let tokens = quote! { $b }; + assert_eq!("\"str\"", tokens.to_string()); +} + +fn test_cow() { + let owned: Cow = Cow::Owned(Ident::new("owned", Span::call_site())); + + let ident = Ident::new("borrowed", Span::call_site()); + let borrowed = Cow::Borrowed(&ident); + + let tokens = quote! { $owned $borrowed }; + assert_eq!("owned borrowed", tokens.to_string()); +} + +fn test_append_tokens() { + let mut a = quote!(a); + let b = quote!(b); + a.extend(b); + assert_eq!("a b", a.to_string()); +} + +fn test_outer_line_comment() { + let tokens = quote! { + /// doc + }; + let expected = "#[doc = \" doc\"]"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_inner_line_comment() { + let tokens = quote! { + //! doc + }; + let expected = "# ! [doc = \" doc\"]"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_outer_block_comment() { + let tokens = quote! { + /** doc */ + }; + let expected = "#[doc = \" doc \"]"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_inner_block_comment() { + let tokens = quote! { + /*! doc */ + }; + let expected = "# ! [doc = \" doc \"]"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_outer_attr() { + let tokens = quote! { + #[inline] + }; + let expected = "#[inline]"; + assert_eq!(expected, tokens.to_string()); +} + +fn test_inner_attr() { + let tokens = quote! { + #![no_std] + }; + let expected = "#! [no_std]"; + 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/basic.rs b/tests/ui/proc-macro/quote/basic.rs new file mode 100644 index 0000000000000..0336dbb785623 --- /dev/null +++ b/tests/ui/proc-macro/quote/basic.rs @@ -0,0 +1,8 @@ +//@ run-pass +//@ proc-macro: basic.rs + +extern crate basic; + +fn main() { + basic::run_tests!(); +} diff --git a/tests/ui/proc-macro/quote-debug.rs b/tests/ui/proc-macro/quote/debug.rs similarity index 100% rename from tests/ui/proc-macro/quote-debug.rs rename to tests/ui/proc-macro/quote/debug.rs diff --git a/tests/ui/proc-macro/quote-debug.stdout b/tests/ui/proc-macro/quote/debug.stdout similarity index 100% rename from tests/ui/proc-macro/quote-debug.stdout rename to tests/ui/proc-macro/quote/debug.stdout From 2ab9db5442a5a3f7f80cd8cbdf493e56270f7877 Mon Sep 17 00:00:00 2001 From: Asuna Date: Thu, 9 Jan 2025 02:29:02 +0100 Subject: [PATCH 4/5] Migrate check-fail tests for `proc_macro::quote!` from `quote` crate --- tests/ui/proc-macro/quote/auxiliary/basic.rs | 1 + .../does-not-have-iter-interpolated-dup.rs | 17 +++++++++++++ ...does-not-have-iter-interpolated-dup.stderr | 10 ++++++++ .../quote/does-not-have-iter-interpolated.rs | 17 +++++++++++++ .../does-not-have-iter-interpolated.stderr | 10 ++++++++ .../quote/does-not-have-iter-separated.rs | 13 ++++++++++ .../quote/does-not-have-iter-separated.stderr | 10 ++++++++ .../ui/proc-macro/quote/does-not-have-iter.rs | 13 ++++++++++ .../quote/does-not-have-iter.stderr | 10 ++++++++ tests/ui/proc-macro/quote/not-quotable.rs | 12 ++++++++++ tests/ui/proc-macro/quote/not-quotable.stderr | 24 +++++++++++++++++++ tests/ui/proc-macro/quote/not-repeatable.rs | 16 +++++++++++++ .../ui/proc-macro/quote/not-repeatable.stderr | 10 ++++++++ 13 files changed, 163 insertions(+) create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-separated.rs create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter.rs create mode 100644 tests/ui/proc-macro/quote/does-not-have-iter.stderr create mode 100644 tests/ui/proc-macro/quote/not-quotable.rs create mode 100644 tests/ui/proc-macro/quote/not-quotable.stderr create mode 100644 tests/ui/proc-macro/quote/not-repeatable.rs create mode 100644 tests/ui/proc-macro/quote/not-repeatable.stderr diff --git a/tests/ui/proc-macro/quote/auxiliary/basic.rs b/tests/ui/proc-macro/quote/auxiliary/basic.rs index 67927e5f6a8a0..c49e9380b8fa1 100644 --- a/tests/ui/proc-macro/quote/auxiliary/basic.rs +++ b/tests/ui/proc-macro/quote/auxiliary/basic.rs @@ -47,6 +47,7 @@ pub fn run_tests(_: TokenStream) -> TokenStream { // - quote_spanned: // - fn test_quote_spanned_impl // - fn test_type_inference_for_span +// - wrong-type-span.rs // - format_ident: // - fn test_format_ident // - fn test_format_ident_strip_raw 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 new file mode 100644 index 0000000000000..2f67ae1bc6edc --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.rs @@ -0,0 +1,17 @@ +// 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; + +use proc_macro::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!($($nonrep $nonrep)*); +} 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 new file mode 100644 index 0000000000000..5f28a46f3181d --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated-dup.stderr @@ -0,0 +1,10 @@ +error: proc macro panicked + --> $DIR/does-not-have-iter-interpolated-dup.rs:16:5 + | +LL | quote!($($nonrep $nonrep)*); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: message: `$` must be followed by an ident or `$` in `quote!` + +error: aborting due to 1 previous error + 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 new file mode 100644 index 0000000000000..1efb3eac64247 --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.rs @@ -0,0 +1,17 @@ +// 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; + +use proc_macro::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!($($nonrep)*); +} 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 new file mode 100644 index 0000000000000..595aa8587634a --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-interpolated.stderr @@ -0,0 +1,10 @@ +error: proc macro panicked + --> $DIR/does-not-have-iter-interpolated.rs:16:5 + | +LL | quote!($($nonrep)*); + | ^^^^^^^^^^^^^^^^^^^ + | + = help: message: `$` must be followed by an ident or `$` in `quote!` + +error: aborting due to 1 previous error + 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 new file mode 100644 index 0000000000000..5f2ddabc390da --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-separated.rs @@ -0,0 +1,13 @@ +// 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; + +use proc_macro::quote; + +fn main() { + quote!($(a b),*); +} 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 new file mode 100644 index 0000000000000..f6f5d7e007d0f --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter-separated.stderr @@ -0,0 +1,10 @@ +error: proc macro panicked + --> $DIR/does-not-have-iter-separated.rs:12:5 + | +LL | quote!($(a b),*); + | ^^^^^^^^^^^^^^^^ + | + = help: message: `$` must be followed by an ident or `$` in `quote!` + +error: aborting due to 1 previous error + diff --git a/tests/ui/proc-macro/quote/does-not-have-iter.rs b/tests/ui/proc-macro/quote/does-not-have-iter.rs new file mode 100644 index 0000000000000..25ffd786cc614 --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter.rs @@ -0,0 +1,13 @@ +// 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; + +use proc_macro::quote; + +fn main() { + quote!($(a b)*); +} diff --git a/tests/ui/proc-macro/quote/does-not-have-iter.stderr b/tests/ui/proc-macro/quote/does-not-have-iter.stderr new file mode 100644 index 0000000000000..0ed1daffc8cdf --- /dev/null +++ b/tests/ui/proc-macro/quote/does-not-have-iter.stderr @@ -0,0 +1,10 @@ +error: proc macro panicked + --> $DIR/does-not-have-iter.rs:12:5 + | +LL | quote!($(a b)*); + | ^^^^^^^^^^^^^^^ + | + = help: message: `$` must be followed by an ident or `$` in `quote!` + +error: aborting due to 1 previous error + diff --git a/tests/ui/proc-macro/quote/not-quotable.rs b/tests/ui/proc-macro/quote/not-quotable.rs new file mode 100644 index 0000000000000..7e38b4410528a --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable.rs @@ -0,0 +1,12 @@ +#![feature(proc_macro_quote)] + +extern crate proc_macro; + +use std::net::Ipv4Addr; + +use proc_macro::quote; + +fn main() { + let ip = Ipv4Addr::LOCALHOST; + let _ = quote! { $ip }; //~ ERROR the trait bound `Ipv4Addr: ToTokens` is not satisfied +} diff --git a/tests/ui/proc-macro/quote/not-quotable.stderr b/tests/ui/proc-macro/quote/not-quotable.stderr new file mode 100644 index 0000000000000..e349b2dce5300 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-quotable.stderr @@ -0,0 +1,24 @@ +error[E0277]: the trait bound `Ipv4Addr: ToTokens` is not satisfied + --> $DIR/not-quotable.rs:11:13 + | +LL | let _ = quote! { $ip }; + | ^^^^^^^^^^^^^^ + | | + | the trait `ToTokens` is not implemented for `Ipv4Addr` + | 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 + = note: this error originates in the macro `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-repeatable.rs b/tests/ui/proc-macro/quote/not-repeatable.rs new file mode 100644 index 0000000000000..d115da7318156 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-repeatable.rs @@ -0,0 +1,16 @@ +// 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; + +use proc_macro::quote; + +struct Ipv4Addr; + +fn main() { + let ip = Ipv4Addr; + let _ = quote! { $($ip)* }; +} diff --git a/tests/ui/proc-macro/quote/not-repeatable.stderr b/tests/ui/proc-macro/quote/not-repeatable.stderr new file mode 100644 index 0000000000000..18fbcd7379858 --- /dev/null +++ b/tests/ui/proc-macro/quote/not-repeatable.stderr @@ -0,0 +1,10 @@ +error: proc macro panicked + --> $DIR/not-repeatable.rs:15:13 + | +LL | let _ = quote! { $($ip)* }; + | ^^^^^^^^^^^^^^^^^^ + | + = help: message: `$` must be followed by an ident or `$` in `quote!` + +error: aborting due to 1 previous error + From ef69c3001b387c135038a62902c803f368f7f3bd Mon Sep 17 00:00:00 2001 From: Asuna Date: Thu, 9 Jan 2025 03:02:23 +0100 Subject: [PATCH 5/5] Fix `proc_macro::quote!` for raw ident --- library/proc_macro/src/quote.rs | 10 ++++++++-- tests/ui/proc-macro/quote/auxiliary/basic.rs | 2 +- tests/ui/proc-macro/quote/debug.rs | 1 + tests/ui/proc-macro/quote/debug.stdout | 19 +++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs index 27c8458a351ca..bcb15912bb65e 100644 --- a/library/proc_macro/src/quote.rs +++ b/library/proc_macro/src/quote.rs @@ -112,8 +112,14 @@ pub fn quote(stream: TokenStream) -> TokenStream { )), &mut ts);) } TokenTree::Ident(tt) => { - minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new( - (@ TokenTree::from(Literal::string(&tt.to_string()))), + let literal = tt.to_string(); + let (literal, ctor) = if let Some(stripped) = literal.strip_prefix("r#") { + (stripped, minimal_quote!(crate::Ident::new_raw)) + } else { + (literal.as_str(), minimal_quote!(crate::Ident::new)) + }; + minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Ident((@ ctor)( + (@ TokenTree::from(Literal::string(literal))), (@ quote_span(proc_macro_crate.clone(), tt.span())), )), &mut ts);) } diff --git a/tests/ui/proc-macro/quote/auxiliary/basic.rs b/tests/ui/proc-macro/quote/auxiliary/basic.rs index c49e9380b8fa1..ef726bbfbe3a0 100644 --- a/tests/ui/proc-macro/quote/auxiliary/basic.rs +++ b/tests/ui/proc-macro/quote/auxiliary/basic.rs @@ -34,7 +34,7 @@ pub fn run_tests(_: TokenStream) -> TokenStream { test_inner_block_comment(); test_outer_attr(); test_inner_attr(); - // test_quote_raw_id(); // FIXME: Fix it in a subsequent commit + test_quote_raw_id(); TokenStream::new() } diff --git a/tests/ui/proc-macro/quote/debug.rs b/tests/ui/proc-macro/quote/debug.rs index 11d144d609f5a..ce113079e5678 100644 --- a/tests/ui/proc-macro/quote/debug.rs +++ b/tests/ui/proc-macro/quote/debug.rs @@ -15,5 +15,6 @@ extern crate proc_macro; fn main() { proc_macro::quote! { let hello = "world"; + let r#raw_ident = r#"raw"literal"#; } } diff --git a/tests/ui/proc-macro/quote/debug.stdout b/tests/ui/proc-macro/quote/debug.stdout index 583599ff97524..3eaad9eb96997 100644 --- a/tests/ui/proc-macro/quote/debug.stdout +++ b/tests/ui/proc-macro/quote/debug.stdout @@ -40,6 +40,25 @@ fn main() { }), &mut ts); crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(';', crate::Spacing::Alone)), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new("let", + crate::Span::recover_proc_macro_span(3))), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Ident(crate::Ident::new_raw("raw_ident", + crate::Span::recover_proc_macro_span(4))), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new('=', + crate::Spacing::Alone)), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Literal({ + let mut iter = + "r#\"raw\"literal\"#".parse::().unwrap().into_iter(); + if let (Some(crate::TokenTree::Literal(mut lit)), None) = + (iter.next(), iter.next()) { + lit.set_span(crate::Span::recover_proc_macro_span(5)); + lit + } else { + ::core::panicking::panic("internal error: entered unreachable code") + } + }), &mut ts); + crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(';', + crate::Spacing::Alone)), &mut ts); ts } }