@@ -3,15 +3,14 @@ use clippy_utils::diagnostics::{
33 multispan_sugg, span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then,
44} ;
55use clippy_utils:: macros:: { is_panic, root_macro_call} ;
6- use clippy_utils:: paths;
76use clippy_utils:: peel_blocks_with_stmt;
87use clippy_utils:: source:: { expr_block, indent_of, snippet, snippet_block, snippet_opt, snippet_with_applicability} ;
98use clippy_utils:: sugg:: Sugg ;
10- use clippy_utils:: ty:: { implements_trait , is_type_diagnostic_item, match_type , peel_mid_ty_refs } ;
9+ use clippy_utils:: ty:: is_type_diagnostic_item;
1110use clippy_utils:: visitors:: is_local_used;
1211use clippy_utils:: {
13- get_parent_expr, is_lang_ctor, is_lint_allowed , is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs,
14- path_to_local_id , peel_blocks, peel_hir_pat_refs, peel_n_hir_expr_refs , recurse_or_patterns, strip_pat_refs,
12+ get_parent_expr, is_lang_ctor, is_refutable, is_unit_expr, is_wild, meets_msrv, msrvs, path_to_local_id ,
13+ peel_blocks, peel_hir_pat_refs, recurse_or_patterns, strip_pat_refs,
1514} ;
1615use core:: iter:: once;
1716use if_chain:: if_chain;
@@ -20,19 +19,20 @@ use rustc_errors::Applicability;
2019use rustc_hir:: def:: { CtorKind , DefKind , Res } ;
2120use rustc_hir:: LangItem :: { OptionNone , OptionSome } ;
2221use rustc_hir:: {
23- self as hir, Arm , BindingAnnotation , Block , BorrowKind , Expr , ExprKind , Local , MatchSource , Mutability , Node , Pat ,
22+ self as hir, Arm , BindingAnnotation , BorrowKind , Expr , ExprKind , Local , MatchSource , Mutability , Node , Pat ,
2423 PatKind , PathSegment , QPath , RangeEnd , TyKind ,
2524} ;
2625use rustc_lint:: { LateContext , LateLintPass } ;
27- use rustc_middle:: ty:: { self , Ty , TyS , VariantDef } ;
26+ use rustc_middle:: ty:: { self , Ty , VariantDef } ;
2827use rustc_semver:: RustcVersion ;
2928use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
3029use rustc_span:: { sym, symbol:: kw, Span } ;
31- use std:: cmp:: { max , Ordering } ;
30+ use std:: cmp:: Ordering ;
3231
3332mod match_like_matches;
3433mod match_same_arms;
3534mod redundant_pattern_match;
35+ mod single_match;
3636
3737declare_clippy_lint ! {
3838 /// ### What it does
@@ -630,7 +630,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
630630 }
631631
632632 if let ExprKind :: Match ( ex, arms, MatchSource :: Normal ) = expr. kind {
633- check_single_match ( cx, ex, arms, expr) ;
633+ single_match :: check ( cx, ex, arms, expr) ;
634634 check_match_bool ( cx, ex, arms, expr) ;
635635 check_overlapping_arms ( cx, ex, arms) ;
636636 check_wild_err_arm ( cx, ex, arms) ;
@@ -710,262 +710,6 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
710710 extract_msrv_attr ! ( LateContext ) ;
711711}
712712
713- #[ rustfmt:: skip]
714- fn check_single_match ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > , arms : & [ Arm < ' _ > ] , expr : & Expr < ' _ > ) {
715- if arms. len ( ) == 2 && arms[ 0 ] . guard . is_none ( ) && arms[ 1 ] . guard . is_none ( ) {
716- if expr. span . from_expansion ( ) {
717- // Don't lint match expressions present in
718- // macro_rules! block
719- return ;
720- }
721- if let PatKind :: Or ( ..) = arms[ 0 ] . pat . kind {
722- // don't lint for or patterns for now, this makes
723- // the lint noisy in unnecessary situations
724- return ;
725- }
726- let els = arms[ 1 ] . body ;
727- let els = if is_unit_expr ( peel_blocks ( els) ) {
728- None
729- } else if let ExprKind :: Block ( Block { stmts, expr : block_expr, .. } , _) = els. kind {
730- if stmts. len ( ) == 1 && block_expr. is_none ( ) || stmts. is_empty ( ) && block_expr. is_some ( ) {
731- // single statement/expr "else" block, don't lint
732- return ;
733- }
734- // block with 2+ statements or 1 expr and 1+ statement
735- Some ( els)
736- } else {
737- // not a block, don't lint
738- return ;
739- } ;
740-
741- let ty = cx. typeck_results ( ) . expr_ty ( ex) ;
742- if * ty. kind ( ) != ty:: Bool || is_lint_allowed ( cx, MATCH_BOOL , ex. hir_id ) {
743- check_single_match_single_pattern ( cx, ex, arms, expr, els) ;
744- check_single_match_opt_like ( cx, ex, arms, expr, ty, els) ;
745- }
746- }
747- }
748-
749- fn check_single_match_single_pattern (
750- cx : & LateContext < ' _ > ,
751- ex : & Expr < ' _ > ,
752- arms : & [ Arm < ' _ > ] ,
753- expr : & Expr < ' _ > ,
754- els : Option < & Expr < ' _ > > ,
755- ) {
756- if is_wild ( arms[ 1 ] . pat ) {
757- report_single_match_single_pattern ( cx, ex, arms, expr, els) ;
758- }
759- }
760-
761- fn report_single_match_single_pattern (
762- cx : & LateContext < ' _ > ,
763- ex : & Expr < ' _ > ,
764- arms : & [ Arm < ' _ > ] ,
765- expr : & Expr < ' _ > ,
766- els : Option < & Expr < ' _ > > ,
767- ) {
768- let lint = if els. is_some ( ) { SINGLE_MATCH_ELSE } else { SINGLE_MATCH } ;
769- let els_str = els. map_or ( String :: new ( ) , |els| {
770- format ! ( " else {}" , expr_block( cx, els, None , ".." , Some ( expr. span) ) )
771- } ) ;
772-
773- let ( pat, pat_ref_count) = peel_hir_pat_refs ( arms[ 0 ] . pat ) ;
774- let ( msg, sugg) = if_chain ! {
775- if let PatKind :: Path ( _) | PatKind :: Lit ( _) = pat. kind;
776- let ( ty, ty_ref_count) = peel_mid_ty_refs( cx. typeck_results( ) . expr_ty( ex) ) ;
777- if let Some ( spe_trait_id) = cx. tcx. lang_items( ) . structural_peq_trait( ) ;
778- if let Some ( pe_trait_id) = cx. tcx. lang_items( ) . eq_trait( ) ;
779- if ty. is_integral( ) || ty. is_char( ) || ty. is_str( )
780- || ( implements_trait( cx, ty, spe_trait_id, & [ ] )
781- && implements_trait( cx, ty, pe_trait_id, & [ ty. into( ) ] ) ) ;
782- then {
783- // scrutinee derives PartialEq and the pattern is a constant.
784- let pat_ref_count = match pat. kind {
785- // string literals are already a reference.
786- PatKind :: Lit ( Expr { kind: ExprKind :: Lit ( lit) , .. } ) if lit. node. is_str( ) => pat_ref_count + 1 ,
787- _ => pat_ref_count,
788- } ;
789- // References are only implicitly added to the pattern, so no overflow here.
790- // e.g. will work: match &Some(_) { Some(_) => () }
791- // will not: match Some(_) { &Some(_) => () }
792- let ref_count_diff = ty_ref_count - pat_ref_count;
793-
794- // Try to remove address of expressions first.
795- let ( ex, removed) = peel_n_hir_expr_refs( ex, ref_count_diff) ;
796- let ref_count_diff = ref_count_diff - removed;
797-
798- let msg = "you seem to be trying to use `match` for an equality check. Consider using `if`" ;
799- let sugg = format!(
800- "if {} == {}{} {}{}" ,
801- snippet( cx, ex. span, ".." ) ,
802- // PartialEq for different reference counts may not exist.
803- "&" . repeat( ref_count_diff) ,
804- snippet( cx, arms[ 0 ] . pat. span, ".." ) ,
805- expr_block( cx, arms[ 0 ] . body, None , ".." , Some ( expr. span) ) ,
806- els_str,
807- ) ;
808- ( msg, sugg)
809- } else {
810- let msg = "you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`" ;
811- let sugg = format!(
812- "if let {} = {} {}{}" ,
813- snippet( cx, arms[ 0 ] . pat. span, ".." ) ,
814- snippet( cx, ex. span, ".." ) ,
815- expr_block( cx, arms[ 0 ] . body, None , ".." , Some ( expr. span) ) ,
816- els_str,
817- ) ;
818- ( msg, sugg)
819- }
820- } ;
821-
822- span_lint_and_sugg (
823- cx,
824- lint,
825- expr. span ,
826- msg,
827- "try this" ,
828- sugg,
829- Applicability :: HasPlaceholders ,
830- ) ;
831- }
832-
833- fn check_single_match_opt_like < ' a > (
834- cx : & LateContext < ' a > ,
835- ex : & Expr < ' _ > ,
836- arms : & [ Arm < ' _ > ] ,
837- expr : & Expr < ' _ > ,
838- ty : Ty < ' a > ,
839- els : Option < & Expr < ' _ > > ,
840- ) {
841- // list of candidate `Enum`s we know will never get any more members
842- let candidates = & [
843- ( & paths:: COW , "Borrowed" ) ,
844- ( & paths:: COW , "Cow::Borrowed" ) ,
845- ( & paths:: COW , "Cow::Owned" ) ,
846- ( & paths:: COW , "Owned" ) ,
847- ( & paths:: OPTION , "None" ) ,
848- ( & paths:: RESULT , "Err" ) ,
849- ( & paths:: RESULT , "Ok" ) ,
850- ] ;
851-
852- // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive
853- // match with the second branch, without enum variants in matches.
854- if !contains_only_wilds ( arms[ 1 ] . pat ) && !form_exhaustive_matches ( arms[ 0 ] . pat , arms[ 1 ] . pat ) {
855- return ;
856- }
857-
858- let mut paths_and_types = Vec :: new ( ) ;
859- if !collect_pat_paths ( & mut paths_and_types, cx, arms[ 1 ] . pat , ty) {
860- return ;
861- }
862-
863- let in_candidate_enum = |path_info : & ( String , & TyS < ' _ > ) | -> bool {
864- let ( path, ty) = path_info;
865- for & ( ty_path, pat_path) in candidates {
866- if path == pat_path && match_type ( cx, ty, ty_path) {
867- return true ;
868- }
869- }
870- false
871- } ;
872- if paths_and_types. iter ( ) . all ( in_candidate_enum) {
873- report_single_match_single_pattern ( cx, ex, arms, expr, els) ;
874- }
875- }
876-
877- /// Collects paths and their types from the given patterns. Returns true if the given pattern could
878- /// be simplified, false otherwise.
879- fn collect_pat_paths < ' a > ( acc : & mut Vec < ( String , Ty < ' a > ) > , cx : & LateContext < ' a > , pat : & Pat < ' _ > , ty : Ty < ' a > ) -> bool {
880- match pat. kind {
881- PatKind :: Wild => true ,
882- PatKind :: Tuple ( inner, _) => inner. iter ( ) . all ( |p| {
883- let p_ty = cx. typeck_results ( ) . pat_ty ( p) ;
884- collect_pat_paths ( acc, cx, p, p_ty)
885- } ) ,
886- PatKind :: TupleStruct ( ref path, ..) => {
887- let path = rustc_hir_pretty:: to_string ( rustc_hir_pretty:: NO_ANN , |s| {
888- s. print_qpath ( path, false ) ;
889- } ) ;
890- acc. push ( ( path, ty) ) ;
891- true
892- } ,
893- PatKind :: Binding ( BindingAnnotation :: Unannotated , .., ident, None ) => {
894- acc. push ( ( ident. to_string ( ) , ty) ) ;
895- true
896- } ,
897- PatKind :: Path ( ref path) => {
898- let path = rustc_hir_pretty:: to_string ( rustc_hir_pretty:: NO_ANN , |s| {
899- s. print_qpath ( path, false ) ;
900- } ) ;
901- acc. push ( ( path, ty) ) ;
902- true
903- } ,
904- _ => false ,
905- }
906- }
907-
908- /// Returns true if the given arm of pattern matching contains wildcard patterns.
909- fn contains_only_wilds ( pat : & Pat < ' _ > ) -> bool {
910- match pat. kind {
911- PatKind :: Wild => true ,
912- PatKind :: Tuple ( inner, _) | PatKind :: TupleStruct ( _, inner, ..) => inner. iter ( ) . all ( contains_only_wilds) ,
913- _ => false ,
914- }
915- }
916-
917- /// Returns true if the given patterns forms only exhaustive matches that don't contain enum
918- /// patterns without a wildcard.
919- fn form_exhaustive_matches ( left : & Pat < ' _ > , right : & Pat < ' _ > ) -> bool {
920- match ( & left. kind , & right. kind ) {
921- ( PatKind :: Wild , _) | ( _, PatKind :: Wild ) => true ,
922- ( PatKind :: Tuple ( left_in, left_pos) , PatKind :: Tuple ( right_in, right_pos) ) => {
923- // We don't actually know the position and the presence of the `..` (dotdot) operator
924- // in the arms, so we need to evaluate the correct offsets here in order to iterate in
925- // both arms at the same time.
926- let len = max (
927- left_in. len ( ) + {
928- if left_pos. is_some ( ) { 1 } else { 0 }
929- } ,
930- right_in. len ( ) + {
931- if right_pos. is_some ( ) { 1 } else { 0 }
932- } ,
933- ) ;
934- let mut left_pos = left_pos. unwrap_or ( usize:: MAX ) ;
935- let mut right_pos = right_pos. unwrap_or ( usize:: MAX ) ;
936- let mut left_dot_space = 0 ;
937- let mut right_dot_space = 0 ;
938- for i in 0 ..len {
939- let mut found_dotdot = false ;
940- if i == left_pos {
941- left_dot_space += 1 ;
942- if left_dot_space < len - left_in. len ( ) {
943- left_pos += 1 ;
944- }
945- found_dotdot = true ;
946- }
947- if i == right_pos {
948- right_dot_space += 1 ;
949- if right_dot_space < len - right_in. len ( ) {
950- right_pos += 1 ;
951- }
952- found_dotdot = true ;
953- }
954- if found_dotdot {
955- continue ;
956- }
957- if !contains_only_wilds ( & left_in[ i - left_dot_space] )
958- && !contains_only_wilds ( & right_in[ i - right_dot_space] )
959- {
960- return false ;
961- }
962- }
963- true
964- } ,
965- _ => false ,
966- }
967- }
968-
969713fn check_match_bool ( cx : & LateContext < ' _ > , ex : & Expr < ' _ > , arms : & [ Arm < ' _ > ] , expr : & Expr < ' _ > ) {
970714 // Type of expression is `bool`.
971715 if * cx. typeck_results ( ) . expr_ty ( ex) . kind ( ) == ty:: Bool {
0 commit comments