-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Add support for repetition to proc_macro::quote
#141608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,6 +44,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; | ||
|
@@ -1613,3 +1614,202 @@ pub mod tracked_path { | |
crate::bridge::client::FreeFunctions::track_path(path); | ||
} | ||
} | ||
|
||
#[doc(hidden)] | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
#[derive(Debug)] | ||
pub struct HasIterator; // True | ||
#[doc(hidden)] | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
#[derive(Debug)] | ||
pub struct ThereIsNoIteratorInRepetition; // False | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl BitOr<ThereIsNoIteratorInRepetition> for ThereIsNoIteratorInRepetition { | ||
type Output = ThereIsNoIteratorInRepetition; | ||
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition { | ||
ThereIsNoIteratorInRepetition | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl BitOr<ThereIsNoIteratorInRepetition> for HasIterator { | ||
type Output = HasIterator; | ||
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator { | ||
HasIterator | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl BitOr<HasIterator> for ThereIsNoIteratorInRepetition { | ||
type Output = HasIterator; | ||
fn bitor(self, _rhs: HasIterator) -> HasIterator { | ||
HasIterator | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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)] | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
pub mod ext { | ||
use core::slice; | ||
use std::collections::btree_set::{self, BTreeSet}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. an alternative for |
||
|
||
use super::{ | ||
HasIterator as HasIter, RepInterp, ThereIsNoIteratorInRepetition as DoesNotHaveIter, | ||
}; | ||
use crate::ToTokens; | ||
|
||
/// Extension trait providing the `quote_into_iter` method on iterators. | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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. | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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. | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
fn next(&self) -> Option<&Self> { | ||
Some(self) | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) { | ||
(self, DoesNotHaveIter) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl<T: ToTokens + ?Sized> RepToTokensExt for T {} | ||
|
||
/// Extension trait providing the `quote_into_iter` method for types that | ||
/// can be referenced as an iterator. | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
pub trait RepAsIteratorExt<'q> { | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
type Iter: Iterator; | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter); | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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)] | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
pub struct RepInterp<T>(pub T); | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
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. | ||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
pub fn next(self) -> Option<T> { | ||
Some(self.0) | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl<T: Iterator> Iterator for RepInterp<T> { | ||
type Item = T::Item; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
self.0.next() | ||
} | ||
} | ||
|
||
#[unstable(feature = "proc_macro_quote", issue = "54722")] | ||
impl<T: ToTokens> ToTokens for RepInterp<T> { | ||
fn to_tokens(&self, tokens: &mut TokenStream) { | ||
self.0.to_tokens(tokens); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,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 +42,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,10 +110,92 @@ 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
after_dollar = false; | ||
match tree { | ||
TokenTree::Group(inner) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add comments in this section about what is happening? |
||
let content = inner.stream(); | ||
|
||
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(sep)), _) if sep.as_char() == '*' => None, | ||
_ => panic!("`$(...)` must be followed by `*` in `quote!`"), | ||
}; | ||
|
||
let meta_vars = collect_meta_vars(content.clone()); | ||
|
||
let mut content_tokens = TokenStream::new(); | ||
minimal_quote!( | ||
use crate::ext::*; | ||
) | ||
.to_tokens(&mut content_tokens); | ||
if sep_opt.is_some() { | ||
minimal_quote!( | ||
let mut _i = 0usize; | ||
) | ||
.to_tokens(&mut content_tokens); | ||
} | ||
minimal_quote!( | ||
let has_iter = crate::ThereIsNoIteratorInRepetition; | ||
) | ||
.to_tokens(&mut content_tokens); | ||
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 content_tokens); | ||
} | ||
minimal_quote!( | ||
let _: crate::HasIterator = has_iter; | ||
) | ||
.to_tokens(&mut content_tokens); | ||
|
||
let while_ident = TokenTree::Ident(Ident::new("while", Span::call_site())); | ||
let true_literal = TokenTree::Ident(Ident::new("true", Span::call_site())); | ||
|
||
let mut inner_tokens = 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 inner_tokens); | ||
} | ||
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; | ||
) | ||
.to_tokens(&mut inner_tokens); | ||
}; | ||
minimal_quote!( | ||
(@ quote(content.clone())).to_tokens(&mut ts); | ||
) | ||
.to_tokens(&mut inner_tokens); | ||
let while_block = TokenTree::Group(Group::new(Delimiter::Brace, inner_tokens)); | ||
|
||
content_tokens.extend(vec![while_ident, true_literal, while_block]); | ||
let block = TokenTree::Group(Group::new(Delimiter::Brace, content_tokens)); | ||
minimal_quote!((@ block)).to_tokens(&mut tokens); | ||
|
||
continue; | ||
} | ||
TokenTree::Ident(_) => { | ||
minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);) | ||
.to_tokens(&mut tokens); | ||
|
@@ -155,6 +276,30 @@ pub fn quote(stream: TokenStream) -> TokenStream { | |
} | ||
} | ||
|
||
fn collect_meta_vars(stream: TokenStream) -> Vec<Ident> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a doc comment? |
||
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(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")] | ||
|
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure whether these annotations are appropriate. (I added them just based on my speculation.)
In addition,
do we need to take care of its license?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can always try removing them and see if it complains - but in general, all crate-public items need a stability gate. Try to make as little as possible actually
pub
of course, but that's difficult because this needs a lot of helpers to be public.This all doesn't need to be in
lib.rs
though, could you move the additions here to thequote
module? And then reexport only what is needed.quote
isMIT AND Apache-2.0
, which is the same as rust-lang/rust so there is no problem here.