bpo-47212: Improve error messages for un-parenthesized generator expressions (GH-32302)

This commit is contained in:
Matthieu Dartiailh 2022-04-05 15:47:13 +02:00 committed by GitHub
parent f1606a5ba5
commit aa0f056a00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 28 additions and 8 deletions

View File

@ -1073,12 +1073,12 @@ func_type_comment[Token*]:
invalid_arguments: invalid_arguments:
| a=args ',' '*' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable argument unpacking follows keyword argument unpacking") } | a=args ',' '*' { RAISE_SYNTAX_ERROR_KNOWN_LOCATION(a, "iterable argument unpacking follows keyword argument unpacking") }
| a=expression b=for_if_clauses ',' [args | expression for_if_clauses] { | a=expression b=for_if_clauses ',' [args | expression for_if_clauses] {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, PyPegen_last_item(b, comprehension_ty)->target, "Generator expression must be parenthesized") } RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, _PyPegen_get_last_comprehension_item(PyPegen_last_item(b, comprehension_ty)), "Generator expression must be parenthesized") }
| a=NAME b='=' expression for_if_clauses { | a=NAME b='=' expression for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?")} RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, b, "invalid syntax. Maybe you meant '==' or ':=' instead of '='?")}
| a=args b=for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a, b) } | a=args b=for_if_clauses { _PyPegen_nonparen_genexp_in_call(p, a, b) }
| args ',' a=expression b=for_if_clauses { | args ',' a=expression b=for_if_clauses {
RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, asdl_seq_GET(b, b->size-1)->target, "Generator expression must be parenthesized") } RAISE_SYNTAX_ERROR_KNOWN_RANGE(a, _PyPegen_get_last_comprehension_item(PyPegen_last_item(b, comprehension_ty)), "Generator expression must be parenthesized") }
| a=args ',' args { _PyPegen_arguments_parsing_error(p, a) } | a=args ',' args { _PyPegen_arguments_parsing_error(p, a) }
invalid_kwarg: invalid_kwarg:
| a[Token*]=('True'|'False'|'None') b='=' { | a[Token*]=('True'|'False'|'None') b='=' {
@ -1257,7 +1257,7 @@ invalid_finally_stmt:
invalid_except_stmt_indent: invalid_except_stmt_indent:
| a='except' expression ['as' NAME ] ':' NEWLINE !INDENT { | a='except' expression ['as' NAME ] ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) } RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) }
| a='except' ':' NEWLINE !INDENT { RAISE_SYNTAX_ERROR("expected an indented block after except statement on line %d", a->lineno) } | a='except' ':' NEWLINE !INDENT { RAISE_INDENTATION_ERROR("expected an indented block after 'except' statement on line %d", a->lineno) }
invalid_except_star_stmt_indent: invalid_except_star_stmt_indent:
| a='except' '*' expression ['as' NAME ] ':' NEWLINE !INDENT { | a='except' '*' expression ['as' NAME ] ':' NEWLINE !INDENT {
RAISE_INDENTATION_ERROR("expected an indented block after 'except*' statement on line %d", a->lineno) } RAISE_INDENTATION_ERROR("expected an indented block after 'except*' statement on line %d", a->lineno) }

View File

@ -198,12 +198,17 @@ class ExceptionTests(unittest.TestCase):
s = '''if True:\n print()\n\texec "mixed tabs and spaces"''' s = '''if True:\n print()\n\texec "mixed tabs and spaces"'''
ckmsg(s, "inconsistent use of tabs and spaces in indentation", TabError) ckmsg(s, "inconsistent use of tabs and spaces in indentation", TabError)
def check(self, src, lineno, offset, encoding='utf-8'): def check(self, src, lineno, offset, end_lineno=None, end_offset=None, encoding='utf-8'):
with self.subTest(source=src, lineno=lineno, offset=offset): with self.subTest(source=src, lineno=lineno, offset=offset):
with self.assertRaises(SyntaxError) as cm: with self.assertRaises(SyntaxError) as cm:
compile(src, '<fragment>', 'exec') compile(src, '<fragment>', 'exec')
self.assertEqual(cm.exception.lineno, lineno) self.assertEqual(cm.exception.lineno, lineno)
self.assertEqual(cm.exception.offset, offset) self.assertEqual(cm.exception.offset, offset)
if end_lineno is not None:
self.assertEqual(cm.exception.end_lineno, end_lineno)
if end_offset is not None:
self.assertEqual(cm.exception.end_offset, end_offset)
if cm.exception.text is not None: if cm.exception.text is not None:
if not isinstance(src, str): if not isinstance(src, str):
src = src.decode(encoding, 'replace') src = src.decode(encoding, 'replace')
@ -235,6 +240,10 @@ class ExceptionTests(unittest.TestCase):
check('match ...:\n case {**rest, "key": value}:\n ...', 2, 19) check('match ...:\n case {**rest, "key": value}:\n ...', 2, 19)
check("[a b c d e f]", 1, 2) check("[a b c d e f]", 1, 2)
check("for x yfff:", 1, 7) check("for x yfff:", 1, 7)
check("f(a for a in b, c)", 1, 3, 1, 15)
check("f(a for a in b if a, c)", 1, 3, 1, 20)
check("f(a, b for b in c)", 1, 6, 1, 18)
check("f(a, b for b in c, d)", 1, 6, 1, 18)
# Errors thrown by compile.c # Errors thrown by compile.c
check('class foo:return 1', 1, 11) check('class foo:return 1', 1, 11)

