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 6014ed555b6..74c37e1bea0 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 @@ -466,6 +466,7 @@ pub fn report_selection_error( } self.try_to_add_help_message( + &root_obligation, &obligation, leaf_trait_predicate, &mut err, @@ -2428,6 +2429,7 @@ fn add_tuple_trait_message( fn try_to_add_help_message( &self, + root_obligation: &PredicateObligation<'tcx>, obligation: &PredicateObligation<'tcx>, trait_predicate: ty::PolyTraitPredicate<'tcx>, err: &mut Diag<'_>, @@ -2511,6 +2513,8 @@ fn try_to_add_help_message( 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 8e0bdce1280..cd8dc4572c2 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/suggestions.rs @@ -4436,6 +4436,41 @@ pub(super) fn suggest_convert_to_slice( } } + /// 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<'_>, diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/do_not_apply_attribute_without_feature_flag.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/do_not_apply_attribute_without_feature_flag.stderr index e56af28f3fb..be17476524a 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/do_not_apply_attribute_without_feature_flag.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/do_not_apply_attribute_without_feature_flag.stderr @@ -15,6 +15,10 @@ note: required by a bound in `check` | LL | fn check(a: impl Foo) {} | ^^^ required by this bound in `check` +help: use a unary tuple instead + | +LL | check(((),)); + | + ++ error: aborting due to 1 previous error diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.current.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.current.stderr index 629fc59361d..ca9a6ebc1c4 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.current.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.current.stderr @@ -12,6 +12,10 @@ note: required by a bound in `check` | LL | fn check(a: impl Foo) {} | ^^^ required by this bound in `check` +help: use a unary tuple instead + | +LL | check(((),)); + | + ++ error: aborting due to 1 previous error diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.next.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.next.stderr index 629fc59361d..ca9a6ebc1c4 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.next.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/supress_suggestions_in_help.next.stderr @@ -12,6 +12,10 @@ note: required by a bound in `check` | LL | fn check(a: impl Foo) {} | ^^^ required by this bound in `check` +help: use a unary tuple instead + | +LL | check(((),)); + | + ++ error: aborting due to 1 previous error diff --git a/tests/ui/layout/rust-call-abi-not-a-tuple-ice-81974.stderr b/tests/ui/layout/rust-call-abi-not-a-tuple-ice-81974.stderr index cceaddf7803..206a6801065 100644 --- a/tests/ui/layout/rust-call-abi-not-a-tuple-ice-81974.stderr +++ b/tests/ui/layout/rust-call-abi-not-a-tuple-ice-81974.stderr @@ -71,6 +71,10 @@ LL | cachedcoso.call_once(1); | note: required by a bound in `call_once` --> $SRC_DIR/core/src/ops/function.rs:LL:COL +help: use a unary tuple instead + | +LL | cachedcoso.call_once((1,)); + | + ++ error: aborting due to 6 previous errors diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap.rs b/tests/ui/on-unimplemented/suggest_tuple_wrap.rs new file mode 100644 index 00000000000..010a47aef62 --- /dev/null +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap.rs @@ -0,0 +1,19 @@ +pub trait Argument {} +impl Argument for u8 {} +impl Argument for i8 {} +impl Argument for String {} +impl Argument for &str {} + +pub trait TupleArgs {} +impl TupleArgs for (A,) {} +impl TupleArgs for (A, B) {} +impl TupleArgs for (A, B, C) {} + +fn convert_into_tuple(_x: impl TupleArgs) {} + +fn main() { + convert_into_tuple(42_u8); + //~^ ERROR E0277 + //~| HELP the following other types implement trait `TupleArgs` + //~| HELP use a unary tuple instead +} diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap.stderr b/tests/ui/on-unimplemented/suggest_tuple_wrap.stderr new file mode 100644 index 00000000000..93dd43aafd9 --- /dev/null +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap.stderr @@ -0,0 +1,25 @@ +error[E0277]: the trait bound `u8: TupleArgs` is not satisfied + --> $DIR/suggest_tuple_wrap.rs:15:24 + | +LL | convert_into_tuple(42_u8); + | ------------------ ^^^^^ the trait `TupleArgs` is not implemented for `u8` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `TupleArgs`: + (A, B) + (A, B, C) + (A,) +note: required by a bound in `convert_into_tuple` + --> $DIR/suggest_tuple_wrap.rs:12:32 + | +LL | fn convert_into_tuple(_x: impl TupleArgs) {} + | ^^^^^^^^^ required by this bound in `convert_into_tuple` +help: use a unary tuple instead + | +LL | convert_into_tuple((42_u8,)); + | + ++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs new file mode 100644 index 00000000000..e0036d30187 --- /dev/null +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.rs @@ -0,0 +1,26 @@ +struct Tuple; + +impl From<(u8,)> for Tuple { + fn from(_: (u8,)) -> Self { + todo!() + } +} +impl From<(u8, u8)> for Tuple { + fn from(_: (u8, u8)) -> Self { + todo!() + } +} +impl From<(u8, u8, u8)> for Tuple { + fn from(_: (u8, u8, u8)) -> Self { + todo!() + } +} + +fn convert_into_tuple(_x: impl Into) {} + +fn main() { + convert_into_tuple(42_u8); + //~^ ERROR E0277 + //~| HELP use a unary tuple instead + //~| HELP the following other types implement trait `From` +} diff --git a/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr new file mode 100644 index 00000000000..6ee08d2cd1b --- /dev/null +++ b/tests/ui/on-unimplemented/suggest_tuple_wrap_root_obligation.stderr @@ -0,0 +1,26 @@ +error[E0277]: the trait bound `Tuple: From` is not satisfied + --> $DIR/suggest_tuple_wrap_root_obligation.rs:22:24 + | +LL | convert_into_tuple(42_u8); + | ------------------ ^^^^^ the trait `From` is not implemented for `Tuple` + | | + | required by a bound introduced by this call + | + = help: the following other types implement trait `From`: + `Tuple` implements `From<(u8, u8)>` + `Tuple` implements `From<(u8, u8, u8)>` + `Tuple` implements `From<(u8,)>` + = note: required for `u8` to implement `Into` +note: required by a bound in `convert_into_tuple` + --> $DIR/suggest_tuple_wrap_root_obligation.rs:19:32 + | +LL | fn convert_into_tuple(_x: impl Into) {} + | ^^^^^^^^^^^ required by this bound in `convert_into_tuple` +help: use a unary tuple instead + | +LL | convert_into_tuple((42_u8,)); + | + ++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/overloaded/overloaded-calls-nontuple.stderr b/tests/ui/overloaded/overloaded-calls-nontuple.stderr index 45a84fc4d7b..22598f3a390 100644 --- a/tests/ui/overloaded/overloaded-calls-nontuple.stderr +++ b/tests/ui/overloaded/overloaded-calls-nontuple.stderr @@ -38,6 +38,10 @@ LL | self.call_mut(z) | note: required by a bound in `call_mut` --> $SRC_DIR/core/src/ops/function.rs:LL:COL +help: use a unary tuple instead + | +LL | self.call_mut((z,)) + | + ++ error[E0059]: cannot use call notation; the first type parameter for the function trait is neither a tuple nor unit --> $DIR/overloaded-calls-nontuple.rs:29:10