[ruff] Fix B004 to skip invalid hasattr/getattr calls (#20486)
## Summary Fixes #20440 Fix B004 to skip invalid hasattr/getattr calls - Add argument validation for `hasattr` and `getattr` - Skip B004 rule when function calls have invalid argument patterns
This commit is contained in:
@@ -52,3 +52,21 @@ class A:
|
||||
|
||||
assert hasattr(A(), "__call__")
|
||||
assert callable(A()) is False
|
||||
|
||||
# https://github.com/astral-sh/ruff/issues/20440
|
||||
def test_invalid_hasattr_calls():
|
||||
hasattr(0, "__call__", 0) # 3 args - invalid
|
||||
hasattr(0, "__call__", x=0) # keyword arg - invalid
|
||||
hasattr(0, "__call__", 0, x=0) # 3 args + keyword - invalid
|
||||
hasattr() # no args - invalid
|
||||
hasattr(0) # 1 arg - invalid
|
||||
hasattr(*(), "__call__", "extra") # unpacking - invalid
|
||||
hasattr(*()) # unpacking - invalid
|
||||
|
||||
def test_invalid_getattr_calls():
|
||||
getattr(0, "__call__", None, "extra") # 4 args - invalid
|
||||
getattr(0, "__call__", default=None) # keyword arg - invalid
|
||||
getattr() # no args - invalid
|
||||
getattr(0) # 1 arg - invalid
|
||||
getattr(*(), "__call__", None, "extra") # unpacking - invalid
|
||||
getattr(*()) # unpacking - invalid
|
||||
|
||||
@@ -717,7 +717,9 @@ pub(crate) fn expression(expr: &Expr, checker: &Checker) {
|
||||
flake8_bugbear::rules::re_sub_positional_args(checker, call);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::UnreliableCallableCheck) {
|
||||
flake8_bugbear::rules::unreliable_callable_check(checker, expr, func, args);
|
||||
flake8_bugbear::rules::unreliable_callable_check(
|
||||
checker, expr, func, args, keywords,
|
||||
);
|
||||
}
|
||||
if checker.is_rule_enabled(Rule::StripWithMultiCharacters) {
|
||||
flake8_bugbear::rules::strip_with_multi_characters(checker, expr, func, args);
|
||||
|
||||
@@ -90,7 +90,11 @@ pub(crate) fn unreliable_callable_check(
|
||||
expr: &Expr,
|
||||
func: &Expr,
|
||||
args: &[Expr],
|
||||
keywords: &[ast::Keyword],
|
||||
) {
|
||||
if !keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
let [obj, attr, ..] = args else {
|
||||
return;
|
||||
};
|
||||
@@ -103,7 +107,21 @@ pub(crate) fn unreliable_callable_check(
|
||||
let Some(builtins_function) = checker.semantic().resolve_builtin_symbol(func) else {
|
||||
return;
|
||||
};
|
||||
if !matches!(builtins_function, "hasattr" | "getattr") {
|
||||
|
||||
// Validate function arguments based on function name
|
||||
let valid_args = match builtins_function {
|
||||
"hasattr" => {
|
||||
// hasattr should have exactly 2 positional arguments and no keywords
|
||||
args.len() == 2
|
||||
}
|
||||
"getattr" => {
|
||||
// getattr should have 2 or 3 positional arguments and no keywords
|
||||
args.len() == 2 || args.len() == 3
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
|
||||
if !valid_args {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,4 +156,6 @@ help: Replace with `callable()`
|
||||
- assert hasattr(A(), "__call__")
|
||||
53 + assert callable(A())
|
||||
54 | assert callable(A()) is False
|
||||
55 |
|
||||
56 | # https://github.com/astral-sh/ruff/issues/20440
|
||||
note: This is an unsafe fix and may change runtime behavior
|
||||
|
||||
Reference in New Issue
Block a user