Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[flake8-comprehensions] improve autofix for C401, C402 and C417 #2806

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
[flake8-comprehensions] improve autofix for C401, C402 and C417
handle autofix when parent is f-string
  • Loading branch information
sbrugman committed Feb 12, 2023
commit d88123d649dbda80d5c41dbd1985be50f899ee39
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1036,9 +1036,9 @@ For more, see [flake8-comprehensions](https://pypi.org/project/flake8-comprehens

| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| C400 | unnecessary-generator-list | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | unnecessary-generator-set | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | unnecessary-generator-dict | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C400 | [unnecessary-generator-list](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-list.md) | Unnecessary generator (rewrite as a `list` comprehension) | 🛠 |
| C401 | [unnecessary-generator-set](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-set.md) | Unnecessary generator (rewrite as a `set` comprehension) | 🛠 |
| C402 | [unnecessary-generator-dict](https://github.com/charliermarsh/ruff/blob/main/docs/rules/unnecessary-generator-dict.md) | Unnecessary generator (rewrite as a `dict` comprehension) | 🛠 |
| C403 | unnecessary-list-comprehension-set | Unnecessary `list` comprehension (rewrite as a `set` comprehension) | 🛠 |
| C404 | unnecessary-list-comprehension-dict | Unnecessary `list` comprehension (rewrite as a `dict` comprehension) | 🛠 |
| C405 | unnecessary-literal-set | Unnecessary `{obj_type}` literal (rewrite as a `set` literal) | 🛠 |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
x = set(
x for x in range(3)
)

y = f'{set(a if a < 6 else 0 for a in range(3))}'
_ = '{}'.format(set(a if a < 6 else 0 for a in range(3)))
print(f'Hello {set(a for a in range(3))} World')

def set(*args, **kwargs):
return None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@
(x, x) for x in range(3)
)
dict(((x, x) for x in range(3)), z=3)
y = f'{dict((x, x) for x in range(3))}'
print(f'Hello {dict((x, x) for x in range(3))} World')
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
all(map(lambda v: isinstance(v, dict), nums))
filter(func, map(lambda v: v, nums))

# When inside f-string, then the fix should be surrounded by whitespace
_ = f"{set(map(lambda x: x % 2 == 0, nums))}"
_ = f"{dict(map(lambda v: (v, v**2), nums))}"

# Error, but unfixable.
# For simple expressions, this could be: `(x if x else 1 for x in nums)`.
# For more complex expressions, this would differ: `(x + 2 if x else 3 for x in nums)`.
Expand Down
14 changes: 12 additions & 2 deletions crates/ruff/src/checkers/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2434,12 +2434,22 @@ where
}
if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorSet) {
flake8_comprehensions::rules::unnecessary_generator_set(
self, expr, func, args, keywords,
self,
expr,
self.current_expr_parent().map(Into::into),
func,
args,
keywords,
);
}
if self.settings.rules.enabled(&Rule::UnnecessaryGeneratorDict) {
flake8_comprehensions::rules::unnecessary_generator_dict(
self, expr, func, args, keywords,
self,
expr,
self.current_expr_parent().map(Into::into),
func,
args,
keywords,
);
}
if self
Expand Down
40 changes: 37 additions & 3 deletions crates/ruff/src/rules/flake8_comprehensions/fixes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ pub fn fix_unnecessary_generator_set(
locator: &Locator,
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
parent: Option<&rustpython_parser::ast::Expr>,
) -> Result<Fix> {
// Expr(Call(GeneratorExp)))) -> Expr(SetComp)))
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
Expand Down Expand Up @@ -113,8 +114,17 @@ pub fn fix_unnecessary_generator_set(
};
tree.codegen(&mut state);

let mut content = state.to_string();

// If parent is f-string then surround with spaces
if let Some(parent_element) = parent {
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } = &parent_element.node {
content = format!(" {content} ");
}
}

Ok(Fix::replacement(
state.to_string(),
content,
expr.location,
expr.end_location.unwrap(),
))
Expand All @@ -126,6 +136,7 @@ pub fn fix_unnecessary_generator_dict(
locator: &Locator,
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
parent: Option<&rustpython_parser::ast::Expr>,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
let mut tree = match_module(module_text)?;
Expand Down Expand Up @@ -176,8 +187,17 @@ pub fn fix_unnecessary_generator_dict(
};
tree.codegen(&mut state);

let mut content = state.to_string();

// If parent is f-string then surround with spaces
if let Some(parent_element) = parent {
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } = &parent_element.node {
content = format!(" {content} ");
}
}

