Skip to content

Commit 43871a9

Browse files
committed
Detect incorrect ; in Option::ok_or_else and Result::map_err
Fix rust-lang#72124.
1 parent a1d06a3 commit 43871a9

File tree

4 files changed

+82
-10
lines changed

4 files changed

+82
-10
lines changed

compiler/rustc_span/src/symbol.rs

+2
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,7 @@ symbols! {
977977
managed_boxes,
978978
manually_drop,
979979
map,
980+
map_err,
980981
marker,
981982
marker_trait_attr,
982983
masked,
@@ -1110,6 +1111,7 @@ symbols! {
11101111
off,
11111112
offset,
11121113
offset_of,
1114+
ok_or_else,
11131115
omit_gdb_pretty_printer_section,
11141116
on,
11151117
on_unimplemented,

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

+63-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;
@@ -1004,6 +1004,7 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
10041004
return;
10051005
}
10061006
let self_ty = trait_ref.self_ty();
1007+
let found_ty = trait_ref.args.get(1).and_then(|a| a.as_type());
10071008

10081009
let mut prev_ty = self.resolve_vars_if_possible(
10091010
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
@@ -1029,17 +1030,76 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
10291030

10301031
// The following logic is simlar to `point_at_chain`, but that's focused on associated types
10311032
let mut expr = expr;
1032-
while let hir::ExprKind::MethodCall(_path_segment, rcvr_expr, _args, span) = expr.kind {
1033+
while let hir::ExprKind::MethodCall(path_segment, rcvr_expr, args, span) = expr.kind {
10331034
// Point at every method call in the chain with the `Result` type.
10341035
// let foo = bar.iter().map(mapper)?;
10351036
// ------ -----------
10361037
expr = rcvr_expr;
10371038
chain.push((span, prev_ty));
10381039

1039-
prev_ty = self.resolve_vars_if_possible(
1040+
let next_ty = self.resolve_vars_if_possible(
10401041
typeck.expr_ty_adjusted_opt(expr).unwrap_or(Ty::new_misc_error(self.tcx)),
10411042
);
10421043

1044+
let is_diagnostic_item = |symbol: Symbol, ty: Ty<'tcx>| {
1045+
let ty::Adt(def, _) = ty.kind() else {
1046+
return false;
1047+
};
1048+
self.tcx.is_diagnostic_item(symbol, def.did())
1049+
};
1050+
// For each method in the chain, see if this is `Result::map_err` or
1051+
// `Option::ok_or_else` and if it is, see if the closure passed to it has an incorrect
1052+
// trailing `;`.
1053+
// Ideally we would instead use `FnCtxt::lookup_method_for_diagnostic` for 100%
1054+
// accurate check, but we are in the wrong stage to do that and looking for
1055+
// `Result::map_err` by checking the Self type and the path segment is enough.
1056+
// sym::ok_or_else
1057+
if let Some(ty) = get_e_type(prev_ty)
1058+
&& let Some(found_ty) = found_ty
1059+
&& (
1060+
(
1061+
path_segment.ident.name == sym::map_err
1062+
&& is_diagnostic_item(sym::Result, next_ty)
1063+
) || (
1064+
path_segment.ident.name == sym::ok_or_else
1065+
&& is_diagnostic_item(sym::Option, next_ty)
1066+
)
1067+
)
1068+
&& [sym::map_err, sym::ok_or_else].contains(&path_segment.ident.name)
1069+
&& let ty::Tuple(tys) = found_ty.kind()
1070+
&& tys.is_empty()
1071+
&& self.can_eq(obligation.param_env, ty, found_ty)
1072+
&& args.len() == 1
1073+
&& let Some(arg) = args.get(0)
1074+
&& let hir::ExprKind::Closure(closure) = arg.kind
1075+
&& let body = self.tcx.hir().body(closure.body)
1076+
&& let hir::ExprKind::Block(block, _) = body.value.kind
1077+
&& let None = block.expr
1078+
&& let [.., stmt] = block.stmts
1079+
&& let hir::StmtKind::Semi(expr) = stmt.kind
1080+
&& let expr_ty = self.resolve_vars_if_possible(
1081+
typeck.expr_ty_adjusted_opt(expr)
1082+
.unwrap_or(Ty::new_misc_error(self.tcx)),
1083+
)
1084+
&& self
1085+
.infcx
1086+
.type_implements_trait(
1087+
self.tcx.get_diagnostic_item(sym::From).unwrap(),
1088+
[self_ty, expr_ty],
1089+
obligation.param_env,
1090+
)
1091+
.must_apply_modulo_regions()
1092+
{
1093+
err.span_suggestion_short(
1094+
stmt.span.with_lo(expr.span.hi()),
1095+
"remove this semicolon",
1096+
String::new(),
1097+
Applicability::MachineApplicable,
1098+
);
1099+
}
1100+
1101+
prev_ty = next_ty;
1102+
10431103
if let hir::ExprKind::Path(hir::QPath::Resolved(None, path)) = expr.kind
10441104
&& let hir::Path { res: hir::def::Res::Local(hir_id), .. } = path
10451105
&& 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)