Skip to content
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

Consider alias bounds when computing liveness in NLL (but this time sound hopefully) #116733

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 23 additions & 20 deletions compiler/rustc_borrowck/src/type_check/liveness/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 3,7 @@ use rustc_data_structures::graph::WithSuccessors;
use rustc_index::bit_set::{HybridBitSet, SparseBitMatrix};
use rustc_index::interval::IntervalSet;
use rustc_infer::infer::canonical::QueryRegionConstraints;
use rustc_infer::infer::outlives::for_liveness;
use rustc_middle::mir::{BasicBlock, Body, ConstraintCategory, Local, Location};
use rustc_middle::traits::query::DropckOutlivesResult;
use rustc_middle::ty::{RegionVid, Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
Expand Down Expand Up @@ -601,34 602,36 @@ impl<'tcx> LivenessContext<'_, '_, '_, 'tcx> {
values::location_set_str(elements, live_at.iter()),
);

let tcx = typeck.tcx();
let borrowck_context = &mut typeck.borrowck_context;

// When using `-Zpolonius=next`, we want to record the loans that flow into this value's
// regions as being live at the given `live_at` points: this will be used to compute the
// location where a loan goes out of scope.
let num_loans = borrowck_context.borrow_set.len();
let mut value_loans = HybridBitSet::new_empty(num_loans);

tcx.for_each_free_region(&value, |live_region| {
let live_region_vid = borrowck_context.universal_regions.to_region_vid(live_region);

borrowck_context
.constraints
.liveness_constraints
.add_elements(live_region_vid, live_at);

// There can only be inflowing loans for this region when we are using
// `-Zpolonius=next`.
if let Some(inflowing) = inflowing_loans.row(live_region_vid) {
value_loans.union(inflowing);
}
let num_loans = typeck.borrowck_context.borrow_set.len();
let value_loans = &mut HybridBitSet::new_empty(num_loans);

value.visit_with(&mut for_liveness::FreeRegionsVisitor {
tcx: typeck.tcx(),
param_env: typeck.param_env,
op: |r| {
let live_region_vid = typeck.borrowck_context.universal_regions.to_region_vid(r);

typeck
.borrowck_context
.constraints
.liveness_constraints
.add_elements(live_region_vid, live_at);

// There can only be inflowing loans for this region when we are using
// `-Zpolonius=next`.
if let Some(inflowing) = inflowing_loans.row(live_region_vid) {
value_loans.union(inflowing);
}
},
});

// Record the loans reaching the value.
if !value_loans.is_empty() {
for point in live_at.iter() {
borrowck_context.live_loans.union_row(point, &value_loans);
typeck.borrowck_context.live_loans.union_row(point, value_loans);
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_infer/src/infer/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 380,8 @@ impl<'tcx> InferCtxt<'tcx> {
.collect(),
);

// FIXME(#42940): This should use the `FreeRegionsVisitor`, but that's
// not currently sound until we have existential regions.
concrete_ty.visit_with(&mut ConstrainOpaqueTypeRegionVisitor {
tcx: self.tcx,
op: |r| self.member_constraint(opaque_type_key, span, concrete_ty, r, &choice_regions),
Expand Down
121 changes: 121 additions & 0 deletions compiler/rustc_infer/src/infer/outlives/for_liveness.rs
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>)> {
compiler-errors marked this conversation as resolved.
Show resolved Hide resolved
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.
Copy link
Member

Choose a reason for hiding this comment

The 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(())
}
}
1 change: 1 addition & 0 deletions compiler/rustc_infer/src/infer/outlives/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 9,7 @@ use rustc_middle::ty;

pub mod components;
pub mod env;
pub mod for_liveness;
pub mod obligations;
pub mod test_type_match;
pub mod verify;
Expand Down
29 changes: 29 additions & 0 deletions tests/ui/borrowck/alias-liveness/gat-static.rs
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() {}
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() {}
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`.
16 changes: 16 additions & 0 deletions tests/ui/borrowck/alias-liveness/higher-ranked.rs
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() {}
17 changes: 17 additions & 0 deletions tests/ui/borrowck/alias-liveness/opaque-capture.rs
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() {}
14 changes: 14 additions & 0 deletions tests/ui/borrowck/alias-liveness/opaque-type-param.rs
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() {}
13 changes: 13 additions & 0 deletions tests/ui/borrowck/alias-liveness/opaque-type-param.stderr
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`.
22 changes: 22 additions & 0 deletions tests/ui/borrowck/alias-liveness/rpit-static.rs
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() {}
18 changes: 18 additions & 0 deletions tests/ui/borrowck/alias-liveness/rpitit-static.rs
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() {}
23 changes: 23 additions & 0 deletions tests/ui/borrowck/alias-liveness/rtn-static.rs
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() {}
Loading
Loading