Repository: we-like-parsers/pegen (language: python)

Issue:
python.gram: reflect changes in cpython
See https://github.com/python/cpython/commit/e5f13ce5b48b551c09fdd0faeafa6ecf860de51c

I will try to add validation of the line and offsets values in the tests but maybe not before next week.


Discussion / hints:
@MatthieuDartiailh You need to rebase this branch on top of main now that #60 has landed.
Will do yes and I need to address the question of the tests too. 
All tests pass locally on 3.10.0, I will try to fix the three broken tests when I get a chance but I would already appreciate a review. 
The three failing are related to this rule
```
| !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
        _PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
        RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
```
but tokenize.py does not trac the level so emulating this will be tricky.
> The three failing are related to this rule
> ```
> | !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
>         _PyPegen_check_legacy_stmt(p, a) ? NULL : p->tokens[p->mark-1]->level == 0 ? NULL :
>         RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Perhaps you forgot a comma?") }
> ```
> but tokenize.py does not trac the level so emulating this will be tricky.

Yeah, we can ignore it for the time being. We could try to reformulate it using existing information or remove the restriction for now and let it be more noisy.
Once this go in I will work on adding the latest improvement to syntax errors:
bpo45716
bpo45764
bpo45727
bpo45450
bpo46836

Fix patch:
diff --git a/data/python.gram b/data/python.gram
index fb2cda5..76190d9 100644
--- a/data/python.gram
+++ b/data/python.gram
@@ -698,7 +698,7 @@ function_def[Union[ast.FunctionDef, ast.AsyncFunctionDef]]:
 
 function_def_raw[Union[ast.FunctionDef, ast.AsyncFunctionDef]]:
     | invalid_def_raw
-    | 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block {
+    | 'def' n=NAME &&'(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block {
         ast.FunctionDef(
             name=n.string,
             args=params or self.make_arguments(None, [], None, [], None),
@@ -708,7 +708,7 @@ function_def_raw[Union[ast.FunctionDef, ast.AsyncFunctionDef]]:
             LOCATIONS,
         )
      }
-    | 'async' 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block {
+    | 'async' 'def' n=NAME &&'(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block {
        self.check_version(
             (3, 5),
             "Async functions are",
@@ -763,12 +763,14 @@ slash_with_default[List[Tuple[ast.arg, Any]]]:
     | a=param_no_default* b=param_with_default+ '/' &')' { ([(p, None) for p in a] if a else []) + b }
 
 star_etc[Tuple[Optional[ast.arg], List[Tuple[ast.arg, Any]], Optional[ast.arg]]]:
+    | invalid_star_etc
     | '*' a=param_no_default b=param_maybe_default* c=[kwds] { (a, b, c) }
     | '*' ',' b=param_maybe_default+ c=[kwds] { (None, b, c) }
     | a=kwds { (None, [], a) }
-    | invalid_star_etc
 
-kwds: '**' a=param_no_default { a }
+kwds[ast.arg]:
+    | invalid_kwds
+    | '**' a=param_no_default { a }
 
 # One parameter.  This *includes* a following comma and type comment.
 #
@@ -794,7 +796,7 @@ param_maybe_default[Tuple[ast.arg, Any]]:
     | a=param c=default? tc=TYPE_COMMENT? &')' { (self.set_arg_type_comment(a, tc), c) }
 param: a=NAME b=annotation? { ast.arg(arg=a.string, annotation=b, LOCATIONS) }
 annotation: ':' a=expression { a }
-default: '=' a=expression { a }
+default: '=' a=expression { a } | invalid_default
 
 # If statement
 # ------------
@@ -1133,6 +1135,7 @@ expressions:
 
 expression (memo):
     | invalid_expression
+    | invalid_legacy_expression
     | a=disjunction 'if' b=disjunction 'else' c=expression {
         ast.IfExp(body=a, test=b, orelse=c, LOCATIONS)
      }
@@ -1407,14 +1410,16 @@ lambda_slash_with_default[List[Tuple[ast.arg, Any]]]:
     | a=lambda_param_no_default* b=lambda_param_with_default+ '/' &':' { ([(p, None) for p in a] if a else []) + b }
 
 lambda_star_etc[Tuple[Optional[ast.arg], List[Tuple[ast.arg, Any]], Optional[ast.arg]]]:
+    | invalid_lambda_star_etc
     | '*' a=lambda_param_no_default b=lambda_param_maybe_default* c=[lambda_kwds] {
        (a, b, c) }
     | '*' ',' b=lambda_param_maybe_default+ c=[lambda_kwds] {
         (None, b, c) }
     | a=lambda_kwds { (None, [], a) }
-    | invalid_lambda_star_etc
 
-lambda_kwds[ast.arg]: '**' a=lambda_param_no_default { a }
+lambda_kwds[ast.arg]:
+    | invalid_lambda_kwds
+    | '**' a=lambda_param_no_default { a }
 
 lambda_param_no_default[ast.arg]:
     | a=lambda_param ',' { a }
@@ -1681,6 +1686,9 @@ invalid_arguments[NoReturn]:
         )
      }
 invalid_kwarg[NoReturn]:
