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,9 +51,7 @@ 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}`. Use a `set` if the elements are hashable." )
5755 } else {
5856 "Consider merging multiple comparisons. Use a `set` if the elements are hashable."
5957 . to_string ( )
@@ -121,13 +119,20 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
121119 continue ;
122120 }
123121
122+ let mut seen_values =
123+ FxHashSet :: with_capacity_and_hasher ( comparators. len ( ) , FxBuildHasher ) ;
124+ let use_set = comparators. iter ( ) . all ( |comparator| {
125+ comparator. is_literal_expr ( ) && seen_values. insert ( HashableExpr :: from ( * comparator) )
126+ } ) ;
127+
124128 let mut diagnostic = Diagnostic :: new (
125129 RepeatedEqualityComparison {
126130 expression : SourceCodeSnippet :: new ( merged_membership_test (
127131 expr,
128132 bool_op. op ,
129133 & comparators,
130134 checker. locator ( ) ,
135+ use_set,
131136 ) ) ,
132137 } ,
133138 bool_op. range ( ) ,
@@ -140,6 +145,20 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
140145 let before = bool_op. values . iter ( ) . take ( * first) . cloned ( ) ;
141146 let after = bool_op. values . iter ( ) . skip ( last + 1 ) . cloned ( ) ;
142147
148+ let comparator = if use_set {
149+ Expr :: Set ( ast:: ExprSet {
150+ elts : comparators. iter ( ) . copied ( ) . cloned ( ) . collect ( ) ,
151+ range : TextRange :: default ( ) ,
152+ } )
153+ } else {
154+ Expr :: Tuple ( ast:: ExprTuple {
155+ elts : comparators. iter ( ) . copied ( ) . cloned ( ) . collect ( ) ,
156+ range : TextRange :: default ( ) ,
157+ ctx : ExprContext :: Load ,
158+ parenthesized : true ,
159+ } )
160+ } ;
161+
143162 diagnostic. set_fix ( Fix :: unsafe_edit ( Edit :: range_replacement (
144163 checker. generator ( ) . expr ( & Expr :: BoolOp ( ast:: ExprBoolOp {
145164 op : bool_op. op ,
@@ -150,12 +169,7 @@ pub(crate) fn repeated_equality_comparison(checker: &mut Checker, bool_op: &ast:
150169 BoolOp :: Or => Box :: from ( [ CmpOp :: In ] ) ,
151170 BoolOp :: And => Box :: from ( [ CmpOp :: NotIn ] ) ,
152171 } ,
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- } ) ] ) ,
172+ comparators : Box :: from ( [ comparator] ) ,
159173 range : bool_op. range ( ) ,
160174 } ) ) )
161175 . chain ( after)
@@ -231,11 +245,13 @@ fn to_allowed_value<'a>(
231245}
232246
233247/// Generate a string like `obj in (a, b, c)` or `obj not in (a, b, c)`.
248+ /// If `use_set` is `true`, the string will use a set instead of a tuple.
234249fn merged_membership_test (
235250 left : & Expr ,
236251 op : BoolOp ,
237252 comparators : & [ & Expr ] ,
238253 locator : & Locator ,
254+ use_set : bool ,
239255) -> String {
240256 let op = match op {
241257 BoolOp :: Or => "in" ,
@@ -246,5 +262,10 @@ fn merged_membership_test(
246262 . iter ( )
247263 . map ( |comparator| locator. slice ( comparator) )
248264 . join ( ", " ) ;
265+
266+ if use_set {
267+ return format ! ( "{left} {op} {{{members}}}" , ) ;
268+ }
269+
249270 format ! ( "{left} {op} ({members})" , )
250271}
0 commit comments