rustc_mir_build/
check_tail_calls.rs

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` is empty, a type error occurred, skip this body.
19    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        // FIXME(#132279): we're clearly in a body here.
31        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    /// Whatever the currently checked body is one of a closure
46    is_closure: bool,
47    /// The result of the checks, `Err(_)` if there was a problem with some
48    /// tail call, `Ok(())` if all of them were fine.
49    found_errors: Result<(), ErrorGuaranteed>,
50    /// Type of the caller function.
51    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        // While the `caller_sig` does have its regions erased, it does not have its
68        // binders anonymized. We call `erase_regions` once again to anonymize any binders
69        // within the signature, such as in function pointer or `dyn Trait` args.
70        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            // Closures in thir look something akin to
99            // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}`
100            // So we have to check for them in this weird way...
101            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                    // This can happen when tail calling `Box` that wraps a function
110                    self.report_nonfn_callee(fn_span, self.thir[fun].span, this);
111                }
112
113                // Tail calling is likely to cause unrelated errors (ABI, argument mismatches),
114                // skip them, producing an error about calling a closure is enough.
115                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            // `fn_sig` below panics otherwise
127            return;
128        };
129
130        // Erase regions since tail calls don't care about lifetimes
131        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            // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used.
152            // e.g.
153            // ```
154            // fn a() -> impl Sized { become b() } // ICE
155            // fn b() -> u8 { 0 }
156            // ```
157            // we should think what is the expected behavior here.
158            // (we should probably just accept this by revealing opaques?)
159            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            // `#[track_caller]` affects the ABI of a function (by adding a location argument),
166            // so a `track_caller` can only tail call other `track_caller` functions.
167            //
168            // The issue is however that we can't know if a function is `track_caller` or not at
169            // this point (THIR can be polymorphic, we may have an unresolved trait function).
170            // We could only allow functions that we *can* resolve and *are* `track_caller`,
171            // but that would turn changing `track_caller`-ness into a breaking change,
172            // which is probably undesirable.
173            //
174            // Also note that we don't check callee's `track_caller`-ness at all, mostly for the
175            // reasons above, but also because we can always tailcall the shim we'd generate for
176            // coercing the function to an `fn()` pointer. (although in that case the tailcall is
177            // basically useless -- the shim calls the actual function, so tailcalling the shim is
178            // equivalent to calling the function)
179            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    /// Returns true if function of type `ty` needs location argument
196    /// (i.e. if a function is marked as `#[track_caller]`).
197    ///
198    /// Panics if the function's instance can't be immediately resolved.
199    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            // FIXME(explicit_tail_calls): highlight the `...`
403            .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            // FIXME(explicit_tail_calls): highlight the function or something...
414            .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}