From bcf81947a9c3c06ddbe1873e7d01b42d36d41617 Mon Sep 17 00:00:00 2001 From: Danny Sepler Date: Fri, 24 Dec 2021 12:44:34 -0500 Subject: [PATCH] Global variables must be assigned to be used --- pyflakes/checker.py | 26 +++++++++++++++++++----- pyflakes/test/test_undefined_names.py | 29 +++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/pyflakes/checker.py b/pyflakes/checker.py index 56fc3ca4..d956319b 100644 --- a/pyflakes/checker.py +++ b/pyflakes/checker.py @@ -262,10 +262,11 @@ class Binding: the node that this binding was last used. """ - def __init__(self, name, source): + def __init__(self, name, source, *, assigned=True): self.name = name self.source = source self.used = False + self.assigned = assigned def __str__(self): return self.name @@ -1073,6 +1074,12 @@ def addBinding(self, node, value): break existing = scope.get(value.name) + global_scope = self.scopeStack[-1] + if (existing and global_scope.get(value.name) == existing and + not existing.assigned): + # make sure the variable is in the global scope before setting as assigned + existing.assigned = True + if (existing and not isinstance(existing, Builtin) and not self.differentForks(node, existing.source)): @@ -1155,6 +1162,10 @@ def handleNodeLoad(self, node): continue binding = scope.get(name, None) + + if getattr(binding, 'assigned', None) is False: + self.report(messages.UndefinedName, node, name) + if isinstance(binding, Annotation) and not self._in_postponed_annotation: continue @@ -1224,12 +1235,19 @@ def handleNodeStore(self, node): continue # if the name was defined in that scope, and the name has # been accessed already in the current scope, and hasn't - # been declared global + # been assigned globally used = name in scope and scope[name].used if used and used[0] is self.scope and name not in self.scope.globals: # then it's probably a mistake self.report(messages.UndefinedLocal, scope[name].used[1], name, scope[name].source) + + # and remove UndefinedName messages already reported for this name + self.messages = [ + m for m in self.messages if not + isinstance(m, messages.UndefinedName) or + m.message_args[0] != name] + break parent_stmt = self.getParent(node) @@ -1933,11 +1951,9 @@ def GLOBAL(self, node): # One 'global' statement can bind multiple (comma-delimited) names. for node_name in node.names: - node_value = Assignment(node_name, node) + node_value = Assignment(node_name, node, assigned=False) # Remove UndefinedName messages already reported for this name. - # TODO: if the global is not used in this scope, it does not - # become a globally defined name. See test_unused_global. self.messages = [ m for m in self.messages if not isinstance(m, messages.UndefinedName) or diff --git a/pyflakes/test/test_undefined_names.py b/pyflakes/test/test_undefined_names.py index f3b89eaa..b997e5cb 100644 --- a/pyflakes/test/test_undefined_names.py +++ b/pyflakes/test/test_undefined_names.py @@ -298,6 +298,35 @@ def c(): bar def b(): global bar; bar = 1 ''') + def test_unassigned_global_is_undefined(self): + """ + If a "global" is never given a value, it is undefined + """ + self.flakes(''' + def a(): + global fu + fu + ''', m.UndefinedName) + + self.flakes(''' + global fu + fu + ''', m.UndefinedName) + + def test_scope_defined_global(self): + """ + If a "global" is defined inside of a function only, + outside of the function it is undefined + """ + self.flakes(''' + global fu + def a(): + fu = 1 + fu + a() + fu + ''', m.UndefinedName) + def test_definedByGlobalMultipleNames(self): """ "global" can accept multiple names.