11use itertools:: Itertools ;
2- use rustc_hash:: { FxBuildHasher , FxHashMap } ;
2+ use rustc_hash:: { FxBuildHasher , FxHashMap , FxHashSet } ;
33
44use ast:: ExprContext ;
55use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
66use ruff_macros:: { derive_message_formats, violation} ;
7- use ruff_python_ast:: comparable:: ComparableExpr ;
7+ use ruff_python_ast:: comparable:: { ComparableExpr , HashableExpr } ;
88use ruff_python_ast:: helpers:: { any_over_expr, contains_effect} ;
99use ruff_python_ast:: { self as ast, BoolOp , CmpOp , Expr } ;
1010use ruff_python_semantic:: SemanticModel ;
@@ -51,12 +51,9 @@ impl AlwaysFixableViolation for RepeatedEqualityComparison {
5151 #[ derive_message_formats]
5252 fn message ( & self ) -> String {
5353 if let Some ( expression) = self . expression . full_display ( ) {
54- format ! (
55- "Consider merging multiple comparisons: `{expression}`. Use a `set` if the elements are hashable."
56- )
54+ format ! ( "Consider merging multiple comparisons: `{expression}`." )
5755 } else {
58- "Consider merging multiple comparisons. Use a `set` if the elements are hashable."
59- . to_string ( )
56+ "Consider merging multiple comparisons." . to_string ( )
6057 }
6158 }
6259
@@ -121,13 +118,20 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
121118 continue ;
122119 }
123120
121+ let mut seen_values =
122+ FxHashSet :: with_capacity_and_hasher ( comparators. len ( ) , FxBuildHasher ) ;
123+ let use_set = comparators
124+ . iter ( )
125+ . all ( |comparator| seen_values. insert ( HashableExpr :: from ( * comparator) ) ) ;
126+
124127 let mut diagnostic = Diagnostic :: new (
125128 RepeatedEqualityComparison {
126129 expression : SourceCodeSnippet :: new ( merged_membership_test (
127130 expr,
128131 bool_op. op ,
129132 & comparators,
130133 checker. locator ( ) ,
134+ use_set,
131135 ) ) ,
132136 } ,
133137 bool_op. range ( ) ,
@@ -140,6 +144,20 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
140144 let before = bool_op. values . iter ( ) . take ( * first) . cloned ( ) ;
141145 let after = bool_op. values . iter ( ) . skip ( last + 1 ) . cloned ( ) ;
142146
147+ let comparator = if use_set {
148+ Expr :: Set ( ast:: ExprSet {
149+ elts : comparators. iter ( ) . copied ( ) . cloned ( ) . collect ( ) ,
150+ range : TextRange :: default ( ) ,
151+ } )
152+ } else {
153+ Expr :: Tuple ( ast:: ExprTuple {
154+ elts : comparators. iter ( ) . copied ( ) . cloned ( ) . collect ( ) ,
155+ range : TextRange :: default ( ) ,
156+ ctx : ExprContext :: Load ,
157+ parenthesized : true ,
158+ } )
159+ } ;
160+
143161 diagnostic. set_fix ( Fix :: unsafe_edit ( Edit :: range_replacement (
144162 checker. generator ( ) . expr ( & Expr :: BoolOp ( ast:: ExprBoolOp {
145163 op : bool_op. op ,
@@ -150,12 +168,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
150168 BoolOp :: Or => Box :: from ( [ CmpOp :: In ] ) ,
151169 BoolOp :: And => Box :: from ( [ CmpOp :: NotIn ] ) ,
152170 } ,
153- comparators : Box :: from ( [ Expr :: Tuple ( ast:: ExprTuple {
154- elts : comparators. iter ( ) . copied ( ) . cloned ( ) . collect ( ) ,
155- range : TextRange :: default ( ) ,
156- ctx : ExprContext :: Load ,
157- parenthesized : true ,
158- } ) ] ) ,
171+ comparators : Box :: from ( [ comparator] ) ,
159172 range : bool_op. range ( ) ,
160173 } ) ) )
161174 . chain ( after)
@@ -231,11 +244,13 @@ fn to_allowed_value<'a>(
231244}
232245
233246/// Generate a string like `obj in (a, b, c)` or `obj not in (a, b, c)`.
247+ /// If `use_set` is `true`, the string will use a set instead of a tuple.
234248fn merged_membership_test (
235249 left : & Expr ,
236250 op : BoolOp ,
237251 comparators : & [ & Expr ] ,
238252 locator : & Locator ,
253+ use_set : bool ,
239254) -> String {
240255 let op = match op {
241256 BoolOp :: Or => "in" ,
@@ -246,5 +261,10 @@ fn merged_membership_test(
246261 . iter ( )
247262 . map ( |comparator| locator. slice ( comparator) )
248263 . join ( ", " ) ;
264+
265+ if use_set {
266+ return format ! ( "{left} {op} {{{members}}}" , ) ;
267+ }
268+
249269 format ! ( "{left} {op} ({members})" , )
250270}
0 commit comments