// 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 { 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::(); let expanded = iter.collect::().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 }