From c7ea022166d2b6d55c3f69bbf69a31b0d7b053e2 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 4 Aug 2025 16:24:41 +0000 Subject: [PATCH 1/2] Enforce tail call type is related to body return type in borrowck --- compiler/rustc_borrowck/src/type_check/mod.rs | 10 +++++++--- .../ret-ty-borrowck-constraints.rs | 16 ++++++++++++++++ .../ret-ty-borrowck-constraints.stderr | 10 ++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs create mode 100644 tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.stderr diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index f5fedbf95c1c1..91973f33bd320 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -845,9 +845,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } - if let TerminatorKind::Call { destination, target, .. } = term.kind { - self.check_call_dest(term, &sig, destination, target, term_location); - } + let (destination, target) = + if let TerminatorKind::Call { destination, target, .. } = term.kind { + (destination, target) + } else { + (RETURN_PLACE.into(), Some(BasicBlock::ZERO)) + }; + self.check_call_dest(term, &sig, destination, target, term_location); // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We diff --git a/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs b/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs new file mode 100644 index 0000000000000..111ae849c0f6a --- /dev/null +++ b/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs @@ -0,0 +1,16 @@ +#![feature(explicit_tail_calls)] +#![expect(incomplete_features)] + +fn link(x: &str) -> &'static str { + become passthrough(x); + //~^ ERROR lifetime may not live long enough +} + +fn passthrough(t: T) -> T { t } + +fn main() { + let x = String::from("hello, world"); + let s = link(&x); + drop(x); + println!("{s}"); +} diff --git a/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.stderr b/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.stderr new file mode 100644 index 0000000000000..26a8e1f0122ad --- /dev/null +++ b/tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.stderr @@ -0,0 +1,10 @@ +error: lifetime may not live long enough + --> $DIR/ret-ty-borrowck-constraints.rs:5:5 + | +LL | fn link(x: &str) -> &'static str { + | - let's call the lifetime of this reference `'1` +LL | become passthrough(x); + | ^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static` + +error: aborting due to 1 previous error + From a573fd958eee4b4ec76bdaa3dcacf1ab3c670d45 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 4 Aug 2025 16:24:41 +0000 Subject: [PATCH 2/2] Don't actually pass BB to check_call_dest --- compiler/rustc_borrowck/src/type_check/mod.rs | 114 +++++++++--------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index 91973f33bd320..148d0de3bab28 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -769,9 +769,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } TerminatorKind::Call { func, args, .. } | TerminatorKind::TailCall { func, args, .. } => { - let call_source = match term.kind { - TerminatorKind::Call { call_source, .. } => call_source, - TerminatorKind::TailCall { .. } => CallSource::Normal, + let (call_source, destination, is_diverging) = match term.kind { + TerminatorKind::Call { call_source, destination, target, .. } => { + (call_source, destination, target.is_none()) + } + TerminatorKind::TailCall { .. } => { + (CallSource::Normal, RETURN_PLACE.into(), false) + } _ => unreachable!(), }; @@ -845,13 +849,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { ); } - let (destination, target) = - if let TerminatorKind::Call { destination, target, .. } = term.kind { - (destination, target) - } else { - (RETURN_PLACE.into(), Some(BasicBlock::ZERO)) - }; - self.check_call_dest(term, &sig, destination, target, term_location); + self.check_call_dest(term, &sig, destination, is_diverging, term_location); // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We @@ -1878,65 +1876,61 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { term: &Terminator<'tcx>, sig: &ty::FnSig<'tcx>, destination: Place<'tcx>, - target: Option, + is_diverging: bool, term_location: Location, ) { let tcx = self.tcx(); - match target { - Some(_) => { - let dest_ty = destination.ty(self.body, tcx).ty; - let dest_ty = self.normalize(dest_ty, term_location); - let category = match destination.as_local() { - Some(RETURN_PLACE) => { - if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) = - self.universal_regions.defining_ty - { - if tcx.is_static(def_id) { - ConstraintCategory::UseAsStatic - } else { - ConstraintCategory::UseAsConst - } + if is_diverging { + // The signature in this call can reference region variables, + // so erase them before calling a query. + let output_ty = self.tcx().erase_regions(sig.output()); + if !output_ty + .is_privately_uninhabited(self.tcx(), self.infcx.typing_env(self.infcx.param_env)) + { + span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig); + } + } else { + let dest_ty = destination.ty(self.body, tcx).ty; + let dest_ty = self.normalize(dest_ty, term_location); + let category = match destination.as_local() { + Some(RETURN_PLACE) => { + if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) = + self.universal_regions.defining_ty + { + if tcx.is_static(def_id) { + ConstraintCategory::UseAsStatic } else { - ConstraintCategory::Return(ReturnConstraint::Normal) + ConstraintCategory::UseAsConst } + } else { + ConstraintCategory::Return(ReturnConstraint::Normal) } - Some(l) if !self.body.local_decls[l].is_user_variable() => { - ConstraintCategory::Boring - } - // The return type of a call is interesting for diagnostics. - _ => ConstraintCategory::Assignment, - }; - - let locations = term_location.to_locations(); - - if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) { - span_mirbug!( - self, - term, - "call dest mismatch ({:?} <- {:?}): {:?}", - dest_ty, - sig.output(), - terr - ); } - - // When `unsized_fn_params` is not enabled, - // this check is done at `check_local`. - if self.unsized_feature_enabled() { - let span = term.source_info.span; - self.ensure_place_sized(dest_ty, span); + Some(l) if !self.body.local_decls[l].is_user_variable() => { + ConstraintCategory::Boring } + // The return type of a call is interesting for diagnostics. + _ => ConstraintCategory::Assignment, + }; + + let locations = term_location.to_locations(); + + if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) { + span_mirbug!( + self, + term, + "call dest mismatch ({:?} <- {:?}): {:?}", + dest_ty, + sig.output(), + terr + ); } - None => { - // The signature in this call can reference region variables, - // so erase them before calling a query. - let output_ty = self.tcx().erase_regions(sig.output()); - if !output_ty.is_privately_uninhabited( - self.tcx(), - self.infcx.typing_env(self.infcx.param_env), - ) { - span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig); - } + + // When `unsized_fn_params` is not enabled, + // this check is done at `check_local`. + if self.unsized_feature_enabled() { + let span = term.source_info.span; + self.ensure_place_sized(dest_ty, span); } } }