use errors::{Applicability, DiagnosticBuilder}; use rustc_parse::parser::Parser; use syntax::ast::{self, *}; use syntax::token::{self, TokenKind}; use syntax::print::pprust; use syntax::ptr::P; use syntax::symbol::{sym, Symbol}; use syntax::tokenstream::{DelimSpan, TokenStream, TokenTree}; use syntax_expand::base::*; use syntax_pos::{Span, DUMMY_SP}; pub fn expand_assert<'cx>( cx: &'cx mut ExtCtxt<'_>, sp: Span, tts: TokenStream, ) -> Box { let Assert { cond_expr, custom_message } = match parse_assert(cx, sp, tts) { Ok(assert) => assert, Err(mut err) => { err.emit(); return DummyResult::any(sp); } }; // `core::panic` and `std::panic` are different macros, so we use call-site // context to pick up whichever is currently in scope. let sp = cx.with_call_site_ctxt(sp); let tokens = custom_message.unwrap_or_else(|| { TokenStream::from(TokenTree::token( TokenKind::lit(token::Str, Symbol::intern(&format!( "assertion failed: {}", pprust::expr_to_string(&cond_expr).escape_debug() )), None), DUMMY_SP, )) }); let args = P(MacArgs::Delimited(DelimSpan::from_single(sp), MacDelimiter::Parenthesis, tokens)); let panic_call = Mac { path: Path::from_ident(Ident::new(sym::panic, sp)), args, prior_type_ascription: None, }; let if_expr = cx.expr_if( sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), cx.expr( sp, ExprKind::Mac(panic_call), ), None, ); MacEager::expr(if_expr) } struct Assert { cond_expr: P, custom_message: Option, } fn parse_assert<'a>( cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream ) -> Result> { let mut parser = cx.new_parser_from_tts(stream); if parser.token == token::Eof { let mut err = cx.struct_span_err(sp, "macro requires a boolean expression as an argument"); err.span_label(sp, "boolean expression required"); return Err(err); } let cond_expr = parser.parse_expr()?; // Some crates use the `assert!` macro in the following form (note extra semicolon): // // assert!( // my_function(); // ); // // Warn about semicolon and suggest removing it. Eventually, this should be turned into an // error. if parser.token == token::Semi { let mut err = cx.struct_span_warn(sp, "macro requires an expression as an argument"); err.span_suggestion( parser.token.span, "try removing semicolon", String::new(), Applicability::MaybeIncorrect ); err.note("this is going to be an error in the future"); err.emit(); parser.bump(); } // Some crates use the `assert!` macro in the following form (note missing comma before // message): // // assert!(true "error message"); // // Parse this as an actual message, and suggest inserting a comma. Eventually, this should be // turned into an error. let custom_message = if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind { let mut err = cx.struct_span_warn(parser.token.span, "unexpected string literal"); let comma_span = cx.source_map().next_point(parser.prev_span); err.span_suggestion_short( comma_span, "try adding a comma", ", ".to_string(), Applicability::MaybeIncorrect ); err.note("this is going to be an error in the future"); err.emit(); parse_custom_message(&mut parser) } else if parser.eat(&token::Comma) { parse_custom_message(&mut parser) } else { None }; if parser.token != token::Eof { parser.expect_one_of(&[], &[])?; unreachable!(); } Ok(Assert { cond_expr, custom_message }) } fn parse_custom_message(parser: &mut Parser<'_>) -> Option { let ts = parser.parse_tokens(); if !ts.is_empty() { Some(ts) } else { None } }