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::span_bug; |
8 | | use rustc_middle::thir::visit::{self, Visitor}; |
9 | | use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir}; |
10 | | use rustc_middle::ty::{self, Ty, TyCtxt}; |
11 | | use rustc_span::def_id::{DefId, LocalDefId}; |
12 | | use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; |
13 | | |
14 | 0 | pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> { |
15 | 0 | let (thir, expr) = tcx.thir_body(def)?; |
16 | 0 | let thir = &thir.borrow(); |
17 | | |
18 | | // If `thir` is empty, a type error occurred, skip this body. |
19 | 0 | if thir.exprs.is_empty() { |
20 | 0 | return Ok(()); |
21 | 0 | } |
22 | | |
23 | 0 | let is_closure = matches!(tcx.def_kind(def), DefKind::Closure); |
24 | 0 | let caller_ty = tcx.type_of(def).skip_binder(); |
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_ty, |
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 | | /// Type of the caller function. |
51 | | caller_ty: Ty<'tcx>, |
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 | self.report_signature_mismatch( |
152 | 0 | expr.span, |
153 | 0 | self.tcx.liberate_late_bound_regions( |
154 | 0 | CRATE_DEF_ID.to_def_id(), |
155 | 0 | self.caller_ty.fn_sig(self.tcx), |
156 | 0 | ), |
157 | 0 | self.tcx.liberate_late_bound_regions(CRATE_DEF_ID.to_def_id(), ty.fn_sig(self.tcx)), |
158 | 0 | ); |
159 | 0 | } |
160 | | |
161 | | { |
162 | | // `#[track_caller]` affects the ABI of a function (by adding a location argument), |
163 | | // so a `track_caller` can only tail call other `track_caller` functions. |
164 | | // |
165 | | // The issue is however that we can't know if a function is `track_caller` or not at |
166 | | // this point (THIR can be polymorphic, we may have an unresolved trait function). |
167 | | // We could only allow functions that we *can* resolve and *are* `track_caller`, |
168 | | // but that would turn changing `track_caller`-ness into a breaking change, |
169 | | // which is probably undesirable. |
170 | | // |
171 | | // Also note that we don't check callee's `track_caller`-ness at all, mostly for the |
172 | | // reasons above, but also because we can always tailcall the shim we'd generate for |
173 | | // coercing the function to an `fn()` pointer. (although in that case the tailcall is |
174 | | // basically useless -- the shim calls the actual function, so tailcalling the shim is |
175 | | // equivalent to calling the function) |
176 | 0 | let caller_needs_location = self.needs_location(self.caller_ty); |
177 | | |
178 | 0 | if caller_needs_location { |
179 | 0 | self.report_track_caller_caller(expr.span); |
180 | 0 | } |
181 | | } |
182 | | |
183 | 0 | if caller_sig.c_variadic { |
184 | 0 | self.report_c_variadic_caller(expr.span); |
185 | 0 | } |
186 | | |
187 | 0 | if callee_sig.c_variadic { |
188 | 0 | self.report_c_variadic_callee(expr.span); |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | | /// Returns true if function of type `ty` needs location argument |
193 | | /// (i.e. if a function is marked as `#[track_caller]`). |
194 | | /// |
195 | | /// Panics if the function's instance can't be immediately resolved. |
196 | 0 | fn needs_location(&self, ty: Ty<'tcx>) -> bool { |
197 | 0 | if let &ty::FnDef(did, substs) = ty.kind() { |
198 | 0 | let instance = |
199 | 0 | ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP); |
200 | | |
201 | 0 | instance.def.requires_caller_location(self.tcx) |
202 | | } else { |
203 | 0 | false |
204 | | } |
205 | 0 | } |
206 | | |
207 | 0 | fn report_in_closure(&mut self, expr: &Expr<'_>) { |
208 | 0 | let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures"); |
209 | 0 | self.found_errors = Err(err); |
210 | 0 | } |
211 | | |
212 | 0 | fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { |
213 | 0 | let err = self |
214 | 0 | .tcx |
215 | 0 | .dcx() |
216 | 0 | .struct_span_err(value.span, "`become` does not support operators") |
217 | 0 | .with_note("using `become` on a builtin operator is not useful") |
218 | 0 | .with_span_suggestion( |
219 | 0 | value.span.until(expr.span), |
220 | | "try using `return` instead", |
221 | | "return ", |
222 | 0 | Applicability::MachineApplicable, |
223 | | ) |
224 | 0 | .emit(); |
225 | 0 | self.found_errors = Err(err); |
226 | 0 | } |
227 | | |
228 | 0 | fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) { |
229 | 0 | let mut err = |
230 | 0 | self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators"); |
231 | | |
232 | 0 | if let &ty::FnDef(did, _substs) = fun_ty.kind() |
233 | 0 | && let parent = self.tcx.parent(did) |
234 | 0 | && matches!(self.tcx.def_kind(parent), DefKind::Trait) |
235 | 0 | && let Some(method) = op_trait_as_method_name(self.tcx, parent) |
236 | | { |
237 | 0 | match args { |
238 | 0 | &[arg] => { |
239 | 0 | let arg = &self.thir[arg]; |
240 | 0 |
|
241 | 0 | err.multipart_suggestion( |
242 | 0 | "try using the method directly", |
243 | 0 | vec![ |
244 | 0 | (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()), |
245 | 0 | (arg.span.shrink_to_hi(), format!(").{method}()")), |
246 | 0 | ], |
247 | 0 | Applicability::MaybeIncorrect, |
248 | 0 | ); |
249 | 0 | } |
250 | 0 | &[lhs, rhs] => { |
251 | 0 | let lhs = &self.thir[lhs]; |
252 | 0 | let rhs = &self.thir[rhs]; |
253 | 0 |
|
254 | 0 | err.multipart_suggestion( |
255 | 0 | "try using the method directly", |
256 | 0 | vec![ |
257 | 0 | (lhs.span.shrink_to_lo(), format!("(")), |
258 | 0 | (lhs.span.between(rhs.span), format!(").{method}(")), |
259 | 0 | (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()), |
260 | 0 | ], |
261 | 0 | Applicability::MaybeIncorrect, |
262 | 0 | ); |
263 | 0 | } |
264 | 0 | _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"), |
265 | | } |
266 | 0 | } |
267 | | |
268 | 0 | self.found_errors = Err(err.emit()); |
269 | 0 | } |
270 | | |
271 | 0 | fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { |
272 | 0 | let err = self |
273 | 0 | .tcx |
274 | 0 | .dcx() |
275 | 0 | .struct_span_err(value.span, "`become` requires a function call") |
276 | 0 | .with_span_note(value.span, "not a function call") |
277 | 0 | .with_span_suggestion( |
278 | 0 | value.span.until(expr.span), |
279 | | "try using `return` instead", |
280 | | "return ", |
281 | 0 | Applicability::MaybeIncorrect, |
282 | | ) |
283 | 0 | .emit(); |
284 | 0 | self.found_errors = Err(err); |
285 | 0 | } |
286 | | |
287 | 0 | fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) { |
288 | 0 | let underscored_args = match tupled_args.kind() { |
289 | 0 | ty::Tuple(tys) if tys.is_empty() => "".to_owned(), |
290 | 0 | ty::Tuple(tys) => std::iter::repeat_n("_, ", tys.len() - 1).chain(["_"]).collect(), |
291 | 0 | _ => "_".to_owned(), |
292 | | }; |
293 | | |
294 | 0 | let err = self |
295 | 0 | .tcx |
296 | 0 | .dcx() |
297 | 0 | .struct_span_err(expr.span, "tail calling closures directly is not allowed") |
298 | 0 | .with_multipart_suggestion( |
299 | | "try casting the closure to a function pointer type", |
300 | 0 | vec![ |
301 | 0 | (fun.span.shrink_to_lo(), "(".to_owned()), |
302 | 0 | (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")), |
303 | | ], |
304 | 0 | Applicability::MaybeIncorrect, |
305 | | ) |
306 | 0 | .emit(); |
307 | 0 | self.found_errors = Err(err); |
308 | 0 | } |
309 | | |
310 | 0 | fn report_calling_intrinsic(&mut self, expr: &Expr<'_>) { |
311 | 0 | let err = self |
312 | 0 | .tcx |
313 | 0 | .dcx() |
314 | 0 | .struct_span_err(expr.span, "tail calling intrinsics is not allowed") |
315 | 0 | .emit(); |
316 | | |
317 | 0 | self.found_errors = Err(err); |
318 | 0 | } |
319 | | |
320 | 0 | fn report_nonfn_callee(&mut self, call_sp: Span, fun_sp: Span, ty: Ty<'_>) { |
321 | 0 | let mut err = self |
322 | 0 | .tcx |
323 | 0 | .dcx() |
324 | 0 | .struct_span_err( |
325 | 0 | call_sp, |
326 | | "tail calls can only be performed with function definitions or pointers", |
327 | | ) |
328 | 0 | .with_note(format!("callee has type `{ty}`")); |
329 | | |
330 | 0 | let mut ty = ty; |
331 | 0 | let mut refs = 0; |
332 | 0 | while ty.is_box() || ty.is_ref() { |
333 | 0 | ty = ty.builtin_deref(false).unwrap(); |
334 | 0 | refs += 1; |
335 | 0 | } |
336 | | |
337 | 0 | if refs > 0 && ty.is_fn() { |
338 | 0 | let thing = if ty.is_fn_ptr() { "pointer" } else { "definition" }; |
339 | | |
340 | 0 | let derefs = |
341 | 0 | std::iter::once('(').chain(std::iter::repeat_n('*', refs)).collect::<String>(); |
342 | | |
343 | 0 | err.multipart_suggestion( |
344 | 0 | format!("consider dereferencing the expression to get a function {thing}"), |
345 | 0 | vec![(fun_sp.shrink_to_lo(), derefs), (fun_sp.shrink_to_hi(), ")".to_owned())], |
346 | 0 | Applicability::MachineApplicable, |
347 | | ); |
348 | 0 | } |
349 | | |
350 | 0 | let err = err.emit(); |
351 | 0 | self.found_errors = Err(err); |
352 | 0 | } |
353 | | |
354 | 0 | fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) { |
355 | 0 | let err = self |
356 | 0 | .tcx |
357 | 0 | .dcx() |
358 | 0 | .struct_span_err(sp, "mismatched function ABIs") |
359 | 0 | .with_note("`become` requires caller and callee to have the same ABI") |
360 | 0 | .with_note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`")) |
361 | 0 | .emit(); |
362 | 0 | self.found_errors = Err(err); |
363 | 0 | } |
364 | | |
365 | 0 | fn report_unsupported_abi(&mut self, sp: Span, callee_abi: ExternAbi) { |
366 | 0 | let err = self |
367 | 0 | .tcx |
368 | 0 | .dcx() |
369 | 0 | .struct_span_err(sp, "ABI does not support guaranteed tail calls") |
370 | 0 | .with_note(format!("`become` is not supported for `extern {callee_abi}` functions")) |
371 | 0 | .emit(); |
372 | 0 | self.found_errors = Err(err); |
373 | 0 | } |
374 | | |
375 | 0 | fn report_signature_mismatch( |
376 | 0 | &mut self, |
377 | 0 | sp: Span, |
378 | 0 | caller_sig: ty::FnSig<'_>, |
379 | 0 | callee_sig: ty::FnSig<'_>, |
380 | 0 | ) { |
381 | 0 | let err = self |
382 | 0 | .tcx |
383 | 0 | .dcx() |
384 | 0 | .struct_span_err(sp, "mismatched signatures") |
385 | 0 | .with_note("`become` requires caller and callee to have matching signatures") |
386 | 0 | .with_note(format!("caller signature: `{caller_sig}`")) |
387 | 0 | .with_note(format!("callee signature: `{callee_sig}`")) |
388 | 0 | .emit(); |
389 | 0 | self.found_errors = Err(err); |
390 | 0 | } |
391 | | |
392 | 0 | fn report_track_caller_caller(&mut self, sp: Span) { |
393 | 0 | let err = self |
394 | 0 | .tcx |
395 | 0 | .dcx() |
396 | 0 | .struct_span_err( |
397 | 0 | sp, |
398 | | "a function marked with `#[track_caller]` cannot perform a tail-call", |
399 | | ) |
400 | 0 | .emit(); |
401 | | |
402 | 0 | self.found_errors = Err(err); |
403 | 0 | } |
404 | | |
405 | 0 | fn report_c_variadic_caller(&mut self, sp: Span) { |
406 | 0 | let err = self |
407 | 0 | .tcx |
408 | 0 | .dcx() |
409 | | // FIXME(explicit_tail_calls): highlight the `...` |
410 | 0 | .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") |
411 | 0 | .emit(); |
412 | | |
413 | 0 | self.found_errors = Err(err); |
414 | 0 | } |
415 | | |
416 | 0 | fn report_c_variadic_callee(&mut self, sp: Span) { |
417 | 0 | let err = self |
418 | 0 | .tcx |
419 | 0 | .dcx() |
420 | | // FIXME(explicit_tail_calls): highlight the function or something... |
421 | 0 | .struct_span_err(sp, "c-variadic functions can't be tail-called") |
422 | 0 | .emit(); |
423 | | |
424 | 0 | self.found_errors = Err(err); |
425 | 0 | } |
426 | | } |
427 | | |
428 | | impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { |
429 | 0 | fn thir(&self) -> &'a Thir<'tcx> { |
430 | 0 | &self.thir |
431 | 0 | } |
432 | | |
433 | 0 | fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { |
434 | 0 | ensure_sufficient_stack(|| { |
435 | 0 | if let ExprKind::Become { value } = expr.kind { |
436 | 0 | let call = &self.thir[value]; |
437 | 0 | self.check_tail_call(call, expr); |
438 | 0 | } |
439 | | |
440 | 0 | visit::walk_expr(self, expr); |
441 | 0 | }); |
442 | 0 | } |
443 | | } |
444 | | |
445 | 0 | fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> { |
446 | 0 | let m = match tcx.as_lang_item(trait_did)? { |
447 | 0 | LangItem::Add => "add", |
448 | 0 | LangItem::Sub => "sub", |
449 | 0 | LangItem::Mul => "mul", |
450 | 0 | LangItem::Div => "div", |
451 | 0 | LangItem::Rem => "rem", |
452 | 0 | LangItem::Neg => "neg", |
453 | 0 | LangItem::Not => "not", |
454 | 0 | LangItem::BitXor => "bitxor", |
455 | 0 | LangItem::BitAnd => "bitand", |
456 | 0 | LangItem::BitOr => "bitor", |
457 | 0 | LangItem::Shl => "shl", |
458 | 0 | LangItem::Shr => "shr", |
459 | 0 | LangItem::AddAssign => "add_assign", |
460 | 0 | LangItem::SubAssign => "sub_assign", |
461 | 0 | LangItem::MulAssign => "mul_assign", |
462 | 0 | LangItem::DivAssign => "div_assign", |
463 | 0 | LangItem::RemAssign => "rem_assign", |
464 | 0 | LangItem::BitXorAssign => "bitxor_assign", |
465 | 0 | LangItem::BitAndAssign => "bitand_assign", |
466 | 0 | LangItem::BitOrAssign => "bitor_assign", |
467 | 0 | LangItem::ShlAssign => "shl_assign", |
468 | 0 | LangItem::ShrAssign => "shr_assign", |
469 | 0 | LangItem::Index => "index", |
470 | 0 | LangItem::IndexMut => "index_mut", |
471 | 0 | _ => return None, |
472 | | }; |
473 | | |
474 | 0 | Some(m) |
475 | 0 | } |