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