Skip to content

Commit c65d2ea

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 9d6d5d4 commit c65d2ea

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
@@ -3564,14 +3564,16 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
35643564
&& let Some(failed_pred) = failed_pred.to_opt_poly_projection_pred()
35653565
&& let Some(found) = failed_pred.skip_binder().term.ty()
35663566
{
3567-
type_diffs = vec![Sorts(ty::error::ExpectedFound {
3568-
expected: Ty::new_alias(
3569-
self.tcx,
3570-
ty::Projection,
3571-
where_pred.skip_binder().projection_ty,
3572-
),
3573-
found,
3574-
})];
3567+
type_diffs = vec![
3568+
Sorts(ty::error::ExpectedFound {
3569+
expected: Ty::new_alias(
3570+
self.tcx,
3571+
ty::Projection,
3572+
where_pred.skip_binder().projection_ty,
3573+
),
3574+
found,
3575+
}),
3576+
];
35753577
}
35763578
}
35773579
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>,
@@ -495,6 +503,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
495503

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

506+
if is_try_conversion {
507+
self.try_conversion_context(&obligation, trait_ref.skip_binder(), &mut err);
508+
}
509+
498510
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
499511
err.span_label(
500512
ret_span,
@@ -941,6 +953,134 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
941953
false
942954
}
943955

956+
/// When the `E` of the resulting `Result<T, E>` in an expression `foo().bar().baz()?`,
957+
/// identify thoe method chain sub-expressions that could or could not have been annotated
958+
/// with `?`.
959+
fn try_conversion_context(
960+
&self,
961+
obligation: &PredicateObligation<'tcx>,
962+
trait_ref: ty::TraitRef<'tcx>,
963+
err: &mut Diagnostic,
964+
) {
965+
let span = obligation.cause.span;
966+
struct V<'v> {
967+
search_span: Span,
968+
found: Option<&'v hir::Expr<'v>>,
969+
}
970+
impl<'v> Visitor<'v> for V<'v> {
971+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
972+
if let hir::ExprKind::Match(expr, _arms, hir::MatchSource::TryDesugar(_)) = ex.kind
973+
{
974+
if ex.span.with_lo(ex.span.hi() - BytePos(1)).source_equal(self.search_span) {
975+
if let hir::ExprKind::Call(_, [expr, ..]) = expr.kind {
976+
self.found = Some(expr);
977+
return;
978+
}
979+
}
980+
}
981+
hir::intravisit::walk_expr(self, ex);
982+
}
983+
}
984+
let hir_id = self.tcx.hir().local_def_id_to_hir_id(obligation.cause.body_id);
985+
let body_id = match self.tcx.hir().find(hir_id) {
986+
Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body_id), .. })) => {
987+
body_id
988+
}
989+
_ => return,
990+
};
991+
let mut v = V { search_span: span, found: None };
992+
v.visit_body(self.tcx.hir().body(*body_id));
993+
let Some(expr) = v.found else {
994+
return;
995+
};
996+
let Some(typeck) = &self.typeck_results else {
997+
return;
998+
};
999+
let Some((ObligationCauseCode::QuestionMark, Some(y))) = obligation.cause.code().parent()
1000+
else {
1001+
return;
1002+
};
1003+
if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) {
1004+
return;
1005+
}
1006+
let self_ty = trait_ref.self_ty();
1007+
1008+
let mut prev_ty = self.resolve_vars_if_possible(
1009+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1010+
);
1011+
let mut annotate_expr = |span: Span, prev_ty: Ty<'tcx>, self_ty: Ty<'tcx>| -> bool {
1012+
// We always look at the `E` type, because that's the only one affected by `?`. If the
1013+
// incorrect `Result<T, E>` is because of the `T`, we'll get an E0308 on the whole
1014+
// expression, after the `?` has "unwrapped" the `T`.
1015+
let ty::Adt(def, args) = prev_ty.kind() else {
1016+
return false;
1017+
};
1018+
let Some(arg) = args.get(1) else {
1019+
return false;
1020+
};
1021+
if !self.tcx.is_diagnostic_item(sym::Result, def.did()) {
1022+
return false;
1023+
}
1024+
let can = if self
1025+
.infcx
1026+
.type_implements_trait(
1027+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1028+
[self_ty.into(), *arg],
1029+
obligation.param_env,
1030+
)
1031+
.must_apply_modulo_regions()
1032+
{
1033+
"can"
1034+
} else {
1035+
"can't"
1036+
};
1037+
err.span_label(
1038+
span,
1039+
format!("this {can} be annotated with `?` because it has type `{prev_ty}`"),
1040+
);
1041+
true
1042+
};
1043+
1044+
// The following logic is simlar to `point_at_chain`, but that's focused on associated types
1045+
let mut expr = expr;
1046+
while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind {
1047+
// Point at every method call in the chain with the `Result` type.
1048+
// let foo = bar.iter().map(mapper)?;
1049+
// ------ -----------
1050+
expr = rcvr_expr;
1051+
if !annotate_expr(span, prev_ty, self_ty) {
1052+
break;
1053+
}
1054+
1055+
prev_ty = self.resolve_vars_if_possible(
1056+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1057+
);
1058+
1059+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1060+
&& let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path
1061+
&& let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id)
1062+
&& let Some(parent) = self.tcx.hir().find_parent(binding.hir_id)
1063+
{
1064+
// We've reached the root of the method call chain...
1065+
if let hir::Node::Local(local) = parent && let Some(binding_expr) = local.init {
1066+
// ...and it is a binding. Get the binding creation and continue the chain.
1067+
expr = binding_expr;
1068+
}
1069+
if let hir::Node::Param(_param) = parent {
1070+
// ...and it is a an fn argument.
1071+
break;
1072+
}
1073+
}
1074+
}
1075+
// `expr` is now the "root" expression of the method call chain, which can be any
1076+
// expression kind, like a method call or a path. If this expression is `Result<T, E>` as
1077+
// well, then we also point at it.
1078+
prev_ty = self.resolve_vars_if_possible(
1079+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1080+
);
1081+
annotate_expr(expr.span, prev_ty, self_ty);
1082+
}
1083+
9441084
fn report_const_param_not_wf(
9451085
&self,
9461086
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)