Repository: hylang/hy

Issue:
`~@ #* …` produces a weird result instead of an error 
```lisp
(defmacro condp-1 [op obj #*body]
  `(cond
     ~@(lfor [val res] (by2s body)
         `((~op ~obj ~val) ~res) )))

(defmacro condp-2 [op obj #*body]
  `(cond
     ~@(unpack-iterable (lfor [val res] (by2s body)
              `((~op ~obj ~val) ~res) ))))

(.macroexpand-1 hy (.read hy "(condp-1 < 10 100 12 1000 123 10000 0 120000 1)"))
(.macroexpand-1 hy (.read hy "(condp-2 < 10 100 12 1000 123 10000 0 120000 1)"))
```

The macroexpansion I want to get is 
```lisp
(cond (< 10 100) 12 (< 10 1000) 123 (< 10 10000) 0 (< 10 120000) 1)
```

instead I'm getting, from condp-1 and condp-2 respectively:

```lisp
;; condp-1
'(cond ((< 10 100) 12) ((< 10 1000) 123) ((< 10 10000) 0) ((< 10 120000) 1))

;; condp-2
'(cond (< 10 100) 12)
```

The first has inner expressions nested a level too deep. The second is missing elements.

Am I missing something about how unpack-iterable works? From testing its behavior, this is the only scenario where I found it to not return all members


Discussion:
I get `NameError: name 'by2s' is not defined` when I try this.
@Kodiologist sorry about that, I left out the import from hyrule

```lisp
(import hyrule.collections [by2s])
```
Oh yeah, you should know that that's a private function that appears in the Hyrule manual due to a bug (https://github.com/hylang/sphinxcontrib-hydomain/issues/18). Use `toolz.partition` instead, or just copy the definition of `by2s` into your own code.

Well, I see no Hy bug in how `condp-1` works. The `lfor` returns two-element expressions, so that's what you get in the result. A simpler example of the same thing is that `` `[~@(lfor  x (range 3)  [x x])]`` returns `'[[0 0] [1 1] [2 2]]`.

`condp-2` seems pathological since you're doing `~@ #* …`, but `~@` always requires exactly one argument, so why unpack? Hy doesn't expect a `#*` here and so it does something nonsensical. We could get a better error message by changing the code that checks the arguments of `~` and `~@` to reject `#*`, but I suspect there are a lot of pathological places you could use `#*`, and it's probably not worth checking explicitly for all of them. I'm not sure what the right approach is here.

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
@@ -222,6 +222,8 @@ def render_quoted_form(compiler, form, level):
         for x in form:
             f_contents, splice = render_quoted_form(compiler, x, level)
             if splice:
+                if is_unpack("iterable", f_contents):
+                    raise compiler._syntax_error(f_contents, "`unpack-iterable` is not allowed here")
                 f_contents = Expression(
                     [
                         Symbol("unpack-iterable"),
diff --git a/hy/models.py b/hy/models.py
--- a/hy/models.py
+++ b/hy/models.py
@@ -203,7 +203,7 @@ class Symbol(Object, str):
     """
     Represents a symbol.
 
-    Symbol objects behave like strings under operations like :hy:func:`get`,
+    Symbol objects behave like strings under operations like :hy:func:`get <hy.pyops.get>`,
     :func:`len`, and :class:`bool`; in particular, ``(bool (hy.models.Symbol "False"))`` is true. Use :hy:func:`hy.eval` to evaluate a symbol.
     """
 
@@ -283,8 +283,8 @@ def __call__(self, data, default=_sentinel):
         :class:`hy.models.Keyword` objects).
 
         The optional second parameter is a default value; if provided, any
-        :class:`KeyError` from :hy:func:`get` will be caught, and the default returned
-        instead."""
+        :class:`KeyError` from :hy:func:`get <hy.pyops.get>` will be caught,
+        and the default returned instead."""
 
         from hy.reader import mangle
 


Test patch:
diff --git a/tests/native_tests/hy_misc.hy b/tests/native_tests/hy_misc.hy
--- a/tests/native_tests/hy_misc.hy
+++ b/tests/native_tests/hy_misc.hy
@@ -8,12 +8,13 @@
 (defn test-gensym []
   (setv s1 (hy.gensym))
   (assert (isinstance s1 hy.models.Symbol))
-  (assert (= 0 (.find s1 "_G\uffff")))
+  (assert (.startswith s1 "_hy_gensym__"))
   (setv s2 (hy.gensym "xx"))
   (setv s3 (hy.gensym "xx"))
-  (assert (= 0 (.find s2 "_xx\uffff")))
-  (assert (not (= s2 s3)))
-  (assert (not (= (str s2) (str s3)))))
+  (assert (.startswith s2 "_hy_gensym_xx_"))
+  (assert (!= s2 s3))
+  (assert (!= (str s2) (str s3)))
+  (assert (.startswith (hy.gensym "•ab") "_hy_gensym_XbulletXab_")))
 
 
 (defmacro mac [x expr]
diff --git a/tests/native_tests/macros.hy b/tests/native_tests/macros.hy
--- a/tests/native_tests/macros.hy
+++ b/tests/native_tests/macros.hy
@@ -120,31 +120,18 @@
 (assert initialized)
 (assert (test-initialized))
 
+
+(defmacro gensym-example []
+  `(setv ~(hy.gensym) 1))
+
 (defn test-gensym-in-macros []
-  (import ast)
-  (import hy.compiler [hy-compile])
-  (import hy.reader [read-many])
-  (setv macro1 "(defmacro nif [expr pos zero neg]
-      (setv g (hy.gensym))
-      `(do
-         (setv ~g ~expr)
-         (cond (> ~g 0) ~pos
-               (= ~g 0) ~zero
-               (< ~g 0) ~neg)))
-
-    (print (nif (inc -1) 1 0 -1))
-    ")
-  ;; expand the macro twice, should use a different
-  ;; gensym each time
-  (setv _ast1 (hy-compile (read-many macro1) __name__))
-  (setv _ast2 (hy-compile (read-many macro1) __name__))
-  (setv s1 (ast.unparse _ast1))
-  (setv s2 (ast.unparse _ast2))
-  ;; and make sure there is something new that starts with _G\uffff
-  (assert (in (hy.mangle "_G\uffff") s1))
-  (assert (in (hy.mangle "_G\uffff") s2))
-  ;; but make sure the two don't match each other
-  (assert (not (= s1 s2))))
+  ; Call `gensym-example` twice, getting a distinct gensym each time.
+  (defclass C []
+    (gensym-example)
+    (gensym-example))
+  (assert (=
+    (len (sfor  a (dir C)  :if (not (.startswith a "__"))  a))
+    2)))
 
 
 (defn test-macro-errors []
diff --git a/tests/native_tests/quote.hy b/tests/native_tests/quote.hy
--- a/tests/native_tests/quote.hy
+++ b/tests/native_tests/quote.hy
@@ -1,3 +1,7 @@
+(import
+  pytest)
+
+
 (defn test-quote []
   (setv q (quote (a b c)))
   (assert (= (len q) 3))
@@ -83,3 +87,9 @@
       [1 2 3]
       [4 5 6])
     [4 5 6])))
+
+
+(defn test-unquote-splice-unpack []
+  ; https://github.com/hylang/hy/issues/2336
+  (with [(pytest.raises hy.errors.HySyntaxError)]
+    (hy.eval '`[~@ #* [[1]]])))