Repository: xonsh/xonsh

Issue:
Xonsh allows for non-default arguments to follow default argument
## Expected Behavior
```
$ def foo(x=1, y):
      print(x, y)
  File "<stdin>", line 1
SyntaxError: non-default argument follows default argument
```
## Current Behavior
```
$ def foo(x=1, y):
      print(x, y)
$ foo(2)
2 1
```

## xonfig

<details>

```
+------------------+----------------------+
| xonsh            | 0.9.20               |
| Git SHA          | 17e56563             |
| Commit Date      | Aug 26 21:17:07 2020 |
| Python           | 3.8.5                |
| PLY              | 3.11                 |
| have readline    | True                 |
| prompt toolkit   | 3.0.4                |
| shell type       | prompt_toolkit       |
| pygments         | 2.6.1                |
| on posix         | True                 |
| on linux         | True                 |
| distro           | arch                 |
| on darwin        | False                |
| on windows       | False                |
| on cygwin        | False                |
| on msys2         | False                |
| is superuser     | False                |
| default encoding | utf-8                |
| xonsh encoding   | utf-8                |
| encoding errors  | surrogateescape      |
+------------------+----------------------+
```

</details>

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
Thanks for reporting this @laloch!

Resolving patch:
diff --git a/xonsh/parsers/base.py b/xonsh/parsers/base.py
--- a/xonsh/parsers/base.py
+++ b/xonsh/parsers/base.py
@@ -962,6 +962,8 @@ def _set_args_def(self, argmts, vals, kwargs=False):
             d = v["default"]
             if kwargs or (d is not None):
                 defs.append(d)
+            elif defs:
+                self._set_error("non-default argument follows default argument")
 
     def _set_regular_args(self, p0, p1, p2, p3, p4):
         if p2 is None and p3 is None:
diff --git a/xonsh/parsers/v38.py b/xonsh/parsers/v38.py
--- a/xonsh/parsers/v38.py
+++ b/xonsh/parsers/v38.py
@@ -57,6 +57,8 @@ def _set_posonly_args_def(self, argmts, vals):
             d = v["default"]
             if d is not None:
                 argmts.defaults.append(d)
+            elif argmts.defaults:
+                self._set_error("non-default argument follows default argument")
 
     def _set_posonly_args(self, p0, p1, p2, p3):
         if p2 is None and p3 is None:
@@ -246,6 +248,9 @@ def p_typedargslist_t12(self, p):
         if len(p) == 4:
             p0 = p[3]
             p0.posonlyargs = p[1].posonlyargs
+            # If posonlyargs contain default arguments, all following arguments must have defaults.
+            if p[1].defaults and (len(p[3].defaults) != len(p[3].args)):
+                self._set_error("non-default argument follows default argument")
         else:
             p0 = p[1]
         p[0] = p0
@@ -393,6 +398,9 @@ def p_varargslist_t12(self, p):
         if len(p) == 4:
             p0 = p[3]
             p0.posonlyargs = p[1].posonlyargs
+            # If posonlyargs contain default arguments, all following arguments must have defaults.
+            if p[1].defaults and (len(p[3].defaults) != len(p[3].args)):
+                self._set_error("non-default argument follows default argument")
         else:
             p0 = p[1]
         p[0] = p0


Test patch:
diff --git a/tests/test_parser.py b/tests/test_parser.py
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -3138,10 +3138,36 @@ def test_syntax_error_bar_kwonlyargs():
     with pytest.raises(SyntaxError):
         PARSER.parse("def spam(*):\n   pass\n", mode="exec")
 
+
+@skip_if_pre_3_8
 def test_syntax_error_bar_posonlyargs():
     with pytest.raises(SyntaxError):
         PARSER.parse("def spam(/):\n   pass\n", mode="exec")
 
+
+@skip_if_pre_3_8
 def test_syntax_error_bar_posonlyargs_no_comma():
     with pytest.raises(SyntaxError):
         PARSER.parse("def spam(x /, y):\n   pass\n", mode="exec")
+
+
+def test_syntax_error_nondefault_follows_default():
+    with pytest.raises(SyntaxError):
+        PARSER.parse("def spam(x=1, y):\n   pass\n", mode="exec")
+
+
+@skip_if_pre_3_8
+def test_syntax_error_posonly_nondefault_follows_default():
+    with pytest.raises(SyntaxError):
+        PARSER.parse("def spam(x, y=1, /, z):\n   pass\n", mode="exec")
+
+
+def test_syntax_error_lambda_nondefault_follows_default():
+    with pytest.raises(SyntaxError):
+        PARSER.parse("lambda x=1, y: x", mode="exec")
+
+
+@skip_if_pre_3_8
+def test_syntax_error_lambda_posonly_nondefault_follows_default():
+    with pytest.raises(SyntaxError):
+        PARSER.parse("lambda x, y=1, /, z: x", mode="exec")