Repository: xonsh/xonsh

Issue:
xonsh does not support type hinting
I'm writing a plugin for Xonsh in pure Python using the Xonsh library. I've added [type hints](https://docs.python.org/3/library/typing.html) so that I can use `mypy` and other static checkers on it.

However, it seems Xonsh does not support this. Type hints not only improve correctness, but also serve as a form of documentation. Since Xonsh is intended to be a superset of Python, it would be nice if Xonsh adds support for type hints.

**Example:**

This works:
```shell
python3 -c "x: int = 5" 
```

This doesn't:
```shell
xonsh -c "x: int = 5" 
```


Discussion:
Hi there, I believe that the type hit syntax is supported. I do see this bug too, though. 

Resolving patch:
diff --git a/xonsh/ast.py b/xonsh/ast.py
--- a/xonsh/ast.py
+++ b/xonsh/ast.py
@@ -167,8 +167,10 @@ def leftmostname(node):
         rtn = leftmostname(node.operand)
     elif isinstance(node, BoolOp):
         rtn = leftmostname(node.values[0])
-    elif isinstance(node, (Assign, AnnAssign)):
+    elif isinstance(node, Assign):
         rtn = leftmostname(node.targets[0])
+    elif isinstance(node, AnnAssign):
+        rtn = leftmostname(node.target)
     elif isinstance(node, (Str, Bytes, JoinedStr)):
         # handles case of "./my executable"
         rtn = leftmostname(node.s)
@@ -541,7 +543,10 @@ def visit_Assign(self, node):
         self.ctxupdate(ups)
         return node
 
-    visit_AnnAssign = visit_Assign
+    def visit_AnnAssign(self, node):
+        """Handle visiting an annotated assignment statement."""
+        self.ctxadd(leftmostname(node.target))
+        return node
 
     def visit_Import(self, node):
         """Handle visiting a import statement."""
diff --git a/xonsh/parsers/v36.py b/xonsh/parsers/v36.py
--- a/xonsh/parsers/v36.py
+++ b/xonsh/parsers/v36.py
@@ -162,17 +162,21 @@ def p_comp_for(self, p):
         p[0]["comps"][0].is_async = 0
 
     def p_expr_stmt_annassign(self, p):
-        """expr_stmt : testlist_star_expr COLON test EQUALS test"""
+        """expr_stmt : testlist_star_expr COLON test EQUALS test
+        | testlist_star_expr COLON test
+        """
         p1 = p[1][0]
         lineno, col = lopen_loc(p1)
-        if len(p[1]) > 1 or not isinstance(p1, ast.Name):
+        if len(p[1]) > 1 or not isinstance(
+            p1, (ast.Name, ast.Attribute, ast.Subscript)
+        ):
             loc = self.currloc(lineno, col)
             self._set_error("only single target can be annotated", loc)
         store_ctx(p1)
         p[0] = ast.AnnAssign(
             target=p1,
             annotation=p[3],
-            value=p[5],
+            value=p[5] if len(p) >= 6 else None,
             simple=1,
             lineno=lineno,
             col_offset=col,


Test patch:
diff --git a/tests/test_execer.py b/tests/test_execer.py
--- a/tests/test_execer.py
+++ b/tests/test_execer.py
@@ -150,6 +150,11 @@ def test_eval_eol():
     assert check_eval("0") and check_eval("0\n")
 
 
+def test_annotated_assign():
+    # issue #3959 - didn't work because of `CtxAwareTransformer`
+    assert check_exec("x : int = 42")
+
+
 def test_exec_eol():
     locs = dict()
     assert check_exec("a=0", locs=locs) and check_exec("a=0\n", locs=locs)
diff --git a/tests/test_parser.py b/tests/test_parser.py
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -1413,6 +1413,10 @@ def test_equals_annotation():
     check_stmts("x : int = 42")
 
 
+def test_equals_annotation_empty():
+    check_stmts("x : int")
+
+
 def test_dict_keys():
     check_stmts('x = {"x": 1}\nx.keys()')