Skip to content

Commit 097664f

Browse files
authored
Rollup merge of #144917 - compiler-errors:tail-call-linked-lifetimes, r=lcnr
Enforce tail call type is related to body return type in borrowck Like all call terminators, tail call terminators instantiate the binder of the callee signature with region variables and equate the arg operand types with that signature's args to ensure that the call is valid. However, unlike normal call terminators, we were forgetting to also relate the return type of the call terminator to anything. In the case of tail call terminators, the correct thing is to relate it to the return type of the caller function (or in other words, the return local `_0`). This meant that if the caller's return type had some lifetime constraint, then that constraint wouldn't flow through the signature and affect the args. This is what's happening in the example test I committed: ```rust fn link(x: &str) -> &'static str { become passthrough(x); } fn passthrough<T>(t: T) -> T { t } fn main() { let x = String::from("hello, world"); let s = link(&x); drop(x); println!("{s}"); } ``` Specifically, the type `x` is `'?0 str`, where `'?0` is some *universal* arg. The type of `passthrough` is `fn(&'?1 str) -> &'?1 str`. Equating the args sets `'?0 = '?1`. However, we need to also equate the return type `&'?1 str` to `&'static str` so that we eventually require that `'?0 = 'static`, which is a borrowck error! ----- Look at the first commit for the functional change, and the second commit is just a refactor because we don't need to pass `Option<BasicBlock>` to `check_call_dest`, but just whether or not the terminator is expected to be diverging (i.e. if the return type is `!`). Fixes #144916
2 parents ed10104 + a573fd9 commit 097664f

File tree

3 files changed

+80
-56
lines changed

3 files changed

+80
-56
lines changed

compiler/rustc_borrowck/src/type_check/mod.rs

Lines changed: 54 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -769,9 +769,13 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
769769
}
770770
TerminatorKind::Call { func, args, .. }
771771
| TerminatorKind::TailCall { func, args, .. } => {
772-
let call_source = match term.kind {
773-
TerminatorKind::Call { call_source, .. } => call_source,
774-
TerminatorKind::TailCall { .. } => CallSource::Normal,
772+
let (call_source, destination, is_diverging) = match term.kind {
773+
TerminatorKind::Call { call_source, destination, target, .. } => {
774+
(call_source, destination, target.is_none())
775+
}
776+
TerminatorKind::TailCall { .. } => {
777+
(CallSource::Normal, RETURN_PLACE.into(), false)
778+
}
775779
_ => unreachable!(),
776780
};
777781

@@ -845,9 +849,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
845849
);
846850
}
847851

848-
if let TerminatorKind::Call { destination, target, .. } = term.kind {
849-
self.check_call_dest(term, &sig, destination, target, term_location);
850-
}
852+
self.check_call_dest(term, &sig, destination, is_diverging, term_location);
851853

