summaryrefslogtreecommitdiffstats
path: root/library/proc_macro/src/quote.rs
diff options
context:
space:
mode:
Diffstat (limited to 'library/proc_macro/src/quote.rs')
-rw-r--r--library/proc_macro/src/quote.rs141
1 files changed, 141 insertions, 0 deletions
diff --git a/library/proc_macro/src/quote.rs b/library/proc_macro/src/quote.rs
new file mode 100644
index 000000000..04fa696d5
--- /dev/null
+++ b/library/proc_macro/src/quote.rs
@@ -0,0 +1,141 @@
+//! # Quasiquoter
+//! This file contains the implementation internals of the quasiquoter provided by `quote!`.
+
+//! 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};
+
+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)*)) };
+ (,) => { 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) };
+ (=) => { Punct::new('=', Spacing::Alone) };
+ ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
+}
+
+macro_rules! 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::<TokenStream>()
+ };
+ ($t:tt) => { TokenTree::from(quote_tt!($t)) };
+}
+
+/// Simpler version of the real `quote!` macro, implemented solely
+/// through `macro_rules`, for bootstrapping the real implementation
+/// (see the `quote` function), which does not have access to the
+/// real `quote!` macro due to the `proc_macro` crate not being
+/// able to depend on itself.
+///
+/// Note: supported tokens are a subset of the real `quote!`, but
+/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
+macro_rules! quote {
+ () => { TokenStream::new() };
+ ($($t:tt)*) => {
+ [
+ $(TokenStream::from(quote_ts!($t)),)*
+ ].iter().cloned().collect::<TokenStream>()
+ };
+}
+
+/// Quote a `TokenStream` into a `TokenStream`.
+/// This is the actual implementation of the `quote!()` proc macro.
+///
+/// It is loaded by the compiler in `register_builtin_macros`.
+#[unstable(feature = "proc_macro_quote", issue = "54722")]
+pub fn quote(stream: TokenStream) -> TokenStream {
+ if stream.is_empty() {
+ return quote!(crate::TokenStream::new());
+ }
+ let proc_macro_crate = 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(quote!(Into::<crate::TokenStream>::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;
+ }
+ }
+
+ Some(quote!(crate::TokenStream::from((@ match tree {
+ TokenTree::Punct(tt) => 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),
+ }),
+ ))),
+ TokenTree::Group(tt) => 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),
+ }),
+ (@ quote(tt.stream())),
+ ))),
+ TokenTree::Ident(tt) => 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({
+ let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
+ .parse::<crate::TokenStream>()
+ .unwrap()
+ .into_iter();
+ if let (Some(crate::TokenTree::Literal(mut lit)), None) =
+ (iter.next(), iter.next())
+ {
+ lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
+ lit
+ } else {
+ unreachable!()
+ }
+ }))
+ })),))
+ })
+ .collect::<TokenStream>();
+
+ if after_dollar {
+ panic!("unexpected trailing `$` in `quote!`");
+ }
+
+ quote!([(@ tokens)].iter().cloned().collect::<crate::TokenStream>())
+}
+
+/// Quote a `Span` into a `TokenStream`.
+/// This is needed to implement a custom quoter.
+#[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)))))
+}