diff options
author | Jacob Walls <jacobtylerwalls@gmail.com> | 2022-03-21 04:49:45 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-21 09:49:45 +0100 |
commit | 2cb553658ebb97fb4326ddcb6bbd904efa6e520a (patch) | |
tree | 1e482edfb5c02cdfb40020d60a4a39d1957589e8 | |
parent | d0e9fb1b24370a8a58c53b680340860aaeff7c29 (diff) | |
download | pylint-git-2cb553658ebb97fb4326ddcb6bbd904efa6e520a.tar.gz |
Fix #4590: `used-before-assignment` false positive for class definition in function scope (#5937)
-rw-r--r-- | ChangeLog | 5 | ||||
-rw-r--r-- | doc/whatsnew/2.13.rst | 5 | ||||
-rw-r--r-- | pylint/checkers/variables.py | 28 | ||||
-rw-r--r-- | tests/functional/u/used/used_before_assignment_class_nested_under_function.py | 13 |
4 files changed, 44 insertions, 7 deletions
@@ -376,6 +376,11 @@ Release date: TBA Closes #5679 +* Fix false positive for ``used-before-assignment`` from a class definition + nested under a function subclassing a class defined outside the function. + + Closes #4590 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/doc/whatsnew/2.13.rst b/doc/whatsnew/2.13.rst index c48703c3f..7b2f18165 100644 --- a/doc/whatsnew/2.13.rst +++ b/doc/whatsnew/2.13.rst @@ -267,6 +267,11 @@ Other Changes Closes #5724 +* Fix false positive for ``used-before-assignment`` from a class definition + nested under a function subclassing a class defined outside the function. + + Closes #4590 + * Fix ``unnecessary_dict_index_lookup`` false positive when deleting a dictionary's entry. Closes #4716 diff --git a/pylint/checkers/variables.py b/pylint/checkers/variables.py index d05f578c0..3580e660e 100644 --- a/pylint/checkers/variables.py +++ b/pylint/checkers/variables.py @@ -236,27 +236,41 @@ def _get_unpacking_extra_info(node, inferred): def _detect_global_scope(node, frame, defframe): - """Detect that the given frames shares a global - scope. + """Detect that the given frames share a global scope. - Two frames shares a global scope when neither + Two frames share a global scope when neither of them are hidden under a function scope, as well - as any of parent scope of them, until the root scope. + as any parent scope of them, until the root scope. In this case, depending from something defined later on - will not work, because it is still undefined. + will only work if guarded by a nested function definition. Example: class A: # B has the same global scope as `C`, leading to a NameError. + # Return True to indicate a shared scope. class B(C): ... class C: ... + Whereas this does not lead to a NameError: + class A: + def guard(): + # Return False to indicate no scope sharing. + class B(C): ... + class C: ... """ def_scope = scope = None if frame and frame.parent: scope = frame.parent.scope() if defframe and defframe.parent: def_scope = defframe.parent.scope() + if ( + isinstance(frame, nodes.ClassDef) + and scope is not def_scope + and scope is utils.get_node_first_ancestor_of_type(node, nodes.FunctionDef) + ): + # If the current node's scope is a class nested under a function, + # and the def_scope is something else, then they aren't shared. + return False if isinstance(frame, nodes.FunctionDef): # If the parent of the current node is a # function, then it can be under its scope @@ -290,12 +304,12 @@ def _detect_global_scope(node, frame, defframe): if break_scopes and len(set(break_scopes)) != 1: # Store different scopes than expected. # If the stored scopes are, in fact, the very same, then it means - # that the two frames (frame and defframe) shares the same scope, + # that the two frames (frame and defframe) share the same scope, # and we could apply our lineno analysis over them. # For instance, this works when they are inside a function, the node # that uses a definition and the definition itself. return False - # At this point, we are certain that frame and defframe shares a scope + # At this point, we are certain that frame and defframe share a scope # and the definition of the first depends on the second. return frame.lineno < defframe.lineno diff --git a/tests/functional/u/used/used_before_assignment_class_nested_under_function.py b/tests/functional/u/used/used_before_assignment_class_nested_under_function.py new file mode 100644 index 000000000..3627ae1e1 --- /dev/null +++ b/tests/functional/u/used/used_before_assignment_class_nested_under_function.py @@ -0,0 +1,13 @@ +"""https://github.com/PyCQA/pylint/issues/4590""" +# pylint: disable=too-few-public-methods + + +def conditional_class_factory(): + """Define a nested class""" + class ConditionalClass(ModuleClass): + """Subclasses a name from the module scope""" + return ConditionalClass + + +class ModuleClass: + """Module-level class""" |