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