cleanup error reporting and add ui tests

This commit is contained in:
Niko Matsakis 2016-10-05 10:17:14 -04:00
parent e77cc9c983
commit bd5fa7532d
15 changed files with 229 additions and 56 deletions

View File

@ -245,6 +245,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
debug!("report_region_errors: {} errors after preprocessing", errors.len());
for error in errors {
debug!("report_region_errors: error = {:?}", error);
match error.clone() {
ConcreteFailure(origin, sub, sup) => {
self.report_concrete_failure(origin, sub, sup).emit();
@ -299,44 +300,58 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
let mut bound_failures = Vec::new();
for error in errors {
// Check whether we can process this error into some other
// form; if not, fall through.
match *error {
ConcreteFailure(ref origin, sub, sup) => {
debug!("processing ConcreteFailure");
match free_regions_from_same_fn(self.tcx, sub, sup) {
Some(ref same_frs) => {
if let SubregionOrigin::CompareImplMethodObligation { .. } = *origin {
// When comparing an impl method against a
// trait method, it is not helpful to suggest
// changes to the impl method. This is
// because the impl method signature is being
// checked using the trait's environment, so
// usually the changes we suggest would
// actually have to be applied to the *trait*
// method (and it's not clear that the trait
// method is even under the user's control).
} else if let Some(same_frs) = free_regions_from_same_fn(self.tcx, sub, sup) {
origins.push(
ProcessedErrorOrigin::ConcreteFailure(
origin.clone(),
sub,
sup));
append_to_same_regions(&mut same_regions, same_frs);
}
_ => {
other_errors.push(error.clone());
append_to_same_regions(&mut same_regions, &same_frs);
continue;
}
}
}
SubSupConflict(ref var_origin, _, sub_r, _, sup_r) => {
debug!("processing SubSupConflict sub: {:?} sup: {:?}", sub_r, sup_r);
match free_regions_from_same_fn(self.tcx, sub_r, sup_r) {
Some(ref same_frs) => {
SubSupConflict(ref var_origin, ref sub_origin, sub, ref sup_origin, sup) => {
debug!("processing SubSupConflict sub: {:?} sup: {:?}", sub, sup);
if let SubregionOrigin::CompareImplMethodObligation { .. } = *sub_origin {
// As above, when comparing an impl method
// against a trait method, it is not helpful
// to suggest changes to the impl method.
} else if let SubregionOrigin::CompareImplMethodObligation { .. } = *sup_origin {
// See above.
} else if let Some(same_frs) = free_regions_from_same_fn(self.tcx, sub, sup) {
origins.push(
ProcessedErrorOrigin::VariableFailure(
var_origin.clone()));
append_to_same_regions(&mut same_regions, same_frs);
}
None => {
other_errors.push(error.clone());
}
append_to_same_regions(&mut same_regions, &same_frs);
continue;
}
}
GenericBoundFailure(ref origin, ref kind, region) => {
bound_failures.push((origin.clone(), kind.clone(), region));
continue;
}
ProcessedErrors(..) => {
bug!("should not encounter a `ProcessedErrors` yet: {:?}", error)
}
}
// No changes to this error.
other_errors.push(error.clone());
}
// ok, let's pull together the errors, sorted in an order that
@ -630,6 +645,18 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
format!("the associated type `{}`", p),
};
if let SubregionOrigin::CompareImplMethodObligation {
span, item_name, impl_item_def_id, trait_item_def_id
} = origin {
self.report_extra_impl_obligation(span,
item_name,
impl_item_def_id,
trait_item_def_id,
&format!("`{}: {}`", bound_kind, sub))
.emit();
return;
}
let mut err = match *sub {
ty::ReFree(ty::FreeRegion {bound_region: ty::BrNamed(..), ..}) => {
// Does the required lifetime have a nice name we can print?
@ -947,6 +974,16 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
"");
err
}
infer::CompareImplMethodObligation { span,
item_name,
impl_item_def_id,
trait_item_def_id } => {
self.report_extra_impl_obligation(span,
item_name,
impl_item_def_id,
trait_item_def_id,
&format!("`{}: {}`", sup, sub))
}
}
}
@ -1792,6 +1829,11 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
"...so that references are valid when the destructor \
runs");
}
infer::CompareImplMethodObligation { span, .. } => {
err.span_note(
span,
"...so that the definition in impl matches the definition from the trait");
}
}
}
}

