Compare commits

..

1 Commits

Author SHA1 Message Date
konstin
7f97547b5f Add increment/decrement 2024-03-14 16:56:06 +01:00
48 changed files with 23725 additions and 23717 deletions

View File

@@ -1,49 +1,5 @@
# Changelog
## 0.3.3
### Preview features
- \[`flake8-bandit`\]: Implement `S610` rule ([#10316](https://github.com/astral-sh/ruff/pull/10316))
- \[`pycodestyle`\] Implement `blank-line-at-end-of-file` (`W391`) ([#10243](https://github.com/astral-sh/ruff/pull/10243))
- \[`pycodestyle`\] Implement `redundant-backslash` (`E502`) ([#10292](https://github.com/astral-sh/ruff/pull/10292))
- \[`pylint`\] - implement `redeclared-assigned-name` (`W0128`) ([#9268](https://github.com/astral-sh/ruff/pull/9268))
### Rule changes
- \[`flake8_comprehensions`\] Handled special case for `C400` which also matches `C416` ([#10419](https://github.com/astral-sh/ruff/pull/10419))
- \[`flake8-bandit`\] Implement upstream updates for `S311`, `S324` and `S605` ([#10313](https://github.com/astral-sh/ruff/pull/10313))
- \[`pyflakes`\] Remove `F401` fix for `__init__` imports by default and allow opt-in to unsafe fix ([#10365](https://github.com/astral-sh/ruff/pull/10365))
- \[`pylint`\] Implement `invalid-bool-return-type` (`E304`) ([#10377](https://github.com/astral-sh/ruff/pull/10377))
- \[`pylint`\] Include builtin warnings in useless-exception-statement (`PLW0133`) ([#10394](https://github.com/astral-sh/ruff/pull/10394))
### CLI
- Add message on success to `ruff check` ([#8631](https://github.com/astral-sh/ruff/pull/8631))
### Bug fixes
- \[`PIE970`\] Allow trailing ellipsis in `typing.TYPE_CHECKING` ([#10413](https://github.com/astral-sh/ruff/pull/10413))
- Avoid `TRIO115` if the argument is a variable ([#10376](https://github.com/astral-sh/ruff/pull/10376))
- \[`F811`\] Avoid removing shadowed imports that point to different symbols ([#10387](https://github.com/astral-sh/ruff/pull/10387))
- Fix `F821` and `F822` false positives in `.pyi` files ([#10341](https://github.com/astral-sh/ruff/pull/10341))
- Fix `F821` false negatives in `.py` files when `from __future__ import annotations` is active ([#10362](https://github.com/astral-sh/ruff/pull/10362))
- Fix case where `Indexer` fails to identify continuation preceded by newline #10351 ([#10354](https://github.com/astral-sh/ruff/pull/10354))
- Sort hash maps in `Settings` display ([#10370](https://github.com/astral-sh/ruff/pull/10370))
- Track conditional deletions in the semantic model ([#10415](https://github.com/astral-sh/ruff/pull/10415))
- \[`C413`\] Wrap expressions in parentheses when negating ([#10346](https://github.com/astral-sh/ruff/pull/10346))
- \[`pycodestyle`\] Do not ignore lines before the first logical line in blank lines rules. ([#10382](https://github.com/astral-sh/ruff/pull/10382))
- \[`pycodestyle`\] Do not trigger `E225` and `E275` when the next token is a ')' ([#10315](https://github.com/astral-sh/ruff/pull/10315))
- \[`pylint`\] Avoid false-positive slot non-assignment for `__dict__` (`PLE0237`) ([#10348](https://github.com/astral-sh/ruff/pull/10348))
- Gate f-string struct size test for Rustc \< 1.76 ([#10371](https://github.com/astral-sh/ruff/pull/10371))
### Documentation
- Use `ruff.toml` format in README ([#10393](https://github.com/astral-sh/ruff/pull/10393))
- \[`RUF008`\] Make it clearer that a mutable default in a dataclass is only valid if it is typed as a ClassVar ([#10395](https://github.com/astral-sh/ruff/pull/10395))
- \[`pylint`\] Extend docs and test in `invalid-str-return-type` (`E307`) ([#10400](https://github.com/astral-sh/ruff/pull/10400))
- Remove `.` from `check` and `format` commands ([#10217](https://github.com/astral-sh/ruff/pull/10217))
## 0.3.2
### Preview features
@@ -1243,7 +1199,7 @@ Read Ruff's new [versioning policy](https://docs.astral.sh/ruff/versioning/).
- \[`refurb`\] Add `single-item-membership-test` (`FURB171`) ([#7815](https://github.com/astral-sh/ruff/pull/7815))
- \[`pylint`\] Add `and-or-ternary` (`R1706`) ([#7811](https://github.com/astral-sh/ruff/pull/7811))
*New rules are added in [preview](https://docs.astral.sh/ruff/preview/).*
_New rules are added in [preview](https://docs.astral.sh/ruff/preview/)._
### Configuration

6
Cargo.lock generated
View File

@@ -2003,7 +2003,7 @@ dependencies = [
[[package]]
name = "ruff"
version = "0.3.3"
version = "0.3.2"
dependencies = [
"anyhow",
"argfile",
@@ -2167,7 +2167,7 @@ dependencies = [
[[package]]
name = "ruff_linter"
version = "0.3.3"
version = "0.3.2"
dependencies = [
"aho-corasick",
"annotate-snippets 0.9.2",
@@ -2448,7 +2448,7 @@ dependencies = [
[[package]]
name = "ruff_shrinking"
version = "0.3.3"
version = "0.3.2"
dependencies = [
"anyhow",
"clap",

View File

@@ -151,7 +151,7 @@ Ruff can also be used as a [pre-commit](https://pre-commit.com/) hook via [`ruff
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.3
rev: v0.3.2
hooks:
# Run the linter.
- id: ruff

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff"
version = "0.3.3"
version = "0.3.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_linter"
version = "0.3.3"
version = "0.3.2"
publish = false
authors = { workspace = true }
edition = { workspace = true }

View File

@@ -1,20 +1,11 @@
# Cannot combine with C416. Should use list comprehension here.
even_nums = list(2 * x for x in range(3))
odd_nums = list(
2 * x + 1 for x in range(3)
)
# Short-circuit case, combine with C416 and should produce x = list(range(3))
x = list(x for x in range(3))
x = list(
x for x in range(3)
)
# Not built-in list.
def list(*args, **kwargs):
return None
list(2 * x for x in range(3))
list(x for x in range(3))

View File

@@ -227,11 +227,3 @@ class Repro[int](Protocol):
def impl(self) -> str:
"""Docstring"""
return self.func()
import typing
if typing.TYPE_CHECKING:
def contains_meaningful_ellipsis() -> list[int]:
"""Allow this in a TYPE_CHECKING block."""
...

View File

@@ -11,13 +11,6 @@ def f():
print(X)
def f():
global X
if X > 0:
del X
###
# Non-errors.
###

View File

@@ -1,6 +0,0 @@
FIRST, FIRST = (1, 2) # PLW0128
FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK

View File

@@ -1,6 +1,6 @@
use ruff_diagnostics::{Diagnostic, Fix};
use ruff_python_semantic::analyze::visibility;
use ruff_python_semantic::{Binding, BindingKind, Imported, ResolvedReference, ScopeKind};
use ruff_python_semantic::{Binding, BindingKind, Imported, ScopeKind};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
@@ -91,29 +91,13 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
if checker.enabled(Rule::GlobalVariableNotAssigned) {
for (name, binding_id) in scope.bindings() {
let binding = checker.semantic.binding(binding_id);
// If the binding is a `global`, then it's a top-level `global` that was never
// assigned in the current scope. If it were assigned, the `global` would be
// shadowed by the assignment.
if binding.kind.is_global() {
// If the binding was conditionally deleted, it will include a reference within
// a `Del` context, but won't be shadowed by a `BindingKind::Deletion`, as in:
// ```python
// if condition:
// del var
// ```
if binding
.references
.iter()
.map(|id| checker.semantic.reference(*id))
.all(ResolvedReference::is_load)
{
diagnostics.push(Diagnostic::new(
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
binding.range(),
));
}
diagnostics.push(Diagnostic::new(
pylint::rules::GlobalVariableNotAssigned {
name: (*name).to_string(),
},
binding.range(),
));
}
}
}

View File

@@ -1389,9 +1389,6 @@ pub(crate) fn statement(stmt: &Stmt, checker: &mut Checker) {
}
}
Stmt::Assign(assign @ ast::StmtAssign { targets, value, .. }) => {
if checker.enabled(Rule::RedeclaredAssignedName) {
pylint::rules::redeclared_assigned_name(checker, targets);
}
if checker.enabled(Rule::LambdaAssignment) {
if let [target] = &targets[..] {
pycodestyle::rules::lambda_assignment(checker, target, value, None, stmt);

View File

@@ -540,11 +540,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
for name in names {
if let Some((scope_id, binding_id)) = self.semantic.nonlocal(name) {
// Mark the binding as "used".
self.semantic.add_local_reference(
binding_id,
ExprContext::Load,
name.range(),
);
self.semantic.add_local_reference(binding_id, name.range());
// Mark the binding in the enclosing scope as "rebound" in the current
// scope.
@@ -2117,8 +2113,7 @@ impl<'a> Checker<'a> {
// Mark anything referenced in `__all__` as used.
// TODO(charlie): `range` here should be the range of the name in `__all__`, not
// the range of `__all__` itself.
self.semantic
.add_global_reference(binding_id, ExprContext::Load, range);
self.semantic.add_global_reference(binding_id, range);
} else {
if self.semantic.global_scope().uses_star_imports() {
if self.enabled(Rule::UndefinedLocalWithImportStarUsage) {

View File

@@ -294,7 +294,6 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Pylint, "W0108") => (RuleGroup::Preview, rules::pylint::rules::UnnecessaryLambda),
(Pylint, "W0120") => (RuleGroup::Stable, rules::pylint::rules::UselessElseOnLoop),
(Pylint, "W0127") => (RuleGroup::Stable, rules::pylint::rules::SelfAssigningVariable),
(Pylint, "W0128") => (RuleGroup::Preview, rules::pylint::rules::RedeclaredAssignedName),
(Pylint, "W0129") => (RuleGroup::Stable, rules::pylint::rules::AssertOnStringLiteral),
(Pylint, "W0131") => (RuleGroup::Stable, rules::pylint::rules::NamedExprWithoutContext),
(Pylint, "W0133") => (RuleGroup::Preview, rules::pylint::rules::UselessExceptionStatement),

View File

@@ -255,7 +255,6 @@ impl Renamer {
| BindingKind::ClassDefinition(_)
| BindingKind::FunctionDefinition(_)
| BindingKind::Deletion
| BindingKind::ConditionalDeletion(_)
| BindingKind::UnboundException(_) => {
Some(Edit::range_replacement(target.to_string(), binding.range()))
}

View File

@@ -1,8 +1,6 @@
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_ast as ast;
use ruff_python_ast::comparable::ComparableExpr;
use ruff_python_ast::ExprGenerator;
use ruff_text_size::{Ranged, TextSize};
use crate::checkers::ast::Checker;
@@ -11,53 +9,37 @@ use super::helpers;
/// ## What it does
/// Checks for unnecessary generators that can be rewritten as `list`
/// comprehensions (or with `list` directly).
/// comprehensions.
///
/// ## Why is this bad?
/// It is unnecessary to use `list` around a generator expression, since
/// there are equivalent comprehensions for these types. Using a
/// comprehension is clearer and more idiomatic.
///
/// Further, if the comprehension can be removed entirely, as in the case of
/// `list(x for x in foo)`, it's better to use `list(foo)` directly, since it's
/// even more direct.
///
/// ## Examples
/// ```python
/// list(f(x) for x in foo)
/// list(x for x in foo)
/// ```
///
/// Use instead:
/// ```python
/// [f(x) for x in foo]
/// list(foo)
/// ```
///
/// ## Fix safety
/// This rule's fix is marked as unsafe, as it may occasionally drop comments
/// when rewriting the call. In most cases, though, comments will be preserved.
#[violation]
pub struct UnnecessaryGeneratorList {
short_circuit: bool,
}
pub struct UnnecessaryGeneratorList;
impl AlwaysFixableViolation for UnnecessaryGeneratorList {
#[derive_message_formats]
fn message(&self) -> String {
if self.short_circuit {
format!("Unnecessary generator (rewrite using `list()`")
} else {
format!("Unnecessary generator (rewrite as a `list` comprehension)")
}
format!("Unnecessary generator (rewrite as a `list` comprehension)")
}
fn fix_title(&self) -> String {
if self.short_circuit {
"Rewrite using `list()`".to_string()
} else {
"Rewrite as a `list` comprehension".to_string()
}
"Rewrite as a `list` comprehension".to_string()
}
}
@@ -74,59 +56,28 @@ pub(crate) fn unnecessary_generator_list(checker: &mut Checker, call: &ast::Expr
if !checker.semantic().is_builtin("list") {
return;
}
if argument.is_generator_expr() {
let mut diagnostic = Diagnostic::new(UnnecessaryGeneratorList, call.range());
let Some(ExprGenerator {
elt, generators, ..
}) = argument.as_generator_expr()
else {
return;
};
// Convert `list(x for x in y)` to `[x for x in y]`.
diagnostic.set_fix({
// Replace `list(` with `[`.
let call_start = Edit::replacement(
"[".to_string(),
call.start(),
call.arguments.start() + TextSize::from(1),
);
// Short-circuit: given `list(x for x in y)`, generate `list(y)` (in lieu of `[x for x in y]`).
if let [generator] = generators.as_slice() {
if generator.ifs.is_empty() && !generator.is_async {
if ComparableExpr::from(elt) == ComparableExpr::from(&generator.target) {
let mut diagnostic = Diagnostic::new(
UnnecessaryGeneratorList {
short_circuit: true,
},
call.range(),
);
let iterator = format!("list({})", checker.locator().slice(generator.iter.range()));
diagnostic.set_fix(Fix::unsafe_edit(Edit::range_replacement(
iterator,
call.range(),
)));
checker.diagnostics.push(diagnostic);
return;
}
}
// Replace `)` with `]`.
let call_end = Edit::replacement(
"]".to_string(),
call.arguments.end() - TextSize::from(1),
call.end(),
);
Fix::unsafe_edits(call_start, [call_end])
});
checker.diagnostics.push(diagnostic);
}
// Convert `list(f(x) for x in y)` to `[f(x) for x in y]`.
let mut diagnostic = Diagnostic::new(
UnnecessaryGeneratorList {
short_circuit: false,
},
call.range(),
);
diagnostic.set_fix({
// Replace `list(` with `[`.
let call_start = Edit::replacement(
"[".to_string(),
call.start(),
call.arguments.start() + TextSize::from(1),
);
// Replace `)` with `]`.
let call_end = Edit::replacement(
"]".to_string(),
call.arguments.end() - TextSize::from(1),
call.end(),
);
Fix::unsafe_edits(call_start, [call_end])
});
checker.diagnostics.push(diagnostic);
}

View File

@@ -1,90 +1,42 @@
---
source: crates/ruff_linter/src/rules/flake8_comprehensions/mod.rs
---
C400.py:2:13: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
C400.py:1:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
1 | # Cannot combine with C416. Should use list comprehension here.
2 | even_nums = list(2 * x for x in range(3))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ C400
3 | odd_nums = list(
4 | 2 * x + 1 for x in range(3)
1 | x = list(x for x in range(3))
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
2 | x = list(
3 | x for x in range(3)
|
= help: Rewrite as a `list` comprehension
Unsafe fix
1 1 | # Cannot combine with C416. Should use list comprehension here.
2 |-even_nums = list(2 * x for x in range(3))
2 |+even_nums = [2 * x for x in range(3)]
3 3 | odd_nums = list(
4 4 | 2 * x + 1 for x in range(3)
5 5 | )
1 |-x = list(x for x in range(3))
1 |+x = [x for x in range(3)]
2 2 | x = list(
3 3 | x for x in range(3)
4 4 | )
C400.py:3:12: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
C400.py:2:5: C400 [*] Unnecessary generator (rewrite as a `list` comprehension)
|
1 | # Cannot combine with C416. Should use list comprehension here.
2 | even_nums = list(2 * x for x in range(3))
3 | odd_nums = list(
| ____________^
4 | | 2 * x + 1 for x in range(3)
5 | | )
1 | x = list(x for x in range(3))
2 | x = list(
| _____^
3 | | x for x in range(3)
4 | | )
| |_^ C400
|
= help: Rewrite as a `list` comprehension
Unsafe fix
1 1 | # Cannot combine with C416. Should use list comprehension here.
2 2 | even_nums = list(2 * x for x in range(3))
3 |-odd_nums = list(
3 |+odd_nums = [
4 4 | 2 * x + 1 for x in range(3)
5 |-)
5 |+]
1 1 | x = list(x for x in range(3))
2 |-x = list(
2 |+x = [
3 3 | x for x in range(3)
4 |-)
4 |+]
5 5 |
6 6 |
7 7 |
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
7 7 | def list(*args, **kwargs):
C400.py:9:5: C400 [*] Unnecessary generator (rewrite using `list()`
|
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
9 | x = list(x for x in range(3))
| ^^^^^^^^^^^^^^^^^^^^^^^^^ C400
10 | x = list(
11 | x for x in range(3)
|
= help: Rewrite using `list()`
Unsafe fix
6 6 |
7 7 |
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
9 |-x = list(x for x in range(3))
9 |+x = list(range(3))
10 10 | x = list(
11 11 | x for x in range(3)
12 12 | )
C400.py:10:5: C400 [*] Unnecessary generator (rewrite using `list()`
|
8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
9 | x = list(x for x in range(3))
10 | x = list(
| _____^
11 | | x for x in range(3)
12 | | )
| |_^ C400
13 |
14 | # Not built-in list.
|
= help: Rewrite using `list()`
Unsafe fix
7 7 |
8 8 | # Short-circuit case, combine with C416 and should produce x = list(range(3))
9 9 | x = list(x for x in range(3))
10 |-x = list(
11 |- x for x in range(3)
12 |-)
10 |+x = list(range(3))
13 11 |
14 12 | # Not built-in list.
15 13 | def list(*args, **kwargs):

View File

@@ -87,12 +87,6 @@ pub(crate) fn unnecessary_placeholder(checker: &mut Checker, body: &[Stmt]) {
let kind = match stmt {
Stmt::Pass(_) => Placeholder::Pass,
Stmt::Expr(expr) if expr.value.is_ellipsis_literal_expr() => {
// In a type-checking block, a trailing ellipsis might be meaningful. A
// user might be using the type-checking context to declare a stub.
if checker.semantic().in_type_checking_block() {
return;
}
// Ellipses are significant in protocol methods and abstract methods. Specifically,
// Pyright uses the presence of an ellipsis to indicate that a method is a stub,
// rather than a default implementation.

View File

@@ -121,7 +121,8 @@ pub(crate) fn runtime_import_in_type_checking_block(
checker
.semantic()
.reference(reference_id)
.in_runtime_context()
.context()
.is_runtime()
})
{
let Some(node_id) = binding.source else {
@@ -154,7 +155,8 @@ pub(crate) fn runtime_import_in_type_checking_block(
if checker.settings.flake8_type_checking.quote_annotations
&& binding.references().all(|reference_id| {
let reference = checker.semantic().reference(reference_id);
reference.in_typing_context() || reference.in_runtime_evaluated_annotation()
reference.context().is_typing()
|| reference.in_runtime_evaluated_annotation()
})
{
actions
@@ -266,7 +268,7 @@ fn quote_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding])
.flat_map(|ImportBinding { binding, .. }| {
binding.references.iter().filter_map(|reference_id| {
let reference = checker.semantic().reference(*reference_id);
if reference.in_runtime_context() {
if reference.context().is_runtime() {
Some(quote_annotation(
reference.expression_id()?,
checker.semantic(),

View File

@@ -499,7 +499,7 @@ fn fix_imports(checker: &Checker, node_id: NodeId, imports: &[ImportBinding]) ->
.flat_map(|ImportBinding { binding, .. }| {
binding.references.iter().filter_map(|reference_id| {
let reference = checker.semantic().reference(*reference_id);
if reference.in_runtime_context() {
if reference.context().is_runtime() {
Some(quote_annotation(
reference.expression_id()?,
checker.semantic(),

View File

@@ -95,7 +95,6 @@ mod tests {
#[test_case(Rule::NonlocalWithoutBinding, Path::new("nonlocal_without_binding.py"))]
#[test_case(Rule::NonSlotAssignment, Path::new("non_slot_assignment.py"))]
#[test_case(Rule::PropertyWithParameters, Path::new("property_with_parameters.py"))]
#[test_case(Rule::RedeclaredAssignedName, Path::new("redeclared_assigned_name.py"))]
#[test_case(
Rule::RedefinedArgumentFromLocal,
Path::new("redefined_argument_from_local.py")

View File

@@ -47,7 +47,6 @@ pub(crate) use non_slot_assignment::*;
pub(crate) use nonlocal_without_binding::*;
pub(crate) use potential_index_error::*;
pub(crate) use property_with_parameters::*;
pub(crate) use redeclared_assigned_name::*;
pub(crate) use redefined_argument_from_local::*;
pub(crate) use redefined_loop_name::*;
pub(crate) use repeated_equality_comparison::*;
@@ -137,7 +136,6 @@ mod non_slot_assignment;
mod nonlocal_without_binding;
mod potential_index_error;
mod property_with_parameters;
mod redeclared_assigned_name;
mod redefined_argument_from_local;
mod redefined_loop_name;
mod repeated_equality_comparison;

View File

@@ -67,7 +67,6 @@ pub(crate) fn non_ascii_name(binding: &Binding, locator: &Locator) -> Option<Dia
| BindingKind::FromImport(_)
| BindingKind::SubmoduleImport(_)
| BindingKind::Deletion
| BindingKind::ConditionalDeletion(_)
| BindingKind::UnboundException(_) => {
return None;
}

View File

@@ -1,76 +0,0 @@
use ruff_python_ast::{self as ast, Expr};
use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_text_size::Ranged;
use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for declared assignments to the same variable multiple times
/// in the same assignment.
///
/// ## Why is this bad?
/// Assigning a variable multiple times in the same assignment is redundant,
/// as the final assignment to the variable is what the value will be.
///
/// ## Example
/// ```python
/// a, b, a = (1, 2, 3)
/// print(a) # 3
/// ```
///
/// Use instead:
/// ```python
/// # this is assuming you want to assign 3 to `a`
/// _, b, a = (1, 2, 3)
/// print(a) # 3
/// ```
///
#[violation]
pub struct RedeclaredAssignedName {
name: String,
}
impl Violation for RedeclaredAssignedName {
#[derive_message_formats]
fn message(&self) -> String {
let RedeclaredAssignedName { name } = self;
format!("Redeclared variable `{name}` in assignment")
}
}
/// PLW0128
pub(crate) fn redeclared_assigned_name(checker: &mut Checker, targets: &Vec<Expr>) {
let mut names: Vec<String> = Vec::new();
for target in targets {
check_expr(checker, target, &mut names);
}
}
fn check_expr(checker: &mut Checker, expr: &Expr, names: &mut Vec<String>) {
match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => {
for target in elts {
check_expr(checker, target, names);
}
}
Expr::Name(ast::ExprName { id, .. }) => {
if checker.settings.dummy_variable_rgx.is_match(id) {
// Ignore dummy variable assignments
return;
}
if names.contains(id) {
checker.diagnostics.push(Diagnostic::new(
RedeclaredAssignedName {
name: id.to_string(),
},
expr.range(),
));
}
names.push(id.to_string());
}
_ => {}
}
}

View File

@@ -1,59 +0,0 @@
---
source: crates/ruff_linter/src/rules/pylint/mod.rs
---
redeclared_assigned_name.py:1:8: PLW0128 Redeclared variable `FIRST` in assignment
|
1 | FIRST, FIRST = (1, 2) # PLW0128
| ^^^^^ PLW0128
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
|
redeclared_assigned_name.py:2:9: PLW0128 Redeclared variable `FIRST` in assignment
|
1 | FIRST, FIRST = (1, 2) # PLW0128
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
| ^^^^^ PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
|
redeclared_assigned_name.py:3:9: PLW0128 Redeclared variable `FIRST` in assignment
|
1 | FIRST, FIRST = (1, 2) # PLW0128
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
| ^^^^^ PLW0128
4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
|
redeclared_assigned_name.py:3:32: PLW0128 Redeclared variable `FIRST` in assignment
|
1 | FIRST, FIRST = (1, 2) # PLW0128
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
| ^^^^^ PLW0128
4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
|
redeclared_assigned_name.py:4:23: PLW0128 Redeclared variable `FIRST` in assignment
|
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
| ^^^^^ PLW0128
5 |
6 | FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK
|
redeclared_assigned_name.py:4:30: PLW0128 Redeclared variable `SECOND` in assignment
|
2 | FIRST, (FIRST, SECOND) = (1, (1, 2)) # PLW0128
3 | FIRST, (FIRST, SECOND, (THIRD, FIRST)) = (1, (1, 2)) # PLW0128
4 | FIRST, SECOND, THIRD, FIRST, SECOND = (1, 2, 3, 4) # PLW0128
| ^^^^^^ PLW0128
5 |
6 | FIRST, SECOND, _, _, _ignored = (1, 2, 3, 4, 5) # OK
|

View File

@@ -141,15 +141,15 @@ impl<'src, 'loc> UselessSuppressionComments<'src, 'loc> {
if comment.kind == SuppressionKind::Off || comment.kind == SuppressionKind::On {
if let Some(
AnyNodeRef::StmtClassDef(StmtClassDef {
name,
decorator_list,
..
})
name,
decorator_list,
..
})
| AnyNodeRef::StmtFunctionDef(StmtFunctionDef {
name,
decorator_list,
..
}),
name,
decorator_list,
..
}),
) = comment.enclosing
{
if comment.line_position.is_own_line() && comment.range.start() < name.start() {
@@ -196,7 +196,7 @@ impl<'src, 'loc> UselessSuppressionComments<'src, 'loc> {
self.captured.sort_by_key(|(t, _)| t.start());
}
fn ignored_comments(&self) -> impl Iterator<Item = (TextRange, IgnoredReason)> + '_ {
fn ignored_comments(&self) -> impl Iterator<Item=(TextRange, IgnoredReason)> + '_ {
self.captured.iter().map(|(r, i)| (*r, *i))
}
}
@@ -276,7 +276,8 @@ const fn is_valid_enclosing_node(node: AnyNodeRef) -> bool {
| AnyNodeRef::StmtIpyEscapeCommand(_)
| AnyNodeRef::ExceptHandlerExceptHandler(_)
| AnyNodeRef::MatchCase(_)
| AnyNodeRef::ElifElseClause(_) => true,
| AnyNodeRef::ElifElseClause(_)
| AnyNodeRef::StmtCrement(_) => true,
AnyNodeRef::ExprBoolOp(_)
| AnyNodeRef::ExprNamed(_)

View File

@@ -19,8 +19,8 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass};
/// Instead of sharing mutable defaults, use the `field(default_factory=...)`
/// pattern.
///
/// If the default value is intended to be mutable, it must be annotated with
/// `typing.ClassVar`; otherwise, a `ValueError` will be raised.
/// If the default value is intended to be mutable, it should be annotated with
/// `typing.ClassVar`.
///
/// ## Examples
/// ```python
@@ -29,8 +29,6 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass};
///
/// @dataclass
/// class A:
/// # A list without a `default_factory` or `ClassVar` annotation
/// # will raise a `ValueError`.
/// mutable_default: list[int] = []
/// ```
///
@@ -46,7 +44,7 @@ use crate::rules::ruff::rules::helpers::{is_class_var_annotation, is_dataclass};
///
/// Or:
/// ```python
/// from dataclasses import dataclass
/// from dataclasses import dataclass, field
/// from typing import ClassVar
///
///

View File

@@ -1543,6 +1543,9 @@ impl<'a> From<&'a ast::Stmt> for ComparableStmt<'a> {
ast::Stmt::Pass(_) => Self::Pass,
ast::Stmt::Break(_) => Self::Break,
ast::Stmt::Continue(_) => Self::Continue,
ast::Stmt::Crement(_) => {
todo!()
}
}
}
}

View File

@@ -432,6 +432,7 @@ pub fn any_over_stmt(stmt: &Stmt, func: &dyn Fn(&Expr) -> bool) -> bool {
Stmt::AugAssign(ast::StmtAugAssign { target, value, .. }) => {
any_over_expr(target, func) || any_over_expr(value, func)
}
Stmt::Crement(ast::StmtCrement { target, .. }) => any_over_expr(target, func),
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -36,6 +36,7 @@ pub enum AnyNode {
StmtTypeAlias(ast::StmtTypeAlias),
StmtAssign(ast::StmtAssign),
StmtAugAssign(ast::StmtAugAssign),
StmtCrement(ast::StmtCrement),
StmtAnnAssign(ast::StmtAnnAssign),
StmtFor(ast::StmtFor),
StmtWhile(ast::StmtWhile),
@@ -130,6 +131,7 @@ impl AnyNode {
AnyNode::StmtTypeAlias(node) => Some(Stmt::TypeAlias(node)),
AnyNode::StmtAssign(node) => Some(Stmt::Assign(node)),
AnyNode::StmtAugAssign(node) => Some(Stmt::AugAssign(node)),
AnyNode::StmtCrement(node) => Some(Stmt::Crement(node)),
AnyNode::StmtAnnAssign(node) => Some(Stmt::AnnAssign(node)),
AnyNode::StmtFor(node) => Some(Stmt::For(node)),
AnyNode::StmtWhile(node) => Some(Stmt::While(node)),
@@ -262,6 +264,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -327,6 +330,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -432,6 +436,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -522,6 +527,7 @@ impl AnyNode {
| AnyNode::StmtTypeAlias(_)
| AnyNode::StmtAssign(_)
| AnyNode::StmtAugAssign(_)
| AnyNode::StmtCrement(_)
| AnyNode::StmtAnnAssign(_)
| AnyNode::StmtFor(_)
| AnyNode::StmtWhile(_)
@@ -637,6 +643,7 @@ impl AnyNode {
Self::StmtTypeAlias(node) => AnyNodeRef::StmtTypeAlias(node),
Self::StmtAssign(node) => AnyNodeRef::StmtAssign(node),
Self::StmtAugAssign(node) => AnyNodeRef::StmtAugAssign(node),
Self::StmtCrement(node) => AnyNodeRef::StmtCrement(node),
Self::StmtAnnAssign(node) => AnyNodeRef::StmtAnnAssign(node),
Self::StmtFor(node) => AnyNodeRef::StmtFor(node),
Self::StmtWhile(node) => AnyNodeRef::StmtWhile(node),
@@ -1125,6 +1132,48 @@ impl AstNode for ast::StmtAugAssign {
visitor.visit_expr(value);
}
}
impl AstNode for ast::StmtCrement {
fn cast(kind: AnyNode) -> Option<Self>
where
Self: Sized,
{
if let AnyNode::StmtCrement(node) = kind {
Some(node)
} else {
None
}
}
fn cast_ref(kind: AnyNodeRef) -> Option<&Self> {
if let AnyNodeRef::StmtCrement(node) = kind {
Some(node)
} else {
None
}
}
fn as_any_node_ref(&self) -> AnyNodeRef {
AnyNodeRef::from(self)
}
fn into_any_node(self) -> AnyNode {
AnyNode::from(self)
}
fn visit_preorder<'a, V>(&'a self, visitor: &mut V)
where
V: PreorderVisitor<'a> + ?Sized,
{
let ast::StmtCrement {
target,
op: _,
range: _,
} = self;
visitor.visit_expr(target);
// TODO(konstin): visitor.visit_operator(op);
}
}
impl AstNode for ast::StmtAnnAssign {
fn cast(kind: AnyNode) -> Option<Self>
where
@@ -4538,6 +4587,7 @@ impl From<Stmt> for AnyNode {
Stmt::Break(node) => AnyNode::StmtBreak(node),
Stmt::Continue(node) => AnyNode::StmtContinue(node),
Stmt::IpyEscapeCommand(node) => AnyNode::StmtIpyEscapeCommand(node),
Stmt::Crement(node) => AnyNode::StmtCrement(node),
}
}
}
@@ -4676,6 +4726,12 @@ impl From<ast::StmtAugAssign> for AnyNode {
}
}
impl From<ast::StmtCrement> for AnyNode {
fn from(node: ast::StmtCrement) -> Self {
AnyNode::StmtCrement(node)
}
}
impl From<ast::StmtAnnAssign> for AnyNode {
fn from(node: ast::StmtAnnAssign) -> Self {
AnyNode::StmtAnnAssign(node)
@@ -5251,6 +5307,7 @@ impl Ranged for AnyNode {
AnyNode::StringLiteral(node) => node.range(),
AnyNode::BytesLiteral(node) => node.range(),
AnyNode::ElifElseClause(node) => node.range(),
AnyNode::StmtCrement(node) => node.range(),
}
}
}
@@ -5266,6 +5323,7 @@ pub enum AnyNodeRef<'a> {
StmtTypeAlias(&'a ast::StmtTypeAlias),
StmtAssign(&'a ast::StmtAssign),
StmtAugAssign(&'a ast::StmtAugAssign),
StmtCrement(&'a ast::StmtCrement),
StmtAnnAssign(&'a ast::StmtAnnAssign),
StmtFor(&'a ast::StmtFor),
StmtWhile(&'a ast::StmtWhile),
@@ -5444,6 +5502,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StringLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::BytesLiteral(node) => NonNull::from(*node).cast(),
AnyNodeRef::ElifElseClause(node) => NonNull::from(*node).cast(),
AnyNodeRef::StmtCrement(node) => NonNull::from(*node).cast(),
}
}
@@ -5546,6 +5605,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StringLiteral(_) => NodeKind::StringLiteral,
AnyNodeRef::BytesLiteral(_) => NodeKind::BytesLiteral,
AnyNodeRef::ElifElseClause(_) => NodeKind::ElifElseClause,
AnyNodeRef::StmtCrement(_) => NodeKind::StmtCrement,
}
}
@@ -5558,6 +5618,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5690,6 +5751,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5754,6 +5816,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5859,6 +5922,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -5949,6 +6013,7 @@ impl<'a> AnyNodeRef<'a> {
| AnyNodeRef::StmtTypeAlias(_)
| AnyNodeRef::StmtAssign(_)
| AnyNodeRef::StmtAugAssign(_)
| AnyNodeRef::StmtCrement(_)
| AnyNodeRef::StmtAnnAssign(_)
| AnyNodeRef::StmtFor(_)
| AnyNodeRef::StmtWhile(_)
@@ -6058,6 +6123,7 @@ impl<'a> AnyNodeRef<'a> {
AnyNodeRef::StmtTypeAlias(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAugAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtCrement(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtAnnAssign(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtFor(node) => node.visit_preorder(visitor),
AnyNodeRef::StmtWhile(node) => node.visit_preorder(visitor),
@@ -6372,6 +6438,12 @@ impl<'a> From<&'a ast::StmtAugAssign> for AnyNodeRef<'a> {
}
}
impl<'a> From<&'a ast::StmtCrement> for AnyNodeRef<'a> {
fn from(node: &'a ast::StmtCrement) -> Self {
AnyNodeRef::StmtCrement(node)
}
}
impl<'a> From<&'a ast::StmtAnnAssign> for AnyNodeRef<'a> {
fn from(node: &'a ast::StmtAnnAssign) -> Self {
AnyNodeRef::StmtAnnAssign(node)
@@ -6837,6 +6909,7 @@ impl<'a> From<&'a Stmt> for AnyNodeRef<'a> {
Stmt::Break(node) => AnyNodeRef::StmtBreak(node),
Stmt::Continue(node) => AnyNodeRef::StmtContinue(node),
Stmt::IpyEscapeCommand(node) => AnyNodeRef::StmtIpyEscapeCommand(node),
Stmt::Crement(node) => AnyNodeRef::StmtCrement(node),
}
}
}
@@ -7073,6 +7146,7 @@ impl Ranged for AnyNodeRef<'_> {
AnyNodeRef::FString(node) => node.range(),
AnyNodeRef::StringLiteral(node) => node.range(),
AnyNodeRef::BytesLiteral(node) => node.range(),
AnyNodeRef::StmtCrement(node) => node.range(),
}
}
}
@@ -7173,4 +7247,5 @@ pub enum NodeKind {
FString,
StringLiteral,
BytesLiteral,
StmtCrement,
}

View File

@@ -101,6 +101,8 @@ pub enum Stmt {
// Jupyter notebook specific
#[is(name = "ipy_escape_command_stmt")]
IpyEscapeCommand(StmtIpyEscapeCommand),
#[is(name = "crement_stmt")]
Crement(StmtCrement),
}
/// An AST node used to represent a IPython escape command at the statement level.
@@ -297,6 +299,26 @@ impl From<StmtAugAssign> for Stmt {
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum CrementKind {
Increment,
Decrement,
}
/// `++` or `--`
#[derive(Clone, Debug, PartialEq)]
pub struct StmtCrement {
pub range: TextRange,
pub target: Box<Expr>,
pub op: CrementKind,
}
impl From<StmtCrement> for Stmt {
fn from(payload: StmtCrement) -> Self {
Stmt::Crement(payload)
}
}
/// See also [AnnAssign](https://docs.python.org/3/library/ast.html#ast.AnnAssign)
#[derive(Clone, Debug, PartialEq)]
pub struct StmtAnnAssign {
@@ -3693,6 +3715,11 @@ impl Ranged for crate::nodes::StmtAugAssign {
self.range
}
}
impl Ranged for crate::nodes::StmtCrement {
fn range(&self) -> TextRange {
self.range
}
}
impl Ranged for crate::nodes::StmtAnnAssign {
fn range(&self) -> TextRange {
self.range
@@ -3816,6 +3843,7 @@ impl Ranged for crate::Stmt {
Self::Break(node) => node.range(),
Self::Continue(node) => node.range(),
Stmt::IpyEscapeCommand(node) => node.range(),
Stmt::Crement(node) => node.range(),
}
}
}

View File

@@ -203,6 +203,14 @@ pub fn walk_stmt<'a, V: Visitor<'a> + ?Sized>(visitor: &mut V, stmt: &'a Stmt) {
visitor.visit_operator(op);
visitor.visit_expr(target);
}
Stmt::Crement(ast::StmtCrement {
target,
op: _,
range: _,
}) => {
// TODO(konstin): visitor.visit_operator(op);
visitor.visit_expr(target);
}
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -209,6 +209,7 @@ where
Stmt::TypeAlias(stmt) => stmt.visit_preorder(visitor),
Stmt::Assign(stmt) => stmt.visit_preorder(visitor),
Stmt::AugAssign(stmt) => stmt.visit_preorder(visitor),
Stmt::Crement(stmt) => stmt.visit_preorder(visitor),
Stmt::AnnAssign(stmt) => stmt.visit_preorder(visitor),
Stmt::For(stmt) => stmt.visit_preorder(visitor),
Stmt::While(stmt) => stmt.visit_preorder(visitor),

View File

@@ -306,6 +306,7 @@ pub fn walk_stmt<V: Transformer + ?Sized>(visitor: &V, stmt: &mut Stmt) {
Stmt::Nonlocal(_) => {}
Stmt::Expr(ast::StmtExpr { value, range: _ }) => visitor.visit_expr(value),
Stmt::Pass(_) | Stmt::Break(_) | Stmt::Continue(_) | Stmt::IpyEscapeCommand(_) => {}
Stmt::Crement(_) => {}
}
}

View File

@@ -4,10 +4,10 @@ use std::ops::Deref;
use ruff_python_ast::str::Quote;
use ruff_python_ast::{
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, DebugText,
ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters, Pattern,
Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar, TypeParamTypeVarTuple,
WithItem,
self as ast, Alias, ArgOrKeyword, BoolOp, CmpOp, Comprehension, ConversionFlag, CrementKind,
DebugText, ExceptHandler, Expr, Identifier, MatchCase, Operator, Parameter, Parameters,
Pattern, Singleton, Stmt, Suite, TypeParam, TypeParamParamSpec, TypeParamTypeVar,
TypeParamTypeVarTuple, WithItem,
};
use ruff_python_ast::{ParameterWithDefault, TypeParams};
use ruff_python_literal::escape::{AsciiEscape, Escape, UnicodeEscape};
@@ -345,6 +345,20 @@ impl<'a> Generator<'a> {
self.unparse_expr(value, precedence::AUG_ASSIGN);
});
}
Stmt::Crement(ast::StmtCrement {
target,
op,
range: _,
}) => {
statement!({
self.unparse_expr(target, precedence::AUG_ASSIGN);
self.p(" ");
self.p(match op {
CrementKind::Increment => "+= 1",
CrementKind::Decrement => "-= 1",
});
});
}
Stmt::AnnAssign(ast::StmtAnnAssign {
target,
annotation,

View File

@@ -706,7 +706,8 @@ impl Format<PyFormatContext<'_>> for FormatEnclosingNode<'_> {
| AnyNodeRef::TypeParamTypeVar(_)
| AnyNodeRef::TypeParamTypeVarTuple(_)
| AnyNodeRef::TypeParamParamSpec(_)
| AnyNodeRef::BytesLiteral(_) => {
| AnyNodeRef::BytesLiteral(_)
| AnyNodeRef::StmtCrement(_) => {
panic!("Range formatting only supports formatting logical lines")
}
}

View File

@@ -45,6 +45,19 @@ impl FormatRule<Stmt, PyFormatContext<'_>> for FormatStmt {
Stmt::Delete(x) => x.format().fmt(f),
Stmt::Assign(x) => x.format().fmt(f),
Stmt::AugAssign(x) => x.format().fmt(f),
Stmt::Crement(x) => {
x.target.format().fmt(f)?;
use ruff_formatter::prelude::*;
match x.op {
ruff_python_ast::CrementKind::Increment => {
ruff_formatter::write!(f, [space(), text("+="), space(), text("1")])?;
}
ruff_python_ast::CrementKind::Decrement => {
ruff_formatter::write!(f, [space(), text("-="), space(), text("1")])?;
}
}
Ok(())
}
Stmt::AnnAssign(x) => x.format().fmt(f),
Stmt::For(x) => x.format().fmt(f),
Stmt::While(x) => x.format().fmt(f),

View File

@@ -1079,7 +1079,9 @@ impl<'source> Lexer<'source> {
}
}
'+' => {
if self.cursor.eat_char('=') {
if self.cursor.eat_char('+') {
Tok::Increment
} else if self.cursor.eat_char('=') {
Tok::PlusEqual
} else {
Tok::Plus
@@ -1166,7 +1168,9 @@ impl<'source> Lexer<'source> {
}
}
'-' => {
if self.cursor.eat_char('=') {
if self.cursor.eat_char('-') {
Tok::Increment
} else if self.cursor.eat_char('=') {
Tok::MinusEqual
} else if self.cursor.eat_char('>') {
Tok::Rarrow

View File

@@ -110,6 +110,16 @@ DelStatement: ast::Stmt = {
};
ExpressionStatement: ast::Stmt = {
<location:@L> <target:TestOrStarExprList> <op:Crement> <end_location:@R> =>? {
invalid::assignment_target(&target.expr)?;
Ok(ast::Stmt::Crement(
ast::StmtCrement {
target: Box::new(set_context(target.into(), ast::ExprContext::Store)),
op,
range: (location..end_location).into()
},
))
},
<location:@L> <expression:TestOrStarExprList> <suffix:AssignSuffix*> <end_location:@R> =>? {
// Just an expression, no assignment:
if suffix.is_empty() {
@@ -204,6 +214,11 @@ AugAssign: ast::Operator = {
"//=" => ast::Operator::FloorDiv,
};
Crement: ast::CrementKind = {
"++" => ast::CrementKind::Increment,
"--" => ast::CrementKind::Decrement,
};
FlowStatement: ast::Stmt = {
<location:@L> "break" <end_location:@R> => {
@@ -2033,6 +2048,8 @@ extern {
">" => token::Tok::Greater,
">=" => token::Tok::GreaterEqual,
"->" => token::Tok::Rarrow,
"++" => token::Tok::Increment,
"--" => token::Tok::Decrement,
"and" => token::Tok::And,
"as" => token::Tok::As,
"assert" => token::Tok::Assert,

File diff suppressed because it is too large Load Diff

View File

@@ -157,6 +157,10 @@ pub enum Tok {
VbarEqual,
/// Token value for caret equal `^=`.
CircumflexEqual,
/// Token value for `++`.
Increment,
/// Token value for `--`.
Decrement,
/// Token value for left shift equal `<<=`.
LeftShiftEqual,
/// Token value for right shift equal `>>=`.
@@ -343,6 +347,8 @@ impl fmt::Display for Tok {
With => f.write_str("'with'"),
Yield => f.write_str("'yield'"),
ColonEqual => f.write_str("':='"),
Increment => f.write_str("++"),
Decrement => f.write_str("--"),
}
}
}
@@ -526,6 +532,8 @@ pub enum TokenKind {
StartModule,
StartInteractive,
StartExpression,
Increment,
Decrement,
}
impl TokenKind {
@@ -620,6 +628,8 @@ impl TokenKind {
| TokenKind::AmperEqual
| TokenKind::VbarEqual
| TokenKind::CircumflexEqual
| TokenKind::Increment
| TokenKind::Decrement
| TokenKind::LeftShiftEqual
| TokenKind::RightShiftEqual
| TokenKind::DoubleStarEqual
@@ -799,6 +809,8 @@ impl TokenKind {
Tok::Yield => TokenKind::Yield,
Tok::StartModule => TokenKind::StartModule,
Tok::StartExpression => TokenKind::StartExpression,
Tok::Increment => TokenKind::Increment,
Tok::Decrement => TokenKind::Decrement,
}
}
}

View File

@@ -75,11 +75,6 @@ impl<'a> Binding<'a> {
self.flags.intersects(BindingFlags::GLOBAL)
}
/// Return `true` if this [`Binding`] was deleted.
pub const fn is_deleted(&self) -> bool {
self.flags.intersects(BindingFlags::DELETED)
}
/// Return `true` if this [`Binding`] represents an assignment to `__all__` with an invalid
/// value (e.g., `__all__ = "Foo"`).
pub const fn is_invalid_all_format(&self) -> bool {
@@ -170,7 +165,6 @@ impl<'a> Binding<'a> {
// Deletions, annotations, `__future__` imports, and builtins are never considered
// redefinitions.
BindingKind::Deletion
| BindingKind::ConditionalDeletion(_)
| BindingKind::Annotation
| BindingKind::FutureImport
| BindingKind::Builtin => {
@@ -271,19 +265,6 @@ bitflags! {
/// ```
const GLOBAL = 1 << 4;
/// The binding was deleted (i.e., the target of a `del` statement).
///
/// For example, the binding could be `x` in:
/// ```python
/// del x
/// ```
///
/// The semantic model will typically shadow a deleted binding via an additional binding
/// with [`BindingKind::Deletion`]; however, conditional deletions (e.g.,
/// `if condition: del x`) do _not_ generate a shadow binding. This flag is thus used to
/// detect whether a binding was _ever_ deleted, even conditionally.
const DELETED = 1 << 5;
/// The binding represents an export via `__all__`, but the assigned value uses an invalid
/// expression (i.e., a non-container type).
///
@@ -291,7 +272,7 @@ bitflags! {
/// ```python
/// __all__ = 1
/// ```
const INVALID_ALL_FORMAT = 1 << 6;
const INVALID_ALL_FORMAT = 1 << 5;
/// The binding represents an export via `__all__`, but the assigned value contains an
/// invalid member (i.e., a non-string).
@@ -300,7 +281,7 @@ bitflags! {
/// ```python
/// __all__ = [1]
/// ```
const INVALID_ALL_OBJECT = 1 << 7;
const INVALID_ALL_OBJECT = 1 << 6;
/// The binding represents a private declaration.
///
@@ -308,7 +289,7 @@ bitflags! {
/// ```python
/// _T = "This is a private variable"
/// ```
const PRIVATE_DECLARATION = 1 << 8;
const PRIVATE_DECLARATION = 1 << 7;
/// The binding represents an unpacked assignment.
///
@@ -316,7 +297,7 @@ bitflags! {
/// ```python
/// (x, y) = 1, 2
/// ```
const UNPACKED_ASSIGNMENT = 1 << 9;
const UNPACKED_ASSIGNMENT = 1 << 8;
}
}
@@ -531,13 +512,6 @@ pub enum BindingKind<'a> {
/// ```
Deletion,
/// A binding for a deletion, like `x` in:
/// ```python
/// if x > 0:
/// del x
/// ```
ConditionalDeletion(BindingId),
/// A binding to bind an exception to a local variable, like `x` in:
/// ```python
/// try:

View File

@@ -5,7 +5,7 @@ use rustc_hash::FxHashMap;
use ruff_python_ast::helpers::from_relative_import;
use ruff_python_ast::name::{QualifiedName, UnqualifiedName};
use ruff_python_ast::{self as ast, Expr, ExprContext, Operator, Stmt};
use ruff_python_ast::{self as ast, Expr, Operator, Stmt};
use ruff_python_stdlib::path::is_python_stub_file;
use ruff_text_size::{Ranged, TextRange, TextSize};
@@ -271,7 +271,7 @@ impl<'a> SemanticModel<'a> {
.get(symbol)
.map_or(true, |binding_id| {
// Treat the deletion of a name as a reference to that name.
self.add_local_reference(binding_id, ExprContext::Del, range);
self.add_local_reference(binding_id, range);
self.bindings[binding_id].is_unbound()
});
@@ -296,9 +296,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
ScopeId::global(),
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
@@ -309,9 +308,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
ScopeId::global(),
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
}
@@ -367,9 +365,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
self.scope_id,
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
@@ -380,9 +377,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
self.scope_id,
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
}
@@ -430,15 +426,6 @@ impl<'a> SemanticModel<'a> {
return ReadResult::UnboundLocal(binding_id);
}
BindingKind::ConditionalDeletion(binding_id) => {
self.unresolved_references.push(
name.range,
self.exceptions(),
UnresolvedReferenceFlags::empty(),
);
return ReadResult::UnboundLocal(binding_id);
}
// If we hit an unbound exception that shadowed a bound name, resole to the
// bound name. For example, given:
//
@@ -459,9 +446,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
self.scope_id,
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
@@ -472,9 +458,8 @@ impl<'a> SemanticModel<'a> {
let reference_id = self.resolved_references.push(
self.scope_id,
self.node_id,
ExprContext::Load,
self.flags,
name.range,
self.flags,
);
self.bindings[binding_id].references.push(reference_id);
}
@@ -563,7 +548,6 @@ impl<'a> SemanticModel<'a> {
match self.bindings[binding_id].kind {
BindingKind::Annotation => continue,
BindingKind::Deletion | BindingKind::UnboundException(None) => return None,
BindingKind::ConditionalDeletion(binding_id) => return Some(binding_id),
BindingKind::UnboundException(Some(binding_id)) => return Some(binding_id),
_ => return Some(binding_id),
}
@@ -1331,28 +1315,18 @@ impl<'a> SemanticModel<'a> {
}
/// Add a reference to the given [`BindingId`] in the local scope.
pub fn add_local_reference(
&mut self,
binding_id: BindingId,
ctx: ExprContext,
range: TextRange,
) {
pub fn add_local_reference(&mut self, binding_id: BindingId, range: TextRange) {
let reference_id =
self.resolved_references
.push(self.scope_id, self.node_id, ctx, self.flags, range);
.push(self.scope_id, self.node_id, range, self.flags);
self.bindings[binding_id].references.push(reference_id);
}
/// Add a reference to the given [`BindingId`] in the global scope.
pub fn add_global_reference(
&mut self,
binding_id: BindingId,
ctx: ExprContext,
range: TextRange,
) {
pub fn add_global_reference(&mut self, binding_id: BindingId, range: TextRange) {
let reference_id =
self.resolved_references
.push(ScopeId::global(), self.node_id, ctx, self.flags, range);
.push(ScopeId::global(), self.node_id, range, self.flags);
self.bindings[binding_id].references.push(reference_id);
}
@@ -1726,6 +1700,7 @@ bitflags! {
/// only required by the Python interpreter, but by runtime type checkers too.
const RUNTIME_REQUIRED_ANNOTATION = 1 << 2;
/// The model is in a type definition.
///
/// For example, the model could be visiting `int` in:
@@ -1911,6 +1886,7 @@ bitflags! {
/// ```
const COMPREHENSION_ASSIGNMENT = 1 << 19;
/// The model is in a module / class / function docstring.
///
/// For example, the model could be visiting either the module, class,

View File

@@ -3,10 +3,10 @@ use std::ops::Deref;
use bitflags::bitflags;
use ruff_index::{newtype_index, IndexSlice, IndexVec};
use ruff_python_ast::ExprContext;
use ruff_source_file::Locator;
use ruff_text_size::{Ranged, TextRange};
use crate::context::ExecutionContext;
use crate::scope::ScopeId;
use crate::{Exceptions, NodeId, SemanticModelFlags};
@@ -18,12 +18,10 @@ pub struct ResolvedReference {
node_id: Option<NodeId>,
/// The scope in which the reference is defined.
scope_id: ScopeId,
/// The expression context in which the reference occurs (e.g., `Load`, `Store`, `Del`).
ctx: ExprContext,
/// The model state in which the reference occurs.
flags: SemanticModelFlags,
/// The range of the reference in the source code.
range: TextRange,
/// The model state in which the reference occurs.
flags: SemanticModelFlags,
}
impl ResolvedReference {
@@ -37,19 +35,13 @@ impl ResolvedReference {
self.scope_id
}
/// Return `true` if the reference occurred in a `Load` operation.
pub const fn is_load(&self) -> bool {
self.ctx.is_load()
}
/// Return `true` if the context is in a typing context.
pub const fn in_typing_context(&self) -> bool {
self.flags.intersects(SemanticModelFlags::TYPING_CONTEXT)
}
/// Return `true` if the context is in a runtime context.
pub const fn in_runtime_context(&self) -> bool {
!self.flags.intersects(SemanticModelFlags::TYPING_CONTEXT)
/// The [`ExecutionContext`] of the reference.
pub const fn context(&self) -> ExecutionContext {
if self.flags.intersects(SemanticModelFlags::TYPING_CONTEXT) {
ExecutionContext::Typing
} else {
ExecutionContext::Runtime
}
}
/// Return `true` if the context is in a typing-only type annotation.
@@ -116,16 +108,14 @@ impl ResolvedReferences {
&mut self,
scope_id: ScopeId,
node_id: Option<NodeId>,
ctx: ExprContext,
flags: SemanticModelFlags,
range: TextRange,
flags: SemanticModelFlags,
) -> ResolvedReferenceId {
self.0.push(ResolvedReference {
node_id,
scope_id,
ctx,
flags,
range,
flags,
})
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "ruff_shrinking"
version = "0.3.3"
version = "0.3.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -14,7 +14,7 @@ Ruff can be used as a [pre-commit](https://pre-commit.com) hook via [`ruff-pre-c
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.3
rev: v0.3.2
hooks:
# Run the linter.
- id: ruff
@@ -27,7 +27,7 @@ To enable lint fixes, add the `--fix` argument to the lint hook:
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.3
rev: v0.3.2
hooks:
# Run the linter.
- id: ruff
@@ -41,7 +41,7 @@ To run the hooks over Jupyter Notebooks too, add `jupyter` to the list of allowe
```yaml
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.3.3
rev: v0.3.2
hooks:
# Run the linter.
- id: ruff

View File

@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "ruff"
version = "0.3.3"
version = "0.3.2"
description = "An extremely fast Python linter and code formatter, written in Rust."
authors = [{ name = "Astral Software Inc.", email = "hey@astral.sh" }]
readme = "README.md"

1
ruff.schema.json generated
View File

@@ -3330,7 +3330,6 @@
"PLW012",
"PLW0120",
"PLW0127",
"PLW0128",
"PLW0129",
"PLW013",
"PLW0131",

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "scripts"
version = "0.3.3"
version = "0.3.2"
description = ""
authors = ["Charles Marsh <charlie.r.marsh@gmail.com>"]