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
This commit is contained in:
Lysandros Nikolaou 2022-09-17 19:09:28 +02:00 committed by GitHub
parent 78359b1d45
commit 7e36abbb78
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 655 additions and 629 deletions

View File

@ -1162,14 +1162,14 @@ invalid_dict_comprehension:
| '{' a='**' bitwise_or for_if_clauses '}' { | '{' a='**' bitwise_or for_if_clauses '}' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") } RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "dict unpacking cannot be used in dict comprehension") }
invalid_parameters: 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="/" ',' { | a="/" ',' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") } RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") }
| (slash_no_default | slash_with_default) param_maybe_default* a='/' { | (slash_no_default | slash_with_default) param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") } 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='/' { | (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 *") } RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
| param_maybe_default+ '/' a='*' { | param_maybe_default+ '/' a='*' {
@ -1190,14 +1190,14 @@ invalid_parameters_helper: # This is only there to avoid type errors
| a=slash_with_default { _PyPegen_singleton_seq(p, a) } | a=slash_with_default { _PyPegen_singleton_seq(p, a) }
| param_with_default+ | param_with_default+
invalid_lambda_parameters: 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="/" ',' { | a="/" ',' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "at least one argument must precede /") } 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='/' { | (lambda_slash_no_default | lambda_slash_with_default) lambda_param_maybe_default* a='/' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ may appear only once") } 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='/' { | (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 *") } RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "/ must be ahead of *") }
| lambda_param_maybe_default+ '/' a='*' { | lambda_param_maybe_default+ '/' a='*' {

View File

@ -23,10 +23,11 @@ class PositionalOnlyTestCase(unittest.TestCase):
compile(codestr + "\n", "<test>", "single") compile(codestr + "\n", "<test>", "single")
def test_invalid_syntax_errors(self): 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, b = 5, /, c): pass", "parameter without a default follows parameter with a default")
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, /, 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", "non-default argument follows default argument") 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", "non-default argument follows default argument") 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, /): pass")
check_syntax_error(self, "def f(*args, a, /): pass") check_syntax_error(self, "def f(*args, a, /): pass")
check_syntax_error(self, "def f(**kwargs, /): pass") check_syntax_error(self, "def f(**kwargs, /): pass")
@ -44,10 +45,11 @@ class PositionalOnlyTestCase(unittest.TestCase):
check_syntax_error(self, "def f(a, *, c, /, d, e): pass") check_syntax_error(self, "def f(a, *, c, /, d, e): pass")
def test_invalid_syntax_errors_async(self): 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, 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", "non-default argument follows default argument") 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", "non-default argument follows default argument") 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", "non-default argument follows default argument") 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, /): pass")
check_syntax_error(self, "async def f(*args, a, /): pass") check_syntax_error(self, "async def f(*args, a, /): pass")
check_syntax_error(self, "async def f(**kwargs, /): pass") check_syntax_error(self, "async def f(**kwargs, /): pass")
@ -231,9 +233,11 @@ class PositionalOnlyTestCase(unittest.TestCase):
self.assertEqual(x(1, 2), 3) self.assertEqual(x(1, 2), 3)
def test_invalid_syntax_lambda(self): 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, b = 5, /, c: None", "parameter without a default follows parameter with a default")
check_syntax_error(self, "lambda a = 5, b, /, c: None", "non-default argument follows default argument") 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, /: None", "non-default argument follows default argument") 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, /: None")
check_syntax_error(self, "lambda *args, a, /: None") check_syntax_error(self, "lambda *args, a, /: None")
check_syntax_error(self, "lambda **kwargs, /: None") check_syntax_error(self, "lambda **kwargs, /: None")

View File

@ -334,7 +334,12 @@ From ast_for_arguments():
>>> def f(x, y=1, z): >>> def f(x, y=1, z):
... pass ... pass
Traceback (most recent call last): 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): >>> def f(x, None):
... pass ... pass
@ -555,6 +560,14 @@ SyntaxError: expected default value expression
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: expected default value expression 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(''' >>> import ast; ast.parse('''
... def f( ... def f(
... *, # type: int ... *, # type: int

View File

@ -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.

1220
Parser/parser.c generated

File diff suppressed because it is too large Load Diff