diff options
| author | Esteban Küber <esteban@kuber.com.ar> | 2025-02-18 20:29:10 +0000 |
|---|---|---|
| committer | Esteban Küber <esteban@kuber.com.ar> | 2025-02-20 18:15:39 +0000 |
| commit | e565eeed78d0e59e475dce34b0833bbf8f84871b (patch) | |
| tree | 1c290b3048e8afdd32dc25ab62fbaf08cbc9eb73 | |
| parent | 28b83ee59698ae069f5355b8e03f976406f410f5 (diff) | |
| download | rust-e565eeed78d0e59e475dce34b0833bbf8f84871b.tar.gz rust-e565eeed78d0e59e475dce34b0833bbf8f84871b.zip | |
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:5: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`
|
= 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`.
11 files changed, 103 insertions, 65 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 a8ee4d61e65..133d8a88de3 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 @@ -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 { @@ -2035,6 +2064,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(..)) => { diff --git a/tests/ui/async-await/issue-84841.stderr b/tests/ui/async-await/issue-84841.stderr index 69c1c882d60..0d008477310 100644 --- a/tests/ui/async-await/issue-84841.stderr +++ b/tests/ui/async-await/issue-84841.stderr @@ -17,8 +17,6 @@ LL | | test()?; ... | LL | | } | |_- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual<_>` is not implemented for `()` error: aborting due to 2 previous errors diff --git a/tests/ui/async-await/try-on-option-in-async.stderr b/tests/ui/async-await/try-on-option-in-async.stderr index 9e0bb42a697..332f4d4ec0c 100644 --- a/tests/ui/async-await/try-on-option-in-async.stderr +++ b/tests/ui/async-await/try-on-option-in-async.stderr @@ -6,8 +6,6 @@ LL | async { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in an async block that returns `{integer}` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `{integer}` error[E0277]: the `?` operator can only be used in an async closure that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-in-async.rs:16:10 @@ -20,8 +18,6 @@ LL | | x?; LL | | 22_u32 LL | | }; | |_____- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32` error[E0277]: the `?` operator can only be used in an async function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-in-async.rs:25:6 @@ -34,8 +30,6 @@ LL | | x?; LL | | 22 LL | | } | |_- this function should return `Result` or `Option` to accept `?` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32` error: aborting due to 3 previous errors diff --git a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr index e22f33fd242..877995dfe12 100644 --- a/tests/ui/return/return-from-residual-sugg-issue-125997.stderr +++ b/tests/ui/return/return-from-residual-sugg-issue-125997.stderr @@ -6,7 +6,6 @@ LL | fn test1() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn test1() -> Result<(), Box<dyn std::error::Error>> { @@ -23,7 +22,6 @@ LL | fn test2() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn test2() -> Result<(), Box<dyn std::error::Error>> { @@ -41,7 +39,6 @@ LL | fn test4(&self) { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a method that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn test4(&self) -> Result<(), Box<dyn std::error::Error>> { @@ -59,7 +56,6 @@ LL | fn test5(&self) { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a method that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn test5(&self) -> Result<(), Box<dyn std::error::Error>> { @@ -78,7 +74,6 @@ LL | fn main() { LL | let mut _file = File::create("foo.txt")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn main() -> Result<(), Box<dyn std::error::Error>> { @@ -99,7 +94,6 @@ LL | let mut _file = File::create("foo.txt")?; LL | mac!(); | ------ in this macro invocation | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding return type | diff --git a/tests/ui/try-trait/bad-interconversion.stderr b/tests/ui/try-trait/bad-interconversion.stderr index 45422be946e..6df05747f32 100644 --- a/tests/ui/try-trait/bad-interconversion.stderr +++ b/tests/ui/try-trait/bad-interconversion.stderr @@ -20,9 +20,6 @@ LL | fn option_to_result() -> Result<u64, String> { | -------------------------------------------- this function returns a `Result` LL | Some(3)?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result<u64, String>` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<u64, String>` - = help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>` error[E0277]: the `?` operator can only be used on `Result`s in a function that returns `Result` --> $DIR/bad-interconversion.rs:15:31 @@ -31,9 +28,6 @@ LL | fn control_flow_to_result() -> Result<u64, String> { | -------------------------------------------------- this function returns a `Result` LL | Ok(ControlFlow::Break(123)?) | ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Result<u64, String>` - | - = help: the trait `FromResidual<ControlFlow<{integer}, Infallible>>` is not implemented for `Result<u64, String>` - = help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>` error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option` --> $DIR/bad-interconversion.rs:20:22 @@ -42,9 +36,6 @@ LL | fn result_to_option() -> Option<u16> { | ------------------------------------ this function returns an `Option` LL | Some(Err("hello")?) | ^ use `.ok()?` if you want to discard the `Result<Infallible, &str>` error information - | - = help: the trait `FromResidual<Result<Infallible, &str>>` is not implemented for `Option<u16>` - = help: the trait `FromResidual<Option<Infallible>>` is implemented for `Option<T>` error[E0277]: the `?` operator can only be used on `Option`s in a function that returns `Option` --> $DIR/bad-interconversion.rs:25:33 @@ -53,9 +44,6 @@ LL | fn control_flow_to_option() -> Option<u64> { | ------------------------------------------ this function returns an `Option` LL | Some(ControlFlow::Break(123)?) | ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Option<u64>` - | - = help: the trait `FromResidual<ControlFlow<{integer}, Infallible>>` is not implemented for `Option<u64>` - = help: the trait `FromResidual<Option<Infallible>>` is implemented for `Option<T>` error[E0277]: the `?` operator can only be used on `ControlFlow`s in a function that returns `ControlFlow` --> $DIR/bad-interconversion.rs:30:39 diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.rs b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs new file mode 100644 index 00000000000..f319610cb3a --- /dev/null +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.rs @@ -0,0 +1,26 @@ +struct E; +struct X; + +fn foo() -> Result<(), Box<dyn std::error::Error>> { //~ NOTE required `E: std::error::Error` because of this + Ok(bar()?) + //~^ ERROR `?` couldn't convert the error: `E: std::error::Error` is not satisfied + //~| NOTE the trait `std::error::Error` is not implemented for `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>` + //~| NOTE in this expansion + //~| NOTE in this expansion + //~| NOTE in this expansion +} +fn bat() -> Result<(), X> { //~ NOTE expected `X` because of this + Ok(bar()?) + //~^ ERROR `?` couldn't convert the error to `X` + //~| NOTE the trait `From<E>` is not implemented for `X` + //~| NOTE this can't be annotated with `?` because it has type `Result<_, E>` + //~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + //~| NOTE in this expansion + //~| NOTE in this expansion +} +fn bar() -> Result<(), E> { + Err(E) +} +fn main() {} diff --git a/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr new file mode 100644 index 00000000000..4e7cd1e2889 --- /dev/null +++ b/tests/ui/try-trait/bad-question-mark-on-trait-object.stderr @@ -0,0 +1,26 @@ +error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied + --> $DIR/bad-question-mark-on-trait-object.rs:5: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` + | + = 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>` + +error[E0277]: `?` couldn't convert the error to `X` + --> $DIR/bad-question-mark-on-trait-object.rs:15:13 + | +LL | fn bat() -> Result<(), X> { + | ------------- expected `X` because of this +LL | Ok(bar()?) + | -----^ the trait `From<E>` is not implemented for `X` + | | + | this can't be annotated with `?` because it has type `Result<_, E>` + | + = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/try-trait/option-to-result.stderr b/tests/ui/try-trait/option-to-result.stderr index 1a5a925f92f..8a4c4707942 100644 --- a/tests/ui/try-trait/option-to-result.stderr +++ b/tests/ui/try-trait/option-to-result.stderr @@ -6,9 +6,6 @@ LL | fn test_result() -> Result<(),()> { LL | let a:Option<()> = Some(()); LL | a?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result<(), ()>` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<(), ()>` - = help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>` error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option` --> $DIR/option-to-result.rs:11:6 @@ -18,9 +15,6 @@ LL | fn test_option() -> Option<i32>{ LL | let a:Result<i32, i32> = Ok(5); LL | a?; | ^ use `.ok()?` if you want to discard the `Result<Infallible, i32>` error information - | - = help: the trait `FromResidual<Result<Infallible, i32>>` is not implemented for `Option<i32>` - = help: the trait `FromResidual<Option<Infallible>>` is implemented for `Option<T>` error: aborting due to 2 previous errors diff --git a/tests/ui/try-trait/try-on-option-diagnostics.stderr b/tests/ui/try-trait/try-on-option-diagnostics.stderr index 9ee540c79fd..08675e242a3 100644 --- a/tests/ui/try-trait/try-on-option-diagnostics.stderr +++ b/tests/ui/try-trait/try-on-option-diagnostics.stderr @@ -6,8 +6,6 @@ LL | fn a_function() -> u32 { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in a function that returns `u32` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32` error[E0277]: the `?` operator can only be used in a closure that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:14:10 @@ -17,8 +15,6 @@ LL | let a_closure = || { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in a closure that returns `{integer}` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `{integer}` error[E0277]: the `?` operator can only be used in a method that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:26:14 @@ -28,8 +24,6 @@ LL | fn a_method() { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in a method that returns `()` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `()` error[E0277]: the `?` operator can only be used in a trait method that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option-diagnostics.rs:39:14 @@ -39,8 +33,6 @@ LL | fn a_trait_method() { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in a trait method that returns `()` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `()` error: aborting due to 4 previous errors diff --git a/tests/ui/try-trait/try-on-option.stderr b/tests/ui/try-trait/try-on-option.stderr index 15d0b28ddc1..aeb519086d8 100644 --- a/tests/ui/try-trait/try-on-option.stderr +++ b/tests/ui/try-trait/try-on-option.stderr @@ -6,9 +6,6 @@ LL | fn foo() -> Result<u32, ()> { LL | let x: Option<u32> = None; LL | x?; | ^ use `.ok_or(...)?` to provide an error compatible with `Result<u32, ()>` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<u32, ()>` - = help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>` error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) --> $DIR/try-on-option.rs:11:6 @@ -18,8 +15,6 @@ LL | fn bar() -> u32 { LL | let x: Option<u32> = None; LL | x?; | ^ cannot use the `?` operator in a function that returns `u32` - | - = help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32` error: aborting due to 2 previous errors diff --git a/tests/ui/try-trait/try-operator-on-main.stderr b/tests/ui/try-trait/try-operator-on-main.stderr index 311e8076ed4..9c2526442ab 100644 --- a/tests/ui/try-trait/try-operator-on-main.stderr +++ b/tests/ui/try-trait/try-operator-on-main.stderr @@ -7,7 +7,6 @@ LL | // error for a `Try` type on a non-`Try` fn LL | std::fs::File::open("foo")?; | ^ cannot use the `?` operator in a function that returns `()` | - = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()` help: consider adding return type | LL ~ fn main() -> Result<(), Box<dyn std::error::Error>> { @@ -33,8 +32,6 @@ LL | fn main() { ... LL | ()?; | ^ cannot use the `?` operator in a function that returns `()` - | - = help: the trait `FromResidual<_>` is not implemented for `()` error[E0277]: the trait bound `(): Try` is not satisfied --> $DIR/try-operator-on-main.rs:14:25 |
