Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-46838: Syntax error improvements for function definitions #31590

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 45 additions & 9 deletions Grammar/python.gram
Original file line number Diff line number Diff line change
Expand Up @@ -306,14 +306,16 @@ slash_with_default[SlashWithDefault*]:
| a=param_no_default* b=param_with_default+ '/' &')' { _PyPegen_slash_with_default(p, (asdl_arg_seq *)a, b) }

star_etc[StarEtc*]:
| invalid_star_etc
| '*' a=param_no_default b=param_maybe_default* c=[kwds] {
_PyPegen_star_etc(p, a, b, c) }
| '*' ',' b=param_maybe_default+ c=[kwds] {
_PyPegen_star_etc(p, NULL, b, c) }
| a=kwds { _PyPegen_star_etc(p, NULL, NULL, a) }
| invalid_star_etc

kwds[arg_ty]: '**' a=param_no_default { a }
kwds[arg_ty]:
| invalid_kwds
| '**' a=param_no_default { a }

# One parameter. This *includes* a following comma and type comment.
#
Expand All @@ -339,7 +341,7 @@ param_maybe_default[NameDefaultPair*]:
| a=param c=default? tc=TYPE_COMMENT? &')' { _PyPegen_name_default_pair(p, a, c, tc) }
param[arg_ty]: a=NAME b=annotation? { _PyAST_arg(a->v.Name.id, b, NULL, EXTRA) }
annotation[expr_ty]: ':' a=expression { a }
default[expr_ty]: '=' a=expression { a }
default[expr_ty]: '=' a=expression { a } | invalid_default

# If statement
# ------------
Expand Down Expand Up @@ -836,14 +838,16 @@ lambda_slash_with_default[SlashWithDefault*]:
| a=lambda_param_no_default* b=lambda_param_with_default+ '/' &':' { _PyPegen_slash_with_default(p, (asdl_arg_seq *)a, b) }

lambda_star_etc[StarEtc*]:
| invalid_lambda_star_etc
| '*' a=lambda_param_no_default b=lambda_param_maybe_default* c=[lambda_kwds] {
_PyPegen_star_etc(p, a, b, c) }
| '*' ',' b=lambda_param_maybe_default+ c=[lambda_kwds] {
_PyPegen_star_etc(p, NULL, b, c) }
| a=lambda_kwds { _PyPegen_star_etc(p, NULL, NULL, a) }
| invalid_lambda_star_etc

lambda_kwds[arg_ty]: '**' a=lambda_param_no_default { a }
lambda_kwds[arg_ty]:
| invalid_lambda_kwds
| '**' a=lambda_param_no_default { a }

lambda_param_no_default[arg_ty]:
| a=lambda_param ',' { a }
Expand Down Expand Up @@ -1151,6 +1155,26 @@ invalid_parameters:
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 | 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='*' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected comma between / and *") }
invalid_default:
| a='=' &(')'|',') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected default value expression") }
invalid_star_etc:
| a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named arguments must follow bare *") }
| '*' ',' TYPE_COMMENT { RAISE_SYNTAX_ERROR("bare * has associated type comment") }
| '*' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional argument cannot have default value") }
| '*' (param_no_default | ',') param_maybe_default* a='*' (param_no_default | ',') {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* argument may appear only once") }
invalid_kwds:
| '**' param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword argument cannot have default value") }
| '**' param ',' a=param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
| '**' param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
invalid_parameters_helper: # This is only there to avoid type errors
| a=slash_with_default { _PyPegen_singleton_seq(p, a) }
| param_with_default+
Expand All @@ -1159,14 +1183,26 @@ invalid_lambda_parameters:
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_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='*' {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expected comma between / and *") }
invalid_lambda_parameters_helper:
| a=lambda_slash_with_default { _PyPegen_singleton_seq(p, a) }
| lambda_param_with_default+
invalid_star_etc:
| a='*' (')' | ',' (')' | '**')) { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "named arguments must follow bare *") }
| '*' ',' TYPE_COMMENT { RAISE_SYNTAX_ERROR("bare * has associated type comment") }
invalid_lambda_star_etc:
| '*' (':' | ',' (':' | '**')) { RAISE_SYNTAX_ERROR("named arguments must follow bare *") }
| '*' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-positional argument cannot have default value") }
| '*' (lambda_param_no_default | ',') lambda_param_maybe_default* a='*' (lambda_param_no_default | ',') {
RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "* argument may appear only once") }
invalid_lambda_kwds:
| '**' lambda_param a='=' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "var-keyword argument cannot have default value") }
| '**' lambda_param ',' a=lambda_param { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
| '**' lambda_param ',' a[Token*]=('*'|'**'|'/') { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "arguments cannot follow var-keyword argument") }
invalid_double_type_comments:
| TYPE_COMMENT NEWLINE TYPE_COMMENT NEWLINE INDENT {
RAISE_SYNTAX_ERROR("Cannot have two type comments on def") }
Expand Down Expand Up @@ -1269,4 +1305,4 @@ invalid_kvpair:
| a=expression !(':') {
RAISE_ERROR_KNOWN_LOCATION(p, PyExc_SyntaxError, a->lineno, a->end_col_offset - 1, a->end_lineno, -1, "':' expected after dictionary key") }
| expression ':' a='*' bitwise_or { RAISE_SYNTAX_ERROR_STARTING_FROM(a, "cannot use a starred expression in a dictionary value") }
| expression a=':' {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") }
| expression a=':' {RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "expression expected after dictionary key and ':'") }
204 changes: 204 additions & 0 deletions Lib/test/test_syntax.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,210 @@
Traceback (most recent call last):
SyntaxError: invalid syntax

