From 693485373b971d1574388b9da785b66c19dc86d2 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 3 Sep 2022 04:57:21 +0000 Subject: [PATCH] Point out incompatible closure bounds --- .../src/traits/error_reporting/mod.rs | 1 + .../src/traits/error_reporting/suggestions.rs | 67 +++++++++++++++++++ src/test/ui/closures/multiple-fn-bounds.rs | 15 +++++ .../ui/closures/multiple-fn-bounds.stderr | 24 +++++++ 4 files changed, 107 insertions(+) create mode 100644 src/test/ui/closures/multiple-fn-bounds.rs create mode 100644 src/test/ui/closures/multiple-fn-bounds.stderr diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs index 490b0e863225d..4e8baa2dfab6c 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs @@ -1255,6 +1255,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { found_span, found_trait_ref, expected_trait_ref, + obligation.cause.code(), ) } else { let (closure_span, found) = found_did diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs index 980e85b45262d..fda6a2236b195 100644 --- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs +++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs @@ -254,8 +254,15 @@ pub trait TypeErrCtxtExt<'tcx> { found_span: Option, found: ty::PolyTraitRef<'tcx>, expected: ty::PolyTraitRef<'tcx>, + cause: &ObligationCauseCode<'tcx>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed>; + fn note_conflicting_closure_bounds( + &self, + cause: &ObligationCauseCode<'tcx>, + err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ); + fn suggest_fully_qualified_path( &self, err: &mut Diagnostic, @@ -1584,6 +1591,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { found_span: Option, found: ty::PolyTraitRef<'tcx>, expected: ty::PolyTraitRef<'tcx>, + cause: &ObligationCauseCode<'tcx>, ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> { pub(crate) fn build_fn_sig_ty<'tcx>( infcx: &InferCtxt<'tcx>, @@ -1645,9 +1653,68 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> { let signature_kind = format!("{argument_kind} signature"); err.note_expected_found(&signature_kind, expected_str, &signature_kind, found_str); + self.note_conflicting_closure_bounds(cause, &mut err); + err } + // Add a note if there are two `Fn`-family bounds that have conflicting argument + // requirements, which will always cause a closure to have a type error. + fn note_conflicting_closure_bounds( + &self, + cause: &ObligationCauseCode<'tcx>, + err: &mut DiagnosticBuilder<'tcx, ErrorGuaranteed>, + ) { + // First, look for an `ExprBindingObligation`, which means we can get + // the unsubstituted predicate list of the called function. And check + // that the predicate that we failed to satisfy is a `Fn`-like trait. + if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = cause + && let predicates = self.tcx.predicates_of(def_id).instantiate_identity(self.tcx) + && let Some(pred) = predicates.predicates.get(*idx) + && let ty::PredicateKind::Trait(trait_pred) = pred.kind().skip_binder() + && ty::ClosureKind::from_def_id(self.tcx, trait_pred.def_id()).is_some() + { + let expected_self = + self.tcx.anonymize_late_bound_regions(pred.kind().rebind(trait_pred.self_ty())); + let expected_substs = self + .tcx + .anonymize_late_bound_regions(pred.kind().rebind(trait_pred.trait_ref.substs)); + + // Find another predicate whose self-type is equal to the expected self type, + // but whose substs don't match. + let other_pred = std::iter::zip(&predicates.predicates, &predicates.spans) + .enumerate() + .find(|(other_idx, (pred, _))| match pred.kind().skip_binder() { + ty::PredicateKind::Trait(trait_pred) + if ty::ClosureKind::from_def_id(self.tcx, trait_pred.def_id()) + .is_some() + && other_idx != idx + // Make sure that the self type matches + // (i.e. constraining this closure) + && expected_self + == self.tcx.anonymize_late_bound_regions( + pred.kind().rebind(trait_pred.self_ty()), + ) + // But the substs don't match (i.e. incompatible args) + && expected_substs + != self.tcx.anonymize_late_bound_regions( + pred.kind().rebind(trait_pred.trait_ref.substs), + ) => + { + true + } + _ => false, + }); + // If we found one, then it's very likely the cause of the error. + if let Some((_, (_, other_pred_span))) = other_pred { + err.span_note( + *other_pred_span, + "closure inferred to have a different signature due to this bound", + ); + } + } + } + fn suggest_fully_qualified_path( &self, err: &mut Diagnostic, diff --git a/src/test/ui/closures/multiple-fn-bounds.rs b/src/test/ui/closures/multiple-fn-bounds.rs new file mode 100644 index 0000000000000..6bb4098e2bbe5 --- /dev/null +++ b/src/test/ui/closures/multiple-fn-bounds.rs @@ -0,0 +1,15 @@ +fn foo bool + Fn(char) -> bool>(f: F) { + //~^ NOTE required by a bound in `foo` + //~| NOTE required by this bound in `foo` + //~| NOTE closure inferred to have a different signature due to this bound + todo!(); +} + +fn main() { + let v = true; + foo(move |x| v); + //~^ ERROR type mismatch in closure arguments + //~| NOTE expected closure signature + //~| NOTE expected due to this + //~| NOTE found signature defined here +} diff --git a/src/test/ui/closures/multiple-fn-bounds.stderr b/src/test/ui/closures/multiple-fn-bounds.stderr new file mode 100644 index 0000000000000..eefc123fed7ac --- /dev/null +++ b/src/test/ui/closures/multiple-fn-bounds.stderr @@ -0,0 +1,24 @@ +error[E0631]: type mismatch in closure arguments + --> $DIR/multiple-fn-bounds.rs:10:5 + | +LL | foo(move |x| v); + | ^^^ -------- found signature defined here + | | + | expected due to this + | + = note: expected closure signature `fn(char) -> _` + found closure signature `for<'a> fn(&'a char) -> _` +note: closure inferred to have a different signature due to this bound + --> $DIR/multiple-fn-bounds.rs:1:11 + | +LL | fn foo bool + Fn(char) -> bool>(f: F) { + | ^^^^^^^^^^^^^^^^^ +note: required by a bound in `foo` + --> $DIR/multiple-fn-bounds.rs:1:31 + | +LL | fn foo bool + Fn(char) -> bool>(f: F) { + | ^^^^^^^^^^^^^^^^ required by this bound in `foo` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0631`.