Skip to content

Commit

Permalink
Auto merge of #64498 - estebank:point-at-arg, r=Centril
Browse files Browse the repository at this point in the history
When possible point at argument causing item obligation failure

Fix #41781, fix #42855, fix #46658, fix #48099, fix #63143.
  • Loading branch information
bors committed Sep 20, 2019
2 parents ea3ba36 + c34d9e6 commit 7225264
Show file tree
Hide file tree
Showing 67 changed files with 499 additions and 371 deletions.
3 changes: 3 additions & 0 deletions src/librustc/traits/chalk_fulfill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
.map(|obligation| FulfillmentError {
obligation: obligation.goal.clone(),
code: FulfillmentErrorCode::CodeAmbiguity,
points_at_arg_span: false,
})
.collect();
Err(errors)
Expand Down Expand Up @@ -129,6 +130,7 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
code: FulfillmentErrorCode::CodeSelectionError(
SelectionError::Unimplemented
),
points_at_arg_span: false,
}),
}
} else {
Expand All @@ -142,6 +144,7 @@ impl TraitEngine<'tcx> for FulfillmentContext<'tcx> {
code: FulfillmentErrorCode::CodeSelectionError(
SelectionError::Unimplemented
),
points_at_arg_span: false,
})
}
}
Expand Down
74 changes: 51 additions & 23 deletions src/librustc/traits/error_reporting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {

// returns if `cond` not occurring implies that `error` does not occur - i.e., that
// `error` occurring implies that `cond` occurs.
fn error_implies(&self,
cond: &ty::Predicate<'tcx>,
error: &ty::Predicate<'tcx>)
-> bool
{
fn error_implies(
&self,
cond: &ty::Predicate<'tcx>,
error: &ty::Predicate<'tcx>,
) -> bool {
if cond == error {
return true
}
Expand Down Expand Up @@ -155,13 +155,21 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
false
}

fn report_fulfillment_error(&self, error: &FulfillmentError<'tcx>,
body_id: Option<hir::BodyId>,
fallback_has_occurred: bool) {
fn report_fulfillment_error(
&self,
error: &FulfillmentError<'tcx>,
body_id: Option<hir::BodyId>,
fallback_has_occurred: bool,
) {
debug!("report_fulfillment_errors({:?})", error);
match error.code {
FulfillmentErrorCode::CodeSelectionError(ref e) => {
self.report_selection_error(&error.obligation, e, fallback_has_occurred);
FulfillmentErrorCode::CodeSelectionError(ref selection_error) => {
self.report_selection_error(
&error.obligation,
selection_error,
fallback_has_occurred,
error.points_at_arg_span,
);
}
FulfillmentErrorCode::CodeProjectionError(ref e) => {
self.report_projection_error(&error.obligation, e);
Expand All @@ -170,19 +178,21 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
self.maybe_report_ambiguity(&error.obligation, body_id);
}
FulfillmentErrorCode::CodeSubtypeError(ref expected_found, ref err) => {
self.report_mismatched_types(&error.obligation.cause,
expected_found.expected,
expected_found.found,
err.clone())
.emit();
self.report_mismatched_types(
&error.obligation.cause,
expected_found.expected,
expected_found.found,
err.clone(),
).emit();
}
}
}

fn report_projection_error(&self,
obligation: &PredicateObligation<'tcx>,
error: &MismatchedProjectionTypes<'tcx>)
{
fn report_projection_error(
&self,
obligation: &PredicateObligation<'tcx>,
error: &MismatchedProjectionTypes<'tcx>,
) {
let predicate =
self.resolve_vars_if_possible(&obligation.predicate);

Expand Down Expand Up @@ -603,6 +613,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
obligation: &PredicateObligation<'tcx>,
error: &SelectionError<'tcx>,
fallback_has_occurred: bool,
points_at_arg: bool,
) {
let span = obligation.cause.span;

Expand Down Expand Up @@ -690,7 +701,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
}

self.suggest_borrow_on_unsized_slice(&obligation.cause.code, &mut err);
self.suggest_fn_call(&obligation, &mut err, &trait_ref);
self.suggest_fn_call(&obligation, &mut err, &trait_ref, points_at_arg);
self.suggest_remove_reference(&obligation, &mut err, &trait_ref);
self.suggest_semicolon_removal(&obligation, &mut err, span, &trait_ref);

Expand Down Expand Up @@ -963,6 +974,7 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
obligation: &PredicateObligation<'tcx>,
err: &mut DiagnosticBuilder<'tcx>,
trait_ref: &ty::Binder<ty::TraitRef<'tcx>>,
points_at_arg: bool,
) {
let self_ty = trait_ref.self_ty();
match self_ty.sty {
Expand Down Expand Up @@ -991,15 +1003,31 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
..
})) = self.tcx.hir().get_if_local(def_id) {
let body = self.tcx.hir().body(*body_id);
err.help(&format!(
"use parentheses to call the function: `{}({})`",
let msg = "use parentheses to call the function";
let snippet = format!(
"{}({})",
ident,
body.params.iter()
.map(|arg| match &arg.pat.node {
hir::PatKind::Binding(_, _, ident, None)
if ident.name != kw::SelfLower => ident.to_string(),
_ => "_".to_string(),
}).collect::<Vec<_>>().join(", ")));
}).collect::<Vec<_>>().join(", "),
);
// When the obligation error has been ensured to have been caused by
// an argument, the `obligation.cause.span` points at the expression
// of the argument, so we can provide a suggestion. This is signaled
// by `points_at_arg`. Otherwise, we give a more general note.
if points_at_arg {
err.span_suggestion(
obligation.cause.span,
msg,
snippet,
Applicability::HasPlaceholders,
);
} else {
err.help(&format!("{}: `{}`", msg, snippet));
}
}
}
_ => {}
Expand Down
8 changes: 6 additions & 2 deletions src/librustc/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,11 @@ EnumTypeFoldableImpl! {

pub struct FulfillmentError<'tcx> {
pub obligation: PredicateObligation<'tcx>,
pub code: FulfillmentErrorCode<'tcx>
pub code: FulfillmentErrorCode<'tcx>,
/// Diagnostics only: we opportunistically change the `code.span` when we encounter an
/// obligation error caused by a call argument. When this is the case, we also signal that in
/// this field to ensure accuracy of suggestions.
pub points_at_arg_span: bool,
}

#[derive(Clone)]
Expand Down Expand Up @@ -1183,7 +1187,7 @@ impl<'tcx> FulfillmentError<'tcx> {
code: FulfillmentErrorCode<'tcx>)
-> FulfillmentError<'tcx>
{
FulfillmentError { obligation: obligation, code: code }
FulfillmentError { obligation: obligation, code: code, points_at_arg_span: false }
}
}

Expand Down
1 change: 1 addition & 0 deletions src/librustc_mir/borrow_check/nll/type_check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1999,6 +1999,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> {
),
&traits::SelectionError::Unimplemented,
false,
false,
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ impl<'f, 'tcx> Coerce<'f, 'tcx> {

// Object safety violations or miscellaneous.
Err(err) => {
self.report_selection_error(&obligation, &err, false);
self.report_selection_error(&obligation, &err, false, false);
// Treat this like an obligation and follow through
// with the unsizing - the lack of a coercion should
// be silent, as it causes a type mismatch later.
Expand Down
77 changes: 67 additions & 10 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,12 +912,12 @@ fn typeck_tables_of(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::TypeckTables<'_> {
};

// All type checking constraints were added, try to fallback unsolved variables.
fcx.select_obligations_where_possible(false);
fcx.select_obligations_where_possible(false, |_| {});
let mut fallback_has_occurred = false;
for ty in &fcx.unsolved_variables() {
fallback_has_occurred |= fcx.fallback_if_possible(ty);
}
fcx.select_obligations_where_possible(fallback_has_occurred);
fcx.select_obligations_where_possible(fallback_has_occurred, |_| {});

// Even though coercion casts provide type hints, we check casts after fallback for
// backwards compatibility. This makes fallback a stronger type hint than a cast coercion.
Expand Down Expand Up @@ -2391,7 +2391,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// possible. This can help substantially when there are
// indirect dependencies that don't seem worth tracking
// precisely.
self.select_obligations_where_possible(false);
self.select_obligations_where_possible(false, |_| {});
ty = self.resolve_vars_if_possible(&ty);

debug!("resolve_type_vars_with_obligations: ty={:?}", ty);
Expand Down Expand Up @@ -2842,7 +2842,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn resolve_generator_interiors(&self, def_id: DefId) {
let mut generators = self.deferred_generator_interiors.borrow_mut();
for (body_id, interior, kind) in generators.drain(..) {
self.select_obligations_where_possible(false);
self.select_obligations_where_possible(false, |_| {});
generator_interior::resolve_interior(self, def_id, body_id, interior, kind);
}
}
Expand Down Expand Up @@ -2879,8 +2879,13 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}

/// Select as many obligations as we can at present.
fn select_obligations_where_possible(&self, fallback_has_occurred: bool) {
if let Err(errors) = self.fulfillment_cx.borrow_mut().select_where_possible(self) {
fn select_obligations_where_possible(
&self,
fallback_has_occurred: bool,
mutate_fullfillment_errors: impl Fn(&mut Vec<traits::FulfillmentError<'tcx>>),
) {
if let Err(mut errors) = self.fulfillment_cx.borrow_mut().select_where_possible(self) {
mutate_fullfillment_errors(&mut errors);
self.report_fulfillment_errors(&errors, self.inh.body_id, fallback_has_occurred);
}
}
Expand Down Expand Up @@ -3288,6 +3293,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
formal_tys.clone()
};

let mut final_arg_types: Vec<(usize, Ty<'_>)> = vec![];

// Check the arguments.
// We do this in a pretty awful way: first we type-check any arguments
// that are not closures, then we type-check the closures. This is so
Expand All @@ -3300,7 +3307,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// an "opportunistic" vtable resolution of any trait bounds on
// the call. This helps coercions.
if check_closures {
self.select_obligations_where_possible(false);
self.select_obligations_where_possible(false, |errors| {
self.point_at_arg_instead_of_call_if_possible(
errors,
&final_arg_types[..],
sp,
&args,
);
})
}

// For C-variadic functions, we don't have a declared type for all of
Expand Down Expand Up @@ -3346,6 +3360,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
// We're processing function arguments so we definitely want to use
// two-phase borrows.
self.demand_coerce(&arg, checked_ty, coerce_ty, AllowTwoPhase::Yes);
final_arg_types.push((i, coerce_ty));

// 3. Relate the expected type and the formal one,
// if the expected type was used for the coercion.
Expand Down Expand Up @@ -3392,6 +3407,44 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
vec![self.tcx.types.err; len]
}

/// Given a vec of evaluated `FullfillmentError`s and an `fn` call argument expressions, we
/// walk the resolved types for each argument to see if any of the `FullfillmentError`s
/// reference a type argument. If they do, and there's only *one* argument that does, we point
/// at the corresponding argument's expression span instead of the `fn` call path span.
fn point_at_arg_instead_of_call_if_possible(
&self,
errors: &mut Vec<traits::FulfillmentError<'_>>,
final_arg_types: &[(usize, Ty<'tcx>)],
call_sp: Span,
args: &'tcx [hir::Expr],
) {
if !call_sp.desugaring_kind().is_some() {
// We *do not* do this for desugared call spans to keep good diagnostics when involving
// the `?` operator.
for error in errors {
if let ty::Predicate::Trait(predicate) = error.obligation.predicate {
// Collect the argument position for all arguments that could have caused this
// `FullfillmentError`.
let mut referenced_in = final_arg_types.iter()
.flat_map(|(i, ty)| {
let ty = self.resolve_vars_if_possible(ty);
// We walk the argument type because the argument's type could have
// been `Option<T>`, but the `FullfillmentError` references `T`.
ty.walk()
.filter(|&ty| ty == predicate.skip_binder().self_ty())
.map(move |_| *i)
});
if let (Some(ref_in), None) = (referenced_in.next(), referenced_in.next()) {
// We make sure that only *one* argument matches the obligation failure
// and thet the obligation's span to its expression's.
error.obligation.cause.span = args[ref_in].span;
error.points_at_arg_span = true;
}
}
}
}
}

// AST fragment checking
fn check_lit(&self,
lit: &hir::Lit,
Expand Down Expand Up @@ -3549,8 +3602,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// Check bounds on type arguments used in the path.
let bounds = self.instantiate_bounds(path_span, did, substs);
let cause = traits::ObligationCause::new(path_span, self.body_id,
traits::ItemObligation(did));
let cause = traits::ObligationCause::new(
path_span,
self.body_id,
traits::ItemObligation(did),
);
self.add_obligations_for_parameters(cause, &bounds);

Some((variant, ty))
Expand Down Expand Up @@ -4674,7 +4730,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
let bounds = self.instantiate_bounds(span, def_id, &substs);
self.add_obligations_for_parameters(
traits::ObligationCause::new(span, self.body_id, traits::ItemObligation(def_id)),
&bounds);
&bounds,
);

// Substitute the values for the type parameters into the type of
// the referenced item.
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/op.rs
Original file line number Diff line number Diff line change
Expand Up @@ -724,7 +724,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
match method {
Some(ok) => {
let method = self.register_infer_ok_obligations(ok);
self.select_obligations_where_possible(false);
self.select_obligations_where_possible(false, |_| {});

Ok(method)
}
Expand Down
10 changes: 5 additions & 5 deletions src/librustc_typeck/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,7 @@ fn check_where_clauses<'tcx, 'fcx>(
});

// Now we build the substituted predicates.
let default_obligations = predicates.predicates.iter().flat_map(|&(pred, _)| {
let default_obligations = predicates.predicates.iter().flat_map(|&(pred, sp)| {
#[derive(Default)]
struct CountParams { params: FxHashSet<u32> }
impl<'tcx> ty::fold::TypeVisitor<'tcx> for CountParams {
Expand Down Expand Up @@ -539,9 +539,9 @@ fn check_where_clauses<'tcx, 'fcx>(
// Avoid duplication of predicates that contain no parameters, for example.
None
} else {
Some(substituted_pred)
Some((substituted_pred, sp))
}
}).map(|pred| {
}).map(|(pred, sp)| {
// Convert each of those into an obligation. So if you have
// something like `struct Foo<T: Copy = String>`, we would
// take that predicate `T: Copy`, substitute to `String: Copy`
Expand All @@ -551,8 +551,8 @@ fn check_where_clauses<'tcx, 'fcx>(
// Note the subtle difference from how we handle `predicates`
// below: there, we are not trying to prove those predicates
// to be *true* but merely *well-formed*.
let pred = fcx.normalize_associated_types_in(span, &pred);
let cause = traits::ObligationCause::new(span, fcx.body_id, traits::ItemObligation(def_id));
let pred = fcx.normalize_associated_types_in(sp, &pred);
let cause = traits::ObligationCause::new(sp, fcx.body_id, traits::ItemObligation(def_id));
traits::Obligation::new(cause, fcx.param_env, pred)
});

Expand Down
Loading

0 comments on commit 7225264

Please sign in to comment.