View File

@ -1307,6 +1307,13 @@ Specialized indentation errors:
Traceback (most recent call last): Traceback (most recent call last):
IndentationError: expected an indented block after 'try' statement on line 1 IndentationError: expected an indented block after 'try' statement on line 1
>>> try:
... something()
... except:
... pass
Traceback (most recent call last):
IndentationError: expected an indented block after 'except' statement on line 3
>>> try: >>> try:
... something() ... something()
... except A: ... except A:

View File

@ -0,0 +1,3 @@
Raise :exc:`IndentationError` instead of :exc:`SyntaxError` for a bare
``except`` with no following indent. Improve :exc:`SyntaxError` locations for
an un-parenthesized generator used as arguments. Patch by Matthieu Dartiailh.

View File

@ -1145,7 +1145,7 @@ _PyPegen_get_expr_name(expr_ty e)
} }
} }
static inline expr_ty expr_ty
_PyPegen_get_last_comprehension_item(comprehension_ty comprehension) { _PyPegen_get_last_comprehension_item(comprehension_ty comprehension) {
if (comprehension->ifs == NULL || asdl_seq_LEN(comprehension->ifs) == 0) { if (comprehension->ifs == NULL || asdl_seq_LEN(comprehension->ifs) == 0) {
return comprehension->iter; return comprehension->iter;

6
Parser/parser.c generated
View File

@ -18968,7 +18968,7 @@ invalid_arguments_rule(Parser *p)
) )
{ {
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses ',' [args | expression for_if_clauses]")); D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "expression for_if_clauses ',' [args | expression for_if_clauses]"));
_res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , PyPegen_last_item ( b , comprehension_ty ) -> target , "Generator expression must be parenthesized" ); _res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , _PyPegen_get_last_comprehension_item ( PyPegen_last_item ( b , comprehension_ty ) ) , "Generator expression must be parenthesized" );
if (_res == NULL && PyErr_Occurred()) { if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1; p->error_indicator = 1;
p->level--; p->level--;
@ -19061,7 +19061,7 @@ invalid_arguments_rule(Parser *p)
) )
{ {
D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args ',' expression for_if_clauses")); D(fprintf(stderr, "%*c+ invalid_arguments[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "args ',' expression for_if_clauses"));
_res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , asdl_seq_GET ( b , b -> size - 1 ) -> target , "Generator expression must be parenthesized" ); _res = RAISE_SYNTAX_ERROR_KNOWN_RANGE ( a , _PyPegen_get_last_comprehension_item ( PyPegen_last_item ( b , comprehension_ty ) ) , "Generator expression must be parenthesized" );
if (_res == NULL && PyErr_Occurred()) { if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1; p->error_indicator = 1;
p->level--; p->level--;
@ -22190,7 +22190,7 @@ invalid_except_stmt_indent_rule(Parser *p)
) )
{ {
D(fprintf(stderr, "%*c+ invalid_except_stmt_indent[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' ':' NEWLINE !INDENT")); D(fprintf(stderr, "%*c+ invalid_except_stmt_indent[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'except' ':' NEWLINE !INDENT"));
_res = RAISE_SYNTAX_ERROR ( "expected an indented block after except statement on line %d" , a -> lineno ); _res = RAISE_INDENTATION_ERROR ( "expected an indented block after 'except' statement on line %d" , a -> lineno );
if (_res == NULL && PyErr_Occurred()) { if (_res == NULL && PyErr_Occurred()) {
p->error_indicator = 1; p->error_indicator = 1;
p->level--; p->level--;

View File

@ -324,6 +324,7 @@ int _PyPegen_check_barry_as_flufl(Parser *, Token *);
int _PyPegen_check_legacy_stmt(Parser *p, expr_ty t); int _PyPegen_check_legacy_stmt(Parser *p, expr_ty t);
mod_ty _PyPegen_make_module(Parser *, asdl_stmt_seq *); mod_ty _PyPegen_make_module(Parser *, asdl_stmt_seq *);
void *_PyPegen_arguments_parsing_error(Parser *, expr_ty); void *_PyPegen_arguments_parsing_error(Parser *, expr_ty);
expr_ty _PyPegen_get_last_comprehension_item(comprehension_ty comprehension);
void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args, asdl_comprehension_seq *comprehensions); void *_PyPegen_nonparen_genexp_in_call(Parser *p, expr_ty args, asdl_comprehension_seq *comprehensions);
// Parser API // Parser API