Skip to content

Commit

Permalink
Reason about nested free variables that appear in a function
Browse files Browse the repository at this point in the history
signature.  In a nutshell, the idea is to (1) report an error if, for
a region pointer `'a T`, the lifetime `'a` is longer than any
lifetimes that appear in `T` (in other words, if a borrowed pointer
outlives any portion of its contents) and then (2) use this to assume
that in a function like `fn(self: &'a &'b T)`, the relationship `'a <=
'b` holds. This is needed for rust-lang#5656.  Fixes rust-lang#5728.
  • Loading branch information
nikomatsakis committed Apr 10, 2013
1 parent 5606fc0 commit 3322595
Show file tree
Hide file tree
Showing 27 changed files with 1,029 additions and 349 deletions.
21 changes: 21 additions & 0 deletions src/libcore/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,19 @@ totalord_impl!(i64)
totalord_impl!(int)
totalord_impl!(uint)

pub fn cmp2<A:TotalOrd,B:TotalOrd>(
a1: &A, b1: &B,
a2: &A, b2: &B) -> Ordering
{
//! Compares (a1, b1) against (a2, b2), where the a values are more significant.

match a1.cmp(a2) {
Less => Less,
Greater => Greater,
Equal => b1.cmp(b2)
}
}

/**
* Trait for values that can be compared for a sort-order.
*
Expand Down Expand Up @@ -193,6 +206,14 @@ mod test {
assert_eq!(12.cmp(-5), Greater);
}

#[test]
fn test_cmp2() {
assert_eq!(cmp2(1, 2, 3, 4), Less);
assert_eq!(cmp2(3, 2, 3, 4), Less);
assert_eq!(cmp2(5, 2, 3, 4), Greater);
assert_eq!(cmp2(5, 5, 5, 4), Greater);
}

#[test]
fn test_int_totaleq() {
assert!(5.equals(&5));
Expand Down
6 changes: 5 additions & 1 deletion src/libcore/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,11 @@ pub fn slice_shift_char<'a>(s: &'a str) -> (char, &'a str) {

/// Prepend a char to a string
pub fn unshift_char(s: &mut ~str, ch: char) {
*s = from_char(ch) + *s;
// This could be more efficient.
let mut new_str = ~"";
new_str.push_char(ch);
new_str.push_str(*s);
*s = new_str;
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/libcore/tuple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,6 @@ impl<A:Ord> Ord for (A,) {
fn gt(&self, other: &(A,)) -> bool { other.lt(&(*self)) }
}


#[cfg(notest)]
impl<A:Eq,B:Eq> Eq for (A, B) {
#[inline(always)]
Expand Down
3 changes: 2 additions & 1 deletion src/librustc/metadata/tydecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,8 @@ fn parse_region(st: @mut PState) -> ty::Region {
assert!(next(st) == '|');
let br = parse_bound_region(st);
assert!(next(st) == ']');
ty::re_free(id, br)
ty::re_free(ty::FreeRegion {scope_id: id,
bound_region: br})
}
's' => {
let id = parse_int(st);
Expand Down
6 changes: 3 additions & 3 deletions src/librustc/metadata/tyencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,12 @@ fn enc_region(w: @io::Writer, cx: @ctxt, r: ty::Region) {
w.write_char('b');
enc_bound_region(w, cx, br);
}
ty::re_free(id, br) => {
ty::re_free(ref fr) => {
w.write_char('f');
w.write_char('[');
w.write_int(id);
w.write_int(fr.scope_id);
w.write_char('|');
enc_bound_region(w, cx, br);
enc_bound_region(w, cx, fr.bound_region);
w.write_char(']');
}
ty::re_scope(nid) => {
Expand Down
5 changes: 4 additions & 1 deletion src/librustc/middle/astencode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,9 +475,12 @@ impl tr for ty::Region {
fn tr(&self, xcx: @ExtendedDecodeContext) -> ty::Region {
match *self {
ty::re_bound(br) => ty::re_bound(br.tr(xcx)),
ty::re_free(id, br) => ty::re_free(xcx.tr_id(id), br.tr(xcx)),
ty::re_scope(id) => ty::re_scope(xcx.tr_id(id)),
ty::re_static | ty::re_infer(*) => *self,
ty::re_free(ref fr) => {
ty::re_free(ty::FreeRegion {scope_id: xcx.tr_id(fr.scope_id),
bound_region: fr.bound_region.tr(xcx)})
}
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/librustc/middle/borrowck/check_loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,9 @@ pub impl CheckLoanCtxt {
Some(e) => return Some(pc_cmt(*e))
}
match self.tcx().region_map.find(&scope_id) {
match self.tcx().region_maps.opt_encl_scope(scope_id) {
None => return default_purity,
Some(&next_scope_id) => scope_id = next_scope_id
Some(next_scope_id) => scope_id = next_scope_id
}
}
}
Expand All @@ -146,9 +146,9 @@ pub impl CheckLoanCtxt {
}
}
match self.tcx().region_map.find(&scope_id) {
match self.tcx().region_maps.opt_encl_scope(scope_id) {
None => return,
Some(&next_scope_id) => scope_id = next_scope_id,
Some(next_scope_id) => scope_id = next_scope_id,
}
}
}
Expand Down Expand Up @@ -270,7 +270,7 @@ pub impl CheckLoanCtxt {

debug!("new_loans has length %?", new_loans.len());

let par_scope_id = *self.tcx().region_map.get(&scope_id);
let par_scope_id = self.tcx().region_maps.encl_scope(scope_id);
for self.walk_loans(par_scope_id) |old_loan| {
debug!("old_loan=%?", self.bccx.loan_to_repr(old_loan));

Expand Down
7 changes: 5 additions & 2 deletions src/librustc/middle/borrowck/gather_loans.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ fn req_loans_in_expr(ex: @ast::expr,
// (if used like `a.b(...)`), the call where it's an argument
// (if used like `x(a.b)`), or the block (if used like `let x
// = a.b`).
let scope_r = ty::re_scope(*self.tcx().region_map.get(&ex.id));
let scope_r = self.tcx().region_maps.encl_region(ex.id);
let rcvr_cmt = self.bccx.cat_expr(rcvr);
self.guarantee_valid(rcvr_cmt, m_imm, scope_r);
visit::visit_expr(ex, self, vt);
Expand Down Expand Up @@ -524,7 +524,10 @@ pub impl GatherLoanCtxt {
// immutable structures, this is just the converse I suppose)

let scope_id = match scope_r {
ty::re_scope(scope_id) | ty::re_free(scope_id, _) => scope_id,
ty::re_scope(scope_id) |
ty::re_free(ty::FreeRegion {scope_id, _}) => {
scope_id
}
_ => {
self.bccx.tcx.sess.span_bug(
cmt.span,
Expand Down
4 changes: 2 additions & 2 deletions src/librustc/middle/borrowck/loan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ pub impl LoanContext {
}
cat_local(local_id) | cat_arg(local_id) | cat_self(local_id) => {
// FIXME(#4903)
let local_scope_id = *self.bccx.tcx.region_map.get(&local_id);
self.issue_loan(cmt, ty::re_scope(local_scope_id), loan_kind,
let local_region = self.bccx.tcx.region_maps.encl_region(local_id);
self.issue_loan(cmt, local_region, loan_kind,
owns_lent_data)
}
cat_stack_upvar(cmt) => {
Expand Down
3 changes: 1 addition & 2 deletions src/librustc/middle/borrowck/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,6 @@ Borrowck results in two maps.
use core::prelude::*;

use middle::mem_categorization::*;
use middle::region;
use middle::ty;
use middle::typeck;
use middle::moves;
Expand Down Expand Up @@ -458,7 +457,7 @@ pub fn root_map() -> root_map {

pub impl BorrowckCtxt {
fn is_subregion_of(&self, r_sub: ty::Region, r_sup: ty::Region) -> bool {
region::is_subregion_of(self.tcx.region_map, r_sub, r_sup)
self.tcx.region_maps.is_subregion_of(r_sub, r_sup)
}

fn cat_expr(&self, expr: @ast::expr) -> cmt {
Expand Down
18 changes: 9 additions & 9 deletions src/librustc/middle/borrowck/preserve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub impl<'self> PreserveCtxt<'self> {
// Maybe if we pass in the parent instead here,
// we can prevent the "scope not found" error
debug!("scope_region thing: %? ", cmt.id);
ty::re_scope(*self.tcx().region_map.get(&cmt.id))
self.tcx().region_maps.encl_region(cmt.id)
};

self.compare_scope(cmt, scope_region)
Expand All @@ -128,27 +128,27 @@ pub impl<'self> PreserveCtxt<'self> {
cmt.span,
~"preserve() called with local and !root_managed_data");
}
let local_scope_id = *self.tcx().region_map.get(&local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
let local_region = self.tcx().region_maps.encl_region(local_id);
self.compare_scope(cmt, local_region)
}
cat_binding(local_id) => {
// Bindings are these kind of weird implicit pointers (cc
// #2329). We require (in gather_loans) that they be
// rooted in an immutable location.
let local_scope_id = *self.tcx().region_map.get(&local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
let local_region = self.tcx().region_maps.encl_region(local_id);
self.compare_scope(cmt, local_region)
}
cat_arg(local_id) => {
// This can happen as not all args are lendable (e.g., &&
// modes). In that case, the caller guarantees stability
// for at least the scope of the fn. This is basically a
// deref of a region ptr.
let local_scope_id = *self.tcx().region_map.get(&local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
let local_region = self.tcx().region_maps.encl_region(local_id);
self.compare_scope(cmt, local_region)
}
cat_self(local_id) => {
let local_scope_id = *self.tcx().region_map.get(&local_id);
self.compare_scope(cmt, ty::re_scope(local_scope_id))
let local_region = self.tcx().region_maps.encl_region(local_id);
self.compare_scope(cmt, local_region)
}
cat_comp(cmt_base, comp_field(*)) |
cat_comp(cmt_base, comp_index(*)) |
Expand Down
7 changes: 5 additions & 2 deletions src/librustc/middle/check_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,11 @@ pub fn specialize(cx: @MatchCheckCtxt,
class_id);
}
_ => {
cx.tcx.sess.span_bug(pat_span,
~"struct pattern didn't resolve to a struct");
cx.tcx.sess.span_bug(
pat_span,
fmt!("struct pattern resolved to %s, \
not a struct",
ty_to_str(cx.tcx, left_ty)));
}
}
let args = vec::map(class_fields, |class_field| {
Expand Down
100 changes: 70 additions & 30 deletions src/librustc/middle/kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -478,13 +478,13 @@ pub fn check_durable(tcx: ty::ctxt, ty: ty::t, sp: span) -> bool {
}
}

/// This is rather subtle. When we are casting a value to a
/// instantiated trait like `a as trait<'r>`, regionck already ensures
/// that any borrowed pointers that appear in the type of `a` are
/// bounded by `&r`. However, it is possible that there are *type
/// parameters* in the type of `a`, and those *type parameters* may
/// have borrowed pointers within them. We have to guarantee that the
/// regions which appear in those type parameters are not obscured.
/// This is rather subtle. When we are casting a value to a instantiated
/// trait like `a as trait<'r>`, regionck already ensures that any borrowed
/// pointers that appear in the type of `a` are bounded by `'r` (ed.: modulo
/// FIXME(#5723)). However, it is possible that there are *type parameters*
/// in the type of `a`, and those *type parameters* may have borrowed pointers
/// within them. We have to guarantee that the regions which appear in those
/// type parameters are not obscured.
///
/// Therefore, we ensure that one of three conditions holds:
///
Expand All @@ -501,6 +501,8 @@ pub fn check_durable(tcx: ty::ctxt, ty: ty::t, sp: span) -> bool {
///
/// (3) The type parameter is owned (and therefore does not contain
/// borrowed ptrs).
///
/// FIXME(#5723)---This code should probably move into regionck.
pub fn check_cast_for_escaping_regions(
cx: Context,
source: @expr,
Expand All @@ -509,40 +511,78 @@ pub fn check_cast_for_escaping_regions(
// Determine what type we are casting to; if it is not an trait, then no
// worries.
let target_ty = ty::expr_ty(cx.tcx, target);
let target_substs = match ty::get(target_ty).sty {
ty::ty_trait(_, ref substs, _) => {(/*bad*/copy *substs)}
_ => { return; /* not a cast to a trait */ }
};
match ty::get(target_ty).sty {
ty::ty_trait(*) => {}
_ => { return; }
}

