Skip to content

Commit

Permalink
auto merge of #15955 : nikomatsakis/rust/issue-5527-new-inference-sch…
Browse files Browse the repository at this point in the history
…eme, r=pcwalton

The inference scheme proposed in <http://smallcultfollowing.com/babysteps/blog/2014/07/09/an-experimental-new-type-inference-scheme-for-rust/>.

This is theoretically a [breaking-change]. It is possible that you may encounter type checking errors, particularly related to closures or functions with higher-ranked lifetimes or object types. Adding more explicit type annotations should help the problem. However, I have not been able to make an example that *actually* successfully compiles with the older scheme and fails with the newer scheme.

f? @pcwalton, @pnkfelix
  • Loading branch information
bors committed Aug 29, 2014
2 parents 51d0d06 + 6e27c2f commit bd159d3
Show file tree
Hide file tree
Showing 49 changed files with 1,535 additions and 1,054 deletions.
1 change: 1 addition & 0 deletions src/librustc/lib.rs
Expand Up @@ -136,6 +136,7 @@ pub mod util {
pub mod common;
pub mod ppaux;
pub mod nodemap;
pub mod snapshot_vec;
}

pub mod lib {
Expand Down
1 change: 0 additions & 1 deletion src/librustc/middle/ty.rs
Expand Up @@ -30,7 +30,6 @@ use middle::subst::{Subst, Substs, VecPerParamSpace};
use middle::subst;
use middle::ty;
use middle::typeck;
use middle::typeck::MethodCall;
use middle::ty_fold;
use middle::ty_fold::{TypeFoldable,TypeFolder};
use middle;
Expand Down
274 changes: 190 additions & 84 deletions src/librustc/middle/typeck/check/regionck.rs
Expand Up @@ -1381,8 +1381,8 @@ fn link_by_ref(rcx: &Rcx,
expr.repr(tcx), callee_scope);
let mc = mc::MemCategorizationContext::new(rcx);
let expr_cmt = ignore_err!(mc.cat_expr(expr));
let region_min = ty::ReScope(callee_scope);
link_region(rcx, expr.span, region_min, ty::ImmBorrow, expr_cmt);
let borrow_region = ty::ReScope(callee_scope);
link_region(rcx, expr.span, borrow_region, ty::ImmBorrow, expr_cmt);
}

