Skip to content

Commit 136e20a

Browse files
committed
gh-153568: Share expression parsing with assignment targets
1 parent fb46c67 commit 136e20a

7 files changed

Lines changed: 1887 additions & 3421 deletions

File tree

Grammar/python.gram

Lines changed: 33 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ assignment[stmt_ty]:
157157
_PyAST_AnnAssign(CHECK(expr_ty, _PyPegen_set_expr_context(p, a, Store)), b, c, 1, EXTRA)
158158
) }
159159
| a=('(' b=single_target ')' { b }
160-
| single_subscript_attribute_target) ':' b=expression c=['=' d=annotated_rhs { d }] {
160+
| attribute_or_subscript_target) ':' b=expression c=['=' d=annotated_rhs { d }] {
161161
CHECK_VERSION(stmt_ty, 6, "Variable annotations syntax is", _PyAST_AnnAssign(a, b, c, 0, EXTRA)) }
162162
| a[asdl_expr_seq*]=(z=star_targets '=' { z })+ b=annotated_rhs !'=' tc=[TYPE_COMMENT] {
163163
_PyAST_Assign(a, b, NEW_TYPE_COMMENT(p, tc), EXTRA) }
@@ -740,16 +740,10 @@ star_expression[expr_ty] (memo):
740740

741741
star_named_expressions[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_named_expression+ [','] { a }
742742

743-
star_named_expressions_sequence[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_named_expression_sequence+ [','] { a }
744-
745743
star_named_expression[expr_ty]:
746744
| '*' a=bitwise_or { _PyAST_Starred(a, Load, EXTRA) }
747745
| named_expression
748746

749-
star_named_expression_sequence[expr_ty]:
750-
| invalid_starred_expression_unpacking_sequence
751-
| star_named_expression
752-
753747
assignment_expression[expr_ty]:
754748
| a=NAME ':=' ~ b=expression {
755749
CHECK_VERSION(expr_ty, 8, "Assignment expressions are",
@@ -791,28 +785,16 @@ comparison[expr_ty]:
791785
| bitwise_or
792786

793787
compare_op_bitwise_or_pair[CmpopExprPair*]:
794-
| eq_bitwise_or
795-
| noteq_bitwise_or
796-
| lte_bitwise_or
797-
| lt_bitwise_or
798-
| gte_bitwise_or
799-
| gt_bitwise_or
800-
| notin_bitwise_or
801-
| in_bitwise_or
802-
| isnot_bitwise_or
803-
| is_bitwise_or
804-
805-
eq_bitwise_or[CmpopExprPair*]: '==' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Eq, a) }
806-
noteq_bitwise_or[CmpopExprPair*]:
788+
| '==' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Eq, a) }
807789
| (tok='!=' { _PyPegen_check_barry_as_flufl(p, tok) ? NULL : tok}) a=bitwise_or {_PyPegen_cmpop_expr_pair(p, NotEq, a) }
808-
lte_bitwise_or[CmpopExprPair*]: '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
809-
lt_bitwise_or[CmpopExprPair*]: '<' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Lt, a) }
810-
gte_bitwise_or[CmpopExprPair*]: '>=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, GtE, a) }
811-
gt_bitwise_or[CmpopExprPair*]: '>' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Gt, a) }
812-
notin_bitwise_or[CmpopExprPair*]: 'not' 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, NotIn, a) }
813-
in_bitwise_or[CmpopExprPair*]: 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, In, a) }
814-
isnot_bitwise_or[CmpopExprPair*]: 'is' 'not' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, IsNot, a) }
815-
is_bitwise_or[CmpopExprPair*]: 'is' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Is, a) }
790+
| '<=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, LtE, a) }
791+
| '<' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Lt, a) }
792+
| '>=' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, GtE, a) }
793+
| '>' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Gt, a) }
794+
| 'not' 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, NotIn, a) }
795+
| 'in' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, In, a) }
796+
| 'is' 'not' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, IsNot, a) }
797+
| 'is' a=bitwise_or { _PyPegen_cmpop_expr_pair(p, Is, a) }
816798

