1
1
use ruff_diagnostics:: { AlwaysFixableViolation , Diagnostic , Edit , Fix } ;
2
2
use ruff_macros:: { derive_message_formats, violation} ;
3
3
use ruff_python_ast as ast;
4
+ use ruff_python_ast:: comparable:: ComparableExpr ;
5
+ use ruff_python_ast:: ExprGenerator ;
4
6
use ruff_text_size:: { Ranged , TextSize } ;
5
7
6
8
use crate :: checkers:: ast:: Checker ;
@@ -10,37 +12,53 @@ use super::helpers;
10
12
11
13
/// ## What it does
12
14
/// Checks for unnecessary generators that can be rewritten as `set`
13
- /// comprehensions.
15
+ /// comprehensions (or with `set` directly) .
14
16
///
15
17
/// ## Why is this bad?
16
18
/// It is unnecessary to use `set` around a generator expression, since
17
19
/// there are equivalent comprehensions for these types. Using a
18
20
/// comprehension is clearer and more idiomatic.
19
21
///
22
+ /// Further, if the comprehension can be removed entirely, as in the case of
23
+ /// `set(x for x in foo)`, it's better to use `set(foo)` directly, since it's
24
+ /// even more direct.
25
+ ///
20
26
/// ## Examples
21
27
/// ```python
22
28
/// set(f(x) for x in foo)
29
+ /// set(x for x in foo)
23
30
/// ```
24
31
///
25
32
/// Use instead:
26
33
/// ```python
27
34
/// {f(x) for x in foo}
35
+ /// set(foo)
28
36
/// ```
29
37
///
30
38
/// ## Fix safety
31
39
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
32
40
/// when rewriting the call. In most cases, though, comments will be preserved.
33
41
#[ violation]
34
- pub struct UnnecessaryGeneratorSet ;
42
+ pub struct UnnecessaryGeneratorSet {
43
+ short_circuit : bool ,
44
+ }
35
45
36
46
impl AlwaysFixableViolation for UnnecessaryGeneratorSet {
37
47
#[ derive_message_formats]
38
48
fn message ( & self ) -> String {
39
- format ! ( "Unnecessary generator (rewrite as a `set` comprehension)" )
49
+ if self . short_circuit {
50
+ format ! ( "Unnecessary generator (rewrite using `set()`" )
51
+ } else {
52
+ format ! ( "Unnecessary generator (rewrite as a `set` comprehension)" )
53
+ }
40
54
}
41
55
42
56
fn fix_title ( & self ) -> String {
43
- "Rewrite as a `set` comprehension" . to_string ( )
57
+ if self . short_circuit {
58
+ "Rewrite using `set()`" . to_string ( )
59
+ } else {
60
+ "Rewrite as a `set` comprehension" . to_string ( )
61
+ }
44
62
}
45
63
}
46
64
@@ -57,28 +75,59 @@ pub(crate) fn unnecessary_generator_set(checker: &mut Checker, call: &ast::ExprC
57
75
if !checker. semantic ( ) . is_builtin ( "set" ) {
58
76
return ;
59
77
}
60
- if argument. is_generator_expr ( ) {
61
- let mut diagnostic = Diagnostic :: new ( UnnecessaryGeneratorSet , call. range ( ) ) ;
62
78
63
- // Convert `set(x for x in y)` to `{x for x in y}`.
64
- diagnostic. set_fix ( {
65
- // Replace `set(` with `}`.
66
- let call_start = Edit :: replacement (
67
- pad_start ( "{" , call. range ( ) , checker. locator ( ) , checker. semantic ( ) ) ,
68
- call. start ( ) ,
69
- call. arguments . start ( ) + TextSize :: from ( 1 ) ,
70
- ) ;
79
+ let Some ( ExprGenerator {
80
+ elt, generators, ..
81
+ } ) = argument. as_generator_expr ( )
82
+ else {
83
+ return ;
84
+ } ;
85
+
86
+ // Short-circuit: given `set(x for x in y)`, generate `set(y)` (in lieu of `{x for x in y}`).
87
+ if let [ generator] = generators. as_slice ( ) {
88
+ if generator. ifs . is_empty ( ) && !generator. is_async {
89
+ if ComparableExpr :: from ( elt) == ComparableExpr :: from ( & generator. target ) {
90
+ let mut diagnostic = Diagnostic :: new (
91
+ UnnecessaryGeneratorSet {
92
+ short_circuit : true ,
93
+ } ,
94
+ call. range ( ) ,
95
+ ) ;
96
+ let iterator = format ! ( "set({})" , checker. locator( ) . slice( generator. iter. range( ) ) ) ;
97
+ diagnostic. set_fix ( Fix :: unsafe_edit ( Edit :: range_replacement (
98
+ iterator,
99
+ call. range ( ) ,
100
+ ) ) ) ;
101
+ checker. diagnostics . push ( diagnostic) ;
102
+ return ;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Convert `set(f(x) for x in y)` to `{f(x) for x in y}`.
108
+ let mut diagnostic = Diagnostic :: new (
109
+ UnnecessaryGeneratorSet {
110
+ short_circuit : false ,
111
+ } ,
112
+ call. range ( ) ,
113
+ ) ;
114
+ diagnostic. set_fix ( {
115
+ // Replace `set(` with `}`.
116
+ let call_start = Edit :: replacement (
117
+ pad_start ( "{" , call. range ( ) , checker. locator ( ) , checker. semantic ( ) ) ,
118
+ call. start ( ) ,
119
+ call. arguments . start ( ) + TextSize :: from ( 1 ) ,
120
+ ) ;
71
121
72
- // Replace `)` with `}`.
73
- let call_end = Edit :: replacement (
74
- pad_end ( "}" , call. range ( ) , checker. locator ( ) , checker. semantic ( ) ) ,
75
- call. arguments . end ( ) - TextSize :: from ( 1 ) ,
76
- call. end ( ) ,
77
- ) ;
122
+ // Replace `)` with `}`.
123
+ let call_end = Edit :: replacement (
124
+ pad_end ( "}" , call. range ( ) , checker. locator ( ) , checker. semantic ( ) ) ,
125
+ call. arguments . end ( ) - TextSize :: from ( 1 ) ,
126
+ call. end ( ) ,
127
+ ) ;
78
128
79
- Fix :: unsafe_edits ( call_start, [ call_end] )
80
- } ) ;
129
+ Fix :: unsafe_edits ( call_start, [ call_end] )
130
+ } ) ;
81
131
82
- checker. diagnostics . push ( diagnostic) ;
83
- }
132
+ checker. diagnostics . push ( diagnostic) ;
84
133
}
0 commit comments