mirror of
https://github.com/rust-lang/rust.git
synced 2025-02-06 03:52:53 +00:00
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.
This commit is contained in:
parent
c1cfab230e
commit
cfb78419cd
@ -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
|
||||
|
@ -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)) => {
|
||||
|
387
compiler/rustc_mir_build/src/check_tail_calls.rs
Normal file
387
compiler/rustc_mir_build/src/check_tail_calls.rs
Normal file
@ -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)
|
||||
}
|
@ -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;
|
||||
|
13
tests/ui/explicit-tail-calls/become-macro.rs
Normal file
13
tests/ui/explicit-tail-calls/become-macro.rs
Normal file
@ -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() {}
|
42
tests/ui/explicit-tail-calls/become-operator.fixed
Normal file
42
tests/ui/explicit-tail-calls/become-operator.fixed
Normal file
@ -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<u32>) -> Wrapping<u32> {
|
||||
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>, _: u8) {
|
||||
become (*b).bitxor_assign(1); //~ error: `become` does not support operators
|
||||
}
|
||||
|
||||
|
||||
fn main() {}
|
42
tests/ui/explicit-tail-calls/become-operator.rs
Normal file
42
tests/ui/explicit-tail-calls/become-operator.rs
Normal file
@ -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<u32>) -> Wrapping<u32> {
|
||||
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>, _: u8) {
|
||||
become *b ^= 1; //~ error: `become` does not support operators
|
||||
}
|
||||
|
||||
|
||||
fn main() {}
|
75
tests/ui/explicit-tail-calls/become-operator.stderr
Normal file
75
tests/ui/explicit-tail-calls/become-operator.stderr
Normal file
@ -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
|
||||
|
18
tests/ui/explicit-tail-calls/become-uncallable.fixed
Normal file
18
tests/ui/explicit-tail-calls/become-uncallable.fixed
Normal file
@ -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() {}
|
18
tests/ui/explicit-tail-calls/become-uncallable.rs
Normal file
18
tests/ui/explicit-tail-calls/become-uncallable.rs
Normal file
@ -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() {}
|
44
tests/ui/explicit-tail-calls/become-uncallable.stderr
Normal file
44
tests/ui/explicit-tail-calls/become-uncallable.stderr
Normal file
@ -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
|
||||
|
31
tests/ui/explicit-tail-calls/closure.fixed
Normal file
31
tests/ui/explicit-tail-calls/closure.fixed
Normal file
@ -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((), ((), ()));
|
||||
}
|
31
tests/ui/explicit-tail-calls/closure.rs
Normal file
31
tests/ui/explicit-tail-calls/closure.rs
Normal file
@ -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((), ((), ()));
|
||||
}
|
46
tests/ui/explicit-tail-calls/closure.stderr
Normal file
46
tests/ui/explicit-tail-calls/closure.stderr
Normal file
@ -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
|
||||
|
8
tests/ui/explicit-tail-calls/in-closure.rs
Normal file
8
tests/ui/explicit-tail-calls/in-closure.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![allow(incomplete_features)]
|
||||
#![feature(explicit_tail_calls)]
|
||||
|
||||
fn main() {
|
||||
|| become f(); //~ error: `become` is not allowed in closures
|
||||
}
|
||||
|
||||
fn f() {}
|
8
tests/ui/explicit-tail-calls/in-closure.stderr
Normal file
8
tests/ui/explicit-tail-calls/in-closure.stderr
Normal file
@ -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
|
||||
|
33
tests/ui/explicit-tail-calls/signature-mismatch.rs
Normal file
33
tests/ui/explicit-tail-calls/signature-mismatch.rs
Normal file
@ -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() {}
|
40
tests/ui/explicit-tail-calls/signature-mismatch.stderr
Normal file
40
tests/ui/explicit-tail-calls/signature-mismatch.stderr
Normal file
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user