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

B006 and B008: Cover additional test cases #239

Merged
merged 7 commits into from Mar 23, 2022

Conversation

jpy-git
Copy link
Contributor

@jpy-git jpy-git commented Mar 22, 2022

Closes #229.
Detects function calls at any level of the default expression.
This includes instances of B008 which occur within instances of B006 (e.g. def f(a=[datetime.now()])) since a user may choose to turn one off but still want to be warned of the other.

.pre-commit-config.yaml Show resolved Hide resolved
Comment on lines -393 to -400
def compose_call_path(self, node):
if isinstance(node, ast.Attribute):
yield from self.compose_call_path(node.value)
yield node.attr
elif isinstance(node, ast.Call):
yield from self.compose_call_path(node.func)
elif isinstance(node, ast.Name):
yield node.id
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was useful in my other visitor and doesn't need to be in this class so separated it out

Comment on lines +413 to +416
def check_for_b006_and_b008(self, node):
visitor = FuntionDefDefaultsVisitor(self.b008_extend_immutable_calls)
visitor.visit(node.args.defaults + node.args.kw_defaults)
self.errors.extend(visitor.errors)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rules now use a visitor to handle functions at any depth

@cooperlees
Copy link
Collaborator

This is a big one. Calling in some much more ast savvy people to help review this one. There are some pieces I need to wrap my head around. Will eventually take the time and work it out, but adding others incase they can beat me.

Copy link
Member

@Zac-HD Zac-HD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me!

bugbear.py Outdated
Comment on lines 775 to 800
float_arg = node.args[0]
if sys.version_info < (3, 8, 0):
# NOTE: pre-3.8, string literals are represented with ast.Str
if isinstance(float_arg, ast.Str):
str_val = float_arg.s
else:
str_val = ""
else:
# NOTE: post-3.8, string literals are represented with ast.Constant
if isinstance(float_arg, ast.Constant):
str_val = float_arg.value
if not isinstance(str_val, str):
str_val = ""
else:
str_val = ""

# NOTE: regex derived from documentation at:
# https://docs.python.org/3/library/functions.html#float
inf_nan_regex = r"^[+-]?(inf|infinity|nan)$"
re_result = re.search(inf_nan_regex, str_val.lower())
is_float_literal = re_result is not None
else:
is_float_literal = False

if not is_float_literal:
self.errors.append(B008(node.lineno, node.col_offset))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this was previously existing code, but we can shorten it considerably by using ast.literal_eval():

Suggested change
float_arg = node.args[0]
if sys.version_info < (3, 8, 0):
# NOTE: pre-3.8, string literals are represented with ast.Str
if isinstance(float_arg, ast.Str):
str_val = float_arg.s
else:
str_val = ""
else:
# NOTE: post-3.8, string literals are represented with ast.Constant
if isinstance(float_arg, ast.Constant):
str_val = float_arg.value
if not isinstance(str_val, str):
str_val = ""
else:
str_val = ""
# NOTE: regex derived from documentation at:
# https://docs.python.org/3/library/functions.html#float
inf_nan_regex = r"^[+-]?(inf|infinity|nan)$"
re_result = re.search(inf_nan_regex, str_val.lower())
is_float_literal = re_result is not None
else:
is_float_literal = False
if not is_float_literal:
self.errors.append(B008(node.lineno, node.col_offset))
try:
value = float(ast.literal_eval(node.args[0]))
except Exception:
pass
else:
if math.isfinite(value):
self.errors.append(B008(node.lineno, node.col_offset))

(OK, I admit that this no longer warns on a redundant float() call wrapped around an "infinity literal" like 1e999, but IMO that's fine.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I've updated the code accordingly and it looks a lot nicer now 😄

Copy link
Collaborator

@cooperlees cooperlees left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All looks good to me, just some questions for my interest.

Especially if Zac can't see anything wrong + his awesome suggestion.

tests/b006_b008.py Show resolved Hide resolved


def kwonlyargs_mutable(*, value=[]):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need an empty list for coverage? I guess you removed this cause of all the list comprehension tests now ...

@cooperlees cooperlees merged commit f9e0f77 into PyCQA:main Mar 23, 2022
pass


def kwonlyargs_mutable(*, value=[]):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cooperlees Re: the question on removing this. I rearranged this test file to structure it a bit more and help come up with extra test cases. The function is still here 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

B008 fails for function calls within expressions
3 participants