Skip to content

Commit 890c4d2

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#137238. ----- CC rust-lang#137232, which was a smaller step related to this.
2 parents 5400270 + 31febc6 commit 890c4d2

11 files changed

+175
-71
lines changed

compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs

+103-23
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,38 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
192192

193193
let have_alt_message = message.is_some() || label.is_some();
194194
let is_try_conversion = self.is_try_conversion(span, main_trait_predicate.def_id());
195+
let is_question_mark = matches!(
196+
root_obligation.cause.code().peel_derives(),
197+
ObligationCauseCode::QuestionMark,
198+
) && !(
199+
self.tcx.is_diagnostic_item(sym::FromResidual, main_trait_predicate.def_id())
200+
|| self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::Try)
201+
);
195202
let is_unsize =
196203
self.tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Unsize);
204+
let question_mark_message = "the question mark operation (`?`) implicitly \
205+
performs a conversion on the error value \
206+
using the `From` trait";
197207
let (message, notes, append_const_msg) = if is_try_conversion {
208+
// We have a `-> Result<_, E1>` and `gives_E2()?`.
198209
(
199210
Some(format!(
200211
"`?` couldn't convert the error to `{}`",
201212
main_trait_predicate.skip_binder().self_ty(),
202213
)),
203-
vec![
204-
"the question mark operation (`?`) implicitly performs a \
205-
conversion on the error value using the `From` trait"
206-
.to_owned(),
207-
],
214+
vec![question_mark_message.to_owned()],
215+
Some(AppendConstMessage::Default),
216+
)
217+
} else if is_question_mark {
218+
// Similar to the case above, but in this case the conversion is for a
219+
// trait object: `-> Result<_, Box<dyn Error>` and `gives_E()?` when
220+
// `E: Error` isn't met.
221+
(
222+
Some(format!(
223+
"`?` couldn't convert the error: `{main_trait_predicate}` is \
224+
not satisfied",
225+
)),
226+
vec![question_mark_message.to_owned()],
208227
Some(AppendConstMessage::Default),
209228
)
210229
} else {
@@ -220,8 +239,10 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
220239
&mut long_ty_file,
221240
);
222241