817799
# Bitwise operators
818800
# -----------------
@@ -1014,14 +996,20 @@ strings[expr_ty] (memo):
1014996
| a[asdl_expr_seq*]=(fstring|string)+ { _PyPegen_concatenate_strings(p, a, EXTRA) }
1015997
| a[asdl_expr_seq*]=tstring+ { _PyPegen_concatenate_tstrings(p, a, EXTRA) }
1016998

999+
display_items[asdl_expr_seq*]: a[asdl_expr_seq*]=','.display_item+ [','] { a }
1000+
1001+
display_item[expr_ty]:
1002+
| invalid_starred_expression_unpacking_sequence
1003+
| star_named_expression
1004+
10171005
list[expr_ty]:
1018-
| '[' a=[star_named_expressions_sequence] ']' { _PyAST_List(a, Load, EXTRA) }
1006+
| '[' a=[display_items] ']' { _PyAST_List(a, Load, EXTRA) }
10191007

10201008
tuple[expr_ty]:
1021-
| '(' a=[y=star_named_expression_sequence ',' z=[star_named_expressions_sequence] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
1009+
| '(' a=[y=display_item ',' z=[display_items] { _PyPegen_seq_insert_in_front(p, y, z) } ] ')' {
10221010
_PyAST_Tuple(a, Load, EXTRA) }
10231011

1024-
set[expr_ty]: '{' a=star_named_expressions_sequence '}' { _PyAST_Set(a, EXTRA) }
1012+
set[expr_ty]: '{' a=display_items '}' { _PyAST_Set(a, EXTRA) }
10251013

10261014
# Dicts
10271015
# -----
@@ -1088,8 +1076,8 @@ args[expr_ty]:
10881076
EXTRA) }
10891077

10901078
kwargs[asdl_seq*]:
1091-
| a=','.kwarg_or_starred+ ',' b=','.kwarg_or_double_starred+ { _PyPegen_join_sequences(p, a, b) }
1092-
| ','.kwarg_or_starred+
1079+
| a=','.kwarg_or_starred+ b=[',' c=','.kwarg_or_double_starred+ { c }] {
1080+
b ? _PyPegen_join_sequences(p, a, b) : a }
10931081
| ','.kwarg_or_double_starred+
10941082

10951083
starred_expression[expr_ty]:
@@ -1115,72 +1103,36 @@ kwarg_or_double_starred[KeywordOrStarred*]:
11151103
# Generic targets
11161104
# ---------------
11171105

1118-
# NOTE: star_targets may contain *bitwise_or, targets may not.
1106+
# Targets reuse primary. Copying the context leaves its cached values in Load.
11191107
star_targets[expr_ty]:
11201108
| a=star_target !',' { a }
11211109
| a=star_target b=(',' c=star_target { c })* [','] {
11221110
_PyAST_Tuple(CHECK(asdl_expr_seq*, _PyPegen_seq_insert_in_front(p, a, b)), Store, EXTRA) }
11231111

1124-
star_targets_list_seq[asdl_expr_seq*]: a[asdl_expr_seq*]=','.star_target+ [','] { a }
1125-
1126-
star_targets_tuple_seq[asdl_expr_seq*]:
1127-
| a=star_target b=(',' c=star_target { c })+ [','] { (asdl_expr_seq*) _PyPegen_seq_insert_in_front(p, a, b) }
1128-
| a=star_target ',' { (asdl_expr_seq*) _PyPegen_singleton_seq(p, a) }
1129-
11301112
star_target[expr_ty] (memo):
11311113
| '*' a=(!'*' star_target) {
1132-
_PyAST_Starred(CHECK(expr_ty, _PyPegen_set_expr_context(p, a, Store)), Store, EXTRA) }
1133-
| target_with_star_atom
1114+
_PyAST_Starred(a, Store, EXTRA) }
1115+
| target
11341116

1135-
target_with_star_atom[expr_ty] (memo):
1136-
| a=t_primary '.' b=NAME !t_lookahead { _PyAST_Attribute(a, b->v.Name.id, Store, EXTRA) }
1137-
| a=t_primary '[' b=slices ']' !t_lookahead { _PyAST_Subscript(a, b, Store, EXTRA) }
1138-
| star_atom
1139-
1140-
star_atom[expr_ty]:
1141-
| a=NAME { _PyPegen_set_expr_context(p, a, Store) }
1142-
| '(' a=target_with_star_atom ')' { _PyPegen_set_expr_context(p, a, Store) }
1143-
| '(' a=[star_targets_tuple_seq] ')' { _PyAST_Tuple(a, Store, EXTRA) }
1144-
| '[' a=[star_targets_list_seq] ']' { _PyAST_List(a, Store, EXTRA) }
1117+
target[expr_ty] (memo):
1118+
| a=target_value { _PyPegen_make_target(p, a, STAR_TARGETS) }
11451119

