Merge pull request 109 from mystor/dup_name_rep

This commit is contained in:
David Tolnay
2019-07-14 16:19:47 -07:00
3 changed files with 334 additions and 80 deletions
+113 -79
View File
@@ -132,20 +132,6 @@ pub mod __rt;
/// - `#( struct #var; )*` — the repetition can contain other tokens
/// - `#( #k => println!("{}", #v), )*` — even multiple interpolations
///
/// There are two limitations around interpolations in a repetition:
///
/// - Every interpolation inside of a repetition must be a distinct variable.
/// That is, `#(#a #a)*` is not allowed. Work around this by collecting `a`
/// into a vector and taking references `a1 = &a` and `a2 = &a` which you use
/// inside the repetition: `#(#a1 #a2)*`. Where possible, use meaningful names
/// that indicate the distinct role of each copy.
///
/// - Every interpolation inside of a repetition must be iterable. If we have
/// `vec` which is a vector and `ident` which is a single identifier,
/// `#(#ident #vec)*` is not allowed. Work around this by using
/// `std::iter::repeat(ident)` to produce an iterable that can be used from
/// within the repetition.
///
/// # Hygiene
///
/// Any interpolated tokens preserve the `Span` information provided by their
@@ -443,99 +429,131 @@ macro_rules! quote_spanned {
// Extract the names of all #metavariables and pass them to the $finish macro.
//
// in: pounded_var_names!(then () a #b c #( #d )* #e)
// in: pounded_var_names!(then!(()) () a #b c #( #d )* #e)
// out: then!(() b d e)
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! pounded_var_names {
($finish:ident ($($found:ident)*) # ( $($inner:tt)* ) $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) # ( $($inner:tt)* ) $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) # { $($inner:tt)* } $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) # { $($inner:tt)* } $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) # $first:ident $($rest:tt)*) => {
pounded_var_names!($finish ($($found)* $first) $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) # $first:ident $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)* $first) $($rest)*)
};
($finish:ident ($($found:ident)*) ( $($inner:tt)* ) $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) ( $($inner:tt)* ) $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) [ $($inner:tt)* ] $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) [ $($inner:tt)* ] $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) { $($inner:tt)* } $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) { $($inner:tt)* } $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($inner)* $($rest)*)
};
($finish:ident ($($found:ident)*) $ignore:tt $($rest:tt)*) => {
pounded_var_names!($finish ($($found)*) $($rest)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*) $ignore:tt $($rest:tt)*) => {
pounded_var_names!($finish!($($extra)*) ($($found)*) $($rest)*)
};
($finish:ident ($($found:ident)*)) => {
$finish!(() $($found)*)
($finish:ident!($($extra:tt)*) ($($found:ident)*)) => {
$finish!($($extra)* $($found)*)
};
}
// in: nested_tuples_pat!(() a b c d e)
// out: ((((a b) c) d) e)
// in: quote_bind_into_iter!(has_iter {} a b c d e)
// out: #[allow(unused_mut)]
// let mut a = a.__quote_into_iter(&mut has_iter);
// #[allow(unused_mut)]
// let mut b = b.__quote_into_iter(&mut has_iter);
// #[allow(unused_mut)]
// let mut c = b.__quote_into_iter(&mut has_iter);
// #[allow(unused_mut)]
// let mut d = b.__quote_into_iter(&mut has_iter);
// #[allow(unused_mut)]
// let mut e = b.__quote_into_iter(&mut has_iter);
//
// in: nested_tuples_pat!(() a)
// out: a
// in: quote_bind_into_iter!(has_iter {} a)
// out: #[allow(unused_mut)]
// let mut a = a.__quote_into_iter(&mut has_iter);
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! nested_tuples_pat {
(()) => {
&()
macro_rules! quote_bind_into_iter {
($has_iter:ident {$($acc:tt)*} $next:ident $($rest:ident)*) => {
quote_bind_into_iter!(
$has_iter
{
$($acc)*
// `mut` may be unused if $next occurs multiple times in the list.
#[allow(unused_mut)]
let mut $next = $next.__quote_into_iter(&mut $has_iter);
}
$($rest)*
);
};
(() $first:ident $($rest:ident)*) => {
nested_tuples_pat!(($first) $($rest)*)
};
(($pat:pat) $first:ident $($rest:ident)*) => {
nested_tuples_pat!((($pat, $first)) $($rest)*)
};
(($done:pat)) => {
$done
($has_iter:ident {$($done:tt)*}) => {
$($done)*
};
}
// in: multi_zip_expr!(() a b c d e)
// out: a.into_iter().zip(b).zip(c).zip(d).zip(e)
// in: quote_bind_next_or_break!({} a b c d e)
// out: let a = match a.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
// let b = match b.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
// let c = match c.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
// let d = match d.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
// let e = match e.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
//
// in: multi_zip_iter!(() a)
// out: a
// in: quote_bind_next_or_break!({} a)
// out: let a = match a.next() {
// Some(_x) => $crate::__rt::RepInterp(_x),
// None => break,
// };
//
// in: quote_bind_next_or_break!({})
// out: break;
#[macro_export(local_inner_macros)]
#[doc(hidden)]
macro_rules! multi_zip_expr {
(()) => {
&[]
macro_rules! quote_bind_next_or_break {
({}) => { break; };
({$($acc:tt)*} $next:ident $($rest:ident)*) => {
quote_bind_next_or_break!({
$($acc)*
let $next = match $next.next() {
Some(_x) => $crate::__rt::RepInterp(_x),
None => break,
};
} $($rest)*);
};
(() $single:ident) => {
$single
};
(() $first:ident $($rest:ident)*) => {
multi_zip_expr!(($first.into_iter()) $($rest)*)
};
(($zips:expr) $first:ident $($rest:ident)*) => {
multi_zip_expr!(($zips.zip($first)) $($rest)*)
};
(($done:expr)) => {
$done
({$($done:tt)*}) => {
$($done)*
};
}
@@ -551,20 +569,36 @@ macro_rules! quote_each_token {
};
($tokens:ident $span:ident # ( $($inner:tt)* ) * $($rest:tt)*) => {
for pounded_var_names!(nested_tuples_pat () $($inner)*)
in pounded_var_names!(multi_zip_expr () $($inner)*) {
quote_each_token!($tokens $span $($inner)*);
{
use $crate::__rt::ext::*;
let mut has_iter = false;
pounded_var_names!(quote_bind_into_iter!(has_iter {}) () $($inner)*);
if has_iter {
loop {
pounded_var_names!(quote_bind_next_or_break!({}) () $($inner)*);
quote_each_token!($tokens $span $($inner)*);
}
}
}
quote_each_token!($tokens $span $($rest)*);
};
($tokens:ident $span:ident # ( $($inner:tt)* ) $sep:tt * $($rest:tt)*) => {
for (_i, pounded_var_names!(nested_tuples_pat () $($inner)*))
in pounded_var_names!(multi_zip_expr () $($inner)*).into_iter().enumerate() {
if _i > 0 {
quote_each_token!($tokens $span $sep);
{
use $crate::__rt::ext::*;
let mut _i = 0usize;
let mut has_iter = false;
pounded_var_names!(quote_bind_into_iter!(has_iter {}) () $($inner)*);
if has_iter {
loop {
pounded_var_names!(quote_bind_next_or_break!({}) () $($inner)*);
if _i > 0 {
quote_each_token!($tokens $span $sep);
}
_i += 1;
quote_each_token!($tokens $span $($inner)*);
}
}
quote_each_token!($tokens $span $($inner)*);
}
quote_each_token!($tokens $span $($rest)*);
};
+158 -1
View File
@@ -1,5 +1,162 @@
use ext::TokenStreamExt;
pub use proc_macro2::*;
use ToTokens;
/// 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.
pub mod ext {
use std::slice;
use ToTokens;
/// Extension trait providing the `__quote_into_iter` method on iterators.
pub trait RepIteratorExt: Iterator + Sized {
#[inline]
fn __quote_into_iter(self, has_iter: &mut bool) -> Self {
*has_iter = true;
self
}
}
impl<T: Iterator> RepIteratorExt for T {}
/// Extension trait providing the `__quote_into_iter` method for
/// non-iterable types. These types don't produce an iterator and don't set
/// the `_has_iter` outparameter.
pub trait RepToTokensExt {
/// Pretend to be an iterator for the purposes of `__quote_into_iter`.
/// This allows repeated calls to `__quote_into_iter` to not set the
/// `has_iter` outparameter.
#[inline]
fn next(&self) -> Option<&Self> {
Some(self)
}
#[inline]
fn __quote_into_iter<'a>(&'a self, _has_iter: &mut bool) -> &'a Self {
self
}
}
impl<T: ToTokens + ?Sized> RepToTokensExt for T {}
/// Extension trait providing the `__quote_into_iter` method for types
/// convertable into slices.
///
/// NOTE: This is implemented manually, rather than by using a blanket impl
/// over `AsRef<[T]>` to reduce the chance of ambiguity conflicts with other
/// `__quote_into_iter` methods from this module.
pub trait RepSliceExt {
type Item;
fn as_slice(&self) -> &[Self::Item];
#[inline]
fn __quote_into_iter<'a>(&'a self, has_iter: &mut bool) -> slice::Iter<'a, Self::Item> {
*has_iter = true;
self.as_slice().iter()
}
}
impl<'a, T: RepSliceExt + ?Sized> RepSliceExt for &'a T {
type Item = T::Item;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
<T as RepSliceExt>::as_slice(*self)
}
}
impl<'a, T: RepSliceExt + ?Sized> RepSliceExt for &'a mut T {
type Item = T::Item;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
<T as RepSliceExt>::as_slice(*self)
}
}
impl<T> RepSliceExt for [T] {
type Item = T;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
self
}
}
impl<T> RepSliceExt for Vec<T> {
type Item = T;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
&self[..]
}
}
macro_rules! array_rep_slice {
($($l:tt)*) => {
$(
impl<T> RepSliceExt for [T; $l] {
type Item = T;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
&self[..]
}
}
)*
}
}
array_rep_slice!(
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
);
}
// Helper type used within interpolations to allow for repeated binding names.
// Implements the relevant traits, and exports a dummy `next()` method.
#[derive(Copy, Clone)]
pub struct RepInterp<T>(pub T);
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.
#[inline]
pub fn next(self) -> Option<T> {
Some(self.0)
}
}
impl<T: ext::RepSliceExt> ext::RepSliceExt for RepInterp<T> {
type Item = T::Item;
#[inline]
fn as_slice(&self) -> &[Self::Item] {
self.0.as_slice()
}
}
impl<T: Iterator> Iterator for RepInterp<T> {
type Item = T::Item;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
}
impl<T: ToTokens> ToTokens for RepInterp<T> {
#[inline]
fn to_tokens(&self, tokens: &mut TokenStream) {
self.0.to_tokens(tokens);
}
}
fn is_ident_start(c: u8) -> bool {
(b'a' <= c && c <= b'z') || (b'A' <= c && c <= b'Z') || c == b'_'
@@ -12,7 +169,7 @@ fn is_ident_continue(c: u8) -> bool {
fn is_ident(token: &str) -> bool {
let mut iter = token.bytes();
let first_ok = iter.next().map(is_ident_start).unwrap_or(false);
first_ok && iter.all(is_ident_continue)
}
+63
View File
@@ -234,10 +234,36 @@ fn test_nested_fancy_repetition() {
#[test]
fn test_empty_repetition() {
#[allow(unreachable_code)]
let tokens = quote!(#(a b)* #(c d),*);
assert_eq!("", tokens.to_string());
}
#[test]
fn test_duplicate_name_repetition() {
let foo = &["a", "b"];
let tokens = quote! {
#(#foo: #foo),*
#(#foo: #foo),*
};
let expected = r#""a" : "a" , "b" : "b" "a" : "a" , "b" : "b""#;
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_duplicate_name_repetition_no_copy() {
let foo = vec!["a".to_owned(), "b".to_owned()];
let tokens = quote! {
#(#foo: #foo),*
};
let expected = r#""a" : "a" , "b" : "b""#;
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_variable_name_conflict() {
// The implementation of `#(...),*` uses the variable `_i` but it should be
@@ -248,6 +274,43 @@ fn test_variable_name_conflict() {
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_nonrep_in_repetition() {
let rep = vec!["a", "b"];
let nonrep = "c";
let tokens = quote! {
#(#rep #rep : #nonrep #nonrep),*
};
let expected = r#""a" "a" : "c" "c" , "b" "b" : "c" "c""#;
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_nonrep_only_repetition() {
let nonrep = "a";
// Without the `has_iter` parameter to `__quote_into_iter()`, this would
// loop infinitely.
let tokens = quote!( #(#nonrep)* );
let expected = "";
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_nonrep_only_repetition_dup() {
let nonrep = "a";
// If the `__quote_into_iter()` method for `nonrep` produced a real
// iterator, this would loop infinitely.
let tokens = quote!( #(#nonrep #nonrep)* );
let expected = "";
assert_eq!(expected, tokens.to_string());
}
#[test]
fn test_empty_quote() {
let tokens = quote!();