852854
// The ordinary liveness rules will ensure that all
853855
// regions in the type of the callee are live here. We
@@ -1874,65 +1876,61 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
18741876
term: &Terminator<'tcx>,
18751877
sig: &ty::FnSig<'tcx>,
18761878
destination: Place<'tcx>,
1877-
target: Option<BasicBlock>,
1879+
is_diverging: bool,
18781880
term_location: Location,
18791881
) {
18801882
let tcx = self.tcx();
1881-
match target {
1882-
Some(_) => {
1883-
let dest_ty = destination.ty(self.body, tcx).ty;
1884-
let dest_ty = self.normalize(dest_ty, term_location);
1885-
let category = match destination.as_local() {
1886-
Some(RETURN_PLACE) => {
1887-
if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) =
1888-
self.universal_regions.defining_ty
1889-
{
1890-
if tcx.is_static(def_id) {
1891-
ConstraintCategory::UseAsStatic
1892-
} else {
1893-
ConstraintCategory::UseAsConst
1894-
}
1883+
if is_diverging {
1884+
// The signature in this call can reference region variables,
1885+
// so erase them before calling a query.
1886+
let output_ty = self.tcx().erase_regions(sig.output());
1887+
if !output_ty
1888+
.is_privately_uninhabited(self.tcx(), self.infcx.typing_env(self.infcx.param_env))
1889+
{
1890+
span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
1891+
}
1892+
} else {
1893+
let dest_ty = destination.ty(self.body, tcx).ty;
1894+
let dest_ty = self.normalize(dest_ty, term_location);
1895+
let category = match destination.as_local() {
1896+
Some(RETURN_PLACE) => {
1897+
if let DefiningTy::Const(def_id, _) | DefiningTy::InlineConst(def_id, _) =
1898+
self.universal_regions.defining_ty
1899+
{
1900+
if tcx.is_static(def_id) {
1901+
ConstraintCategory::UseAsStatic
18951902
} else {
1896-
ConstraintCategory::Return(ReturnConstraint::Normal)
1903+
ConstraintCategory::UseAsConst
18971904
}
1905+
} else {
1906+
ConstraintCategory::Return(ReturnConstraint::Normal)
18981907
}
1899-
Some(l) if !self.body.local_decls[l].is_user_variable() => {
1900-
ConstraintCategory::Boring
1901-
}
1902-
// The return type of a call is interesting for diagnostics.
1903-
_ => ConstraintCategory::Assignment,
1904-
};
1905-
1906-
let locations = term_location.to_locations();
1907-
1908-
if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) {
1909-
span_mirbug!(
1910-
self,
1911-
term,
1912-
"call dest mismatch ({:?} <- {:?}): {:?}",
1913-
dest_ty,
1914-
sig.output(),
1915-
terr
1916-
);
19171908
}
1918-
1919-
// When `unsized_fn_params` is not enabled,
1920-
// this check is done at `check_local`.
1921-
if self.unsized_feature_enabled() {
1922-
let span = term.source_info.span;
1923-
self.ensure_place_sized(dest_ty, span);
1909+
Some(l) if !self.body.local_decls[l].is_user_variable() => {
1910+
ConstraintCategory::Boring
19241911
}
1912+
// The return type of a call is interesting for diagnostics.
1913+
_ => ConstraintCategory::Assignment,
1914+
};
1915+
1916+
let locations = term_location.to_locations();
1917+
1918+
if let Err(terr) = self.sub_types(sig.output(), dest_ty, locations, category) {
1919+
span_mirbug!(
1920+
self,
1921+
term,
1922+
"call dest mismatch ({:?} <- {:?}): {:?}",
1923+
dest_ty,
1924+
sig.output(),
1925+
terr
1926+
);
19251927
}
1926-
None => {
1927-
// The signature in this call can reference region variables,
1928-
// so erase them before calling a query.
1929-
let output_ty = self.tcx().erase_regions(sig.output());
1930-
if !output_ty.is_privately_uninhabited(
1931-
self.tcx(),
1932-
self.infcx.typing_env(self.infcx.param_env),
1933-
) {
1934-
span_mirbug!(self, term, "call to converging function {:?} w/o dest", sig);
1935-
}
1928+
1929+
// When `unsized_fn_params` is not enabled,
1930+
// this check is done at `check_local`.
1931+
if self.unsized_feature_enabled() {
1932+
let span = term.source_info.span;
1933+
self.ensure_place_sized(dest_ty, span);
19361934
}
19371935
}
19381936
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#![feature(explicit_tail_calls)]
2+
#![expect(incomplete_features)]
3+
4+
fn link(x: &str) -> &'static str {
5+
become passthrough(x);
6+
//~^ ERROR lifetime may not live long enough
7+
}
8+
9+
fn passthrough<T>(t: T) -> T { t }
10+
11+
fn main() {
12+
let x = String::from("hello, world");
13+
let s = link(&x);
14+
drop(x);
15+
println!("{s}");
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: lifetime may not live long enough
2+
--> $DIR/ret-ty-borrowck-constraints.rs:5:5
3+
|
4+
LL | fn link(x: &str) -> &'static str {
5+
| - let's call the lifetime of this reference `'1`
6+
LL | become passthrough(x);
7+
| ^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'static`
8+
9+
error: aborting due to 1 previous error
10+

0 commit comments

Comments
 (0)