Skip to content

Commit 98e5317

Browse files
committed
Detect incorrect ; in Option::ok_or_else and Result::map_err
Fix rust-lang#72124.
1 parent 5381796 commit 98e5317

File tree

4 files changed

+86
-10
lines changed

4 files changed

+86
-10
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

+67-3
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use rustc_session::config::{DumpSolverProofTree, TraitSolver};
4141
use rustc_session::Limit;
4242
use rustc_span::def_id::LOCAL_CRATE;
4343
use rustc_span::symbol::sym;
44-
use rustc_span::{BytePos, ExpnKind, Span, DUMMY_SP};
44+
use rustc_span::{BytePos, ExpnKind, Span, Symbol, DUMMY_SP};
4545
use std::borrow::Cow;
4646
use std::fmt;
4747
use std::iter;
@@ -1045,6 +1045,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
10451045
return;
10461046
}
10471047
let self_ty = trait_ref.self_ty();
1048+
let found_ty = trait_ref.args.get(1).and_then(|a| a.as_type());
10481049

10491050
let mut prev_ty = self.resolve_vars_if_possible(
10501051
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
@@ -1070,17 +1071,80 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
10701071

10711072
// The following logic is simlar to `point_at_chain`, but that's focused on associated types
10721073
let mut expr = expr;
1073-
while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind {
1074+
while let hir::ExprKind::MethodCall(path_segment, rcvr_expr, args, span) = expr.kind {
10741075
// Point at every method call in the chain with the `Result` type.
10751076
// let foo = bar.iter().map(mapper)?;
10761077
// ------ -----------
10771078
expr = rcvr_expr;
10781079
chain.push((span, prev_ty));
10791080

1080-
prev_ty = self.resolve_vars_if_possible(
1081+
let next_ty = self.resolve_vars_if_possible(
10811082
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
10821083
);
10831084

1085+
let is_diagnostic_item = |symbol: Symbol, ty: Ty<'tcx>| {
1086+
let ty::Adt(def, _) = ty.kind() else {
1087+
return false;
1088+
};
1089+
self.tcx.is_diagnostic_item(symbol, def.did())
1090+
};
1091+
// For each method in the chain, see if this is `Result::map_err` or
1092+
// `Option::ok_or_else` and if it is, see if the closure passed to it has an incorrect
1093+
// trailing `;`.
1094+
if let Some(ty) = get_e_type(prev_ty)
1095+
&& let Some(found_ty) = found_ty
1096+
// Ideally we would instead use `FnCtxt::lookup_method_for_diagnostic` for 100%
1097+
// accurate check, but we are in the wrong stage to do that and looking for
1098+
// `Result::map_err` by checking the Self type and the path segment is enough.
1099+
// sym::ok_or_else
1100+
&& (
1101+
( // Result::map_err
1102+
path_segment.ident.name == sym::map_err
1103+
&& is_diagnostic_item(sym::Result, next_ty)
1104+
) || ( // Option::ok_or_else
1105+
path_segment.ident.name == sym::ok_or_else
1106+
&& is_diagnostic_item(sym::Option, next_ty)
1107+
)
1108+
)
1109+
// Found `Result<_, ()>?`
1110+
&& let ty::Tuple(tys) = found_ty.kind()
1111+
&& tys.is_empty()
1112+
// The current method call returns `Result<_, ()>`
1113+
&& self.can_eq(obligation.param_env, ty, found_ty)
1114+
// There's a single argument in the method call and it is a closure
1115+
&& args.len() == 1
1116+
&& let Some(arg) = args.get(0)
1117+
&& let hir::ExprKind::Closure(closure) = arg.kind
1118+
// The closure has a block for its body with no tail expression
1119+
&& let body = self.tcx.hir().body(closure.body)
1120+
&& let hir::ExprKind::Block(block, _) = body.value.kind
1121+
&& let None = block.expr
1122+
// The last statement is of a type that can be converted to the return error type
1123+
&& let [.., stmt] = block.stmts
1124+
&& let hir::StmtKind::Semi(expr) = stmt.kind
1125+
&& let expr_ty = self.resolve_vars_if_possible(
1126+
typeck.expr_ty_adjusted_opt(expr)
1127+
.unwrap_or(Ty::new_misc_error(self.tcx)),
1128+
)
1129+
&& self
1130+
.infcx
1131+
.type_implements_trait(
1132+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1133+
[self_ty, expr_ty],
1134+
obligation.param_env,
1135+
)
1136+
.must_apply_modulo_regions()
1137+
{
1138+
err.span_suggestion_short(
1139+
stmt.span.with_lo(expr.span.hi()),
1140+
"remove this semicolon",
1141+
String::new(),
1142+
Applicability::MachineApplicable,
1143+
);
1144+
}
1145+
1146+
prev_ty = next_ty;
1147+
10841148
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
10851149
&& let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path
10861150
&& let Some(hir::Node::Pat(binding)) = self.tcx.hir().find(*hir_id)

tests/ui/traits/question-mark-result-err-mismatch.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ fn foo() -> Result<String, String> { //~ NOTE expected `String` because of this
88
});
99
let one = x
1010
.map(|s| ())
11-
.map_err(|_| ()) //~ NOTE this can't be annotated with `?` because it has type `Result<_, ()>`
11+
.map_err(|e| { //~ NOTE this can't be annotated with `?` because it has type `Result<_, ()>`
12+
e; //~ HELP remove this semicolon
13+
})
1214
.map(|()| "")?; //~ ERROR `?` couldn't convert the error to `String`
1315
//~^ NOTE in this expansion of desugaring of operator `?`
1416
//~| NOTE in this expansion of desugaring of operator `?`
@@ -17,6 +19,7 @@ fn foo() -> Result<String, String> { //~ NOTE expected `String` because of this
1719
//~| NOTE the trait `From<()>` is not implemented for `String`
1820
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
1921
//~| NOTE required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
22+
//~| HELP the following other types implement trait `From<T>`:
2023
Ok(one.to_string())
2124
}
2225

@@ -33,6 +36,7 @@ fn bar() -> Result<(), String> { //~ NOTE expected `String` because of this
3336
//~| NOTE the trait `From<()>` is not implemented for `String`
3437
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
3538
//~| NOTE required for `Result<(), String>` to implement `FromResidual<Result<Infallible, ()>>`
39+
//~| HELP the following other types implement trait `From<T>`:
3640
Ok(one)
3741
}
3842

@@ -42,7 +46,7 @@ fn baz() -> Result<String, String> { //~ NOTE expected `String` because of this
4246
.split_whitespace()
4347
.next()
4448
.ok_or_else(|| { //~ NOTE this can't be annotated with `?` because it has type `Result<_, ()>`
45-
"Couldn't split the test string";
49+
"Couldn't split the test string"; //~ HELP remove this semicolon
4650
})?;
4751
//~^ ERROR `?` couldn't convert the error to `String`
4852
//~| NOTE in this expansion of desugaring of operator `?`
@@ -52,6 +56,7 @@ fn baz() -> Result<String, String> { //~ NOTE expected `String` because of this
5256
//~| NOTE the trait `From<()>` is not implemented for `String`
5357
//~| NOTE the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
5458
//~| NOTE required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
59+
//~| HELP the following other types implement trait `From<T>`:
5560
Ok(one.to_string())
5661
}
5762

tests/ui/traits/question-mark-result-err-mismatch.stderr

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error[E0277]: `?` couldn't convert the error to `String`
2-
--> $DIR/question-mark-result-err-mismatch.rs:12:22
2+
--> $DIR/question-mark-result-err-mismatch.rs:14:22
33
|
44
LL | fn foo() -> Result<String, String> {
55
| ---------------------- expected `String` because of this
@@ -10,8 +10,12 @@ LL | | "Couldn't split the test string"
1010
LL | | });
1111
| |__________- this has type `Result<_, &str>`
1212
...
13-
LL | .map_err(|_| ())
14-
| --------------- this can't be annotated with `?` because it has type `Result<_, ()>`
13+
LL | .map_err(|e| {
14+
| __________-
15+
LL | | e;
16+
| | - help: remove this semicolon
17+
LL | | })
18+
| |__________- this can't be annotated with `?` because it has type `Result<_, ()>`
1519
LL | .map(|()| "")?;
1620
| ^ the trait `From<()>` is not implemented for `String`
1721
|
@@ -26,7 +30,7 @@ LL | .map(|()| "")?;
2630
= note: required for `Result<String, String>` to implement `FromResidual<Result<Infallible, ()>>`
2731

2832
error[E0277]: `?` couldn't convert the error to `String`
29-
--> $DIR/question-mark-result-err-mismatch.rs:27:25
33+
--> $DIR/question-mark-result-err-mismatch.rs:30:25
3034
|
3135
LL | fn bar() -> Result<(), String> {
3236
| ------------------ expected `String` because of this
@@ -49,14 +53,15 @@ LL | .map_err(|_| ())?;
4953
= note: required for `Result<(), String>` to implement `FromResidual<Result<Infallible, ()>>`
5054

5155
error[E0277]: `?` couldn't convert the error to `String`
52-
--> $DIR/question-mark-result-err-mismatch.rs:46:11
56+
--> $DIR/question-mark-result-err-mismatch.rs:50:11
5357
|
5458
LL | fn baz() -> Result<String, String> {
5559
| ---------------------- expected `String` because of this
5660
...
5761
LL | .ok_or_else(|| {
5862
| __________-
5963
LL | | "Couldn't split the test string";
64+
| | - help: remove this semicolon
6065
LL | | })?;
6166
| | -^ the trait `From<()>` is not implemented for `String`
6267
| |__________|

0 commit comments

Comments
 (0)