-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Consider alias bounds when computing liveness in NLL (but this time sound hopefully) #116733
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bors
merged 5 commits into
rust-lang:master
from
compiler-errors:alias-liveness-but-this-time-sound
Oct 29, 2023
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
184d5ef
Consider alias bounds when considering lliveness for alias types in NLL
compiler-errors e425d85
Consider param-env candidates, too
compiler-errors 371d8a8
Consider static specially
compiler-errors 1005970
Use a visitor that could be reused by opaque type capture check later
compiler-errors f0e385d
Flesh out tests more
compiler-errors File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
121 changes: 121 additions & 0 deletions
121
compiler/rustc_infer/src/infer/outlives/for_liveness.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; | ||
|
||
use std::ops::ControlFlow; | ||
|
||
use crate::infer::outlives::test_type_match; | ||
use crate::infer::region_constraints::VerifyIfEq; | ||
|
||
/// Visits free regions in the type that are relevant for liveness computation. | ||
/// These regions are passed to `OP`. | ||
/// | ||
/// Specifically, we visit all of the regions of types recursively, except if | ||
/// the type is an alias, we look at the outlives bounds in the param-env | ||
/// and alias's item bounds. If there is a unique outlives bound, then visit | ||
/// that instead. If there is not a unique but there is a `'static` outlives | ||
/// bound, then don't visit anything. Otherwise, walk through the opaque's | ||
/// regions structurally. | ||
pub struct FreeRegionsVisitor<'tcx, OP: FnMut(ty::Region<'tcx>)> { | ||
pub tcx: TyCtxt<'tcx>, | ||
pub param_env: ty::ParamEnv<'tcx>, | ||
pub op: OP, | ||
} | ||
|
||
impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for FreeRegionsVisitor<'tcx, OP> | ||
where | ||
OP: FnMut(ty::Region<'tcx>), | ||
{ | ||
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>( | ||
&mut self, | ||
t: &ty::Binder<'tcx, T>, | ||
) -> ControlFlow<Self::BreakTy> { | ||
t.super_visit_with(self); | ||
ControlFlow::Continue(()) | ||
} | ||
|
||
fn visit_region(&mut self, r: ty::Region<'tcx>) -> ControlFlow<Self::BreakTy> { | ||
match *r { | ||
// ignore bound regions, keep visiting | ||
ty::ReLateBound(_, _) => ControlFlow::Continue(()), | ||
_ => { | ||
(self.op)(r); | ||
ControlFlow::Continue(()) | ||
} | ||
} | ||
} | ||
|
||
fn visit_ty(&mut self, ty: Ty<'tcx>) -> ControlFlow<Self::BreakTy> { | ||
// We're only interested in types involving regions | ||
if !ty.flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS) { | ||
return ControlFlow::Continue(()); | ||
} | ||
|
||
match ty.kind() { | ||
// We can prove that an alias is live two ways: | ||
// 1. All the components are live. | ||
// | ||
// 2. There is a known outlives bound or where-clause, and that | ||
// region is live. | ||
// | ||
// We search through the item bounds and where clauses for | ||
// either `'static` or a unique outlives region, and if one is | ||
// found, we just need to prove that that region is still live. | ||
// If one is not found, then we continue to walk through the alias. | ||
ty::Alias(kind, ty::AliasTy { def_id, args, .. }) => { | ||
let tcx = self.tcx; | ||
let param_env = self.param_env; | ||
let outlives_bounds: Vec<_> = tcx | ||
.item_bounds(def_id) | ||
.iter_instantiated(tcx, args) | ||
.chain(param_env.caller_bounds()) | ||
.filter_map(|clause| { | ||
let outlives = clause.as_type_outlives_clause()?; | ||
if let Some(outlives) = outlives.no_bound_vars() | ||
&& outlives.0 == ty | ||
{ | ||
Some(outlives.1) | ||
} else { | ||
test_type_match::extract_verify_if_eq( | ||
tcx, | ||
param_env, | ||
&outlives.map_bound(|ty::OutlivesPredicate(ty, bound)| { | ||
VerifyIfEq { ty, bound } | ||
}), | ||
ty, | ||
) | ||
} | ||
}) | ||
.collect(); | ||
// If we find `'static`, then we know the alias doesn't capture *any* regions. | ||
// Otherwise, all of the outlives regions should be equal -- if they're not, | ||
// we don't really know how to proceed, so we continue recursing through the | ||
// alias. | ||
if outlives_bounds.contains(&tcx.lifetimes.re_static) { | ||
// no | ||
} else if let Some(r) = outlives_bounds.first() | ||
&& outlives_bounds[1..].iter().all(|other_r| other_r == r) | ||
{ | ||
assert!(r.type_flags().intersects(ty::TypeFlags::HAS_FREE_REGIONS)); | ||
r.visit_with(self)?; | ||
} else { | ||
// Skip lifetime parameters that are not captures. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should fix #116794. you can add a regression test to close the issue. |
||
let variances = match kind { | ||
ty::Opaque => Some(self.tcx.variances_of(*def_id)), | ||
_ => None, | ||
}; | ||
|
||
for (idx, s) in args.iter().enumerate() { | ||
if variances.map(|variances| variances[idx]) != Some(ty::Variance::Bivariant) { | ||
s.visit_with(self)?; | ||
} | ||
} | ||
} | ||
} | ||
|
||
_ => { | ||
ty.super_visit_with(self)?; | ||
} | ||
} | ||
|
||
ControlFlow::Continue(()) | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// check-pass | ||
|
||
trait Foo { | ||
type Assoc<'a> | ||
where | ||
Self: 'a; | ||
|
||
fn assoc(&mut self) -> Self::Assoc<'_>; | ||
} | ||
|
||
fn overlapping_mut<T>(mut t: T) | ||
where | ||
T: Foo, | ||
for<'a> T::Assoc<'a>: 'static, | ||
{ | ||
let a = t.assoc(); | ||
let b = t.assoc(); | ||
} | ||
|
||
fn live_past_borrow<T>(mut t: T) | ||
where | ||
T: Foo, | ||
for<'a> T::Assoc<'a>: 'static { | ||
let x = t.assoc(); | ||
drop(t); | ||
drop(x); | ||
} | ||
|
||
fn main() {} |
16 changes: 16 additions & 0 deletions
16
tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// known-bug: #42940 | ||
|
||
trait Captures<'a> {} | ||
impl<T> Captures<'_> for T {} | ||
|
||
trait Outlives<'a>: 'a {} | ||
impl<'a, T: 'a> Outlives<'a> for T {} | ||
|
||
// Test that we treat `for<'a> Opaque: 'a` as `Opaque: 'static` | ||
fn test<'o>(v: &'o Vec<i32>) -> impl Captures<'o> + for<'a> Outlives<'a> {} | ||
|
||
fn statik() -> impl Sized { | ||
test(&vec![]) | ||
} | ||
|
||
fn main() {} |
16 changes: 16 additions & 0 deletions
16
tests/ui/borrowck/alias-liveness/higher-ranked-outlives-for-capture.stderr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
error[E0716]: temporary value dropped while borrowed | ||
--> $DIR/higher-ranked-outlives-for-capture.rs:13:11 | ||
| | ||
LL | test(&vec![]) | ||
| ------^^^^^^- | ||
| | | | ||
| | creates a temporary value which is freed while still in use | ||
| argument requires that borrow lasts for `'static` | ||
LL | } | ||
| - temporary value is freed at the end of this statement | ||
| | ||
= note: this error originates in the macro `vec` (in Nightly builds, run with -Z macro-backtrace for more info) | ||
|
||
error: aborting due to previous error | ||
|
||
For more information about this error, try `rustc --explain E0716`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// check-pass | ||
|
||
trait Captures<'a> {} | ||
impl<T> Captures<'_> for T {} | ||
|
||
trait Outlives<'a>: 'a {} | ||
impl<'a, T: 'a> Outlives<'a> for T {} | ||
|
||
// Test that we treat `for<'a> Opaque: 'a` as `Opaque: 'static` | ||
fn test<'o>(v: &'o Vec<i32>) -> impl Captures<'o> + for<'a> Outlives<'a> {} | ||
|
||
fn opaque_doesnt_use_temporary() { | ||
let a = test(&vec![]); | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// check-pass | ||
|
||
// Check that opaques capturing early and late-bound vars correctly mark | ||
// regions required to be live using the item bounds. | ||
|
||
trait Captures<'a> {} | ||
impl<T> Captures<'_> for T {} | ||
|
||
fn captures_temp_late<'a>(x: &'a Vec<i32>) -> impl Sized + Captures<'a> + 'static {} | ||
fn captures_temp_early<'a: 'a>(x: &'a Vec<i32>) -> impl Sized + Captures<'a> + 'static {} | ||
|
||
fn test() { | ||
let x = captures_temp_early(&vec![]); | ||
let y = captures_temp_late(&vec![]); | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// known-bug: #42940 | ||
|
||
trait Trait {} | ||
impl Trait for () {} | ||
|
||
fn foo<'a>(s: &'a str) -> impl Trait + 'static { | ||
bar(s) | ||
} | ||
|
||
fn bar<P: AsRef<str>>(s: P) -> impl Trait + 'static { | ||
() | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
error[E0700]: hidden type for `impl Trait + 'static` captures lifetime that does not appear in bounds | ||
--> $DIR/opaque-type-param.rs:7:5 | ||
| | ||
LL | fn foo<'a>(s: &'a str) -> impl Trait + 'static { | ||
| -- -------------------- opaque type defined here | ||
| | | ||
| hidden type `impl Trait + 'static` captures the lifetime `'a` as defined here | ||
LL | bar(s) | ||
| ^^^^^^ | ||
|
||
error: aborting due to previous error | ||
|
||
For more information about this error, try `rustc --explain E0700`. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// check-pass | ||
|
||
trait Captures<'a> {} | ||
impl<T> Captures<'_> for T {} | ||
|
||
fn foo(x: &mut i32) -> impl Sized + Captures<'_> + 'static {} | ||
|
||
fn overlapping_mut() { | ||
let i = &mut 1; | ||
let x = foo(i); | ||
let y = foo(i); | ||
} | ||
|
||
fn live_past_borrow() { | ||
let y; | ||
{ | ||
let x = &mut 1; | ||
y = foo(x); | ||
} | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// check-pass | ||
|
||
trait Foo { | ||
fn rpitit(&mut self) -> impl Sized + 'static; | ||
} | ||
|
||
fn live_past_borrow<T: Foo>(mut t: T) { | ||
let x = t.rpitit(); | ||
drop(t); | ||
drop(x); | ||
} | ||
|
||
fn overlapping_mut<T: Foo>(mut t: T) { | ||
let a = t.rpitit(); | ||
let b = t.rpitit(); | ||
} | ||
|
||
fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// check-pass | ||
|
||
#![feature(return_type_notation)] | ||
//~^ WARN the feature `return_type_notation` is incomplete | ||
|
||
trait Foo { | ||
fn borrow(&mut self) -> impl Sized + '_; | ||
} | ||
|
||
fn live_past_borrow<T: Foo<borrow(): 'static>>(mut t: T) { | ||
let x = t.borrow(); | ||
drop(t); | ||
drop(x); | ||
} | ||
|
||
// Test that the `'_` item bound in `borrow` does not cause us to | ||
// overlook the `'static` RTN bound. | ||
fn overlapping_mut<T: Foo<borrow(): 'static>>(mut t: T) { | ||
let x = t.borrow(); | ||
let x = t.borrow(); | ||
} | ||
|
||
fn main() {} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.