11461120
single_target[expr_ty]:
1147-
| single_subscript_attribute_target
1148-
| a=NAME { _PyPegen_set_expr_context(p, a, Store) }
1149-
| '(' a=single_target ')' { a }
1150-
1151-
single_subscript_attribute_target[expr_ty]:
1152-
| a=t_primary '.' b=NAME !t_lookahead { _PyAST_Attribute(a, b->v.Name.id, Store, EXTRA) }
1153-
| a=t_primary '[' b=slices ']' !t_lookahead { _PyAST_Subscript(a, b, Store, EXTRA) }
1154-
1155-
t_primary[expr_ty]:
1156-
| a=t_primary '.' b=NAME &t_lookahead { _PyAST_Attribute(a, b->v.Name.id, Load, EXTRA) }
1157-
| a=t_primary '[' b=slices ']' &t_lookahead { _PyAST_Subscript(a, b, Load, EXTRA) }
1158-
| a=t_primary b=genexp &t_lookahead {
1159-
_PyAST_Call(a, CHECK(asdl_expr_seq*, (asdl_expr_seq*)_PyPegen_singleton_seq(p, b)), NULL, EXTRA) }
1160-
| a=t_primary '(' b=[arguments] ')' &t_lookahead {
1161-
_PyAST_Call(a,
1162-
(b) ? ((expr_ty) b)->v.Call.args : NULL,
1163-
(b) ? ((expr_ty) b)->v.Call.keywords : NULL,
1164-
EXTRA) }
1165-
| a=atom &t_lookahead { a }
1121+
| a=target_value { _PyPegen_make_target(p, a, SINGLE_TARGETS) }
1122+
1123+
attribute_or_subscript_target[expr_ty]:
1124+
| a=target_value { _PyPegen_make_target(p, a, ATTRIBUTE_OR_SUBSCRIPT_TARGETS) }
11661125

1167-
t_lookahead: '(' | '[' | '.'
1126+
target_value[expr_ty]:
1127+
| a=primary !('(' | '[' | '.') { a }
11681128

11691129
# Targets for del statements
11701130
# --------------------------
11711131

11721132
del_targets[asdl_expr_seq*]: a[asdl_expr_seq*]=','.del_target+ [','] { a }
11731133

11741134
del_target[expr_ty] (memo):
1175-
| a=t_primary '.' b=NAME !t_lookahead { _PyAST_Attribute(a, b->v.Name.id, Del, EXTRA) }
1176-
| a=t_primary '[' b=slices ']' !t_lookahead { _PyAST_Subscript(a, b, Del, EXTRA) }
1177-
| del_t_atom
1178-
1179-
del_t_atom[expr_ty]:
1180-
| a=NAME { _PyPegen_set_expr_context(p, a, Del) }
1181-
| '(' a=del_target ')' { _PyPegen_set_expr_context(p, a, Del) }
1182-
| '(' a=[del_targets] ')' { _PyAST_Tuple(a, Del, EXTRA) }
1183-
| '[' a=[del_targets] ']' { _PyAST_List(a, Del, EXTRA) }
1135+
| a=target_value { _PyPegen_make_target(p, a, DEL_TARGETS) }
11841136

11851137
# TYPING ELEMENTS
11861138
# ---------------

Lib/test/test_ast/test_ast.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,45 @@ def test_issue18374_binop_col_offset(self):
696696
self.assertEqual(grandchild_binop.end_col_offset, 3)
697697
self.assertEqual(grandchild_binop.end_lineno, 1)
698698