fn link_region_from_node_type(rcx: &Rcx,
Expand All @@ -1408,102 +1408,54 @@ fn link_region_from_node_type(rcx: &Rcx,

fn link_region(rcx: &Rcx,
span: Span,
region_min: ty::Region,
kind: ty::BorrowKind,
cmt_borrowed: mc::cmt) {
borrow_region: ty::Region,
borrow_kind: ty::BorrowKind,
borrow_cmt: mc::cmt) {
/*!
* Informs the inference engine that a borrow of `cmt`
* must have the borrow kind `kind` and lifetime `region_min`.
* If `cmt` is a deref of a region pointer with
* lifetime `r_borrowed`, this will add the constraint that
* `region_min <= r_borrowed`.
* Informs the inference engine that `borrow_cmt` is being
* borrowed with kind `borrow_kind` and lifetime `borrow_region`.
* In order to ensure borrowck is satisfied, this may create
* constraints between regions, as explained in
* `link_reborrowed_region()`.
*/

// Iterate through all the things that must be live at least
// for the lifetime `region_min` for the borrow to be valid:
let mut cmt_borrowed = cmt_borrowed;
let mut borrow_cmt = borrow_cmt;
let mut borrow_kind = borrow_kind;

loop {
debug!("link_region(region_min={}, kind={}, cmt_borrowed={})",
region_min.repr(rcx.tcx()),
kind.repr(rcx.tcx()),
cmt_borrowed.repr(rcx.tcx()));
match cmt_borrowed.cat.clone() {
mc::cat_deref(base, _, mc::BorrowedPtr(_, r_borrowed)) |
mc::cat_deref(base, _, mc::Implicit(_, r_borrowed)) => {
// References to an upvar `x` are translated to
// `*x`, since that is what happens in the
// underlying machine. We detect such references
// and treat them slightly differently, both to
// offer better error messages and because we need
// to infer the kind of borrow (mut, const, etc)
// to use for each upvar.
let cause = match base.cat {
mc::cat_upvar(ref upvar_id, _) => {
match rcx.fcx.inh.upvar_borrow_map.borrow_mut()
.find_mut(upvar_id) {
Some(upvar_borrow) => {
debug!("link_region: {} <= {}",
region_min.repr(rcx.tcx()),
upvar_borrow.region.repr(rcx.tcx()));
adjust_upvar_borrow_kind_for_loan(
*upvar_id,
upvar_borrow,
kind);
infer::ReborrowUpvar(span, *upvar_id)
}
None => {
rcx.tcx().sess.span_bug(
span,
format!("Illegal upvar id: {}",
upvar_id.repr(
rcx.tcx())).as_slice());
}
}
debug!("link_region(borrow_region={}, borrow_kind={}, borrow_cmt={})",
borrow_region.repr(rcx.tcx()),
borrow_kind.repr(rcx.tcx()),
borrow_cmt.repr(rcx.tcx()));
match borrow_cmt.cat.clone() {
mc::cat_deref(ref_cmt, _,
mc::Implicit(ref_kind, ref_region)) |
mc::cat_deref(ref_cmt, _,
mc::BorrowedPtr(ref_kind, ref_region)) => {
match link_reborrowed_region(rcx, span,
borrow_region, borrow_kind,
ref_cmt, ref_region, ref_kind) {
Some((c, k)) => {
borrow_cmt = c;
borrow_kind = k;
}

_ => {
infer::Reborrow(span)
None => {
return;
}
};

debug!("link_region: {} <= {}",
region_min.repr(rcx.tcx()),
r_borrowed.repr(rcx.tcx()));
rcx.fcx.mk_subr(cause, region_min, r_borrowed);

if kind != ty::ImmBorrow {
// If this is a mutable borrow, then the thing
// being borrowed will have to be unique.
// In user code, this means it must be an `&mut`
// borrow, but for an upvar, we might opt
// for an immutable-unique borrow.
adjust_upvar_borrow_kind_for_unique(rcx, base);
}

// Borrowing an `&mut` pointee for `region_min` is
// only valid if the pointer resides in a unique
// location which is itself valid for
// `region_min`. We don't care about the unique
// part, but we may need to influence the
// inference to ensure that the location remains
// valid.
//
// FIXME(#8624) fixing borrowck will require this
// if m == ast::m_mutbl {
// cmt_borrowed = cmt_base;
// } else {
// return;
// }
return;
}

mc::cat_discr(cmt_base, _) |
mc::cat_downcast(cmt_base) |
mc::cat_deref(cmt_base, _, mc::GcPtr(..)) |
mc::cat_deref(cmt_base, _, mc::OwnedPtr) |
mc::cat_interior(cmt_base, _) => {
// Interior or owned data requires its base to be valid
cmt_borrowed = cmt_base;
// Borrowing interior or owned data requires the base
// to be valid and borrowable in the same fashion.
borrow_cmt = cmt_base;
borrow_kind = borrow_kind;
}

mc::cat_deref(_, _, mc::UnsafePtr(..)) |
mc::cat_static_item |
mc::cat_copied_upvar(..) |
Expand All @@ -1519,6 +1471,154 @@ fn link_region(rcx: &Rcx,
}
}

fn link_reborrowed_region(rcx: &Rcx,
span: Span,
borrow_region: ty::Region,
borrow_kind: ty::BorrowKind,
ref_cmt: mc::cmt,
ref_region: ty::Region,
ref_kind: ty::BorrowKind)
-> Option<(mc::cmt, ty::BorrowKind)>
{
/*!
* This is the most complicated case: the path being borrowed is
* itself the referent of a borrowed pointer. Let me give an
* example fragment of code to make clear(er) the situation:
*
* let r: &'a mut T = ...; // the original reference "r" has lifetime 'a
* ...
* &'z *r // the reborrow has lifetime 'z
*
* Now, in this case, our primary job is to add the inference
* constraint that `'z <= 'a`. Given this setup, let's clarify the
* parameters in (roughly) terms of the example:
*
* A borrow of: `& 'z bk * r` where `r` has type `& 'a bk T`
* borrow_region ^~ ref_region ^~
* borrow_kind ^~ ref_kind ^~
* ref_cmt ^
*
* Here `bk` stands for some borrow-kind (e.g., `mut`, `uniq`, etc).
*
* Unfortunately, there are some complications beyond the simple
* scenario I just painted:
*
* 1. The reference `r` might in fact be a "by-ref" upvar. In that
* case, we have two jobs. First, we are inferring whether this reference
* should be an `&T`, `&mut T`, or `&uniq T` reference, and we must
* adjust that based on this borrow (e.g., if this is an `&mut` borrow,
* then `r` must be an `&mut` reference). Second, whenever we link
* two regions (here, `'z <= 'a`), we supply a *cause*, and in this
* case we adjust the cause to indicate that the reference being
* "reborrowed" is itself an upvar. This provides a nicer error message
* should something go wrong.
*
* 2. There may in fact be more levels of reborrowing. In the
* example, I said the borrow was like `&'z *r`, but it might
* in fact be a borrow like `&'z **q` where `q` has type `&'a
* &'b mut T`. In that case, we want to ensure that `'z <= 'a`
* and `'z <= 'b`. This is explained more below.
*
* The return value of this function indicates whether we need to
* recurse and process `ref_cmt` (see case 2 above).
*/

// Detect references to an upvar `x`:
let cause = match ref_cmt.cat {
mc::cat_upvar(ref upvar_id, _) => {
let mut upvar_borrow_map =
rcx.fcx.inh.upvar_borrow_map.borrow_mut();
match upvar_borrow_map.find_mut(upvar_id) {
Some(upvar_borrow) => {
// Adjust mutability that we infer for the upvar
// so it can accommodate being borrowed with
// mutability `kind`:
adjust_upvar_borrow_kind_for_loan(*upvar_id,
upvar_borrow,
borrow_kind);

infer::ReborrowUpvar(span, *upvar_id)
}
None => {
rcx.tcx().sess.span_bug(
span,
format!("Illegal upvar id: {}",
upvar_id.repr(
rcx.tcx())).as_slice());
}
}
}

_ => {
infer::Reborrow(span)
}
};

debug!("link_reborrowed_region: {} <= {}",
borrow_region.repr(rcx.tcx()),
ref_region.repr(rcx.tcx()));
rcx.fcx.mk_subr(cause, borrow_region, ref_region);

// Decide whether we need to recurse and link any regions within
// the `ref_cmt`. This is concerned for the case where the value
// being reborrowed is in fact a borrowed pointer found within
// another borrowed pointer. For example:
//
// let p: &'b &'a mut T = ...;
// ...
// &'z **p
//
// What makes this case particularly tricky is that, if the data
// being borrowed is a `&mut` or `&uniq` borrow, borrowck requires
// not only that `'z <= 'a`, (as before) but also `'z <= 'b`
// (otherwise the user might mutate through the `&mut T` reference
// after `'b` expires and invalidate the borrow we are looking at
// now).
//
// So let's re-examine our parameters in light of this more
// complicated (possible) scenario:
//
// A borrow of: `& 'z bk * * p` where `p` has type `&'b bk & 'a bk T`
// borrow_region ^~ ref_region ^~
// borrow_kind ^~ ref_kind ^~
// ref_cmt ^~~
//
// (Note that since we have not examined `ref_cmt.cat`, we don't
// know whether this scenario has occurred; but I wanted to show
// how all the types get adjusted.)
match ref_kind {
ty::ImmBorrow => {
// The reference being reborrowed is a sharable ref of
// type `&'a T`. In this case, it doesn't matter where we
// *found* the `&T` pointer, the memory it references will
// be valid and immutable for `'a`. So we can stop here.
//
// (Note that the `borrow_kind` must also be ImmBorrow or
// else the user is borrowed imm memory as mut memory,
// which means they'll get an error downstream in borrowck
// anyhow.)
return None;
}

ty::MutBorrow | ty::UniqueImmBorrow => {
// The reference being reborrowed is either an `&mut T` or
// `&uniq T`. This is the case where recursion is needed.
//
// One interesting twist is that we can weaken the borrow
// kind when we recurse: to reborrow an `&mut` referent as
// mutable, borrowck requires a unique path to the `&mut`
// reference but not necessarily a *mutable* path.
let new_borrow_kind = match borrow_kind {
ty::ImmBorrow =>
ty::ImmBorrow,
ty::MutBorrow | ty::UniqueImmBorrow =>
ty::UniqueImmBorrow
};
return Some((ref_cmt, new_borrow_kind));
}
}
}

fn adjust_borrow_kind_for_assignment_lhs(rcx: &Rcx,
lhs: &ast::Expr) {
/*!
Expand All @@ -1534,6 +1634,12 @@ fn adjust_borrow_kind_for_assignment_lhs(rcx: &Rcx,

fn adjust_upvar_borrow_kind_for_mut(rcx: &Rcx,
cmt: mc::cmt) {
/*!
* Indicates that `cmt` is being directly mutated (e.g., assigned
* to). If cmt contains any by-ref upvars, this implies that
* those upvars must be borrowed using an `&mut` borow.
*/

let mut cmt = cmt;
loop {
debug!("adjust_upvar_borrow_kind_for_mut(cmt={})",
Expand Down
14 changes: 7 additions & 7 deletions src/librustc/middle/typeck/infer/coercion.rs
Expand Up @@ -247,7 +247,7 @@ impl<'f> Coerce<'f> {
let a_borrowed = ty::mk_rptr(self.get_ref().infcx.tcx,
r_borrow,
mt {ty: inner_ty, mutbl: mutbl_b});
if_ok!(sub.tys(a_borrowed, b));
try!(sub.tys(a_borrowed, b));

Ok(Some(AutoDerefRef(AutoDerefRef {
autoderefs: 1,
Expand All @@ -273,7 +273,7 @@ impl<'f> Coerce<'f> {
let r_borrow = self.get_ref().infcx.next_region_var(coercion);
let unsized_ty = ty::mk_slice(self.get_ref().infcx.tcx, r_borrow,
mt {ty: t_a, mutbl: mutbl_b});
if_ok!(self.get_ref().infcx.try(|| sub.tys(unsized_ty, b)));
try!(self.get_ref().infcx.try(|| sub.tys(unsized_ty, b)));
Ok(Some(AutoDerefRef(AutoDerefRef {
autoderefs: 0,
autoref: Some(ty::AutoPtr(r_borrow,
Expand Down Expand Up @@ -316,7 +316,7 @@ impl<'f> Coerce<'f> {
let ty = ty::mk_rptr(self.get_ref().infcx.tcx,
r_borrow,
ty::mt{ty: ty, mutbl: mt_b.mutbl});
if_ok!(self.get_ref().infcx.try(|| sub.tys(ty, b)));
try!(self.get_ref().infcx.try(|| sub.tys(ty, b)));
debug!("Success, coerced with AutoDerefRef(1, \
AutoPtr(AutoUnsize({:?})))", kind);
Ok(Some(AutoDerefRef(AutoDerefRef {
Expand All @@ -334,7 +334,7 @@ impl<'f> Coerce<'f> {
match self.unsize_ty(sty_a, t_b) {
Some((ty, kind)) => {
let ty = ty::mk_uniq(self.get_ref().infcx.tcx, ty);
if_ok!(self.get_ref().infcx.try(|| sub.tys(ty, b)));
try!(self.get_ref().infcx.try(|| sub.tys(ty, b)));
debug!("Success, coerced with AutoDerefRef(1, \
AutoUnsizeUniq({:?}))", kind);
Ok(Some(AutoDerefRef(AutoDerefRef {
Expand Down Expand Up @@ -458,7 +458,7 @@ impl<'f> Coerce<'f> {
}
};

if_ok!(self.subtype(a_borrowed, b));
try!(self.subtype(a_borrowed, b));
Ok(Some(AutoDerefRef(AutoDerefRef {
autoderefs: 1,
autoref: Some(AutoPtr(r_a, b_mutbl, None))
Expand Down Expand Up @@ -512,7 +512,7 @@ impl<'f> Coerce<'f> {
sig: fn_ty_a.sig.clone(),
.. *fn_ty_b
});
if_ok!(self.subtype(a_closure, b));
try!(self.subtype(a_closure, b));
Ok(Some(adj))
})
}
Expand All @@ -536,7 +536,7 @@ impl<'f> Coerce<'f> {

// check that the types which they point at are compatible
let a_unsafe = ty::mk_ptr(self.get_ref().infcx.tcx, mt_a);
if_ok!(self.subtype(a_unsafe, b));
try!(self.subtype(a_unsafe, b));

// although references and unsafe ptrs have the same
// representation, we still register an AutoDerefRef so that
Expand Down

0 comments on commit bd159d3

Please sign in to comment.