View File

@ -360,6 +360,15 @@ pub enum SubregionOrigin<'tcx> {
// Region constraint arriving from destructor safety
SafeDestructor(Span),
// Comparing the signature and requirements of an impl method against
// the containing trait.
CompareImplMethodObligation {
span: Span,
item_name: ast::Name,
impl_item_def_id: DefId,
trait_item_def_id: DefId,
},
}
/// Places that type/region parameters can appear.
@ -1152,16 +1161,16 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
}
pub fn region_outlives_predicate(&self,
span: Span,
cause: &traits::ObligationCause<'tcx>,
predicate: &ty::PolyRegionOutlivesPredicate<'tcx>)
-> UnitResult<'tcx>
{
self.commit_if_ok(|snapshot| {
let (ty::OutlivesPredicate(r_a, r_b), skol_map) =
self.skolemize_late_bound_regions(predicate, snapshot);
let origin = RelateRegionParamBound(span);
let origin = SubregionOrigin::from_cause(cause, || RelateRegionParamBound(cause.span));
self.sub_regions(origin, r_b, r_a); // `b : a` ==> `a <= b`
self.leak_check(false, span, &skol_map, snapshot)?;
self.leak_check(false, cause.span, &skol_map, snapshot)?;
Ok(self.pop_skolemized(skol_map, snapshot))
})
}
@ -1792,6 +1801,30 @@ impl<'tcx> SubregionOrigin<'tcx> {
AddrOf(a) => a,
AutoBorrow(a) => a,
SafeDestructor(a) => a,
CompareImplMethodObligation { span, .. } => span,
}
}
pub fn from_cause<F>(cause: &traits::ObligationCause<'tcx>,
default: F)
-> Self
where F: FnOnce() -> Self
{
match cause.code {
traits::ObligationCauseCode::ReferenceOutlivesReferent(ref_type) =>
SubregionOrigin::ReferenceOutlivesReferent(ref_type, cause.span),
traits::ObligationCauseCode::CompareImplMethodObligation { item_name,
impl_item_def_id,
trait_item_def_id } =>
SubregionOrigin::CompareImplMethodObligation {
span: cause.span,
item_name: item_name,
impl_item_def_id: impl_item_def_id,
trait_item_def_id: trait_item_def_id,
},
_ => default(),
}
}
}

View File

