Skip to content

Commit 23e35c6

Browse files
committed
Add support for repetition to proc_macro::quote
1 parent 68ac5ab commit 23e35c6

14 files changed

+519
-63
lines changed

library/proc_macro/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ mod diagnostic;
4545
mod escape;
4646
mod to_tokens;
4747

48+
use core::ops::BitOr;
4849
use std::ffi::CStr;
4950
use std::ops::{Range, RangeBounds};
5051
use std::path::PathBuf;
@@ -237,7 +238,7 @@ impl Default for TokenStream {
237238
}
238239

239240
#[unstable(feature = "proc_macro_quote", issue = "54722")]
240-
pub use quote::{quote, quote_span};
241+
pub use quote::{HasIterator, RepInterp, ThereIsNoIteratorInRepetition, ext, quote, quote_span};
241242

242243
fn tree_to_bridge_tree(
243244
tree: TokenTree,

library/proc_macro/src/quote.rs

Lines changed: 325 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,183 @@
55
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.
66
77
use crate::{
8-
Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
8+
BitOr, Delimiter, Group, Ident, Literal, Punct, Spacing, Span, ToTokens, TokenStream, TokenTree,
99
};
1010

11+
#[doc(hidden)]
12+
pub struct HasIterator; // True
13+
#[doc(hidden)]
14+
pub struct ThereIsNoIteratorInRepetition; // False
15+
16+
impl BitOr<ThereIsNoIteratorInRepetition> for ThereIsNoIteratorInRepetition {
17+
type Output = ThereIsNoIteratorInRepetition;
18+
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition {
19+
ThereIsNoIteratorInRepetition
20+
}
21+
}
22+
23+
impl BitOr<ThereIsNoIteratorInRepetition> for HasIterator {
24+
type Output = HasIterator;
25+
fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator {
26+
HasIterator
27+
}
28+
}
29+
30+
impl BitOr<HasIterator> for ThereIsNoIteratorInRepetition {
31+
type Output = HasIterator;
32+
fn bitor(self, _rhs: HasIterator) -> HasIterator {
33+
HasIterator
34+
}
35+
}
36+
37+
impl BitOr<HasIterator> for HasIterator {
38+
type Output = HasIterator;
39+
fn bitor(self, _rhs: HasIterator) -> HasIterator {
40+
HasIterator
41+
}
42+
}
43+
44+
/// Extension traits used by the implementation of `quote!`. These are defined
45+
/// in separate traits, rather than as a single trait due to ambiguity issues.
46+
///
47+
/// These traits expose a `quote_into_iter` method which should allow calling
48+
/// whichever impl happens to be applicable. Calling that method repeatedly on
49+
/// the returned value should be idempotent.
50+
#[doc(hidden)]
51+
pub mod ext {
52+
use core::slice;
53+
use std::collections::btree_set::{self, BTreeSet};
54+
55+
use super::{
56+
HasIterator as HasIter, RepInterp, ThereIsNoIteratorInRepetition as DoesNotHaveIter,
57+
};
58+
use crate::ToTokens;
59+
60+
/// Extension trait providing the `quote_into_iter` method on iterators.
61+
#[doc(hidden)]
62+
pub trait RepIteratorExt: Iterator + Sized {
63+
fn quote_into_iter(self) -> (Self, HasIter) {
64+
(self, HasIter)
65+
}
66+
}
67+
68+
impl<T: Iterator> RepIteratorExt for T {}
69+
70+
/// Extension trait providing the `quote_into_iter` method for
71+
/// non-iterable types. These types interpolate the same value in each
72+
/// iteration of the repetition.
73+
#[doc(hidden)]
74+
pub trait RepToTokensExt {
75+
/// Pretend to be an iterator for the purposes of `quote_into_iter`.
76+
/// This allows repeated calls to `quote_into_iter` to continue
77+
/// correctly returning DoesNotHaveIter.
78+
fn next(&self) -> Option<&Self> {
79+
Some(self)
80+
}
81+
82+
fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) {
83+
(self, DoesNotHaveIter)
84+
}
85+
}
86+
87+
impl<T: ToTokens + ?Sized> RepToTokensExt for T {}
88+
89+
/// Extension trait providing the `quote_into_iter` method for types that
90+
/// can be referenced as an iterator.
91+
#[doc(hidden)]
92+
pub trait RepAsIteratorExt<'q> {
93+
type Iter: Iterator;
94+
95+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter);
96+
}
97+
98+
impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &T {
99+
type Iter = T::Iter;
100+
101+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
102+
<T as RepAsIteratorExt>::quote_into_iter(*self)
103+
}
104+
}
105+
106+
impl<'q, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &mut T {
107+
type Iter = T::Iter;
108+
109+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
110+
<T as RepAsIteratorExt>::quote_into_iter(*self)
111+
}
112+
}
113+
114+
impl<'q, T: 'q> RepAsIteratorExt<'q> for [T] {
115+
type Iter = slice::Iter<'q, T>;
116+
117+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
118+
(self.iter(), HasIter)
119+
}
120+
}
121+
122+
impl<'q, T: 'q, const N: usize> RepAsIteratorExt<'q> for [T; N] {
123+
type Iter = slice::Iter<'q, T>;
124+
125+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
126+
(self.iter(), HasIter)
127+
}
128+
}
129+
130+
impl<'q, T: 'q> RepAsIteratorExt<'q> for Vec<T> {
131+
type Iter = slice::Iter<'q, T>;
132+
133+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
134+
(self.iter(), HasIter)
135+
}
136+
}
137+
138+
impl<'q, T: 'q> RepAsIteratorExt<'q> for BTreeSet<T> {
139+
type Iter = btree_set::Iter<'q, T>;
140+
141+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
142+
(self.iter(), HasIter)
143+
}
144+
}
145+
146+
impl<'q, T: RepAsIteratorExt<'q>> RepAsIteratorExt<'q> for RepInterp<T> {
147+
type Iter = T::Iter;
148+
149+
fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) {
150+
self.0.quote_into_iter()
151+
}
152+
}
153+
}
154+
155+
// Helper type used within interpolations to allow for repeated binding names.
156+
// Implements the relevant traits, and exports a dummy `next()` method.
157+
#[derive(Copy, Clone)]
158+
#[doc(hidden)]
159+
pub struct RepInterp<T>(pub T);
160+
161+
impl<T> RepInterp<T> {
162+
// This method is intended to look like `Iterator::next`, and is called when
163+
// a name is bound multiple times, as the previous binding will shadow the
164+
// original `Iterator` object. This allows us to avoid advancing the
165+
// iterator multiple times per iteration.
166+
pub fn next(self) -> Option<T> {
167+
Some(self.0)
168+
}
169+
}
170+
171+
impl<T: Iterator> Iterator for RepInterp<T> {
172+
type Item = T::Item;
173+
174+
fn next(&mut self) -> Option<Self::Item> {
175+
self.0.next()
176+
}
177+
}
178+
179+
impl<T: ToTokens> ToTokens for RepInterp<T> {
180+
fn to_tokens(&self, tokens: &mut TokenStream) {
181+
self.0.to_tokens(tokens);
182+
}
183+
}
184+
11185
macro_rules! minimal_quote_tt {
12186
(($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, minimal_quote!($($t)*)) };
13187
([$($t:tt)*]) => { Group::new(Delimiter::Bracket, minimal_quote!($($t)*)) };
@@ -20,7 +194,13 @@ macro_rules! minimal_quote_tt {
20194
(>) => { Punct::new('>', Spacing::Alone) };
21195
(&) => { Punct::new('&', Spacing::Alone) };
22196
(=) => { Punct::new('=', Spacing::Alone) };
197+
(#) => { Punct::new('#', Spacing::Alone) };
198+
(|) => { Punct::new('|', Spacing::Alone) };
199+
(:) => { Punct::new(':', Spacing::Alone) };
200+
(*) => { Punct::new('*', Spacing::Alone) };
201+
(_) => { Ident::new("_", Span::def_site()) };
23202
($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
203+
($lit:literal) => { stringify!($lit).parse::<Literal>().unwrap() };
24204
}
25205

26206
macro_rules! minimal_quote_ts {
@@ -36,6 +216,39 @@ macro_rules! minimal_quote_ts {
36216
[c.0, c.1].into_iter().collect::<TokenStream>()
37217
}
38218
};
219+
(=>) => {
220+
{
221+
let mut c = (
222+
TokenTree::from(Punct::new('=', Spacing::Joint)),
223+
TokenTree::from(Punct::new('>', Spacing::Alone))
224+
);
225+
c.0.set_span(Span::def_site());
226+
c.1.set_span(Span::def_site());
227+
[c.0, c.1].into_iter().collect::<TokenStream>()
228+
}
229+
};
230+
(+=) => {
231+
{
232+
let mut c = (
233+
TokenTree::from(Punct::new('+', Spacing::Joint)),
234+
TokenTree::from(Punct::new('=', Spacing::Alone))
235+
);
236+
c.0.set_span(Span::def_site());
237+
c.1.set_span(Span::def_site());
238+
[c.0, c.1].into_iter().collect::<TokenStream>()
239+
}
240+
};
241+
(!=) => {
242+
{
243+
let mut c = (
244+
TokenTree::from(Punct::new('!', Spacing::Joint)),
245+
TokenTree::from(Punct::new('=', Spacing::Alone))
246+
);
247+
c.0.set_span(Span::def_site());
248+
c.1.set_span(Span::def_site());
249+
[c.0, c.1].into_iter().collect::<TokenStream>()
250+
}
251+
};
39252
($t:tt) => { TokenTree::from(minimal_quote_tt!($t)) };
40253
}
41254

@@ -71,17 +284,99 @@ pub fn quote(stream: TokenStream) -> TokenStream {
71284
let mut after_dollar = false;
72285

73286
let mut tokens = crate::TokenStream::new();
74-
for tree in stream {
287+
let mut iter = stream.into_iter().peekable();
288+
while let Some(tree) = iter.next() {
75289
if after_dollar {
76290
after_dollar = false;
77291
match tree {
292+
TokenTree::Group(tt) => {
293+
// Handles repetition by expanding `$( CONTENTS ) SEP_OPT *` to `{ REP_EXPANDED }`.
294+
let contents = tt.stream();
295+
296+
// The `*` token is also consumed here.
297+
let sep_opt: Option<Punct> = match (iter.next(), iter.peek()) {
298+
(Some(TokenTree::Punct(sep)), Some(TokenTree::Punct(star)))
299+
if sep.spacing() == Spacing::Joint && star.as_char() == '*' =>
300+
{
301+
iter.next();
302+
Some(sep)
303+
}
304+
(Some(TokenTree::Punct(star)), _) if star.as_char() == '*' => None,
305+
_ => panic!("`$(...)` must be followed by `*` in `quote!`"),
306+
};
307+
308+
let mut rep_expanded = TokenStream::new();
309+
310+
// Append setup code for a `while`, where recursively quoted `CONTENTS`
311+
// and `SEP_OPT` are repeatedly processed, to `REP_EXPANDED`.
312+
let meta_vars = collect_meta_vars(contents.clone());
313+
minimal_quote!(
314+
use crate::ext::*;
315+
(@ if sep_opt.is_some() {
316+
minimal_quote!(let mut _i = 0usize;)
317+
} else {
318+
minimal_quote!(();)
319+
})
320+
let has_iter = crate::ThereIsNoIteratorInRepetition;
321+
)
322+
.to_tokens(&mut rep_expanded);
323+
for meta_var in &meta_vars {
324+
minimal_quote!(
325+
#[allow(unused_mut)]
326+
let (mut (@ meta_var), i) = (@ meta_var).quote_into_iter();
327+
let has_iter = has_iter | i;
328+
)
329+
.to_tokens(&mut rep_expanded);
330+
}
331+
minimal_quote!(let _: crate::HasIterator = has_iter;)
332+
.to_tokens(&mut rep_expanded);
333+
334+
// Append the `while` to `REP_EXPANDED`.
335+
let mut while_body = TokenStream::new();
336+
for meta_var in &meta_vars {
337+
minimal_quote!(
338+
let (@ meta_var) = match (@ meta_var).next() {
339+
Some(_x) => crate::RepInterp(_x),
340+
None => break,
341+
};
342+
)
343+
.to_tokens(&mut while_body);
344+
}
345+
minimal_quote!(
346+
(@ if let Some(sep) = sep_opt {
347+
minimal_quote!(
348+
if _i > 0 {
349+
(@ minimal_quote!(crate::ToTokens::to_tokens(&crate::TokenTree::Punct(crate::Punct::new(
350+
(@ TokenTree::from(Literal::character(sep.as_char()))),
351+
(@ minimal_quote!(crate::Spacing::Alone)),
352+
)), &mut ts);))
353+
}
354+
_i += 1;
355+
)
356+
} else {
357+
minimal_quote!(();)
358+
})
359+
(@ quote(contents.clone())).to_tokens(&mut ts);
360+
)
361+
.to_tokens(&mut while_body);
362+
rep_expanded.extend(vec![
363+
TokenTree::Ident(Ident::new("while", Span::call_site())),
364+
TokenTree::Ident(Ident::new("true", Span::call_site())),
365+
TokenTree::Group(Group::new(Delimiter::Brace, while_body)),
366+
]);
367+
368+
minimal_quote!((@ TokenTree::Group(Group::new(Delimiter::Brace, rep_expanded)))).to_tokens(&mut tokens);
369+
continue;
370+
}
78371
TokenTree::Ident(_) => {
79372
minimal_quote!(crate::ToTokens::to_tokens(&(@ tree), &mut ts);)
80373
.to_tokens(&mut tokens);
81374
continue;
82375
}
83376
TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
84-
_ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
377+
_ => panic!(
378+
"`$` must be followed by an ident or `$` or a repetition group in `quote!`"
379+
),
85380
}
86381
} else if let TokenTree::Punct(ref tt) = tree {
87382
if tt.as_char() == '$' {
@@ -155,6 +450,33 @@ pub fn quote(stream: TokenStream) -> TokenStream {
155450
}
156451
}
157452

453+
/// Helper function to support macro repetitions like `$( CONTENTS ) SEP_OPT *` in `quote!`.
454+
/// Recursively collects all `Ident`s (meta-variables) that follow a `$`
455+
/// from the given `CONTENTS` stream, preserving their order of appearance.
456+
fn collect_meta_vars(content_stream: TokenStream) -> Vec<Ident> {
457+
fn helper(stream: TokenStream, out: &mut Vec<Ident>) {
458+
let mut iter = stream.into_iter().peekable();
459+
while let Some(tree) = iter.next() {
460+
match &tree {
461+
TokenTree::Punct(tt) if tt.as_char() == '$' => {
462+
if let Some(TokenTree::Ident(id)) = iter.peek() {
463+
out.push(id.clone());
464+
iter.next();
465+
}
466+
}
467+
TokenTree::Group(tt) => {
468+
helper(tt.stream(), out);
469+
}
470+
_ => {}
471+
}
472+
}
473+
}
474+
475+
let mut vars = Vec::new();
476+
helper(content_stream, &mut vars);
477+
vars
478+
}
479+
158480
/// Quote a `Span` into a `TokenStream`.
159481
/// This is needed to implement a custom quoter.
160482
#[unstable(feature = "proc_macro_quote", issue = "54722")]

0 commit comments

Comments
 (0)