Repository: hylang/hy

Issue:
Rethink fn arguments list
Hy currently has `&optional`, `&key`, `&kwargs`, `&rest`, and `&kwonly`.

Python does not *seem* this complicated. It only has `*` and `**` to remember. Clojure only has `&`, but it can use that with its dict destructure to [emulate kwargs](https://stackoverflow.com/questions/717963/clojure-keyword-arguments#3969693). It's an elegant solution, but I don't think it's good enough for Hy, which needs to interoperate closely with Python.

To be fair, Hy is not any worse than Common Lisp's `&allow-other-keys &environment &rest &aux &key &whole &body &optional`.

Is there any way we can simplify this? Any simplification here would be a very deep change, and bound to be controversial. But it's easier to build advanced macros on simpler, more consistent special forms. The time for breaking changes is sooner, not later.

I think the special form should follow Python as closely as possible. I don't know that I ever use `&key`, but that kind of thing probably belongs in a macro built on top of `fn`, not in the special form itself.

If we re-do `#*` and `#**` to create their own Hy models, as discussed in hylang/hyrule#52, then we could also use these in the arguments list. This would make Hy more consistent with Python, and with its own other features that use that syntax.

But that would only take care of `&rest` and `&kwargs`. Using `#*`, we can't distinguish `&rest` from `&kwonly` without commas. And how do we indicate default arguments without an infix `=`? It can't just be a list without `&optional`, or it looks like the destructuring argument bind we still have from Python2. It's also nice to be able to get a `None` default without typing it explicitly.

So how else could this work?
### Option 1
Maybe from this:
```Hy
(defn foo [a &optional ["b" 1] [c :c] d &rest args &kwonly x [y 2] &kwargs kwargs])
```
To this:
```Hy
(defn foo [a  :b 1  :c ':c  :d None  #* args  x  :y 2  #** kwargs])
```
And the equivalent Python.
```Python
def foo(a, b=1, c=HyKeyword("c"), d=None, *args, x, y=2, **kwargs): pass
```
Here we're using :keyword pairs to stand for `=`. This makes it look like a Hy function call, which is more consistent and easier to remember. Note that we have to quote keywords we want to pass as data, so they aren't interpreted as a kwarg name. But function calls work the same way. (And given the strange way our keywords work currently hylang/hy#1352, it would actually expand to `c='\ufdd0:c'`. We might want to change this.)

It seems a lot easier to work with, both for humans, and in macros. There aren't two ways to do the same thing, like `&optional` and `&key`. But we don't have our implicit `None` from `&optional` anymore. Oh well, neither did Python. We could implement it with a macro later, maybe. 

And how should `&kwonly` work? We can do `[#* _ x y]`, but that's not the same. It silently ignores extra arguments instead of raising an error. One possibility is a new Hy model named `#*,`, which you'd use instead of `#*` in those cases. That seems conceptually the same as `&kwonly` though. Is there a more elegant way to do this?

And finally, what about destructuring something with a default? (e.g. `def foo((a, b)=[1, 2]): pass`) Actually, Hy can't do this! hylang/hy#1410. And neither can Python3. No loss. Except, I'd like to add this feature in a `fn=:` macro hylang/hy#1410, hylang/hy#1328. What should it look like? I'm not sure. 

Maybe like `(defn=: foo [ #:(, a b) [1 2]])`? This (ab)uses the one of the (Hypothetical) slice notations I proposed from hylang/hy#541.



Discussion:
### Option 2

```Hy
(defn foo [a {b 1  c :c  d None} args & x {y 2  z 3} kwargs])
(defn akw [{} args & {} kwargs])
(defn only [& x {y 2}])
```
```Python
def foo(a, b=1, c=HyKeyword("c"), d=None, *args, x, y=2, z=3, **kwargs): pass
def akw(*args, *kwargs):pass
def only(*, x, y=2):pass
```
Here, optional args go in `{}` after the required positional args (like using `&key`). The `*args` part is assumed to follow that. So `(defn foo [{} args])` means `def foo(*args)`. 

The `&` now splits the positional args from the kwargs. So anything following `&` is kwonly. It similarly uses `{}` for anything optional. So `(defn foo [& x {y 2}])` means `def foo(*, x, y=2):pass`.

And finally the kwargs follows that dict. So `(defn foo [{& {} kwargs])` means `def foo(**kwargs):pass`, and `(defn foo [{} args & {} kwargs])` means `def foo(*args, **kwargs):pass`. Not as nice as Python, but shorter than what we have now: `(defn foo [&rest args &kwargs kwargs])`.

This gives macros more (and more consistent) structure to work with. I'm not sure how `fn=:` should look here either, because it's already using brackets a lot.

This seems to handle `&kwonly` more elegantly than **Option 1**, but I don't think it's pretty overall.

Resolving patch:
diff --git a/hy/compiler.py b/hy/compiler.py
--- a/hy/compiler.py
+++ b/hy/compiler.py
@@ -3,12 +3,13 @@
 # This file is part of Hy, which is free software licensed under the Expat
 # license. See the LICENSE.
 
+from itertools import dropwhile
 from hy.models import (HyObject, HyExpression, HyKeyword, HyInteger, HyComplex,
                        HyString, HyFComponent, HyFString, HyBytes, HySymbol,
                        HyFloat, HyList, HySet, HyDict, HySequence, wrap_value)
 from hy.model_patterns import (FORM, SYM, KEYWORD, STR, sym, brackets, whole,
                                notpexpr, dolike, pexpr, times, Tag, tag, unpack)
-from funcparserlib.parser import some, many, oneplus, maybe, NoParseError
+from funcparserlib.parser import some, many, oneplus, maybe, NoParseError, a
 from hy.errors import (HyCompileError, HyTypeError, HyLanguageError,
                        HySyntaxError, HyEvalError, HyInternalError)
 
@@ -329,9 +330,11 @@ def mkexpr(*items, **kwargs):
 def mklist(*items, **kwargs):
    return make_hy_model(HyList, items, kwargs.get('rest'))
 
+def pvalue(root, wanted):
+    return pexpr(sym(root) + wanted) >> (lambda x: x[0])
 
 # Parse an annotation setting.
-OPTIONAL_ANNOTATION = maybe(pexpr(sym("annotate*") + FORM) >> (lambda x: x[0]))
+OPTIONAL_ANNOTATION = maybe(pvalue("annotate*", FORM))
 
 
 def is_annotate_expression(model):
@@ -1485,21 +1488,21 @@ def make_not(operand):
 
         return ret
 
-    NASYM = some(lambda x: isinstance(x, HySymbol) and x not in (
-        "&optional", "&rest", "&kwonly", "&kwargs"))
+    NASYM = some(lambda x: isinstance(x, HySymbol) and x not in (HySymbol("/"), HySymbol("*")))
+    argument = OPTIONAL_ANNOTATION + (NASYM | brackets(NASYM, FORM))
+    varargs = lambda unpack_type, wanted: OPTIONAL_ANNOTATION + pvalue(unpack_type, wanted)
+    kwonly_delim = some(lambda x: isinstance(x, HySymbol) and x == HySymbol("*"))
     @special(["fn", "fn*", "fn/a"], [
         # The starred version is for internal use (particularly, in the
         # definition of `defn`). It ensures that a FunctionDef is
         # produced rather than a Lambda.
         OPTIONAL_ANNOTATION,
         brackets(
-            many(OPTIONAL_ANNOTATION + NASYM),
-            maybe(sym("&optional") + many(OPTIONAL_ANNOTATION
-                                            + (NASYM | brackets(SYM, FORM)))),
-            maybe(sym("&rest") + OPTIONAL_ANNOTATION + NASYM),
-            maybe(sym("&kwonly") + many(OPTIONAL_ANNOTATION
-                                        + (NASYM | brackets(SYM, FORM)))),
-            maybe(sym("&kwargs") + OPTIONAL_ANNOTATION + NASYM)),
+            maybe(many(argument) + sym("/")),
+            many(argument),
+            maybe(kwonly_delim | varargs("unpack-iterable", NASYM)),
+            many(argument),
+            maybe(varargs("unpack-mapping", NASYM))),
         many(FORM)])
     def compile_function_def(self, expr, root, returns, params, body):
         force_functiondef = root in ("fn*", "fn/a")
@@ -1519,27 +1522,48 @@ def compile_function_def(self, expr, root, returns, params, body):
             returns_result = self.compile(returns)
             ret += returns_result
 
-        mandatory, optional, rest, kwonly, kwargs = params
+        posonly, args, rest, kwonly, kwargs = params
 
-        optional = optional or []
-        kwonly = kwonly or []
-
-        mandatory_ast, _, ret = self._compile_arguments_set(mandatory, False, ret)
-        optional_ast, optional_defaults, ret = self._compile_arguments_set(optional, True, ret)
-        kwonly_ast, kwonly_defaults, ret = self._compile_arguments_set(kwonly, False, ret)
+        if not (posonly or posonly is None):
+            raise self._syntax_error(
+                params, "positional only delimiter '/' must have an argument"
+            )
+
+        posonly = posonly or []
+
+        is_positional_arg = lambda x: isinstance(x[1], HySymbol)
+        invalid_non_default = next(
+            (arg
+             for arg in dropwhile(is_positional_arg, posonly + args)
+             if is_positional_arg(arg)),
+            None
+        )
+        if invalid_non_default:
+            raise self._syntax_error(
+                invalid_non_default[1], "non-default argument follows default argument"
+            )
 
+        posonly_ast, posonly_defaults, ret = self._compile_arguments_set(posonly, ret)
+        args_ast, args_defaults, ret = self._compile_arguments_set(args, ret)
+        kwonly_ast, kwonly_defaults, ret = self._compile_arguments_set(kwonly, ret, True)
         rest_ast = kwargs_ast = None
 
-        if rest is not None:
-            [rest_ast], _, ret = self._compile_arguments_set([rest], False, ret)
-        if kwargs is not None:
-            [kwargs_ast], _, ret = self._compile_arguments_set([kwargs], False, ret)
+        if rest == HySymbol("*"): # rest is a positional only marker
+            if not kwonly:
+                raise self._syntax_error(rest, "named arguments must follow bare *")
+            rest_ast = None
+        elif rest: # rest is capturing varargs
+            [rest_ast], _, ret = self._compile_arguments_set([rest], ret)
+        if kwargs:
+            [kwargs_ast], _, ret = self._compile_arguments_set([kwargs], ret)
 
         args = ast.arguments(
-            args=mandatory_ast + optional_ast, defaults=optional_defaults,
+            args=args_ast,
+            defaults=[*posonly_defaults, *args_defaults],
             vararg=rest_ast,
-            posonlyargs=[],
-            kwonlyargs=kwonly_ast, kw_defaults=kwonly_defaults,
+            posonlyargs=posonly_ast,
+            kwonlyargs=kwonly_ast,
+            kw_defaults=kwonly_defaults,
             kwarg=kwargs_ast)
 
         body = self._compile_branch(body)
@@ -1563,7 +1587,7 @@ def compile_function_def(self, expr, root, returns, params, body):
         ret += Result(expr=ast_name, temp_variables=[ast_name, ret.stmts[-1]])
         return ret
 
-    def _compile_arguments_set(self, decls, implicit_default_none, ret):
+    def _compile_arguments_set(self, decls, ret, is_kwonly=False):
         args_ast = []
         args_defaults = []
 
@@ -1576,8 +1600,6 @@ def _compile_arguments_set(self, decls, implicit_default_none, ret):
                 sym, default = decl
             else:
                 sym = decl
-                if implicit_default_none:
-                    default = HySymbol('None').replace(sym)
 
             if ann is not None:
                 ret += self.compile(ann)
@@ -1588,7 +1610,10 @@ def _compile_arguments_set(self, decls, implicit_default_none, ret):
             if default is not None:
                 ret += self.compile(default)
                 args_defaults.append(ret.force_expr)
-            else:
+            # Kwonly args without defaults are considered required
+            elif not isinstance(decl, HyList) and is_kwonly:
+                args_defaults.append(None)
+            elif isinstance(decl, HyList):
                 # Note that the only time any None should ever appear here
                 # is in kwargs, since the order of those with defaults vs
                 # those without isn't significant in the same way as


Test patch:
diff --git a/tests/compilers/test_ast.py b/tests/compilers/test_ast.py
--- a/tests/compilers/test_ast.py
+++ b/tests/compilers/test_ast.py
@@ -418,8 +418,8 @@ def test_ast_non_decoratable():
 
 def test_ast_lambda_lists():
     """Ensure the compiler chokes on invalid lambda-lists"""
-    cant_compile('(fn [&optional [a b c]] a)')
-    cant_compile('(fn [&optional [1 2]] (list 1 2))')
+    cant_compile('(fn [[a b c]] a)')
+    cant_compile('(fn [[1 2]] (list 1 2))')
 
 
 def test_ast_print():
@@ -436,20 +436,20 @@ def test_ast_tuple():
 
 def test_lambda_list_keywords_rest():
     """ Ensure we can compile functions with lambda list keywords."""
-    can_compile("(fn [x &rest xs] (print xs))")
-    cant_compile("(fn [x &rest xs &rest ys] (print xs))")
-    can_compile("(fn [&optional a &rest xs] (print xs))")
+    can_compile("(fn [x #* xs] (print xs))")
+    cant_compile("(fn [x #* xs #* ys] (print xs))")
+    can_compile("(fn [[a None] #* xs] (print xs))")
 
 
 def test_lambda_list_keywords_kwargs():
-    """ Ensure we can compile functions with &kwargs."""
-    can_compile("(fn [x &kwargs kw] (list x kw))")
-    cant_compile("(fn [x &kwargs xs &kwargs ys] (list x xs ys))")
-    can_compile("(fn [&optional x &kwargs kw] (list x kw))")
+    """ Ensure we can compile functions with #** kwargs."""
+    can_compile("(fn [x #** kw] (list x kw))")
+    cant_compile("(fn [x #** xs #** ys] (list x xs ys))")
+    can_compile("(fn [[x None] #** kw] (list x kw))")
 
 
 def test_lambda_list_keywords_kwonly():
-    kwonly_demo = "(fn [&kwonly a [b 2]] (print 1) (print a b))"
+    kwonly_demo = "(fn [* a [b 2]] (print 1) (print a b))"
     code = can_compile(kwonly_demo)
     for i, kwonlyarg_name in enumerate(('a', 'b')):
         assert kwonlyarg_name == code.body[0].args.kwonlyargs[i].arg
@@ -459,9 +459,9 @@ def test_lambda_list_keywords_kwonly():
 
 def test_lambda_list_keywords_mixed():
     """ Ensure we can mix them up."""
-    can_compile("(fn [x &rest xs &kwargs kw] (list x xs kw))")
-    cant_compile("(fn [x &rest xs &fasfkey {bar \"baz\"}])")
-    can_compile("(fn [x &rest xs &kwonly kwoxs &kwargs kwxs]"
+    can_compile("(fn [x #* xs #** kw] (list x xs kw))")
+    cant_compile("(fn [x #* xs &fasfkey {bar \"baz\"}])")
+    can_compile("(fn [x #* xs kwoxs #** kwxs]"
                 "  (list x xs kwxs kwoxs))")
 
 
diff --git a/tests/native_tests/contrib/destructure.hy b/tests/native_tests/contrib/destructure.hy
--- a/tests/native_tests/contrib/destructure.hy
+++ b/tests/native_tests/contrib/destructure.hy
@@ -263,4 +263,3 @@
           "options" "xyzq"}]
     (assert (= [a b c d style colour options]
                [1 2 3 4 "pretty" "purple" "xyzq"]))))
