Skip to content

Commit 94fd369

Browse files
authored
Rollup merge of rust-lang#116496 - estebank:question-method-chain-context, r=compiler-errors
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:27:25 | LL | fn bar() -> Result<(), String> { | ------------------ expected `String` because of this LL | let x = foo(); | ----- this 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.
2 parents ec94480 + cce82d8 commit 94fd369

File tree

7 files changed

+364
-6
lines changed

7 files changed

+364
-6
lines changed

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ symbols! {
973973
managed_boxes,
974974
manually_drop,
975975
map,
976+
map_err,
976977
marker,
977978
marker_trait_attr,
978979
masked,
@@ -1137,6 +1138,7 @@ symbols! {
11371138
offset,
11381139
offset_of,
11391140
offset_of_enum,
1141+
ok_or_else,
11401142
omit_gdb_pretty_printer_section,
11411143
on,
11421144
on_unimplemented,

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

+232-3
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, Symbol, DUMMY_SP};
4445
use std::borrow::Cow;
4546
use std::fmt;
4647
use std::iter;
@@ -106,6 +107,13 @@ pub trait TypeErrCtxtExt<'tcx> {
106107

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

110+
fn try_conversion_context(
111+
&self,
112+
obligation: &PredicateObligation<'tcx>,
113+
trait_ref: ty::TraitRef<'tcx>,
114+
err: &mut Diagnostic,
115+
) -> bool;
116+
109117
fn report_const_param_not_wf(
110118
&self,
111119
ty: Ty<'tcx>,
@@ -509,6 +517,11 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
509517

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

520+
let mut suggested = false;
521+
if is_try_conversion {
522+
suggested = self.try_conversion_context(&obligation, trait_ref.skip_binder(), &mut err);
523+
}
524+
512525
if is_try_conversion && let Some(ret_span) = self.return_type_span(&obligation) {
513526
err.span_label(
514527
ret_span,
@@ -609,8 +622,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
609622

610623
self.suggest_floating_point_literal(&obligation, &mut err, &trait_ref);
611624
self.suggest_dereferencing_index(&obligation, &mut err, trait_predicate);
612-
let mut suggested =
613-
self.suggest_dereferences(&obligation, &mut err, trait_predicate);
625+
suggested |= self.suggest_dereferences(&obligation, &mut err, trait_predicate);
614626
suggested |= self.suggest_fn_call(&obligation, &mut err, trait_predicate);
615627
let impl_candidates = self.find_similar_impl_candidates(trait_predicate);
616628
suggested = if let &[cand] = &impl_candidates[..] {
@@ -982,6 +994,223 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
982994
false
983995
}
984996

997+
/// When the `E` of the resulting `Result<T, E>` in an expression `foo().bar().baz()?`,
998+
/// identify thoe method chain sub-expressions that could or could not have been annotated
999+
/// with `?`.
1000+
fn try_conversion_context(
1001+
&self,
1002+
obligation: &PredicateObligation<'tcx>,
1003+
trait_ref: ty::TraitRef<'tcx>,
1004+
err: &mut Diagnostic,
1005+
) -> bool {
1006+
let span = obligation.cause.span;
1007+
struct V<'v> {
1008+
search_span: Span,
1009+
found: Option<&'v hir::Expr<'v>>,
1010+
}
1011+
impl<'v> Visitor<'v> for V<'v> {
1012+
fn visit_expr(&mut self, ex: &'v hir::Expr<'v>) {
1013+
if let hir::ExprKind::Match(expr, _arms, hir::MatchSource::TryDesugar(_)) = ex.kind
1014+
{
1015+
if ex.span.with_lo(ex.span.hi() - BytePos(1)).source_equal(self.search_span) {
1016+
if let hir::ExprKind::Call(_, [expr, ..]) = expr.kind {
1017+
self.found = Some(expr);
1018+
return;
1019+
}
1020+
}
1021+
}
1022+
hir::intravisit::walk_expr(self, ex);
1023+
}
1024+
}
1025+
let hir_id = self.tcx.hir().local_def_id_to_hir_id(obligation.cause.body_id);
1026+
let body_id = match self.tcx.hir().find(hir_id) {
1027+
Some(hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, _, body_id), .. })) => {
1028+
body_id
1029+
}
1030+
_ => return false,
1031+
};
1032+
let mut v = V { search_span: span, found: None };
1033+
v.visit_body(self.tcx.hir().body(*body_id));
1034+
let Some(expr) = v.found else {
1035+
return false;
1036+
};
1037+
let Some(typeck) = &self.typeck_results else {
1038+
return false;
1039+
};
1040+
let Some((ObligationCauseCode::QuestionMark, Some(y))) = obligation.cause.code().parent()
1041+
else {
1042+
return false;
1043+
};
1044+
if !self.tcx.is_diagnostic_item(sym::FromResidual, y.def_id()) {
1045+
return false;
1046+
}
1047+
let self_ty = trait_ref.self_ty();
1048+
let found_ty = trait_ref.args.get(1).and_then(|a| a.as_type());
1049+
1050+
let mut prev_ty = self.resolve_vars_if_possible(
1051+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1052+
);
1053+
1054+
// We always look at the `E` type, because that's the only one affected by `?`. If the
1055+
// incorrect `Result<T, E>` is because of the `T`, we'll get an E0308 on the whole
1056+
// expression, after the `?` has "unwrapped" the `T`.
1057+
let get_e_type = |prev_ty: Ty<'tcx>| -> Option<Ty<'tcx>> {
1058+
let ty::Adt(def, args) = prev_ty.kind() else {
1059+
return None;
1060+
};
1061+
let Some(arg) = args.get(1) else {
1062+
return None;
1063+
};
1064+
if !self.tcx.is_diagnostic_item(sym::Result, def.did()) {
1065+
return None;
1066+
}
1067+
Some(arg.as_type()?)
1068+
};
1069+
1070+
let mut suggested = false;
1071+
let mut chain = vec![];
1072+
1073+
// The following logic is simlar to `point_at_chain`, but that's focused on associated types
1074+
let mut expr = expr;
1075+
while let hir::ExprKind::MethodCall(path_segment, rcvr_expr, args, span) = expr.kind {
1076+
// Point at every method call in the chain with the `Result` type.
1077+
// let foo = bar.iter().map(mapper)?;
1078+
// ------ -----------
1079+
expr = rcvr_expr;
1080+
chain.push((span, prev_ty));
1081+
1082+
let next_ty = self.resolve_vars_if_possible(
1083+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1084+
);
1085+
1086+
let is_diagnostic_item = |symbol: Symbol, ty: Ty<'tcx>| {
1087+
let ty::Adt(def, _) = ty.kind() else {
1088+
return false;
1089+
};
1090+
self.tcx.is_diagnostic_item(symbol, def.did())
1091+
};
1092+
// For each method in the chain, see if this is `Result::map_err` or
1093+
// `Option::ok_or_else` and if it is, see if the closure passed to it has an incorrect
1094+
// trailing `;`.
1095+
if let Some(ty) = get_e_type(prev_ty)
1096+
&& let Some(found_ty) = found_ty
1097+
// Ideally we would instead use `FnCtxt::lookup_method_for_diagnostic` for 100%
1098+
// accurate check, but we are in the wrong stage to do that and looking for
1099+
// `Result::map_err` by checking the Self type and the path segment is enough.
1100+
// sym::ok_or_else
1101+
&& (
1102+
( // Result::map_err
1103+
path_segment.ident.name == sym::map_err
1104+
&& is_diagnostic_item(sym::Result, next_ty)
1105+
) || ( // Option::ok_or_else
1106+
path_segment.ident.name == sym::ok_or_else
1107+
&& is_diagnostic_item(sym::Option, next_ty)
1108+
)
1109+
)
1110+
// Found `Result<_, ()>?`
1111+
&& let ty::Tuple(tys) = found_ty.kind()
1112+
&& tys.is_empty()
1113+
// The current method call returns `Result<_, ()>`
1114+
&& self.can_eq(obligation.param_env, ty, found_ty)
1115+
// There's a single argument in the method call and it is a closure
1116+
&& args.len() == 1
1117+
&& let Some(arg) = args.get(0)
1118+
&& let hir::ExprKind::Closure(closure) = arg.kind
1119+
// The closure has a block for its body with no tail expression
1120+
&& let body = self.tcx.hir().body(closure.body)
1121+
&& let hir::ExprKind::Block(block, _) = body.value.kind
1122+
&& let None = block.expr
1123+
// The last statement is of a type that can be converted to the return error type
1124+
&& let [.., stmt] = block.stmts
1125+
&& let hir::StmtKind::Semi(expr) = stmt.kind
1126+
&& let expr_ty = self.resolve_vars_if_possible(
1127+
typeck.expr_ty_adjusted_opt(expr)
1128+
.unwrap_or(Ty::new_misc_error(self.tcx)),
1129+
)
1130+
&& self
1131+
.infcx
1132+
.type_implements_trait(
1133+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1134+
[self_ty, expr_ty],
1135+
obligation.param_env,
1136+
)
1137+
.must_apply_modulo_regions()
1138+
{
1139+
suggested = true;
1140+
err.span_suggestion_short(
1141+
stmt.span.with_lo(expr.span.hi()),
1142+
"remove this semicolon",
1143+
String::new(),
1144+
Applicability::MachineApplicable,
1145+
);
1146+
}
1147+
1148+
prev_ty = next_ty;
1149+
1150+
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
1151+
&& let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path
1152+
&& let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id)
1153+
&& let Some(parent) = self.tcx.hir().find_parent(binding.hir_id)
1154+
{
1155+
// We've reached the root of the method call chain...
1156+
if let hir::Node::Local(local) = parent
1157+
&& let Some(binding_expr) = local.init
1158+
{
1159+
// ...and it is a binding. Get the binding creation and continue the chain.
1160+
expr = binding_expr;
1161+
}
1162+
if let hir::Node::Param(_param) = parent {
1163+
// ...and it is a an fn argument.
1164+
break;
1165+
}
1166+
}
1167+
}
1168+
// `expr` is now the "root" expression of the method call chain, which can be any
1169+
// expression kind, like a method call or a path. If this expression is `Result<T, E>` as
1170+
// well, then we also point at it.
1171+
prev_ty = self.resolve_vars_if_possible(
1172+
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
1173+
);
1174+
chain.push((expr.span, prev_ty));
1175+
1176+
let mut prev = None;
1177+
for (span, err_ty) in chain.into_iter().rev() {
1178+
let err_ty = get_e_type(err_ty);
1179+
let err_ty = match (err_ty, prev) {
1180+
(Some(err_ty), Some(prev)) if !self.can_eq(obligation.param_env, err_ty, prev) => {
1181+
err_ty
1182+
}
1183+
(Some(err_ty), None) => err_ty,
1184+
_ => {
1185+
prev = err_ty;
1186+
continue;
1187+
}
1188+
};
1189+
if self
1190+
.infcx
1191+
.type_implements_trait(
1192+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1193+
[self_ty, err_ty],
1194+
obligation.param_env,
1195+
)
1196+
.must_apply_modulo_regions()
1197+
{
1198+
if !suggested {
1199+
err.span_label(span, format!("this has type `Result<_, {err_ty}>`"));
1200+
}
1201+
} else {
1202+
err.span_label(
1203+
span,
1204+
format!(
1205+
"this can't be annotated with `?` because it has type `Result<_, {err_ty}>`",
1206+
),
1207+
);
1208+
}
1209+
prev = Some(err_ty);
1210+
}
1211+
suggested
1212+
}
1213+
9851214
fn report_const_param_not_wf(
9861215
&self,
9871216
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(|| {
7+
"Couldn't split the test string"
8+
});
9+
let one = x
10+
.map(|s| ())
11+
.map_err(|e| { //~ NOTE this can't be annotated with `?` because it has type `Result<_, ()>`
12+
e; //~ HELP remove this semicolon
13+
})
14+
.map(|()| "")?; //~ ERROR `?` couldn't convert the error to `String`
15+
//~^ NOTE in this expansion of desugaring of operator `?`
16+
//~| NOTE in this expansion of desugaring of operator `?`
17+
//~| NOTE in this expansion of desugaring of operator `?`
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 has type `Result<_, String>`
26+
let one = x
27+
.map(|s| ())
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+
//~| HELP the following other types implement trait `From<T>`:
38+
Ok(one)
39+
}
40+
41+
fn baz() -> Result<String, String> { //~ NOTE expected `String` because of this
42+
let test = String::from("one,two");
43+
let one = test
44+
.split_whitespace()
45+
.next()
46+
.ok_or_else(|| { //~ NOTE this can't be annotated with `?` because it has type `Result<_, ()>`
47+
"Couldn't split the test string"; //~ HELP remove this semicolon
48+
})?;
49+
//~^ ERROR `?` couldn't convert the error to `String`
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() {}

0 commit comments

Comments
 (0)