From ccaa2f855e34028ff9be745ecc9803e720d34b5e Mon Sep 17 00:00:00 2001 From: Niko Matsakis Date: Tue, 15 Mar 2016 04:49:10 -0400 Subject: [PATCH] Suppress fallback and ambiguity errors If the infcx has observed other errors, then suppress both default type parameter fallback (which can be unreliable, as the full constraint set is not available) and errors related to unresovled variables (annoyingly, integer type variables cannot currently be unified with error, so that has to be a separate mechanism). Also add a flag to `infcx` to allow us to independently indicate when we have observed an error and hence should trigger this suppression mode. --- src/librustc/infer/mod.rs | 51 +++++++++++++++++-- src/librustc/infer/sub.rs | 1 + src/librustc/traits/error_reporting.rs | 6 +++ src/librustc_typeck/astconv.rs | 7 +++ src/librustc_typeck/check/_match.rs | 2 + src/librustc_typeck/check/cast.rs | 21 +++++++- src/librustc_typeck/check/mod.rs | 33 +++++++++++- src/librustc_typeck/collect.rs | 4 ++ src/test/compile-fail/cast-rfc0401-2.rs | 20 ++++++++ src/test/compile-fail/cast-rfc0401.rs | 2 +- .../{ => derived-errors}/issue-30580.rs | 0 .../derived-errors/issue-31997.rs | 27 ++++++++++ src/test/compile-fail/issue-26480.rs | 2 - 13 files changed, 166 insertions(+), 10 deletions(-) create mode 100644 src/test/compile-fail/cast-rfc0401-2.rs rename src/test/compile-fail/{ => derived-errors}/issue-30580.rs (100%) create mode 100644 src/test/compile-fail/derived-errors/issue-31997.rs diff --git a/src/librustc/infer/mod.rs b/src/librustc/infer/mod.rs index 4c1216aa862..c6a329bd64f 100644 --- a/src/librustc/infer/mod.rs +++ b/src/librustc/infer/mod.rs @@ -35,7 +35,7 @@ use ty::fold::{TypeFolder, TypeFoldable}; use ty::relate::{Relate, RelateResult, TypeRelation}; use traits::{self, PredicateObligations, ProjectionMode}; use rustc_data_structures::unify::{self, UnificationTable}; -use std::cell::{RefCell, Ref}; +use std::cell::{Cell, RefCell, Ref}; use std::fmt; use syntax::ast; use syntax::codemap; @@ -110,6 +110,25 @@ pub struct InferCtxt<'a, 'tcx: 'a> { // documentation for `ProjectionMode`. projection_mode: ProjectionMode, + // When an error occurs, we want to avoid reporting "derived" + // errors that are due to this original failure. Normally, we + // handle this with the `err_count_on_creation` count, which + // basically just tracks how many errors were reported when we + // started type-checking a fn and checks to see if any new errors + // have been reported since then. Not great, but it works. + // + // However, when errors originated in other passes -- notably + // resolve -- this heuristic breaks down. Therefore, we have this + // auxiliary flag that one can set whenever one creates a + // type-error that is due to an error in a prior pass. + // + // Don't read this flag directly, call `is_tainted_by_errors()` + // and `set_tainted_by_errors()`. + tainted_by_errors_flag: Cell, + + // Track how many errors were reported when this infcx is created. + // If the number of errors increases, that's also a sign (line + // `tained_by_errors`) to avoid reporting certain kinds of errors. err_count_on_creation: usize, } @@ -379,6 +398,7 @@ pub fn new_infer_ctxt<'a, 'tcx>(tcx: &'a TyCtxt<'tcx>, reported_trait_errors: RefCell::new(FnvHashSet()), normalize: false, projection_mode: projection_mode, + tainted_by_errors_flag: Cell::new(false), err_count_on_creation: tcx.sess.err_count() } } @@ -1128,15 +1148,36 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { .map(|method| resolve_ty(method.ty))) } - pub fn errors_since_creation(&self) -> bool { - self.tcx.sess.err_count() - self.err_count_on_creation != 0 + /// True if errors have been reported since this infcx was + /// created. This is sometimes used as a heuristic to skip + /// reporting errors that often occur as a result of earlier + /// errors, but where it's hard to be 100% sure (e.g., unresolved + /// inference variables, regionck errors). + pub fn is_tainted_by_errors(&self) -> bool { + debug!("is_tainted_by_errors(err_count={}, err_count_on_creation={}, \ + tainted_by_errors_flag={})", + self.tcx.sess.err_count(), + self.err_count_on_creation, + self.tainted_by_errors_flag.get()); + + if self.tcx.sess.err_count() > self.err_count_on_creation { + return true; // errors reported since this infcx was made + } + self.tainted_by_errors_flag.get() + } + + /// Set the "tainted by errors" flag to true. We call this when we + /// observe an error from a prior pass. + pub fn set_tainted_by_errors(&self) { + debug!("set_tainted_by_errors()"); + self.tainted_by_errors_flag.set(true) } pub fn node_type(&self, id: ast::NodeId) -> Ty<'tcx> { match self.tables.borrow().node_types.get(&id) { Some(&t) => t, // FIXME - None if self.errors_since_creation() => + None if self.is_tainted_by_errors() => self.tcx.types.err, None => { bug!("no type for node {}: {} in fcx", @@ -1158,7 +1199,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> { free_regions: &FreeRegionMap, subject_node_id: ast::NodeId) { let errors = self.region_vars.resolve_regions(free_regions, subject_node_id); - if !self.errors_since_creation() { + if !self.is_tainted_by_errors() { // As a heuristic, just skip reporting region errors // altogether if other errors have been reported while // this infcx was in use. This is totally hokey but diff --git a/src/librustc/infer/sub.rs b/src/librustc/infer/sub.rs index ece8c0c696a..71e1efe220f 100644 --- a/src/librustc/infer/sub.rs +++ b/src/librustc/infer/sub.rs @@ -91,6 +91,7 @@ impl<'a, 'tcx> TypeRelation<'a, 'tcx> for Sub<'a, 'tcx> { } (&ty::TyError, _) | (_, &ty::TyError) => { + infcx.set_tainted_by_errors(); Ok(self.tcx().types.err) } diff --git a/src/librustc/traits/error_reporting.rs b/src/librustc/traits/error_reporting.rs index 286733c7c26..44cd05907df 100644 --- a/src/librustc/traits/error_reporting.rs +++ b/src/librustc/traits/error_reporting.rs @@ -609,6 +609,12 @@ pub fn maybe_report_ambiguity<'a, 'tcx>(infcx: &InferCtxt<'a, 'tcx>, predicate, obligation); + // Ambiguity errors are often caused as fallout from earlier + // errors. So just ignore them if this infcx is tainted. + if infcx.is_tainted_by_errors() { + return; + } + match predicate { ty::Predicate::Trait(ref data) => { let trait_ref = data.to_poly_trait_ref(); diff --git a/src/librustc_typeck/astconv.rs b/src/librustc_typeck/astconv.rs index 21122e7095d..3f9f8a45272 100644 --- a/src/librustc_typeck/astconv.rs +++ b/src/librustc_typeck/astconv.rs @@ -154,6 +154,12 @@ pub trait AstConv<'tcx> { _trait_ref: ty::TraitRef<'tcx>, _item_name: ast::Name) -> Ty<'tcx>; + + /// Invoked when we encounter an error from some prior pass + /// (e.g. resolve) that is translated into a ty-error. This is + /// used to help suppress derived errors typeck might otherwise + /// report. + fn set_tainted_by_errors(&self); } pub fn ast_region_to_region(tcx: &TyCtxt, lifetime: &hir::Lifetime) @@ -1532,6 +1538,7 @@ fn base_def_to_ty<'tcx>(this: &AstConv<'tcx>, prim_ty_to_ty(tcx, base_segments, prim_ty) } Def::Err => { + this.set_tainted_by_errors(); return this.tcx().types.err; } _ => { diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs index 8dbd6496b6f..a7a04f4a85f 100644 --- a/src/librustc_typeck/check/_match.rs +++ b/src/librustc_typeck/check/_match.rs @@ -209,6 +209,7 @@ pub fn check_pat<'a, 'tcx>(pcx: &pat_ctxt<'a, 'tcx>, let self_ty = fcx.to_ty(&qself.ty); let path_res = if let Some(&d) = tcx.def_map.borrow().get(&pat.id) { if d.base_def == Def::Err { + fcx.infcx().set_tainted_by_errors(); fcx.write_error(pat.id); return; } @@ -628,6 +629,7 @@ fn check_pat_enum<'a, 'tcx>(pcx: &pat_ctxt<'a, 'tcx>, let path_res = match tcx.def_map.borrow().get(&pat.id) { Some(&path_res) if path_res.base_def != Def::Err => path_res, _ => { + fcx.infcx().set_tainted_by_errors(); fcx.write_error(pat.id); if let Some(subpats) = subpats { diff --git a/src/librustc_typeck/check/cast.rs b/src/librustc_typeck/check/cast.rs index 91cdb8d966d..ea872a92dcf 100644 --- a/src/librustc_typeck/check/cast.rs +++ b/src/librustc_typeck/check/cast.rs @@ -121,8 +121,27 @@ impl<'tcx> CastCheck<'tcx> { } } - fn report_cast_error<'a>(&self, fcx: &FnCtxt<'a, 'tcx>, + fn report_cast_error<'a>(&self, + fcx: &FnCtxt<'a, 'tcx>, e: CastError) { + // As a heuristic, don't report errors if there are unresolved + // inference variables floating around AND we've already + // reported some errors in this fn. It happens often that those + // inference variables are unresolved precisely *because* of + // the errors we've already reported. See #31997. + // + // Note: it's kind of annoying that we need this. Fallback is + // modified to push all unresolved inference variables to + // ty-err, but it's STILL possible to see fallback for + // integral/float variables, because those cannot be unified + // with ty-error. + if + fcx.infcx().is_tainted_by_errors() && + (self.cast_ty.has_infer_types() || self.expr_ty.has_infer_types()) + { + return; + } + match e { CastError::NeedViaPtr | CastError::NeedViaThinPtr | diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index 67b91f7838c..5bc6184263c 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -1240,6 +1240,10 @@ impl<'a, 'tcx> AstConv<'tcx> for FnCtxt<'a, 'tcx> { { self.normalize_associated_type(span, trait_ref, item_name) } + + fn set_tainted_by_errors(&self) { + self.infcx().set_tainted_by_errors() + } } impl<'a, 'tcx> FnCtxt<'a, 'tcx> { @@ -1771,16 +1775,37 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn default_type_parameters(&self) { use rustc::ty::error::UnconstrainedNumeric::Neither; use rustc::ty::error::UnconstrainedNumeric::{UnconstrainedInt, UnconstrainedFloat}; + + // Defaulting inference variables becomes very dubious if we have + // encountered type-checking errors. Therefore, if we think we saw + // some errors in this function, just resolve all uninstanted type + // varibles to TyError. + if self.infcx().is_tainted_by_errors() { + for ty in &self.infcx().unsolved_variables() { + if let ty::TyInfer(_) = self.infcx().shallow_resolve(ty).sty { + debug!("default_type_parameters: defaulting `{:?}` to error", ty); + demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.err); + } + } + return; + } + for ty in &self.infcx().unsolved_variables() { let resolved = self.infcx().resolve_type_vars_if_possible(ty); if self.infcx().type_var_diverges(resolved) { + debug!("default_type_parameters: defaulting `{:?}` to `()` because it diverges", + resolved); demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().mk_nil()); } else { match self.infcx().type_is_unconstrained_numeric(resolved) { UnconstrainedInt => { + debug!("default_type_parameters: defaulting `{:?}` to `i32`", + resolved); demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.i32) }, UnconstrainedFloat => { + debug!("default_type_parameters: defaulting `{:?}` to `f32`", + resolved); demand::eqtype(self, codemap::DUMMY_SP, *ty, self.tcx().types.f64) } Neither => { } @@ -3232,6 +3257,7 @@ fn check_expr_with_expectation_and_lvalue_pref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, // Find the relevant variant let def = lookup_full_def(tcx, path.span, expr.id); if def == Def::Err { + fcx.infcx().set_tainted_by_errors(); check_struct_fields_on_error(fcx, expr.id, fields, base_expr); return; } @@ -3435,6 +3461,7 @@ fn check_expr_with_expectation_and_lvalue_pref<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, expr.span, id); } else { + fcx.infcx().set_tainted_by_errors(); fcx.write_ty(id, fcx.tcx().types.err); } } @@ -4408,8 +4435,12 @@ pub fn instantiate_path<'a, 'tcx>(fcx: &FnCtxt<'a, 'tcx>, Def::ForeignMod(..) | Def::Local(..) | Def::Label(..) | - Def::Upvar(..) | + Def::Upvar(..) => { + segment_spaces = vec![None; segments.len()]; + } + Def::Err => { + fcx.infcx().set_tainted_by_errors(); segment_spaces = vec![None; segments.len()]; } } diff --git a/src/librustc_typeck/collect.rs b/src/librustc_typeck/collect.rs index 2e1a6846843..39465d6ffe3 100644 --- a/src/librustc_typeck/collect.rs +++ b/src/librustc_typeck/collect.rs @@ -383,6 +383,10 @@ impl<'a, 'tcx> AstConv<'tcx> for ItemCtxt<'a, 'tcx> { { self.tcx().mk_projection(trait_ref, item_name) } + + fn set_tainted_by_errors(&self) { + // no obvious place to track this, just let it go + } } /// Interface used to find the bounds on a type parameter from within diff --git a/src/test/compile-fail/cast-rfc0401-2.rs b/src/test/compile-fail/cast-rfc0401-2.rs new file mode 100644 index 00000000000..fdc250f9946 --- /dev/null +++ b/src/test/compile-fail/cast-rfc0401-2.rs @@ -0,0 +1,20 @@ +// Copyright 2015 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// RFC 401 test extracted into distinct file. This is because some the +// change to suppress "derived" errors wound up suppressing this error +// message, since the fallback for `3` doesn't occur. + +fn main() { + let _ = 3 as bool; + //~^ ERROR cannot cast as `bool` + //~| HELP see a detailed explanation + //~| HELP compare with zero +} diff --git a/src/test/compile-fail/cast-rfc0401.rs b/src/test/compile-fail/cast-rfc0401.rs index dcd49e34bb2..fcfb5706e5d 100644 --- a/src/test/compile-fail/cast-rfc0401.rs +++ b/src/test/compile-fail/cast-rfc0401.rs @@ -58,7 +58,7 @@ fn main() let _ = f as *const u8; //~^ ERROR casting //~^^ HELP through a usize first - let _ = 3 as bool; + let _ = 3_i32 as bool; //~^ ERROR cannot cast as `bool` //~^^ HELP compare with zero //~^^^ HELP run `rustc --explain E0054` to see a detailed explanation diff --git a/src/test/compile-fail/issue-30580.rs b/src/test/compile-fail/derived-errors/issue-30580.rs similarity index 100% rename from src/test/compile-fail/issue-30580.rs rename to src/test/compile-fail/derived-errors/issue-30580.rs diff --git a/src/test/compile-fail/derived-errors/issue-31997.rs b/src/test/compile-fail/derived-errors/issue-31997.rs new file mode 100644 index 00000000000..cf283f6d3e4 --- /dev/null +++ b/src/test/compile-fail/derived-errors/issue-31997.rs @@ -0,0 +1,27 @@ +// Copyright 2012 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// Test that the resolve failure does not lead to downstream type errors. +// See issue #31997. + +trait TheTrait { } + +fn closure(x: F) -> Result + where F: FnMut() -> T, T: TheTrait, +{ + unimplemented!() +} + +fn foo() -> Result<(), ()> { + try!(closure(|| bar(0 as *mut _))); //~ ERROR unresolved name `bar` + Ok(()) +} + +fn main() { } diff --git a/src/test/compile-fail/issue-26480.rs b/src/test/compile-fail/issue-26480.rs index 903df42291c..44aff623860 100644 --- a/src/test/compile-fail/issue-26480.rs +++ b/src/test/compile-fail/issue-26480.rs @@ -31,7 +31,6 @@ macro_rules! write { macro_rules! cast { ($x:expr) => ($x as ()) - //~^ ERROR non-scalar cast: `i32` as `()` } fn main() { @@ -40,5 +39,4 @@ fn main() { //~^ NOTE in this expansion of write! cast!(2); - //~^ NOTE in this expansion of cast! }