@@ -5,7 +5,7 @@ use crate::utils::usage::is_unused;
55use crate :: utils:: {
66 span_lint_and_help, span_lint_and_note,
77 expr_block, in_macro, is_allowed, is_expn_of, is_wild, match_qpath, match_type, multispan_sugg, remove_blocks,
8- snippet, snippet_with_applicability, span_lint_and_sugg, span_lint_and_then,
8+ snippet, snippet_block , snippet_with_applicability, span_lint_and_sugg, span_lint_and_then,
99} ;
1010use if_chain:: if_chain;
1111use rustc:: lint:: in_external_macro;
@@ -14,7 +14,7 @@ use rustc_errors::Applicability;
1414use rustc_hir:: def:: CtorKind ;
1515use rustc_hir:: * ;
1616use rustc_lint:: { LateContext , LateLintPass , LintContext } ;
17- use rustc_session:: { declare_lint_pass , declare_tool_lint } ;
17+ use rustc_session:: { declare_tool_lint , impl_lint_pass } ;
1818use rustc_span:: source_map:: Span ;
1919use std:: cmp:: Ordering ;
2020use std:: collections:: Bound ;
@@ -245,12 +245,47 @@ declare_clippy_lint! {
245245 "a wildcard pattern used with others patterns in same match arm"
246246}
247247
248+ declare_clippy_lint ! {
249+ /// **What it does:** Checks for matches being used to destructure a single-variant enum
250+ /// or tuple struct where a `let` will suffice.
251+ ///
252+ /// **Why is this bad?** Just readability – `let` doesn't nest, whereas a `match` does.
253+ ///
254+ /// **Known problems:** None.
255+ ///
256+ /// **Example:**
257+ /// ```rust
258+ /// enum Wrapper {
259+ /// Data(i32),
260+ /// }
261+ ///
262+ /// let wrapper = Wrapper::Data(42);
263+ ///
264+ /// let data = match wrapper {
265+ /// Wrapper::Data(i) => i,
266+ /// };
267+ /// ```
268+ ///
269+ /// The correct use would be:
270+ /// ```rust
271+ /// enum Wrapper {
272+ /// Data(i32),
273+ /// }
274+ ///
275+ /// let wrapper = Wrapper::Data(42);
276+ /// let Wrapper::Data(data) = wrapper;
277+ /// ```
278+ pub INFALLIBLE_DESTRUCTURING_MATCH ,
279+ style,
280+ "a `match` statement with a single infallible arm instead of a `let`"
281+ }
282+
248283declare_clippy_lint ! {
249284 /// **What it does:** Checks for useless match that binds to only one value.
250285 ///
251286 /// **Why is this bad?** Readability and needless complexity.
252287 ///
253- /// **Known problems:** This situation frequently happen in macros, so can't lint there .
288+ /// **Known problems:** None .
254289 ///
255290 /// **Example:**
256291 /// ```rust
@@ -272,7 +307,12 @@ declare_clippy_lint! {
272307 "a match with a single binding instead of using `let` statement"
273308}
274309
275- declare_lint_pass ! ( Matches => [
310+ #[ derive( Default ) ]
311+ pub struct Matches {
312+ infallible_destructuring_match_linted : bool ,
313+ }
314+
315+ impl_lint_pass ! ( Matches => [
276316 SINGLE_MATCH ,
277317 MATCH_REF_PATS ,
278318 MATCH_BOOL ,
@@ -283,6 +323,7 @@ declare_lint_pass!(Matches => [
283323 WILDCARD_ENUM_MATCH_ARM ,
284324 WILDCARD_IN_OR_PATTERNS ,
285325 MATCH_SINGLE_BINDING ,
326+ INFALLIBLE_DESTRUCTURING_MATCH
286327] ) ;
287328
288329impl < ' a , ' tcx > LateLintPass < ' a , ' tcx > for Matches {
@@ -298,12 +339,51 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> for Matches {
298339 check_wild_enum_match ( cx, ex, arms) ;
299340 check_match_as_ref ( cx, ex, arms, expr) ;
300341 check_wild_in_or_pats ( cx, arms) ;
301- check_match_single_binding ( cx, ex, arms, expr) ;
342+
343+ if self . infallible_destructuring_match_linted {
344+ self . infallible_destructuring_match_linted = false ;
345+ } else {
346+ check_match_single_binding ( cx, ex, arms, expr) ;
347+ }
302348 }
303349 if let ExprKind :: Match ( ref ex, ref arms, _) = expr. kind {
304350 check_match_ref_pats ( cx, ex, arms, expr) ;
305351 }
306352 }
353+
354+ fn check_local ( & mut self , cx : & LateContext < ' a , ' tcx > , local : & ' tcx Local < ' _ > ) {
355+ if_chain ! {
356+ if let Some ( ref expr) = local. init;
357+ if let ExprKind :: Match ( ref target, ref arms, MatchSource :: Normal ) = expr. kind;
358+ if arms. len( ) == 1 && arms[ 0 ] . guard. is_none( ) ;
359+ if let PatKind :: TupleStruct (
360+ QPath :: Resolved ( None , ref variant_name) , ref args, _) = arms[ 0 ] . pat. kind;
361+ if args. len( ) == 1 ;
362+ if let Some ( arg) = get_arg_name( & args[ 0 ] ) ;
363+ let body = remove_blocks( & arms[ 0 ] . body) ;
364+ if match_var( body, arg) ;
365+
366+ then {
367+ let mut applicability = Applicability :: MachineApplicable ;
368+ self . infallible_destructuring_match_linted = true ;
369+ span_lint_and_sugg(
370+ cx,
371+ INFALLIBLE_DESTRUCTURING_MATCH ,
372+ local. span,
373+ "you seem to be trying to use `match` to destructure a single infallible pattern. \
374+ Consider using `let`",
375+ "try this" ,
376+ format!(
377+ "let {}({}) = {};" ,
378+ snippet_with_applicability( cx, variant_name. span, ".." , & mut applicability) ,
379+ snippet_with_applicability( cx, local. pat. span, ".." , & mut applicability) ,
380+ snippet_with_applicability( cx, target. span, ".." , & mut applicability) ,
381+ ) ,
382+ applicability,
383+ ) ;
384+ }
385+ }
386+ }
307387}
308388
309389#[ rustfmt:: skip]
@@ -746,21 +826,31 @@ fn check_match_single_binding(cx: &LateContext<'_, '_>, ex: &Expr<'_>, arms: &[A
746826 return ;
747827 }
748828 if arms. len ( ) == 1 {
749- let bind_names = arms[ 0 ] . pat . span ;
750- let matched_vars = ex. span ;
751- span_lint_and_sugg (
752- cx,
753- MATCH_SINGLE_BINDING ,
754- expr. span ,
755- "this match could be written as a `let` statement" ,
756- "try this" ,
757- format ! (
758- "let {} = {};" ,
759- snippet( cx, bind_names, ".." ) ,
760- snippet( cx, matched_vars, ".." )
761- ) ,
762- Applicability :: HasPlaceholders ,
763- ) ;
829+ if is_refutable ( cx, arms[ 0 ] . pat ) {
830+ return ;
831+ }
832+ match arms[ 0 ] . pat . kind {
833+ PatKind :: Binding ( ..) | PatKind :: Tuple ( _, _) => {
834+ let bind_names = arms[ 0 ] . pat . span ;
835+ let matched_vars = ex. span ;
836+ let match_body = remove_blocks ( & arms[ 0 ] . body ) ;
837+ span_lint_and_sugg (
838+ cx,
839+ MATCH_SINGLE_BINDING ,
840+ expr. span ,
841+ "this match could be written as a `let` statement" ,
842+ "consider using `let` statement" ,
843+ format ! (
844+ "let {} = {};\n {}" ,
845+ snippet( cx, bind_names, ".." ) ,
846+ snippet( cx, matched_vars, ".." ) ,
847+ snippet_block( cx, match_body. span, ".." )
848+ ) ,
849+ Applicability :: MachineApplicable ,
850+ ) ;
851+ } ,
852+ _ => ( ) ,
853+ }
764854 }
765855}
766856
0 commit comments