bpo-46838: Syntax error improvements for function definitions (GH-31590)

This commit is contained in:
Pablo Galindo Salgado 2022-03-22 11:38:41 +00:00 committed by GitHub
parent deeaac49e2
commit 7d810b6a4e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 3488 additions and 963 deletions

View file

@ -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.
#
@ -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
# ------------
@ -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 }
@ -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+
@ -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") }
@ -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 ':'") }

View file

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

View file

@ -0,0 +1,2 @@
Improve syntax errors for incorrect function definitions. Patch by Pablo
Galindo

4191
Parser/parser.c generated

File diff suppressed because it is too large Load diff