@ -36,6 +36,7 @@ use util::nodemap::{FnvHashMap, FnvHashSet};
use std::cmp;
use std::fmt;
use syntax::ast;
use syntax_pos::Span;
use errors::DiagnosticBuilder;
@ -417,6 +418,32 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
self.report_overflow_error(&cycle[0], false);
}
pub fn report_extra_impl_obligation(&self,
error_span: Span,
item_name: ast::Name,
_impl_item_def_id: DefId,
trait_item_def_id: DefId,
requirement: &fmt::Display)
-> DiagnosticBuilder<'tcx>
{
let mut err =
struct_span_err!(self.tcx.sess,
error_span,
E0276,
"impl has stricter requirements than trait");
if let Some(trait_item_span) = self.tcx.map.span_if_local(trait_item_def_id) {
err.span_label(trait_item_span,
&format!("definition of `{}` from trait", item_name));
}
err.span_label(
error_span,
&format!("impl has extra requirement {}", requirement));
err
}
pub fn report_selection_error(&self,
obligation: &PredicateObligation<'tcx>,
error: &SelectionError<'tcx>)
@ -424,12 +451,16 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
let span = obligation.cause.span;
let mut err = match *error {
SelectionError::Unimplemented => {
if let ObligationCauseCode::CompareImplMethodObligation = obligation.cause.code {
span_err!(
self.tcx.sess, span, E0276,
"the requirement `{}` appears on the impl \
method but not on the corresponding trait method",
obligation.predicate);
if let ObligationCauseCode::CompareImplMethodObligation {
item_name, impl_item_def_id, trait_item_def_id
} = obligation.cause.code {
self.report_extra_impl_obligation(
span,
item_name,
impl_item_def_id,
trait_item_def_id,
&format!("`{}`", obligation.predicate))
.emit();
return;
} else {
match obligation.predicate {
@ -492,7 +523,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
ty::Predicate::RegionOutlives(ref predicate) => {
let predicate = self.resolve_type_vars_if_possible(predicate);
let err = self.region_outlives_predicate(span,
let err = self.region_outlives_predicate(&obligation.cause,
&predicate).err().unwrap();
struct_span_err!(self.tcx.sess, span, E0279,
"the requirement `{}` is not satisfied (`{}`)",
@ -886,7 +917,7 @@ impl<'a, 'gcx, 'tcx> InferCtxt<'a, 'gcx, 'tcx> {
&parent_predicate,
&data.parent_code);
}
ObligationCauseCode::CompareImplMethodObligation => {
ObligationCauseCode::CompareImplMethodObligation { .. } => {
err.note(
&format!("the requirement `{}` appears on the impl method \
but not on the corresponding trait method",

View File

@ -526,7 +526,7 @@ fn process_predicate<'a, 'gcx, 'tcx>(
}
ty::Predicate::RegionOutlives(ref binder) => {
match selcx.infcx().region_outlives_predicate(obligation.cause.span, binder) {
match selcx.infcx().region_outlives_predicate(&obligation.cause, binder) {
Ok(()) => Ok(Some(Vec::new())),
Err(_) => Err(CodeSelectionError(Unimplemented)),
}

View File

@ -138,7 +138,11 @@ pub enum ObligationCauseCode<'tcx> {
ImplDerivedObligation(DerivedObligationCause<'tcx>),
CompareImplMethodObligation,
CompareImplMethodObligation {
item_name: ast::Name,
impl_item_def_id: DefId,
trait_item_def_id: DefId
},
}
#[derive(Clone, Debug, PartialEq, Eq)]

View File

@ -195,8 +195,14 @@ impl<'a, 'tcx> Lift<'tcx> for traits::ObligationCauseCode<'a> {
super::ImplDerivedObligation(ref cause) => {
tcx.lift(cause).map(super::ImplDerivedObligation)
}
super::CompareImplMethodObligation => {
Some(super::CompareImplMethodObligation)
super::CompareImplMethodObligation { item_name,
impl_item_def_id,
trait_item_def_id } => {
Some(super::CompareImplMethodObligation {
item_name: item_name,
impl_item_def_id: impl_item_def_id,
trait_item_def_id: trait_item_def_id,
})
}
}
}
@ -459,7 +465,7 @@ impl<'tcx> TypeFoldable<'tcx> for traits::ObligationCauseCode<'tcx> {
super::FieldSized |
super::ConstSized |
super::SharedStatic |
super::CompareImplMethodObligation => self.clone(),
super::CompareImplMethodObligation { .. } => self.clone(),
super::ProjectionWf(proj) => super::ProjectionWf(proj.fold_with(folder)),
super::ReferenceOutlivesReferent(ty) => {
@ -492,7 +498,7 @@ impl<'tcx> TypeFoldable<'tcx> for traits::ObligationCauseCode<'tcx> {
super::FieldSized |
super::ConstSized |
super::SharedStatic |
super::CompareImplMethodObligation => false,
super::CompareImplMethodObligation { .. } => false,
super::ProjectionWf(proj) => proj.visit_with(visitor),
super::ReferenceOutlivesReferent(ty) => ty.visit_with(visitor),

View File

@ -363,7 +363,11 @@ pub fn compare_impl_method<'a, 'tcx>(ccx: &CrateCtxt<'a, 'tcx>,
let cause = traits::ObligationCause {
span: impl_m_span,
body_id: impl_m_body_id,
code: traits::ObligationCauseCode::CompareImplMethodObligation,
code: traits::ObligationCauseCode::CompareImplMethodObligation {
item_name: impl_m.name,
impl_item_def_id: impl_m.def_id,
trait_item_def_id: trait_m.def_id,
},
};
fulfillment_cx.borrow_mut().register_predicate_obligation(

View File

@ -356,7 +356,7 @@ impl<'a, 'gcx, 'tcx> RegionCtxt<'a, 'gcx, 'tcx> {
debug!("visit_region_obligations: r_o={:?} cause={:?}",
r_o, r_o.cause);
let sup_type = self.resolve_type(r_o.sup_type);
let origin = self.code_to_origin(r_o.cause.span, sup_type, &r_o.cause.code);
let origin = self.code_to_origin(&r_o.cause, sup_type);
self.type_must_outlive(origin, sup_type, r_o.sub_region);
}
@ -366,16 +366,10 @@ impl<'a, 'gcx, 'tcx> RegionCtxt<'a, 'gcx, 'tcx> {
}
fn code_to_origin(&self,
span: Span,
sup_type: Ty<'tcx>,
code: &traits::ObligationCauseCode<'tcx>)
cause: &traits::ObligationCause<'tcx>,
sup_type: Ty<'tcx>)
-> SubregionOrigin<'tcx> {
match *code {
traits::ObligationCauseCode::ReferenceOutlivesReferent(ref_type) =>
infer::ReferenceOutlivesReferent(ref_type, span),
_ =>
infer::RelateParamBound(span, sup_type),
}
SubregionOrigin::from_cause(cause, || infer::RelateParamBound(cause.span, sup_type))
}
/// This method populates the region map's `free_region_map`. It walks over the transformed

View File

@ -18,8 +18,7 @@ trait Master<'a, T: ?Sized, U> {
// `U::Item: 'a` does not imply that `U: 'a`
impl<'a, U: Iterator> Master<'a, U::Item, U> for () {
fn foo() where U: 'a { }
//~^ ERROR parameter type `V` may not live long enough
fn foo() where U: 'a { } //~ ERROR E0276
}
fn main() {

View File

@ -0,0 +1,11 @@
error[E0276]: impl has stricter requirements than trait
--> $DIR/proj-outlives-region.rs:21:5
|
16 | fn foo() where T: 'a;
| --------------------- definition of `foo` from trait
...
21 | fn foo() where U: 'a { } //~ ERROR E0276
| ^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `U: 'a`
error: aborting due to previous error

View File

@ -0,0 +1,11 @@
error[E0276]: impl has stricter requirements than trait
--> $DIR/region-unrelated.rs:21:5
|
16 | fn foo() where T: 'a;
| --------------------- definition of `foo` from trait
...
21 | fn foo() where V: 'a { }
| ^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `V: 'a`
error: aborting due to previous error

View File

@ -0,0 +1,27 @@
// Copyright 2016 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.
#![allow(dead_code)]
// Test that we elaborate `Type: 'region` constraints and infer various important things.
trait Master<'a, 'b> {
fn foo();
}
// `U: 'a` does not imply `V: 'a`
impl<'a, 'b> Master<'a, 'b> for () {
fn foo() where 'a: 'b { }
//~^ ERROR parameter type `V` may not live long enough
}
fn main() {
println!("Hello, world!");
}

View File

@ -0,0 +1,11 @@
error[E0276]: impl has stricter requirements than trait
--> $DIR/region.rs:21:5
|
16 | fn foo();
| --------- definition of `foo` from trait
...
21 | fn foo() where 'a: 'b { }
| ^^^^^^^^^^^^^^^^^^^^^^^^^ impl has extra requirement `'a: 'b`
error: aborting due to previous error

View File

@ -36,12 +36,12 @@ while [[ "$1" != "" ]]; do
STDOUT_NAME="${1/%.rs/.stdout}"
shift
if [ -f $BUILD_DIR/$STDOUT_NAME ] && \
! (diff $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME > /dev/null); then
! (diff $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME >& /dev/null); then
echo updating $MYDIR/$STDOUT_NAME
cp $BUILD_DIR/$STDOUT_NAME $MYDIR/$STDOUT_NAME
fi
if [ -f $BUILD_DIR/$STDERR_NAME ] && \
! (diff $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME > /dev/null); then
! (diff $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME >& /dev/null); then
echo updating $MYDIR/$STDERR_NAME
cp $BUILD_DIR/$STDERR_NAME $MYDIR/$STDERR_NAME
fi