Skip to content

Commit

Permalink
proc_macro: clean up the implementation of quasi-quoting.
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Jul 19, 2018
1 parent d10d0b3 commit 56aaa53
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 185 deletions.
14 changes: 4 additions & 10 deletions src/libproc_macro/lib.rs
Expand Up @@ -145,6 +145,9 @@ impl fmt::Debug for TokenStream {
}
}

#[unstable(feature = "proc_macro_quote", issue = "38356")]
pub use quote::{quote, quote_span};

/// Creates a token stream containing a single token tree.
#[stable(feature = "proc_macro_lib2", since = "1.29.0")]
impl From<TokenTree> for TokenStream {
Expand Down Expand Up @@ -237,7 +240,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 `$$`.
///
/// This is a dummy macro, the actual implementation is in quote::Quoter
/// This is a dummy macro, the actual implementation is in `quote::quote`.`
#[unstable(feature = "proc_macro_quote", issue = "38356")]
#[macro_export]
macro_rules! quote { () => {} }
Expand All @@ -246,13 +249,6 @@ macro_rules! quote { () => {} }
#[doc(hidden)]
mod quote;

/// Quote a `Span` into a `TokenStream`.
/// This is needed to implement a custom quoter.
#[unstable(feature = "proc_macro_quote", issue = "38356")]
pub fn quote_span(span: Span) -> TokenStream {
quote::Quote::quote(span)
}

/// A region of source code, along with macro expansion information.
#[stable(feature = "proc_macro_lib2", since = "1.29.0")]
#[derive(Copy, Clone)]
Expand Down Expand Up @@ -1364,8 +1360,6 @@ impl TokenTree {
#[unstable(feature = "proc_macro_internals", issue = "27812")]
#[doc(hidden)]
pub mod __internal {
pub use quote::{Quoter, unquote};

use std::cell::Cell;
use std::ptr;

Expand Down
265 changes: 92 additions & 173 deletions src/libproc_macro/quote.rs
Expand Up @@ -14,34 +14,26 @@
//! This quasiquoter uses macros 2.0 hygiene to reliably access
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.

use {Delimiter, Literal, Spacing, Span, Ident, Punct, Group, TokenStream, TokenTree};

use syntax::ext::base::{ExtCtxt, ProcMacro};
use syntax::tokenstream;

/// This is the actual quote!() proc macro
///
/// It is manually loaded in CStore::load_macro_untracked
pub struct Quoter;

pub fn unquote<T: Into<TokenStream> + Clone>(tokens: &T) -> TokenStream {
tokens.clone().into()
}

pub trait Quote {
fn quote(self) -> TokenStream;
}

macro_rules! tt2ts {
($e:expr) => (TokenStream::from(TokenTree::from($e)))
}

macro_rules! quote_tok {
(,) => { tt2ts!(Punct::new(',', Spacing::Alone)) };
(.) => { tt2ts!(Punct::new('.', Spacing::Alone)) };
(:) => { tt2ts!(Punct::new(':', Spacing::Alone)) };
(;) => { tt2ts!(Punct::new(';', Spacing::Alone)) };
(|) => { tt2ts!(Punct::new('|', Spacing::Alone)) };
use {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) };
(=) => { 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)),
Expand All @@ -54,57 +46,45 @@ macro_rules! quote_tok {
})
.collect::<TokenStream>()
};
(!) => { tt2ts!(Punct::new('!', Spacing::Alone)) };
(<) => { tt2ts!(Punct::new('<', Spacing::Alone)) };
(>) => { tt2ts!(Punct::new('>', Spacing::Alone)) };
(_) => { tt2ts!(Punct::new('_', Spacing::Alone)) };
(0) => { tt2ts!(Literal::i8_unsuffixed(0)) };
(&) => { tt2ts!(Punct::new('&', Spacing::Alone)) };
(=) => { tt2ts!(Punct::new('=', Spacing::Alone)) };
($i:ident) => { tt2ts!(Ident::new(stringify!($i), Span::def_site())) };
}

