about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/mod.rs1
-rw-r--r--compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs67
-rw-r--r--src/test/ui/closures/multiple-fn-bounds.rs15
-rw-r--r--src/test/ui/closures/multiple-fn-bounds.stderr24
4 files changed, 107 insertions, 0 deletions
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 490b0e86322..4e8baa2dfab 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 980e85b4526..fda6a2236b1 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<Span>,
         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<Span>,
         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 00000000000..6bb4098e2bb
--- /dev/null
+++ b/src/test/ui/closures/multiple-fn-bounds.rs
@@ -0,0 +1,15 @@
+fn foo<F: Fn(&char) -> 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 00000000000..eefc123fed7
--- /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<F: Fn(&char) -> bool + Fn(char) -> bool>(f: F) {
+   |           ^^^^^^^^^^^^^^^^^
+note: required by a bound in `foo`
+  --> $DIR/multiple-fn-bounds.rs:1:31
+   |
+LL | fn foo<F: Fn(&char) -> 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`.