Repository: xonsh/xonsh

Issue:
Dictionary Comprehension Crash
xonsh just crashed when running the following:

```
$ x = {k:float(v) for k,v in x.iteritems()}
```

The error is coming from an `assert False` in the parser.  It looks like an issue with the multiple assignment, since wrapping `k,v` in parens works:

```
$ x = {k:float(v) for (k,v) in x.items()}
```



Discussion:
Also an issue in list comprehensions:

```
$ x = [(k,float(v)) for k,v in x.items()]
Traceback (most recent call last):
  File "/usr/local/bin/xonsh", line 3, in <module>
    main()
  File "/usr/local/lib/python3.4/site-packages/xonsh/main.py", line 68, in main
    shell.cmdloop()
  File "/usr/local/lib/python3.4/site-packages/xonsh/shell.py", line 181, in cmdloop
    super(Shell, self).cmdloop(intro=intro)
  File "/usr/local/lib/python3.4/cmd.py", line 138, in cmdloop
    stop = self.onecmd(line)
  File "/usr/local/lib/python3.4/cmd.py", line 211, in onecmd
    return self.default(line)
  File "/usr/local/lib/python3.4/site-packages/xonsh/shell.py", line 127, in default
    code = self.push(line)
  File "/usr/local/lib/python3.4/site-packages/xonsh/shell.py", line 152, in push
    locs=self.ctx)
  File "/usr/local/lib/python3.4/site-packages/xonsh/execer.py", line 92, in compile
    tree = self.parse(input, ctx, mode=mode)
  File "/usr/local/lib/python3.4/site-packages/xonsh/execer.py", line 70, in parse
    tree = self._parse_ctx_free(input, mode=mode)
  File "/usr/local/lib/python3.4/site-packages/xonsh/execer.py", line 148, in _parse_ctx_free
    debug_level=self.debug_level)
  File "/usr/local/lib/python3.4/site-packages/xonsh/parser.py", line 258, in parse
    tree = self.parser.parse(input=s, lexer=self.lexer, debug=debug_level)
  File "/usr/local/lib/python3.4/site-packages/ply/yacc.py", line 265, in parse
    return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc)
  File "/usr/local/lib/python3.4/site-packages/ply/yacc.py", line 971, in parseopt_notrack
    p.callable(pslice)
  File "/usr/local/lib/python3.4/site-packages/xonsh/parser.py", line 2049, in p_comp_for
    assert False
AssertionError
```

OK, I'll try to take a look later tonight.


Resolving patch:
diff --git a/xonsh/parser.py b/xonsh/parser.py
--- a/xonsh/parser.py
+++ b/xonsh/parser.py
@@ -2045,9 +2045,11 @@ def p_comp_iter(self, p):
     def p_comp_for(self, p):
         """comp_for : FOR exprlist IN or_test comp_iter_opt"""
         targs, it, p5 = p[2], p[4], p[5]
-        if len(targs) != 1:
-            assert False
-        targ = targs[0]
+        if len(targs) == 1:
+            targ = targs[0]
+        else:
+            targ = ensure_has_elts(targs, lineno=self.lineno, 
+                                   col_offset=self.col)
         store_ctx(targ)
         comp = ast.comprehension(target=targ, iter=it, ifs=[])
         comps = [comp]


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -481,20 +481,26 @@ def test_setcomp_if_setcomp_if():
 def test_dictcomp():
     yield check_ast, '{x: x for x in "mom"}'
 
+def test_dictcomp_unpack_parens():
+    yield check_ast, '{k: v for (k, v) in {"x": 42}.items()}'
+
+def test_dictcomp_unpack_no_parens():
+    yield check_ast, '{k: v for k, v in {"x": 42}.items()}'
+
 def test_dictcomp_if():
-    yield check_ast, '{x:x for x in "mom" if True}'
+    yield check_ast, '{x: x for x in "mom" if True}'
 
 def test_dictcomp_if_and():
-    yield check_ast, '{x:x for x in "mom" if True and x == "m"}'
+    yield check_ast, '{x: x for x in "mom" if True and x == "m"}'
 
 def test_dbl_dictcomp():
-    yield check_ast, '{x:y for x in "mom" for y in "dad"}'
+    yield check_ast, '{x: y for x in "mom" for y in "dad"}'
 
 def test_dictcomp_if_dictcomp():
-    yield check_ast, '{x:y for x in "mom" if True for y in "dad"}'
+    yield check_ast, '{x: y for x in "mom" if True for y in "dad"}'
 
 def test_dictcomp_if_dictcomp_if():
-    yield check_ast, '{x:y for x in "mom" if True for y in "dad" if y == "d"}'
+    yield check_ast, '{x: y for x in "mom" if True for y in "dad" if y == "d"}'
 
 def test_lambda():
     yield check_ast, 'lambda: 42'