mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									78359b1d45
								
							
						
					
					
						commit
						7e36abbb78
					
				
					 5 changed files with 655 additions and 629 deletions
				
			
		| 
						 | 
					@ -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='*' {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,10 +23,11 @@ def assertRaisesSyntaxError(self, codestr, regex="invalid syntax"):
 | 
				
			||||||
            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 @@ def test_invalid_syntax_errors(self):
 | 
				
			||||||
        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 @@ def test_lambdas(self):
 | 
				
			||||||
        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")
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -334,7 +334,12 @@
 | 
				
			||||||
>>> 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 @@
 | 
				
			||||||
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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
									
									
									
								
							
							
						
						
									
										1220
									
								
								Parser/parser.c
									
										
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue