summary refs log tree commit diff
path: root/compiler/rustc_trait_selection/src
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2024-11-04 18:12:48 +0100
committerGitHub <noreply@github.com>2024-11-04 18:12:48 +0100
commita4f323ce9c00a1a51eae998c373103e23b622a7a (patch)
tree0788c16493dc1a71ea5277855be55abb0eaf06af /compiler/rustc_trait_selection/src
parent46ae1555e26d0bc39651cdb249b2ae740d46f3a3 (diff)
parent5a48fe2c202a7aff935daf6b7cc56a12ad0ae72a (diff)
downloadrust-a4f323ce9c00a1a51eae998c373103e23b622a7a.tar.gz
rust-a4f323ce9c00a1a51eae998c373103e23b622a7a.zip
Rollup merge of #132583 - mejrs:tuples, r=compiler-errors
Suggest creating unary tuples when types don't match a trait

When you want to have a variadic function, a common workaround to implement this is to create a trait and then implement that trait for various tuples. For example in `pyo3` there exists
```rust
/// Calls the object with only positional arguments.
pub fn call1(&self, args: impl IntoPy<Py<PyTuple>>) -> PyResult<&PyAny> {
   ...
}
```

with various impls like
```rust
impl<A: IntoPy<PyObject> IntoPy<Py<PyAny>> for (A,)
impl<A: IntoPy<PyObject, B: IntoPy<PyObject> IntoPy<Py<PyAny>> for (A, B)
... etc
```

This means that if you want to call the method with a single item you have to create a unary tuple, like `(x,)`, rather than just `x`.

This PR implements a suggestion to do that, if applicable.
Diffstat (limited to 'compiler/rustc_trait_selection/src')
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs4
-rw-r--r--compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs35
2 files changed, 39 insertions, 0 deletions
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs
index f5cd99222e3..a1c97a38567 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs
@@ -471,6 +471,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
                         }
 
                         self.try_to_add_help_message(
+                            &root_obligation,
                             &obligation,
                             leaf_trait_predicate,
                             &mut err,
@@ -2488,6 +2489,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
 
     fn try_to_add_help_message(
         &self,
+        root_obligation: &PredicateObligation<'tcx>,
         obligation: &PredicateObligation<'tcx>,
         trait_predicate: ty::PolyTraitPredicate<'tcx>,
         err: &mut Diag<'_>,
@@ -2575,6 +2577,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
                 impl_candidates.as_slice(),
                 span,
             );
+
+            self.suggest_tuple_wrapping(err, root_obligation, obligation);
         }
     }
 
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
index e155effa1e3..86fd4c230f6 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs
@@ -4452,6 +4452,41 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
         }
     }
 
+    /// If the type failed selection but the trait is implemented for `(T,)`, suggest that the user
+    /// creates a unary tuple
+    ///
+    /// This is a common gotcha when using libraries that emulate variadic functions with traits for tuples.
+    pub(super) fn suggest_tuple_wrapping(
+        &self,
+        err: &mut Diag<'_>,
+        root_obligation: &PredicateObligation<'tcx>,
+        obligation: &PredicateObligation<'tcx>,
+    ) {
+        let ObligationCauseCode::FunctionArg { arg_hir_id, .. } = obligation.cause.code() else {
+            return;
+        };
+
+        let Some(root_pred) = root_obligation.predicate.as_trait_clause() else { return };
+
+        let trait_ref = root_pred.map_bound(|root_pred| {
+            root_pred
+                .trait_ref
+                .with_self_ty(self.tcx, Ty::new_tup(self.tcx, &[root_pred.trait_ref.self_ty()]))
+        });
+
+        let obligation =
+            Obligation::new(self.tcx, obligation.cause.clone(), obligation.param_env, trait_ref);
+
+        if self.predicate_must_hold_modulo_regions(&obligation) {
+            let arg_span = self.tcx.hir().span(*arg_hir_id);
+            err.multipart_suggestion_verbose(
+                format!("use a unary tuple instead"),
+                vec![(arg_span.shrink_to_lo(), "(".into()), (arg_span.shrink_to_hi(), ",)".into())],
+                Applicability::MaybeIncorrect,
+            );
+        }
+    }
+
     pub(super) fn explain_hrtb_projection(
         &self,
         diag: &mut Diag<'_>,