Skip to content

Fix used-before-assignment false positive for class definition in function scope #5937

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,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
Expand Down
5 changes: 5 additions & 0 deletions doc/whatsnew/2.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,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
Expand Down
28 changes: 21 additions & 7 deletions pylint/checkers/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -290,12 +304,12 @@ class C: ...
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

Expand Down
Original file line number Diff line number Diff line change
@@ -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"""