Repository: ipython/ipython

Issue:
async-detection: nonlocal does not raise syntaxerror
See https://github.com/ipython/ipython/issues/11303#issuecomment-421297197

```
In [1]: x = 1
   ...: def f():
   ...:     nonlocal x
   ...:     x = 10000
```
Should raise but does not.

It's minor, but may be good to fix as behavior is likely undefined.
async-detection: nonlocal does not raise syntaxerror
See https://github.com/ipython/ipython/issues/11303#issuecomment-421297197

```
In [1]: x = 1
   ...: def f():
   ...:     nonlocal x
   ...:     x = 10000
```
Should raise but does not.

It's minor, but may be good to fix as behavior is likely undefined.


Discussion:
Hey @Carreau 
Can I take this up for hacktoberfest? Could you please give me some idea as to what needs to be done?
Of course,

Feel free to ask for pointers. This one is not one of the easiest if you have never played with AST. 

Here are some pointers:
Ast unofficial doc: https://greentreesnakes.readthedocs.io/en/latest/
see the work done by pganssle in #11265, in particulat the AstNodeVisitor [here](https://github.com/ipython/ipython/pull/11265/files#diff-54a26c8144762acb631794c13952ff7cR101), 

it's likely a matter of having it go only _once_ in FunctionDefs, and in these prevent nonlocal.
Sure! I'll send in a PR for this 👍 
And ask for pointers as well. Thanks!
Hey @Carreau 
Can I take this up for hacktoberfest? Could you please give me some idea as to what needs to be done?
Of course,

Feel free to ask for pointers. This one is not one of the easiest if you have never played with AST. 

Here are some pointers:
Ast unofficial doc: https://greentreesnakes.readthedocs.io/en/latest/
see the work done by pganssle in #11265, in particulat the AstNodeVisitor [here](https://github.com/ipython/ipython/pull/11265/files#diff-54a26c8144762acb631794c13952ff7cR101), 

it's likely a matter of having it go only _once_ in FunctionDefs, and in these prevent nonlocal.
Sure! I'll send in a PR for this 👍 
And ask for pointers as well. Thanks!

Resolving patch:
diff --git a/IPython/core/async_helpers.py b/IPython/core/async_helpers.py
--- 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)


Test patch:
diff --git a/IPython/core/tests/test_async_helpers.py b/IPython/core/tests/test_async_helpers.py
--- a/IPython/core/tests/test_async_helpers.py
+++ b/IPython/core/tests/test_async_helpers.py
@@ -227,6 +227,35 @@ def nest_case(context, case):
                             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("""