diff --git a/src/librustc/middle/traits/fulfill.rs b/src/librustc/middle/traits/fulfill.rs index c0caa1d7c79..6baf4e6c048 100644 --- a/src/librustc/middle/traits/fulfill.rs +++ b/src/librustc/middle/traits/fulfill.rs @@ -35,12 +35,18 @@ pub struct FulfillmentContext { // A list of all obligations that have been registered with this // fulfillment context. trait_obligations: Vec, + + // Remembers the count of trait obligations that we have already + // attempted to select. This is used to avoid repeating work + // when `select_new_obligations` is called. + attempted_mark: uint, } impl FulfillmentContext { pub fn new() -> FulfillmentContext { FulfillmentContext { trait_obligations: Vec::new(), + attempted_mark: 0, } } @@ -74,18 +80,49 @@ impl FulfillmentContext { } } + pub fn select_new_obligations<'a,'tcx>(&mut self, + infcx: &InferCtxt<'a,'tcx>, + param_env: &ty::ParameterEnvironment, + typer: &Typer<'tcx>) + -> Result<(),Vec> + { + /*! + * Attempts to select obligations that were registered since + * the call to a selection routine. This is used by the type checker + * to eagerly attempt to resolve obligations in hopes of gaining + * type information. It'd be equally valid to use `select_where_possible` + * but it results in `O(n^2)` performance (#18208). + */ + + let mut selcx = SelectionContext::new(infcx, param_env, typer); + self.select(&mut selcx, true) + } + pub fn select_where_possible<'a,'tcx>(&mut self, infcx: &InferCtxt<'a,'tcx>, param_env: &ty::ParameterEnvironment, typer: &Typer<'tcx>) -> Result<(),Vec> { - let tcx = infcx.tcx; let mut selcx = SelectionContext::new(infcx, param_env, typer); + self.select(&mut selcx, false) + } - debug!("select_where_possible({} obligations) start", - self.trait_obligations.len()); + fn select(&mut self, + selcx: &mut SelectionContext, + only_new_obligations: bool) + -> Result<(),Vec> + { + /*! + * Attempts to select obligations using `selcx`. If + * `only_new_obligations` is true, then it only attempts to + * select obligations that haven't been seen before. + */ + debug!("select({} obligations, only_new_obligations={}) start", + self.trait_obligations.len(), + only_new_obligations); + let tcx = selcx.tcx(); let mut errors = Vec::new(); loop { @@ -96,30 +133,47 @@ impl FulfillmentContext { let mut selections = Vec::new(); + // If we are only attempting obligations we haven't seen yet, + // then set `skip` to the number of obligations we've already + // seen. + let mut skip = if only_new_obligations { + self.attempted_mark + } else { + 0 + }; + // First pass: walk each obligation, retaining // only those that we cannot yet process. self.trait_obligations.retain(|obligation| { - match selcx.select(obligation) { - Ok(None) => { - true - } - Ok(Some(s)) => { - selections.push(s); - false - } - Err(selection_err) => { - debug!("obligation: {} error: {}", - obligation.repr(tcx), - selection_err.repr(tcx)); - - errors.push(FulfillmentError::new( - (*obligation).clone(), - CodeSelectionError(selection_err))); - false + // Hack: Retain does not pass in the index, but we want + // to avoid processing the first `start_count` entries. + if skip > 0 { + skip -= 1; + true + } else { + match selcx.select(obligation) { + Ok(None) => { + true + } + Ok(Some(s)) => { + selections.push(s); + false + } + Err(selection_err) => { + debug!("obligation: {} error: {}", + obligation.repr(tcx), + selection_err.repr(tcx)); + errors.push(FulfillmentError::new( + (*obligation).clone(), + CodeSelectionError(selection_err))); + false + } } } }); + self.attempted_mark = self.trait_obligations.len(); + if self.trait_obligations.len() == count { // Nothing changed. break; @@ -133,7 +187,7 @@ impl FulfillmentContext { } } - debug!("select_where_possible({} obligations, {} errors) done", + debug!("select({} obligations, {} errors) done", self.trait_obligations.len(), errors.len()); diff --git a/src/librustc/middle/typeck/check/method.rs b/src/librustc/middle/typeck/check/method.rs index f8604eeb5c6..82a3d1a523d 100644 --- a/src/librustc/middle/typeck/check/method.rs +++ b/src/librustc/middle/typeck/check/method.rs @@ -88,7 +88,7 @@ use middle::ty; use middle::typeck::astconv::AstConv; use middle::typeck::check::{FnCtxt, NoPreference, PreferMutLvalue}; use middle::typeck::check::{impl_self_ty}; -use middle::typeck::check::vtable::select_fcx_obligations_where_possible; +use middle::typeck::check::vtable::select_new_fcx_obligations; use middle::typeck::check; use middle::typeck::infer; use middle::typeck::{MethodCall, MethodCallee}; @@ -1302,7 +1302,7 @@ impl<'a, 'tcx> LookupContext<'a, 'tcx> { // the `Self` trait). let callee = self.confirm_candidate(rcvr_ty, &candidate); - select_fcx_obligations_where_possible(self.fcx); + select_new_fcx_obligations(self.fcx); Some(Ok(callee)) } diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 5f6795f24c1..53a4a8141e6 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -101,7 +101,6 @@ use middle::typeck::check::method::{AutoderefReceiver}; use middle::typeck::check::method::{CheckTraitsAndInherentMethods}; use middle::typeck::check::regionmanip::replace_late_bound_regions; use middle::typeck::CrateCtxt; -use middle::typeck::infer::{resolve_type, force_tvar}; use middle::typeck::infer; use middle::typeck::rscope::RegionScope; use middle::typeck::{lookup_def_ccx}; @@ -1412,7 +1411,7 @@ fn check_cast(fcx: &FnCtxt, } // casts from C-like enums are allowed } else if t_1_is_char { - let t_e = fcx.infcx().resolve_type_vars_if_possible(t_e); + let t_e = fcx.infcx().shallow_resolve(t_e); if ty::get(t_e).sty != ty::ty_uint(ast::TyU8) { fcx.type_error_message(span, |actual| { format!("only `u8` can be cast as \ @@ -2564,7 +2563,7 @@ fn check_argument_types<'a>(fcx: &FnCtxt, // an "opportunistic" vtable resolution of any trait // bounds on the call. if check_blocks { - vtable::select_fcx_obligations_where_possible(fcx); + vtable::select_new_fcx_obligations(fcx); } // For variadic functions, we don't have a declared type for all of @@ -2988,9 +2987,11 @@ fn check_expr_with_unifier(fcx: &FnCtxt, // 'else' branch. let expected = match expected.only_has_type() { ExpectHasType(ety) => { - match infer::resolve_type(fcx.infcx(), Some(sp), ety, force_tvar) { - Ok(rty) if !ty::type_is_ty_var(rty) => ExpectHasType(rty), - _ => NoExpectation + let ety = fcx.infcx().shallow_resolve(ety); + if !ty::type_is_ty_var(ety) { + ExpectHasType(ety) + } else { + NoExpectation } } _ => NoExpectation @@ -4037,7 +4038,7 @@ fn check_expr_with_unifier(fcx: &FnCtxt, ast::ExprForLoop(ref pat, ref head, ref block, _) => { check_expr(fcx, &**head); let typ = lookup_method_for_for_loop(fcx, &**head, expr.id); - vtable::select_fcx_obligations_where_possible(fcx); + vtable::select_new_fcx_obligations(fcx); let pcx = pat_ctxt { fcx: fcx, @@ -5393,18 +5394,32 @@ pub fn instantiate_path(fcx: &FnCtxt, // Resolves `typ` by a single level if `typ` is a type variable. If no // resolution is possible, then an error is reported. -pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, tp: ty::t) -> ty::t { - match infer::resolve_type(fcx.infcx(), Some(sp), tp, force_tvar) { - Ok(t_s) if !ty::type_is_ty_var(t_s) => t_s, - _ => { - fcx.type_error_message(sp, |_actual| { - "the type of this value must be known in this \ - context".to_string() - }, tp, None); - demand::suptype(fcx, sp, ty::mk_err(), tp); - tp - } +pub fn structurally_resolved_type(fcx: &FnCtxt, sp: Span, mut ty: ty::t) -> ty::t { + // If `ty` is a type variable, see whether we already know what it is. + ty = fcx.infcx().shallow_resolve(ty); + + // If not, try resolve pending fcx obligations. Those can shed light. + // + // FIXME(#18391) -- This current strategy can lead to bad performance in + // extreme cases. We probably ought to smarter in general about + // only resolving when we need help and only resolving obligations + // will actually help. + if ty::type_is_ty_var(ty) { + vtable::select_fcx_obligations_where_possible(fcx); + ty = fcx.infcx().shallow_resolve(ty); } + + // If not, error. + if ty::type_is_ty_var(ty) { + fcx.type_error_message(sp, |_actual| { + "the type of this value must be known in this \ + context".to_string() + }, ty, None); + demand::suptype(fcx, sp, ty::mk_err(), ty); + ty = ty::mk_err(); + } + + ty } // Returns the one-level-deep structure of the given type. diff --git a/src/librustc/middle/typeck/check/vtable.rs b/src/librustc/middle/typeck/check/vtable.rs index d557a2b713b..08a1e95fd90 100644 --- a/src/librustc/middle/typeck/check/vtable.rs +++ b/src/librustc/middle/typeck/check/vtable.rs @@ -339,6 +339,24 @@ pub fn select_fcx_obligations_where_possible(fcx: &FnCtxt) { } } +pub fn select_new_fcx_obligations(fcx: &FnCtxt) { + /*! + * Try to select any fcx obligation that we haven't tried yet, + * in an effort to improve inference. You could just call + * `select_fcx_obligations_where_possible` except that it leads + * to repeated work. + */ + + match + fcx.inh.fulfillment_cx + .borrow_mut() + .select_new_obligations(fcx.infcx(), &fcx.inh.param_env, fcx) + { + Ok(()) => { } + Err(errors) => { report_fulfillment_errors(fcx, &errors); } + } +} + fn note_obligation_cause(fcx: &FnCtxt, obligation: &Obligation) { let tcx = fcx.tcx();