-
diff --git a/tests/native_tests/contrib/walk.hy b/tests/native_tests/contrib/walk.hy
--- a/tests/native_tests/contrib/walk.hy
+++ b/tests/native_tests/contrib/walk.hy
@@ -89,26 +89,26 @@
 
 (defn test-smacrolet []
   (setv form '(do
-                (setv foo (fn [a &optional [b 1]] (* b (inc a))))
+                (setv foo (fn [a [b 1]] (* b (inc a))))
                 (* b (foo 7)))
         form1 (macroexpand
                 '(smacrolet [b c]
-                   (setv foo (fn [a &optional [b 1]] (* b (inc a))))
+                   (setv foo (fn [a [b 1]] (* b (inc a))))
                    (* b (foo 7))))
         form2 (macroexpand
                 '(smacrolet [a c]
-                   (setv foo (fn [a &optional [b 1]] (* b (inc a))))
+                   (setv foo (fn [a [b 1]] (* b (inc a))))
                    (* b (foo 7))))
         form3 (macroexpand
                 '(smacrolet [foo bar]
-                   (setv foo (fn [a &optional [b 1]] (* b (inc a))))
+                   (setv foo (fn [a [b 1]] (* b (inc a))))
                    (* b (foo 7)))))
   (assert (= form1 '(do
-                      (setv foo (fn [a &optional [b 1]] (* b (inc a))))
+                      (setv foo (fn [a [b 1]] (* b (inc a))))
                       (* c (foo 7)))))
   (assert (= form2 form))
   (assert (= form3 '(do
-                      (setv bar (fn [a &optional [b 1]] (* b (inc a))))
+                      (setv bar (fn [a [b 1]] (* b (inc a))))
                       (* b (bar 7))))))
 
 (defn test-let-basic []
@@ -334,7 +334,7 @@
         a 88
         c 64
         &rest 12]
