提交 58dc0a05 编写于 作者: B bors

auto merge of #18386 : nikomatsakis/rust/issue-18208, r=pnkfelix

Avoid O(n^2) performance by reconsidering the full set of obligations only when we are about to report an error (#18208). I found it is still important to consider the full set in order to make tests like `let x: Vec<_> = obligations.iter().collect()` work.

I think we lack the infrastructure to write a regression test for this, but when I did manual testing I found a massive reduction in type-checking time for extreme examples like those found in #18208 vs stage0.

f? @dotdash
......@@ -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());
......
......@@ -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))
}
......
......@@ -101,7 +101,6 @@
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.
......
......@@ -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();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册