Auto merge of #41651 - arielb1:missing-adjustment-2, r=eddyb

refactor the handling of lvalue ops

I think I got the code into a "mostly sane" situation.

Fixes #41604.

beta-nominating because fixes regression in #41578. I think I can do a smaller fix, but the previous code is too fragile.

r? @eddyb
This commit is contained in:
bors 2017-04-30 13:38:12 +00:00
commit 06fb4d2564
7 changed files with 207 additions and 233 deletions

View File

@ -10,7 +10,7 @@
use astconv::AstConv;
use super::FnCtxt;
use super::{FnCtxt, LvalueOp};
use check::coercion::AsCoercionSite;
use rustc::infer::InferOk;
@ -18,7 +18,7 @@ use rustc::traits;
use rustc::ty::{self, Ty, TraitRef};
use rustc::ty::{ToPredicate, TypeFoldable};
use rustc::ty::{MethodCall, MethodCallee};
use rustc::ty::{LvaluePreference, NoPreference, PreferMutLvalue};
use rustc::ty::{LvaluePreference, NoPreference};
use rustc::hir;
use syntax_pos::Span;
@ -213,39 +213,12 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
span: Span,
base_expr: Option<&hir::Expr>,
base_ty: Ty<'tcx>,
lvalue_pref: LvaluePreference)
pref: LvaluePreference)
-> Option<InferOk<'tcx, 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,
Symbol::intern("deref_mut"),
trait_did,
base_ty,
None)
}
_ => None,
};
let rcvr = base_expr.map(|base_expr| super::AdjustedRcvr {
rcvr_expr: base_expr, autoderefs: 0, unsize: false
});
// 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,
Symbol::intern("deref"),
trait_did,
base_ty,
None)
}
(method, _) => method,
};
method
self.try_overloaded_lvalue_op(span, rcvr, base_ty, &[], pref, LvalueOp::Deref)
}
}

View File

