Skip to content

Commit

Permalink
refactor autoderef to avoid registering obligations
Browse files Browse the repository at this point in the history
Refactor `FnCtxt::autoderef` to use an external iterator and to not
register any obligation from the main autoderef loop, but rather to
register them after (and if) the loop successfully completes.

Fixes #24819
Fixes #25801
Fixes #27631
Fixes #31258
Fixes #31964
Fixes #32320
Fixes #33515
Fixes #33755
  • Loading branch information
Ariel Ben-Yehuda authored and arielb1 committed May 24, 2016
1 parent dd6e8d4 commit c209d44
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 356 deletions.
2 changes: 1 addition & 1 deletion src/librustc/traits/mod.rs
Expand Up @@ -31,7 +31,7 @@ pub use self::coherence::overlapping_impls;
pub use self::coherence::OrphanCheckErr;
pub use self::fulfill::{FulfillmentContext, GlobalFulfilledPredicates, RegionObligation};
pub use self::project::{MismatchedProjectionTypes, ProjectionMode};
pub use self::project::{normalize, Normalized};
pub use self::project::{normalize, normalize_projection_type, Normalized};
pub use self::object_safety::ObjectSafetyViolation;
pub use self::object_safety::MethodViolationCode;
pub use self::select::{EvaluationCache, SelectionContext, SelectionCache};
Expand Down
3 changes: 2 additions & 1 deletion src/librustc/ty/adjustment.rs
Expand Up @@ -235,8 +235,9 @@ impl<'a, 'gcx, 'tcx> ty::TyS<'tcx> {
None => {
span_bug!(
expr_span,
"the {}th autoderef failed: {}",
"the {}th autoderef for {} failed: {}",
autoderef,
expr_id,
adjusted_ty);
}
}
Expand Down
210 changes: 210 additions & 0 deletions src/librustc_typeck/check/autoderef.rs
@@ -0,0 +1,210 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use astconv::AstConv;

use super::FnCtxt;

use rustc::traits;
use rustc::ty::{self, Ty, TraitRef};
use rustc::ty::{ToPredicate, TypeFoldable};
use rustc::ty::{MethodCall, MethodCallee};
use rustc::ty::subst::Substs;
use rustc::ty::{LvaluePreference, NoPreference, PreferMutLvalue};
use rustc::hir;

use syntax::codemap::Span;
use syntax::parse::token;

#[derive(Copy, Clone, Debug)]
enum AutoderefKind {
Builtin,
Overloaded
}

pub struct Autoderef<'a, 'gcx: 'tcx, 'tcx: 'a> {
fcx: &'a FnCtxt<'a, 'gcx, 'tcx>,
steps: Vec<(Ty<'tcx>, AutoderefKind)>,
cur_ty: Ty<'tcx>,
obligations: Vec<traits::PredicateObligation<'tcx>>,
at_start: bool,
span: Span
}