223-
let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item(main_trait_predicate.def_id(), LangItem::TransmuteTrait)
224-
{
242+
let (err_msg, safe_transmute_explanation) = if self.tcx.is_lang_item(
243+
main_trait_predicate.def_id(),
244+
LangItem::TransmuteTrait,
245+
) {
225246
// Recompute the safe transmute reason and use that for the error reporting
226247
match self.get_safe_transmute_error_and_reason(
227248
obligation.clone(),
@@ -249,18 +270,22 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
249270
*err.long_ty_path() = long_ty_file;
250271

251272
let mut suggested = false;
252-
if is_try_conversion {
273+
if is_try_conversion || is_question_mark {
253274
suggested = self.try_conversion_context(&obligation, main_trait_predicate, &mut err);
254275
}
255276

256-
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
257-
err.span_label(
258-
ret_span,
259-
format!(
260-
"expected `{}` because of this",
261-
main_trait_predicate.skip_binder().self_ty()
262-
),
263-
);
277+
if let Some(ret_span) = self.return_type_span(&obligation) {
278+
if is_try_conversion {
279+
err.span_label(
280+
ret_span,
281+
format!(
282+
"expected `{}` because of this",
283+
main_trait_predicate.skip_binder().self_ty()
284+
),
285+
);
286+
} else if is_question_mark {
287+
err.span_label(ret_span, format!("required `{main_trait_predicate}` because of this"));
288+
}
264289
}
265290

266291
if tcx.is_lang_item(leaf_trait_predicate.def_id(), LangItem::Tuple) {
@@ -302,10 +327,14 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
302327
// If it has a custom `#[rustc_on_unimplemented]`
303328
// error message, let's display it as the label!
304329
err.span_label(span, s);
305-
if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_)) {
330+
if !matches!(leaf_trait_predicate.skip_binder().self_ty().kind(), ty::Param(_))
306331
// When the self type is a type param We don't need to "the trait
307332
// `std::marker::Sized` is not implemented for `T`" as we will point
308333
// at the type param with a label to suggest constraining it.
334+
&& !self.tcx.is_diagnostic_item(sym::FromResidual, leaf_trait_predicate.def_id())
335+
// Don't say "the trait `FromResidual<Option<Infallible>>` is
336+
// not implemented for `Result<T, E>`".
337+
{
309338
err.help(explanation);
310339
}
311340
} else if let Some(custom_explanation) = safe_transmute_explanation {
@@ -932,16 +961,12 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
932961
let Some(typeck) = &self.typeck_results else {
933962
return false;
934963
};
935-
let Some((ObligationCauseCode::QuestionMark, Some(y))) =
936-
obligation.cause.code().parent_with_predicate()
937-
else {
964+
let ObligationCauseCode::QuestionMark = obligation.cause.code().peel_derives() else {
938965
return false;
939966
};
940-
if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) {
941-
return false;
942-
}
943967
let self_ty = trait_pred.skip_binder().self_ty();
944968
let found_ty = trait_pred.skip_binder().trait_ref.args.get(1).and_then(|a| a.as_type());
969+
self.note_missing_impl_for_question_mark(err, self_ty, found_ty, trait_pred);
945970

946971
let mut prev_ty = self.resolve_vars_if_possible(
947972
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
@@ -1106,6 +1131,56 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
11061131
suggested
11071132
}
11081133

1134+
fn note_missing_impl_for_question_mark(
1135+
&self,
1136+
err: &mut Diag<'_>,
1137+
self_ty: Ty<'_>,
1138+
found_ty: Option<Ty<'_>>,
1139+
trait_pred: ty::PolyTraitPredicate<'tcx>,
1140+
) {
1141+
match (self_ty.kind(), found_ty) {
1142+
(ty::Adt(def, _), Some(ty))
1143+
if let ty::Adt(found, _) = ty.kind()
1144+
&& def.did().is_local()
1145+
&& found.did().is_local() =>
1146+
{
1147+
err.span_note(
1148+
self.tcx.def_span(def.did()),
1149+
format!("`{self_ty}` needs to implement `From<{ty}>`"),
1150+
);
1151+
err.span_note(
1152+
self.tcx.def_span(found.did()),
1153+
format!("alternatively, `{ty}` needs to implement `Into<{self_ty}>`"),
1154+
);
1155+
}
1156+
(ty::Adt(def, _), None) if def.did().is_local() => {
1157+
err.span_note(
1158+
self.tcx.def_span(def.did()),
1159+
format!(
1160+
"`{self_ty}` needs to implement `{}`",
1161+
trait_pred.skip_binder().trait_ref.print_only_trait_path(),
1162+
),
1163+
);
1164+
}
1165+
(ty::Adt(def, _), Some(ty)) if def.did().is_local() => {
1166+
err.span_note(
1167+
self.tcx.def_span(def.did()),
1168+
format!("`{self_ty}` needs to implement `From<{ty}>`"),
1169+
);
1170+
}
1171+
(_, Some(ty))
1172+
if let ty::Adt(def, _) = ty.kind()
1173+
&& def.did().is_local() =>
1174+
{
1175+
err.span_note(
1176+
self.tcx.def_span(def.did()),
1177+
format!("`{ty}` needs to implement `Into<{self_ty}>`"),
1178+
);
1179+
}
1180+
_ => {}
1181+
}
1182+
}
1183+
11091184
fn report_const_param_not_wf(
11101185
&self,
11111186
ty: Ty<'tcx>,
@@ -2035,6 +2110,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
20352110
return false;
20362111
}
20372112
if let &[cand] = &candidates[..] {
2113+
if self.tcx.is_diagnostic_item(sym::FromResidual, cand.def_id)
2114+
&& !self.tcx.features().enabled(sym::try_trait_v2)
2115+
{
2116+
return false;
2117+
}
20382118
let (desc, mention_castable) =
20392119
match (cand.self_ty().kind(), trait_pred.self_ty().skip_binder().kind()) {
20402120
(ty::FnPtr(..), ty::FnDef(..)) => {

tests/ui/async-await/issue-84841.stderr

-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ LL | | test()?;
1717
... |
1818
LL | | }
1919
| |_- this function should return `Result` or `Option` to accept `?`
20-
|
21-
= help: the trait `FromResidual<_>` is not implemented for `()`
2220

2321
error: aborting due to 2 previous errors
2422

tests/ui/async-await/try-on-option-in-async.stderr

-6
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ LL | async {
66
LL | let x: Option<u32> = None;
77
LL | x?;
88
| ^ cannot use the `?` operator in an async block that returns `{integer}`
9-
|
10-
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `{integer}`
119

1210
error[E0277]: the `?` operator can only be used in an async closure that returns `Result` or `Option` (or another type that implements `FromResidual`)
1311
--> $DIR/try-on-option-in-async.rs:16:10
@@ -20,8 +18,6 @@ LL | | x?;
2018
LL | | 22_u32
2119
LL | | };
2220
| |_____- this function should return `Result` or `Option` to accept `?`
23-
|
24-
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32`
2521

2622
error[E0277]: the `?` operator can only be used in an async function that returns `Result` or `Option` (or another type that implements `FromResidual`)
2723
--> $DIR/try-on-option-in-async.rs:25:6
@@ -34,8 +30,6 @@ LL | | x?;
3430
LL | | 22
3531
LL | | }
3632
| |_- this function should return `Result` or `Option` to accept `?`
37-
|
38-
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `u32`
3933

4034
error: aborting due to 3 previous errors
4135

tests/ui/return/return-from-residual-sugg-issue-125997.stderr

-6
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ LL | fn test1() {
66
LL | let mut _file = File::create("foo.txt")?;
77
| ^ cannot use the `?` operator in a function that returns `()`
88
|
9-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
109
help: consider adding return type
1110
|
1211
LL ~ fn test1() -> Result<(), Box<dyn std::error::Error>> {
@@ -23,7 +22,6 @@ LL | fn test2() {
2322
LL | let mut _file = File::create("foo.txt")?;
2423
| ^ cannot use the `?` operator in a function that returns `()`
2524
|
26-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
2725
help: consider adding return type
2826
|
2927
LL ~ fn test2() -> Result<(), Box<dyn std::error::Error>> {
@@ -41,7 +39,6 @@ LL | fn test4(&self) {
4139
LL | let mut _file = File::create("foo.txt")?;
4240
| ^ cannot use the `?` operator in a method that returns `()`
4341
|
44-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
4542
help: consider adding return type
4643
|
4744
LL ~ fn test4(&self) -> Result<(), Box<dyn std::error::Error>> {
@@ -59,7 +56,6 @@ LL | fn test5(&self) {
5956
LL | let mut _file = File::create("foo.txt")?;
6057
| ^ cannot use the `?` operator in a method that returns `()`
6158
|
62-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
6359
help: consider adding return type
6460
|
6561
LL ~ fn test5(&self) -> Result<(), Box<dyn std::error::Error>> {
@@ -78,7 +74,6 @@ LL | fn main() {
7874
LL | let mut _file = File::create("foo.txt")?;
7975
| ^ cannot use the `?` operator in a function that returns `()`
8076
|
81-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
8277
help: consider adding return type
8378
|
8479
LL ~ fn main() -> Result<(), Box<dyn std::error::Error>> {
@@ -99,7 +94,6 @@ LL | let mut _file = File::create("foo.txt")?;
9994
LL | mac!();
10095
| ------ in this macro invocation
10196
|
102-
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
10397
= note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info)
10498
help: consider adding return type
10599
|

tests/ui/try-trait/bad-interconversion.stderr

-12
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ LL | fn option_to_result() -> Result<u64, String> {
2020
| -------------------------------------------- this function returns a `Result`
2121
LL | Some(3)?;
2222
| ^ use `.ok_or(...)?` to provide an error compatible with `Result<u64, String>`
23-
|
24-
= help: the trait `FromResidual<Option<Infallible>>` is not implemented for `Result<u64, String>`
25-
= help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>`
2623

2724
error[E0277]: the `?` operator can only be used on `Result`s in a function that returns `Result`
2825
--> $DIR/bad-interconversion.rs:15:31
@@ -31,9 +28,6 @@ LL | fn control_flow_to_result() -> Result<u64, String> {
3128
| -------------------------------------------------- this function returns a `Result`
3229
LL | Ok(ControlFlow::Break(123)?)
3330
| ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Result<u64, String>`
34-
|
35-
= help: the trait `FromResidual<ControlFlow<{integer}, Infallible>>` is not implemented for `Result<u64, String>`
36-
= help: the trait `FromResidual<Result<Infallible, E>>` is implemented for `Result<T, F>`
3731

3832
error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option`
3933
--> $DIR/bad-interconversion.rs:20:22
@@ -42,9 +36,6 @@ LL | fn result_to_option() -> Option<u16> {
4236
| ------------------------------------ this function returns an `Option`
4337
LL | Some(Err("hello")?)
4438
| ^ use `.ok()?` if you want to discard the `Result<Infallible, &str>` error information
45-
|
46-
= help: the trait `FromResidual<Result<Infallible, &str>>` is not implemented for `Option<u16>`
47-
= help: the trait `FromResidual<Option<Infallible>>` is implemented for `Option<T>`
4839

4940
error[E0277]: the `?` operator can only be used on `Option`s in a function that returns `Option`
5041
--> $DIR/bad-interconversion.rs:25:33
@@ -53,9 +44,6 @@ LL | fn control_flow_to_option() -> Option<u64> {
5344
| ------------------------------------------ this function returns an `Option`
5445
LL | Some(ControlFlow::Break(123)?)
5546
| ^ this `?` produces `ControlFlow<{integer}, Infallible>`, which is incompatible with `Option<u64>`
56-
|
57-
= help: the trait `FromResidual<ControlFlow<{integer}, Infallible>>` is not implemented for `Option<u64>`
58-
= help: the trait `FromResidual<Option<Infallible>>` is implemented for `Option<T>`
5947

6048
error[E0277]: the `?` operator can only be used on `ControlFlow`s in a function that returns `ControlFlow`
6149
--> $DIR/bad-interconversion.rs:30:39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
struct E;
2+
//~^ NOTE `E` needs to implement `std::error::Error`
3+
//~| NOTE alternatively, `E` needs to implement `Into<X>`
4+
struct X; //~ NOTE `X` needs to implement `From<E>`
5+
6+
fn foo() -> Result<(), Box<dyn std::error::Error>> { //~ NOTE required `E: std::error::Error` because of this
7+
Ok(bar()?)
8+
//~^ ERROR `?` couldn't convert the error: `E: std::error::Error` is not satisfied
9+
//~| NOTE the trait `std::error::Error` is not implemented for `E`
10+
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
11+
//~| NOTE required for `Box<dyn std::error::Error>` to implement `From<E>`
12+
//~| NOTE this has type `Result<_, E>`
13+
//~| NOTE in this expansion
14+
//~| NOTE in this expansion
15+
//~| NOTE in this expansion
16+
}
17+
fn bat() -> Result<(), X> { //~ NOTE expected `X` because of this
18+
Ok(bar()?)
19+
//~^ ERROR `?` couldn't convert the error to `X`
20+
//~| NOTE the trait `From<E>` is not implemented for `X`
21+
//~| NOTE this can't be annotated with `?` because it has type `Result<_, E>`
22+
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
23+
//~| NOTE in this expansion
24+
//~| NOTE in this expansion
25+
}
26+
fn bar() -> Result<(), E> {
27+
Err(E)
28+
}
29+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
error[E0277]: `?` couldn't convert the error: `E: std::error::Error` is not satisfied
2+
--> $DIR/bad-question-mark-on-trait-object.rs:7:13
3+
|
4+
LL | fn foo() -> Result<(), Box<dyn std::error::Error>> {
5+
| -------------------------------------- required `E: std::error::Error` because of this
6+
LL | Ok(bar()?)
7+
| -----^ the trait `std::error::Error` is not implemented for `E`
8+
| |
9+
| this has type `Result<_, E>`
10+
|
11+
note: `E` needs to implement `std::error::Error`
12+
--> $DIR/bad-question-mark-on-trait-object.rs:1:1
13+
|
14+
LL | struct E;
15+
| ^^^^^^^^
16+
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
17+
= note: required for `Box<dyn std::error::Error>` to implement `From<E>`
18+
19+
error[E0277]: `?` couldn't convert the error to `X`
20+
--> $DIR/bad-question-mark-on-trait-object.rs:18:13
21+
|
22+
LL | fn bat() -> Result<(), X> {
23+
| ------------- expected `X` because of this
24+
LL | Ok(bar()?)
25+
| -----^ the trait `From<E>` is not implemented for `X`
26+
| |
27+
| this can't be annotated with `?` because it has type `Result<_, E>`
28+
|
29+
note: `X` needs to implement `From<E>`
30+
--> $DIR/bad-question-mark-on-trait-object.rs:4:1
31+
|
32+
LL | struct X;
33+
| ^^^^^^^^
34+
note: alternatively, `E` needs to implement `Into<X>`
35+
--> $DIR/bad-question-mark-on-trait-object.rs:1:1
36+
|
37+
LL | struct E;
38+
| ^^^^^^^^
39+
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
40+
41+
error: aborting due to 2 previous errors
42+
43+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)