diff --git a/README.rst b/README.rst index 8de3e44..22f7cb7 100644 --- a/README.rst +++ b/README.rst @@ -308,6 +308,11 @@ MIT Change Log ---------- +Future +~~~~~~~~~ + +* B906: Ignore ``visit_`` functions with a ``_fields`` attribute that can't contain ast.AST subnodes. (#330) + 23.1.17 ~~~~~~~~~ diff --git a/bugbear.py b/bugbear.py index ba1eefc..aad3557 100644 --- a/bugbear.py +++ b/bugbear.py @@ -997,12 +997,29 @@ def check_for_b906(self, node: ast.FunctionDef): if not node.name.startswith("visit_"): return - # extract what's visited, only error if it's a valid ast subclass - # with a non-empty _fields attribute - which is what's iterated over in - # ast.NodeVisitor.generic_visit + # extract what's visited class_name = node.name[len("visit_") :] class_type = getattr(ast, class_name, None) - if class_type is None or not getattr(class_type, "_fields", None): + + if ( + # not a valid ast subclass + class_type is None + # doesn't have a non-empty '_fields' attribute - which is what's + # iterated over in ast.NodeVisitor.generic_visit + or not getattr(class_type, "_fields", None) + # or can't contain any ast subnodes that could be visited + # See https://docs.python.org/3/library/ast.html#abstract-grammar + or class_type.__name__ + in ( + "alias", + "Constant", + "Global", + "MatchSingleton", + "MatchStar", + "Nonlocal", + "TypeIgnore", + ) + ): return for n in itertools.chain.from_iterable(ast.walk(nn) for nn in node.body): diff --git a/tests/b906.py b/tests/b906.py index 35cfe42..f947c14 100644 --- a/tests/b906.py +++ b/tests/b906.py @@ -54,3 +54,33 @@ def a(): def visit_(): ... + + +# Check exceptions for ast types that only contain ADSL builtin types +# i.e. don't contain any ast.AST subnodes and therefore don't need a generic_visit +def visit_alias(): + ... + + +def visit_Constant(): + ... + + +def visit_Global(): + ... + + +def visit_MatchSingleton(): + ... + + +def visit_MatchStar(): + ... + + +def visit_Nonlocal(): + ... + + +def visit_TypeIgnore(): + ...