diff --git a/parso/python/normalizer.py b/parso/python/normalizer.py index e3cbab8..02d1f8d 100644 --- a/parso/python/normalizer.py +++ b/parso/python/normalizer.py @@ -100,6 +100,7 @@ class Context(object): self._used_name_dict = {} self._global_names = [] self._nonlocal_names = [] + self._nonlocal_names_in_subscopes = [] self._add_syntax_error = add_syntax_error def is_async_funcdef(self): @@ -125,14 +126,17 @@ class Context(object): self._used_name_dict.setdefault(name.value, []).append(name) def finalize(self): + """ + Returns a list of nonlocal names that need to be part of that scope. + """ self._analyze_names(self._global_names, 'global') self._analyze_names(self._nonlocal_names, 'nonlocal') # Python2.6 doesn't have dict comprehensions. - nonlocal_name_strs = dict((n.value, n) for n in self._nonlocal_names) - for global_name in self._global_names: + global_name_strs = dict((n.value, n) for n in self._global_names) + for nonlocal_name in self._nonlocal_names: try: - nonlocal_name = nonlocal_name_strs[global_name.value] + global_name = global_name_strs[nonlocal_name.value] except KeyError: continue @@ -143,6 +147,17 @@ class Context(object): error_name = nonlocal_name self._add_syntax_error(message, error_name) + nonlocals_not_handled = [] + for nonlocal_name in self._nonlocal_names_in_subscopes: + search = nonlocal_name.value + if search in global_name_strs or self.parent_context is None: + message = "no binding for nonlocal '%s' found" % nonlocal_name.value + self._add_syntax_error(message, nonlocal_name) + elif not self.is_function() or \ + nonlocal_name.value not in self._used_name_dict: + nonlocals_not_handled.append(nonlocal_name) + return self._nonlocal_names + nonlocals_not_handled + def _analyze_names(self, globals_or_nonlocals, type_): def raise_(message): self._add_syntax_error(message % (base_name.value, type_), base_name) @@ -198,7 +213,7 @@ class Context(object): def add_context(self, node): new_context = Context(node, self._add_syntax_error, parent_context=self) yield new_context - new_context.finalize() + self._nonlocal_names_in_subscopes += new_context.finalize() class ErrorFinder(Normalizer): @@ -627,6 +642,8 @@ class ErrorFinder(Normalizer): self._error_dict.setdefault(line, (code, message, node)) def finalize(self): + self._context.finalize() + for code, message, node in self._error_dict.values(): self.issues.append(Issue(node, code, message)) diff --git a/parso/python/tokenize.py b/parso/python/tokenize.py index d99d152..9d8b6b8 100644 --- a/parso/python/tokenize.py +++ b/parso/python/tokenize.py @@ -71,6 +71,7 @@ def _all_string_prefixes(version_info): if version_info <= (2, 7): # TODO this is actually not 100% valid. ur is valid in Python 2.7, # while ru is not. + # TODO rb is also not valid. _valid_string_prefixes.append('ur') # if we add binary f-strings, add: ['fb', 'fbr'] diff --git a/test/normalizer_issue_files/allowed_syntax.py b/test/normalizer_issue_files/allowed_syntax.py index 20e2959..e166e14 100644 --- a/test/normalizer_issue_files/allowed_syntax.py +++ b/test/normalizer_issue_files/allowed_syntax.py @@ -47,13 +47,12 @@ except ZeroDivisionError: pass -class X(): - nonlocal a - - def c(): - class X(): - nonlocal a + a = 3 + + def d(): + class X(): + nonlocal a def x(): diff --git a/test/test_python_errors.py b/test/test_python_errors.py index 731747d..804cbce 100644 --- a/test/test_python_errors.py +++ b/test/test_python_errors.py @@ -175,10 +175,30 @@ FAILING_EXAMPLES = [ '''), dedent(''' def x(): - global a - nonlocal a + a = 4 + def y(): + global a + nonlocal a '''), # Missing binding of nonlocal + dedent(''' + def x(): + nonlocal a + '''), + dedent(''' + def x(): + def y(): + nonlocal a + '''), + dedent(''' + def x(): + a = 4 + def y(): + global a + print(a) + def z(): + nonlocal a + '''),