[ruff] Implemented used-dummy-variable (RUF052) (#14611)

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Lokejoke 2024-12-03 08:36:16 +01:00 committed by GitHub
parent a69dfd4a74
commit bf0fd04e4e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 778 additions and 8 deletions

View file

@ -0,0 +1,106 @@
# Correct
for _ in range(5):
pass
_valid_type = int
_valid_var_1: _valid_type
_valid_var_1 = 1
_valid_var_2 = 2
_valid_var_3 = _valid_var_1 + _valid_var_2
def _valid_fun():
pass
_valid_fun()
def fun(arg):
_valid_unused_var = arg
pass
_x = "global"
def fun():
global _x
return _x
def fun():
__dunder__ = "dunder variable"
return __dunder__
def fun():
global _x
_x = "reassigned global"
return _x
class _ValidClass:
pass
_ValidClass()
class ClassOk:
_valid_private_cls_attr = 1
print(_valid_private_cls_attr)
def __init__(self):
self._valid_private_ins_attr = 2
print(self._valid_private_ins_attr)
def _valid_method(self):
return self._valid_private_ins_attr
def method(arg):
_valid_unused_var = arg
return
def fun(x):
_ = 1
__ = 2
___ = 3
if x == 1:
return _
if x == 2:
return __
if x == 3:
return ___
return x
# Incorrect
class Class_:
def fun(self):
_var = "method variable" # [RUF052]
return _var
def fun(_var): # [RUF052]
return _var
def fun():
_list = "built-in" # [RUF052]
return _list
x = "global"
def fun():
global x
_x = "shadows global" # [RUF052]
return _x
def foo():
x = "outer"
def bar():
nonlocal x
_x = "shadows nonlocal" # [RUF052]
return _x
bar()
return x
def fun():
x = "local"
_x = "shadows local" # [RUF052]
return _x

View file

@ -10,6 +10,7 @@ use crate::rules::{
/// Run lint rules over the [`Binding`]s.
pub(crate) fn bindings(checker: &mut Checker) {
if !checker.any_enabled(&[
Rule::AssignmentInAssert,
Rule::InvalidAllFormat,
Rule::InvalidAllObject,
Rule::NonAsciiName,
@ -18,7 +19,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
Rule::UnsortedDunderSlots,
Rule::UnusedVariable,
Rule::UnquotedTypeAlias,
Rule::AssignmentInAssert,
Rule::UsedDummyVariable,
]) {
return;
}
@ -88,6 +89,11 @@ pub(crate) fn bindings(checker: &mut Checker) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::UsedDummyVariable) {
if let Some(diagnostic) = ruff::rules::used_dummy_variable(checker, binding) {
checker.diagnostics.push(diagnostic);
}
}
if checker.enabled(Rule::AssignmentInAssert) {
if let Some(diagnostic) = ruff::rules::assignment_in_assert(checker, binding) {
checker.diagnostics.push(diagnostic);

View file

@ -984,6 +984,7 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
(Ruff, "040") => (RuleGroup::Preview, rules::ruff::rules::InvalidAssertMessageLiteralArgument),
(Ruff, "041") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryNestedLiteral),
(Ruff, "048") => (RuleGroup::Preview, rules::ruff::rules::MapIntVersionParsing),
(Ruff, "052") => (RuleGroup::Preview, rules::ruff::rules::UsedDummyVariable),
(Ruff, "055") => (RuleGroup::Preview, rules::ruff::rules::UnnecessaryRegularExpression),
(Ruff, "100") => (RuleGroup::Stable, rules::ruff::rules::UnusedNOQA),
(Ruff, "101") => (RuleGroup::Stable, rules::ruff::rules::RedirectedNOQA),

View file

@ -10,6 +10,7 @@ mod tests {
use std::path::Path;
use anyhow::Result;
use regex::Regex;
use rustc_hash::FxHashSet;
use test_case::test_case;
@ -70,6 +71,7 @@ mod tests {
#[test_case(Rule::InvalidAssertMessageLiteralArgument, Path::new("RUF040.py"))]
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.py"))]
#[test_case(Rule::UnnecessaryNestedLiteral, Path::new("RUF041.pyi"))]
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"))]
fn rules(rule_code: Rule, path: &Path) -> Result<()> {
let snapshot = format!("{}_{}", rule_code.noqa_code(), path.to_string_lossy());
let diagnostics = test_path(
@ -449,4 +451,32 @@ mod tests {
assert_messages!(snapshot, diagnostics);
Ok(())
}
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"^_+", 1)]
#[test_case(Rule::UsedDummyVariable, Path::new("RUF052.py"), r"", 2)]
fn custom_regexp_preset(
rule_code: Rule,
path: &Path,
regex_pattern: &str,
id: u8,
) -> Result<()> {
// Compile the regex from the pattern string
let regex = Regex::new(regex_pattern).unwrap();
let snapshot = format!(
"custom_dummy_var_regexp_preset__{}_{}_{}",
rule_code.noqa_code(),
path.to_string_lossy(),
id,
);
let diagnostics = test_path(
Path::new("ruff").join(path).as_path(),
&settings::LinterSettings {
dummy_variable_rgx: regex,
..settings::LinterSettings::for_rule(rule_code)
},
)?;
assert_messages!(snapshot, diagnostics);
Ok(())
}
}

View file

@ -38,6 +38,7 @@ pub(crate) use unraw_re_pattern::*;
pub(crate) use unsafe_markup_use::*;
pub(crate) use unused_async::*;
pub(crate) use unused_noqa::*;
pub(crate) use used_dummy_variable::*;
pub(crate) use useless_if_else::*;
pub(crate) use zip_instead_of_pairwise::*;
@ -85,6 +86,7 @@ mod unraw_re_pattern;
mod unsafe_markup_use;
mod unused_async;
mod unused_noqa;
mod used_dummy_variable;
mod useless_if_else;
mod zip_instead_of_pairwise;

View file

@ -0,0 +1,221 @@
use ruff_diagnostics::{Diagnostic, Fix, FixAvailability, Violation};
use ruff_macros::{derive_message_formats, ViolationMetadata};
use ruff_python_ast::helpers::is_dunder;
use ruff_python_semantic::{Binding, BindingKind, ScopeId};
use ruff_python_stdlib::{
builtins::is_python_builtin, identifiers::is_identifier, keyword::is_keyword,
};
use ruff_text_size::Ranged;
use crate::{checkers::ast::Checker, renamer::Renamer};
/// ## What it does
/// Checks for accesses of local dummy variables, excluding `_` and dunder variables.
///
/// By default, "dummy variables" are any variables with names that start with leading
/// underscores. However, this is customisable using the `dummy-variable-rgx` setting).
///
/// ## Why is this bad?
/// Marking a variable with a leading underscore conveys that it is intentionally unused within the function or method.
/// When these variables are later referenced in the code, it causes confusion and potential misunderstandings about
/// the code's intention. A variable marked as "unused" being subsequently used suggests oversight or unintentional use.
/// This detracts from the clarity and maintainability of the codebase.
///
/// Sometimes leading underscores are used to avoid variables shadowing other variables, Python builtins, or Python
/// keywords. However, [PEP 8] recommends to use trailing underscores for this rather than leading underscores.
///
/// ## Example
/// ```python
/// def function():
/// _variable = 3
/// return _variable + 1
/// ```
///
/// Use instead:
/// ```python
/// def function():
/// variable = 3
/// return variable + 1
/// ```
///
/// ## Fix availability
/// An fix is only available for variables that start with leading underscores.
///
/// ## Options
/// - [`lint.dummy-variable-rgx`]
///
///
/// [PEP 8]: https://peps.python.org/pep-0008/
#[derive(ViolationMetadata)]
pub(crate) struct UsedDummyVariable {
name: String,
shadowed_kind: Option<ShadowedKind>,
}
impl Violation for UsedDummyVariable {
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
#[derive_message_formats]
fn message(&self) -> String {
format!("Local dummy variable `{}` is accessed", self.name)
}
fn fix_title(&self) -> Option<String> {
if let Some(shadowed_kind) = self.shadowed_kind {
return Some(match shadowed_kind {
ShadowedKind::BuiltIn => {
"Prefer using trailing underscores to avoid shadowing a built-in".to_string()
}
ShadowedKind::Keyword => {
"Prefer using trailing underscores to avoid shadowing a keyword".to_string()
}
ShadowedKind::Some => {
"Prefer using trailing underscores to avoid shadowing a variable".to_string()
}
ShadowedKind::None => "Remove leading underscores".to_string(),
});
}
None
}
}
/// RUF052
pub(crate) fn used_dummy_variable(checker: &Checker, binding: &Binding) -> Option<Diagnostic> {
let name = binding.name(checker.source());
// Ignore `_` and dunder variables
if name == "_" || is_dunder(name) {
return None;
}
// only used variables
if binding.is_unused() {
return None;
}
// Only variables defined via function arguments or assignments.
if !matches!(
binding.kind,
BindingKind::Argument | BindingKind::Assignment
) {
return None;
}
// This excludes `global` and `nonlocal` variables.
if binding.is_global() || binding.is_nonlocal() {
return None;
}
let semantic = checker.semantic();
// Only variables defined in function scopes
let scope = &semantic.scopes[binding.scope];
if !scope.kind.is_function() {
return None;
}
if !checker.settings.dummy_variable_rgx.is_match(name) {
return None;
}
let shadowed_kind = try_shadowed_kind(name, checker, binding.scope);
let mut diagnostic = Diagnostic::new(
UsedDummyVariable {
name: name.to_string(),
shadowed_kind,
},
binding.range(),
);
// If fix available
if let Some(shadowed_kind) = shadowed_kind {
// Get the possible fix based on the scope
if let Some(fix) = get_possible_fix(name, shadowed_kind, binding.scope, checker) {
diagnostic.try_set_fix(|| {
Renamer::rename(name, &fix, scope, semantic, checker.stylist())
.map(|(edit, rest)| Fix::safe_edits(edit, rest))
});
}
}
Some(diagnostic)
}
/// Enumeration of various ways in which a binding can shadow other variables
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum ShadowedKind {
/// The variable shadows a global, nonlocal or local symbol
Some,
/// The variable shadows a builtin symbol
BuiltIn,
/// The variable shadows a keyword
Keyword,
/// The variable does not shadow any other symbols
None,
}
/// Suggests a potential alternative name to resolve a shadowing conflict.
fn get_possible_fix(
name: &str,
kind: ShadowedKind,
scope_id: ScopeId,
checker: &Checker,
) -> Option<String> {
// Remove leading underscores for processing
let trimmed_name = name.trim_start_matches('_');
// Construct the potential fix name based on ShadowedKind
let fix_name = match kind {
ShadowedKind::Some | ShadowedKind::BuiltIn | ShadowedKind::Keyword => {
format!("{trimmed_name}_") // Append an underscore
}
ShadowedKind::None => trimmed_name.to_string(),
};
// Check if the fix name is again dummy identifier
if checker.settings.dummy_variable_rgx.is_match(&fix_name) {
return None;
}
// Ensure the fix name is not already taken in the scope or enclosing scopes
if !checker
.semantic()
.is_available_in_scope(&fix_name, scope_id)
{
return None;
}
// Check if the fix name is a valid identifier
is_identifier(&fix_name).then_some(fix_name)
}
/// Determines the kind of shadowing or conflict for a given variable name.
fn try_shadowed_kind(name: &str, checker: &Checker, scope_id: ScopeId) -> Option<ShadowedKind> {
// If the name starts with an underscore, we don't consider it
if !name.starts_with('_') {
return None;
}
// Trim the leading underscores for further checks
let trimmed_name = name.trim_start_matches('_');
// Check the kind in order of precedence
if is_keyword(trimmed_name) {
return Some(ShadowedKind::Keyword);
}
if is_python_builtin(
trimmed_name,
checker.settings.target_version.minor(),
checker.source_type.is_ipynb(),
) {
return Some(ShadowedKind::BuiltIn);
}
if !checker
.semantic()
.is_available_in_scope(trimmed_name, scope_id)
{
return Some(ShadowedKind::Some);
}
// Default to no shadowing
Some(ShadowedKind::None)
}

View file

@ -0,0 +1,132 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF052.py:77:9: RUF052 [*] Local dummy variable `_var` is accessed
|
75 | class Class_:
76 | def fun(self):
77 | _var = "method variable" # [RUF052]
| ^^^^ RUF052
78 | return _var
|
= help: Remove leading underscores
Safe fix
74 74 |
75 75 | class Class_:
76 76 | def fun(self):
77 |- _var = "method variable" # [RUF052]
78 |- return _var
77 |+ var = "method variable" # [RUF052]
78 |+ return var
79 79 |
80 80 | def fun(_var): # [RUF052]
81 81 | return _var
RUF052.py:80:9: RUF052 [*] Local dummy variable `_var` is accessed
|
78 | return _var
79 |
80 | def fun(_var): # [RUF052]
| ^^^^ RUF052
81 | return _var
|
= help: Remove leading underscores
Safe fix
77 77 | _var = "method variable" # [RUF052]
78 78 | return _var
79 79 |
80 |-def fun(_var): # [RUF052]
81 |- return _var
80 |+def fun(var): # [RUF052]
81 |+ return var
82 82 |
83 83 | def fun():
84 84 | _list = "built-in" # [RUF052]
RUF052.py:84:5: RUF052 [*] Local dummy variable `_list` is accessed
|
83 | def fun():
84 | _list = "built-in" # [RUF052]
| ^^^^^ RUF052
85 | return _list
|
= help: Prefer using trailing underscores to avoid shadowing a built-in
Safe fix
81 81 | return _var
82 82 |
83 83 | def fun():
84 |- _list = "built-in" # [RUF052]
85 |- return _list
84 |+ list_ = "built-in" # [RUF052]
85 |+ return list_
86 86 |
87 87 | x = "global"
88 88 |
RUF052.py:91:5: RUF052 [*] Local dummy variable `_x` is accessed
|
89 | def fun():
90 | global x
91 | _x = "shadows global" # [RUF052]
| ^^ RUF052
92 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
88 88 |
89 89 | def fun():
90 90 | global x
91 |- _x = "shadows global" # [RUF052]
92 |- return _x
91 |+ x_ = "shadows global" # [RUF052]
92 |+ return x_
93 93 |
94 94 | def foo():
95 95 | x = "outer"
RUF052.py:98:5: RUF052 [*] Local dummy variable `_x` is accessed
|
96 | def bar():
97 | nonlocal x
98 | _x = "shadows nonlocal" # [RUF052]
| ^^ RUF052
99 | return _x
100 | bar()
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
95 95 | x = "outer"
96 96 | def bar():
97 97 | nonlocal x
98 |- _x = "shadows nonlocal" # [RUF052]
99 |- return _x
98 |+ x_ = "shadows nonlocal" # [RUF052]
99 |+ return x_
100 100 | bar()
101 101 | return x
102 102 |
RUF052.py:105:5: RUF052 [*] Local dummy variable `_x` is accessed
|
103 | def fun():
104 | x = "local"
105 | _x = "shadows local" # [RUF052]
| ^^ RUF052
106 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
102 102 |
103 103 | def fun():
104 104 | x = "local"
105 |- _x = "shadows local" # [RUF052]
106 |- return _x
105 |+ x_ = "shadows local" # [RUF052]
106 |+ return x_

View file

@ -0,0 +1,132 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF052.py:77:9: RUF052 [*] Local dummy variable `_var` is accessed
|
75 | class Class_:
76 | def fun(self):
77 | _var = "method variable" # [RUF052]
| ^^^^ RUF052
78 | return _var
|
= help: Remove leading underscores
Safe fix
74 74 |
75 75 | class Class_:
76 76 | def fun(self):
77 |- _var = "method variable" # [RUF052]
78 |- return _var
77 |+ var = "method variable" # [RUF052]
78 |+ return var
79 79 |
80 80 | def fun(_var): # [RUF052]
81 81 | return _var
RUF052.py:80:9: RUF052 [*] Local dummy variable `_var` is accessed
|
78 | return _var
79 |
80 | def fun(_var): # [RUF052]
| ^^^^ RUF052
81 | return _var
|
= help: Remove leading underscores
Safe fix
77 77 | _var = "method variable" # [RUF052]
78 78 | return _var
79 79 |
80 |-def fun(_var): # [RUF052]
81 |- return _var
80 |+def fun(var): # [RUF052]
81 |+ return var
82 82 |
83 83 | def fun():
84 84 | _list = "built-in" # [RUF052]
RUF052.py:84:5: RUF052 [*] Local dummy variable `_list` is accessed
|
83 | def fun():
84 | _list = "built-in" # [RUF052]
| ^^^^^ RUF052
85 | return _list
|
= help: Prefer using trailing underscores to avoid shadowing a built-in
Safe fix
81 81 | return _var
82 82 |
83 83 | def fun():
84 |- _list = "built-in" # [RUF052]
85 |- return _list
84 |+ list_ = "built-in" # [RUF052]
85 |+ return list_
86 86 |
87 87 | x = "global"
88 88 |
RUF052.py:91:5: RUF052 [*] Local dummy variable `_x` is accessed
|
89 | def fun():
90 | global x
91 | _x = "shadows global" # [RUF052]
| ^^ RUF052
92 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
88 88 |
89 89 | def fun():
90 90 | global x
91 |- _x = "shadows global" # [RUF052]
92 |- return _x
91 |+ x_ = "shadows global" # [RUF052]
92 |+ return x_
93 93 |
94 94 | def foo():
95 95 | x = "outer"
RUF052.py:98:5: RUF052 [*] Local dummy variable `_x` is accessed
|
96 | def bar():
97 | nonlocal x
98 | _x = "shadows nonlocal" # [RUF052]
| ^^ RUF052
99 | return _x
100 | bar()
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
95 95 | x = "outer"
96 96 | def bar():
97 97 | nonlocal x
98 |- _x = "shadows nonlocal" # [RUF052]
99 |- return _x
98 |+ x_ = "shadows nonlocal" # [RUF052]
99 |+ return x_
100 100 | bar()
101 101 | return x
102 102 |
RUF052.py:105:5: RUF052 [*] Local dummy variable `_x` is accessed
|
103 | def fun():
104 | x = "local"
105 | _x = "shadows local" # [RUF052]
| ^^ RUF052
106 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable
Safe fix
102 102 |
103 103 | def fun():
104 104 | x = "local"
105 |- _x = "shadows local" # [RUF052]
106 |- return _x
105 |+ x_ = "shadows local" # [RUF052]
106 |+ return x_

View file

@ -0,0 +1,121 @@
---
source: crates/ruff_linter/src/rules/ruff/mod.rs
snapshot_kind: text
---
RUF052.py:21:9: RUF052 Local dummy variable `arg` is accessed
|
19 | _valid_fun()
20 |
21 | def fun(arg):
| ^^^ RUF052
22 | _valid_unused_var = arg
23 | pass
|
RUF052.py:50:18: RUF052 Local dummy variable `self` is accessed
|
48 | print(_valid_private_cls_attr)
49 |
50 | def __init__(self):
| ^^^^ RUF052
51 | self._valid_private_ins_attr = 2
52 | print(self._valid_private_ins_attr)
|
RUF052.py:54:23: RUF052 Local dummy variable `self` is accessed
|
52 | print(self._valid_private_ins_attr)
53 |
54 | def _valid_method(self):
| ^^^^ RUF052
55 | return self._valid_private_ins_attr
|
RUF052.py:57:16: RUF052 Local dummy variable `arg` is accessed
|
55 | return self._valid_private_ins_attr
56 |
57 | def method(arg):
| ^^^ RUF052
58 | _valid_unused_var = arg
59 | return
|
RUF052.py:61:9: RUF052 Local dummy variable `x` is accessed
|
59 | return
60 |
61 | def fun(x):
| ^ RUF052
62 | _ = 1
63 | __ = 2
|
RUF052.py:77:9: RUF052 Local dummy variable `_var` is accessed
|
75 | class Class_:
76 | def fun(self):
77 | _var = "method variable" # [RUF052]
| ^^^^ RUF052
78 | return _var
|
= help: Remove leading underscores
RUF052.py:80:9: RUF052 Local dummy variable `_var` is accessed
|
78 | return _var
79 |
80 | def fun(_var): # [RUF052]
| ^^^^ RUF052
81 | return _var
|
= help: Remove leading underscores
RUF052.py:84:5: RUF052 Local dummy variable `_list` is accessed
|
83 | def fun():
84 | _list = "built-in" # [RUF052]
| ^^^^^ RUF052
85 | return _list
|
= help: Prefer using trailing underscores to avoid shadowing a built-in
RUF052.py:91:5: RUF052 Local dummy variable `_x` is accessed
|
89 | def fun():
90 | global x
91 | _x = "shadows global" # [RUF052]
| ^^ RUF052
92 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable
RUF052.py:95:3: RUF052 Local dummy variable `x` is accessed
|
94 | def foo():
95 | x = "outer"
| ^ RUF052
96 | def bar():
97 | nonlocal x
|
RUF052.py:98:5: RUF052 Local dummy variable `_x` is accessed
|
96 | def bar():
97 | nonlocal x
98 | _x = "shadows nonlocal" # [RUF052]
| ^^ RUF052
99 | return _x
100 | bar()
|
= help: Prefer using trailing underscores to avoid shadowing a variable
RUF052.py:105:5: RUF052 Local dummy variable `_x` is accessed
|
103 | def fun():
104 | x = "local"
105 | _x = "shadows local" # [RUF052]
| ^^ RUF052
106 | return _x
|
= help: Prefer using trailing underscores to avoid shadowing a variable

View file

@ -325,9 +325,15 @@ impl<'a> SemanticModel<'a> {
}
/// Return `true` if `member` is an "available" symbol, i.e., a symbol that has not been bound
/// in the current scope, or in any containing scope.
/// in the current scope currently being visited, or in any containing scope.
pub fn is_available(&self, member: &str) -> bool {
self.lookup_symbol(member)
self.is_available_in_scope(member, self.scope_id)
}
/// Return `true` if `member` is an "available" symbol in a given scope, i.e.,
/// a symbol that has not been bound in that current scope, or in any containing scope.
pub fn is_available_in_scope(&self, member: &str, scope_id: ScopeId) -> bool {
self.lookup_symbol_in_scope(member, scope_id, false)
.map(|binding_id| &self.bindings[binding_id])
.map_or(true, |binding| binding.kind.is_builtin())
}
@ -620,10 +626,22 @@ impl<'a> SemanticModel<'a> {
}
}
/// Lookup a symbol in the current scope. This is a carbon copy of [`Self::resolve_load`], but
/// doesn't add any read references to the resolved symbol.
/// Lookup a symbol in the current scope.
pub fn lookup_symbol(&self, symbol: &str) -> Option<BindingId> {
if self.in_forward_reference() {
self.lookup_symbol_in_scope(symbol, self.scope_id, self.in_forward_reference())
}
/// Lookup a symbol in a certain scope
///
/// This is a carbon copy of [`Self::resolve_load`], but
/// doesn't add any read references to the resolved symbol.
pub fn lookup_symbol_in_scope(
&self,
symbol: &str,
scope_id: ScopeId,
in_forward_reference: bool,
) -> Option<BindingId> {
if in_forward_reference {
if let Some(binding_id) = self.scopes.global().get(symbol) {
if !self.bindings[binding_id].is_unbound() {
return Some(binding_id);
@ -633,7 +651,7 @@ impl<'a> SemanticModel<'a> {
let mut seen_function = false;
let mut class_variables_visible = true;
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
for (index, scope_id) in self.scopes.ancestor_ids(scope_id).enumerate() {
let scope = &self.scopes[scope_id];
if scope.kind.is_class() {
if seen_function && matches!(symbol, "__class__") {

View file

@ -1,5 +1,5 @@
// See: https://github.com/python/cpython/blob/9d692841691590c25e6cf5b2250a594d3bf54825/Lib/keyword.py#L18
pub(crate) fn is_keyword(name: &str) -> bool {
pub fn is_keyword(name: &str) -> bool {
matches!(
name,
"False"

1
ruff.schema.json generated
View file

@ -3845,6 +3845,7 @@
"RUF041",
"RUF048",
"RUF05",
"RUF052",
"RUF055",
"RUF1",
"RUF10",