489 lines
12 KiB
Rust
489 lines
12 KiB
Rust
#![feature(proc_macro_quote)]
|
|
#![feature(proc_macro_totokens)]
|
|
|
|
extern crate proc_macro;
|
|
|
|
use std::borrow::Cow;
|
|
use std::collections::BTreeSet;
|
|
use std::ffi::{CStr, CString};
|
|
|
|
use proc_macro::*;
|
|
|
|
#[proc_macro]
|
|
pub fn run_tests(_: TokenStream) -> TokenStream {
|
|
test_quote_impl();
|
|
test_substitution();
|
|
test_iter();
|
|
test_array();
|
|
test_advanced();
|
|
test_integer();
|
|
test_floating();
|
|
test_char();
|
|
test_str();
|
|
test_string();
|
|
test_c_str();
|
|
test_c_string();
|
|
test_interpolated_literal();
|
|
test_ident();
|
|
test_underscore();
|
|
test_duplicate();
|
|
test_fancy_repetition();
|
|
test_nested_fancy_repetition();
|
|
test_duplicate_name_repetition();
|
|
test_duplicate_name_repetition_no_copy();
|
|
test_btreeset_repetition();
|
|
test_variable_name_conflict();
|
|
test_nonrep_in_repetition();
|
|
test_empty_quote();
|
|
test_box_str();
|
|
test_cow();
|
|
test_append_tokens();
|
|
test_outer_line_comment();
|
|
test_inner_line_comment();
|
|
test_outer_block_comment();
|
|
test_inner_block_comment();
|
|
test_outer_attr();
|
|
test_inner_attr();
|
|
test_star_after_repetition();
|
|
test_quote_raw_id();
|
|
|
|
TokenStream::new()
|
|
}
|
|
|
|
// Based on https://github.com/dtolnay/quote/blob/0245506323a3616daa2ee41c6ad0b871e4d78ae4/tests/test.rs
|
|
//
|
|
// FIXME(quote):
|
|
// The following tests are removed because they are not supported yet in `proc_macro::quote!`
|
|
//
|
|
// - quote_spanned:
|
|
// - fn test_quote_spanned_impl
|
|
// - fn test_type_inference_for_span
|
|
// - wrong-type-span.rs
|
|
// - format_ident:
|
|
// - fn test_closure
|
|
// - fn test_format_ident
|
|
// - fn test_format_ident_strip_raw
|
|
|
|
struct X;
|
|
|
|
impl ToTokens for X {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
Ident::new("X", Span::call_site()).to_tokens(tokens)
|
|
}
|
|
}
|
|
|
|
fn test_quote_impl() {
|
|
let tokens = quote! {
|
|
impl<'a, T: ToTokens> ToTokens for &'a T {
|
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
|
(**self).to_tokens(tokens)
|
|
}
|
|
}
|
|
};
|
|
|
|
let expected = r#"impl < 'a, T : ToTokens > ToTokens for & 'a T
|
|
{
|
|
fn to_tokens(& self, tokens : & mut TokenStream)
|
|
{ (** self).to_tokens(tokens) }
|
|
}"#;
|
|
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_substitution() {
|
|
let x = X;
|
|
let tokens = quote!($x <$x> ($x) [$x] {$x});
|
|
|
|
let expected = "X <X > (X) [X] { X }";
|
|
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_iter() {
|
|
let primes = &[X, X, X, X];
|
|
|
|
assert_eq!("X X X X", quote!($($primes)*).to_string());
|
|
|
|
assert_eq!("X, X, X, X,", quote!($($primes,)*).to_string());
|
|
|
|
assert_eq!("X, X, X, X", quote!($($primes),*).to_string());
|
|
}
|
|
|
|
fn test_array() {
|
|
let array: [u8; 40] = [0; 40];
|
|
let _ = quote!($($array $array)*);
|
|
|
|
let ref_array: &[u8; 40] = &[0; 40];
|
|
let _ = quote!($($ref_array $ref_array)*);
|
|
|
|
let ref_slice: &[u8] = &[0; 40];
|
|
let _ = quote!($($ref_slice $ref_slice)*);
|
|
|
|
let array: [X; 2] = [X, X]; // !Copy
|
|
let _ = quote!($($array $array)*);
|
|
|
|
let ref_array: &[X; 2] = &[X, X];
|
|
let _ = quote!($($ref_array $ref_array)*);
|
|
|
|
let ref_slice: &[X] = &[X, X];
|
|
let _ = quote!($($ref_slice $ref_slice)*);
|
|
|
|
let array_of_array: [[u8; 2]; 2] = [[0; 2]; 2];
|
|
let _ = quote!($($($array_of_array)*)*);
|
|
}
|
|
|
|
fn test_advanced() {
|
|
let generics = quote!( <'a, T> );
|
|
|
|
let where_clause = quote!( where T: Serialize );
|
|
|
|
let field_ty = quote!(String);
|
|
|
|
let item_ty = quote!(Cow<'a, str>);
|
|
|
|
let path = quote!(SomeTrait::serialize_with);
|
|
|
|
let value = quote!(self.x);
|
|
|
|
let tokens = quote! {
|
|
struct SerializeWith $generics $where_clause {
|
|
value: &'a $field_ty,
|
|
phantom: ::std::marker::PhantomData<$item_ty>,
|
|
}
|
|
|
|
impl $generics ::serde::Serialize for SerializeWith $generics $where_clause {
|
|
fn serialize<S>(&self, s: &mut S) -> Result<(), S::Error>
|
|
where S: ::serde::Serializer
|
|
{
|
|
$path(self.value, s)
|
|
}
|
|
}
|
|
|
|
SerializeWith {
|
|
value: $value,
|
|
phantom: ::std::marker::PhantomData::<$item_ty>,
|
|
}
|
|
};
|
|
|
|
let expected = r#"struct SerializeWith < 'a, T > where T : Serialize
|
|
{
|
|
value : & 'a String, phantom : :: std :: marker :: PhantomData <Cow < 'a,
|
|
str > >,
|
|
} impl < 'a, T > :: serde :: Serialize for SerializeWith < 'a, T > where T :
|
|
Serialize
|
|
{
|
|
fn serialize < S > (& self, s : & mut S) -> Result < (), S :: Error >
|
|
where S : :: serde :: Serializer
|
|
{ SomeTrait :: serialize_with(self.value, s) }
|
|
} SerializeWith
|
|
{
|
|
value : self.x, phantom : :: std :: marker :: PhantomData ::<Cow < 'a, str
|
|
> >,
|
|
}"#;
|
|
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_integer() {
|
|
let ii8 = -1i8;
|
|
let ii16 = -1i16;
|
|
let ii32 = -1i32;
|
|
let ii64 = -1i64;
|
|
let ii128 = -1i128;
|
|
let iisize = -1isize;
|
|
let uu8 = 1u8;
|
|
let uu16 = 1u16;
|
|
let uu32 = 1u32;
|
|
let uu64 = 1u64;
|
|
let uu128 = 1u128;
|
|
let uusize = 1usize;
|
|
|
|
let tokens = quote! {
|
|
1 1i32 1u256
|
|
$ii8 $ii16 $ii32 $ii64 $ii128 $iisize
|
|
$uu8 $uu16 $uu32 $uu64 $uu128 $uusize
|
|
};
|
|
let expected = r#"1 1i32 1u256 -1i8 -1i16 -1i32 -1i64 -1i128 -1isize 1u8 1u16 1u32 1u64 1u128
|
|
1usize"#;
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_floating() {
|
|
let e32 = 2.345f32;
|
|
|
|
let e64 = 2.345f64;
|
|
|
|
let tokens = quote! {
|
|
$e32
|
|
$e64
|
|
};
|
|
let expected = concat!("2.345f32 2.345f64");
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_char() {
|
|
let zero = '\u{1}';
|
|
let dollar = '$';
|
|
let pound = '#';
|
|
let quote = '"';
|
|
let apost = '\'';
|
|
let newline = '\n';
|
|
let heart = '\u{2764}';
|
|
|
|
let tokens = quote! {
|
|
$zero $dollar $pound $quote $apost $newline $heart
|
|
};
|
|
let expected = "'\\u{1}' '$' '#' '\"' '\\'' '\\n' '\u{2764}'";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_str() {
|
|
let s = "\u{1} a 'b \" c";
|
|
let tokens = quote!($s);
|
|
let expected = "\"\\u{1} a 'b \\\" c\"";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_string() {
|
|
let s = "\u{1} a 'b \" c".to_string();
|
|
let tokens = quote!($s);
|
|
let expected = "\"\\u{1} a 'b \\\" c\"";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_c_str() {
|
|
let s = CStr::from_bytes_with_nul(b"\x01 a 'b \" c\0").unwrap();
|
|
let tokens = quote!($s);
|
|
let expected = "c\"\\u{1} a 'b \\\" c\"";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_c_string() {
|
|
let s = CString::new(&b"\x01 a 'b \" c"[..]).unwrap();
|
|
let tokens = quote!($s);
|
|
let expected = "c\"\\u{1} a 'b \\\" c\"";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_interpolated_literal() {
|
|
macro_rules! m {
|
|
($literal:literal) => {
|
|
quote!($literal)
|
|
};
|
|
}
|
|
|
|
let tokens = m!(1);
|
|
let expected = "1";
|
|
assert_eq!(expected, tokens.to_string());
|
|
|
|
let tokens = m!(-1);
|
|
let expected = "- 1";
|
|
assert_eq!(expected, tokens.to_string());
|
|
|
|
let tokens = m!(true);
|
|
let expected = "true";
|
|
assert_eq!(expected, tokens.to_string());
|
|
|
|
let tokens = m!(-true);
|
|
let expected = "- true";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_ident() {
|
|
let foo = Ident::new("Foo", Span::call_site());
|
|
let bar = Ident::new(&format!("Bar{}", 7), Span::call_site());
|
|
let tokens = quote!(struct $foo; enum $bar {});
|
|
let expected = "struct Foo; enum Bar7 {}";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_underscore() {
|
|
let tokens = quote!(let _;);
|
|
let expected = "let _;";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_duplicate() {
|
|
let ch = 'x';
|
|
|
|
let tokens = quote!($ch $ch);
|
|
|
|
let expected = "'x' 'x'";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_fancy_repetition() {
|
|
let foo = vec!["a", "b"];
|
|
let bar = vec![true, false];
|
|
|
|
let tokens = quote! {
|
|
$($foo: $bar),*
|
|
};
|
|
|
|
let expected = r#""a" : true, "b" : false"#;
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_nested_fancy_repetition() {
|
|
let nested = vec![vec!['a', 'b', 'c'], vec!['x', 'y', 'z']];
|
|
|
|
let tokens = quote! {
|
|
$(
|
|
$($nested)*
|
|
),*
|
|
};
|
|
|
|
let expected = "'a' 'b' 'c', 'x' 'y' 'z'";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
fn test_btreeset_repetition() {
|
|
let mut set = BTreeSet::new();
|
|
set.insert("a".to_owned());
|
|
set.insert("b".to_owned());
|
|
|
|
let tokens = quote! {
|
|
$($set: $set),*
|
|
};
|
|
|
|
let expected = r#""a" : "a", "b" : "b""#;
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_variable_name_conflict() {
|
|
// The implementation of `#(...),*` uses the variable `_i` but it should be
|
|
// fine, if a little confusing when debugging.
|
|
let _i = vec!['a', 'b'];
|
|
let tokens = quote! { $($_i),* };
|
|
let expected = "'a', 'b'";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
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());
|
|
}
|
|
|
|
fn test_empty_quote() {
|
|
let tokens = quote!();
|
|
assert_eq!("", tokens.to_string());
|
|
}
|
|
|
|
fn test_box_str() {
|
|
let b = "str".to_owned().into_boxed_str();
|
|
let tokens = quote! { $b };
|
|
assert_eq!("\"str\"", tokens.to_string());
|
|
}
|
|
|
|
fn test_cow() {
|
|
let owned: Cow<Ident> = Cow::Owned(Ident::new("owned", Span::call_site()));
|
|
|
|
let ident = Ident::new("borrowed", Span::call_site());
|
|
let borrowed = Cow::Borrowed(&ident);
|
|
|
|
let tokens = quote! { $owned $borrowed };
|
|
assert_eq!("owned borrowed", tokens.to_string());
|
|
}
|
|
|
|
fn test_append_tokens() {
|
|
let mut a = quote!(a);
|
|
let b = quote!(b);
|
|
a.extend(b);
|
|
assert_eq!("a b", a.to_string());
|
|
}
|
|
|
|
fn test_outer_line_comment() {
|
|
let tokens = quote! {
|
|
/// doc
|
|
};
|
|
let expected = "#[doc = \" doc\"]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_inner_line_comment() {
|
|
let tokens = quote! {
|
|
//! doc
|
|
};
|
|
let expected = "# ! [doc = \" doc\"]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_outer_block_comment() {
|
|
let tokens = quote! {
|
|
/** doc */
|
|
};
|
|
let expected = "#[doc = \" doc \"]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_inner_block_comment() {
|
|
let tokens = quote! {
|
|
/*! doc */
|
|
};
|
|
let expected = "# ! [doc = \" doc \"]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_outer_attr() {
|
|
let tokens = quote! {
|
|
#[inline]
|
|
};
|
|
let expected = "#[inline]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_inner_attr() {
|
|
let tokens = quote! {
|
|
#![no_std]
|
|
};
|
|
let expected = "#! [no_std]";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
// https://github.com/dtolnay/quote/issues/130
|
|
fn test_star_after_repetition() {
|
|
let c = vec!['0', '1'];
|
|
let tokens = quote! {
|
|
$(
|
|
f($c);
|
|
)*
|
|
*out = None;
|
|
};
|
|
let expected = "f('0'); f('1'); * out = None;";
|
|
assert_eq!(expected, tokens.to_string());
|
|
}
|
|
|
|
fn test_quote_raw_id() {
|
|
let id = quote!(r#raw_id);
|
|
assert_eq!(id.to_string(), "r#raw_id");
|
|
}
|