rustc_mir_build/src/check_tail_calls.rs
Line | Count | Source |
1 | | use rustc_abi::ExternAbi; |
2 | | use rustc_data_structures::stack::ensure_sufficient_stack; |
3 | | use rustc_errors::Applicability; |
4 | | use rustc_hir::LangItem; |
5 | | use rustc_hir::def::DefKind; |
6 | | use rustc_hir::def_id::CRATE_DEF_ID; |
7 | | use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; |
8 | | use rustc_middle::span_bug; |
9 | | use rustc_middle::thir::visit::{self, Visitor}; |
10 | | use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir}; |
11 | | use rustc_middle::ty::{self, Ty, TyCtxt}; |
12 | | use rustc_span::def_id::{DefId, LocalDefId}; |
13 | | use rustc_span::{ErrorGuaranteed, Span}; |
14 | | |
15 | 0 | pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> { |
16 | 0 | let (thir, expr) = tcx.thir_body(def)?; |
17 | 0 | let thir = &thir.borrow(); |
18 | | |
19 | | // If `thir` is empty, a type error occurred, skip this body. |
20 | 0 | if thir.exprs.is_empty() { |
21 | 0 | return Ok(()); |
22 | 0 | } |
23 | | |
24 | 0 | let is_closure = matches!(tcx.def_kind(def), DefKind::Closure); |
25 | | |
26 | 0 | let mut visitor = TailCallCkVisitor { |
27 | 0 | tcx, |
28 | 0 | thir, |
29 | 0 | found_errors: Ok(()), |
30 | 0 | // FIXME(#132279): we're clearly in a body here. |
31 | 0 | typing_env: ty::TypingEnv::non_body_analysis(tcx, def), |
32 | 0 | is_closure, |
33 | 0 | caller_def_id: def, |
34 | 0 | }; |
35 | | |
36 | 0 | visitor.visit_expr(&thir[expr]); |
37 | | |
38 | 0 | visitor.found_errors |
39 | 0 | } |
40 | | |
41 | | struct 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 | | /// `LocalDefId` of the caller function. |
51 | | caller_def_id: LocalDefId, |
52 | | } |
53 | | |
54 | | impl<'tcx> TailCallCkVisitor<'_, 'tcx> { |
55 | 0 | fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) { |
56 | 0 | if self.is_closure { |
57 | 0 | self.report_in_closure(expr); |
58 | 0 | return; |
59 | 0 | } |
60 | | |
61 | 0 | let BodyTy::Fn(caller_sig) = self.thir.body_type else { |
62 | 0 | span_bug!( |
63 | 0 | call.span, |
64 | | "`become` outside of functions should have been disallowed by hir_typeck" |
65 | | ) |
66 | | }; |
67 | | // While the `caller_sig` does have its free regions erased, it does not have its |
68 | | // binders anonymized. We call `erase_and_anonymize_regions` once again to anonymize any binders |
69 | | // within the signature, such as in function pointer or `dyn Trait` args. |
70 | 0 | let caller_sig = self.tcx.erase_and_anonymize_regions(caller_sig); |
71 | | |
72 | 0 | let ExprKind::Scope { value, .. } = call.kind else { |
73 | 0 | span_bug!(call.span, "expected scope, found: {call:?}") |
74 | | }; |
75 | 0 | let value = &self.thir[value]; |
76 | | |
77 | 0 | if matches!( |
78 | 0 | value.kind, |
79 | | ExprKind::Binary { .. } |
80 | | | ExprKind::Unary { .. } |
81 | | | ExprKind::AssignOp { .. } |
82 | | | ExprKind::Index { .. } |
83 | | ) { |
84 | 0 | self.report_builtin_op(call, expr); |
85 | 0 | return; |
86 | 0 | } |
87 | | |
88 | 0 | let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else { |
89 | 0 | self.report_non_call(value, expr); |
90 | 0 | return; |
91 | | }; |
92 | | |
93 | 0 | if !from_hir_call { |
94 | 0 | self.report_op(ty, args, fn_span, expr); |
95 | 0 | } |
96 | | |
97 | 0 | 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 | 0 | let parent = self.tcx.parent(did); |
102 | 0 | if self.tcx.fn_trait_kind_from_def_id(parent).is_some() |
103 | 0 | && let Some(this) = args.first() |
104 | 0 | && let Some(this) = this.as_type() |
105 | | { |
106 | 0 | if this.is_closure() { |
107 | 0 | self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr); |
108 | 0 | } else { |
109 | 0 | // This can happen when tail calling `Box` that wraps a function |
110 | 0 | self.report_nonfn_callee(fn_span, self.thir[fun].span, this); |
111 | 0 | } |
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 | 0 | return; |
116 | 0 | }; |
117 | | |
118 | 0 | if self.tcx.intrinsic(did).is_some() { |
119 | 0 | self.report_calling_intrinsic(expr); |
120 | 0 | } |
121 | 0 | } |
122 | | |
123 | 0 | let (ty::FnDef(..) | ty::FnPtr(..)) = ty.kind() else { |
124 | 0 | self.report_nonfn_callee(fn_span, self.thir[fun].span, ty); |
125 | | |
126 | | // `fn_sig` below panics otherwise |
127 | 0 | return; |
128 | | }; |
129 | | |
130 | | // Erase regions since tail calls don't care about lifetimes |
131 | 0 | let callee_sig = |
132 | 0 | self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx)); |
133 | | |
134 | 0 | if caller_sig.abi() != callee_sig.abi() { |
135 | 0 | self.report_abi_mismatch(expr.span, caller_sig.abi(), callee_sig.abi()); |
136 | 0 | } |
137 | | |
138 | 0 | if !callee_sig.abi().supports_guaranteed_tail_call() { |
139 | 0 | self.report_unsupported_abi(expr.span, callee_sig.abi()); |
140 | 0 | } |
141 | | |
142 | | // FIXME(explicit_tail_calls): this currently fails for cases where opaques are used. |
143 | | // e.g. |
144 | | // ``` |
145 | | // fn a() -> impl Sized { become b() } // ICE |
146 | | // fn b() -> u8 { 0 } |
147 | | // ``` |
148 | | // we should think what is the expected behavior here. |
149 | | // (we should probably just accept this by revealing opaques?) |
150 | 0 | if caller_sig.inputs_and_output != callee_sig.inputs_and_output { |
151 | 0 | let caller_ty = self.tcx.type_of(self.caller_def_id).skip_binder(); |
152 | 0 |
|
153 | 0 | self.report_signature_mismatch( |
154 | 0 | expr.span, |
155 | 0 | self.tcx.liberate_late_bound_regions( |
156 | 0 | CRATE_DEF_ID.to_def_id(), |
157 | 0 | caller_ty.fn_sig(self.tcx), |
158 | 0 | ), |
159 | 0 | self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)), |
160 | 0 | ); |
161 | 0 | } |
162 | | |
163 | | { |
164 | | // `#[track_caller]` affects the ABI of a function (by adding a location argument), |
165 | | // so a `track_caller` can only tail call other `track_caller` functions. |
166 | | // |
167 | | // The issue is however that we can't know if a function is `track_caller` or not at |
168 | | // this point (THIR can be polymorphic, we may have an unresolved trait function). |
169 | | // We could only allow functions that we *can* resolve and *are* `track_caller`, |
170 | | // but that would turn changing `track_caller`-ness into a breaking change, |
171 | | // which is probably undesirable. |
172 | | // |
173 | | // Also note that we don't check callee's `track_caller`-ness at all, mostly for the |
174 | | // reasons above, but also because we can always tailcall the shim we'd generate for |
175 | | // coercing the function to an `fn()` pointer. (although in that case the tailcall is |
176 | | // basically useless -- the shim calls the actual function, so tailcalling the shim is |
177 | | // equivalent to calling the function) |
178 | 0 | let caller_needs_location = self.caller_needs_location(); |
179 | | |
180 | 0 | if caller_needs_location { |
181 | 0 | self.report_track_caller_caller(expr.span); |
182 | 0 | } |
183 | | } |
184 | | |
185 | 0 | if caller_sig.c_variadic() { |
186 | 0 | self.report_c_variadic_caller(expr.span); |
187 | 0 | } |
188 | | |
189 | 0 | if callee_sig.c_variadic() { |
190 | 0 | self.report_c_variadic_callee(expr.span); |
191 | 0 | } |
192 | 0 | } |
193 | | |
194 | | /// Returns true if the caller function needs a location argument |
195 | | /// (i.e. if a function is marked as `#[track_caller]`) |
196 | 0 | fn caller_needs_location(&self) -> bool { |
197 | 0 | let flags = self.tcx.codegen_fn_attrs(self.caller_def_id).flags; |
198 | 0 | flags.contains(CodegenFnAttrFlags::TRACK_CALLER) |
199 | 0 | } |
200 | | |
201 | 0 | fn report_in_closure(&mut self, expr: &Expr<'_>) { |
202 | 0 | let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures"); |
203 | 0 | self.found_errors = Err(err); |
204 | 0 | } |
205 | | |
206 | 0 | fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { |
207 | 0 | let err = self |
208 | 0 | .tcx |
209 | 0 | .dcx() |
210 | 0 | .struct_span_err(value.span, "`become` does not support operators") |
211 | 0 | .with_note("using `become` on a builtin operator is not useful") |
212 | 0 | .with_span_suggestion( |
213 | 0 | value.span.until(expr.span), |
214 | | "try using `return` instead", |
215 | | "return ", |
216 | 0 | Applicability::MachineApplicable, |
217 | | ) |
218 | 0 | .emit(); |
219 | 0 | self.found_errors = Err(err); |
220 | 0 | } |
221 | | |
222 | 0 | fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) { |
223 | 0 | let mut err = |
224 | 0 | self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators"); |
225 | | |
226 | 0 | if let &ty::FnDef(did, _substs) = fun_ty.kind() |
227 | 0 | && let parent = self.tcx.parent(did) |
228 | 0 | && matches!(self.tcx.def_kind(parent), DefKind::Trait) |
229 | 0 | && let Some(method) = op_trait_as_method_name(self.tcx, parent) |
230 | | { |
231 | 0 | match args { |
232 | 0 | &[arg] => { |
233 | 0 | let arg = &self.thir[arg]; |
234 | 0 |
|
235 | 0 | err.multipart_suggestion( |
236 | 0 | "try using the method directly", |
237 | 0 | vec![ |
238 | 0 | (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()), |
239 | 0 | (arg.span.shrink_to_hi(), format!(").{method}()")), |
240 | 0 | ], |
241 | 0 | Applicability::MaybeIncorrect, |
242 | 0 | ); |
243 | 0 | } |
244 | 0 | &[lhs, rhs] => { |
245 | 0 | let lhs = &self.thir[lhs]; |
246 | 0 | let rhs = &self.thir[rhs]; |
247 | 0 |
|
248 | 0 | err.multipart_suggestion( |
249 | 0 | "try using the method directly", |
250 | 0 | vec![ |
251 | 0 | (lhs.span.shrink_to_lo(), format!("(")), |
252 | 0 | (lhs.span.between(rhs.span), format!(").{method}(")), |
253 | 0 | (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()), |
254 | 0 | ], |
255 | 0 | Applicability::MaybeIncorrect, |
256 | 0 | ); |
257 | 0 | } |
258 | 0 | _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"), |
259 | | } |
260 | 0 | } |
261 | | |
262 | 0 | self.found_errors = Err(err.emit()); |
263 | 0 | } |
264 | | |
265 | 0 | fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { |
266 | 0 | let err = self |
267 | 0 | .tcx |
268 | 0 | .dcx() |
269 | 0 | .struct_span_err(value.span, "`become` requires a function call") |
270 | 0 | .with_span_note(value.span, "not a function call") |
271 | 0 | .with_span_suggestion( |
272 | 0 | value.span.until(expr.span), |
273 | | "try using `return` instead", |
274 | | "return ", |
275 | 0 | Applicability::MaybeIncorrect, |
276 | | ) |
277 | 0 | .emit(); |
278 | 0 | self.found_errors = Err(err); |
279 | 0 | } |
280 | | |
281 | 0 | fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) { |
282 | 0 | let underscored_args = match tupled_args.kind() { |
283 | 0 | ty::Tuple(tys) if tys.is_empty() => "".to_owned(), |
284 | 0 | ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(), |
285 | 0 | _ => "_".to_owned(), |
286 | | }; |
287 | | |
288 | 0 | let err = self |
289 | 0 | .tcx |
290 | 0 | .dcx() |
291 | 0 | .struct_span_err(expr.span, "tail calling closures directly is not allowed") |
292 | 0 | .with_multipart_suggestion( |
293 | | "try casting the closure to a function pointer type", |
294 | 0 | vec![ |
295 | 0 | (fun.span.shrink_to_lo(), "(".to_owned()), |
296 | 0 | (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")), |
297 | | ], |
298 | 0 | Applicability::MaybeIncorrect, |
299 | | ) |
300 | 0 | .emit(); |
301 | 0 | self.found_errors = Err(err); |
302 | 0 | } |
303 | | |
304 | 0 | fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) { |
305 | 0 | let err = self |
306 | 0 | .tcx |
307 | 0 | .dcx() |
308 | 0 | .struct_span_err(expr.span, "tail calling intrinsics is not allowed") |
309 | 0 | .emit(); |
310 | | |
311 | 0 | self.found_errors = Err(err); |
312 | 0 | } |
313 | | |
314 | 0 | fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) { |
315 | 0 | let mut err = self |
316 | 0 | .tcx |
317 | 0 | .dcx() |
318 | 0 | .struct_span_err( |
319 | 0 | call_sp, |
320 | | "tail calls can only be performed with function definitions or pointers", |
321 | | ) |
322 | 0 | .with_note(format!("callee has type `{ty}`")); |
323 | | |
324 | 0 | let mut ty = ty; |
325 | 0 | let mut refs = 0; |
326 | 0 | while ty.is_box() || ty.is_ref() { |
327 | 0 | ty = ty.builtin_deref(false).unwrap(); |
328 | 0 | refs += 1; |
329 | 0 | } |
330 | | |
331 | 0 | if refs > 0 && ty.is_fn() { |
332 | 0 | let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" }; |
333 | | |
334 | 0 | let derefs = |
335 | 0 | std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>(); |
336 | | |
337 | 0 | err.multipart_suggestion( |
338 | 0 | format!("consider dereferencing the expression to get a function {thing}"), |
339 | 0 | vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())], |
340 | 0 | Applicability::MachineApplicable, |
341 | | ); |
342 | 0 | } |
343 | | |
344 | 0 | let err = err.emit(); |
345 | 0 | self.found_errors = Err(err); |
346 | 0 | } |
347 | | |
348 | 0 | fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) { |
349 | 0 | let err = self |
350 | 0 | .tcx |
351 | 0 | .dcx() |
352 | 0 | .struct_span_err(sp, "mismatched function ABIs") |
353 | 0 | .with_note("`become` requires caller and callee to have the same ABI") |
354 | 0 | .with_note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`")) |
355 | 0 | .emit(); |
356 | 0 | self.found_errors = Err(err); |
357 | 0 | } |
358 | | |
359 | 0 | fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) { |
360 | 0 | let err = self |
361 | 0 | .tcx |
362 | 0 | .dcx() |
363 | 0 | .struct_span_err(sp, "ABI does not support guaranteed tail calls") |
364 | 0 | .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) |
365 | 0 | .emit(); |
366 | 0 | self.found_errors = Err(err); |
367 | 0 | } |
368 | | |
369 | 0 | fn report_signature_mismatch( |
370 | 0 | &mut self, |
371 | 0 | sp: Span, |
372 | 0 | caller_sig: ty::FnSig<'_>, |
373 | 0 | callee_sig: ty::FnSig<'_>, |
374 | 0 | ) { |
375 | 0 | let err = self |
376 | 0 | .tcx |
377 | 0 | .dcx() |
378 | 0 | .struct_span_err(sp, "mismatched signatures") |
379 | 0 | .with_note("`become` requires caller and callee to have matching signatures") |
380 | 0 | .with_note(format!("caller signature: `{caller_sig}`")) |
381 | 0 | .with_note(format!("callee signature: `{callee_sig}`")) |
382 | 0 | .emit(); |
383 | 0 | self.found_errors = Err(err); |
384 | 0 | } |
385 | | |
386 | 0 | fn report_track_caller_caller(&mut self, sp: Span) { |
387 | 0 | let err = self |
388 | 0 | .tcx |
389 | 0 | .dcx() |
390 | 0 | .struct_span_err( |
391 | 0 | sp, |
392 | | "a function marked with `#[track_caller]` cannot perform a tail-call", |
393 | | ) |
394 | 0 | .emit(); |
395 | | |
396 | 0 | self.found_errors = Err(err); |
397 | 0 | } |
398 | | |
399 | 0 | fn report_c_variadic_caller(&mut self, sp: Span) { |
400 | 0 | let err = self |
401 | 0 | .tcx |
402 | 0 | .dcx() |
403 | | // FIXME(explicit_tail_calls): highlight the `...` |
404 | 0 | .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") |
405 | 0 | .emit(); |
406 | | |
407 | 0 | self.found_errors = Err(err); |
408 | 0 | } |
409 | | |
410 | 0 | fn report_c_variadic_callee(&mut self, sp: Span) { |
411 | 0 | let err = self |
412 | 0 | .tcx |
413 | 0 | .dcx() |
414 | | // FIXME(explicit_tail_calls): highlight the function or something... |
415 | 0 | .struct_span_err(sp, "c-variadic functions can't be tail-called") |
416 | 0 | .emit(); |
417 | | |
418 | 0 | self.found_errors = Err(err); |
419 | 0 | } |
420 | | } |
421 | | |
422 | | impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { |
423 | 0 | fn thir(&self) -> &'a Thir<'tcx> { |
424 | 0 | &self.thir |
425 | 0 | } |
426 | | |
427 | 0 | fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { |
428 | 0 | ensure_sufficient_stack(|| { |
429 | 0 | if let ExprKind::Become { value } = expr.kind { |
430 | 0 | let call = &self.thir[value]; |
431 | 0 | self.check_tail_call(call, expr); |
432 | 0 | } |
433 | | |
434 | 0 | visit::walk_expr(self, expr); |
435 | 0 | }); |
436 | 0 | } |
437 | | } |
438 | | |
439 | 0 | fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> { |
440 | 0 | let m = match tcx.as_lang_item(trait_did)? { |
441 | 0 | LangItem::Add => "add", |
442 | 0 | LangItem::Sub => "sub", |
443 | 0 | LangItem::Mul => "mul", |
444 | 0 | LangItem::Div => "div", |
445 | 0 | LangItem::Rem => "rem", |
446 | 0 | LangItem::Neg => "neg", |
447 | 0 | LangItem::Not => "not", |
448 | 0 | LangItem::BitXor => "bitxor", |
449 | 0 | LangItem::BitAnd => "bitand", |
450 | 0 | LangItem::BitOr => "bitor", |
451 | 0 | LangItem::Shl => "shl", |
452 | 0 | LangItem::Shr => "shr", |
453 | 0 | LangItem::AddAssign => "add_assign", |
454 | 0 | LangItem::SubAssign => "sub_assign", |
455 | 0 | LangItem::MulAssign => "mul_assign", |
456 | 0 | LangItem::DivAssign => "div_assign", |
457 | 0 | LangItem::RemAssign => "rem_assign", |
458 | 0 | LangItem::BitXorAssign => "bitxor_assign", |
459 | 0 | LangItem::BitAndAssign => "bitand_assign", |
460 | 0 | LangItem::BitOrAssign => "bitor_assign", |
461 | 0 | LangItem::ShlAssign => "shl_assign", |
462 | 0 | LangItem::ShrAssign => "shr_assign", |
463 | 0 | LangItem::Index => "index", |
464 | 0 | LangItem::IndexMut => "index_mut", |
465 | 0 | _ => return None, |
466 | | }; |
467 | | |
468 | 0 | Some(m) |
469 | 0 | } |