From cfb78419cd865a19dac54d234d2a6abde0f14628 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 11 May 2023 11:43:09 +0000 Subject: [PATCH 1/6] implement checks for tail calls this implements checks necessary to guarantee that we can actually perform a tail call. while extremely restrictive, this is what is documented in the RFC, and all these checks are needed for one reason or another. --- compiler/rustc_middle/src/query/mod.rs | 6 + compiler/rustc_mir_build/src/build/mod.rs | 4 + .../rustc_mir_build/src/check_tail_calls.rs | 387 ++++++++++++++++++ compiler/rustc_mir_build/src/lib.rs | 2 + tests/ui/explicit-tail-calls/become-macro.rs | 13 + .../explicit-tail-calls/become-operator.fixed | 42 ++ .../ui/explicit-tail-calls/become-operator.rs | 42 ++ .../become-operator.stderr | 75 ++++ .../become-uncallable.fixed | 18 + .../explicit-tail-calls/become-uncallable.rs | 18 + .../become-uncallable.stderr | 44 ++ tests/ui/explicit-tail-calls/closure.fixed | 31 ++ tests/ui/explicit-tail-calls/closure.rs | 31 ++ tests/ui/explicit-tail-calls/closure.stderr | 46 +++ tests/ui/explicit-tail-calls/in-closure.rs | 8 + .../ui/explicit-tail-calls/in-closure.stderr | 8 + .../explicit-tail-calls/signature-mismatch.rs | 33 ++ .../signature-mismatch.stderr | 40 ++ 18 files changed, 848 insertions(+) create mode 100644 compiler/rustc_mir_build/src/check_tail_calls.rs create mode 100644 tests/ui/explicit-tail-calls/become-macro.rs create mode 100644 tests/ui/explicit-tail-calls/become-operator.fixed create mode 100644 tests/ui/explicit-tail-calls/become-operator.rs create mode 100644 tests/ui/explicit-tail-calls/become-operator.stderr create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.fixed create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.rs create mode 100644 tests/ui/explicit-tail-calls/become-uncallable.stderr create mode 100644 tests/ui/explicit-tail-calls/closure.fixed create mode 100644 tests/ui/explicit-tail-calls/closure.rs create mode 100644 tests/ui/explicit-tail-calls/closure.stderr create mode 100644 tests/ui/explicit-tail-calls/in-closure.rs create mode 100644 tests/ui/explicit-tail-calls/in-closure.stderr create mode 100644 tests/ui/explicit-tail-calls/signature-mismatch.rs create mode 100644 tests/ui/explicit-tail-calls/signature-mismatch.stderr diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 0f2a6d598a0fd..d3748af40777b 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -916,6 +916,12 @@ rustc_queries! { cache_on_disk_if { true } } + /// Checks well-formedness of tail calls (`become f()`). + query check_tail_calls(key: LocalDefId) -> Result<(), rustc_errors::ErrorGuaranteed> { + desc { |tcx| "tail-call-checking `{}`", tcx.def_path_str(key) } + cache_on_disk_if { true } + } + /// Returns the types assumed to be well formed while "inside" of the given item. /// /// Note that we've liberated the late bound regions of function signatures, so diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index 3317f3b7f8acb..f43c29d8f5d68 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -50,6 +50,10 @@ pub(crate) fn mir_build<'tcx>(tcx: TyCtxtAt<'tcx>, def: LocalDefId) -> Body<'tcx return construct_error(tcx, def, e); } + if let Err(err) = tcx.check_tail_calls(def) { + return construct_error(tcx, def, err); + } + let body = match tcx.thir_body(def) { Err(error_reported) => construct_error(tcx, def, error_reported), Ok((thir, expr)) => { diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs new file mode 100644 index 0000000000000..911a6cb7de606 --- /dev/null +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -0,0 +1,387 @@ +use rustc_abi::ExternAbi; +use rustc_errors::Applicability; +use rustc_hir::LangItem; +use rustc_hir::def::DefKind; +use rustc_middle::span_bug; +use rustc_middle::thir::visit::{self, Visitor}; +use rustc_middle::thir::{BodyTy, Expr, ExprId, ExprKind, Thir}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::def_id::{DefId, LocalDefId}; +use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span}; + +pub(crate) fn check_tail_calls(tcx: TyCtxt<'_>, def: LocalDefId) -> Result<(), ErrorGuaranteed> { + let (thir, expr) = tcx.thir_body(def)?; + let thir = &thir.borrow(); + + // If `thir` is empty, a type error occurred, skip this body. + if thir.exprs.is_empty() { + return Ok(()); + } + + let is_closure = matches!(tcx.def_kind(def), DefKind::Closure); + let caller_ty = tcx.type_of(def).skip_binder(); + + let mut visitor = TailCallCkVisitor { + tcx, + thir, + found_errors: Ok(()), + // FIXME(#132279): we're clearly in a body here. + typing_env: ty::TypingEnv::non_body_analysis(tcx, def), + is_closure, + caller_ty, + }; + + visitor.visit_expr(&thir[expr]); + + visitor.found_errors +} + +struct TailCallCkVisitor<'a, 'tcx> { + tcx: TyCtxt<'tcx>, + thir: &'a Thir<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + /// Whatever the currently checked body is one of a closure + is_closure: bool, + /// The result of the checks, `Err(_)` if there was a problem with some + /// tail call, `Ok(())` if all of them were fine. + found_errors: Result<(), ErrorGuaranteed>, + /// Type of the caller function. + caller_ty: Ty<'tcx>, +} + +impl<'tcx> TailCallCkVisitor<'_, 'tcx> { + fn check_tail_call(&mut self, call: &Expr<'_>, expr: &Expr<'_>) { + if self.is_closure { + self.report_in_closure(expr); + return; + } + + let BodyTy::Fn(caller_sig) = self.thir.body_type else { + span_bug!( + call.span, + "`become` outside of functions should have been disallowed by hit_typeck" + ) + }; + + let ExprKind::Scope { value, .. } = call.kind else { + span_bug!(call.span, "expected scope, found: {call:?}") + }; + let value = &self.thir[value]; + + if matches!( + value.kind, + ExprKind::Binary { .. } + | ExprKind::Unary { .. } + | ExprKind::AssignOp { .. } + | ExprKind::Index { .. } + ) { + self.report_builtin_op(call, expr); + return; + } + + let ExprKind::Call { ty, fun, ref args, from_hir_call, fn_span } = value.kind else { + self.report_non_call(value, expr); + return; + }; + + if !from_hir_call { + self.report_op(ty, args, fn_span, expr); + } + + // Closures in thir look something akin to + // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}` + // So we have to check for them in this weird way... + if let &ty::FnDef(did, substs) = ty.kind() { + let parent = self.tcx.parent(did); + let fn_ = self.tcx.require_lang_item(LangItem::Fn, Some(expr.span)); + let fn_once = self.tcx.require_lang_item(LangItem::FnOnce, Some(expr.span)); + let fn_mut = self.tcx.require_lang_item(LangItem::FnMut, Some(expr.span)); + if [fn_, fn_once, fn_mut].contains(&parent) { + if substs.first().and_then(|arg| arg.as_type()).is_some_and(|t| t.is_closure()) { + self.report_calling_closure( + &self.thir[fun], + substs[1].as_type().unwrap(), + expr, + ); + + // Tail calling is likely to cause unrelated errors (ABI, argument mismatches) + return; + } + }; + } + + // Erase regions since tail calls don't care about lifetimes + let callee_sig = + self.tcx.normalize_erasing_late_bound_regions(self.typing_env, ty.fn_sig(self.tcx)); + + if caller_sig.abi != callee_sig.abi { + self.report_abi_mismatch(expr.span, caller_sig.abi, callee_sig.abi); + } + + if caller_sig.inputs_and_output != callee_sig.inputs_and_output { + if caller_sig.inputs() != callee_sig.inputs() { + self.report_arguments_mismatch(expr.span, caller_sig, callee_sig); + } + + if caller_sig.output() != callee_sig.output() { + span_bug!(expr.span, "hir typeck should have checked the return type already"); + } + } + + { + let caller_needs_location = self.needs_location(self.caller_ty); + let callee_needs_location = self.needs_location(ty); + + if caller_needs_location != callee_needs_location { + self.report_track_caller_mismatch(expr.span, caller_needs_location); + } + } + + if caller_sig.c_variadic { + self.report_c_variadic_caller(expr.span); + } + + if callee_sig.c_variadic { + self.report_c_variadic_callee(expr.span); + } + } + + /// Returns true if function of type `ty` needs location argument + /// (i.e. if a function is marked as `#[track_caller]`) + fn needs_location(&self, ty: Ty<'tcx>) -> bool { + if let &ty::FnDef(did, substs) = ty.kind() { + let instance = + ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP) + .polymorphize(self.tcx); + + instance.def.requires_caller_location(self.tcx) + } else { + false + } + } + + fn report_in_closure(&mut self, expr: &Expr<'_>) { + let err = self.tcx.dcx().span_err(expr.span, "`become` is not allowed in closures"); + self.found_errors = Err(err); + } + + fn report_builtin_op(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + let err = self + .tcx + .dcx() + .struct_span_err(value.span, "`become` does not support operators") + .with_note("using `become` on a builtin operator is not useful") + .with_span_suggestion( + value.span.until(expr.span), + "try using `return` instead", + "return ", + Applicability::MachineApplicable, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_op(&mut self, fun_ty: Ty<'_>, args: &[ExprId], fn_span: Span, expr: &Expr<'_>) { + let mut err = + self.tcx.dcx().struct_span_err(fn_span, "`become` does not support operators"); + + if let &ty::FnDef(did, _substs) = fun_ty.kind() + && let parent = self.tcx.parent(did) + && matches!(self.tcx.def_kind(parent), DefKind::Trait) + && let Some(method) = op_trait_as_method_name(self.tcx, parent) + { + match args { + &[arg] => { + let arg = &self.thir[arg]; + + err.multipart_suggestion( + "try using the method directly", + vec![ + (fn_span.shrink_to_lo().until(arg.span), "(".to_owned()), + (arg.span.shrink_to_hi(), format!(").{method}()")), + ], + Applicability::MaybeIncorrect, + ); + } + &[lhs, rhs] => { + let lhs = &self.thir[lhs]; + let rhs = &self.thir[rhs]; + + err.multipart_suggestion( + "try using the method directly", + vec![ + (lhs.span.shrink_to_lo(), format!("(")), + (lhs.span.between(rhs.span), format!(").{method}(")), + (rhs.span.between(expr.span.shrink_to_hi()), ")".to_owned()), + ], + Applicability::MaybeIncorrect, + ); + } + _ => span_bug!(expr.span, "operator with more than 2 args? {args:?}"), + } + } + + self.found_errors = Err(err.emit()); + } + + fn report_non_call(&mut self, value: &Expr<'_>, expr: &Expr<'_>) { + let err = self + .tcx + .dcx() + .struct_span_err(value.span, "`become` requires a function call") + .with_span_note(value.span, "not a function call") + .with_span_suggestion( + value.span.until(expr.span), + "try using `return` instead", + "return ", + Applicability::MaybeIncorrect, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_calling_closure(&mut self, fun: &Expr<'_>, tupled_args: Ty<'_>, expr: &Expr<'_>) { + let underscored_args = match tupled_args.kind() { + ty::Tuple(tys) if tys.is_empty() => "".to_owned(), + ty::Tuple(tys) => std::iter::repeat("_, ").take(tys.len() - 1).chain(["_"]).collect(), + _ => "_".to_owned(), + }; + + let err = self + .tcx + .dcx() + .struct_span_err(expr.span, "tail calling closures directly is not allowed") + .with_multipart_suggestion( + "try casting the closure to a function pointer type", + vec![ + (fun.span.shrink_to_lo(), "(".to_owned()), + (fun.span.shrink_to_hi(), format!(" as fn({underscored_args}) -> _)")), + ], + Applicability::MaybeIncorrect, + ) + .emit(); + self.found_errors = Err(err); + } + + fn report_abi_mismatch(&mut self, sp: Span, caller_abi: ExternAbi, callee_abi: ExternAbi) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, "mismatched function ABIs") + .with_note("`become` requires caller and callee to have the same ABI") + .with_note(format!("caller ABI is `{caller_abi}`, while callee ABI is `{callee_abi}`")) + .emit(); + self.found_errors = Err(err); + } + + fn report_arguments_mismatch( + &mut self, + sp: Span, + caller_sig: ty::FnSig<'_>, + callee_sig: ty::FnSig<'_>, + ) { + let err = self + .tcx + .dcx() + .struct_span_err(sp, "mismatched signatures") + .with_note("`become` requires caller and callee to have matching signatures") + .with_note(format!("caller signature: `{caller_sig}`")) + .with_note(format!("callee signature: `{callee_sig}`")) + .emit(); + self.found_errors = Err(err); + } + + fn report_track_caller_mismatch(&mut self, sp: Span, caller_needs_location: bool) { + let err = match caller_needs_location { + true => self + .tcx + .dcx() + .struct_span_err( + sp, + "a function marked with `#[track_caller]` cannot tail-call one that is not", + ) + .emit(), + false => self + .tcx + .dcx() + .struct_span_err( + sp, + "a function mot marked with `#[track_caller]` cannot tail-call one that is", + ) + .emit(), + }; + + self.found_errors = Err(err); + } + + fn report_c_variadic_caller(&mut self, sp: Span) { + let err = self + .tcx + .dcx() + // FIXME(explicit_tail_calls): highlight the `...` + .struct_span_err(sp, "tail-calls are not allowed in c-variadic functions") + .emit(); + + self.found_errors = Err(err); + } + + fn report_c_variadic_callee(&mut self, sp: Span) { + let err = self + .tcx + .dcx() + // FIXME(explicit_tail_calls): highlight the function or something... + .struct_span_err(sp, "c-variadic functions can't be tail-called") + .emit(); + + self.found_errors = Err(err); + } +} + +impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { + fn thir(&self) -> &'a Thir<'tcx> { + &self.thir + } + + fn visit_expr(&mut self, expr: &'a Expr<'tcx>) { + if let ExprKind::Become { value } = expr.kind { + let call = &self.thir[value]; + self.check_tail_call(call, expr); + } + + visit::walk_expr(self, expr); + } +} + +fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> { + let trait_did = Some(trait_did); + let items = tcx.lang_items(); + let m = match () { + _ if trait_did == items.get(LangItem::Add) => "add", + _ if trait_did == items.get(LangItem::Sub) => "sub", + _ if trait_did == items.get(LangItem::Mul) => "mul", + _ if trait_did == items.get(LangItem::Div) => "div", + _ if trait_did == items.get(LangItem::Rem) => "rem", + _ if trait_did == items.get(LangItem::Neg) => "neg", + _ if trait_did == items.get(LangItem::Not) => "not", + _ if trait_did == items.get(LangItem::BitXor) => "bitxor", + _ if trait_did == items.get(LangItem::BitAnd) => "bitand", + _ if trait_did == items.get(LangItem::BitOr) => "bitor", + _ if trait_did == items.get(LangItem::Shl) => "shl", + _ if trait_did == items.get(LangItem::Shr) => "shr", + _ if trait_did == items.get(LangItem::AddAssign) => "add_assign", + _ if trait_did == items.get(LangItem::SubAssign) => "sub_assign", + _ if trait_did == items.get(LangItem::MulAssign) => "mul_assign", + _ if trait_did == items.get(LangItem::DivAssign) => "div_assign", + _ if trait_did == items.get(LangItem::RemAssign) => "rem_assign", + _ if trait_did == items.get(LangItem::BitXorAssign) => "bitxor_assign", + _ if trait_did == items.get(LangItem::BitAndAssign) => "bitand_assign", + _ if trait_did == items.get(LangItem::BitOrAssign) => "bitor_assign", + _ if trait_did == items.get(LangItem::ShlAssign) => "shl_assign", + _ if trait_did == items.get(LangItem::ShrAssign) => "shr_assign", + _ if trait_did == items.get(LangItem::Index) => "index", + _ if trait_did == items.get(LangItem::IndexMut) => "index_mut", + _ => return None, + }; + + Some(m) +} diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs index 3dbb552cdbbef..833e5019865e8 100644 --- a/compiler/rustc_mir_build/src/lib.rs +++ b/compiler/rustc_mir_build/src/lib.rs @@ -12,6 +12,7 @@ // tidy-alphabetical-end mod build; +mod check_tail_calls; mod check_unsafety; mod errors; pub mod lints; @@ -28,6 +29,7 @@ pub fn provide(providers: &mut Providers) { providers.closure_saved_names_of_captured_variables = build::closure_saved_names_of_captured_variables; providers.check_unsafety = check_unsafety::check_unsafety; + providers.check_tail_calls = check_tail_calls::check_tail_calls; providers.thir_body = thir::cx::thir_body; providers.hooks.thir_tree = thir::print::thir_tree; providers.hooks.thir_flat = thir::print::thir_flat; diff --git a/tests/ui/explicit-tail-calls/become-macro.rs b/tests/ui/explicit-tail-calls/become-macro.rs new file mode 100644 index 0000000000000..0bbc483b2c703 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-macro.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls, decl_macro)] + +macro call($f:expr $(, $args:expr)* $(,)?) { + ($f)($($args),*) +} + +fn main() { + become call!(f); +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.fixed b/tests/ui/explicit-tail-calls/become-operator.fixed new file mode 100644 index 0000000000000..36925d21b7a9c --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.fixed @@ -0,0 +1,42 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +#![allow(unused)] +use std::num::Wrapping; +use std::ops::{Not, Add, BitXorAssign}; + +// built-ins and overloaded operators are handled differently + +fn f(a: u64, b: u64) -> u64 { + return a + b; //~ error: `become` does not support operators +} + +fn g(a: String, b: &str) -> String { + become (a).add(b); //~ error: `become` does not support operators +} + +fn h(x: u64) -> u64 { + return !x; //~ error: `become` does not support operators +} + +fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping { + become (x).not(); //~ error: `become` does not support operators +} + +fn builtin_index(x: &[u8], i: usize) -> u8 { + return x[i] //~ error: `become` does not support operators +} + +// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`, +// and so need additional handling + +fn a(a: &mut u8, _: u8) { + return *a ^= 1; //~ error: `become` does not support operators +} + +fn b(b: &mut Wrapping, _: u8) { + become (*b).bitxor_assign(1); //~ error: `become` does not support operators +} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.rs b/tests/ui/explicit-tail-calls/become-operator.rs new file mode 100644 index 0000000000000..b1af3f59df7f0 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.rs @@ -0,0 +1,42 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +#![allow(unused)] +use std::num::Wrapping; +use std::ops::{Not, Add, BitXorAssign}; + +// built-ins and overloaded operators are handled differently + +fn f(a: u64, b: u64) -> u64 { + become a + b; //~ error: `become` does not support operators +} + +fn g(a: String, b: &str) -> String { + become a + b; //~ error: `become` does not support operators +} + +fn h(x: u64) -> u64 { + become !x; //~ error: `become` does not support operators +} + +fn i_do_not_know_any_more_letters(x: Wrapping) -> Wrapping { + become !x; //~ error: `become` does not support operators +} + +fn builtin_index(x: &[u8], i: usize) -> u8 { + become x[i] //~ error: `become` does not support operators +} + +// FIXME(explicit_tail_calls): overloaded index is represented like `[&]*x.index(i)`, +// and so need additional handling + +fn a(a: &mut u8, _: u8) { + become *a ^= 1; //~ error: `become` does not support operators +} + +fn b(b: &mut Wrapping, _: u8) { + become *b ^= 1; //~ error: `become` does not support operators +} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-operator.stderr b/tests/ui/explicit-tail-calls/become-operator.stderr new file mode 100644 index 0000000000000..26e4343faaea7 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-operator.stderr @@ -0,0 +1,75 @@ +error: `become` does not support operators + --> $DIR/become-operator.rs:11:12 + | +LL | become a + b; + | -------^^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:15:12 + | +LL | become a + b; + | ^^^^^ + | +help: try using the method directly + | +LL | become (a).add(b); + | + ~~~~~~ + + +error: `become` does not support operators + --> $DIR/become-operator.rs:19:12 + | +LL | become !x; + | -------^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:23:12 + | +LL | become !x; + | ^^ + | +help: try using the method directly + | +LL | become (x).not(); + | ~ +++++++ + +error: `become` does not support operators + --> $DIR/become-operator.rs:27:12 + | +LL | become x[i] + | -------^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:34:12 + | +LL | become *a ^= 1; + | -------^^^^^^^ + | | + | help: try using `return` instead: `return` + | + = note: using `become` on a builtin operator is not useful + +error: `become` does not support operators + --> $DIR/become-operator.rs:38:12 + | +LL | become *b ^= 1; + | ^^^^^^^ + | +help: try using the method directly + | +LL | become (*b).bitxor_assign(1); + | + ~~~~~~~~~~~~~~~~ + + +error: aborting due to 7 previous errors + diff --git a/tests/ui/explicit-tail-calls/become-uncallable.fixed b/tests/ui/explicit-tail-calls/become-uncallable.fixed new file mode 100644 index 0000000000000..af6785e08fe69 --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.fixed @@ -0,0 +1,18 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +#![allow(unused)] + +fn f() -> u64 { + return 1; //~ error: `become` requires a function call +} + +fn g() { + return { h() }; //~ error: `become` requires a function call +} + +fn h() { + return *&g(); //~ error: `become` requires a function call +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-uncallable.rs b/tests/ui/explicit-tail-calls/become-uncallable.rs new file mode 100644 index 0000000000000..60026b0d5d6fb --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.rs @@ -0,0 +1,18 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +#![allow(unused)] + +fn f() -> u64 { + become 1; //~ error: `become` requires a function call +} + +fn g() { + become { h() }; //~ error: `become` requires a function call +} + +fn h() { + become *&g(); //~ error: `become` requires a function call +} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/become-uncallable.stderr b/tests/ui/explicit-tail-calls/become-uncallable.stderr new file mode 100644 index 0000000000000..90f10b05d2afc --- /dev/null +++ b/tests/ui/explicit-tail-calls/become-uncallable.stderr @@ -0,0 +1,44 @@ +error: `become` requires a function call + --> $DIR/become-uncallable.rs:7:12 + | +LL | become 1; + | -------^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:7:12 + | +LL | become 1; + | ^ + +error: `become` requires a function call + --> $DIR/become-uncallable.rs:11:12 + | +LL | become { h() }; + | -------^^^^^^^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:11:12 + | +LL | become { h() }; + | ^^^^^^^ + +error: `become` requires a function call + --> $DIR/become-uncallable.rs:15:12 + | +LL | become *&g(); + | -------^^^^^ + | | + | help: try using `return` instead: `return` + | +note: not a function call + --> $DIR/become-uncallable.rs:15:12 + | +LL | become *&g(); + | ^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui/explicit-tail-calls/closure.fixed b/tests/ui/explicit-tail-calls/closure.fixed new file mode 100644 index 0000000000000..18384d91e0ffe --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.fixed @@ -0,0 +1,31 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +fn a() { + become ((|| ()) as fn() -> _)(); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aa((): ()) { + become ((|()| ()) as fn(_) -> _)(()); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aaa((): (), _: i32) { + become ((|(), _| ()) as fn(_, _) -> _)((), 1); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) { + let f = |(), ((), ())| (((), ()), ()); + become (f as fn(_, _) -> _)((), ((), ())); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn main() { + a(); + aa(()); + aaa((), 1); + v((), ((), ())); +} diff --git a/tests/ui/explicit-tail-calls/closure.rs b/tests/ui/explicit-tail-calls/closure.rs new file mode 100644 index 0000000000000..b65ebed594bfd --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.rs @@ -0,0 +1,31 @@ +//@ run-rustfix +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +fn a() { + become (|| ())(); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aa((): ()) { + become (|()| ())(()); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn aaa((): (), _: i32) { + become (|(), _| ())((), 1); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn v((): (), ((), ()): ((), ())) -> (((), ()), ()) { + let f = |(), ((), ())| (((), ()), ()); + become f((), ((), ())); + //~^ ERROR: tail calling closures directly is not allowed +} + +fn main() { + a(); + aa(()); + aaa((), 1); + v((), ((), ())); +} diff --git a/tests/ui/explicit-tail-calls/closure.stderr b/tests/ui/explicit-tail-calls/closure.stderr new file mode 100644 index 0000000000000..5d57bece68e2c --- /dev/null +++ b/tests/ui/explicit-tail-calls/closure.stderr @@ -0,0 +1,46 @@ +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:6:5 + | +LL | become (|| ())(); + | ^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|| ()) as fn() -> _)(); + | + +++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:11:5 + | +LL | become (|()| ())(()); + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|()| ()) as fn(_) -> _)(()); + | + ++++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:16:5 + | +LL | become (|(), _| ())((), 1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become ((|(), _| ()) as fn(_, _) -> _)((), 1); + | + +++++++++++++++++ + +error: tail calling closures directly is not allowed + --> $DIR/closure.rs:22:5 + | +LL | become f((), ((), ())); + | ^^^^^^^^^^^^^^^^^^^^^^ + | +help: try casting the closure to a function pointer type + | +LL | become (f as fn(_, _) -> _)((), ((), ())); + | + +++++++++++++++++ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/explicit-tail-calls/in-closure.rs b/tests/ui/explicit-tail-calls/in-closure.rs new file mode 100644 index 0000000000000..225bc0a7fd762 --- /dev/null +++ b/tests/ui/explicit-tail-calls/in-closure.rs @@ -0,0 +1,8 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +fn main() { + || become f(); //~ error: `become` is not allowed in closures +} + +fn f() {} diff --git a/tests/ui/explicit-tail-calls/in-closure.stderr b/tests/ui/explicit-tail-calls/in-closure.stderr new file mode 100644 index 0000000000000..d221f732e938b --- /dev/null +++ b/tests/ui/explicit-tail-calls/in-closure.stderr @@ -0,0 +1,8 @@ +error: `become` is not allowed in closures + --> $DIR/in-closure.rs:5:8 + | +LL | || become f(); + | ^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.rs b/tests/ui/explicit-tail-calls/signature-mismatch.rs new file mode 100644 index 0000000000000..3a01cc1ca2fd2 --- /dev/null +++ b/tests/ui/explicit-tail-calls/signature-mismatch.rs @@ -0,0 +1,33 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +#![feature(c_variadic)] + +fn _f0((): ()) { + become _g0(); //~ error: mismatched signatures +} + +fn _g0() {} + + +fn _f1() { + become _g1(()); //~ error: mismatched signatures +} + +fn _g1((): ()) {} + + +extern "C" fn _f2() { + become _g2(); //~ error: mismatched function ABIs +} + +fn _g2() {} + + +fn _f3() { + become _g3(); //~ error: mismatched function ABIs +} + +extern "C" fn _g3() {} + + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.stderr b/tests/ui/explicit-tail-calls/signature-mismatch.stderr new file mode 100644 index 0000000000000..ba9e9dcb98483 --- /dev/null +++ b/tests/ui/explicit-tail-calls/signature-mismatch.stderr @@ -0,0 +1,40 @@ +error: mismatched signatures + --> $DIR/signature-mismatch.rs:6:5 + | +LL | become _g0(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn(())` + = note: callee signature: `fn()` + +error: mismatched signatures + --> $DIR/signature-mismatch.rs:13:5 + | +LL | become _g1(()); + | ^^^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have matching signatures + = note: caller signature: `fn()` + = note: callee signature: `fn(())` + +error: mismatched function ABIs + --> $DIR/signature-mismatch.rs:20:5 + | +LL | become _g2(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"C"`, while callee ABI is `"Rust"` + +error: mismatched function ABIs + --> $DIR/signature-mismatch.rs:27:5 + | +LL | become _g3(); + | ^^^^^^^^^^^^ + | + = note: `become` requires caller and callee to have the same ABI + = note: caller ABI is `"Rust"`, while callee ABI is `"C"` + +error: aborting due to 4 previous errors + From 3208b8649e2e117b08e5bbcae46061d44bf72203 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 29 Nov 2024 04:28:02 +0100 Subject: [PATCH 2/6] use `expect(incomplete_feature)` instead of `allow` in tail call tests --- tests/ui/explicit-tail-calls/become-macro.rs | 2 +- tests/ui/explicit-tail-calls/become-operator.fixed | 2 +- tests/ui/explicit-tail-calls/become-operator.rs | 2 +- tests/ui/explicit-tail-calls/become-outside.rs | 2 +- tests/ui/explicit-tail-calls/become-uncallable.fixed | 2 +- tests/ui/explicit-tail-calls/become-uncallable.rs | 2 +- tests/ui/explicit-tail-calls/closure.fixed | 2 +- tests/ui/explicit-tail-calls/closure.rs | 2 +- tests/ui/explicit-tail-calls/constck.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-arg-move.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs | 2 +- tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs | 2 +- tests/ui/explicit-tail-calls/drop-order.rs | 2 +- tests/ui/explicit-tail-calls/in-closure.rs | 2 +- tests/ui/explicit-tail-calls/return-lifetime-sub.rs | 2 +- tests/ui/explicit-tail-calls/return-mismatches.rs | 2 +- tests/ui/explicit-tail-calls/signature-mismatch.rs | 2 +- tests/ui/explicit-tail-calls/unsafeck.rs | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/ui/explicit-tail-calls/become-macro.rs b/tests/ui/explicit-tail-calls/become-macro.rs index 0bbc483b2c703..0a9d069ceb3cb 100644 --- a/tests/ui/explicit-tail-calls/become-macro.rs +++ b/tests/ui/explicit-tail-calls/become-macro.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls, decl_macro)] macro call($f:expr $(, $args:expr)* $(,)?) { diff --git a/tests/ui/explicit-tail-calls/become-operator.fixed b/tests/ui/explicit-tail-calls/become-operator.fixed index 36925d21b7a9c..24baef42d69a3 100644 --- a/tests/ui/explicit-tail-calls/become-operator.fixed +++ b/tests/ui/explicit-tail-calls/become-operator.fixed @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![allow(unused)] use std::num::Wrapping; diff --git a/tests/ui/explicit-tail-calls/become-operator.rs b/tests/ui/explicit-tail-calls/become-operator.rs index b1af3f59df7f0..e547e979b6485 100644 --- a/tests/ui/explicit-tail-calls/become-operator.rs +++ b/tests/ui/explicit-tail-calls/become-operator.rs @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![allow(unused)] use std::num::Wrapping; diff --git a/tests/ui/explicit-tail-calls/become-outside.rs b/tests/ui/explicit-tail-calls/become-outside.rs index 9c90d929111e1..29738bb9a299f 100644 --- a/tests/ui/explicit-tail-calls/become-outside.rs +++ b/tests/ui/explicit-tail-calls/become-outside.rs @@ -1,5 +1,5 @@ //@ revisions: constant array -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #[cfg(constant)] diff --git a/tests/ui/explicit-tail-calls/become-uncallable.fixed b/tests/ui/explicit-tail-calls/become-uncallable.fixed index af6785e08fe69..b77c46ea43549 100644 --- a/tests/ui/explicit-tail-calls/become-uncallable.fixed +++ b/tests/ui/explicit-tail-calls/become-uncallable.fixed @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![allow(unused)] diff --git a/tests/ui/explicit-tail-calls/become-uncallable.rs b/tests/ui/explicit-tail-calls/become-uncallable.rs index 60026b0d5d6fb..a73b9258aaabb 100644 --- a/tests/ui/explicit-tail-calls/become-uncallable.rs +++ b/tests/ui/explicit-tail-calls/become-uncallable.rs @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![allow(unused)] diff --git a/tests/ui/explicit-tail-calls/closure.fixed b/tests/ui/explicit-tail-calls/closure.fixed index 18384d91e0ffe..4af71c5d4cc95 100644 --- a/tests/ui/explicit-tail-calls/closure.fixed +++ b/tests/ui/explicit-tail-calls/closure.fixed @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] fn a() { diff --git a/tests/ui/explicit-tail-calls/closure.rs b/tests/ui/explicit-tail-calls/closure.rs index b65ebed594bfd..79a1b5296b0ae 100644 --- a/tests/ui/explicit-tail-calls/closure.rs +++ b/tests/ui/explicit-tail-calls/closure.rs @@ -1,5 +1,5 @@ //@ run-rustfix -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] fn a() { diff --git a/tests/ui/explicit-tail-calls/constck.rs b/tests/ui/explicit-tail-calls/constck.rs index 938f15f12c091..36fc3ef6f995b 100644 --- a/tests/ui/explicit-tail-calls/constck.rs +++ b/tests/ui/explicit-tail-calls/constck.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] const fn f() { diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs index 5a105ee4eb59d..0a61c90bd00a7 100644 --- a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] pub const fn test(_: &Type) { diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs index 50bf6c946ca8f..a34482a352725 100644 --- a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs +++ b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] pub const fn test(x: &Type) { diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs index 88ff3a4a5ad13..610eb74a91e37 100644 --- a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs +++ b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] pub const fn test(s: String) -> String { diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs index 86041b669601d..53d46d92bd7b7 100644 --- a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -1,5 +1,5 @@ //@ run-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] /// A very unnecessarily complicated "implementation" of the Collatz conjecture. diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs index 54e68b2b7f793..2a04d4893e68a 100644 --- a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs @@ -1,6 +1,6 @@ //@ revisions: become return //@ [become] run-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] // This is an identity function (`|x| x`), but implemented using recursion. diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs index 3d69cde29895a..fba4a2692afe3 100644 --- a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] pub const fn f() { diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs index e20730446ec55..242336be4845e 100644 --- a/tests/ui/explicit-tail-calls/drop-order.rs +++ b/tests/ui/explicit-tail-calls/drop-order.rs @@ -1,7 +1,7 @@ // FIXME(explicit_tail_calls): enable this test once rustc_codegen_ssa supports tail calls //@ ignore-test: tail calls are not implemented in rustc_codegen_ssa yet, so this causes 🧊 //@ run-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] use std::cell::RefCell; diff --git a/tests/ui/explicit-tail-calls/in-closure.rs b/tests/ui/explicit-tail-calls/in-closure.rs index 225bc0a7fd762..b23ff5a1e05b4 100644 --- a/tests/ui/explicit-tail-calls/in-closure.rs +++ b/tests/ui/explicit-tail-calls/in-closure.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] fn main() { diff --git a/tests/ui/explicit-tail-calls/return-lifetime-sub.rs b/tests/ui/explicit-tail-calls/return-lifetime-sub.rs index 1243fba9b5883..df0982cc02bdc 100644 --- a/tests/ui/explicit-tail-calls/return-lifetime-sub.rs +++ b/tests/ui/explicit-tail-calls/return-lifetime-sub.rs @@ -1,5 +1,5 @@ //@ check-pass -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] fn _f<'a>() -> &'a [u8] { diff --git a/tests/ui/explicit-tail-calls/return-mismatches.rs b/tests/ui/explicit-tail-calls/return-mismatches.rs index 935a1a1d28b02..e48766b89b00c 100644 --- a/tests/ui/explicit-tail-calls/return-mismatches.rs +++ b/tests/ui/explicit-tail-calls/return-mismatches.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] fn _f0<'a>() -> &'static [u8] { diff --git a/tests/ui/explicit-tail-calls/signature-mismatch.rs b/tests/ui/explicit-tail-calls/signature-mismatch.rs index 3a01cc1ca2fd2..a32ac9d8bfee3 100644 --- a/tests/ui/explicit-tail-calls/signature-mismatch.rs +++ b/tests/ui/explicit-tail-calls/signature-mismatch.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] #![feature(c_variadic)] diff --git a/tests/ui/explicit-tail-calls/unsafeck.rs b/tests/ui/explicit-tail-calls/unsafeck.rs index 872a70ca3a0a7..f8a8140cedc33 100644 --- a/tests/ui/explicit-tail-calls/unsafeck.rs +++ b/tests/ui/explicit-tail-calls/unsafeck.rs @@ -1,4 +1,4 @@ -#![allow(incomplete_features)] +#![expect(incomplete_features)] #![feature(explicit_tail_calls)] const fn f() { From d93ea6bc79a06bcf6c8cbbd3af24f8724acd5009 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 29 Nov 2024 20:25:57 +0100 Subject: [PATCH 3/6] simplify things by using `tcx.fn_trait_kind_from_def_id` --- .../rustc_mir_build/src/check_tail_calls.rs | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 911a6cb7de606..f7264341c0a8b 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -91,22 +91,16 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { // Closures in thir look something akin to // `for<'a> extern "rust-call" fn(&'a [closure@...], ()) -> <[closure@...] as FnOnce<()>>::Output {<[closure@...] as Fn<()>>::call}` // So we have to check for them in this weird way... - if let &ty::FnDef(did, substs) = ty.kind() { + if let &ty::FnDef(did, args) = ty.kind() { let parent = self.tcx.parent(did); - let fn_ = self.tcx.require_lang_item(LangItem::Fn, Some(expr.span)); - let fn_once = self.tcx.require_lang_item(LangItem::FnOnce, Some(expr.span)); - let fn_mut = self.tcx.require_lang_item(LangItem::FnMut, Some(expr.span)); - if [fn_, fn_once, fn_mut].contains(&parent) { - if substs.first().and_then(|arg| arg.as_type()).is_some_and(|t| t.is_closure()) { - self.report_calling_closure( - &self.thir[fun], - substs[1].as_type().unwrap(), - expr, - ); - - // Tail calling is likely to cause unrelated errors (ABI, argument mismatches) - return; - } + if self.tcx.fn_trait_kind_from_def_id(parent).is_some() + && args.first().and_then(|arg| arg.as_type()).is_some_and(Ty::is_closure) + { + self.report_calling_closure(&self.thir[fun], args[1].as_type().unwrap(), expr); + + // Tail calling is likely to cause unrelated errors (ABI, argument mismatches), + // skip them, producing an error about calling a closure is enough. + return; }; } From ef5808a03596385b32bdb74de55ac10c6e74f0ba Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 29 Nov 2024 20:26:30 +0100 Subject: [PATCH 4/6] add a fixme for tailcalls with opaque types --- compiler/rustc_mir_build/src/check_tail_calls.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index f7264341c0a8b..e6a25634e29d3 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -117,6 +117,14 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { self.report_arguments_mismatch(expr.span, caller_sig, callee_sig); } + // FIXME(explicit_tail_calls): this currenly fails for cases where opaques are used. + // e.g. + // ``` + // fn a() -> impl Sized { become b() } // ICE + // fn b() -> u8 { 0 } + // ``` + // we should think what is the expected behavior here. + // (we should probably just accept this by revealing opaques?) if caller_sig.output() != callee_sig.output() { span_bug!(expr.span, "hir typeck should have checked the return type already"); } From c6454dd582c66a53bbf91947098af59689f61c1e Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 29 Nov 2024 20:27:24 +0100 Subject: [PATCH 5/6] don't polymorphize without a reason to --- compiler/rustc_mir_build/src/check_tail_calls.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index e6a25634e29d3..6eade2049702e 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -153,8 +153,7 @@ impl<'tcx> TailCallCkVisitor<'_, 'tcx> { fn needs_location(&self, ty: Ty<'tcx>) -> bool { if let &ty::FnDef(did, substs) = ty.kind() { let instance = - ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP) - .polymorphize(self.tcx); + ty::Instance::expect_resolve(self.tcx, self.typing_env, did, substs, DUMMY_SP); instance.def.requires_caller_location(self.tcx) } else { From 144d6cc65ba5d3ebdf1e20cc1a27bd3964402921 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Fri, 29 Nov 2024 20:28:02 +0100 Subject: [PATCH 6/6] simplify things using `tcx.as_lang_item` --- .../rustc_mir_build/src/check_tail_calls.rs | 52 +++++++++---------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_mir_build/src/check_tail_calls.rs b/compiler/rustc_mir_build/src/check_tail_calls.rs index 6eade2049702e..b1f46d37d5062 100644 --- a/compiler/rustc_mir_build/src/check_tail_calls.rs +++ b/compiler/rustc_mir_build/src/check_tail_calls.rs @@ -354,33 +354,31 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for TailCallCkVisitor<'a, 'tcx> { } fn op_trait_as_method_name(tcx: TyCtxt<'_>, trait_did: DefId) -> Option<&'static str> { - let trait_did = Some(trait_did); - let items = tcx.lang_items(); - let m = match () { - _ if trait_did == items.get(LangItem::Add) => "add", - _ if trait_did == items.get(LangItem::Sub) => "sub", - _ if trait_did == items.get(LangItem::Mul) => "mul", - _ if trait_did == items.get(LangItem::Div) => "div", - _ if trait_did == items.get(LangItem::Rem) => "rem", - _ if trait_did == items.get(LangItem::Neg) => "neg", - _ if trait_did == items.get(LangItem::Not) => "not", - _ if trait_did == items.get(LangItem::BitXor) => "bitxor", - _ if trait_did == items.get(LangItem::BitAnd) => "bitand", - _ if trait_did == items.get(LangItem::BitOr) => "bitor", - _ if trait_did == items.get(LangItem::Shl) => "shl", - _ if trait_did == items.get(LangItem::Shr) => "shr", - _ if trait_did == items.get(LangItem::AddAssign) => "add_assign", - _ if trait_did == items.get(LangItem::SubAssign) => "sub_assign", - _ if trait_did == items.get(LangItem::MulAssign) => "mul_assign", - _ if trait_did == items.get(LangItem::DivAssign) => "div_assign", - _ if trait_did == items.get(LangItem::RemAssign) => "rem_assign", - _ if trait_did == items.get(LangItem::BitXorAssign) => "bitxor_assign", - _ if trait_did == items.get(LangItem::BitAndAssign) => "bitand_assign", - _ if trait_did == items.get(LangItem::BitOrAssign) => "bitor_assign", - _ if trait_did == items.get(LangItem::ShlAssign) => "shl_assign", - _ if trait_did == items.get(LangItem::ShrAssign) => "shr_assign", - _ if trait_did == items.get(LangItem::Index) => "index", - _ if trait_did == items.get(LangItem::IndexMut) => "index_mut", + let m = match tcx.as_lang_item(trait_did)? { + LangItem::Add => "add", + LangItem::Sub => "sub", + LangItem::Mul => "mul", + LangItem::Div => "div", + LangItem::Rem => "rem", + LangItem::Neg => "neg", + LangItem::Not => "not", + LangItem::BitXor => "bitxor", + LangItem::BitAnd => "bitand", + LangItem::BitOr => "bitor", + LangItem::Shl => "shl", + LangItem::Shr => "shr", + LangItem::AddAssign => "add_assign", + LangItem::SubAssign => "sub_assign", + LangItem::MulAssign => "mul_assign", + LangItem::DivAssign => "div_assign", + LangItem::RemAssign => "rem_assign", + LangItem::BitXorAssign => "bitxor_assign", + LangItem::BitAndAssign => "bitand_assign", + LangItem::BitOrAssign => "bitor_assign", + LangItem::ShlAssign => "shl_assign", + LangItem::ShrAssign => "shr_assign", + LangItem::Index => "index", + LangItem::IndexMut => "index_mut", _ => return None, };