// Collect up the regions that appear in the target type. We want to
// ensure that these lifetimes are shorter than all lifetimes that are in
// the source type. See test `src/test/compile-fail/regions-trait-2.rs`
let mut target_regions = ~[];
ty::walk_regions_and_ty(
cx.tcx,
target_ty,
|r| {
if !r.is_bound() {
target_regions.push(r);
}
},
|_| true);

// Check, based on the region associated with the trait, whether it can
// possibly escape the enclosing fn item (note that all type parameters
// must have been declared on the enclosing fn item):
match target_substs.self_r {
Some(ty::re_scope(*)) => { return; /* case (1) */ }
None | Some(ty::re_static) | Some(ty::re_free(*)) => {}
Some(ty::re_bound(*)) | Some(ty::re_infer(*)) => {
cx.tcx.sess.span_bug(
source.span,
fmt!("bad region found in kind: %?", target_substs.self_r));
}
// must have been declared on the enclosing fn item).
if target_regions.any(|r| is_re_scope(*r)) {
return; /* case (1) */
}

// Assuming the trait instance can escape, then ensure that each parameter
// either appears in the trait type or is owned:
// either appears in the trait type or is owned.
let target_params = ty::param_tys_in_type(target_ty);
let source_ty = ty::expr_ty(cx.tcx, source);
do ty::walk_ty(source_ty) |ty| {
match ty::get(ty).sty {
ty::ty_param(source_param) => {
if target_params.contains(&source_param) {
/* case (2) */
} else {
check_durable(cx.tcx, ty, source.span); /* case (3) */
ty::walk_regions_and_ty(
cx.tcx,
source_ty,

|_r| {
// FIXME(#5723) --- turn this check on once &Objects are usable
//
// if !target_regions.any(|t_r| is_subregion_of(cx, *t_r, r)) {
// cx.tcx.sess.span_err(
// source.span,
// fmt!("source contains borrowed pointer with lifetime \
// not found in the target type `%s`",
// ty_to_str(cx.tcx, target_ty)));
// note_and_explain_region(
// cx.tcx, "source data is only valid for ", r, "");
// }
},

|ty| {
match ty::get(ty).sty {
ty::ty_param(source_param) => {
if target_params.contains(&source_param) {
/* case (2) */
} else {
check_durable(cx.tcx, ty, source.span); /* case (3) */
}
}
_ => {}
}
}
_ => {}
true
});

fn is_re_scope(+r: ty::Region) -> bool {
match r {
ty::re_scope(*) => true,
_ => false
}
}

fn is_subregion_of(cx: Context, r_sub: ty::Region, r_sup: ty::Region) -> bool {
cx.tcx.region_maps.is_subregion_of(r_sub, r_sup)
}
}

/// Ensures that values placed into a ~Trait are copyable and sendable.
Expand Down
Loading

0 comments on commit 3322595

Please sign in to comment.