1use rustc_abi::ExternAbi;
2use rustc_data_structures::stack::ensure_sufficient_stack;
3use rustc_errors::Applicability;
4use rustc_hir::LangItem;
5use rustc_hir::def::DefKind;
6use rustc_hir::def_id::CRATE_DEF_ID;
7use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
8use rustc_middle::span_bug;
9use rustc_middle::thir::visit::{self, Visitor};
10use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir};
11use rustc_middle::ty::{self, Ty, TyCtxt};
12use rustc_span::def_id::{DefId, LocalDefId};
13use rustc_span::{ErrorGuaranteed, Span};
14
15pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> {
16 let (thir, expr) = tcx.thir_body(def)?;
17 let thir = &thir.borrow();
18
19 if thir.exprs.is_empty() {
21 return Ok(());
22 }
23
24 let is_closure = #[allow(non_exhaustive_omitted_patterns)] match tcx.def_kind(def) {
DefKind::Closure => true,
_ => false,
}matches!(tcx.def_kind(def), DefKind::Closure);
25
26 let mut visitor = TailCallCkVisitor {
27 tcx,
28 thir,
29 found_errors: Ok(()),
30 typing_env: ty::TypingEnv::post_typeck_until_borrowck_for_mir_build(tcx, def),
31 is_closure,
32 caller_def_id: def,
33 };
34
35 visitor.visit_expr(&thir[expr]);
36
37 visitor.found_errors
38}
39
40struct TailCallCkVisitor<'a, 'tcx> {
41 tcx: TyCtxt<'tcx>,
42 thir: &'a Thir<'tcx>,
43 typing_env: ty::TypingEnv<'tcx>,
44 is_closure: bool,
46 found_errors: Result<(), ErrorGuaranteed>,
49 caller_def_id: LocalDefId,
51}
52
53impl<'tcx> TailCallCkVisitor<'_, 'tcx> {
54 fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) {
55 if self.is_closure {
56 self.report_in_closure(expr);
57 return;
58 }
59
60 let BodyTy::Fn(caller_sig) = self.thir.body_type else {
61 ::rustc_middle::util::bug::span_bug_fmt(call.span,
format_args!("`become` outside of functions should have been disallowed by hir_typeck"))span_bug!(
62 call.span,
63 "`become` outside of functions should have been disallowed by hir_typeck"
64 )
65 };
66 let caller_sig = self.tcx.erase_and_anonymize_regions(caller_sig);
70
71 let ExprKind::Scope { value, .. } = call.kind else {
72 ::rustc_middle::util::bug::span_bug_fmt(call.span,
format_args!("expected scope, found: {0:?}", call))span_bug!(call.span, "expected scope, found: {call:?}")
73 };
74 let value = &self.thir[value];
75
76 if #[allow(non_exhaustive_omitted_patterns)] match value.kind {
ExprKind::Binary { .. } | ExprKind::Unary { .. } | ExprKind::AssignOp { ..
} | ExprKind::Index { .. } => true,
_ => false,
}matches!(
77 value.kind,
78 ExprKind::Binary { .. }
79 | ExprKind::Unary { .. }
80 | ExprKind::AssignOp { .. }
81 | ExprKind::Index { .. }
82 ) {
83 self.report_builtin_op(call, expr);
84 return;
85 }
86
87 let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else {
88 self.report_non_call(value, expr);
89 return;
90 };
91
92 if !from_hir_call {
93 self.report_op(ty, args, fn_span, expr);
94 }
95
96 if let &ty::FnDef(did, args) = ty.kind() {
97 let parent = self.tcx.parent(did);
101 if self.tcx.fn_trait_kind_from_def_id(parent).is_some()
102 && let Some(this) = args.first()
103 && let Some(this) = this.as_type()
104 {
105 if this.is_closure() {
106 self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr);
107 } else {
108 self.report_nonfn_callee(fn_span, self.thir[fun].span, this);
110 }
111
112 return;
115 };
116
117 if self.tcx.intrinsic(did).is_some() {
118 self.report_calling_intrinsic(expr);
119 }
120 }
121
122 let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else {
123 self.report_nonfn_callee(fn_span, self.thir[fun].span, ty);
124
125 return;
127 };
128
129 let callee_sig =
131 self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx));
132
133 if caller_sig.abi() != callee_sig.abi() {
134 self.report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi());
135 }
136
137 if !callee_sig.abi().supports_guaranteed_tail_call() {
138 self.report_unsupported_abi(expr.span, callee_sig.abi());
139 }
140
141 if caller_sig.inputs_and_output != callee_sig.inputs_and_output
150 && !#[allow(non_exhaustive_omitted_patterns)] match callee_sig.abi() {
ExternAbi::RustTail => true,
_ => false,
}matches!(callee_sig.abi(), ExternAbi::RustTail)
151 {
152 let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder();
153
154 self.report_signature_mismatch(
155 expr.span,
156 self.tcx.liberate_late_bound_regions(
157 CRATE_DEF_ID.to_def_id(),
158 caller_ty.fn_sig(self.tcx),
159 ),
160 self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)),
161 );
162 }
163
164 {
165 let caller_needs_location = self.caller_needs_location();
180
181 if caller_needs_location {
182 self.report_track_caller_caller(expr.span);
183 }
184 }
185
186 if caller_sig.c_variadic() {
187 self.report_c_variadic_caller(expr.span);
188 }
189
190 if callee_sig.c_variadic() {
191 self.report_c_variadic_callee(expr.span);
192 }
193
194 for &arg_ty in callee_sig.inputs() {
195 if !arg_ty.is_sized(self.tcx, self.typing_env) {
196 self.report_unsized_argument(expr.span, arg_ty);
197 }
198 }
199 }
200
201 fn caller_needs_location(&self) -> bool {
204 let flags = self.tcx.codegen_fn_attrs(self.caller_def_id).flags;
205 flags.contains(CodegenFnAttrFlags::TRACK_CALLER)
206 }
207
208 fn report_in_closure(&mut self, expr: &Expr<'_>) {
209 let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures");
210 self.found_errors = Err(err);
211 }
212
213 fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
214 let err = self
215 .tcx
216 .dcx()
217 .struct_span_err(value.span, "`become` does not support operators")
218 .with_note("using `become` on a builtin operator is not useful")
219 .with_span_suggestion(
220 value.span.until(expr.span),
221 "try using `return` instead",
222 "return ",
223 Applicability::MachineApplicable,
224 )
225 .emit();
226 self.found_errors = Err(err);
227 }
228
229 fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) {
230 let mut err =
231 self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators");
232
233 if let &ty::FnDef(did, _substs) = fun_ty.kind()
234 && let parent = self.tcx.parent(did)
235 && #[allow(non_exhaustive_omitted_patterns)] match self.tcx.def_kind(parent) {
DefKind::Trait => true,
_ => false,
}matches!(self.tcx.def_kind(parent), DefKind::Trait)
236 && let Some(method) = op_trait_as_method_name(self.tcx, parent)
237 {
238 match args {
239 &[arg] => {
240 let arg = &self.thir[arg];
241
242 err.multipart_suggestion(
243 "try using the method directly",
244 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(fn_span.shrink_to_lo().until(arg.span), "(".to_owned()),
(arg.span.shrink_to_hi(),
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(").{0}()", method))
}))]))vec![
245 (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()),
246 (arg.span.shrink_to_hi(), format!(").{method}()")),
247 ],
248 Applicability::MaybeIncorrect,
249 );
250 }
251 &[lhs, rhs] => {
252 let lhs = &self.thir[lhs];
253 let rhs = &self.thir[rhs];
254
255 err.multipart_suggestion(
256 "try using the method directly",
257 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(lhs.span.shrink_to_lo(),
::alloc::__export::must_use({
::alloc::fmt::format(format_args!("("))
})),
(lhs.span.between(rhs.span),
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(").{0}(", method))
})),
(rhs.span.between(expr.span.shrink_to_hi()),
")".to_owned())]))vec![
258 (lhs.span.shrink_to_lo(), format!("(")),
259 (lhs.span.between(rhs.span), format!(").{method}(")),
260 (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()),
261 ],
262 Applicability::MaybeIncorrect,
263 );
264 }
265 _ => ::rustc_middle::util::bug::span_bug_fmt(expr.span,
format_args!("operator with more than 2 args? {0:?}", args))span_bug!(expr.span, "operator with more than 2 args? {args:?}"),
266 }
267 }
268
269 self.found_errors = Err(err.emit());
270 }
271
272 fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) {
273 let err = self
274 .tcx
275 .dcx()
276 .struct_span_err(value.span, "`become` requires a function call")
277 .with_span_note(value.span, "not a function call")
278 .with_span_suggestion(
279 value.span.until(expr.span),
280 "try using `return` instead",
281 "return ",
282 Applicability::MaybeIncorrect,
283 )
284 .emit();
285 self.found_errors = Err(err);
286 }
287
288 fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) {
289 let underscored_args = match tupled_args.kind() {
290 ty::Tuple(tys) if tys.is_empty() => "".to_owned(),
291 ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(),
292 _ => "_".to_owned(),
293 };
294
295 let err = self
296 .tcx
297 .dcx()
298 .struct_span_err(expr.span, "tail calling closures directly is not allowed")
299 .with_multipart_suggestion(
300 "try casting the closure to a function pointer type",
301 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(fun.span.shrink_to_lo(), "(".to_owned()),
(fun.span.shrink_to_hi(),
::alloc::__export::must_use({
::alloc::fmt::format(format_args!(" as fn({0}) -> _)",
underscored_args))
}))]))vec![
302 (fun.span.shrink_to_lo(), "(".to_owned()),
303 (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")),
304 ],
305 Applicability::MaybeIncorrect,
306 )
307 .emit();
308 self.found_errors = Err(err);
309 }
310
311 fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) {
312 let err = self
313 .tcx
314 .dcx()
315 .struct_span_err(expr.span, "tail calling intrinsics is not allowed")
316 .emit();
317
318 self.found_errors = Err(err);
319 }
320
321 fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) {
322 let mut err = self
323 .tcx
324 .dcx()
325 .struct_span_err(
326 call_sp,
327 "tail calls can only be performed with function definitions or pointers",
328 )
329 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("callee has type `{0}`", ty))
})format!("callee has type `{ty}`"));
330
331 let mut ty = ty;
332 let mut refs = 0;
333 while ty.is_box() || ty.is_ref() {
334 ty = ty.builtin_deref(false).unwrap();
335 refs += 1;
336 }
337
338 if refs > 0 && ty.is_fn() {
339 let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" };
340
341 let derefs =
342 std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>();
343
344 err.multipart_suggestion(
345 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("consider dereferencing the expression to get a function {0}",
thing))
})format!("consider dereferencing the expression to get a function {thing}"),
346 ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[(fun_sp.shrink_to_lo(), derefs),
(fun_sp.shrink_to_hi(), ")".to_owned())]))vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())],
347 Applicability::MachineApplicable,
348 );
349 }
350
351 let err = err.emit();
352 self.found_errors = Err(err);
353 }
354
355 fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) {
356 let err = self
357 .tcx
358 .dcx()
359 .struct_span_err(sp, "mismatched function ABIs")
360 .with_note("`become` requires caller and callee to have the same ABI")
361 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("caller ABI is `{0}`, while callee ABI is `{1}`",
caller_abi, callee_abi))
})format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`"))
362 .emit();
363 self.found_errors = Err(err);
364 }
365
366 fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) {
367 let err = self
368 .tcx
369 .dcx()
370 .struct_span_err(sp, "ABI does not support guaranteed tail calls")
371 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`become` is not supported for `extern {0}` functions",
callee_abi))
})format!("`become` is not supported for `extern {callee_abi}` functions"))
372 .emit();
373 self.found_errors = Err(err);
374 }
375
376 fn report_signature_mismatch(
377 &mut self,
378 sp: Span,
379 caller_sig: ty::FnSig<'_>,
380 callee_sig: ty::FnSig<'_>,
381 ) {
382 let err = self
383 .tcx
384 .dcx()
385 .struct_span_err(sp, "mismatched signatures")
386 .with_note("`become` requires caller and callee to have matching signatures")
387 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("caller signature: `{0}`",
caller_sig))
})format!("caller signature: `{caller_sig}`"))
388 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("callee signature: `{0}`",
callee_sig))
})format!("callee signature: `{callee_sig}`"))
389 .emit();
390 self.found_errors = Err(err);
391 }
392
393 fn report_track_caller_caller(&mut self, sp: Span) {
394 let err = self
395 .tcx
396 .dcx()
397 .struct_span_err(
398 sp,
399 "a function marked with `#[track_caller]` cannot perform a tail-call",
400 )
401 .emit();
402
403 self.found_errors = Err(err);
404 }
405
406 fn report_c_variadic_caller(&mut self, sp: Span) {
407 let err = self
408 .tcx
409 .dcx()
410 .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions")
412 .emit();
413
414 self.found_errors = Err(err);
415 }
416
417 fn report_c_variadic_callee(&mut self, sp: Span) {
418 let err = self
419 .tcx
420 .dcx()
421 .struct_span_err(sp, "c-variadic functions can't be tail-called")
423 .emit();
424
425 self.found_errors = Err(err);
426 }
427
428 fn report_unsized_argument(&mut self, sp: Span, arg_ty: Ty<'tcx>) {
429 let err = self
430 .tcx
431 .dcx()
432 .struct_span_err(sp, ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsized arguments cannot be used in a tail call"))
})format!("unsized arguments cannot be used in a tail call"))
433 .with_note(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unsized argument of type `{0}`",
arg_ty))
})format!("unsized argument of type `{arg_ty}`"))
434 .emit();
435
436 self.found_errors = Err(err);
437 }
438}
439
440impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> {
441 fn thir(&self) -> &'a Thir<'tcx> {
442 &self.thir
443 }
444
445 fn visit_expr(&mut self, expr: &'a Expr<'tcx>) {
446 ensure_sufficient_stack(|| {
447 if let ExprKind::Become { value } = expr.kind {
448 let call = &self.thir[value];
449 self.check_tail_call(call, expr);
450 }
451
452 visit::walk_expr(self, expr);
453 });
454 }
455}
456
457fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> {
458 let m = match tcx.as_lang_item(trait_did)? {
459 LangItem::Add => "add",
460 LangItem::Sub => "sub",
461 LangItem::Mul => "mul",
462 LangItem::Div => "div",
463 LangItem::Rem => "rem",
464 LangItem::Neg => "neg",
465 LangItem::Not => "not",
466 LangItem::BitXor => "bitxor",
467 LangItem::BitAnd => "bitand",
468 LangItem::BitOr => "bitor",
469 LangItem::Shl => "shl",
470 LangItem::Shr => "shr",
471 LangItem::AddAssign => "add_assign",
472 LangItem::SubAssign => "sub_assign",
473 LangItem::MulAssign => "mul_assign",
474 LangItem::DivAssign => "div_assign",
475 LangItem::RemAssign => "rem_assign",
476 LangItem::BitXorAssign => "bitxor_assign",
477 LangItem::BitAndAssign => "bitand_assign",
478 LangItem::BitOrAssign => "bitor_assign",
479 LangItem::ShlAssign => "shl_assign",
480 LangItem::ShrAssign => "shr_assign",
481 LangItem::Index => "index",
482 LangItem::IndexMut => "index_mut",
483 _ => return None,
484 };
485
486 Some(m)
487}