Skip to content

Commit debb958

Browse files
committed
Provide context when ? can't be called because of Result<_, E>
When a method chain ending in `?` causes an E0277 because the expression's `Result::Err` variant doesn't have a type that can be converted to the `Result<_, E>` type parameter in the return type, provide additional context of which parts of the chain can and can't support the `?` operator. ``` error[E0277]: `?` couldn't convert the error to `String` --> $DIR/question-mark-result-err-mismatch.rs:28:25 | LL | fn bar() -> Result<(), String> { | ------------------ expected `String` because of this LL | let x = foo(); | ----- this can be annotated with `?` because it has type `Result<String, String>` LL | let one = x LL | .map(|s| ()) | ----------- this can be annotated with `?` because it has type `Result<(), String>` LL | .map_err(|_| ())?; | ---------------^ the trait `From<()>` is not implemented for `String` | | | this can't be annotated with `?` because it has type `Result<(), ()>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `From<T>`: <String as From<char>> <String as From<Box<str>>> <String as From<Cow<'a, str>>> <String as From<&str>> <String as From<&mut str>> <String as From<&String>> = note: required for `Result<(), String>` to implement `FromResidual<Result<Infallible, ()>>` ``` Fix rust-lang#72124.
1 parent b049093 commit debb958

File tree

7 files changed

+302
-12
lines changed

7 files changed

+302
-12
lines changed

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

+10-8
Original file line numberDiff line numberDiff line change
@@ -3605,14 +3605,16 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
36053605
&& let Some(failed_pred) = failed_pred.to_opt_poly_projection_pred()
36063606
&& let Some(found) = failed_pred.skip_binder().term.ty()
36073607
{
3608-
type_diffs = vec![Sorts(ty::error::ExpectedFound {
3609-
expected: Ty::new_alias(
3610-
self.tcx,
3611-
ty::Projection,
3612-
where_pred.skip_binder().projection_ty,
3613-
),
3614-
found,
3615-
})];
3608+
type_diffs = vec![
3609+
Sorts(ty::error::ExpectedFound {
3610+
expected: Ty::new_alias(
3611+
self.tcx,
3612+
ty::Projection,
3613+
where_pred.skip_binder().projection_ty,
3614+
),
3615+
found,
3616+
}),
3617+
];
36163618
}
36173619
}
36183620
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind

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

+141-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use super::suggestions::{get_explanation_based_on_obligation, TypeErrCtxtExt as
33
use crate::errors::{ClosureFnMutLabel, ClosureFnOnceLabel, ClosureKindMismatch};
44
use crate::infer::error_reporting::{TyCategory, TypeAnnotationNeeded as ErrorCode};
55
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
6+
use crate::infer::InferCtxtExt as _;
67
use crate::infer::{self, InferCtxt};
78
use crate::traits::error_reporting::infer_ctxt_ext::InferCtxtExt;
89
use crate::traits::error_reporting::{ambiguity, ambiguity::Ambiguity::*};
@@ -40,7 +41,7 @@ use rustc_session::config::{DumpSolverProofTree, TraitSolver};
4041
use rustc_session::Limit;
4142
use rustc_span::def_id::LOCAL_CRATE;
4243
use rustc_span::symbol::sym;
43-
use rustc_span::{ExpnKind, Span, DUMMY_SP};
44+
use rustc_span::{BytePos, ExpnKind, Span, DUMMY_SP};
4445
use std::borrow::Cow;
4546
use std::fmt;
4647
use std::iter;
@@ -100,6 +101,13 @@ pub trait TypeErrCtxtExt<'tcx> {
100101

101102
fn fn_arg_obligation(&self, obligation: &PredicateObligation<'tcx>) -> bool;
102103

104+
fn try_conversion_context(
105+
&self,
106+
obligation: &PredicateObligation<'tcx>,
107+
trait_ref: ty::TraitRef<'tcx>,
108+
err: &mut Diagnostic,
109+
);
110+
103111
fn report_const_param_not_wf(
104112
&self,
105113
ty: Ty<'tcx>,
@@ -499,6 +507,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
499507

500508
let mut err = struct_span_err!(self.tcx.sess, span, E0277, "{}", err_msg);
501509

510+
if is_try_conversion {
511+
self.try_conversion_context(&obligation, trait_ref.skip_binder(), &mut err);
512+
}
513+
502514
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
503515
err.span_label(
504516
ret_span,
@@ -947,6 +959,134 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
947959
false
948960
}
949961

962+
/// When the `E` of the resulting `Result<T, E>` in an expression `foo().bar().baz()?`,
963+
/// identify thoe method chain sub-expressions that could or could not have been annotated
964+
/// with `?`.
965+
fn try_conversion_context(
966+
&self,
967+
obligation: &PredicateObligation<'tcx>,
968+
trait_ref: ty::TraitRef<'tcx>,
969+
err: &mut Diagnostic,
970+
) {
971+
let span = obligation.cause.span;
972+
struct V<'v> {
973+
search_span: Span,
974+
found: Option<&'v hir::Expr<'v>>,
975+
}
976+
impl<'v> Visitor<'v> for V<'v> {
977+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
978+
if let hir::ExprKind::Match(expr, _arms, hir::MatchSource::TryDesugar(_)) = ex.kind
979+
{
980+
if ex.span.with_lo(ex.span.hi() - BytePos(1)).source_equal(self.search_span) {
981+
if let hir::ExprKind::Call(_, [expr, ..]) = expr.kind {
982+
self.found = Some(expr);
983+
return;
984+
}
985+
}
986+
}
987+
hir::intravisit::walk_expr(self, ex);
988+
}
989+
}
990+
let hir_id = self.tcx.hir().local_def_id_to_hir_id(obligation.cause.body_id);
991+
let body_id = match self.tcx.hir().find(hir_id) {
992+
Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body_id), .. })) => {
993+
body_id
994+
}
995+
_ => return,
996+
};
997+
let mut v = V { search_span: span, found: None };
998+
v.visit_body(self.tcx.hir().body(*body_id));
999+
let Some(expr) = v.found else {
1000+
return;
1001+
};
1002+
let Some(typeck) = &self.typeck_results else {
1003+
return;
1004+
};
1005+
let Some((ObligationCauseCode::QuestionMark, Some(y))) = obligation.cause.code().parent()
1006+
else {
1007+
return;
1008+
};
1009+
if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) {
1010+
return;
1011+
}
1012+
let self_ty = trait_ref.self_ty();
1013+
1014+
let mut prev_ty = self.resolve_vars_if_possible(
1015+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1016+
);
1017+
let mut annotate_expr = |span: Span, prev_ty: Ty<'tcx>, self_ty: Ty<'tcx>| -> bool {
1018+
// We always look at the `E` type, because that's the only one affected by `?`. If the
1019+
// incorrect `Result<T, E>` is because of the `T`, we'll get an E0308 on the whole
1020+
// expression, after the `?` has "unwrapped" the `T`.
1021+
let ty::Adt(def, args) = prev_ty.kind() else {
1022+
return false;
1023+
};
1024+
let Some(arg) = args.get(1) else {
1025+
return false;
1026+
};
1027+
if !self.tcx.is_diagnostic_item(sym::Result, def.did()) {
1028+
return false;
1029+
}
1030+
let can = if self
1031+
.infcx
1032+
.type_implements_trait(
1033+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1034+
[self_ty.into(), *arg],
1035+
obligation.param_env,
1036+
)
1037+
.must_apply_modulo_regions()
1038+
{
1039+
"can"
1040+
} else {
1041+
"can't"
1042+
};
1043+
err.span_label(
1044+
span,
1045+
format!("this {can} be annotated with `?` because it has type `{prev_ty}`"),
1046+
);
1047+
true
1048+
};
1049+
1050+
// The following logic is simlar to `point_at_chain`, but that's focused on associated types
1051+
let mut expr = expr;
1052+
while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind {
1053+
// Point at every method call in the chain with the `Result` type.
1054+
// let foo = bar.iter().map(mapper)?;
1055+
// ------ -----------
1056+
expr = rcvr_expr;
1057+
if !annotate_expr(span, prev_ty, self_ty) {
1058+
break;
1059+
}
1060+
1061+
prev_ty = self.resolve_vars_if_possible(
1062+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1063+
);
1064+
1065+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1066+
&& let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path
1067+
&& let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id)
1068+
&& let Some(parent) = self.tcx.hir().find_parent(binding.hir_id)
1069+
{
1070+
// We've reached the root of the method call chain...
1071+
if let hir::Node::Local(local) = parent && let Some(binding_expr) = local.init {
1072+
// ...and it is a binding. Get the binding creation and continue the chain.
1073+
expr = binding_expr;
1074+
}
1075+
if let hir::Node::Param(_param) = parent {
1076+
// ...and it is a an fn argument.
1077+
break;
1078+
}
1079+
}
1080+
}
1081+
// `expr` is now the "root" expression of the method call chain, which can be any
1082+
// expression kind, like a method call or a path. If this expression is `Result<T, E>` as
1083+
// well, then we also point at it.
1084+
prev_ty = self.resolve_vars_if_possible(
1085+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1086+
);
1087+
annotate_expr(expr.span, prev_ty, self_ty);
1088+
}
1089+
9501090
fn report_const_param_not_wf(
9511091
&self,
9521092
ty: Ty<'tcx>,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
fn foo() -> Result<String, String> { //~ NOTE expected `String` because of this
2+
let test = String::from("one,two");
3+
let x = test
4+
.split_whitespace()
5+
.next()
6+
.ok_or_else(|| { //~ NOTE this can be annotated with `?` because it has type `Result<&str, &str>`
7+
"Couldn't split the test string"
8+
});
9+
let one = x
10+
.map(|s| ()) //~ NOTE this can be annotated with `?` because it has type `Result<(), &str>`
11+
.map_err(|_| ()) //~ NOTE this can't be annotated with `?` because it has type `Result<(), ()>`
12+
.map(|()| "")?; //~ ERROR `?` couldn't convert the error to `String`
13+
//~^ NOTE in this expansion of desugaring of operator `?`
14+
//~| NOTE in this expansion of desugaring of operator `?`
15+
//~| NOTE in this expansion of desugaring of operator `?`
16+
//~| NOTE in this expansion of desugaring of operator `?`
17+
//~| NOTE this can't be annotated with `?` because it has type `Result<&str, ()>`
18+
//~| NOTE the trait `From<()>` is not implemented for `String`
19+
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
20+
//~| NOTE required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
21+
Ok(one.to_string())
22+
}
23+
24+
fn bar() -> Result<(), String> { //~ NOTE expected `String` because of this
25+
let x = foo(); //~ NOTE this can be annotated with `?` because it has type `Result<String, String>`
26+
let one = x
27+
.map(|s| ()) //~ NOTE this can be annotated with `?` because it has type `Result<(), String>`
28+
.map_err(|_| ())?; //~ ERROR `?` couldn't convert the error to `String`
29+
//~^ NOTE in this expansion of desugaring of operator `?`
30+
//~| NOTE in this expansion of desugaring of operator `?`
31+
//~| NOTE in this expansion of desugaring of operator `?`
32+
//~| NOTE in this expansion of desugaring of operator `?`
33+
//~| NOTE this can't be annotated with `?` because it has type `Result<(), ()>`
34+
//~| NOTE the trait `From<()>` is not implemented for `String`
35+
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
36+
//~| NOTE required for `Result<(), String>` to implement `FromResidual<Result<Infallible, ()>>`
37+
Ok(one)
38+
}
39+
40+
fn baz() -> Result<String, String> { //~ NOTE expected `String` because of this
41+
let test = String::from("one,two");
42+
let one = test
43+
.split_whitespace()
44+
.next()
45+
.ok_or_else(|| { //~ NOTE this can't be annotated with `?` because it has type `Result<&str, ()>`
46+
"Couldn't split the test string";
47+
})?;
48+
//~^ ERROR `?` couldn't convert the error to `String`
49+
//~| NOTE in this expansion of desugaring of operator `?`
50+
//~| NOTE in this expansion of desugaring of operator `?`
51+
//~| NOTE in this expansion of desugaring of operator `?`
52+
//~| NOTE in this expansion of desugaring of operator `?`
53+
//~| NOTE the trait `From<()>` is not implemented for `String`
54+
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
55+
//~| NOTE required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
56+
Ok(one.to_string())
57+
}
58+
59+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
error[E0277]: `?` couldn't convert the error to `String`
2+
--> $DIR/question-mark-result-err-mismatch.rs:12:22
3+
|
4+
LL | fn foo() -> Result<String, String> {
5+
| ---------------------- expected `String` because of this
6+
...
7+
LL | .ok_or_else(|| {
8+
| __________-
9+
LL | | "Couldn't split the test string"
10+
LL | | });
11+
| |__________- this can be annotated with `?` because it has type `Result<&str, &str>`
12+
LL | let one = x
13+
LL | .map(|s| ())
14+
| ----------- this can be annotated with `?` because it has type `Result<(), &str>`
15+
LL | .map_err(|_| ())
16+
| --------------- this can't be annotated with `?` because it has type `Result<(), ()>`
17+
LL | .map(|()| "")?;
18+
| ------------^ the trait `From<()>` is not implemented for `String`
19+
| |
20+
| this can't be annotated with `?` because it has type `Result<&str, ()>`
21+
|
22+
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
23+
= help: the following other types implement trait `From<T>`:
24+
<String as From<char>>
25+
<String as From<Box<str>>>
26+
<String as From<Cow<'a, str>>>
27+
<String as From<&str>>
28+
<String as From<&mut str>>
29+
<String as From<&String>>
30+
= note: required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
31+
32+
error[E0277]: `?` couldn't convert the error to `String`
33+
--> $DIR/question-mark-result-err-mismatch.rs:28:25
34+
|
35+
LL | fn bar() -> Result<(), String> {
36+
| ------------------ expected `String` because of this
37+
LL | let x = foo();
38+
| ----- this can be annotated with `?` because it has type `Result<String, String>`
39+
LL | let one = x
40+
LL | .map(|s| ())
41+
| ----------- this can be annotated with `?` because it has type `Result<(), String>`
42+
LL | .map_err(|_| ())?;
43+
| ---------------^ the trait `From<()>` is not implemented for `String`
44+
| |
45+
| this can't be annotated with `?` because it has type `Result<(), ()>`
46+
|
47+
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
48+
= help: the following other types implement trait `From<T>`:
49+
<String as From<char>>
50+
<String as From<Box<str>>>
51+
<String as From<Cow<'a, str>>>
52+
<String as From<&str>>
53+
<String as From<&mut str>>
54+
<String as From<&String>>
55+
= note: required for `Result<(), String>` to implement `FromResidual<Result<Infallible, ()>>`
56+
57+
error[E0277]: `?` couldn't convert the error to `String`
58+
--> $DIR/question-mark-result-err-mismatch.rs:47:11
59+
|
60+
LL | fn baz() -> Result<String, String> {
61+
| ---------------------- expected `String` because of this
62+
...
63+
LL | .ok_or_else(|| {
64+
| __________-
65+
LL | | "Couldn't split the test string";
66+
LL | | })?;
67+
| | -^ the trait `From<()>` is not implemented for `String`
68+
| |__________|
69+
| this can't be annotated with `?` because it has type `Result<&str, ()>`
70+
|
71+
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
72+
= help: the following other types implement trait `From<T>`:
73+
<String as From<char>>
74+
<String as From<Box<str>>>
75+
<String as From<Cow<'a, str>>>
76+
<String as From<&str>>
77+
<String as From<&mut str>>
78+
<String as From<&String>>
79+
= note: required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
80+
81+
error: aborting due to 3 previous errors
82+
83+
For more information about this error, try `rustc --explain E0277`.

tests/ui/try-block/try-block-bad-type.stderr

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ error[E0277]: `?` couldn't convert the error to `TryFromSliceError`
22
--> $DIR/try-block-bad-type.rs:7:16
33
|
44
LL | Err("")?;
5-
| ^ the trait `From<&str>` is not implemented for `TryFromSliceError`
5+
| -------^ the trait `From<&str>` is not implemented for `TryFromSliceError`
6+
| |
7+
| this can't be annotated with `?` because it has type `Result<_, &str>`
68
|
79
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
810
= help: the trait `From<Infallible>` is implemented for `TryFromSliceError`

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ error[E0277]: `?` couldn't convert the error to `u8`
44
LL | fn result_to_result() -> Result<u64, u8> {
55
| --------------- expected `u8` because of this
66
LL | Ok(Err(123_i32)?)
7-
| ^ the trait `From<i32>` is not implemented for `u8`
7+
| ------------^ the trait `From<i32>` is not implemented for `u8`
8+
| |
9+
| this can't be annotated with `?` because it has type `Result<_, i32>`
810
|
911
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
1012
= help: the following other types implement trait `From<T>`:

tests/ui/try-trait/issue-32709.stderr

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ error[E0277]: `?` couldn't convert the error to `()`
44
LL | fn a() -> Result<i32, ()> {
55
| --------------- expected `()` because of this
66
LL | Err(5)?;
7-
| ^ the trait `From<{integer}>` is not implemented for `()`
7+
| ------^ the trait `From<{integer}>` is not implemented for `()`
8+
| |
9+
| this can't be annotated with `?` because it has type `Result<_, {integer}>`
810
|
911
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
1012
= help: the following other types implement trait `From<T>`:

0 commit comments

Comments
 (0)