diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py index a6ff860..c9ba182 100644 --- a/IPython/core/async_helpers.py +++ b/IPython/core/async_helpers.py @@ -97,14 +97,22 @@ class _AsyncSyntaxErrorVisitor(ast.NodeVisitor): the implementation involves wrapping the repl in an async function, it is erroneously allowed (e.g. yield or return at the top level) """ + def __init__(self): + self.depth = 0 + super().__init__() def generic_visit(self, node): func_types = (ast.FunctionDef, ast.AsyncFunctionDef) - invalid_types = (ast.Return, ast.Yield, ast.YieldFrom) - - if isinstance(node, func_types): - return # Don't recurse into functions - elif isinstance(node, invalid_types): + invalid_types_by_depth = { + 0: (ast.Return, ast.Yield, ast.YieldFrom), + 1: (ast.Nonlocal,) + } + + should_traverse = self.depth < max(invalid_types_by_depth.keys()) + if isinstance(node, func_types) and should_traverse: + self.depth += 1 + super().generic_visit(node) + elif isinstance(node, invalid_types_by_depth[self.depth]): raise SyntaxError() else: super().generic_visit(node) diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py index 6bf64e2..20ad3d0 100644 --- a/IPython/core/tests/test_async_helpers.py +++ b/IPython/core/tests/test_async_helpers.py @@ -227,6 +227,35 @@ if sys.version_info > (3, 5): else: iprc(cell) + def test_nonlocal(self): + # fails if outer scope is not a function scope or if var not defined + with self.assertRaises(SyntaxError): + iprc("nonlocal x") + iprc(""" + x = 1 + def f(): + nonlocal x + x = 10000 + yield x + """) + iprc(""" + def f(): + def g(): + nonlocal x + x = 10000 + yield x + """) + + # works if outer scope is a function scope and var exists + iprc(""" + def f(): + x = 20 + def g(): + nonlocal x + x = 10000 + yield x + """) + def test_execute(self): iprc("""