From 2819e98d1048238bf94dc2ba88ae2455d01861bc Mon Sep 17 00:00:00 2001 From: Carl Friedrich Bolz-Tereick Date: Tue, 9 Nov 2021 15:03:32 +0100 Subject: bpo-45764: improve error message when missing '(' after 'def' (GH-29484) to achieve this, change the grammar to expect the '(' token after 'def' NAME. Automerge-Triggered-By: GH:pablogsal --- Grammar/python.gram | 4 ++-- Lib/test/test_syntax.py | 11 ++++++++++ .../2021-11-09-12-19-22.bpo-45764.8RLhWL.rst | 9 ++++++++ Parser/parser.c | 24 +++++++++++----------- 4 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2021-11-09-12-19-22.bpo-45764.8RLhWL.rst diff --git a/Grammar/python.gram b/Grammar/python.gram index ffe035f..d1901a5 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -264,11 +264,11 @@ function_def[stmt_ty]: function_def_raw[stmt_ty]: | invalid_def_raw - | 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block { + | 'def' n=NAME &&'(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block { _PyAST_FunctionDef(n->v.Name.id, (params) ? params : CHECK(arguments_ty, _PyPegen_empty_arguments(p)), b, NULL, a, NEW_TYPE_COMMENT(p, tc), EXTRA) } - | ASYNC 'def' n=NAME '(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block { + | ASYNC 'def' n=NAME &&'(' params=[params] ')' a=['->' z=expression { z }] &&':' tc=[func_type_comment] b=block { CHECK_VERSION( stmt_ty, 5, diff --git a/Lib/test/test_syntax.py b/Lib/test/test_syntax.py index 014a30a..65d18e4 100644 --- a/Lib/test/test_syntax.py +++ b/Lib/test/test_syntax.py @@ -898,6 +898,17 @@ leading to spurious errors. Traceback (most recent call last): SyntaxError: cannot assign to attribute here. Maybe you meant '==' instead of '='? + +Missing parens after function definition + + >>> def f: + Traceback (most recent call last): + SyntaxError: expected '(' + + >>> async def f: + Traceback (most recent call last): + SyntaxError: expected '(' + Custom error messages for try blocks that are not followed by except/finally >>> try: diff --git a/Misc/NEWS.d/next/Core and Builtins/2021-11-09-12-19-22.bpo-45764.8RLhWL.rst b/Misc/NEWS.d/next/Core and Builtins/2021-11-09-12-19-22.bpo-45764.8RLhWL.rst new file mode 100644 index 0000000..d30b6a4 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2021-11-09-12-19-22.bpo-45764.8RLhWL.rst @@ -0,0 +1,9 @@ +The parser now gives a better error message when leaving out the opening +parenthesis ``(`` after a ``def``-statement:: + + >>> def f: + File "", line 1 + def f: + ^ + SyntaxError: expected '(' + diff --git a/Parser/parser.c b/Parser/parser.c index 8ef7980..b508c1d 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -4157,8 +4157,8 @@ function_def_rule(Parser *p) // function_def_raw: // | invalid_def_raw -// | 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block -// | ASYNC 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block +// | 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block +// | ASYNC 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block static stmt_ty function_def_raw_rule(Parser *p) { @@ -4197,12 +4197,12 @@ function_def_raw_rule(Parser *p) D(fprintf(stderr, "%*c%s function_def_raw[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_def_raw")); } - { // 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block + { // 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block if (p->error_indicator) { D(p->level--); return NULL; } - D(fprintf(stderr, "%*c> function_def_raw[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + D(fprintf(stderr, "%*c> function_def_raw[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); Token * _keyword; Token * _literal; Token * _literal_1; @@ -4217,7 +4217,7 @@ function_def_raw_rule(Parser *p) && (n = _PyPegen_name_token(p)) // NAME && - (_literal = _PyPegen_expect_token(p, 7)) // token='(' + (_literal = _PyPegen_expect_forced_token(p, 7, "(")) // forced_token='(' && (params = params_rule(p), !p->error_indicator) // params? && @@ -4232,7 +4232,7 @@ function_def_raw_rule(Parser *p) (b = block_rule(p)) // block ) { - D(fprintf(stderr, "%*c+ function_def_raw[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + D(fprintf(stderr, "%*c+ function_def_raw[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); if (_token == NULL) { D(p->level--); @@ -4252,14 +4252,14 @@ function_def_raw_rule(Parser *p) } p->mark = _mark; D(fprintf(stderr, "%*c%s function_def_raw[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); } - { // ASYNC 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block + { // ASYNC 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block if (p->error_indicator) { D(p->level--); return NULL; } - D(fprintf(stderr, "%*c> function_def_raw[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + D(fprintf(stderr, "%*c> function_def_raw[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "ASYNC 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); Token * _keyword; Token * _literal; Token * _literal_1; @@ -4277,7 +4277,7 @@ function_def_raw_rule(Parser *p) && (n = _PyPegen_name_token(p)) // NAME && - (_literal = _PyPegen_expect_token(p, 7)) // token='(' + (_literal = _PyPegen_expect_forced_token(p, 7, "(")) // forced_token='(' && (params = params_rule(p), !p->error_indicator) // params? && @@ -4292,7 +4292,7 @@ function_def_raw_rule(Parser *p) (b = block_rule(p)) // block ) { - D(fprintf(stderr, "%*c+ function_def_raw[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "ASYNC 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + D(fprintf(stderr, "%*c+ function_def_raw[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "ASYNC 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); if (_token == NULL) { D(p->level--); @@ -4312,7 +4312,7 @@ function_def_raw_rule(Parser *p) } p->mark = _mark; D(fprintf(stderr, "%*c%s function_def_raw[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "ASYNC 'def' NAME '(' params? ')' ['->' expression] &&':' func_type_comment? block")); + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "ASYNC 'def' NAME &&'(' params? ')' ['->' expression] &&':' func_type_comment? block")); } _res = NULL; done: -- cgit v0.12