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 rust-lang/rust#144916
This commit is contained in:
commit
65479f7353
3 changed files with 84 additions and 60 deletions
|
|
@ -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,9 +849,7 @@ 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);
|
||||
}
|
||||
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
|
||||
|
|
@ -1874,65 +1876,61 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
|
|||
term: &Terminator<'tcx>,
|
||||
sig: &ty::FnSig<'tcx>,
|
||||
destination: Place<'tcx>,
|
||||
target: Option<BasicBlock>,
|
||||
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
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
} 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::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs
Normal file
16
tests/ui/explicit-tail-calls/ret-ty-borrowck-constraints.rs
Normal file
|
|
@ -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 { t }
|
||||
|
||||
fn main() {
|
||||
let x = String::from("hello, world");
|
||||
let s = link(&x);
|
||||
drop(x);
|
||||
println!("{s}");
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue