]> git.lizzy.rs Git - rust.git/commitdiff
Avoid O(n^2) performance by reconsidering the full set of obligations only when we...
authorNiko Matsakis <niko@alum.mit.edu>
Tue, 28 Oct 2014 11:13:15 +0000 (07:13 -0400)
committerNiko Matsakis <niko@alum.mit.edu>
Tue, 28 Oct 2014 12:18:21 +0000 (08:18 -0400)
src/librustc/middle/traits/fulfill.rs
src/librustc/middle/typeck/check/method.rs
src/librustc/middle/typeck/check/mod.rs
src/librustc/middle/typeck/check/vtable.rs

index c0caa1d7c79fb638968938fefa552091cb8613a7..6baf4e6c048903f9b517b4933e9f37a936f3588b 100644 (file)
@@ -35,12 +35,18 @@ pub struct FulfillmentContext {
     // A list of all obligations that have been registered with this
     // fulfillment context.
     trait_obligations: Vec<Obligation>,
+
+    // 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 @@ pub fn select_all_or_error<'a,'tcx>(&mut self,
         }
     }
 
+    pub fn select_new_obligations<'a,'tcx>(&mut self,
+                                           infcx: &InferCtxt<'a,'tcx>,
+                                           param_env: &ty::ParameterEnvironment,
+                                           typer: &Typer<'tcx>)
+                                           -> Result<(),Vec<FulfillmentError>>
+    {
+        /*!
+         * 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<FulfillmentError>>
     {
-        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<FulfillmentError>>
+    {
+        /*!
+         * 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 @@ pub fn select_where_possible<'a,'tcx>(&mut self,
 
             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 @@ pub fn select_where_possible<'a,'tcx>(&mut self,
             }
         }
 
-        debug!("select_where_possible({} obligations, {} errors) done",
+        debug!("select({} obligations, {} errors) done",
                self.trait_obligations.len(),
                errors.len());
 
index f8604eeb5c69c8c48c8e81c172cd7283e953b60a..82a3d1a523d3979ab6cc878ba46364900d5caf7c 100644 (file)
@@ -88,7 +88,7 @@ trait `ToString` imported, and I call `to_string()` on a value of type `T`,
 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 @@ fn consider_extension_candidates(&self, rcvr_ty: ty::t)
         // 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))
     }
index 5f6795f24c1e9bf6617a76a8b2c82d9e4274ab31..53a4a8141e65c489e25d33de4aedd29a01c37328 100644 (file)
 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_then_else(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_struct_fields_on_error(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 @@ fn adjust_region_parameters(
 
 // 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.
index d557a2b713be7127a3169de9ba05facef2fe4ce4..08a1e95fd90a1fd7aea5aa1a72c7487d59443b38 100644 (file)
@@ -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();