>>> def foo(/,a,b=,c):
... pass
Traceback (most recent call last):
SyntaxError: at least one argument must precede /

>>> def foo(a,/,/,b,c):
... pass
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> def foo(a,/,a1,/,b,c):
... pass
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> def foo(a=1,/,/,*b,/,c):
... pass
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> def foo(a,/,a1=1,/,b,c):
... pass
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> def foo(a,*b,c,/,d,e):
... pass
Traceback (most recent call last):
SyntaxError: / must be ahead of *

>>> def foo(a=1,*b,c=3,/,d,e):
... pass
Traceback (most recent call last):
SyntaxError: / must be ahead of *

>>> def foo(a,*b=3,c):
... pass
Traceback (most recent call last):
SyntaxError: var-positional argument cannot have default value

>>> def foo(a,*b: int=,c):
... pass
Traceback (most recent call last):
SyntaxError: var-positional argument cannot have default value

>>> def foo(a,**b=3):
... pass
Traceback (most recent call last):
SyntaxError: var-keyword argument cannot have default value

>>> def foo(a,**b: int=3):
... pass
Traceback (most recent call last):
SyntaxError: var-keyword argument cannot have default value

>>> def foo(a,*a, b, **c, d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> def foo(a,*a, b, **c, d=4):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> def foo(a,*a, b, **c, *d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> def foo(a,*a, b, **c, **d):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> def foo(a=1,/,**b,/,c):
... pass
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> def foo(*b,*d):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> def foo(a,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> def foo(a,b,/,c,*b,c,*d,*e,c):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> def foo(a,b,/,c,*b,c,*d,**e):
... pass
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> def foo(a=1,/*,b,c):
... pass
Traceback (most recent call last):
SyntaxError: expected comma between / and *

>>> def foo(a=1,d=,c):
... pass
Traceback (most recent call last):
SyntaxError: expected default value expression

>>> def foo(a,d=,c):
... pass
Traceback (most recent call last):
SyntaxError: expected default value expression

>>> def foo(a,d: int=,c):
... pass
Traceback (most recent call last):
SyntaxError: expected default value expression

>>> lambda /,a,b,c: None
Traceback (most recent call last):
SyntaxError: at least one argument must precede /

>>> lambda a,/,/,b,c: None
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> lambda a,/,a1,/,b,c: None
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> lambda a=1,/,/,*b,/,c: None
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> lambda a,/,a1=1,/,b,c: None
Traceback (most recent call last):
SyntaxError: / may appear only once

>>> lambda a,*b,c,/,d,e: None
Traceback (most recent call last):
SyntaxError: / must be ahead of *

>>> lambda a=1,*b,c=3,/,d,e: None
Traceback (most recent call last):
SyntaxError: / must be ahead of *

>>> lambda a=1,/*,b,c: None
Traceback (most recent call last):
SyntaxError: expected comma between / and *

>>> lambda a,*b=3,c: None
Traceback (most recent call last):
SyntaxError: var-positional argument cannot have default value

>>> lambda a,**b=3: None
Traceback (most recent call last):
SyntaxError: var-keyword argument cannot have default value

>>> lambda a, *a, b, **c, d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> lambda a,*a, b, **c, d=4: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> lambda a,*a, b, **c, *d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> lambda a,*a, b, **c, **d: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> lambda a=1,/,**b,/,c: None
Traceback (most recent call last):
SyntaxError: arguments cannot follow var-keyword argument

>>> lambda *b,*d: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> lambda a,*b,c,*d,*e,c: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> lambda a,b,/,c,*b,c,*d,*e,c: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> lambda a,b,/,c,*b,c,*d,**e: None
Traceback (most recent call last):
SyntaxError: * argument may appear only once

>>> lambda a=1,d=,c: None
Traceback (most recent call last):
SyntaxError: expected default value expression

>>> lambda a,d=,c: None
Traceback (most recent call last):
SyntaxError: expected default value expression

>>> import ast; ast.parse('''
... def f(
... *, # type: int
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve syntax errors for incorrect function definitions. Patch by Pablo
Galindo
Loading