diff options
Diffstat (limited to 'tests/ui/proc-macro/auxiliary/expand-expr.rs')
| -rw-r--r-- | tests/ui/proc-macro/auxiliary/expand-expr.rs | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/tests/ui/proc-macro/auxiliary/expand-expr.rs b/tests/ui/proc-macro/auxiliary/expand-expr.rs new file mode 100644 index 00000000000..1d6ef8a1361 --- /dev/null +++ b/tests/ui/proc-macro/auxiliary/expand-expr.rs @@ -0,0 +1,166 @@ +// force-host +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![deny(warnings)] +#![feature(proc_macro_expand, proc_macro_span)] + +extern crate proc_macro; + +use proc_macro::*; +use std::str::FromStr; + +// Flatten the TokenStream, removing any toplevel `Delimiter::None`s for +// comparison. +fn flatten(ts: TokenStream) -> Vec<TokenTree> { + ts.into_iter() + .flat_map(|tt| match &tt { + TokenTree::Group(group) if group.delimiter() == Delimiter::None => { + flatten(group.stream()) + } + _ => vec![tt], + }) + .collect() +} + +// Assert that two TokenStream values are roughly equal to one-another. +fn assert_ts_eq(lhs: &TokenStream, rhs: &TokenStream) { + let ltts = flatten(lhs.clone()); + let rtts = flatten(rhs.clone()); + + if ltts.len() != rtts.len() { + panic!( + "expected the same number of tts ({} == {})\nlhs:\n{:#?}\nrhs:\n{:#?}", + ltts.len(), + rtts.len(), + lhs, + rhs + ) + } + + for (ltt, rtt) in ltts.iter().zip(&rtts) { + match (ltt, rtt) { + (TokenTree::Group(l), TokenTree::Group(r)) => { + assert_eq!( + l.delimiter(), + r.delimiter(), + "expected delimiters to match for {:?} and {:?}", + l, + r + ); + assert_ts_eq(&l.stream(), &r.stream()); + } + (TokenTree::Punct(l), TokenTree::Punct(r)) => assert_eq!( + (l.as_char(), l.spacing()), + (r.as_char(), r.spacing()), + "expected punct to match for {:?} and {:?}", + l, + r + ), + (TokenTree::Ident(l), TokenTree::Ident(r)) => assert_eq!( + l.to_string(), + r.to_string(), + "expected ident to match for {:?} and {:?}", + l, + r + ), + (TokenTree::Literal(l), TokenTree::Literal(r)) => assert_eq!( + l.to_string(), + r.to_string(), + "expected literal to match for {:?} and {:?}", + l, + r + ), + (l, r) => panic!("expected type to match for {:?} and {:?}", l, r), + } + } +} + +#[proc_macro] +pub fn expand_expr_is(input: TokenStream) -> TokenStream { + let mut iter = input.into_iter(); + let mut expected_tts = Vec::new(); + let comma = loop { + match iter.next() { + Some(TokenTree::Punct(p)) if p.as_char() == ',' => break p, + Some(tt) => expected_tts.push(tt), + None => panic!("expected comma"), + } + }; + + // Make sure that `Ident` and `Literal` objects from this proc-macro's + // environment are not invalidated when `expand_expr` recursively invokes + // another macro by taking a local copy, and checking it after the fact. + let pre_expand_span = comma.span(); + let pre_expand_ident = Ident::new("ident", comma.span()); + let pre_expand_literal = Literal::string("literal"); + let pre_expand_call_site = Span::call_site(); + + let expected = expected_tts.into_iter().collect::<TokenStream>(); + let expanded = iter.collect::<TokenStream>().expand_expr().expect("expand_expr failed"); + assert!( + expected.to_string() == expanded.to_string(), + "assert failed\nexpected: `{}`\nexpanded: `{}`", + expected.to_string(), + expanded.to_string() + ); + + // Also compare the raw tts to make sure they line up. + assert_ts_eq(&expected, &expanded); + + assert!(comma.span().eq(&pre_expand_span), "pre-expansion span is still equal"); + assert_eq!(pre_expand_ident.to_string(), "ident", "pre-expansion identifier is still valid"); + assert_eq!( + pre_expand_literal.to_string(), + "\"literal\"", + "pre-expansion literal is still valid" + ); + assert!(Span::call_site().eq(&pre_expand_call_site), "pre-expansion call-site is still equal"); + + TokenStream::new() +} + +#[proc_macro] +pub fn expand_expr_fail(input: TokenStream) -> TokenStream { + match input.expand_expr() { + Ok(ts) => panic!("expand_expr unexpectedly succeeded: `{}`", ts), + Err(_) => TokenStream::new(), + } +} + +#[proc_macro] +pub fn check_expand_expr_file(ts: TokenStream) -> TokenStream { + // Check that the passed in `file!()` invocation and a parsed `file!` + // invocation expand to the same literal. + let input_t = ts.expand_expr().expect("expand_expr failed on macro input").to_string(); + let parse_t = TokenStream::from_str("file!{}") + .unwrap() + .expand_expr() + .expect("expand_expr failed on internal macro") + .to_string(); + assert_eq!(input_t, parse_t); + + // Check that the literal matches `Span::call_site().source_file().path()` + let expect_t = + Literal::string(&Span::call_site().source_file().path().to_string_lossy()).to_string(); + assert_eq!(input_t, expect_t); + + TokenStream::new() +} + +#[proc_macro] +pub fn recursive_expand(_: TokenStream) -> TokenStream { + // Recursively call until we hit the recursion limit and get an error. + // + // NOTE: This doesn't panic if expansion fails because that'll cause a very + // large number of errors to fill the output. + TokenStream::from_str("recursive_expand!{}") + .unwrap() + .expand_expr() + .unwrap_or(std::iter::once(TokenTree::Literal(Literal::u32_suffixed(0))).collect()) +} + +#[proc_macro] +pub fn echo_pm(input: TokenStream) -> TokenStream { + input +} |
