Skip to content

Commit

Permalink
Implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed May 25, 2022
1 parent fe9c64d commit 03cc60a
Show file tree
Hide file tree
Showing 6 changed files with 576 additions and 80 deletions.
182 changes: 105 additions & 77 deletions compiler/rustc_builtin_macros/src/assert.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
mod context;

use crate::edition_panic::use_panic_2021;
use rustc_ast::ptr::P;
use rustc_ast::token;
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
use rustc_ast::{self as ast, *};
use rustc_ast::{Expr, ExprKind, MacArgs, MacCall, MacDelimiter, Path, PathSegment, UnOp};
use rustc_ast_pretty::pprust;
use rustc_errors::{Applicability, PResult};
use rustc_expand::base::*;
use rustc_expand::base::{DummyResult, ExtCtxt, MacEager, MacResult};
use rustc_parse::parser::Parser;
use rustc_span::symbol::{sym, Ident, Symbol};
use rustc_span::{Span, DUMMY_SP};
Expand All @@ -15,7 +17,7 @@ pub fn expand_assert<'cx>(
span: Span,
tts: TokenStream,
) -> Box<dyn MacResult + 'cx> {
let Assert { cond_expr, custom_message } = match parse_assert(cx, span, tts) {
let Assert { cond_expr, custom_message } = match Assert::parse(cx, span, tts) {
Ok(assert) => assert,
Err(mut err) => {
err.emit();
Expand All @@ -25,13 +27,13 @@ pub fn expand_assert<'cx>(

// `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(span);
let call_site_span = cx.with_call_site_ctxt(span);

let panic_call = if let Some(tokens) = custom_message {
let path = if use_panic_2021(span) {
let panic_path = || {
if use_panic_2021(span) {
// On edition 2021, we always call `$crate::panic::panic_2021!()`.
Path {
span: sp,
span: call_site_span,
segments: cx
.std_path(&[sym::panic, sym::panic_2021])
.into_iter()
Expand All @@ -42,27 +44,39 @@ pub fn expand_assert<'cx>(
} else {
// Before edition 2021, we call `panic!()` unqualified,
// such that it calls either `std::panic!()` or `core::panic!()`.
Path::from_ident(Ident::new(sym::panic, sp))
};
// Pass the custom message to panic!().
cx.expr(
sp,
Path::from_ident(Ident::new(sym::panic, call_site_span))
}
};

// Simply uses the user provided message instead of generating custom outputs
let expr = if let Some(tokens) = custom_message {
let then = cx.expr(
call_site_span,
ExprKind::MacCall(MacCall {
path,
path: panic_path(),
args: P(MacArgs::Delimited(
DelimSpan::from_single(sp),
DelimSpan::from_single(call_site_span),
MacDelimiter::Parenthesis,
tokens,
)),
prior_type_ascription: None,
}),
)
} else {
);
expr_if_not(cx, call_site_span, cond_expr, then, None)
}
// If `generic_assert` is enabled, generates rich captured outputs
//
// FIXME(c410-f3r) See https://github.com/rust-lang/rust/issues/96949
else if let Some(features) = cx.ecfg.features && features.generic_assert {
context::Context::new(cx, call_site_span).build(cond_expr, panic_path())
}
// If `generic_assert` is not enabled, outputs "assertion failed: ..."
else {
// Pass our own message directly to $crate::panicking::panic(),
// because it might contain `{` and `}` that should always be
// passed literally.
cx.expr_call_global(
sp,
let then = cx.expr_call_global(
call_site_span,
cx.std_path(&[sym::panicking, sym::panic]),
vec![cx.expr_str(
DUMMY_SP,
Expand All @@ -71,82 +85,96 @@ pub fn expand_assert<'cx>(
pprust::expr_to_string(&cond_expr).escape_debug()
)),
)],
)
);
expr_if_not(cx, call_site_span, cond_expr, then, None)
};
let if_expr =
cx.expr_if(sp, cx.expr(sp, ExprKind::Unary(UnOp::Not, cond_expr)), panic_call, None);
MacEager::expr(if_expr)

MacEager::expr(expr)
}

struct Assert {
cond_expr: P<ast::Expr>,
cond_expr: P<Expr>,
custom_message: Option<TokenStream>,
}

fn parse_assert<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
let mut parser = cx.new_parser_from_tts(stream);
impl Assert {
fn parse<'a>(cx: &mut ExtCtxt<'a>, sp: Span, stream: TokenStream) -> PResult<'a, Assert> {
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();
// );
//
// Emit an error about semicolon and suggest removing it.
if parser.token == token::Semi {
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
err.span_suggestion(
parser.token.span,
"try removing semicolon",
String::new(),
Applicability::MaybeIncorrect,
);
err.emit();
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);
}

parser.bump();
}
let cond_expr = parser.parse_expr()?;

// Some crates use the `assert!` macro in the following form (note missing comma before
// message):
//
// assert!(true "error message");
//
// Emit an error and suggest inserting a comma.
let custom_message =
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
let comma_span = parser.prev_token.span.shrink_to_hi();
err.span_suggestion_short(
comma_span,
"try adding a comma",
", ".to_string(),
// Some crates use the `assert!` macro in the following form (note extra semicolon):
//
// assert!(
// my_function();
// );
//
// Emit an error about semicolon and suggest removing it.
if parser.token == token::Semi {
let mut err = cx.struct_span_err(sp, "macro requires an expression as an argument");
err.span_suggestion(
parser.token.span,
"try removing semicolon",
String::new(),
Applicability::MaybeIncorrect,
);
err.emit();

parse_custom_message(&mut parser)
} else if parser.eat(&token::Comma) {
parse_custom_message(&mut parser)
} else {
None
};
parser.bump();
}

// Some crates use the `assert!` macro in the following form (note missing comma before
// message):
//
// assert!(true "error message");
//
// Emit an error and suggest inserting a comma.
let custom_message =
if let token::Literal(token::Lit { kind: token::Str, .. }) = parser.token.kind {
let mut err = cx.struct_span_err(parser.token.span, "unexpected string literal");
let comma_span = parser.prev_token.span.shrink_to_hi();
err.span_suggestion_short(
comma_span,
"try adding a comma",
", ".to_string(),
Applicability::MaybeIncorrect,
);
err.emit();

if parser.token != token::Eof {
return parser.unexpected();
Self::parse_custom_message(&mut parser)
} else if parser.eat(&token::Comma) {
Self::parse_custom_message(&mut parser)
} else {
None
};

if parser.token != token::Eof {
return parser.unexpected();
}

Ok(Assert { cond_expr, custom_message })
}

Ok(Assert { cond_expr, custom_message })
fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
let ts = parser.parse_tokens();
if !ts.is_empty() { Some(ts) } else { None }
}
}

fn parse_custom_message(parser: &mut Parser<'_>) -> Option<TokenStream> {
let ts = parser.parse_tokens();
if !ts.is_empty() { Some(ts) } else { None }
// if !{ ... } { ... } else { ... }
fn expr_if_not(
cx: &ExtCtxt<'_>,
span: Span,
cond: P<Expr>,
then: P<Expr>,
els: Option<P<Expr>>,
) -> P<Expr> {
cx.expr_if(span, cx.expr(span, ExprKind::Unary(UnOp::Not, cond)), then, els)
}
Loading

0 comments on commit 03cc60a

Please sign in to comment.