macro_rules! quote_tree {
((unquote $($t:tt)*)) => { $($t)* };
((quote $($t:tt)*)) => { ($($t)*).quote() };
(($($t:tt)*)) => { tt2ts!(Group::new(Delimiter::Parenthesis, quote!($($t)*))) };
([$($t:tt)*]) => { tt2ts!(Group::new(Delimiter::Bracket, quote!($($t)*))) };
({$($t:tt)*}) => { tt2ts!(Group::new(Delimiter::Brace, quote!($($t)*))) };
($t:tt) => { quote_tok!($t) };
($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)*) => {
[$(quote_tree!($t),)*].iter()
.cloned()
.flat_map(|x| x.into_iter())
.collect::<TokenStream>()
[
$(TokenStream::from(quote_ts!($t)),)*
].iter().cloned().collect::<TokenStream>()
};
}

impl ProcMacro for Quoter {
fn expand<'cx>(&self, cx: &'cx mut ExtCtxt,
_: ::syntax_pos::Span,
stream: tokenstream::TokenStream)
-> tokenstream::TokenStream {
::__internal::set_sess(cx, || TokenStream(stream).quote().0)
/// Quote a `TokenStream` into a `TokenStream`.
/// This is the actual `quote!()` proc macro.
///
/// It is manually loaded in `CStore::load_macro_untracked`.
#[unstable(feature = "proc_macro_quote", issue = "38356")]
pub fn quote(stream: TokenStream) -> TokenStream {
if stream.is_empty() {
return quote!(::TokenStream::new());
}
}

impl Quote for TokenStream {
fn quote(self) -> TokenStream {
if self.is_empty() {
return quote!(::TokenStream::new());
}
let mut after_dollar = false;
let tokens = self.into_iter().filter_map(|tree| {
let mut after_dollar = false;
let tokens = stream
.into_iter()
.filter_map(|tree| {
if after_dollar {
after_dollar = false;
match tree {
TokenTree::Ident(_) => {
let tree = TokenStream::from(tree);
return Some(quote!(::__internal::unquote(&(unquote tree)),));
return Some(quote!(Into::<::TokenStream>::into(
Clone::clone(&(@ tree))),));
}
TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
_ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
Expand All @@ -116,116 +96,55 @@ impl Quote for TokenStream {
}
}

Some(quote!(::TokenStream::from((quote tree)),))
}).flat_map(|t| t.into_iter()).collect::<TokenStream>();

if after_dollar {
panic!("unexpected trailing `$` in `quote!`");
}

quote!(
[(unquote tokens)].iter()
.cloned()
.flat_map(|x| x.into_iter())
.collect::<::TokenStream>()
)
}
}

impl Quote for TokenTree {
fn quote(self) -> TokenStream {
match self {
TokenTree::Punct(tt) => quote!(::TokenTree::Punct( (quote tt) )),
TokenTree::Group(tt) => quote!(::TokenTree::Group( (quote tt) )),
TokenTree::Ident(tt) => quote!(::TokenTree::Ident( (quote tt) )),
TokenTree::Literal(tt) => quote!(::TokenTree::Literal( (quote tt) )),
}
}
}

impl Quote for char {
fn quote(self) -> TokenStream {
TokenTree::from(Literal::character(self)).into()
}
}

impl<'a> Quote for &'a str {
fn quote(self) -> TokenStream {
TokenTree::from(Literal::string(self)).into()
}
}

impl Quote for u16 {
fn quote(self) -> TokenStream {
TokenTree::from(Literal::u16_unsuffixed(self)).into()
}
}

impl Quote for Group {
fn quote(self) -> TokenStream {
quote!(::Group::new((quote self.delimiter()), (quote self.stream())))
}
}

impl Quote for Punct {
fn quote(self) -> TokenStream {
quote!(::Punct::new((quote self.as_char()), (quote self.spacing())))
}
}

impl Quote for Ident {
fn quote(self) -> TokenStream {
quote!(::Ident::new((quote self.sym.as_str()), (quote self.span())))
}
}
Some(quote!(::TokenStream::from((@ match tree {
TokenTree::Punct(tt) => quote!(::TokenTree::Punct(::Punct::new(
(@ TokenTree::from(Literal::character(tt.as_char()))),
(@ match tt.spacing() {
Spacing::Alone => quote!(::Spacing::Alone),
Spacing::Joint => quote!(::Spacing::Joint),
}),
))),
TokenTree::Group(tt) => quote!(::TokenTree::Group(::Group::new(
(@ match tt.delimiter() {
Delimiter::Parenthesis => quote!(::Delimiter::Parenthesis),
Delimiter::Brace => quote!(::Delimiter::Brace),
Delimiter::Bracket => quote!(::Delimiter::Bracket),
Delimiter::None => quote!(::Delimiter::None),
}),
(@ quote(tt.stream())),
))),
TokenTree::Ident(tt) => quote!(::TokenTree::Ident(::Ident::new(
(@ TokenTree::from(Literal::string(&tt.to_string()))),
(@ quote_span(tt.span())),
))),
TokenTree::Literal(tt) => quote!(::TokenTree::Literal({
let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
.parse::<::TokenStream>()
.unwrap()
.into_iter();
if let (Some(::TokenTree::Literal(mut lit)), None) =
(iter.next(), iter.next())
{
lit.set_span((@ quote_span(tt.span())));
lit
} else {
unreachable!()
}
}))
})),))
})
.collect::<TokenStream>();

impl Quote for Span {
fn quote(self) -> TokenStream {
quote!(::Span::def_site())
if after_dollar {
panic!("unexpected trailing `$` in `quote!`");
}
}

impl Quote for Literal {
fn quote(self) -> TokenStream {
quote! {{
let mut iter = (quote self.to_string())
.parse::<::TokenStream>()
.unwrap()
.into_iter();
if let (Some(::TokenTree::Literal(mut lit)), None) = (iter.next(), iter.next()) {
lit.set_span((quote self.span));
lit
} else {
unreachable!()
}
}}
}
}

impl Quote for Delimiter {
fn quote(self) -> TokenStream {
macro_rules! gen_match {
($($i:ident),*) => {
match self {
$(Delimiter::$i => { quote!(::Delimiter::$i) })*
}
}
}

gen_match!(Parenthesis, Brace, Bracket, None)
}
quote!([(@ tokens)].iter().cloned().collect::<::TokenStream>())
}

impl Quote for Spacing {
fn quote(self) -> TokenStream {
macro_rules! gen_match {
($($i:ident),*) => {
match self {
$(Spacing::$i => { quote!(::Spacing::$i) })*
}
}
}

gen_match!(Alone, Joint)
}
/// Quote a `Span` into a `TokenStream`.
/// This is needed to implement a custom quoter.
#[unstable(feature = "proc_macro_quote", issue = "38356")]
pub fn quote_span(_: Span) -> TokenStream {
quote!(::Span::def_site())
}
6 changes: 4 additions & 2 deletions src/librustc_metadata/cstore_impl.rs
Expand Up @@ -39,7 +39,6 @@ use syntax::ast;
use syntax::attr;
use syntax::codemap;
use syntax::edition::Edition;
use syntax::ext::base::SyntaxExtension;
use syntax::parse::filemap_to_stream;
use syntax::symbol::Symbol;
use syntax_pos::{Span, NO_EXPANSION, FileName};
Expand Down Expand Up @@ -517,8 +516,11 @@ impl CrateStore for cstore::CStore {
return LoadedMacro::ProcMacro(proc_macros[id.index.to_proc_macro_index()].1.clone());
} else if data.name == "proc_macro" &&
self.get_crate_data(id.krate).item_name(id.index) == "quote" {
use syntax::ext::base::SyntaxExtension;
use syntax_ext::proc_macro_impl::BangProcMacro;

let ext = SyntaxExtension::ProcMacro {
expander: Box::new(::proc_macro::__internal::Quoter),
expander: Box::new(BangProcMacro { inner: ::proc_macro::quote }),
allow_internal_unstable: true,
edition: data.root.edition,
};
Expand Down
1 change: 1 addition & 0 deletions src/librustc_metadata/lib.rs
Expand Up @@ -19,6 +19,7 @@
#![feature(libc)]
#![feature(macro_at_most_once_rep)]
#![feature(proc_macro_internals)]
#![feature(proc_macro_quote)]
#![feature(quote)]
#![feature(rustc_diagnostic_macros)]
#![feature(slice_sort_by_cached_key)]
Expand Down

0 comments on commit 56aaa53

Please sign in to comment.