Skip to content

Commit

Permalink
pythongh-91210: Improve error message when non-default param follows …
Browse files Browse the repository at this point in the history
…default (pythonGH-95933)

- Improve error message when parameter without a default follows one with a default
- Show same error message when positional-only params precede the default/non-default sequence
  • Loading branch information
lysnikolaou authored Sep 17, 2022
1 parent 78359b1 commit 7e36abb
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 365 deletions.
16 changes: 8 additions & 8 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -1162,14 +1162,14 @@ invalid_dict_comprehension:
| '{' a='**' bitwise_or for_if_clauses '}' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") }
invalid_parameters:
| param_no_default* invalid_parameters_helper a=param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
| param_no_default* a='(' param_no_default+ ','? b=')' {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Function parameters cannot be parenthesized") }
| a="/" ',' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
| (slash_no_default | slash_with_default) param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
| slash_no_default? param_no_default* invalid_parameters_helper a=param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameter without a default follows parameter with a default") }
| param_no_default* a='(' param_no_default+ ','? b=')' {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Function parameters cannot be parenthesized") }
| (slash_no_default | slash_with_default)? param_maybe_default* '*' (',' | param_no_default) param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
| param_maybe_default+ '/' a='*' {
Expand All @@ -1190,14 +1190,14 @@ invalid_parameters_helper: # This is only there to avoid type errors
| a=slash_with_default { _PyPegen_singleton_seq(p, a) }
| param_with_default+
invalid_lambda_parameters:
| lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
| lambda_param_no_default* a='(' ','.lambda_param+ ','? b=')' {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Lambda expression parameters cannot be parenthesized") }
| a="/" ',' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
| (lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
| lambda_slash_no_default? lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameter without a default follows parameter with a default") }
| lambda_param_no_default* a='(' ','.lambda_param+ ','? b=')' {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Lambda expression parameters cannot be parenthesized") }
| (lambda_slash_no_default | lambda_slash_with_default)? lambda_param_maybe_default* '*' (',' | lambda_param_no_default) lambda_param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
| lambda_param_maybe_default+ '/' a='*' {
Expand Down
26 changes: 15 additions & 11 deletions Lib/test/test_positional_only_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ def assertRaisesSyntaxError(self, codestr, regex="invalid syntax"):
compile(codestr + "\n", "<test>", "single")

def test_invalid_syntax_errors(self):
check_syntax_error(self, "def f(a, b = 5, /, c): pass", "non-default argument follows default argument")
check_syntax_error(self, "def f(a = 5, b, /, c): pass", "non-default argument follows default argument")
check_syntax_error(self, "def f(a = 5, b=1, /, c, *, d=2): pass", "non-default argument follows default argument")
check_syntax_error(self, "def f(a = 5, b, /): pass", "non-default argument follows default argument")
check_syntax_error(self, "def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "def f(a = 5, b=1, /, c, *, d=2): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "def f(a = 5, b, /): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "def f(a, /, b = 5, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "def f(*args, /): pass")
check_syntax_error(self, "def f(*args, a, /): pass")
check_syntax_error(self, "def f(**kwargs, /): pass")
Expand All @@ -44,10 +45,11 @@ def test_invalid_syntax_errors(self):
check_syntax_error(self, "def f(a, *, c, /, d, e): pass")

def test_invalid_syntax_errors_async(self):
check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "non-default argument follows default argument")
check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "non-default argument follows default argument")
check_syntax_error(self, "async def f(a = 5, b=1, /, c, d=2): pass", "non-default argument follows default argument")
check_syntax_error(self, "async def f(a = 5, b, /): pass", "non-default argument follows default argument")
check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "async def f(a = 5, b=1, /, c, d=2): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "async def f(a = 5, b, /): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "async def f(a, /, b = 5, c): pass", "parameter without a default follows parameter with a default")
check_syntax_error(self, "async def f(*args, /): pass")
check_syntax_error(self, "async def f(*args, a, /): pass")
check_syntax_error(self, "async def f(**kwargs, /): pass")
Expand Down Expand Up @@ -231,9 +233,11 @@ def test_lambdas(self):
self.assertEqual(x(1, 2), 3)

def test_invalid_syntax_lambda(self):
check_syntax_error(self, "lambda a, b = 5, /, c: None", "non-default argument follows default argument")
check_syntax_error(self, "lambda a = 5, b, /, c: None", "non-default argument follows default argument")
check_syntax_error(self, "lambda a = 5, b, /: None", "non-default argument follows default argument")
check_syntax_error(self, "lambda a, b = 5, /, c: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda a = 5, b, /, c: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda a = 5, b=1, /, c, *, d=2: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda a = 5, b, /: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda a, /, b = 5, c: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda *args, /: None")
check_syntax_error(self, "lambda *args, a, /: None")
check_syntax_error(self, "lambda **kwargs, /: None")
Expand Down
15 changes: 14 additions & 1 deletion Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,12 @@
>>> def f(x, y=1, z):
... pass
Traceback (most recent call last):
SyntaxError: non-default argument follows default argument
SyntaxError: parameter without a default follows parameter with a default
>>> def f(x, /, y=1, z):
... pass
Traceback (most recent call last):
SyntaxError: parameter without a default follows parameter with a default
>>> def f(x, None):
... pass
Expand Down Expand Up @@ -555,6 +560,14 @@
Traceback (most recent call last):
SyntaxError: expected default value expression
>>> lambda a,d=3,c: None
Traceback (most recent call last):
SyntaxError: parameter without a default follows parameter with a default
>>> lambda a,/,d=3,c: None
Traceback (most recent call last):
SyntaxError: parameter without a default follows parameter with a default
>>> import ast; ast.parse('''
... def f(
... *, # type: int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve error message when a parameter without a default value follows one with a default value, and show the same message, even when the non-default/default sequence is preceded by positional-only parameters.
Loading

0 comments on commit 7e36abb

Please sign in to comment.