699+
def test_primary_target_context(self):
700+
for expression in ('factory().field[index]', 'factory()[index].field'):
701+
for context in (ast.Load, ast.Store, ast.Del):
702+
with self.subTest(expression=expression, context=context):
703+
if context is ast.Load:
704+
node = ast.parse(expression).body[0].value
705+
elif context is ast.Store:
706+
node = ast.parse(f'{expression} = value').body[0].targets[0]
707+
else:
708+
node = ast.parse(f'del {expression}').body[0].targets[0]
709+
self.assertIsInstance(node.ctx, context)
710+
for child in ast.walk(node):
711+
if child is not node and hasattr(child, 'ctx'):
712+
self.assertIsInstance(child.ctx, ast.Load)
713+
714+
def test_container_target_context(self):
715+
for expression in ('(a, [b, c])', '[a, (b, c)]', '((a))', '()', '[]'):
716+
for context in (ast.Load, ast.Store, ast.Del):
717+
with self.subTest(expression=expression, context=context):
718+
if context is ast.Load:
719+
source = expression
720+
elif context is ast.Store:
721+
source = f'{expression} = value'
722+
else:
723+
source = f'del {expression}'
724+
statement = ast.parse(source).body[0]
725+
node = statement.value if context is ast.Load else statement.targets[0]
726+
for child in ast.walk(node):
727+
if hasattr(child, 'ctx'):
728+
self.assertIsInstance(child.ctx, context)
729+
self.assertEqual(ast.get_source_segment(source, node),
730+
ast.get_source_segment(expression,
731+
ast.parse(expression, mode='eval').body))
732+
733+
node = ast.parse('[a, *[b, *c]] = value').body[0].targets[0]
734+
for child in ast.walk(node):
735+
if hasattr(child, 'ctx'):
736+
self.assertIsInstance(child.ctx, ast.Store)
737+
699738
def test_issue39579_dotted_name_end_col_offset(self):
700739
tree = ast.parse('@a.b.c\ndef f(): pass')
701740
attr_b = tree.body[0].decorator_list[0].value

Lib/test/test_syntax.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3400,7 +3400,7 @@ def test_invalid_line_continuation_error_position(self):
34003400

34013401
def test_invalid_line_continuation_left_recursive(self):
34023402
# Check bpo-42218: SyntaxErrors following left-recursive rules
3403-
# (t_primary_raw in this case) need to be tested explicitly
3403+
# (primary_raw in this case) need to be tested explicitly
34043404
self._check_error("A.\u018a\\ ",
34053405
"unexpected character after line continuation character")
34063406
self._check_error("A.\u03bc\\\n",
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Speed up parsing by sharing expression rules with assignment and deletion
2+
targets, and simplify comparison and keyword argument rules.

Parser/action_helpers.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,19 @@ _PyPegen_set_expr_context(Parser *p, expr_ty expr, expr_context_ty ctx)
346346
return new;
347347
}
348348

349+
/* Check the target form before copying it into Store or Del context. */
350+
expr_ty
351+
_PyPegen_make_target(Parser *p, expr_ty expr, TARGETS_TYPE targets_type)
352+
{
353+
assert(expr != NULL);
354+
assert(targets_type != FOR_TARGETS);
355+
if (_PyPegen_get_invalid_target(expr, targets_type) != NULL) {
356+
return NULL;
357+
}
358+
return _PyPegen_set_expr_context(
359+
p, expr, targets_type == DEL_TARGETS ? Del : Store);
360+
}
361+
349362
/* Constructs a KeyValuePair that is used when parsing a dict's key value pairs */
350363
KeyValuePair *
351364
_PyPegen_key_value_pair(Parser *p, expr_ty key, expr_ty value)
@@ -1236,6 +1249,13 @@ _PyPegen_get_invalid_target(expr_ty e, TARGETS_TYPE targets_type)
12361249
return NULL;
12371250
}
12381251

1252+
if (targets_type == SINGLE_TARGETS || targets_type == ATTRIBUTE_OR_SUBSCRIPT_TARGETS) {
1253+
if (targets_type == SINGLE_TARGETS && e->kind == Name_kind) {
1254+
return NULL;
1255+
}
1256+
return e->kind == Attribute_kind || e->kind == Subscript_kind ? NULL : e;
1257+
}
1258+
12391259
#define VISIT_CONTAINER(CONTAINER, TYPE) do { \
12401260
Py_ssize_t len = asdl_seq_LEN((CONTAINER)->v.TYPE.elts);\
12411261
for (Py_ssize_t i = 0; i < len; i++) {\

0 commit comments

Comments
 (0)