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:
laloch: The tests are failing, because our parser is unable to raise an error from production rules. Until the error handler is fixed this at least prevents the parser from generating invalid code.
scopatz: hmm that kinda sucks.  Thanks for working on this though @laloch 
laloch: See #3724.

Patch:
diff --git a/news/non-default-follows-default.rst b/news/non-default-follows-default.rst
new file mode 100644
index 00000000..09625de1
--- /dev/null
+++ b/news/non-default-follows-default.rst
@@ -0,0 +1,23 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* Non-default parameters can not follow defaults anymore.
+
+**Security:**
+
+* <news item>
diff --git a/xonsh/parsers/base.py b/xonsh/parsers/base.py
index cb333853..419550fc 100644
--- a/xonsh/parsers/base.py
+++ b/xonsh/parsers/base.py
@@ -962,6 +962,8 @@ class BaseParser(object):
             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
index 21b3e090..1282df68 100644
--- a/xonsh/parsers/v38.py
+++ b/xonsh/parsers/v38.py
@@ -57,6 +57,8 @@ class Parser(ThreeSixParser):
             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 @@ class Parser(ThreeSixParser):
         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 @@ class Parser(ThreeSixParser):
         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
index 35b4691d..1ec14537 100644
--- 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")