Repository: hylang/hy

Issue:
Remove mandatory brackets at the end of `dfor`
We've removed a lot of unnecessary brackets elsewhere, but we still require

```
(dfor  x (range 10)  [x (** x 2)])
```

instead of

```
(dfor  x (range 10)  x (** x 2))
```


Discussion:

Resolving patch:
diff --git a/hy/core/result_macros.py b/hy/core/result_macros.py
--- a/hy/core/result_macros.py
+++ b/hy/core/result_macros.py
@@ -13,7 +13,7 @@
 from contextlib import nullcontext
 from itertools import dropwhile
 
-from funcparserlib.parser import forward_decl, many, maybe, oneplus, some
+from funcparserlib.parser import finished, forward_decl, many, maybe, oneplus, some
 
 from hy.compiler import Result, asty, hy_eval, mkexpr
 from hy.errors import HyEvalError, HyInternalError, HyTypeError
@@ -698,7 +698,9 @@ def compile_if(compiler, expr, _, cond, body, orel_expr):
     ["for"], [brackets(loopers), many(notpexpr("else")) + maybe(dolike("else"))]
 )
 @pattern_macro(["lfor", "sfor", "gfor"], [loopers, FORM])
-@pattern_macro(["dfor"], [loopers, brackets(FORM, FORM)])
+@pattern_macro(["dfor"], [loopers, finished])
+# Here `finished` is a hack replacement for FORM + FORM:
+# https://github.com/vlasovskikh/funcparserlib/issues/75
 def compile_comprehension(compiler, expr, root, parts, final):
     node_class = {
         "for": asty.For,
@@ -715,6 +717,13 @@ def compile_comprehension(compiler, expr, root, parts, final):
         # Compile the parts.
         if is_for:
             parts = parts[0]
+        if node_class is asty.DictComp:
+            if not (parts and parts[-1].tag == "for"):
+                raise compiler._syntax_error(
+                    parts[-1] if parts else parts,
+                    "`dfor` must end with key and value forms",
+                )
+            final = parts.pop().value
         if not parts:
             return Result(
                 expr=asty.parse(


Test patch:
diff --git a/tests/native_tests/comprehensions.hy b/tests/native_tests/comprehensions.hy
--- a/tests/native_tests/comprehensions.hy
+++ b/tests/native_tests/comprehensions.hy
@@ -8,13 +8,13 @@
   ; Forms that get compiled to real comprehensions
   (assert (is (type (lfor x "abc" x)) list))
   (assert (is (type (sfor x "abc" x)) set))
-  (assert (is (type (dfor x "abc" [x x])) dict))
+  (assert (is (type (dfor x "abc" x x)) dict))
   (assert (is (type (gfor x "abc" x)) types.GeneratorType))
 
   ; Forms that get compiled to loops
   (assert (is (type (lfor x "abc" :do (setv y 1) x)) list))
   (assert (is (type (sfor x "abc" :do (setv y 1) x)) set))
-  (assert (is (type (dfor x "abc" :do (setv y 1) [x x])) dict))
+  (assert (is (type (dfor x "abc" :do (setv y 1) x x)) dict))
   (assert (is (type (gfor x "abc" :do (setv y 1) x)) types.GeneratorType)))
 
 
@@ -88,7 +88,7 @@
     (setv expr (+ (hy.models.Expression
                     [(hy.models.Symbol specialop)]) (cut expr 1 None)))
     (when (= specialop "dfor")
-      (setv expr (+ (cut expr -1) `([~(get expr -1) 1]))))
+      (+= expr `(1)))
     (when (= specialop "for")
       (setv expr `(do
         (setv out [])
@@ -110,7 +110,7 @@
   (assert (= (lfor 1) []))
   (assert (= (sfor 1) #{}))
   (assert (= (list (gfor 1)) []))
-  (assert (= (dfor [1 2]) {})))
+  (assert (= (dfor 1 2) {})))
 
 
 (defn test-raise-in-comp []
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -36,9 +36,9 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
 (setv emptyset #{})
 (setv emptydict {})
 
-(setv mylistcomp (lfor x (range 10) :if (% x 2) x))
-(setv mysetcomp (sfor x (range 5) :if (not (% x 2)) x))
-(setv mydictcomp (dfor k "abcde" :if (!= k "c") [k (.upper k)]))
+(setv mylistcomp (lfor  x (range 10)  :if (% x 2)  x))
+(setv mysetcomp (sfor  x (range 5)  :if (not (% x 2))  x))
+(setv mydictcomp (dfor  k "abcde"  :if (!= k "c")  k (.upper k)))
 
 (import itertools [cycle])
 (setv mygenexpr (gfor x (cycle [1 2 3]) :if (!= x 2) x))