Skip to content

Commit 7e36abb

Browse files
authored
gh-91210: Improve error message when non-default param follows default (GH-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
1 parent 78359b1 commit 7e36abb

File tree

5 files changed

+391
-365
lines changed

5 files changed

+391
-365
lines changed

Grammar/python.gram

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1162,14 +1162,14 @@ invalid_dict_comprehension:
11621162
| '{' a='**' bitwise_or for_if_clauses '}' {
11631163
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") }
11641164
invalid_parameters:
1165-
| param_no_default* invalid_parameters_helper a=param_no_default {
1166-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
1167-
| param_no_default* a='(' param_no_default+ ','? b=')' {
1168-
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Function parameters cannot be parenthesized") }
11691165
| a="/" ',' {
11701166
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
11711167
| (slash_no_default | slash_with_default) param_maybe_default* a='/' {
11721168
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
1169+
| slash_no_default? param_no_default* invalid_parameters_helper a=param_no_default {
1170+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameter without a default follows parameter with a default") }
1171+
| param_no_default* a='(' param_no_default+ ','? b=')' {
1172+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Function parameters cannot be parenthesized") }
11731173
| (slash_no_default | slash_with_default)? param_maybe_default* '*' (',' | param_no_default) param_maybe_default* a='/' {
11741174
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
11751175
| param_maybe_default+ '/' a='*' {
@@ -1190,14 +1190,14 @@ invalid_parameters_helper: # This is only there to avoid type errors
11901190
| a=slash_with_default { _PyPegen_singleton_seq(p, a) }
11911191
| param_with_default+
11921192
invalid_lambda_parameters:
1193-
| lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
1194-
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "non-default argument follows default argument") }
1195-
| lambda_param_no_default* a='(' ','.lambda_param+ ','? b=')' {
1196-
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Lambda expression parameters cannot be parenthesized") }
11971193
| a="/" ',' {
11981194
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
11991195
| (lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* a='/' {
12001196
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") }
1197+
| lambda_slash_no_default? lambda_param_no_default* invalid_lambda_parameters_helper a=lambda_param_no_default {
1198+
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "parameter without a default follows parameter with a default") }
1199+
| lambda_param_no_default* a='(' ','.lambda_param+ ','? b=')' {
1200+
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "Lambda expression parameters cannot be parenthesized") }
12011201
| (lambda_slash_no_default | lambda_slash_with_default)? lambda_param_maybe_default* '*' (',' | lambda_param_no_default) lambda_param_maybe_default* a='/' {
12021202
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
12031203
| lambda_param_maybe_default+ '/' a='*' {

Lib/test/test_positional_only_arg.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ def assertRaisesSyntaxError(self, codestr, regex="invalid syntax"):
2323
compile(codestr + "\n", "<test>", "single")
2424

2525
def test_invalid_syntax_errors(self):
26-
check_syntax_error(self, "def f(a, b = 5, /, c): pass", "non-default argument follows default argument")
27-
check_syntax_error(self, "def f(a = 5, b, /, c): pass", "non-default argument follows default argument")
28-
check_syntax_error(self, "def f(a = 5, b=1, /, c, *, d=2): pass", "non-default argument follows default argument")
29-
check_syntax_error(self, "def f(a = 5, b, /): pass", "non-default argument follows default argument")
26+
check_syntax_error(self, "def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default")
27+
check_syntax_error(self, "def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default")
28+
check_syntax_error(self, "def f(a = 5, b=1, /, c, *, d=2): pass", "parameter without a default follows parameter with a default")
29+
check_syntax_error(self, "def f(a = 5, b, /): pass", "parameter without a default follows parameter with a default")
30+
check_syntax_error(self, "def f(a, /, b = 5, c): pass", "parameter without a default follows parameter with a default")
3031
check_syntax_error(self, "def f(*args, /): pass")
3132
check_syntax_error(self, "def f(*args, a, /): pass")
3233
check_syntax_error(self, "def f(**kwargs, /): pass")
@@ -44,10 +45,11 @@ def test_invalid_syntax_errors(self):
4445
check_syntax_error(self, "def f(a, *, c, /, d, e): pass")
4546

4647
def test_invalid_syntax_errors_async(self):
47-
check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "non-default argument follows default argument")
48-
check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "non-default argument follows default argument")
49-
check_syntax_error(self, "async def f(a = 5, b=1, /, c, d=2): pass", "non-default argument follows default argument")
50-
check_syntax_error(self, "async def f(a = 5, b, /): pass", "non-default argument follows default argument")
48+
check_syntax_error(self, "async def f(a, b = 5, /, c): pass", "parameter without a default follows parameter with a default")
49+
check_syntax_error(self, "async def f(a = 5, b, /, c): pass", "parameter without a default follows parameter with a default")
50+
check_syntax_error(self, "async def f(a = 5, b=1, /, c, d=2): pass", "parameter without a default follows parameter with a default")
51+
check_syntax_error(self, "async def f(a = 5, b, /): pass", "parameter without a default follows parameter with a default")
52+
check_syntax_error(self, "async def f(a, /, b = 5, c): pass", "parameter without a default follows parameter with a default")
5153
check_syntax_error(self, "async def f(*args, /): pass")
5254
check_syntax_error(self, "async def f(*args, a, /): pass")
5355
check_syntax_error(self, "async def f(**kwargs, /): pass")
@@ -231,9 +233,11 @@ def test_lambdas(self):
231233
self.assertEqual(x(1, 2), 3)
232234

233235
def test_invalid_syntax_lambda(self):
234-
check_syntax_error(self, "lambda a, b = 5, /, c: None", "non-default argument follows default argument")
235-
check_syntax_error(self, "lambda a = 5, b, /, c: None", "non-default argument follows default argument")
236-
check_syntax_error(self, "lambda a = 5, b, /: None", "non-default argument follows default argument")
236+
check_syntax_error(self, "lambda a, b = 5, /, c: None", "parameter without a default follows parameter with a default")
237+
check_syntax_error(self, "lambda a = 5, b, /, c: None", "parameter without a default follows parameter with a default")
238+
check_syntax_error(self, "lambda a = 5, b=1, /, c, *, d=2: None", "parameter without a default follows parameter with a default")
239+
check_syntax_error(self, "lambda a = 5, b, /: None", "parameter without a default follows parameter with a default")
240+
check_syntax_error(self, "lambda a, /, b = 5, c: None", "parameter without a default follows parameter with a default")
237241
check_syntax_error(self, "lambda *args, /: None")
238242
check_syntax_error(self, "lambda *args, a, /: None")
239243
check_syntax_error(self, "lambda **kwargs, /: None")

Lib/test/test_syntax.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,12 @@
334334
>>> def f(x, y=1, z):
335335
... pass
336336
Traceback (most recent call last):
337-
SyntaxError: non-default argument follows default argument
337+
SyntaxError: parameter without a default follows parameter with a default
338+
339+
>>> def f(x, /, y=1, z):
340+
... pass
341+
Traceback (most recent call last):
342+
SyntaxError: parameter without a default follows parameter with a default
338343
339344
>>> def f(x, None):
340345
... pass
@@ -555,6 +560,14 @@
555560
Traceback (most recent call last):
556561
SyntaxError: expected default value expression
557562
563+
>>> lambda a,d=3,c: None
564+
Traceback (most recent call last):
565+
SyntaxError: parameter without a default follows parameter with a default
566+
567+
>>> lambda a,/,d=3,c: None
568+
Traceback (most recent call last):
569+
SyntaxError: parameter without a default follows parameter with a default
570+
558571
>>> import ast; ast.parse('''
559572
... def f(
560573
... *, # type: int
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
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.

0 commit comments

Comments
 (0)