@ -165,11 +165,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
};
match self.lookup_method_in_trait_adjusted(call_expr.span,
Some(&callee_expr),
Some(super::AdjustedRcvr {
rcvr_expr: callee_expr,
autoderefs,
unsize: false
}),
method_name,
trait_def_id,
autoderefs,
false,
adjusted_ty,
None) {
None => continue,

View File

@ -10,7 +10,7 @@
use super::probe;
use check::{FnCtxt, callee};
use check::{FnCtxt, LvalueOp, callee};
use hir::def_id::DefId;
use rustc::ty::subst::Substs;
use rustc::traits;
@ -433,137 +433,81 @@ impl<'a, 'gcx, 'tcx> ConfirmContext<'a, 'gcx, 'tcx> {
for (i, &expr) in exprs.iter().rev().enumerate() {
debug!("convert_lvalue_derefs_to_mutable: i={} expr={:?}", i, expr);
// Count autoderefs. We don't need to fix up the autoref - the parent
// expression will fix them up for us.
let adjustment = self.tables.borrow().adjustments.get(&expr.id).cloned();
match adjustment {
Some(Adjustment { kind: Adjust::DerefRef { autoderefs, .. }, .. }) => {
if autoderefs > 0 {
let mut autoderef = self.autoderef(expr.span, self.node_ty(expr.id));
autoderef.nth(autoderefs).unwrap_or_else(|| {
span_bug!(expr.span,
"expr was deref-able {} times but now isn't?",
autoderefs);
});
autoderef.finalize(PreferMutLvalue, expr);
// Fix up the adjustment.
let autoderefs = match self.tables.borrow_mut().adjustments.get_mut(&expr.id) {
Some(&mut Adjustment {
kind: Adjust::DerefRef { autoderefs, ref mut autoref, .. }, ref mut target
}) => {
if let &mut Some(AutoBorrow::Ref(_, ref mut mutbl)) = autoref {
*mutbl = hir::Mutability::MutMutable;
*target = match target.sty {
ty::TyRef(r, ty::TypeAndMut { ty, .. }) =>
self.tcx.mk_ref(r, ty::TypeAndMut { ty, mutbl: *mutbl }),
_ => span_bug!(expr.span, "AutoBorrow::Ref resulted in non-ref {:?}",
target)
};
}
autoderefs
}
Some(_) | None => {}
Some(_) | None => 0
};
if autoderefs > 0 {
let mut autoderef = self.autoderef(expr.span, self.node_ty(expr.id));
autoderef.nth(autoderefs).unwrap_or_else(|| {
span_bug!(expr.span,
"expr was deref-able {} times but now isn't?",
autoderefs);
});
autoderef.finalize(PreferMutLvalue, expr);
}
// Don't retry the first one or we might infinite loop!
if i == 0 {
continue;
}
match expr.node {
hir::ExprIndex(ref base_expr, ref index_expr) => {
// If this is an overloaded index, the
// adjustment will include an extra layer of
// autoref because the method is an &self/&mut
// self method. We have to peel it off to get
// the raw adjustment that `try_index_step`
// expects. This is annoying and horrible. We
// ought to recode this routine so it doesn't
// (ab)use the normal type checking paths.
let adj = self.tables.borrow_mut().adjustments.remove(&base_expr.id);
let (autoderefs, unsize, adjusted_base_ty) = match adj {
Some(Adjustment {
kind: Adjust::DerefRef { autoderefs, autoref, unsize },
target
}) => {
match autoref {
None => {
assert!(!unsize);
}
Some(AutoBorrow::Ref(..)) => {}
Some(_) => {
span_bug!(base_expr.span,
"unexpected adjustment autoref {:?}",
adj);
}
}
(autoderefs, unsize, if unsize {
target.builtin_deref(false, NoPreference)
.expect("fixup: AutoBorrow::Ref is not &T")
.ty
} else {
let ty = self.node_ty(base_expr.id);
let mut ty = self.shallow_resolve(ty);
let mut method_type = |method_call: ty::MethodCall| {
self.tables.borrow().method_map.get(&method_call).map(|m| {
self.resolve_type_vars_if_possible(&m.ty)
})
};
if !ty.references_error() {
for i in 0..autoderefs {
ty = ty.adjust_for_autoderef(self.tcx,
base_expr.id,
base_expr.span,
i as u32,
&mut method_type);
}
}
ty
})
}
None => (0, false, self.node_ty(base_expr.id)),
Some(_) => {
span_bug!(base_expr.span, "unexpected adjustment type");
}
};
let index_expr_ty = self.node_ty(index_expr.id);
let adjusted_base_ty = self.resolve_type_vars_if_possible(&adjusted_base_ty);
let index_expr_ty = self.resolve_type_vars_if_possible(&index_expr_ty);
let result = self.try_index_step(ty::MethodCall::expr(expr.id),
expr,
&base_expr,
adjusted_base_ty,
autoderefs,
unsize,
PreferMutLvalue,
index_expr_ty);
if let Some((input_ty, return_ty)) = result {
self.demand_suptype(index_expr.span, input_ty, index_expr_ty);
let expr_ty = self.node_ty(expr.id);
self.demand_suptype(expr.span, expr_ty, return_ty);
} else {
// We could not perform a mutable index. Re-apply the
// immutable index adjustments - borrowck will detect
// this as an error.
if let Some(adjustment) = adjustment {
self.apply_adjustment(expr.id, adjustment);
}
self.tcx.sess.delay_span_bug(
expr.span, "convert_lvalue_derefs_to_mutable failed");
}
self.convert_lvalue_op_to_mutable(
LvalueOp::Index, expr, base_expr, &[index_expr_ty]);
}
hir::ExprUnary(hir::UnDeref, ref base_expr) => {
// if this is an overloaded deref, then re-evaluate with
// a preference for mut
let method_call = ty::MethodCall::expr(expr.id);
if self.tables.borrow().method_map.contains_key(&method_call) {
self.tables.borrow_mut().adjustments.remove(&base_expr.id);
let method = self.try_overloaded_deref(expr.span,
Some(&base_expr),
self.node_ty(base_expr.id),
PreferMutLvalue);
let ok = method.expect("re-trying deref failed");
let method = self.register_infer_ok_obligations(ok);
self.tables.borrow_mut().method_map.insert(method_call, method);
}
self.convert_lvalue_op_to_mutable(
LvalueOp::Deref, expr, base_expr, &[]);
}
_ => {}
}
}
}
fn convert_lvalue_op_to_mutable(&self,
op: LvalueOp,
expr: &hir::Expr,
base_expr: &hir::Expr,
arg_tys: &[Ty<'tcx>])
{
debug!("convert_lvalue_op_to_mutable({:?}, {:?}, {:?}, {:?})",
op, expr, base_expr, arg_tys);
let method_call = ty::MethodCall::expr(expr.id);
if !self.tables.borrow().method_map.contains_key(&method_call) {
debug!("convert_lvalue_op_to_mutable - builtin, nothing to do");
return
}
let base_ty = self.tables.borrow().adjustments.get(&base_expr.id)
.map_or_else(|| self.node_ty(expr.id), |adj| adj.target);
let base_ty = self.resolve_type_vars_if_possible(&base_ty);
// Need to deref because overloaded lvalue ops take self by-reference.
let base_ty = base_ty.builtin_deref(false, NoPreference)
.expect("lvalue op takes something that is not a ref")
.ty;
let method = self.try_overloaded_lvalue_op(
expr.span, None, base_ty, arg_tys, PreferMutLvalue, op);
let ok = method.expect("re-trying op failed");
let method = self.register_infer_ok_obligations(ok);
debug!("convert_lvalue_op_to_mutable: method={:?}", method);
self.tables.borrow_mut().method_map.insert(method_call, method);
}
///////////////////////////////////////////////////////////////////////////
// MISCELLANY

View File

@ -10,7 +10,7 @@
//! Method lookup: the secret sauce of Rust. See `README.md`.
use check::FnCtxt;
use check::{FnCtxt, AdjustedRcvr};
use hir::def::Def;
use hir::def_id::DefId;
use rustc::ty::subst::Substs;
@ -153,24 +153,6 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
supplied_method_types))
}
pub fn lookup_method_in_trait(&self,
span: Span,
self_expr: Option<&hir::Expr>,
m_name: ast::Name,
trait_def_id: DefId,
self_ty: ty::Ty<'tcx>,
opt_input_types: Option<Vec<ty::Ty<'tcx>>>)
-> Option<InferOk<'tcx, ty::MethodCallee<'tcx>>> {
self.lookup_method_in_trait_adjusted(span,
self_expr,
m_name,
trait_def_id,
0,
false,
self_ty,
opt_input_types)
}
/// `lookup_in_trait_adjusted` is used for overloaded operators.
/// It does a very narrow slice of what the normal probe/confirm path does.
/// In particular, it doesn't really do any probing: it simply constructs
@ -184,18 +166,16 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
/// this method is basically the same as confirmation.
pub fn lookup_method_in_trait_adjusted(&self,
span: Span,
self_expr: Option<&hir::Expr>,
self_info: Option<AdjustedRcvr>,
m_name: ast::Name,
trait_def_id: DefId,
autoderefs: usize,
unsize: bool,
self_ty: ty::Ty<'tcx>,
opt_input_types: Option<Vec<ty::Ty<'tcx>>>)
-> Option<InferOk<'tcx, ty::MethodCallee<'tcx>>> {
debug!("lookup_in_trait_adjusted(self_ty={:?}, self_expr={:?}, \
debug!("lookup_in_trait_adjusted(self_ty={:?}, self_info={:?}, \
m_name={}, trait_def_id={:?})",
self_ty,
self_expr,
self_info,
m_name,
trait_def_id);
@ -288,10 +268,10 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
obligations.push(traits::Obligation::new(cause, ty::Predicate::WellFormed(method_ty)));
// Insert any adjustments needed (always an autoref of some mutability).
if let Some(self_expr) = self_expr {
if let Some(AdjustedRcvr { rcvr_expr, autoderefs, unsize }) = self_info {
debug!("lookup_in_trait_adjusted: inserting adjustment if needed \
(self-id={}, autoderefs={}, unsize={}, fty={:?})",
self_expr.id, autoderefs, unsize, original_method_ty);
rcvr_expr.id, autoderefs, unsize, original_method_ty);
let original_sig = original_method_ty.fn_sig();
let autoref = match (&original_sig.input(0).skip_binder().sty,
@ -308,7 +288,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
}
};
self.apply_adjustment(self_expr.id, Adjustment {
self.apply_adjustment(rcvr_expr.id, Adjustment {
kind: Adjust::DerefRef {
autoderefs: autoderefs,
autoref: autoref,

View File

@ -361,6 +361,19 @@ impl UnsafetyState {
}
}
#[derive(Debug, Copy, Clone)]
pub enum LvalueOp {
Deref,
Index
}
#[derive(Copy, Clone, Debug)]
pub struct AdjustedRcvr<'a> {
pub rcvr_expr: &'a hir::Expr,
pub autoderefs: usize,
pub unsize: bool
}
/// Tracks whether executing a node may exit normally (versus
/// return/break/panic, which "diverge", leaving dead code in their
/// wake). Tracked semi-automatically (through type variables marked
@ -2156,9 +2169,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
while let Some((adj_ty, autoderefs)) = autoderef.next() {
if let Some(final_mt) = self.try_index_step(
MethodCall::expr(expr.id),
expr, base_expr, adj_ty, autoderefs,
false, lvalue_pref, idx_ty)
MethodCall::expr(expr.id), expr, Some(AdjustedRcvr {
rcvr_expr: base_expr,
autoderefs,
unsize: false
}), base_expr.span, adj_ty, lvalue_pref, idx_ty)
{
autoderef.finalize(lvalue_pref, base_expr);
return Some(final_mt);
@ -2166,10 +2181,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
if let ty::TyArray(element_ty, _) = adj_ty.sty {
autoderef.finalize(lvalue_pref, base_expr);
let adjusted_ty = self.tcx.mk_slice(element_ty);
let adj_ty = self.tcx.mk_slice(element_ty);
return self.try_index_step(
MethodCall::expr(expr.id), expr, base_expr,
adjusted_ty, autoderefs, true, lvalue_pref, idx_ty);
MethodCall::expr(expr.id), expr, Some(AdjustedRcvr {
rcvr_expr: base_expr,
autoderefs,
unsize: true
}), base_expr.span, adj_ty, lvalue_pref, idx_ty)
}
}
autoderef.unambiguous_final_ty();
@ -2184,71 +2202,44 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
fn try_index_step(&self,
method_call: MethodCall,
expr: &hir::Expr,
base_expr: &'gcx hir::Expr,
base_expr: Option<AdjustedRcvr>,
base_span: Span,
adjusted_ty: Ty<'tcx>,
autoderefs: usize,
unsize: bool,
lvalue_pref: LvaluePreference,
index_ty: Ty<'tcx>)
-> Option<(/*index type*/ Ty<'tcx>, /*element type*/ Ty<'tcx>)>
{
let tcx = self.tcx;
debug!("try_index_step(expr={:?}, base_expr.id={:?}, adjusted_ty={:?}, \
autoderefs={}, unsize={}, index_ty={:?})",
debug!("try_index_step(expr={:?}, base_expr={:?}, adjusted_ty={:?}, \
index_ty={:?})",
expr,
base_expr,
adjusted_ty,
autoderefs,
unsize,
index_ty);
let input_ty = self.next_ty_var(TypeVariableOrigin::AutoDeref(base_expr.span));
let input_ty = self.next_ty_var(TypeVariableOrigin::AutoDeref(base_span));
// First, try built-in indexing.
match (adjusted_ty.builtin_index(), &index_ty.sty) {
(Some(ty), &ty::TyUint(ast::UintTy::Us)) | (Some(ty), &ty::TyInfer(ty::IntVar(_))) => {
debug!("try_index_step: success, using built-in indexing");
// If we had `[T; N]`, we should've caught it before unsizing to `[T]`.
assert!(!unsize);
self.apply_autoderef_adjustment(base_expr.id, autoderefs, adjusted_ty);
if let Some(base_expr) = base_expr {
assert!(!base_expr.unsize);
self.apply_autoderef_adjustment(
base_expr.rcvr_expr.id, base_expr.autoderefs, adjusted_ty);
}
return Some((tcx.types.usize, ty));
}
_ => {}
}
// Try `IndexMut` first, if preferred.
let method = match (lvalue_pref, tcx.lang_items.index_mut_trait()) {
(PreferMutLvalue, Some(trait_did)) => {
self.lookup_method_in_trait_adjusted(expr.span,
Some(&base_expr),
Symbol::intern("index_mut"),
trait_did,
autoderefs,
unsize,
adjusted_ty,
Some(vec![input_ty]))
}
_ => None,
};
// Otherwise, fall back to `Index`.
let method = match (method, tcx.lang_items.index_trait()) {
(None, Some(trait_did)) => {
self.lookup_method_in_trait_adjusted(expr.span,
Some(&base_expr),
Symbol::intern("index"),
trait_did,
autoderefs,
unsize,
adjusted_ty,
Some(vec![input_ty]))
}
(method, _) => method,
};
// If some lookup succeeds, write callee into table and extract index/element
// type from the method signature.
// If some lookup succeeded, install method in table
let method = self.try_overloaded_lvalue_op(
expr.span, base_expr, adjusted_ty, &[input_ty], lvalue_pref, LvalueOp::Index);
method.map(|ok| {
debug!("try_index_step: success, using overloaded indexing");
let method = self.register_infer_ok_obligations(ok);
@ -2257,6 +2248,67 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
})
}
fn resolve_lvalue_op(&self, op: LvalueOp, is_mut: bool) -> (Option<DefId>, Symbol) {
let (tr, name) = match (op, is_mut) {
(LvalueOp::Deref, false) =>
(self.tcx.lang_items.deref_trait(), "deref"),
(LvalueOp::Deref, true) =>
(self.tcx.lang_items.deref_mut_trait(), "deref_mut"),
(LvalueOp::Index, false) =>
(self.tcx.lang_items.index_trait(), "index"),
(LvalueOp::Index, true) =>
(self.tcx.lang_items.index_mut_trait(), "index_mut"),
};
(tr, Symbol::intern(name))
}
fn try_overloaded_lvalue_op(&self,
span: Span,
base_expr: Option<AdjustedRcvr>,
base_ty: Ty<'tcx>,
arg_tys: &[Ty<'tcx>],
lvalue_pref: LvaluePreference,
op: LvalueOp)
-> Option<InferOk<'tcx, MethodCallee<'tcx>>>
{
debug!("try_overloaded_lvalue_op({:?},{:?},{:?},{:?},{:?})",
span,
base_expr,
base_ty,
lvalue_pref,
op);
// Try Mut first, if preferred.
let (mut_tr, mut_op) = self.resolve_lvalue_op(op, true);
let method = match (lvalue_pref, mut_tr) {
(PreferMutLvalue, Some(trait_did)) => {
self.lookup_method_in_trait_adjusted(span,
base_expr,
mut_op,
trait_did,
base_ty,
Some(arg_tys.to_owned()))
}
_ => None,
};
// Otherwise, fall back to the immutable version.
let (imm_tr, imm_op) = self.resolve_lvalue_op(op, false);
let method = match (method, imm_tr) {
(None, Some(trait_did)) => {
self.lookup_method_in_trait_adjusted(span,
base_expr,
imm_op,
trait_did,
base_ty,
Some(arg_tys.to_owned()))
}
(method, _) => method,
};
method
}
fn check_method_argument_types(&self,
sp: Span,
method_fn_ty: Ty<'tcx>,

View File

@ -398,12 +398,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
let method = match trait_did {
Some(trait_did) => {
self.lookup_method_in_trait(expr.span,
Some(lhs_expr),
opname,
trait_did,
lhs_ty,
Some(other_tys))
let lhs_expr = Some(super::AdjustedRcvr {
rcvr_expr: lhs_expr, autoderefs: 0, unsize: false
});
self.lookup_method_in_trait_adjusted(expr.span,
lhs_expr,
opname,
trait_did,
lhs_ty,
Some(other_tys))
}
None => None
};

View File

@ -0,0 +1,20 @@
// Copyright 2017 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.
struct B;
impl B {
fn init(&mut self) {}
}
fn main() {
let mut b = [B];
b[1-1].init();
}