Rollup merge of #137245 - estebank:from-residual-note-2, r=oli-obk

Tweak E0277 when predicate comes indirectly from ?

When a `?` operation requires an `Into` conversion with additional bounds (like having a concrete error but wanting to convert to a trait object), we handle it speficically and provide the same kind of information we give other `?` related errors.

```
error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied
  --> $DIR/bad-question-mark-on-trait-object.rs:7:13
   |
LL | fn foo() -> Result<(), Box<dyn std::error::Error>> {
   |             -------------------------------------- required `E: std::error::Error` because of this
LL |     Ok(bar()?)
   |        -----^ the trait `std::error::Error` is not implemented for `E`
   |        |
   |        this has type `Result<_, E>`
   |
note: `E` needs to implement `std::error::Error`
  --> $DIR/bad-question-mark-on-trait-object.rs:1:1
   |
LL | struct E;
   | ^^^^^^^^
   = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
   = note: required for `Box<dyn std::error::Error>` to implement `From<E>`
```

Avoid talking about `FromResidual` when other more relevant information is being given, particularly from `rust_on_unimplemented`.

Fix #137238.

-----

CC #137232, which was a smaller step related to this.
This commit is contained in:
Matthias Krüger 2025-02-22 01:01:41 +01:00 committed by GitHub
commit 890c4d2e26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 175 additions and 71 deletions

View file

@ -192,19 +192,38 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let have_alt_message = message.is_some() || label.is_some();
let is_try_conversion = self.is_try_conversion(span, main_trait_predicate.def_id());
let is_question_mark = matches!(
root_obligation.cause.code().peel_derives(),
ObligationCauseCode::QuestionMark,
) && !(
self.tcx.is_diagnostic_item(sym::FromResidual, main_trait_predicate.def_id())
|| self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::Try)
);
let is_unsize =
self.tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Unsize);
let question_mark_message = "the question mark operation (`?`) implicitly \
performs a conversion on the error value \
using the `From` trait";
let (message, notes, append_const_msg) = if is_try_conversion {
// We have a `-> Result<_, E1>` and `gives_E2()?`.
(
Some(format!(
"`?` couldn't convert the error to `{}`",
main_trait_predicate.skip_binder().self_ty(),
)),
vec![
"the question mark operation (`?`) implicitly performs a \
conversion on the error value using the `From` trait"
.to_owned(),
],
vec![question_mark_message.to_owned()],
Some(AppendConstMessage::Default),
)
} else if is_question_mark {
// Similar to the case above, but in this case the conversion is for a
// trait object: `-> Result<_, Box<dyn Error>` and `gives_E()?` when
// `E: Error` isn't met.
(
Some(format!(
"`?` couldn't convert the error: `{main_trait_predicate}` is \
not satisfied",
)),
vec![question_mark_message.to_owned()],
Some(AppendConstMessage::Default),
)
} else {
@ -220,8 +239,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
&mut long_ty_file,
);
let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::TransmuteTrait)
{
let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item(
main_trait_predicate.def_id(),
LangItem::TransmuteTrait,
) {
// Recompute the safe transmute reason and use that for the error reporting
match self.get_safe_transmute_error_and_reason(
obligation.clone(),
@ -249,18 +270,22 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
*err.long_ty_path() = long_ty_file;
let mut suggested = false;
if is_try_conversion {
if is_try_conversion || is_question_mark {
suggested = self.try_conversion_context(&obligation, main_trait_predicate, &mut err);
}
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
err.span_label(
ret_span,
format!(
"expected `{}` because of this",
main_trait_predicate.skip_binder().self_ty()
),
);
if let Some(ret_span) = self.return_type_span(&obligation) {
if is_try_conversion {
err.span_label(
ret_span,
format!(
"expected `{}` because of this",
main_trait_predicate.skip_binder().self_ty()
),
);
} else if is_question_mark {
err.span_label(ret_span, format!("required `{main_trait_predicate}` because of this"));
}
}
if tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Tuple) {
@ -302,10 +327,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
// If it has a custom `#[rustc_on_unimplemented]`
// error message, let's display it as the label!
err.span_label(span, s);
if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) {
if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_))
// When the self type is a type param We don't need to "the trait
// `std::marker::Sized` is not implemented for `T`" as we will point
// at the type param with a label to suggest constraining it.
&& !self.tcx.is_diagnostic_item(sym::FromResidual, leaf_trait_predicate.def_id())
// Don't say "the trait `FromResidual<Option<Infallible>>` is
// not implemented for `Result<T, E>`".
{
err.help(explanation);
}
} else if let Some(custom_explanation) = safe_transmute_explanation {
@ -932,16 +961,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
let Some(typeck) = &self.typeck_results else {
return false;
};
let Some((ObligationCauseCode::QuestionMark, Some(y))) =
obligation.cause.code().parent_with_predicate()
else {
let ObligationCauseCode::QuestionMark = obligation.cause.code().peel_derives() else {
return false;
};
if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) {
return false;
}
let self_ty = trait_pred.skip_binder().self_ty();
let found_ty = trait_pred.skip_binder().trait_ref.args.get(1).and_then(|a| a.as_type());
self.note_missing_impl_for_question_mark(err, self_ty, found_ty, trait_pred);
let mut prev_ty = self.resolve_vars_if_possible(
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
@ -1106,6 +1131,56 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
suggested
}
fn note_missing_impl_for_question_mark(
&self,
err: &mut Diag<'_>,
self_ty: Ty<'_>,
found_ty: Option<Ty<'_>>,
trait_pred: ty::PolyTraitPredicate<'tcx>,
) {
match (self_ty.kind(), found_ty) {
(ty::Adt(def, _), Some(ty))
if let ty::Adt(found, _) = ty.kind()
&& def.did().is_local()
&& found.did().is_local() =>
{
err.span_note(
self.tcx.def_span(def.did()),
format!("`{self_ty}` needs to implement `From<{ty}>`"),
);
err.span_note(
self.tcx.def_span(found.did()),
format!("alternatively, `{ty}` needs to implement `Into<{self_ty}>`"),
);
}
(ty::Adt(def, _), None) if def.did().is_local() => {
err.span_note(
self.tcx.def_span(def.did()),
format!(
"`{self_ty}` needs to implement `{}`",
trait_pred.skip_binder().trait_ref.print_only_trait_path(),
),
);
}
(ty::Adt(def, _), Some(ty)) if def.did().is_local() => {
err.span_note(
self.tcx.def_span(def.did()),
format!("`{self_ty}` needs to implement `From<{ty}>`"),
);
}
(_, Some(ty))
if let ty::Adt(def, _) = ty.kind()
&& def.did().is_local() =>
{
err.span_note(
self.tcx.def_span(def.did()),
format!("`{ty}` needs to implement `Into<{self_ty}>`"),
);
}
_ => {}
}
}
fn report_const_param_not_wf(
&self,
ty: Ty<'tcx>,
@ -2035,6 +2110,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
return false;
}
if let &[cand] = &candidates[..] {
if self.tcx.is_diagnostic_item(sym::FromResidual, cand.def_id)
&& !self.tcx.features().enabled(sym::try_trait_v2)
{
return false;
}
let (desc, mention_castable) =
match (cand.self_ty().kind(), trait_pred.self_ty().skip_binder().kind()) {
(ty::FnPtr(..), ty::FnDef(..)) => {