ruff/crates/ruff_linter/resources/test/fixtures/flake8_bugbear/B004.py
Takayuki Maeda 43cda2dfe9
[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
2025-09-19 13:44:42 -05:00

72 lines
1.8 KiB
Python

def this_is_a_bug():
o = object()
if hasattr(o, "__call__"):
print("Ooh, callable! Or is it?")
if getattr(o, "__call__", False):
print("Ooh, callable! Or is it?")
def still_a_bug():
import builtins
o = object()
if builtins.hasattr(o, "__call__"):
print("B U G")
if builtins.getattr(o, "__call__", False):
print("B U G")
def trickier_fix_for_this_one():
o = object()
def callable(x):
return True
if hasattr(o, "__call__"):
print("STILL a bug!")
def this_is_fine():
o = object()
if callable(o):
print("Ooh, this is actually callable.")
# https://github.com/astral-sh/ruff/issues/18741
# The autofix for this is unsafe due to the comments.
hasattr(
# comment 1
obj, # comment 2
# comment 3
"__call__", # comment 4
# comment 5
)
import operator
assert hasattr(operator, "__call__")
assert callable(operator) is False
class A:
def __init__(self): self.__call__ = None
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