Ok(Fix::replacement(
state.to_string(),
content,
expr.location,
expr.end_location.unwrap(),
))
Expand Down Expand Up @@ -921,6 +941,7 @@ pub fn fix_unnecessary_map(
locator: &Locator,
stylist: &Stylist,
expr: &rustpython_parser::ast::Expr,
parent: Option<&rustpython_parser::ast::Expr>,
kind: &str,
) -> Result<Fix> {
let module_text = locator.slice_source_code_range(&Range::from_located(expr));
Expand Down Expand Up @@ -1061,8 +1082,21 @@ pub fn fix_unnecessary_map(
};
tree.codegen(&mut state);

let mut content = state.to_string();

// If parent is f-string then surround with spaces
if kind == "set" || kind == "dict" {
if let Some(parent_element) = parent {
if let &rustpython_parser::ast::ExprKind::FormattedValue { .. } =
&parent_element.node
{
content = format!(" {content} ");
}
}
}

Ok(Fix::replacement(
state.to_string(),
content,
expr.location,
expr.end_location.unwrap(),
))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ use crate::rules::flake8_comprehensions::fixes;
use crate::violation::AlwaysAutofixableViolation;

define_violation!(
/// ## What it does
/// Checks for unnecessary generator that can be rewritten as `dict` comprehension.
///
/// ## Why is this bad?
/// It is unnecessary to use `dict` around a generator expression, since there are
/// equivalent comprehensions for these types.
///
/// ## Examples
/// ```python
/// dict((x, f(x)) for x in foo)
/// ```
///
/// Use instead:
/// ```python
/// {x: f(x) for x in foo}
/// ```
///
pub struct UnnecessaryGeneratorDict;
);
impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict {
Expand All @@ -27,6 +44,7 @@ impl AlwaysAutofixableViolation for UnnecessaryGeneratorDict {
pub fn unnecessary_generator_dict(
checker: &mut Checker,
expr: &Expr,
parent: Option<&Expr>,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
Expand All @@ -44,6 +62,7 @@ pub fn unnecessary_generator_dict(
checker.locator,
checker.stylist,
expr,
parent,
) {
Ok(fix) => {
diagnostic.amend(fix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ use crate::rules::flake8_comprehensions::fixes;
use crate::violation::AlwaysAutofixableViolation;

define_violation!(
/// ## What it does
/// Checks for unnecessary generator that can be rewritten as `list` comprehension.
///
/// ## Why is this bad?
/// It is unnecessary to use `list` around a generator expression, since there are
/// equivalent comprehensions for these types.
///
/// ## Examples
/// ```python
/// list(f(x) for x in foo)
/// ```
///
/// Use instead:
/// ```python
/// [f(x) for x in foo]
/// ```
///
pub struct UnnecessaryGeneratorList;
);
impl AlwaysAutofixableViolation for UnnecessaryGeneratorList {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ use crate::rules::flake8_comprehensions::fixes;
use crate::violation::AlwaysAutofixableViolation;

define_violation!(
/// ## What it does
/// Checks for unnecessary generator that can be rewritten as `set` comprehension.
///
/// ## Why is this bad?
/// It is unnecessary to use `set` around a generator expression, since there are
/// equivalent comprehensions for these types.
///
/// ## Examples
/// ```python
/// set(f(x) for x in foo)
/// ```
///
/// Use instead:
/// ```python
/// {f(x) for x in foo}
/// ```
///
pub struct UnnecessaryGeneratorSet;
);
impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet {
Expand All @@ -27,6 +44,7 @@ impl AlwaysAutofixableViolation for UnnecessaryGeneratorSet {
pub fn unnecessary_generator_set(
checker: &mut Checker,
expr: &Expr,
parent: Option<&Expr>,
func: &Expr,
args: &[Expr],
keywords: &[Keyword],
Expand All @@ -40,7 +58,12 @@ pub fn unnecessary_generator_set(
if let ExprKind::GeneratorExp { .. } = argument {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorSet, Range::from_located(expr));
if checker.patch(diagnostic.kind.rule()) {
match fixes::fix_unnecessary_generator_set(checker.locator, checker.stylist, expr) {
match fixes::fix_unnecessary_generator_set(
checker.locator,
checker.stylist,
expr,
parent,
) {
Ok(fix) => {
diagnostic.amend(fix);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub fn unnecessary_map(
checker.locator,
checker.stylist,
expr,
parent,
"generator",
) {
Ok(fix) => {
Expand Down Expand Up @@ -141,6 +142,7 @@ pub fn unnecessary_map(
checker.locator,
checker.stylist,
expr,
parent,
id,
) {
Ok(fix) => {
Expand Down Expand Up @@ -173,6 +175,7 @@ pub fn unnecessary_map(
checker.locator,
checker.stylist,
expr,
parent,
id,
) {
Ok(fix) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/rules/flake8_comprehensions/mod.rs
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
expression: diagnostics
---
- kind:
Expand Down Expand Up @@ -40,4 +40,58 @@ expression: diagnostics
row: 4
column: 1
parent: ~
- kind:
UnnecessaryGeneratorSet: ~
location:
row: 5
column: 7
end_location:
row: 5
column: 48
fix:
content:
- " {a if a < 6 else 0 for a in range(3)} "
location:
row: 5
column: 7
end_location:
row: 5
column: 48
parent: ~
- kind:
UnnecessaryGeneratorSet: ~
location:
row: 6
column: 16
end_location:
row: 6
column: 57
fix:
content:
- "{a if a < 6 else 0 for a in range(3)}"
location:
row: 6
column: 16
end_location:
row: 6
column: 57
parent: ~
- kind:
UnnecessaryGeneratorSet: ~
location:
row: 7
column: 15
end_location:
row: 7
column: 39
fix:
content:
- " {a for a in range(3)} "
location:
row: 7
column: 15
end_location:
row: 7
column: 39
parent: ~

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
source: src/rules/flake8_comprehensions/mod.rs
source: crates/ruff/src/rules/flake8_comprehensions/mod.rs
expression: diagnostics
---
- kind:
Expand Down Expand Up @@ -40,4 +40,40 @@ expression: diagnostics
row: 4
column: 1
parent: ~
- kind:
UnnecessaryGeneratorDict: ~
location:
row: 6
column: 7
end_location:
row: 6
column: 37
fix:
content:
- " {x: x for x in range(3)} "
location:
row: 6
column: 7
end_location:
row: 6
column: 37
parent: ~
- kind:
UnnecessaryGeneratorDict: ~
location:
row: 7
column: 15
end_location:
row: 7
column: 45
fix:
content:
- " {x: x for x in range(3)} "
location:
row: 7
column: 15
end_location:
row: 7
column: 45
parent: ~

Loading