rustc_builtin_macros/assert/
context.rs1use rustc_ast::token::{self, Delimiter, IdentIsRaw};
2use rustc_ast::tokenstream::{DelimSpan, TokenStream, TokenTree};
3use rustc_ast::{
4 BinOpKind, BorrowKind, DUMMY_NODE_ID, DelimArgs, Expr, ExprKind, ItemKind, MacCall, MethodCall,
5 Mutability, Path, PathSegment, Stmt, StructRest, UnOp, UseTree, UseTreeKind,
6};
7use rustc_ast_pretty::pprust;
8use rustc_data_structures::fx::FxHashSet;
9use rustc_expand::base::ExtCtxt;
10use rustc_span::{Ident, Span, Symbol, sym};
11use thin_vec::{ThinVec, thin_vec};
12
13pub(super) struct Context<'cx, 'a> {
14 best_case_captures: Vec<Stmt>,
19 capture_decls: Vec<Capture>,
21 cx: &'cx ExtCtxt<'a>,
22 fmt_string: String,
24 is_consumed: bool,
27 local_bind_decls: Vec<Stmt>,
29 paths: FxHashSet<Ident>,
36 span: Span,
37}
38
39impl<'cx, 'a> Context<'cx, 'a> {
40 pub(super) fn new(cx: &'cx ExtCtxt<'a>, span: Span) -> Self {
41 Self {
42 best_case_captures: <_>::default(),
43 capture_decls: <_>::default(),
44 cx,
45 fmt_string: <_>::default(),
46 is_consumed: true,
47 local_bind_decls: <_>::default(),
48 paths: <_>::default(),
49 span,
50 }
51 }
52
53 pub(super) fn build(mut self, mut cond_expr: Box<Expr>, panic_path: Path) -> Box<Expr> {
73 let expr_str = pprust::expr_to_string(&cond_expr);
74 self.manage_cond_expr(&mut cond_expr);
75 let initial_imports = self.build_initial_imports();
76 let panic = self.build_panic(&expr_str, panic_path);
77 let cond_expr_with_unlikely = self.build_unlikely(cond_expr);
78
79 let Self { best_case_captures, capture_decls, cx, local_bind_decls, span, .. } = self;
80
81 let mut assert_then_stmts = ThinVec::with_capacity(2);
82 assert_then_stmts.extend(best_case_captures);
83 assert_then_stmts.push(self.cx.stmt_expr(panic));
84 let assert_then = self.cx.block(span, assert_then_stmts);
85
86 let mut stmts = ThinVec::with_capacity(4);
87 stmts.push(initial_imports);
88 stmts.extend(capture_decls.into_iter().map(|c| c.decl));
89 stmts.extend(local_bind_decls);
90 stmts.push(
91 cx.stmt_expr(cx.expr(span, ExprKind::If(cond_expr_with_unlikely, assert_then, None))),
92 );
93 cx.expr_block(cx.block(span, stmts))
94 }
95
96 fn build_initial_imports(&self) -> Stmt {
100 let nested_tree = |this: &Self, sym| {
101 (
102 UseTree {
103 prefix: this.cx.path(this.span, vec![Ident::with_dummy_span(sym)]),
104 kind: UseTreeKind::Simple(None),
105 span: this.span,
106 },
107 DUMMY_NODE_ID,
108 )
109 };
110 self.cx.stmt_item(
111 self.span,
112 self.cx.item(
113 self.span,
114 thin_vec![self.cx.attr_nested_word(sym::allow, sym::unused_imports, self.span)],
115 ItemKind::Use(UseTree {
116 prefix: self.cx.path(self.span, self.cx.std_path(&[sym::asserting])),
117 kind: UseTreeKind::Nested {
118 items: thin_vec![
119 nested_tree(self, sym::TryCaptureGeneric),
120 nested_tree(self, sym::TryCapturePrintable),
121 ],
122 span: self.span,
123 },
124 span: self.span,
125 }),
126 ),
127 )
128 }
129
130 fn build_unlikely(&self, cond_expr: Box<Expr>) -> Box<Expr> {
132 let unlikely_path = self.cx.std_path(&[sym::intrinsics, sym::unlikely]);
133 self.cx.expr_call(
134 self.span,
135 self.cx.expr_path(self.cx.path(self.span, unlikely_path)),
136 thin_vec![self.cx.expr(self.span, ExprKind::Unary(UnOp::Not, cond_expr))],
137 )
138 }
139
140 fn build_panic(&self, expr_str: &str, panic_path: Path) -> Box<Expr> {
148 let escaped_expr_str = escape_to_fmt(expr_str);
149 let initial = [
150 TokenTree::token_joint(
151 token::Literal(token::Lit {
152 kind: token::LitKind::Str,
153 symbol: Symbol::intern(&if self.fmt_string.is_empty() {
154 format!("Assertion failed: {escaped_expr_str}")
155 } else {
156 format!(
157 "Assertion failed: {escaped_expr_str}\nWith captures:\n{}",
158 &self.fmt_string
159 )
160 }),
161 suffix: None,
162 }),
163 self.span,
164 ),
165 TokenTree::token_alone(token::Comma, self.span),
166 ];
167 let captures = self.capture_decls.iter().flat_map(|cap| {
168 [
169 TokenTree::token_joint(
170 token::Ident(cap.ident.name, IdentIsRaw::No),
171 cap.ident.span,
172 ),
173 TokenTree::token_alone(token::Comma, self.span),
174 ]
175 });
176 self.cx.expr(
177 self.span,
178 ExprKind::MacCall(Box::new(MacCall {
179 path: panic_path,
180 args: Box::new(DelimArgs {
181 dspan: DelimSpan::from_single(self.span),
182 delim: Delimiter::Parenthesis,
183 tokens: initial.into_iter().chain(captures).collect::<TokenStream>(),
184 }),
185 })),
186 )
187 }
188
189 fn manage_cond_expr(&mut self, expr: &mut Box<Expr>) {
193 match &mut expr.kind {
194 ExprKind::AddrOf(_, mutability, local_expr) => {
195 self.with_is_consumed_management(matches!(mutability, Mutability::Mut), |this| {
196 this.manage_cond_expr(local_expr)
197 });
198 }
199 ExprKind::Array(local_exprs) => {
200 for local_expr in local_exprs {
201 self.manage_cond_expr(local_expr);
202 }
203 }
204 ExprKind::Binary(op, lhs, rhs) => {
205 self.with_is_consumed_management(
206 matches!(
207 op.node,
208 BinOpKind::Add
209 | BinOpKind::And
210 | BinOpKind::BitAnd
211 | BinOpKind::BitOr
212 | BinOpKind::BitXor
213 | BinOpKind::Div
214 | BinOpKind::Mul
215 | BinOpKind::Or
216 | BinOpKind::Rem
217 | BinOpKind::Shl
218 | BinOpKind::Shr
219 | BinOpKind::Sub
220 ),
221 |this| {
222 this.manage_cond_expr(lhs);
223 this.manage_cond_expr(rhs);
224 },
225 );
226 }
227 ExprKind::Call(_, local_exprs) => {
228 for local_expr in local_exprs {
229 self.manage_cond_expr(local_expr);
230 }
231 }
232 ExprKind::Cast(local_expr, _) => {
233 self.manage_cond_expr(local_expr);
234 }
235 ExprKind::If(local_expr, _, _) => {
236 self.manage_cond_expr(local_expr);
237 }
238 ExprKind::Index(prefix, suffix, _) => {
239 self.manage_cond_expr(prefix);
240 self.manage_cond_expr(suffix);
241 }
242 ExprKind::Let(_, local_expr, _, _) => {
243 self.manage_cond_expr(local_expr);
244 }
245 ExprKind::Match(local_expr, ..) => {
246 self.manage_cond_expr(local_expr);
247 }
248 ExprKind::MethodCall(call) => {
249 for arg in &mut call.args {
250 self.manage_cond_expr(arg);
251 }
252 }
253 ExprKind::Path(_, Path { segments, .. }) if let [path_segment] = &segments[..] => {
254 let path_ident = path_segment.ident;
255 self.manage_initial_capture(expr, path_ident);
256 }
257 ExprKind::Paren(local_expr) => {
258 self.manage_cond_expr(local_expr);
259 }
260 ExprKind::Range(prefix, suffix, _) => {
261 if let Some(elem) = prefix {
262 self.manage_cond_expr(elem);
263 }
264 if let Some(elem) = suffix {
265 self.manage_cond_expr(elem);
266 }
267 }
268 ExprKind::Repeat(local_expr, elem) => {
269 self.manage_cond_expr(local_expr);
270 self.manage_cond_expr(&mut elem.value);
271 }
272 ExprKind::Struct(elem) => {
273 for field in &mut elem.fields {
274 self.manage_cond_expr(&mut field.expr);
275 }
276 if let StructRest::Base(local_expr) = &mut elem.rest {
277 self.manage_cond_expr(local_expr);
278 }
279 }
280 ExprKind::Tup(local_exprs) => {
281 for local_expr in local_exprs {
282 self.manage_cond_expr(local_expr);
283 }
284 }
285 ExprKind::Unary(un_op, local_expr) => {
286 self.with_is_consumed_management(matches!(un_op, UnOp::Neg | UnOp::Not), |this| {
287 this.manage_cond_expr(local_expr)
288 });
289 }
290 ExprKind::Assign(_, _, _)
295 | ExprKind::AssignOp(_, _, _)
296 | ExprKind::Gen(_, _, _, _)
297 | ExprKind::Await(_, _)
298 | ExprKind::Use(_, _)
299 | ExprKind::Block(_, _)
300 | ExprKind::Break(_, _)
301 | ExprKind::Closure(_)
302 | ExprKind::ConstBlock(_)
303 | ExprKind::Continue(_)
304 | ExprKind::Dummy
305 | ExprKind::Err(_)
306 | ExprKind::Field(_, _)
307 | ExprKind::ForLoop { .. }
308 | ExprKind::FormatArgs(_)
309 | ExprKind::IncludedBytes(..)
310 | ExprKind::InlineAsm(_)
311 | ExprKind::Lit(_)
312 | ExprKind::Loop(_, _, _)
313 | ExprKind::MacCall(_)
314 | ExprKind::OffsetOf(_, _)
315 | ExprKind::Path(_, _)
316 | ExprKind::Ret(_)
317 | ExprKind::Try(_)
318 | ExprKind::TryBlock(_)
319 | ExprKind::Type(_, _)
320 | ExprKind::Underscore
321 | ExprKind::While(_, _, _)
322 | ExprKind::Yeet(_)
323 | ExprKind::Become(_)
324 | ExprKind::Yield(_)
325 | ExprKind::UnsafeBinderCast(..) => {}
326 }
327 }
328
329 fn manage_initial_capture(&mut self, expr: &mut Box<Expr>, path_ident: Ident) {
334 if self.paths.contains(&path_ident) {
335 return;
336 } else {
337 self.fmt_string.push_str(" ");
338 self.fmt_string.push_str(path_ident.as_str());
339 self.fmt_string.push_str(" = {:?}\n");
340 let _ = self.paths.insert(path_ident);
341 }
342 let curr_capture_idx = self.capture_decls.len();
343 let capture_string = format!("__capture{curr_capture_idx}");
344 let ident = Ident::new(Symbol::intern(&capture_string), self.span);
345 let init_std_path = self.cx.std_path(&[sym::asserting, sym::Capture, sym::new]);
346 let init = self.cx.expr_call(
347 self.span,
348 self.cx.expr_path(self.cx.path(self.span, init_std_path)),
349 ThinVec::new(),
350 );
351 let capture = Capture { decl: self.cx.stmt_let(self.span, true, ident, init), ident };
352 self.capture_decls.push(capture);
353 self.manage_try_capture(ident, curr_capture_idx, expr);
354 }
355
356 fn manage_try_capture(
363 &mut self,
364 capture: Ident,
365 curr_capture_idx: usize,
366 expr: &mut Box<Expr>,
367 ) {
368 let local_bind_string = format!("__local_bind{curr_capture_idx}");
369 let local_bind = Ident::new(Symbol::intern(&local_bind_string), self.span);
370 self.local_bind_decls.push(self.cx.stmt_let(
371 self.span,
372 false,
373 local_bind,
374 self.cx.expr_addr_of(self.span, expr.clone()),
375 ));
376 let wrapper = self.cx.expr_call(
377 self.span,
378 self.cx.expr_path(
379 self.cx.path(self.span, self.cx.std_path(&[sym::asserting, sym::Wrapper])),
380 ),
381 thin_vec![self.cx.expr_path(Path::from_ident(local_bind))],
382 );
383 let try_capture_call = self
384 .cx
385 .stmt_expr(expr_method_call(
386 self.cx,
387 PathSegment {
388 args: None,
389 id: DUMMY_NODE_ID,
390 ident: Ident::new(sym::try_capture, self.span),
391 },
392 expr_paren(self.cx, self.span, self.cx.expr_addr_of(self.span, wrapper)),
393 thin_vec![expr_addr_of_mut(
394 self.cx,
395 self.span,
396 self.cx.expr_path(Path::from_ident(capture)),
397 )],
398 self.span,
399 ))
400 .add_trailing_semicolon();
401 let local_bind_path = self.cx.expr_path(Path::from_ident(local_bind));
402 let rslt = if self.is_consumed {
403 let ret = self.cx.stmt_expr(local_bind_path);
404 self.cx.expr_block(self.cx.block(self.span, thin_vec![try_capture_call, ret]))
405 } else {
406 self.best_case_captures.push(try_capture_call);
407 local_bind_path
408 };
409 *expr = self.cx.expr_deref(self.span, rslt);
410 }
411
412 fn with_is_consumed_management(&mut self, curr_is_consumed: bool, f: impl FnOnce(&mut Self)) {
415 let prev_is_consumed = self.is_consumed;
416 self.is_consumed = curr_is_consumed;
417 f(self);
418 self.is_consumed = prev_is_consumed;
419 }
420}
421
422#[derive(Debug)]
424struct Capture {
425 decl: Stmt,
429 ident: Ident,
433}
434
435fn escape_to_fmt(s: &str) -> String {
437 let mut rslt = String::with_capacity(s.len());
438 for c in s.chars() {
439 rslt.extend(c.escape_debug());
440 match c {
441 '{' | '}' => rslt.push(c),
442 _ => {}
443 }
444 }
445 rslt
446}
447
448fn expr_addr_of_mut(cx: &ExtCtxt<'_>, sp: Span, e: Box<Expr>) -> Box<Expr> {
449 cx.expr(sp, ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, e))
450}
451
452fn expr_method_call(
453 cx: &ExtCtxt<'_>,
454 seg: PathSegment,
455 receiver: Box<Expr>,
456 args: ThinVec<Box<Expr>>,
457 span: Span,
458) -> Box<Expr> {
459 cx.expr(span, ExprKind::MethodCall(Box::new(MethodCall { seg, receiver, args, span })))
460}
461
462fn expr_paren(cx: &ExtCtxt<'_>, sp: Span, e: Box<Expr>) -> Box<Expr> {
463 cx.expr(sp, ExprKind::Paren(e))
464}