+    | a=('True'|'False'|'None') b='=' {
+        self.raise_syntax_error_known_range(f"cannot assign to {a.string}", a, b)
+     }
     | a=NAME b='=' expression for_if_clauses {
         self.raise_syntax_error_known_range(
             "invalid syntax. Maybe you meant '==' or ':=' instead of '='?", a, b
@@ -1704,11 +1712,14 @@ invalid_legacy_expression:
         None
      }
 invalid_expression[NoReturn]:
-    | invalid_legacy_expression
     # !(NAME STRING) is not matched so we don't show this error with some invalid string prefixes like: kf"dsfsdf"
     # Soft keywords need to also be ignored because they can be parsed as NAME NAME
     | !(NAME STRING | SOFT_KEYWORD) a=disjunction b=expression_without_invalid {
-        self.raise_syntax_error_known_range("invalid syntax. Perhaps you forgot a comma?", a, b)
+        (
+            self.raise_syntax_error_known_range("invalid syntax. Perhaps you forgot a comma?", a, b)
+            if not isinstance(a, ast.Name) or a.id not in ("print", "exec")
+            else None
+        )
      }
     | a=disjunction 'if' b=disjunction !('else'|':') {
         self.raise_syntax_error_known_range("expected 'else' after 'if' expression", a, b)
@@ -1792,6 +1803,46 @@ invalid_parameters[NoReturn]:
     | param_no_default* invalid_parameters_helper a=param_no_default {
         self.raise_syntax_error_known_location("non-default argument follows default argument", a)
      }
+    | param_no_default* a='(' param_no_default+ ','? b=')' {
+        self.raise_syntax_error_known_range("Function parameters cannot be parenthesized", a, b)
+     }
+    | a="/" ',' {
+        self.raise_syntax_error_known_location("at least one argument must precede /", a)
+     }
+    | (slash_no_default | slash_with_default) param_maybe_default* a='/' {
+        self.raise_syntax_error_known_location("/ may appear only once", a)
+     }
+    | (slash_no_default | slash_with_default)? param_maybe_default* '*' (',' | param_no_default) param_maybe_default* a='/' {
+        self.raise_syntax_error_known_location("/ must be ahead of *", a)
+     }
+    | param_maybe_default+ '/' a='*' {
+        self.raise_syntax_error_known_location("expected comma between / and *", a)
+     }
+invalid_default:
+    | a='=' &(')'|',') {
+        self.raise_syntax_error_known_location("expected default value expression", a)
+     }
+invalid_star_etc:
+    | a='*' (')' | ',' (')' | '**')) {
+        self.raise_syntax_error_known_location("named arguments must follow bare *", a)
+     }
+    | '*' ',' TYPE_COMMENT { self.raise_syntax_error("bare * has associated type comment") }
+    | '*' param a='=' {
+        self.raise_syntax_error_known_location("var-positional argument cannot have default value", a)
+     }
+    | '*' (param_no_default | ',') param_maybe_default* a='*' (param_no_default | ',') {
+        self.raise_syntax_error_known_location("* argument may appear only once", a)
+     }
+invalid_kwds:
+    | '**' param a='=' {
+        self.raise_syntax_error_known_location("var-keyword argument cannot have default value", a)
+     }
+    | '**' param ',' a=param {
+        self.raise_syntax_error_known_location("arguments cannot follow var-keyword argument", a)
+     }
+    | '**' param ',' a=('*'|'**'|'/') {
+        self.raise_syntax_error_known_location("arguments cannot follow var-keyword argument", a)
+     }
 invalid_parameters_helper: # This is only there to avoid type errors
     | a=slash_with_default { [a] }
     | a=param_with_default+
@@ -1799,18 +1850,44 @@ invalid_lambda_parameters[NoReturn]:
     | lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
         self.raise_syntax_error_known_location("non-default argument follows default argument", a)
      }
+    | lambda_param_no_default* a='(' ','.lambda_param+ ','? b=')' {
+        self.raise_syntax_error_known_range("Lambda expression parameters cannot be parenthesized", a, b)
+     }
+    | a="/" ',' {
+        self.raise_syntax_error_known_location("at least one argument must precede /", a)
+     }
+    | (lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* a='/' {
+        self.raise_syntax_error_known_location("/ may appear only once", a)
+     }
+    | (lambda_slash_no_default | lambda_slash_with_default)? lambda_param_maybe_default* '*' (',' | lambda_param_no_default) lambda_param_maybe_default* a='/' {
+        self.raise_syntax_error_known_location("/ must be ahead of *", a)
+     }
+    | lambda_param_maybe_default+ '/' a='*' {
+        self.raise_syntax_error_known_location("expected comma between / and *", a)
+     }
 invalid_lambda_parameters_helper[NoReturn]:
     | a=lambda_slash_with_default { [a] }
     | a=lambda_param_with_default+
-invalid_star_etc[NoReturn]:
-    | a='*' (')' | ',' (')' | '**')) {
-        self.raise_syntax_error_known_location("named arguments must follow bare *", a)
-     }
-    | '*' ',' TYPE_COMMENT { self.raise_syntax_error("bare * has associated type comment") }
 invalid_lambda_star_etc[NoReturn]:
     | '*' (':' | ',' (':' | '**')) {
         self.raise_syntax_error("named arguments must follow bare *")
      }
+    | '*' lambda_param a='=' {
+        self.raise_syntax_error_known_location("var-positional argument cannot have default value", a)
+     }
+    | '*' (lambda_param_no_default | ',') lambda_param_maybe_default* a='*' (lambda_param_no_default | ',') {
+        self.raise_syntax_error_known_location("* argument may appear only once", a)
+     }
+invalid_lambda_kwds:
+    | '**' lambda_param a='=' {
+        self.raise_syntax_error_known_location("var-keyword argument cannot have default value", a)
+     }
+    | '**' lambda_param ',' a=lambda_param {
+        self.raise_syntax_error_known_location("arguments cannot follow var-keyword argument", a)
+     }
+    | '**' lambda_param ',' a=('*'|'**'|'/') {
+        self.raise_syntax_error_known_location("arguments cannot follow var-keyword argument", a)
+     }
 invalid_double_type_comments[NoReturn]:
     | TYPE_COMMENT NEWLINE TYPE_COMMENT NEWLINE INDENT {
         self.raise_syntax_error("Cannot have two type comments on def")


Test patch:
diff --git a/tests/python_parser/test_syntax_error_handling.py b/tests/python_parser/test_syntax_error_handling.py
index ce435dd..dcf7b60 100644
--- a/tests/python_parser/test_syntax_error_handling.py
+++ b/tests/python_parser/test_syntax_error_handling.py
@@ -5,7 +5,15 @@
 
 
 def parse_invalid_syntax(
-    python_parse_file, python_parse_str, tmp_path, source, exc_cls, message, start, end
+    python_parse_file,
+    python_parse_str,
+    tmp_path,
+    source,
+    exc_cls,
+    message,
+    start,
+    end,
+    min_python_version=(3, 10),
 ):
 
     # Check we obtain the expected error from Python
@@ -35,7 +43,7 @@ def parse_invalid_syntax(
         python_parse_file(str(test_file))
 
     # Check Python message but do not expect message to match for earlier Python versions
-    if sys.version_info >= (3, 10):
+    if sys.version_info >= min_python_version:
         # NOTE ugly hack for 1 CPython bug
         if exc_cls is SyntaxError and e.type is IndentationError:
             assert message.replace("'", "") in py_exc.args[0]
@@ -46,20 +54,36 @@ def parse_invalid_syntax(
     assert message in str(e.exconly())
 
     # Check start/end line/column on Python 3.10
-    if sys.version_info >= (3, 10):
-        for parser, exc in [("Python", py_exc), ("pegen", e.value)]:
-            if (
-                exc.lineno != start[0]
-                or exc.offset != start[1]
-                # Do not check end for indentation errors
-                or (not isinstance(e, IndentationError) and exc.end_lineno != end[0])
-                or (not isinstance(e, IndentationError) and (end[1] is not None and exc.end_offset != end[1]))
-            ):
+
+    for parser, exc in ([("Python", py_exc)] if sys.version_info >= min_python_version else []) + [
+        ("pegen", e.value)
+    ]:
+        if (
+            exc.lineno != start[0]
+            or exc.offset != start[1]
+            # Do not check end for indentation errors
+            or (
+                sys.version_info >= (3, 10)
+                and not isinstance(e, IndentationError)
+                and exc.end_lineno != end[0]
+            )
+            or (
+                sys.version_info >= (3, 10)
+                and not isinstance(e, IndentationError)
+                and (end[1] is not None and exc.end_offset != end[1])
+            )
+        ):
+            if sys.version_info >= (3, 10):
                 raise ValueError(
                     f"Expected locations of {start} and {end}, but got "
                     f"{(exc.lineno, exc.offset)} and {(exc.end_lineno, exc.end_offset)} "
                     f"from {parser}"
                 )
+            else:
+                raise ValueError(
+                    f"Expected locations of {start}, but got "
+                    f"{(exc.lineno, exc.offset)} from {parser}"
+                )
 
 
 @pytest.mark.parametrize(
@@ -84,6 +108,7 @@ def test_syntax_error_in_str(
         ("2 if 4", "expected 'else' after 'if' expression", (1, 1), (1, 7)),
         ("(a 1 if b else 2)", "invalid syntax. Perhaps you forgot a comma?", (1, 2), (1, 17)),
         ("(a lambda: 1)", "invalid syntax. Perhaps you forgot a comma?", (1, 2), (1, 13)),
+        ("(a b)", "invalid syntax. Perhaps you forgot a comma?", (1, 2), (1, 5)),
         ("print 1", "Missing parentheses in call to 'print'", (1, 1), (1, 8)),
         ("exec 1", "Missing parentheses in call to 'exec'", (1, 1), (1, 7)),
         ("a if b", "expected 'else' after 'if' expression", (1, 1), (1, 7)),
@@ -135,7 +160,12 @@ def test_invalid_statements(
         # NOTE CPython bug, should report 15 as expected (we use None to omit the check)
         ("f(a for a in b, c)", "Generator expression must be parenthesized", (1, 3), (1, None)),
         # NOTE CPython bug, should report 20 as expected (we use None to omit the check)
-        ("f(a for a in b if a, c)", "Generator expression must be parenthesized", (1, 3), (1, None)),
+        (
+            "f(a for a in b if a, c)",
+            "Generator expression must be parenthesized",
+            (1, 3),
+            (1, None),
+        ),
         # NOTE CPython bug, should report 15 as expected (we use None to omit the check)
         (
             "f(a for a in b, c for c in d)",
@@ -168,13 +198,24 @@ def test_invalid_statements(
             (1, 3),
             (1, 9),
         ),
+        ("f(True=1)", "cannot assign to True", (1, 3), (1, 8)),
+        ("f(False=1)", "cannot assign to False", (1, 3), (1, 9)),
+        ("f(None=1)", "cannot assign to None", (1, 3), (1, 8)),
     ],
 )
 def test_invalid_call_arguments(
     python_parse_file, python_parse_str, tmp_path, source, message, start, end
 ):
     parse_invalid_syntax(
-        python_parse_file, python_parse_str, tmp_path, source, SyntaxError, message, start, end
+        python_parse_file,
+        python_parse_str,
+        tmp_path,
+        source,
+        SyntaxError,
+        message,
+        start,
+        end,
+        (3, 11) if "cannot assign" in message else (3, 10),
     )
 
 
@@ -325,24 +366,314 @@ def test_invalid_comprehension(
 
 
 @pytest.mark.parametrize(
-    "source, start, end",
+    "source, message, start, end, py_version",
     [
-        ("def f(a=1, b):\n\tpass", (1, 12), (1, 13)),
-        ("def f(a=1, /, b):\n\tpass", (1, 15), (1, 16)),
-        ("lambda x=1, y: x", (1, 13), (1, 14)),
-        ("lambda x=1, /, y: x", (1, 16), (1, 17)),
+        (
+            "def f(a=1, b):\n\tpass",
+            "non-default argument follows default argument",
+            (1, 12),
+            (1, 13),
+            (3, 10),
+        ),
+        (
+            "def f(a=1, /, b):\n\tpass",
+            "non-default argument follows default argument",
+            (1, 15),
+            (1, 16),
+            (3, 10),
+        ),
+        (
+            "def f(x, (y, z), w):\n\tpass",
+            "parameters cannot be parenthesized",
+            (1, 10),
+            (1, 16),
+            (3, 11),
+        ),
+        (
+            "def f((x, y, z, w)):\n\tpass",
+            "parameters cannot be parenthesized",
+            (1, 7),
+            (1, 19),
+            (3, 11),
+        ),
+        (
+            "def f(x, (y, z, w)):\n\tpass",
+            "parameters cannot be parenthesized",
+            (1, 10),
+            (1, 19),
+            (3, 11),
+        ),
+        (
+            "def f((x, y, z), w):\n\tpass",
+            "parameters cannot be parenthesized",
+            (1, 7),
+            (1, 16),
+            (3, 11),
+        ),
+        (
+            "def foo(/,a,b=,c):\n\tpass",
+            "at least one argument must precede /",
+            (1, 9),
+            (1, 10),
+            (3, 11),
+        ),
+        ("def foo(a,/,/,b,c):\n\tpass", "/ may appear only once", (1, 13), (1, 14), (3, 11)),
+        ("def foo(a,/,a1,/,b,c):\n\tpass", "/ may appear only once", (1, 16), (1, 17), (3, 11)),
+        ("def foo(a=1,/,/,*b,/,c):\n\tpass", "/ may appear only once", (1, 15), (1, 16), (3, 11)),
+        ("def foo(a,/,a1=1,/,b,c):\n\tpass", "/ may appear only once", (1, 18), (1, 19), (3, 11)),
+        ("def foo(a,*b,c,/,d,e):\n\tpass", "/ must be ahead of *", (1, 16), (1, 17), (3, 11)),
+        ("def foo(a=1,*b,c=3,/,d,e):\n\tpass", "/ must be ahead of *", (1, 20), (1, 21), (3, 11)),
+        (
+            "def foo(a,*b=3,c):\n\tpass",
+            "var-positional argument cannot have default value",
+            (1, 13),
+            (1, 14),
+            (3, 11),
+        ),
+        (
+            "def foo(a,*b: int=,c):\n\tpass",
+            "var-positional argument cannot have default value",
+            (1, 18),
+            (1, 19),
+            (3, 11),
+        ),
+        (
+            "def foo(a,**b=3):\n\tpass",
+            "var-keyword argument cannot have default value",
+            (1, 14),
+            (1, 15),
+            (3, 11),
+        ),
+        (
+            "def foo(a,**b: int=3):\n\tpass",
+            "var-keyword argument cannot have default value",
+            (1, 19),
+            (1, 20),
+            (3, 11),
+        ),
+        (
+            "def foo(a,*a, b, **c, d):\n\tpass",
+            "arguments cannot follow var-keyword argument",
+            (1, 23),
+            (1, 24),
+            (3, 11),
+        ),
+        (
+            "def foo(a,*a, b, **c, d=4):\n\tpass",
+            "arguments cannot follow var-keyword argument",
+            (1, 23),
+            (1, 24),
+            (3, 11),
+        ),
+        (
+            "def foo(a,*a, b, **c, *d):\n\tpass",
+            "arguments cannot follow var-keyword argument",
+            (1, 23),
+            (1, 24),
+            (3, 11),
+        ),
+        (
+            "def foo(a,*a, b, **c, **d):\n\tpass",
+            "arguments cannot follow var-keyword argument",
+            (1, 23),
+            (1, 25),
+            (3, 11),
+        ),
+        (
+            "def foo(a=1,/,**b,/,c):\n\tpass",
+            "arguments cannot follow var-keyword argument",
+            (1, 19),
+            (1, 20),
+            (3, 11),
+        ),
+        ("def foo(*b,*d):\n\tpass", "* argument may appear only once", (1, 12), (1, 13), (3, 11)),
+        (
+            "def foo(a,*b,c,*d,*e,c):\n\tpass",
+            "* argument may appear only once",
+            (1, 16),
+            (1, 17),
+            (3, 11),
+        ),
+        (
+            "def foo(a,b,/,c,*b,c,*d,*e,c):\n\tpass",
+            "* argument may appear only once",
+            (1, 22),
+            (1, 23),
+            (3, 11),
+        ),
+        (
+            "def foo(a,b,/,c,*b,c,*d,**e):\n\tpass",
+            "* argument may appear only once",
+            (1, 22),
+            (1, 23),
+            (3, 11),
+        ),
+        (
+            "def foo(a=1,/*,b,c):\n\tpass",
+            "expected comma between / and *",
+            (1, 14),
+            (1, 15),
+            (3, 11),
+        ),
+        (
+            "def foo(a=1,d=,c):\n\tpasss",
+            "expected default value expression",
+            (1, 14),
+            (1, 15),
+            (3, 11),
+        ),
+        (
+            "def foo(a,d=,c):\n\tpass",
+            "expected default value expression",
+            (1, 12),
+            (1, 13),
+            (3, 11),
+        ),
+        (
+            "def foo(a,d: int=,c):\n\tpass",
+            "expected default value expression",
+            (1, 17),
+            (1, 18),
+            (3, 11),
+        ),
+        (
+            "lambda x=1, y: x",
+            "non-default argument follows default argument",
+            (1, 13),
+            (1, 14),
+            (3, 10),
+        ),
+        (
+            "lambda x=1, /, y: x",
+            "non-default argument follows default argument",
+            (1, 16),
+            (1, 17),
+            (3, 10),
+        ),
+        (
+            "lambda x, (y, z), w: None",
+            "parameters cannot be parenthesized",
+            (1, 11),
+            (1, 17),
+            (3, 11),
+        ),
+        (
+            "lambda (x, y, z, w): None",
+            "parameters cannot be parenthesized",
+            (1, 8),
+            (1, 20),
+            (3, 11),
+        ),
+        (
+            "lambda x, (y, z, w): None",
+            "parameters cannot be parenthesized",
+            (1, 11),
+            (1, 20),
+            (3, 11),
+        ),
+        (
+            "lambda (x, y, z), w: None",
+            "parameters cannot be parenthesized",
+            (1, 8),
+            (1, 17),
+            (3, 11),
+        ),
+        ("lambda /,a,b,c: None", "at least one argument must precede /", (1, 8), (1, 9), (3, 11)),
+        ("lambda a,/,/,b,c: None", "/ may appear only once", (1, 12), (1, 13), (3, 11)),
+        ("lambda a,/,a1,/,b,c: None", "/ may appear only once", (1, 15), (1, 16), (3, 11)),
+        ("lambda a=1,/,/,*b,/,c: None", "/ may appear only once", (1, 14), (1, 15), (3, 11)),
+        ("lambda a,/,a1=1,/,b,c: None", "/ may appear only once", (1, 17), (1, 18), (3, 11)),
+        ("lambda a,*b,c,/,d,e: None", "/ must be ahead of *", (1, 15), (1, 16), (3, 11)),
+        ("lambda a=1,*b,c=3,/,d,e: None", "/ must be ahead of *", (1, 19), (1, 20), (3, 11)),
+        ("lambda a=1,/*,b,c: None", "expected comma between / and *", (1, 13), (1, 14), (3, 11)),
+        (
+            "lambda a,*b=3,c: None",
+            "var-positional argument cannot have default value",
+            (1, 12),
+            (1, 13),
+            (3, 11),
+        ),
+        (
+            "lambda a,**b=3: None",
+            "var-keyword argument cannot have default value",
+            (1, 13),
+            (1, 14),
+            (3, 11),
+        ),
+        (
+            "lambda a, *a, b, **c, d: None",
+            "arguments cannot follow var-keyword argument",
+            (1, 23),
+            (1, 24),
+            (3, 11),
+        ),
+        (
+            "lambda a,*a, b, **c, d=4: None",
+            "arguments cannot follow var-keyword argument",
+            (1, 22),
+            (1, 23),
+            (3, 11),
+        ),
+        (
+            "lambda a,*a, b, **c, *d: None",
+            "arguments cannot follow var-keyword argument",
+            (1, 22),
+            (1, 23),
+            (3, 11),
+        ),
+        (
+            "lambda a,*a, b, **c, **d: None",
+            "arguments cannot follow var-keyword argument",
+            (1, 22),
+            (1, 24),
+            (3, 11),
+        ),
+        (
+            "lambda a=1,/,**b,/,c: None",
+            "arguments cannot follow var-keyword argument",
+            (1, 18),
+            (1, 19),
+            (3, 11),
+        ),
+        ("lambda *b,*d: None", "* argument may appear only once", (1, 11), (1, 12), (3, 11)),
+        (
+            "lambda a,*b,c,*d,*e,c: None",
+            "* argument may appear only once",
+            (1, 15),
+            (1, 16),
+            (3, 11),
+        ),
+        (
+            "lambda a,b,/,c,*b,c,*d,*e,c: None",
+            "* argument may appear only once",
+            (1, 21),
+            (1, 22),
+            (3, 11),
+        ),
+        (
+            "lambda a,b,/,c,*b,c,*d,**e: None",
+            "* argument may appear only once",
+            (1, 21),
+            (1, 22),
+            (3, 11),
+        ),
+        ("lambda a=1,d=,c: None", "expected default value expression", (1, 13), (1, 14), (3, 11)),
+        ("lambda a,d=,c: None", "expected default value expression", (1, 11), (1, 12), (3, 11)),
     ],
 )
-def test_invalid_parameters(python_parse_file, python_parse_str, tmp_path, source, start, end):
+def test_invalid_parameters(
+    python_parse_file, python_parse_str, tmp_path, source, message, start, end, py_version
+):
     parse_invalid_syntax(
         python_parse_file,
         python_parse_str,
         tmp_path,
         source,
         SyntaxError,
-        "non-default argument follows default argument",
+        message,
         start,
         end,
+        py_version,
     )
 
 
@@ -878,6 +1209,8 @@ def test_invalid_for_stmt(
             (2, 1),
             (2, 5),
         ),
+        ("def f:", SyntaxError, "expected '('", (1, 6), (1, 6)),
+        ("async def f:", SyntaxError, "expected '('", (1, 12), (1, 12)),
         # (
         #     "def f():\n# type: () -> int\n# type: () -> str\n\tpass",
         #     SyntaxError,
@@ -889,7 +1222,15 @@ def test_invalid_def_stmt(
     python_parse_file, python_parse_str, tmp_path, source, exception, message, start, end
 ):
     parse_invalid_syntax(
-        python_parse_file, python_parse_str, tmp_path, source, exception, message, start, end
+        python_parse_file,
+        python_parse_str,
+        tmp_path,
+        source,
+        exception,
+        message,
+        start,
+        end,
+        (3, 11) if exception is SyntaxError else (3, 10),
     )