impl<'a, 'gcx, 'tcx> Iterator for Autoderef<'a, 'gcx, 'tcx> {
type Item = (Ty<'tcx>, usize);

fn next(&mut self) -> Option<Self::Item> {
let tcx = self.fcx.tcx;

debug!("autoderef: steps={:?}, cur_ty={:?}",
self.steps, self.cur_ty);
if self.at_start {
self.at_start = false;
debug!("autoderef stage #0 is {:?}", self.cur_ty);
return Some((self.cur_ty, 0));
}

if self.steps.len() == tcx.sess.recursion_limit.get() {
// We've reached the recursion limit, error gracefully.
span_err!(tcx.sess, self.span, E0055,
"reached the recursion limit while auto-dereferencing {:?}",
self.cur_ty);
return None;
}

if self.cur_ty.is_ty_var() {
return None;
}

// Otherwise, deref if type is derefable:
let (kind, new_ty) = if let Some(mt) = self.cur_ty.builtin_deref(false, NoPreference) {
(AutoderefKind::Builtin, mt.ty)
} else {
match self.overloaded_deref_ty(self.cur_ty) {
Some(ty) => (AutoderefKind::Overloaded, ty),
_ => return None
}
};

if new_ty.references_error() {
return None;
}

self.steps.push((self.cur_ty, kind));
debug!("autoderef stage #{:?} is {:?} from {:?}", self.steps.len(),
new_ty, (self.cur_ty, kind));
self.cur_ty = new_ty;

Some((self.cur_ty, self.steps.len()))
}
}

impl<'a, 'gcx, 'tcx> Autoderef<'a, 'gcx, 'tcx> {
fn overloaded_deref_ty(&mut self, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
debug!("overloaded_deref_ty({:?})", ty);

let tcx = self.fcx.tcx();

// <cur_ty as Deref>
let trait_ref = TraitRef {
def_id: match tcx.lang_items.deref_trait() {
Some(f) => f,
None => return None
},
substs: tcx.mk_substs(Substs::new_trait(vec![], vec![], self.cur_ty))
};

let cause = traits::ObligationCause::misc(self.span, self.fcx.body_id);

let mut selcx = traits::SelectionContext::new(self.fcx);
let obligation = traits::Obligation::new(cause.clone(), trait_ref.to_predicate());
if !selcx.evaluate_obligation(&obligation) {
debug!("overloaded_deref_ty: cannot match obligation");
return None;
}

let normalized = traits::normalize_projection_type(
&mut selcx,
ty::ProjectionTy {
trait_ref: trait_ref,
item_name: token::intern("Target")
},
cause,
0
);

debug!("overloaded_deref_ty({:?}) = {:?}", ty, normalized);
self.obligations.extend(normalized.obligations);

Some(self.fcx.resolve_type_vars_if_possible(&normalized.value))
}

pub fn unambiguous_final_ty(&self) -> Ty<'tcx> {
self.fcx.structurally_resolved_type(self.span, self.cur_ty)
}

pub fn finalize<'b, I>(self, pref: LvaluePreference, exprs: I)
where I: IntoIterator<Item=&'b hir::Expr>
{
let methods : Vec<_> = self.steps.iter().map(|&(ty, kind)| {
if let AutoderefKind::Overloaded = kind {
self.fcx.try_overloaded_deref(self.span, None, ty, pref)
} else {
None
}
}).collect();

debug!("finalize({:?}) - {:?},{:?}", pref, methods, self.obligations);

for expr in exprs {
debug!("finalize - finalizing #{} - {:?}", expr.id, expr);
for (n, method) in methods.iter().enumerate() {
if let &Some(method) = method {
let method_call = MethodCall::autoderef(expr.id, n as u32);
self.fcx.tables.borrow_mut().method_map.insert(method_call, method);
}
}
}

for obligation in self.obligations {
self.fcx.register_predicate(obligation);
}
}
}

impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
pub fn autoderef(&'a self,
span: Span,
base_ty: Ty<'tcx>)
-> Autoderef<'a, 'gcx, 'tcx>
{
Autoderef {
fcx: self,
steps: vec![],
cur_ty: self.resolve_type_vars_if_possible(&base_ty),
obligations: vec![],
at_start: true,
span: span
}
}

pub fn try_overloaded_deref(&self,
span: Span,
base_expr: Option<&hir::Expr>,
base_ty: Ty<'tcx>,
lvalue_pref: LvaluePreference)
-> Option<MethodCallee<'tcx>>
{
debug!("try_overloaded_deref({:?},{:?},{:?},{:?})",
span, base_expr, base_ty, lvalue_pref);
// Try DerefMut first, if preferred.
let method = match (lvalue_pref, self.tcx.lang_items.deref_mut_trait()) {
(PreferMutLvalue, Some(trait_did)) => {
self.lookup_method_in_trait(span, base_expr,
token::intern("deref_mut"), trait_did,
base_ty, None)
}
_ => None
};

// Otherwise, fall back to Deref.
let method = match (method, self.tcx.lang_items.deref_trait()) {
(None, Some(trait_did)) => {
self.lookup_method_in_trait(span, base_expr,
token::intern("deref"), trait_did,
base_ty, None)
}
(method, _) => method
};

method
}
}
18 changes: 8 additions & 10 deletions src/librustc_typeck/check/callee.rs
Expand Up @@ -9,7 +9,7 @@
// except according to those terms.

use super::{DeferredCallResolution, Expectation, FnCtxt,
TupleArgumentsFlag, UnresolvedTypeAction};
TupleArgumentsFlag};

