From 01882733c99dedfc075a6e9d0c9f866b7787c996 Mon Sep 17 00:00:00 2001 From: yukang Date: Thu, 29 Sep 2022 05:52:48 +0800 Subject: [PATCH] fix #102320, suggest unwrap_or_else when a closure is passed to unwrap_or instead of suggesting calling it --- .../src/check/fn_ctxt/suggestions.rs | 5 +- .../src/check/method/suggest.rs | 54 +++++++++++++++++++ .../suggestions/sugg-else-for-closure.fixed | 8 +++ .../ui/suggestions/sugg-else-for-closure.rs | 8 +++ .../suggestions/sugg-else-for-closure.stderr | 23 ++++++++ 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/suggestions/sugg-else-for-closure.fixed create mode 100644 src/test/ui/suggestions/sugg-else-for-closure.rs create mode 100644 src/test/ui/suggestions/sugg-else-for-closure.stderr diff --git a/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs b/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs index 05ed3b299726c..7b1a2ad35cdfe 100644 --- a/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_analysis/src/check/fn_ctxt/suggestions.rs @@ -65,7 +65,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } /// When encountering an fn-like type, try accessing the output of the type - /// // and suggesting calling it if it satisfies a predicate (i.e. if the + /// and suggesting calling it if it satisfies a predicate (i.e. if the /// output has a method or a field): /// ```compile_fail,E0308 /// fn foo(x: usize) -> usize { x } @@ -139,7 +139,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sugg, applicability, ); - return true; } false @@ -338,6 +337,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { err.span_suggestion(sp, &msg, suggestion, applicability); } + } else if self.suggest_else_fn_with_closure(err, expr, found, expected) + { } else if self.suggest_fn_call(err, expr, found, |output| self.can_coerce(output, expected)) && let ty::FnDef(def_id, ..) = &found.kind() && let Some(sp) = self.tcx.hir().span_if_local(*def_id) diff --git a/compiler/rustc_hir_analysis/src/check/method/suggest.rs b/compiler/rustc_hir_analysis/src/check/method/suggest.rs index 0e82e4956c7c1..ad1084bd1b1b6 100644 --- a/compiler/rustc_hir_analysis/src/check/method/suggest.rs +++ b/compiler/rustc_hir_analysis/src/check/method/suggest.rs @@ -2324,6 +2324,60 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` + /// FIXME: currently not working for suggesting `map_or_else`, see #102408 + pub(crate) fn suggest_else_fn_with_closure( + &self, + err: &mut Diagnostic, + expr: &hir::Expr<'_>, + found: Ty<'tcx>, + expected: Ty<'tcx>, + ) -> bool { + let Some((_def_id_or_name, output, _inputs)) = self.extract_callable_info(expr, found) + else { return false; }; + + if !self.can_coerce(output, expected) { + return false; + } + + let parent = self.tcx.hir().get_parent_node(expr.hir_id); + if let Some(Node::Expr(call_expr)) = self.tcx.hir().find(parent) && + let hir::ExprKind::MethodCall( + hir::PathSegment { ident: method_name, .. }, + self_expr, + args, + .., + ) = call_expr.kind && + let Some(self_ty) = self.typeck_results.borrow().expr_ty_opt(self_expr) { + let new_name = Ident { + name: Symbol::intern(&format!("{}_else", method_name.as_str())), + span: method_name.span, + }; + let probe = self.lookup_probe( + expr.span, + new_name, + self_ty, + self_expr, + ProbeScope::TraitsInScope, + ); + + // check the method arguments number + if let Ok(pick) = probe && + let fn_sig = self.tcx.fn_sig(pick.item.def_id) && + let fn_args = fn_sig.skip_binder().inputs() && + fn_args.len() == args.len() + 1 { + err.span_suggestion_verbose( + method_name.span.shrink_to_hi(), + &format!("try calling `{}` instead", new_name.name.as_str()), + "_else", + Applicability::MaybeIncorrect, + ); + return true; + } + } + false + } + /// Checks whether there is a local type somewhere in the chain of /// autoderefs of `rcvr_ty`. fn type_derefs_to_local( diff --git a/src/test/ui/suggestions/sugg-else-for-closure.fixed b/src/test/ui/suggestions/sugg-else-for-closure.fixed new file mode 100644 index 0000000000000..cf381d9da8be9 --- /dev/null +++ b/src/test/ui/suggestions/sugg-else-for-closure.fixed @@ -0,0 +1,8 @@ +// run-rustfix + +fn main() { + let x = "com.example.app"; + let y: Option<&str> = None; + let _s = y.unwrap_or_else(|| x.split('.').nth(1).unwrap()); + //~^ ERROR: mismatched types [E0308] +} diff --git a/src/test/ui/suggestions/sugg-else-for-closure.rs b/src/test/ui/suggestions/sugg-else-for-closure.rs new file mode 100644 index 0000000000000..540ced91fc950 --- /dev/null +++ b/src/test/ui/suggestions/sugg-else-for-closure.rs @@ -0,0 +1,8 @@ +// run-rustfix + +fn main() { + let x = "com.example.app"; + let y: Option<&str> = None; + let _s = y.unwrap_or(|| x.split('.').nth(1).unwrap()); + //~^ ERROR: mismatched types [E0308] +} diff --git a/src/test/ui/suggestions/sugg-else-for-closure.stderr b/src/test/ui/suggestions/sugg-else-for-closure.stderr new file mode 100644 index 0000000000000..55a0eee18179a --- /dev/null +++ b/src/test/ui/suggestions/sugg-else-for-closure.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/sugg-else-for-closure.rs:6:26 + | +LL | let _s = y.unwrap_or(|| x.split('.').nth(1).unwrap()); + | --------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `&str`, found closure + | | + | arguments to this function are incorrect + | + = note: expected reference `&str` + found closure `[closure@$DIR/sugg-else-for-closure.rs:6:26: 6:28]` +note: associated function defined here + --> $SRC_DIR/core/src/option.rs:LL:COL + | +LL | pub const fn unwrap_or(self, default: T) -> T + | ^^^^^^^^^ +help: try calling `unwrap_or_else` instead + | +LL | let _s = y.unwrap_or_else(|| x.split('.').nth(1).unwrap()); + | +++++ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`.