Skip to content

Commit 19f23b6

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 #72124.
1 parent 94bc9c7 commit 19f23b6

File tree

7 files changed

+299
-6
lines changed

7 files changed

+299
-6
lines changed

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -3437,7 +3437,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
34373437
let mut type_diffs = vec![];
34383438
if let ObligationCauseCode::ExprBindingObligation(def_id, _, _, idx) = parent_code
34393439
&& let Some(node_args) = typeck_results.node_args_opt(call_hir_id)
3440-
&& let where_clauses = self.tcx.predicates_of(def_id).instantiate(self.tcx, node_args)
3440+
&& let where_clauses =
3441+
self.tcx.predicates_of(def_id).instantiate(self.tcx, node_args)
34413442
&& let Some(where_pred) = where_clauses.predicates.get(*idx)
34423443
{
34433444
if let Some(where_pred) = where_pred.as_trait_clause()
@@ -3469,7 +3470,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
34693470
{
34703471
type_diffs = vec![
34713472
Sorts(ty::error::ExpectedFound {
3472-
expected: Ty::new_alias(self.tcx,ty::Projection, where_pred.skip_binder().projection_ty),
3473+
expected: Ty::new_alias(
3474+
self.tcx,
3475+
ty::Projection,
3476+
where_pred.skip_binder().projection_ty,
3477+
),
34733478
found,
34743479
}),
34753480
];

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;
@@ -104,6 +105,13 @@ pub trait TypeErrCtxtExt<'tcx> {
104105
error: &SelectionError<'tcx>,
105106
);
106107

108+
fn try_conversion_context(
109+
&self,
110+
obligation: &PredicateObligation<'tcx>,
111+
trait_ref: ty::TraitRef<'tcx>,
112+
err: &mut Diagnostic,
113+
);
114+
107115
fn report_const_param_not_wf(
108116
&self,
109117
ty: Ty<'tcx>,
@@ -498,6 +506,10 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
498506

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

509+
if is_try_conversion {
510+
self.try_conversion_context(&obligation, trait_ref.skip_binder(), &mut err);
511+
}
512+
501513
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
502514
err.span_label(
503515
ret_span,
@@ -924,6 +936,134 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
924936
err.emit();
925937
}
926938

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