-    (defn foo [a b &rest xs]
+    (defn foo [a b #* xs]
       (-= a 1)
       (setv xs (list xs))
       (.append xs 42)
@@ -350,7 +350,7 @@
 (defn test-let-kwargs []
   (let [kws 6
         &kwargs 13]
-    (defn foo [&kwargs kws]
+    (defn foo [#** kws]
       (, &kwargs kws))
     (assert (= kws 6))
     (assert (= (foo :a 1)
@@ -360,7 +360,7 @@
   (let [a 1
         b 6
         d 2]
-    (defn foo [&optional [a a] b [c d]]
+    (defn foo [[a a] [b None] [c d]]
       (, a b c))
     (assert (= (foo)
                (, 1 None 2)))
@@ -369,7 +369,7 @@
 
 (defn test-let-closure []
   (let [count 0]
-    (defn +count [&optional [x 1]]
+    (defn +count [[x 1]]
       (+= count x)
       count))
   ;; let bindings can still exist outside of a let body
diff --git a/tests/native_tests/extra/anaphoric.hy b/tests/native_tests/extra/anaphoric.hy
--- a/tests/native_tests/extra/anaphoric.hy
+++ b/tests/native_tests/extra/anaphoric.hy
@@ -173,7 +173,7 @@
   (assert (= (#%(identity (, %5 %4 %3 %2 %1)) 1 2 3 4 5) (, 5 4 3 2 1)))
   (assert (= (#%(identity (, %1 %2 %3 %4 %5)) 1 2 3 4 5) (, 1 2 3 4 5)))
   (assert (= (#%(identity (, %1 %5 %2 %3 %4)) 1 2 3 4 5) (, 1 5 2 3 4)))
-  ;; test &rest
+  ;; test #*
   (assert (= (#%(sum %*) 1 2 3) 6))
   (assert (= (#%(identity (, %1 %*)) 10 1 2 3) (, 10 (, 1 2 3))))
   ;; no parameters
@@ -190,7 +190,7 @@
   (assert (= (#%(%1 2 4) +) 6))
   (assert (= (#%(%1 2 4) -) -2))
   (assert (= (#%(%1 2 4) /) 0.5))
-  ;; test &rest &kwargs
+  ;; test #* #**
   (assert (= (#%(, %* %**) 1 2 :a 'b)
              (, (, 1 2)
                 (dict :a 'b))))
diff --git a/tests/native_tests/language.hy b/tests/native_tests/language.hy
--- a/tests/native_tests/language.hy
+++ b/tests/native_tests/language.hy
@@ -401,7 +401,7 @@
   "NATIVE: test fn operator"
   (setv square (fn [x] (* x x)))
   (assert (= 4 (square 2)))
-  (setv lambda_list (fn [test &rest args] (, test args)))
+  (setv lambda_list (fn [test #* args] (, test args)))
   (assert (= (, 1 (, 2 3)) (lambda_list 1 2 3))))
 
 
@@ -416,7 +416,8 @@
   ; Python 3-only forms of unpacking are in py3_only_tests.hy
   (setv l [1 2 3])
   (setv d {"a" "x" "b" "y"})
-  (defn fun [&optional x1 x2 x3 x4 a b c] [x1 x2 x3 x4 a b c])
+  (defn fun [[x1 None] [x2 None] [x3 None] [x4 None] [a None] [b None] [c None]]
+    [x1 x2 x3 x4 a b c])
   (assert (= (fun 5 #* l) [5 1 2 3 None None None]))
   (assert (= (+ #* l) 6))
   (assert (= (fun 5 #** d) [5 None None None "x" "y" None]))
@@ -439,7 +440,7 @@
 
   (defclass X [object] [])
   (defclass M [object]
-    (defn meth [self &rest args &kwargs kwargs]
+    (defn meth [self #* args #** kwargs]
       (.join " " (+ (, "meth") args
         (tuple (map (fn [k] (get kwargs k)) (sorted (.keys kwargs))))))))
 
@@ -890,8 +891,8 @@
 
 (defn test-defn-annotations []
 
-  (defn f [^int p1 p2 ^str p3 &optional ^str o1 ^int [o2 0]
-           &rest ^str rest &kwonly ^str k1 ^int [k2 0] &kwargs ^bool kwargs])
+  (defn f [^int p1 p2 ^str p3 ^str [o1 None] ^int [o2 0]
+           ^str #* rest ^str k1 ^int [k2 0] ^bool #** kwargs])
 
   (for [[k v] (.items (dict
       :p1 int  :p3 str  :o1 str  :o2 int
@@ -1348,13 +1349,13 @@ cee\"} dee" "ey bee\ncee dee"))
 
 (defn test-lambda-keyword-lists []
   "NATIVE: test lambda keyword lists"
-  (defn foo [x &rest xs &kwargs kw] [x xs kw])
+  (defn foo [x #* xs #** kw] [x xs kw])
   (assert (= (foo 10 20 30) [10 (, 20 30) {}])))
 
 
 (defn test-optional-arguments []
-  "NATIVE: test &optional function arguments"
-  (defn foo [a b &optional c [d 42]] [a b c d])
+  "NATIVE: test optional function arguments"
+  (defn foo [a b [c None] [d 42]] [a b c d])
   (assert (= (foo 1 2) [1 2 None 42]))
   (assert (= (foo 1 2 3) [1 2 3 42]))
   (assert (= (foo 1 2 3 4) [1 2 3 4])))
@@ -1612,7 +1613,7 @@ macros()
 (defn test-only-parse-lambda-list-in-defn []
   "NATIVE: test lambda lists are only parsed in defn"
   (with [(pytest.raises NameError)]
-    (setv x [&rest spam]  y 1)))
+    (setv x [#* spam]  y 1)))
 
 (defn test-read []
   "NATIVE: test that read takes something for stdin and reads"
@@ -1668,7 +1669,7 @@ macros()
   (assert (= (kwtest :key (kwtest :key2 "value")) {"key" {"key2" "value"}}))
   (assert (= ((get (kwtest :key (fn [x] (* x 2))) "key") 3) 6)))
 
-(defmacro identify-keywords [&rest elts]
+(defmacro identify-keywords [#* elts]
   `(list
     (map
      (fn [x] (if (is-keyword x) "keyword" "other"))
@@ -1730,11 +1731,11 @@ macros()
 (defn test-kwonly []
   "NATIVE: test keyword-only arguments"
   ;; keyword-only with default works
-  (defn kwonly-foo-default-false [&kwonly [foo False]] foo)
+  (defn kwonly-foo-default-false [* [foo False]] foo)
   (assert (= (kwonly-foo-default-false) False))
   (assert (= (kwonly-foo-default-false :foo True) True))
   ;; keyword-only without default ...
-  (defn kwonly-foo-no-default [&kwonly foo] foo)
+  (defn kwonly-foo-no-default [* foo] foo)
   (with [e (pytest.raises TypeError)]
     (kwonly-foo-no-default))
   (assert (in "missing 1 required keyword-only argument: 'foo'"
@@ -1742,7 +1743,7 @@ macros()
   ;; works
   (assert (= (kwonly-foo-no-default :foo "quux") "quux"))
   ;; keyword-only with other arg types works
-  (defn function-of-various-args [a b &rest args &kwonly foo &kwargs kwargs]
+  (defn function-of-various-args [a b #* args foo #** kwargs]
     (, a b args foo kwargs))
   (assert (= (function-of-various-args 1 2 3 4 :foo 5 :bar 6 :quux 7)
              (, 1 2 (, 3 4)  5 {"bar" 6 "quux" 7}))))
@@ -1789,7 +1790,7 @@ macros()
   (let [a 1
         b 6
         d 2]
-       (defn foo [&kwonly [a a] b [c d]]
+       (defn foo [* [a a] b [c d]]
          (, a b c))
        (assert (= (foo :b "b")
                   (, 1 "b" 2)))
@@ -1832,7 +1833,7 @@ macros()
   (assert (= ["a" #*l "b" #*p #*l] ["a" 1 2 3 "b" 4 5 1 2 3]))
   (assert (= (, "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
   (assert (= #{"a" #*l "b" #*p #*l} #{"a" "b" 1 2 3 4 5}))
-  (defn f [&rest args] args)
+  (defn f [#* args] args)
   (assert (= (f "a" #*l "b" #*p #*l) (, "a" 1 2 3 "b" 4 5 1 2 3)))
   (assert (= (+ #*l #*p) 15))
   (assert (= (and #*l) 3)))
@@ -1842,7 +1843,8 @@ macros()
   (setv d1 {"a" 1 "b" 2})
   (setv d2 {"c" 3 "d" 4})
   (assert (= {1 "x" #**d1 #**d2 2 "y"} {"a" 1 "b" 2 "c" 3 "d" 4 1 "x" 2 "y"}))
-  (defn fun [&optional a b c d e f] [a b c d e f])
+  (defn fun [[a None] [b None] [c None] [d None] [e None] [f None]]
+    [a b c d e f])
   (assert (= (fun #**d1 :e "eee" #**d2) [1 2 3 4 "eee" None])))
 
 
@@ -1949,9 +1951,8 @@ macros()
 
 (defn test-pep-487 []
   (defclass QuestBase []
-    (defn --init-subclass-- [cls swallow &kwargs kwargs]
+    (defn --init-subclass-- [cls swallow #** kwargs]
       (setv cls.swallow swallow)))
 
   (defclass Quest [QuestBase :swallow "african"])
   (assert (= (. (Quest) swallow) "african")))
-
diff --git a/tests/native_tests/mangling.hy b/tests/native_tests/mangling.hy
--- a/tests/native_tests/mangling.hy
+++ b/tests/native_tests/mangling.hy
@@ -126,7 +126,7 @@
   (assert (= (f :foo? 3 :☘ 4 :a 1 :a-b 2) [1 2 3 4]))
   (assert (= (f :is_foo 3 :hyx_XshamrockX 4 :a 1 :a_b 2) [1 2 3 4]))
 
-  (defn g [&kwargs x]
+  (defn g [#** x]
     x)
   (assert (= (g :foo? 3 :☘ 4 :a 1 :a-b 2)
              {"a" 1  "a_b" 2  "is_foo" 3  "hyx_XshamrockX" 4}))
diff --git a/tests/native_tests/model_patterns.hy b/tests/native_tests/model_patterns.hy
--- a/tests/native_tests/model_patterns.hy
+++ b/tests/native_tests/model_patterns.hy
@@ -2,7 +2,7 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(defmacro do-until [&rest args]
+(defmacro do-until [#* args]
   (import
     [hy.model-patterns [whole FORM notpexpr dolike]]
     [funcparserlib.parser [many]])
@@ -26,7 +26,7 @@
     (until (+= n 1) (>= n 3)))
   (assert (= s "xxxx")))
 
-(defmacro loop [&rest args]
+(defmacro loop [#* args]
   (import
     [hy.model-patterns [whole FORM sym SYM]]
     [funcparserlib.parser [many]])
diff --git a/tests/native_tests/native_macros.hy b/tests/native_tests/native_macros.hy
--- a/tests/native_tests/native_macros.hy
+++ b/tests/native_tests/native_macros.hy
@@ -5,7 +5,7 @@
 (import pytest
         [hy.errors [HyTypeError HyMacroExpansionError]])
 
-(defmacro rev [&rest body]
+(defmacro rev [#* body]
   "Execute the `body` statements in reverse"
   (quasiquote (do (unquote-splice (list (reversed body))))))
 
@@ -40,7 +40,7 @@
   (defmacro a-list [] [1 2])
   (assert (= (a-list) [1 2]))
 
-  (defmacro a-tuple [&rest b] b)
+  (defmacro a-tuple [#* b] b)
   (assert (= (a-tuple 1 2) [1 2]))
 
   (defmacro a-dict [] {1 2})
@@ -62,17 +62,17 @@
   (foo x y))
 
 (defn test-macro-kw []
-  "NATIVE: test that an error is raised when &kwonly or &kwargs is used in a macro"
+  "NATIVE: test that an error is raised when * or #** is used in a macro"
   (try
-    (eval '(defmacro f [&kwonly a b]))
+    (eval '(defmacro f [* a b]))
     (except [e HyTypeError]
-      (assert (= e.msg "macros cannot use &kwonly")))
+      (assert (= e.msg "macros cannot use '*', or '#**'")))
     (else (assert False)))
 
   (try
-    (eval '(defmacro f [&kwargs kw]))
+    (eval '(defmacro f [#** kw]))
     (except [e HyTypeError]
-      (assert (= e.msg "macros cannot use &kwargs")))
+      (assert (= e.msg "macros cannot use '*', or '#**'")))
     (else (assert False))))
 
 (defn test-macro-bad-name []
@@ -89,9 +89,9 @@
 
 (defn test-optional-and-unpacking-in-macro []
   ; https://github.com/hylang/hy/issues/1154
-  (defn f [&rest args]
+  (defn f [#* args]
     (+ "f:" (repr args)))
-  (defmacro mac [&optional x]
+  (defmacro mac [[x None]]
    `(f #* [~x]))
   (assert (= (mac) "f:(None,)")))
 
@@ -266,8 +266,8 @@
   (setv foo 40)
   (foo! (+= foo 1))
   (assert (= 41 foo))
-  ;; test &optional args
-  (defmacro! bar! [o!a &optional [o!b 1]] `(do ~g!a ~g!a ~g!b ~g!b))
+  ;; test optional args
+  (defmacro! bar! [o!a [o!b 1]] `(do ~g!a ~g!a ~g!b ~g!b))
   ;; test that o!s are evaluated once only
   (bar! (+= foo 1) (+= foo 1))
   (assert (= 43 foo))
@@ -334,7 +334,7 @@
     x)
 
   (try
-    (defmain [&rest args]
+    (defmain [#* args]
       (main 42))
     (assert False)
     (except [e SystemExit]
diff --git a/tests/native_tests/operators.hy b/tests/native_tests/operators.hy
--- a/tests/native_tests/operators.hy
+++ b/tests/native_tests/operators.hy
@@ -2,7 +2,7 @@
 ;; This file is part of Hy, which is free software licensed under the Expat
 ;; license. See the LICENSE.
 
-(defmacro op-and-shadow-test [op &rest body]
+(defmacro op-and-shadow-test [op #* body]
   ; Creates two tests with the given `body`, one where all occurrences
   ; of the symbol `f` are syntactically replaced with `op` (a test of
   ; the real operator), and one where the body is preceded by an
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
@@ -88,7 +88,7 @@
   (assert (= q qq)))
 
 
-(defmacro doodle [&rest body]
+(defmacro doodle [#* body]
   `(do ~@body))
 
 (defn test-unquote-splice []
diff --git a/tests/native_tests/tag_macros.hy b/tests/native_tests/tag_macros.hy
--- a/tests/native_tests/tag_macros.hy
+++ b/tests/native_tests/tag_macros.hy
@@ -88,12 +88,12 @@
   (defn increment-arguments [func]
     "Increments each argument passed to the decorated function."
     ((wraps func)
-       (fn [&rest args &kwargs kwargs]
+       (fn [#* args #** kwargs]
          (func #* (map inc args)
                #** (dfor [k v] (.items kwargs) [k (inc v)])))))
 
   #@(increment-arguments
-     (defn foo [&rest args &kwargs kwargs]
+     (defn foo [#* args #** kwargs]
        "Bar."
        (, args kwargs)))
 
@@ -108,7 +108,7 @@
   ;; We can use the #@ tag macro to apply more than one decorator
   #@(increment-arguments
      increment-arguments
-     (defn double-foo [&rest args &kwargs kwargs]
+     (defn double-foo [#* args #** kwargs]
        "Bar."
        (, args kwargs)))
 
diff --git a/tests/resources/bin/main.hy b/tests/resources/bin/main.hy
--- a/tests/resources/bin/main.hy
+++ b/tests/resources/bin/main.hy
@@ -1,4 +1,4 @@
-(defmain [&rest args]
+(defmain [#* args]
   (print (+ "<" (.join "|" (cut args 1)) ">"))
   (print "Hello World")
   (if (in "exit1" args)
diff --git a/tests/resources/macros.hy b/tests/resources/macros.hy
--- a/tests/resources/macros.hy
+++ b/tests/resources/macros.hy
@@ -1,12 +1,12 @@
 (setv module-name-var "tests.resources.macros")
 
 (defmacro thread-set-ab []
-  (defn f [&rest args] (.join "" (+ (, "a") args)))
+  (defn f [#* args] (.join "" (+ (, "a") args)))
   (setv variable (HySymbol (-> "b" (f))))
   `(setv ~variable 2))
 
 (defmacro threadtail-set-cd []
-  (defn f [&rest args] (.join "" (+ (, "c") args)))
+  (defn f [#* args] (.join "" (+ (, "c") args)))
   (setv variable (HySymbol (->> "d" (f))))
   `(setv ~variable 5))
 
@@ -19,4 +19,4 @@
 defined, then the expansion namespace/module"
   `(.format (+ "This macro was created in {}, expanded in {} "
                "and passed the value {}.")
-            ~module-name-var module-name-var ~x))
\ No newline at end of file
+            ~module-name-var module-name-var ~x))
diff --git a/tests/resources/pydemo.hy b/tests/resources/pydemo.hy
--- a/tests/resources/pydemo.hy
+++ b/tests/resources/pydemo.hy
@@ -116,7 +116,7 @@ Call me Ishmael. Some years ago—never mind how long precisely—having little
   (else
     (setv ran-try-else True)))
 
-(defn fun [a b &optional [c 9] [d 10] &rest args &kwargs kwargs]
+(defn fun [a b [c 9] [d 10] #* args #** kwargs]
   "function docstring"
   [a b c d args (sorted (.items kwargs))])
 (setv funcall1 (fun 1 2 3 4 "a" "b" "c" :k1 "v1" :k2 "v2"))
diff --git a/tests/resources/tlib.hy b/tests/resources/tlib.hy
--- a/tests/resources/tlib.hy
+++ b/tests/resources/tlib.hy
@@ -1,9 +1,9 @@
 (setv SECRET_MESSAGE "Hello World")
 
-(defmacro qplah [&rest tree]
+(defmacro qplah [#* tree]
   `[8 ~@tree])
 
-(defmacro parald [&rest tree]
+(defmacro parald [#* tree]
   `[9 ~@tree])
 
 (defmacro ✈ [arg]