Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When possible point at argument causing item obligation failure #64498

Merged
merged 8 commits into from
Sep 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
estebank marked this conversation as resolved.
Show resolved Hide resolved
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,
estebank marked this conversation as resolved.
Show resolved Hide resolved
}

#[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 @@ -885,12 +885,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 @@ -2356,7 +2356,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 @@ -2807,7 +2807,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 @@ -2844,8 +2844,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 @@ -3253,6 +3258,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
formal_tys.clone()
};

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is going to regress happy-path compile-time perf? (I don't expect by much but still...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no way of doing this without collecting the resolved types for the general case (there's a lazy way to do it that would only work with fully resolved types in the arguments).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sure, just pointing out that there is a cost to this, which may be totally acceptable. :)


// 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 @@ -3265,7 +3272,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(
estebank marked this conversation as resolved.
Show resolved Hide resolved
errors,
&final_arg_types[..],
sp,
&args,
);
})
}

// For C-variadic functions, we don't have a declared type for all of
Expand Down Expand Up @@ -3311,6 +3325,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 @@ -3357,6 +3372,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 @@ -3514,8 +3567,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 @@ -4639,7 +4695,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