use CrateCtxt;
use middle::cstore::LOCAL_CRATE;
Expand Down Expand Up @@ -72,15 +72,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
{
self.check_expr(callee_expr);
let original_callee_ty = self.expr_ty(callee_expr);
let (callee_ty, _, result) =
self.autoderef(callee_expr.span,
original_callee_ty,
|| Some(callee_expr),
UnresolvedTypeAction::Error,
LvaluePreference::NoPreference,
|adj_ty, idx| {
self.try_overloaded_call_step(call_expr, callee_expr, adj_ty, idx)
});

let mut autoderef = self.autoderef(callee_expr.span, original_callee_ty);
let result = autoderef.by_ref().flat_map(|(adj_ty, idx)| {
self.try_overloaded_call_step(call_expr, callee_expr, adj_ty, idx)
}).next();
let callee_ty = autoderef.unambiguous_final_ty();
autoderef.finalize(LvaluePreference::NoPreference, Some(callee_expr));

match result {
None => {
Expand Down
32 changes: 16 additions & 16 deletions src/librustc_typeck/check/coercion.rs
Expand Up @@ -60,7 +60,7 @@
//! sort of a minor point so I've opted to leave it for later---after all
//! we may want to adjust precisely when coercions occur.

use check::{FnCtxt, UnresolvedTypeAction};
use check::{FnCtxt};

use rustc::hir;
use rustc::infer::{Coercion, InferOk, TypeOrigin, TypeTrace};
Expand Down Expand Up @@ -220,7 +220,8 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
-> CoerceResult<'tcx>
// FIXME(eddyb) use copyable iterators when that becomes ergonomic.
where E: Fn() -> I,
I: IntoIterator<Item=&'a hir::Expr> {
I: IntoIterator<Item=&'a hir::Expr>
{

debug!("coerce_borrowed_pointer(a={:?}, b={:?})", a, b);

Expand All @@ -240,18 +241,16 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {

let span = self.origin.span();

let lvalue_pref = LvaluePreference::from_mutbl(mt_b.mutbl);
let mut first_error = None;
let mut r_borrow_var = None;
let (_, autoderefs, success) = self.autoderef(span, a, exprs,
UnresolvedTypeAction::Ignore,
lvalue_pref,
|referent_ty, autoderef|
{
if autoderef == 0 {
let mut autoderef = self.autoderef(span, a);
let mut success = None;

for (referent_ty, autoderefs) in autoderef.by_ref() {
if autoderefs == 0 {
// Don't let this pass, otherwise it would cause
// &T to autoref to &&T.
return None;
continue
}

// At this point, we have deref'd `a` to `referent_ty`. So
Expand Down Expand Up @@ -326,7 +325,7 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
// and let regionck figure it out.
let r = if !self.use_lub {
r_b // [2] above
} else if autoderef == 1 {
} else if autoderefs == 1 {
r_a // [3] above
} else {
if r_borrow_var.is_none() { // create var lazilly, at most once
Expand All @@ -341,30 +340,31 @@ impl<'f, 'gcx, 'tcx> Coerce<'f, 'gcx, 'tcx> {
mutbl: mt_b.mutbl // [1] above
});
match self.unify(derefd_ty_a, b) {
Ok(ty) => Some(ty),
Ok(ty) => { success = Some((ty, autoderefs)); break },
Err(err) => {
if first_error.is_none() {
first_error = Some(err);
}
None
}
}
});
}

// Extract type or return an error. We return the first error
// we got, which should be from relating the "base" type
// (e.g., in example above, the failure from relating `Vec<T>`
// to the target type), since that should be the least
// confusing.
let ty = match success {
Some(ty) => ty,
let (ty, autoderefs) = match success {
Some(d) => d,
None => {
let err = first_error.expect("coerce_borrowed_pointer had no error");
debug!("coerce_borrowed_pointer: failed with err = {:?}", err);
return Err(err);
}
};

autoderef.finalize(LvaluePreference::from_mutbl(mt_b.mutbl), exprs());

// Now apply the autoref. We have to extract the region out of
// the final ref type we got.
if ty == a && mt_a.mutbl == hir::MutImmutable && autoderefs == 1 {
Expand Down

0 comments on commit c209d44

Please sign in to comment.