From 453e96e3020d38cfcaebf82b24cb681c6384fa82 Mon Sep 17 00:00:00 2001 From: Tomas R Date: Tue, 31 Oct 2023 22:02:42 +0100 Subject: gh-111420: Allow type comments in parenthesized `with` statements (#111468) --- Grammar/python.gram | 4 ++-- Lib/test/test_ast.py | 4 ++++ Lib/test/test_type_comments.py | 16 ++++++++++++++++ .../2023-10-29-20-11-21.gh-issue-111420.IUT-GK.rst | 1 + Parser/parser.c | 15 +++++++++------ 5 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2023-10-29-20-11-21.gh-issue-111420.IUT-GK.rst diff --git a/Grammar/python.gram b/Grammar/python.gram index ec964d6..55930b0 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -391,8 +391,8 @@ for_stmt[stmt_ty]: with_stmt[stmt_ty]: | invalid_with_stmt_indent - | 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' b=block { - CHECK_VERSION(stmt_ty, 9, "Parenthesized context managers are", _PyAST_With(a, b, NULL, EXTRA)) } + | 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' tc=[TYPE_COMMENT] b=block { + CHECK_VERSION(stmt_ty, 9, "Parenthesized context managers are", _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA)) } | 'with' a[asdl_withitem_seq*]=','.with_item+ ':' tc=[TYPE_COMMENT] b=block { _PyAST_With(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) } | 'async' 'with' '(' a[asdl_withitem_seq*]=','.with_item+ ','? ')' ':' b=block { diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index fa96e25..69c356e 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -100,6 +100,8 @@ exec_tests = [ # With "with x as y: pass", "with x as y, z as q: pass", + "with (x as y): pass", + "with (x, y): pass", # Raise "raise Exception('string')", # TryExcept @@ -3015,6 +3017,8 @@ exec_results = [ ('Module', [('If', (1, 0, 6, 6), ('Name', (1, 3, 1, 4), 'a', ('Load',)), [('Pass', (2, 2, 2, 6))], [('If', (3, 0, 6, 6), ('Name', (3, 5, 3, 6), 'b', ('Load',)), [('Pass', (4, 2, 4, 6))], [('Pass', (6, 2, 6, 6))])])], []), ('Module', [('With', (1, 0, 1, 17), [('withitem', ('Name', (1, 5, 1, 6), 'x', ('Load',)), ('Name', (1, 10, 1, 11), 'y', ('Store',)))], [('Pass', (1, 13, 1, 17))], None)], []), ('Module', [('With', (1, 0, 1, 25), [('withitem', ('Name', (1, 5, 1, 6), 'x', ('Load',)), ('Name', (1, 10, 1, 11), 'y', ('Store',))), ('withitem', ('Name', (1, 13, 1, 14), 'z', ('Load',)), ('Name', (1, 18, 1, 19), 'q', ('Store',)))], [('Pass', (1, 21, 1, 25))], None)], []), +('Module', [('With', (1, 0, 1, 19), [('withitem', ('Name', (1, 6, 1, 7), 'x', ('Load',)), ('Name', (1, 11, 1, 12), 'y', ('Store',)))], [('Pass', (1, 15, 1, 19))], None)], []), +('Module', [('With', (1, 0, 1, 17), [('withitem', ('Name', (1, 6, 1, 7), 'x', ('Load',)), None), ('withitem', ('Name', (1, 9, 1, 10), 'y', ('Load',)), None)], [('Pass', (1, 13, 1, 17))], None)], []), ('Module', [('Raise', (1, 0, 1, 25), ('Call', (1, 6, 1, 25), ('Name', (1, 6, 1, 15), 'Exception', ('Load',)), [('Constant', (1, 16, 1, 24), 'string', None)], []), None)], []), ('Module', [('Try', (1, 0, 4, 6), [('Pass', (2, 2, 2, 6))], [('ExceptHandler', (3, 0, 4, 6), ('Name', (3, 7, 3, 16), 'Exception', ('Load',)), None, [('Pass', (4, 2, 4, 6))])], [], [])], []), ('Module', [('Try', (1, 0, 4, 6), [('Pass', (2, 2, 2, 6))], [], [], [('Pass', (4, 2, 4, 6))])], []), diff --git a/Lib/test/test_type_comments.py b/Lib/test/test_type_comments.py index 9a11fab..5a911da 100644 --- a/Lib/test/test_type_comments.py +++ b/Lib/test/test_type_comments.py @@ -66,6 +66,14 @@ with context() as a: # type: int pass """ +parenthesized_withstmt = """\ +with (a as b): # type: int + pass + +with (a, b): # type: int + pass +""" + vardecl = """\ a = 0 # type: int """ @@ -300,6 +308,14 @@ class TypeCommentTests(unittest.TestCase): tree = self.classic_parse(withstmt) self.assertEqual(tree.body[0].type_comment, None) + def test_parenthesized_withstmt(self): + for tree in self.parse_all(parenthesized_withstmt, minver=9): + self.assertEqual(tree.body[0].type_comment, "int") + self.assertEqual(tree.body[1].type_comment, "int") + tree = self.classic_parse(parenthesized_withstmt) + self.assertEqual(tree.body[0].type_comment, None) + self.assertEqual(tree.body[1].type_comment, None) + def test_vardecl(self): for tree in self.parse_all(vardecl): self.assertEqual(tree.body[0].type_comment, "int") diff --git a/Misc/NEWS.d/next/Core and Builtins/2023-10-29-20-11-21.gh-issue-111420.IUT-GK.rst b/Misc/NEWS.d/next/Core and Builtins/2023-10-29-20-11-21.gh-issue-111420.IUT-GK.rst new file mode 100644 index 0000000..6646ecf --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2023-10-29-20-11-21.gh-issue-111420.IUT-GK.rst @@ -0,0 +1 @@ +Allow type comments in parenthesized ``with`` statements diff --git a/Parser/parser.c b/Parser/parser.c index f367cf9..ca8e9d0 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -6483,7 +6483,7 @@ for_stmt_rule(Parser *p) // with_stmt: // | invalid_with_stmt_indent -// | 'with' '(' ','.with_item+ ','? ')' ':' block +// | 'with' '(' ','.with_item+ ','? ')' ':' TYPE_COMMENT? block // | 'with' ','.with_item+ ':' TYPE_COMMENT? block // | 'async' 'with' '(' ','.with_item+ ','? ')' ':' block // | 'async' 'with' ','.with_item+ ':' TYPE_COMMENT? block @@ -6528,12 +6528,12 @@ with_stmt_rule(Parser *p) D(fprintf(stderr, "%*c%s with_stmt[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_with_stmt_indent")); } - { // 'with' '(' ','.with_item+ ','? ')' ':' block + { // 'with' '(' ','.with_item+ ','? ')' ':' TYPE_COMMENT? block if (p->error_indicator) { p->level--; return NULL; } - D(fprintf(stderr, "%*c> with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' block")); + D(fprintf(stderr, "%*c> with_stmt[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' TYPE_COMMENT? block")); Token * _keyword; Token * _literal; Token * _literal_1; @@ -6542,6 +6542,7 @@ with_stmt_rule(Parser *p) UNUSED(_opt_var); // Silence compiler warnings asdl_withitem_seq* a; asdl_stmt_seq* b; + void *tc; if ( (_keyword = _PyPegen_expect_token(p, 629)) // token='with' && @@ -6555,10 +6556,12 @@ with_stmt_rule(Parser *p) && (_literal_2 = _PyPegen_expect_token(p, 11)) // token=':' && + (tc = _PyPegen_expect_token(p, TYPE_COMMENT), !p->error_indicator) // TYPE_COMMENT? + && (b = block_rule(p)) // block ) { - D(fprintf(stderr, "%*c+ with_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' block")); + D(fprintf(stderr, "%*c+ with_stmt[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' TYPE_COMMENT? block")); Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); if (_token == NULL) { p->level--; @@ -6568,7 +6571,7 @@ with_stmt_rule(Parser *p) UNUSED(_end_lineno); // Only used by EXTRA macro int _end_col_offset = _token->end_col_offset; UNUSED(_end_col_offset); // Only used by EXTRA macro - _res = CHECK_VERSION ( stmt_ty , 9 , "Parenthesized context managers are" , _PyAST_With ( a , b , NULL , EXTRA ) ); + _res = CHECK_VERSION ( stmt_ty , 9 , "Parenthesized context managers are" , _PyAST_With ( a , b , NEW_TYPE_COMMENT ( p , tc ) , EXTRA ) ); if (_res == NULL && PyErr_Occurred()) { p->error_indicator = 1; p->level--; @@ -6578,7 +6581,7 @@ with_stmt_rule(Parser *p) } p->mark = _mark; D(fprintf(stderr, "%*c%s with_stmt[%d-%d]: %s failed!\n", p->level, ' ', - p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' block")); + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "'with' '(' ','.with_item+ ','? ')' ':' TYPE_COMMENT? block")); } { // 'with' ','.with_item+ ':' TYPE_COMMENT? block if (p->error_indicator) { -- cgit v0.12