diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aafe40804c9b59..4ada9ffe995011 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -149,7 +149,7 @@ jobs: check_generated_files: name: 'Check if generated files are up to date' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -299,7 +299,7 @@ jobs: build_ubuntu: name: 'Ubuntu' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -366,7 +366,7 @@ jobs: build_ubuntu_ssltests: name: 'Ubuntu SSL tests with OpenSSL' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -420,7 +420,7 @@ jobs: test_hypothesis: name: "Hypothesis tests on Ubuntu" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true' @@ -529,7 +529,7 @@ jobs: build_asan: name: 'Address sanitizer' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' diff --git a/.github/workflows/build_min.yml b/.github/workflows/build_min.yml index 4c5c75e6b41e9c..aac99bc8f62374 100644 --- a/.github/workflows/build_min.yml +++ b/.github/workflows/build_min.yml @@ -134,7 +134,7 @@ jobs: check_generated_files: name: 'Check if generated files are up to date' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -286,7 +286,7 @@ jobs: build_ubuntu: name: 'Ubuntu' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' @@ -355,7 +355,7 @@ jobs: # # build_ubuntu_ssltests: # name: 'Ubuntu SSL tests with OpenSSL' - # runs-on: ubuntu-20.04 + # runs-on: ubuntu-22.04 # timeout-minutes: 60 # needs: check_source # if: needs.check_source.outputs.run_tests == 'true' @@ -409,7 +409,7 @@ jobs: test_hypothesis: name: "Hypothesis tests on Ubuntu" - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true' @@ -518,7 +518,7 @@ jobs: build_asan: name: 'Address sanitizer' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 timeout-minutes: 60 needs: check_source if: needs.check_source.outputs.run_tests == 'true' diff --git a/Grammar/python.gram b/Grammar/python.gram index f88dc2bdd92327..da615cbf1e483b 100644 --- a/Grammar/python.gram +++ b/Grammar/python.gram @@ -674,6 +674,7 @@ expressions[expr_ty]: expression[expr_ty] (memo): | invalid_expression | invalid_legacy_expression + | move_expr | a=disjunction 'if' b=disjunction 'else' c=expression { _PyAST_IfExp(b, a, c, EXTRA) } | disjunction | lambdef @@ -908,6 +909,19 @@ lambda_param_maybe_default[NameDefaultPair*]: | a=lambda_param c=default? &':' { _PyPegen_name_default_pair(p, a, c, NULL) } lambda_param[arg_ty]: a=NAME { _PyAST_arg(a->v.Name.id, NULL, NULL, EXTRA) } +# Move Keyword +# ------------- +move_expr[expr_ty]: + | "move" b=move_source { _PyAST_Move(_PyPegen_set_expr_context(p, b, Del), EXTRA); } + +# Very similar to the definition of `del_target` with the difference, that move +# also reads the value and only one value can be moved at a time, while delete +# allows the deletion of multiple locations at once. +move_source[expr_ty]: + | a=t_primary '.' b=NAME !t_lookahead { _PyAST_Attribute(a, b->v.Name.id, Del, EXTRA) } + | a=t_primary '[' b=slices ']' !t_lookahead { _PyAST_Subscript(a, b, Del, EXTRA) } + | a=NAME { _PyPegen_set_expr_context(p, a, Del) } + # LITERALS # ======== diff --git a/Include/internal/pycore_ast.h b/Include/internal/pycore_ast.h index b568902bb1e381..fec90c144ca775 100644 --- a/Include/internal/pycore_ast.h +++ b/Include/internal/pycore_ast.h @@ -359,11 +359,12 @@ struct _stmt { enum _expr_kind {BoolOp_kind=1, NamedExpr_kind=2, BinOp_kind=3, UnaryOp_kind=4, Lambda_kind=5, IfExp_kind=6, Dict_kind=7, Set_kind=8, ListComp_kind=9, SetComp_kind=10, DictComp_kind=11, - GeneratorExp_kind=12, Await_kind=13, Yield_kind=14, - YieldFrom_kind=15, Compare_kind=16, Call_kind=17, - FormattedValue_kind=18, JoinedStr_kind=19, Constant_kind=20, - Attribute_kind=21, Subscript_kind=22, Starred_kind=23, - Name_kind=24, List_kind=25, Tuple_kind=26, Slice_kind=27}; + GeneratorExp_kind=12, Move_kind=13, Await_kind=14, + Yield_kind=15, YieldFrom_kind=16, Compare_kind=17, + Call_kind=18, FormattedValue_kind=19, JoinedStr_kind=20, + Constant_kind=21, Attribute_kind=22, Subscript_kind=23, + Starred_kind=24, Name_kind=25, List_kind=26, Tuple_kind=27, + Slice_kind=28}; struct _expr { enum _expr_kind kind; union { @@ -429,6 +430,10 @@ struct _expr { asdl_comprehension_seq *generators; } GeneratorExp; + struct { + expr_ty source; + } Move; + struct { expr_ty value; } Await; @@ -802,6 +807,8 @@ expr_ty _PyAST_DictComp(expr_ty key, expr_ty value, asdl_comprehension_seq * expr_ty _PyAST_GeneratorExp(expr_ty elt, asdl_comprehension_seq * generators, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); +expr_ty _PyAST_Move(expr_ty source, int lineno, int col_offset, int end_lineno, + int end_col_offset, PyArena *arena); expr_ty _PyAST_Await(expr_ty value, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena); expr_ty _PyAST_Yield(expr_ty value, int lineno, int col_offset, int end_lineno, diff --git a/Include/internal/pycore_ast_state.h b/Include/internal/pycore_ast_state.h index 0c0d53f3e5d7e9..00eba4443ed036 100644 --- a/Include/internal/pycore_ast_state.h +++ b/Include/internal/pycore_ast_state.h @@ -105,6 +105,7 @@ struct ast_state { PyObject *Mod_singleton; PyObject *Mod_type; PyObject *Module_type; + PyObject *Move_type; PyObject *Mult_singleton; PyObject *Mult_type; PyObject *Name_type; @@ -237,6 +238,7 @@ struct ast_state { PyObject *right; PyObject *simple; PyObject *slice; + PyObject *source; PyObject *step; PyObject *stmt_type; PyObject *subject; diff --git a/Lib/ast.py b/Lib/ast.py index 07044706dc3608..a240e3931cdd88 100644 --- a/Lib/ast.py +++ b/Lib/ast.py @@ -1681,6 +1681,10 @@ def visit_Lambda(self, node): self.set_precedence(_Precedence.TEST, node.body) self.traverse(node.body) + def visit_Move(self, node): + self.write("move ") + self.traverse(node.source) + def visit_alias(self, node): self.write(node.name) if node.asname: diff --git a/Lib/keyword.py b/Lib/keyword.py index e22c837835e740..0980c0b302b6e6 100644 --- a/Lib/keyword.py +++ b/Lib/keyword.py @@ -57,6 +57,7 @@ '_', 'case', 'match', + 'move', 'type' ] diff --git a/Lib/test/test_move.py b/Lib/test/test_move.py new file mode 100644 index 00000000000000..974014c6cfe2d6 --- /dev/null +++ b/Lib/test/test_move.py @@ -0,0 +1,300 @@ +import unittest +import sys +import inspect + +global_var_1 = 0 +global_var_2 = 2 + +# This class tests the move of local variables, these are called `NAME` +# in Pythons grammar. +class TestNameMoves(unittest.TestCase): + + def test_move_local_basic(self): + x = 42 + y = move x + self.assertEqual(y, 42) + # Check that x has been deleted from locals + self.assertNotIn("x", locals()) + + def test_move_local_as_arg(self): + def take_one(x): + self.assertEqual(x, "Super secret value") + + y = "Super secret value" + take_one(move y) + + # Check that y has been deleted from locals + self.assertNotIn("y", locals()) + + def test_read_local_before_move(self): + def take_several(cp, mv): + self.assertEqual(cp, "Mister Flesh Stick") + self.assertEqual(mv, "Mister Flesh Stick") + + x = "Mister Flesh Stick" + + # Test that any reads of `x` before `move x` are valid + take_several(x, move x) + + # Check that `x` has been removed + self.assertNotIn("x", locals()) + + def test_read_local_after_move(self): + def take_several(moved_val, was_found_after_move): + self.assertEqual(moved_val, "Sir Reginald") + self.assertFalse(was_found_after_move) + + x = "Sir Reginald" + # Test that x is no longer in `locals()` after the move + take_several(move x, "x" in locals()) + + # Checks that `move` nulls the previous reference before a function is + # called with the moved value. This property is checked from the ref count + # apposed to just `"x" in locals()` like above. + def test_move_local_deletes_ref_before_func_call(self): + def check_unique(self, unique_value): + # Ref count should be 2, one from the `unique_value` variable and + # one from the argument being passed in + self.assertEqual(sys.getrefcount(unique_value), 2) + # Moving it into `sys.getrefcount()` should give a refcount of 1 + self.assertEqual(sys.getrefcount(move unique_value), 1) + + # Warning, the type of the object can influence the ref count. + # A string will have additional ref counts when used as a function + # argument. A dictionary will not add any magical references. + source = {} + + # Move the value into the uniqueness check + check_unique(self, move source) + # Check that `x` has been removed + self.assertNotIn("x", locals()) + +class TestGlobalMoves(unittest.TestCase): + # Check that we can move global variables and that this move is + # visible to other functions. + def test_move_global(self): + def setup(): + global global_var_1 + global global_var_2 + global_var_1 = "Magic value I" + global_var_2 = None + def move_globals(): + global global_var_1 + global global_var_2 + global_var_2 = move global_var_1 + def check_globals(self): + global global_var_2 + self.assertEqual(global_var_2, "Magic value I") + + setup() + move_globals() + check_globals(self) + +# This class tests the move of attributes +class TestAttributeMoves(unittest.TestCase): + # A simple class to add attributes to + class A: + pass + # A simple class with slots + class BWithSlots: + __slots__ = ["a", "b"] + + def test_move_attribute_basic(self): + a = self.A() + a.f = 12 + + f = move a.f + self.assertEqual(f, 12) + + # Check that the attribute has been deleted + with self.assertRaises(AttributeError): + x = a.f + + def test_move_slot(self): + b = self.BWithSlots() + b.a = 12 + + a = move b.a + self.assertEqual(a, 12) + + # Check that the attribute has been deleted + with self.assertRaises(AttributeError): + x = b.a + + # Check that we can reassign to the slot we moved + b.a = 13 + self.assertEqual(b.a, 13) + + def test_move_attribute_as_arg(self): + def take_one(x): + self.assertEqual(x, "Claptrap") + + a = self.A() + a.name = "Claptrap" + take_one(move a.name) + + # Check that the attribute has been deleted + with self.assertRaises(AttributeError): + x = a.name + + def test_read_attribute_before_move(self): + def take_several(cp, mv): + self.assertEqual(cp, "Dr. Zed") + self.assertEqual(mv, "Dr. Zed") + + a = self.A() + a.name = "Dr. Zed" + + # Test that any reads of `a.name` before `move a.name` are valid + take_several(a.name, move a.name) + + # Check that the attribute has been deleted + with self.assertRaises(AttributeError): + x = a.name + + def test_read_attribute_after_move(self): + def take_several(moved_val, was_found_after_move): + self.assertEqual(moved_val, "Scooter") + self.assertFalse(was_found_after_move) + + a = self.A() + a.name = "Scooter" + + # Test that x is no longer in `a` after the move + take_several(move a.name, "name" in dir(a)) + + # Checks that `move` removes the previous reference before a function is + # called with the moved value. This property is checked by the ref count. + def test_move_attribute_deletes_ref_before_func_call(self): + def check_unique(unique_value): + # Ref count should be 2, one from the `unique_value` variable and + # one from the argument being passed in + self.assertEqual(sys.getrefcount(unique_value), 2) + # Moving it into `sys.getrefcount()` should give a refcount of 1 + self.assertEqual(sys.getrefcount(move unique_value), 1) + + # Warning, the type of the object can influence the ref count. + # A string will have additional ref counts when used as a function + + a = self.A() + a.source = {} + + # Move the value into the uniqueness check + check_unique(move a.source) + # Check that the attribute has been deleted + with self.assertRaises(AttributeError): + x = a.source + +# This class tests the move of list items, also called subscript in Python's +# grammar +class TestListMoves(unittest.TestCase): + def test_move_list_item_from_start(self): + lst = [1, 2, 3, 4, 5] + + # Test moving the first element + first = move lst[0] + self.assertEqual(first, 1) + + # Check that the value has been removed from the list + self.assertEqual(lst, [2, 3, 4, 5]) + + def test_move_list_item_from_end(self): + lst = [1, 2, 3, 4, 5] + + # Test moving the first element + last = move lst[-1] + self.assertEqual(last, 5) + + # Check that the value has been removed from the list + self.assertEqual(lst, [1, 2, 3, 4]) + + def test_move_list_drain(self): + lst = [1, 2, 3, 4, 5] + self.assertEqual(1, move lst[0]) + self.assertEqual(2, move lst[0]) + self.assertEqual(3, move lst[0]) + self.assertEqual(4, move lst[0]) + self.assertEqual(5, move lst[0]) + self.assertEqual(len(lst), 0) + + def test_move_list_back_into_list(self): + lst = [1, 2, 3] + + # This should take the value '1' and then replace the new value in slot + # `lst[0]` + lst[0] = move lst[0] + + self.assertEqual(lst[0], 1) + self.assertEqual(lst[1], 3) + + def test_move_list_range(self): + lst = [1, 2, 3, 4, 5] + + sub = move lst[1:4] + self.assertEqual(lst, [1, 5]) + self.assertEqual(sub, [2, 3, 4]) + + def test_move_list_all_items(self): + lst = [1, 2, 3, 4, 5] + + sub = move lst[:] + self.assertEqual(lst, []) + self.assertEqual(sub, [1, 2, 3, 4, 5]) + + def test_move_list_with_stride(self): + odd = [1, 2, 3, 4, 5, 6] + + even = move odd[1::2] + self.assertEqual(odd, [1, 3, 5]) + self.assertEqual(even, [2, 4, 6]) + + def test_move_list_reversed(self): + lst = [1, 2, 3, 4, 5] + + sub = move lst[::-1] + self.assertEqual(lst, []) + self.assertEqual(sub, [5, 4, 3, 2, 1]) + +# This class tests the move of dictionary items. +class TestDictMoves(unittest.TestCase): + def test_move_dict_item(self): + map = {"1": "one", "2": "two", "3": "NaN"} + + name = move map["2"] + self.assertEqual(name, "two") + self.assertEqual(len(map), 2) + self.assertNotIn("2", map) + +# Move is a soft keyword, this should allow existing values and functions called +# `move` to remain valid. Let's test this: +class TestMoveKeywordSoftness(unittest.TestCase): + def test_move_function_name(self): + def move(move): + self.assertEqual(move, "value") + value = "value" + # Call the `move` function with `move` as an argument + move(value) + + self.assertEqual(value, "value") + + # Call the `move` function with the value moved out of `move` + move(move value) + # Check that `value` has been removed + self.assertNotIn("value", locals()) + + def test_move_variable_name(self): + def take_one(move): + self.assertEqual(move, "value") + move = "value" + + # Call the `move` function with `move` as an argument + take_one(move) + self.assertEqual(move, "value") + + # Call the `move` function with the value moved out of `move` + take_one(move move) + # Check that `move` has been removed + self.assertNotIn("move", locals()) + +if __name__ == "__main__": + unittest.main() diff --git a/Lib/test/test_unparse.py b/Lib/test/test_unparse.py index bdf7b0588bee67..3923df0618d663 100644 --- a/Lib/test/test_unparse.py +++ b/Lib/test/test_unparse.py @@ -466,6 +466,8 @@ def test_type_ignore(self): ): self.check_ast_roundtrip(statement, type_comments=True) + def test_move_expression(self): + self.check_ast_roundtrip("x = move a") class CosmeticTestCase(ASTTestCase): """Test if there are cosmetic issues caused by unnecessary additions""" diff --git a/Parser/Python.asdl b/Parser/Python.asdl index 0d154867276c36..d8419daecb60d0 100644 --- a/Parser/Python.asdl +++ b/Parser/Python.asdl @@ -69,6 +69,8 @@ module Python | SetComp(expr elt, comprehension* generators) | DictComp(expr key, expr value, comprehension* generators) | GeneratorExp(expr elt, comprehension* generators) + -- the grammar constrains where move can occur and the source expression + | Move(expr source) -- the grammar constrains where yield expressions can occur | Await(expr value) | Yield(expr? value) diff --git a/Parser/action_helpers.c b/Parser/action_helpers.c index e68a9cac25908c..e02debd0168147 100644 --- a/Parser/action_helpers.c +++ b/Parser/action_helpers.c @@ -1041,6 +1041,8 @@ _PyPegen_get_expr_name(expr_ty e) return "tuple"; case Lambda_kind: return "lambda"; + case Move_kind: + return "move expression"; case Call_kind: return "function call"; case BoolOp_kind: diff --git a/Parser/parser.c b/Parser/parser.c index 5d2adf7417b2ec..a2ed6506344ad1 100644 --- a/Parser/parser.c +++ b/Parser/parser.c @@ -75,6 +75,7 @@ static char *soft_keywords[] = { "_", "case", "match", + "move", "type", NULL, }; @@ -230,373 +231,375 @@ static char *soft_keywords[] = { #define lambda_param_with_default_type 1149 #define lambda_param_maybe_default_type 1150 #define lambda_param_type 1151 -#define fstring_middle_type 1152 -#define fstring_replacement_field_type 1153 -#define fstring_conversion_type 1154 -#define fstring_full_format_spec_type 1155 -#define fstring_format_spec_type 1156 -#define string_type 1157 -#define strings_type 1158 -#define list_type 1159 -#define tuple_type 1160 -#define set_type 1161 -#define dict_type 1162 -#define double_starred_kvpairs_type 1163 -#define double_starred_kvpair_type 1164 -#define kvpair_type 1165 -#define for_if_clauses_type 1166 -#define for_if_clause_type 1167 -#define listcomp_type 1168 -#define setcomp_type 1169 -#define genexp_type 1170 -#define dictcomp_type 1171 -#define arguments_type 1172 -#define args_type 1173 -#define kwargs_type 1174 -#define starred_expression_type 1175 -#define kwarg_or_starred_type 1176 -#define kwarg_or_double_starred_type 1177 -#define star_targets_type 1178 -#define star_targets_list_seq_type 1179 -#define star_targets_tuple_seq_type 1180 -#define star_target_type 1181 -#define target_with_star_atom_type 1182 -#define star_atom_type 1183 -#define single_target_type 1184 -#define single_subscript_attribute_target_type 1185 -#define t_primary_type 1186 // Left-recursive -#define t_lookahead_type 1187 -#define del_targets_type 1188 -#define del_target_type 1189 -#define del_t_atom_type 1190 -#define type_expressions_type 1191 -#define func_type_comment_type 1192 -#define invalid_arguments_type 1193 -#define invalid_kwarg_type 1194 -#define expression_without_invalid_type 1195 -#define invalid_legacy_expression_type 1196 -#define invalid_expression_type 1197 -#define invalid_named_expression_type 1198 -#define invalid_assignment_type 1199 -#define invalid_ann_assign_target_type 1200 -#define invalid_del_stmt_type 1201 -#define invalid_block_type 1202 -#define invalid_comprehension_type 1203 -#define invalid_dict_comprehension_type 1204 -#define invalid_parameters_type 1205 -#define invalid_default_type 1206 -#define invalid_star_etc_type 1207 -#define invalid_kwds_type 1208 -#define invalid_parameters_helper_type 1209 -#define invalid_lambda_parameters_type 1210 -#define invalid_lambda_parameters_helper_type 1211 -#define invalid_lambda_star_etc_type 1212 -#define invalid_lambda_kwds_type 1213 -#define invalid_double_type_comments_type 1214 -#define invalid_with_item_type 1215 -#define invalid_for_target_type 1216 -#define invalid_group_type 1217 -#define invalid_import_type 1218 -#define invalid_import_from_targets_type 1219 -#define invalid_with_stmt_type 1220 -#define invalid_with_stmt_indent_type 1221 -#define invalid_try_stmt_type 1222 -#define invalid_except_stmt_type 1223 -#define invalid_finally_stmt_type 1224 -#define invalid_except_stmt_indent_type 1225 -#define invalid_except_star_stmt_indent_type 1226 -#define invalid_match_stmt_type 1227 -#define invalid_case_block_type 1228 -#define invalid_as_pattern_type 1229 -#define invalid_class_pattern_type 1230 -#define invalid_class_argument_pattern_type 1231 -#define invalid_if_stmt_type 1232 -#define invalid_elif_stmt_type 1233 -#define invalid_else_stmt_type 1234 -#define invalid_while_stmt_type 1235 -#define invalid_for_stmt_type 1236 -#define invalid_def_raw_type 1237 -#define invalid_class_def_raw_type 1238 -#define invalid_double_starred_kvpairs_type 1239 -#define invalid_kvpair_type 1240 -#define invalid_starred_expression_type 1241 -#define invalid_replacement_field_type 1242 -#define invalid_conversion_character_type 1243 -#define _loop0_1_type 1244 -#define _loop0_2_type 1245 -#define _loop0_3_type 1246 -#define _loop1_4_type 1247 -#define _loop0_6_type 1248 -#define _gather_5_type 1249 -#define _tmp_7_type 1250 -#define _tmp_8_type 1251 -#define _tmp_9_type 1252 -#define _tmp_10_type 1253 -#define _tmp_11_type 1254 -#define _tmp_12_type 1255 -#define _tmp_13_type 1256 -#define _tmp_14_type 1257 -#define _loop1_15_type 1258 -#define _tmp_16_type 1259 -#define _tmp_17_type 1260 -#define _tmp_18_type 1261 -#define _loop0_20_type 1262 -#define _gather_19_type 1263 -#define _loop0_22_type 1264 -#define _gather_21_type 1265 -#define _tmp_23_type 1266 -#define _tmp_24_type 1267 -#define _loop0_25_type 1268 -#define _loop1_26_type 1269 -#define _loop0_28_type 1270 -#define _gather_27_type 1271 -#define _tmp_29_type 1272 -#define _loop0_31_type 1273 -#define _gather_30_type 1274 -#define _tmp_32_type 1275 -#define _loop1_33_type 1276 -#define _tmp_34_type 1277 -#define _tmp_35_type 1278 -#define _tmp_36_type 1279 -#define _loop0_37_type 1280 -#define _loop0_38_type 1281 -#define _loop0_39_type 1282 -#define _loop1_40_type 1283 -#define _loop0_41_type 1284 -#define _loop1_42_type 1285 -#define _loop1_43_type 1286 -#define _loop1_44_type 1287 -#define _loop0_45_type 1288 -#define _loop1_46_type 1289 -#define _loop0_47_type 1290 -#define _loop1_48_type 1291 -#define _loop0_49_type 1292 -#define _loop0_50_type 1293 -#define _loop1_51_type 1294 -#define _loop0_53_type 1295 -#define _gather_52_type 1296 -#define _loop0_55_type 1297 -#define _gather_54_type 1298 -#define _loop0_57_type 1299 -#define _gather_56_type 1300 -#define _loop0_59_type 1301 -#define _gather_58_type 1302 -#define _tmp_60_type 1303 -#define _loop1_61_type 1304 -#define _loop1_62_type 1305 -#define _tmp_63_type 1306 -#define _tmp_64_type 1307 -#define _loop1_65_type 1308 -#define _loop0_67_type 1309 -#define _gather_66_type 1310 -#define _tmp_68_type 1311 -#define _tmp_69_type 1312 -#define _tmp_70_type 1313 -#define _tmp_71_type 1314 -#define _loop0_73_type 1315 -#define _gather_72_type 1316 -#define _loop0_75_type 1317 -#define _gather_74_type 1318 -#define _tmp_76_type 1319 -#define _loop0_78_type 1320 -#define _gather_77_type 1321 -#define _loop0_80_type 1322 -#define _gather_79_type 1323 -#define _loop0_82_type 1324 -#define _gather_81_type 1325 -#define _loop1_83_type 1326 -#define _loop1_84_type 1327 -#define _loop0_86_type 1328 -#define _gather_85_type 1329 -#define _loop1_87_type 1330 -#define _loop1_88_type 1331 -#define _loop1_89_type 1332 -#define _tmp_90_type 1333 -#define _loop0_92_type 1334 -#define _gather_91_type 1335 -#define _tmp_93_type 1336 -#define _tmp_94_type 1337 -#define _tmp_95_type 1338 -#define _tmp_96_type 1339 -#define _tmp_97_type 1340 -#define _tmp_98_type 1341 -#define _loop0_99_type 1342 -#define _loop0_100_type 1343 -#define _loop0_101_type 1344 -#define _loop1_102_type 1345 -#define _loop0_103_type 1346 -#define _loop1_104_type 1347 -#define _loop1_105_type 1348 -#define _loop1_106_type 1349 -#define _loop0_107_type 1350 -#define _loop1_108_type 1351 -#define _loop0_109_type 1352 -#define _loop1_110_type 1353 -#define _loop0_111_type 1354 -#define _loop1_112_type 1355 -#define _tmp_113_type 1356 -#define _loop0_114_type 1357 -#define _loop1_115_type 1358 -#define _tmp_116_type 1359 -#define _loop0_118_type 1360 -#define _gather_117_type 1361 -#define _loop1_119_type 1362 -#define _loop0_120_type 1363 -#define _loop0_121_type 1364 -#define _tmp_122_type 1365 -#define _loop0_124_type 1366 -#define _gather_123_type 1367 -#define _tmp_125_type 1368 -#define _loop0_127_type 1369 -#define _gather_126_type 1370 -#define _loop0_129_type 1371 -#define _gather_128_type 1372 -#define _loop0_131_type 1373 -#define _gather_130_type 1374 -#define _loop0_133_type 1375 -#define _gather_132_type 1376 -#define _loop0_134_type 1377 -#define _loop0_136_type 1378 -#define _gather_135_type 1379 -#define _loop1_137_type 1380 -#define _tmp_138_type 1381 -#define _loop0_140_type 1382 -#define _gather_139_type 1383 -#define _loop0_142_type 1384 -#define _gather_141_type 1385 -#define _loop0_144_type 1386 -#define _gather_143_type 1387 -#define _loop0_146_type 1388 -#define _gather_145_type 1389 -#define _loop0_148_type 1390 -#define _gather_147_type 1391 -#define _tmp_149_type 1392 -#define _tmp_150_type 1393 -#define _tmp_151_type 1394 -#define _tmp_152_type 1395 -#define _tmp_153_type 1396 -#define _tmp_154_type 1397 -#define _tmp_155_type 1398 -#define _tmp_156_type 1399 -#define _tmp_157_type 1400 -#define _tmp_158_type 1401 -#define _tmp_159_type 1402 -#define _loop0_160_type 1403 -#define _loop0_161_type 1404 -#define _loop0_162_type 1405 -#define _tmp_163_type 1406 -#define _tmp_164_type 1407 -#define _tmp_165_type 1408 -#define _tmp_166_type 1409 -#define _tmp_167_type 1410 -#define _loop0_168_type 1411 -#define _loop0_169_type 1412 -#define _loop0_170_type 1413 -#define _loop1_171_type 1414 -#define _tmp_172_type 1415 -#define _loop0_173_type 1416 -#define _tmp_174_type 1417 -#define _loop0_175_type 1418 -#define _loop1_176_type 1419 -#define _tmp_177_type 1420 -#define _tmp_178_type 1421 -#define _tmp_179_type 1422 -#define _loop0_180_type 1423 -#define _tmp_181_type 1424 -#define _tmp_182_type 1425 -#define _loop1_183_type 1426 -#define _tmp_184_type 1427 -#define _loop0_185_type 1428 -#define _loop0_186_type 1429 -#define _loop0_187_type 1430 -#define _loop0_189_type 1431 -#define _gather_188_type 1432 -#define _tmp_190_type 1433 -#define _loop0_191_type 1434 -#define _tmp_192_type 1435 -#define _loop0_193_type 1436 -#define _loop1_194_type 1437 -#define _loop1_195_type 1438 -#define _tmp_196_type 1439 -#define _tmp_197_type 1440 -#define _loop0_198_type 1441 -#define _tmp_199_type 1442 -#define _tmp_200_type 1443 -#define _tmp_201_type 1444 -#define _loop0_203_type 1445 -#define _gather_202_type 1446 -#define _loop0_205_type 1447 -#define _gather_204_type 1448 -#define _loop0_207_type 1449 -#define _gather_206_type 1450 -#define _loop0_209_type 1451 -#define _gather_208_type 1452 -#define _loop0_211_type 1453 -#define _gather_210_type 1454 -#define _tmp_212_type 1455 -#define _loop0_213_type 1456 -#define _loop1_214_type 1457 -#define _tmp_215_type 1458 -#define _loop0_216_type 1459 -#define _loop1_217_type 1460 -#define _tmp_218_type 1461 -#define _tmp_219_type 1462 -#define _tmp_220_type 1463 -#define _tmp_221_type 1464 -#define _tmp_222_type 1465 -#define _tmp_223_type 1466 -#define _tmp_224_type 1467 -#define _tmp_225_type 1468 -#define _tmp_226_type 1469 -#define _tmp_227_type 1470 -#define _loop0_229_type 1471 -#define _gather_228_type 1472 -#define _tmp_230_type 1473 -#define _tmp_231_type 1474 -#define _tmp_232_type 1475 -#define _tmp_233_type 1476 -#define _tmp_234_type 1477 -#define _tmp_235_type 1478 -#define _tmp_236_type 1479 -#define _tmp_237_type 1480 -#define _tmp_238_type 1481 -#define _tmp_239_type 1482 -#define _tmp_240_type 1483 -#define _tmp_241_type 1484 -#define _tmp_242_type 1485 -#define _loop0_243_type 1486 -#define _tmp_244_type 1487 -#define _tmp_245_type 1488 -#define _tmp_246_type 1489 -#define _tmp_247_type 1490 -#define _tmp_248_type 1491 -#define _tmp_249_type 1492 -#define _tmp_250_type 1493 -#define _tmp_251_type 1494 -#define _tmp_252_type 1495 -#define _tmp_253_type 1496 -#define _tmp_254_type 1497 -#define _tmp_255_type 1498 -#define _tmp_256_type 1499 -#define _tmp_257_type 1500 -#define _tmp_258_type 1501 -#define _tmp_259_type 1502 -#define _tmp_260_type 1503 -#define _tmp_261_type 1504 -#define _tmp_262_type 1505 -#define _tmp_263_type 1506 -#define _tmp_264_type 1507 -#define _tmp_265_type 1508 -#define _tmp_266_type 1509 -#define _tmp_267_type 1510 -#define _tmp_268_type 1511 -#define _tmp_269_type 1512 -#define _tmp_270_type 1513 -#define _tmp_271_type 1514 -#define _tmp_272_type 1515 -#define _tmp_273_type 1516 -#define _tmp_274_type 1517 -#define _tmp_275_type 1518 +#define move_expr_type 1152 +#define move_source_type 1153 +#define fstring_middle_type 1154 +#define fstring_replacement_field_type 1155 +#define fstring_conversion_type 1156 +#define fstring_full_format_spec_type 1157 +#define fstring_format_spec_type 1158 +#define string_type 1159 +#define strings_type 1160 +#define list_type 1161 +#define tuple_type 1162 +#define set_type 1163 +#define dict_type 1164 +#define double_starred_kvpairs_type 1165 +#define double_starred_kvpair_type 1166 +#define kvpair_type 1167 +#define for_if_clauses_type 1168 +#define for_if_clause_type 1169 +#define listcomp_type 1170 +#define setcomp_type 1171 +#define genexp_type 1172 +#define dictcomp_type 1173 +#define arguments_type 1174 +#define args_type 1175 +#define kwargs_type 1176 +#define starred_expression_type 1177 +#define kwarg_or_starred_type 1178 +#define kwarg_or_double_starred_type 1179 +#define star_targets_type 1180 +#define star_targets_list_seq_type 1181 +#define star_targets_tuple_seq_type 1182 +#define star_target_type 1183 +#define target_with_star_atom_type 1184 +#define star_atom_type 1185 +#define single_target_type 1186 +#define single_subscript_attribute_target_type 1187 +#define t_primary_type 1188 // Left-recursive +#define t_lookahead_type 1189 +#define del_targets_type 1190 +#define del_target_type 1191 +#define del_t_atom_type 1192 +#define type_expressions_type 1193 +#define func_type_comment_type 1194 +#define invalid_arguments_type 1195 +#define invalid_kwarg_type 1196 +#define expression_without_invalid_type 1197 +#define invalid_legacy_expression_type 1198 +#define invalid_expression_type 1199 +#define invalid_named_expression_type 1200 +#define invalid_assignment_type 1201 +#define invalid_ann_assign_target_type 1202 +#define invalid_del_stmt_type 1203 +#define invalid_block_type 1204 +#define invalid_comprehension_type 1205 +#define invalid_dict_comprehension_type 1206 +#define invalid_parameters_type 1207 +#define invalid_default_type 1208 +#define invalid_star_etc_type 1209 +#define invalid_kwds_type 1210 +#define invalid_parameters_helper_type 1211 +#define invalid_lambda_parameters_type 1212 +#define invalid_lambda_parameters_helper_type 1213 +#define invalid_lambda_star_etc_type 1214 +#define invalid_lambda_kwds_type 1215 +#define invalid_double_type_comments_type 1216 +#define invalid_with_item_type 1217 +#define invalid_for_target_type 1218 +#define invalid_group_type 1219 +#define invalid_import_type 1220 +#define invalid_import_from_targets_type 1221 +#define invalid_with_stmt_type 1222 +#define invalid_with_stmt_indent_type 1223 +#define invalid_try_stmt_type 1224 +#define invalid_except_stmt_type 1225 +#define invalid_finally_stmt_type 1226 +#define invalid_except_stmt_indent_type 1227 +#define invalid_except_star_stmt_indent_type 1228 +#define invalid_match_stmt_type 1229 +#define invalid_case_block_type 1230 +#define invalid_as_pattern_type 1231 +#define invalid_class_pattern_type 1232 +#define invalid_class_argument_pattern_type 1233 +#define invalid_if_stmt_type 1234 +#define invalid_elif_stmt_type 1235 +#define invalid_else_stmt_type 1236 +#define invalid_while_stmt_type 1237 +#define invalid_for_stmt_type 1238 +#define invalid_def_raw_type 1239 +#define invalid_class_def_raw_type 1240 +#define invalid_double_starred_kvpairs_type 1241 +#define invalid_kvpair_type 1242 +#define invalid_starred_expression_type 1243 +#define invalid_replacement_field_type 1244 +#define invalid_conversion_character_type 1245 +#define _loop0_1_type 1246 +#define _loop0_2_type 1247 +#define _loop0_3_type 1248 +#define _loop1_4_type 1249 +#define _loop0_6_type 1250 +#define _gather_5_type 1251 +#define _tmp_7_type 1252 +#define _tmp_8_type 1253 +#define _tmp_9_type 1254 +#define _tmp_10_type 1255 +#define _tmp_11_type 1256 +#define _tmp_12_type 1257 +#define _tmp_13_type 1258 +#define _tmp_14_type 1259 +#define _loop1_15_type 1260 +#define _tmp_16_type 1261 +#define _tmp_17_type 1262 +#define _tmp_18_type 1263 +#define _loop0_20_type 1264 +#define _gather_19_type 1265 +#define _loop0_22_type 1266 +#define _gather_21_type 1267 +#define _tmp_23_type 1268 +#define _tmp_24_type 1269 +#define _loop0_25_type 1270 +#define _loop1_26_type 1271 +#define _loop0_28_type 1272 +#define _gather_27_type 1273 +#define _tmp_29_type 1274 +#define _loop0_31_type 1275 +#define _gather_30_type 1276 +#define _tmp_32_type 1277 +#define _loop1_33_type 1278 +#define _tmp_34_type 1279 +#define _tmp_35_type 1280 +#define _tmp_36_type 1281 +#define _loop0_37_type 1282 +#define _loop0_38_type 1283 +#define _loop0_39_type 1284 +#define _loop1_40_type 1285 +#define _loop0_41_type 1286 +#define _loop1_42_type 1287 +#define _loop1_43_type 1288 +#define _loop1_44_type 1289 +#define _loop0_45_type 1290 +#define _loop1_46_type 1291 +#define _loop0_47_type 1292 +#define _loop1_48_type 1293 +#define _loop0_49_type 1294 +#define _loop0_50_type 1295 +#define _loop1_51_type 1296 +#define _loop0_53_type 1297 +#define _gather_52_type 1298 +#define _loop0_55_type 1299 +#define _gather_54_type 1300 +#define _loop0_57_type 1301 +#define _gather_56_type 1302 +#define _loop0_59_type 1303 +#define _gather_58_type 1304 +#define _tmp_60_type 1305 +#define _loop1_61_type 1306 +#define _loop1_62_type 1307 +#define _tmp_63_type 1308 +#define _tmp_64_type 1309 +#define _loop1_65_type 1310 +#define _loop0_67_type 1311 +#define _gather_66_type 1312 +#define _tmp_68_type 1313 +#define _tmp_69_type 1314 +#define _tmp_70_type 1315 +#define _tmp_71_type 1316 +#define _loop0_73_type 1317 +#define _gather_72_type 1318 +#define _loop0_75_type 1319 +#define _gather_74_type 1320 +#define _tmp_76_type 1321 +#define _loop0_78_type 1322 +#define _gather_77_type 1323 +#define _loop0_80_type 1324 +#define _gather_79_type 1325 +#define _loop0_82_type 1326 +#define _gather_81_type 1327 +#define _loop1_83_type 1328 +#define _loop1_84_type 1329 +#define _loop0_86_type 1330 +#define _gather_85_type 1331 +#define _loop1_87_type 1332 +#define _loop1_88_type 1333 +#define _loop1_89_type 1334 +#define _tmp_90_type 1335 +#define _loop0_92_type 1336 +#define _gather_91_type 1337 +#define _tmp_93_type 1338 +#define _tmp_94_type 1339 +#define _tmp_95_type 1340 +#define _tmp_96_type 1341 +#define _tmp_97_type 1342 +#define _tmp_98_type 1343 +#define _loop0_99_type 1344 +#define _loop0_100_type 1345 +#define _loop0_101_type 1346 +#define _loop1_102_type 1347 +#define _loop0_103_type 1348 +#define _loop1_104_type 1349 +#define _loop1_105_type 1350 +#define _loop1_106_type 1351 +#define _loop0_107_type 1352 +#define _loop1_108_type 1353 +#define _loop0_109_type 1354 +#define _loop1_110_type 1355 +#define _loop0_111_type 1356 +#define _loop1_112_type 1357 +#define _tmp_113_type 1358 +#define _loop0_114_type 1359 +#define _loop1_115_type 1360 +#define _tmp_116_type 1361 +#define _loop0_118_type 1362 +#define _gather_117_type 1363 +#define _loop1_119_type 1364 +#define _loop0_120_type 1365 +#define _loop0_121_type 1366 +#define _tmp_122_type 1367 +#define _loop0_124_type 1368 +#define _gather_123_type 1369 +#define _tmp_125_type 1370 +#define _loop0_127_type 1371 +#define _gather_126_type 1372 +#define _loop0_129_type 1373 +#define _gather_128_type 1374 +#define _loop0_131_type 1375 +#define _gather_130_type 1376 +#define _loop0_133_type 1377 +#define _gather_132_type 1378 +#define _loop0_134_type 1379 +#define _loop0_136_type 1380 +#define _gather_135_type 1381 +#define _loop1_137_type 1382 +#define _tmp_138_type 1383 +#define _loop0_140_type 1384 +#define _gather_139_type 1385 +#define _loop0_142_type 1386 +#define _gather_141_type 1387 +#define _loop0_144_type 1388 +#define _gather_143_type 1389 +#define _loop0_146_type 1390 +#define _gather_145_type 1391 +#define _loop0_148_type 1392 +#define _gather_147_type 1393 +#define _tmp_149_type 1394 +#define _tmp_150_type 1395 +#define _tmp_151_type 1396 +#define _tmp_152_type 1397 +#define _tmp_153_type 1398 +#define _tmp_154_type 1399 +#define _tmp_155_type 1400 +#define _tmp_156_type 1401 +#define _tmp_157_type 1402 +#define _tmp_158_type 1403 +#define _tmp_159_type 1404 +#define _loop0_160_type 1405 +#define _loop0_161_type 1406 +#define _loop0_162_type 1407 +#define _tmp_163_type 1408 +#define _tmp_164_type 1409 +#define _tmp_165_type 1410 +#define _tmp_166_type 1411 +#define _tmp_167_type 1412 +#define _loop0_168_type 1413 +#define _loop0_169_type 1414 +#define _loop0_170_type 1415 +#define _loop1_171_type 1416 +#define _tmp_172_type 1417 +#define _loop0_173_type 1418 +#define _tmp_174_type 1419 +#define _loop0_175_type 1420 +#define _loop1_176_type 1421 +#define _tmp_177_type 1422 +#define _tmp_178_type 1423 +#define _tmp_179_type 1424 +#define _loop0_180_type 1425 +#define _tmp_181_type 1426 +#define _tmp_182_type 1427 +#define _loop1_183_type 1428 +#define _tmp_184_type 1429 +#define _loop0_185_type 1430 +#define _loop0_186_type 1431 +#define _loop0_187_type 1432 +#define _loop0_189_type 1433 +#define _gather_188_type 1434 +#define _tmp_190_type 1435 +#define _loop0_191_type 1436 +#define _tmp_192_type 1437 +#define _loop0_193_type 1438 +#define _loop1_194_type 1439 +#define _loop1_195_type 1440 +#define _tmp_196_type 1441 +#define _tmp_197_type 1442 +#define _loop0_198_type 1443 +#define _tmp_199_type 1444 +#define _tmp_200_type 1445 +#define _tmp_201_type 1446 +#define _loop0_203_type 1447 +#define _gather_202_type 1448 +#define _loop0_205_type 1449 +#define _gather_204_type 1450 +#define _loop0_207_type 1451 +#define _gather_206_type 1452 +#define _loop0_209_type 1453 +#define _gather_208_type 1454 +#define _loop0_211_type 1455 +#define _gather_210_type 1456 +#define _tmp_212_type 1457 +#define _loop0_213_type 1458 +#define _loop1_214_type 1459 +#define _tmp_215_type 1460 +#define _loop0_216_type 1461 +#define _loop1_217_type 1462 +#define _tmp_218_type 1463 +#define _tmp_219_type 1464 +#define _tmp_220_type 1465 +#define _tmp_221_type 1466 +#define _tmp_222_type 1467 +#define _tmp_223_type 1468 +#define _tmp_224_type 1469 +#define _tmp_225_type 1470 +#define _tmp_226_type 1471 +#define _tmp_227_type 1472 +#define _loop0_229_type 1473 +#define _gather_228_type 1474 +#define _tmp_230_type 1475 +#define _tmp_231_type 1476 +#define _tmp_232_type 1477 +#define _tmp_233_type 1478 +#define _tmp_234_type 1479 +#define _tmp_235_type 1480 +#define _tmp_236_type 1481 +#define _tmp_237_type 1482 +#define _tmp_238_type 1483 +#define _tmp_239_type 1484 +#define _tmp_240_type 1485 +#define _tmp_241_type 1486 +#define _tmp_242_type 1487 +#define _loop0_243_type 1488 +#define _tmp_244_type 1489 +#define _tmp_245_type 1490 +#define _tmp_246_type 1491 +#define _tmp_247_type 1492 +#define _tmp_248_type 1493 +#define _tmp_249_type 1494 +#define _tmp_250_type 1495 +#define _tmp_251_type 1496 +#define _tmp_252_type 1497 +#define _tmp_253_type 1498 +#define _tmp_254_type 1499 +#define _tmp_255_type 1500 +#define _tmp_256_type 1501 +#define _tmp_257_type 1502 +#define _tmp_258_type 1503 +#define _tmp_259_type 1504 +#define _tmp_260_type 1505 +#define _tmp_261_type 1506 +#define _tmp_262_type 1507 +#define _tmp_263_type 1508 +#define _tmp_264_type 1509 +#define _tmp_265_type 1510 +#define _tmp_266_type 1511 +#define _tmp_267_type 1512 +#define _tmp_268_type 1513 +#define _tmp_269_type 1514 +#define _tmp_270_type 1515 +#define _tmp_271_type 1516 +#define _tmp_272_type 1517 +#define _tmp_273_type 1518 +#define _tmp_274_type 1519 +#define _tmp_275_type 1520 static mod_ty file_rule(Parser *p); static mod_ty interactive_rule(Parser *p); @@ -750,6 +753,8 @@ static arg_ty lambda_param_no_default_rule(Parser *p); static NameDefaultPair* lambda_param_with_default_rule(Parser *p); static NameDefaultPair* lambda_param_maybe_default_rule(Parser *p); static arg_ty lambda_param_rule(Parser *p); +static expr_ty move_expr_rule(Parser *p); +static expr_ty move_source_rule(Parser *p); static expr_ty fstring_middle_rule(Parser *p); static expr_ty fstring_replacement_field_rule(Parser *p); static ResultTokenWithMetadata* fstring_conversion_rule(Parser *p); @@ -11033,6 +11038,7 @@ expressions_rule(Parser *p) // expression: // | invalid_expression // | invalid_legacy_expression +// | move_expr // | disjunction 'if' disjunction 'else' expression // | disjunction // | lambdef @@ -11099,6 +11105,25 @@ expression_rule(Parser *p) D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "invalid_legacy_expression")); } + { // move_expr + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> expression[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "move_expr")); + expr_ty move_expr_var; + if ( + (move_expr_var = move_expr_rule(p)) // move_expr + ) + { + D(fprintf(stderr, "%*c+ expression[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "move_expr")); + _res = move_expr_var; + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s expression[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "move_expr")); + } { // disjunction 'if' disjunction 'else' expression if (p->error_indicator) { p->level--; @@ -15865,6 +15890,210 @@ lambda_param_rule(Parser *p) return _res; } +// move_expr: "move" move_source +static expr_ty +move_expr_rule(Parser *p) +{ + if (p->level++ == MAXSTACK) { + _Pypegen_stack_overflow(p); + } + if (p->error_indicator) { + p->level--; + return NULL; + } + expr_ty _res = NULL; + int _mark = p->mark; + if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) { + p->error_indicator = 1; + p->level--; + return NULL; + } + int _start_lineno = p->tokens[_mark]->lineno; + UNUSED(_start_lineno); // Only used by EXTRA macro + int _start_col_offset = p->tokens[_mark]->col_offset; + UNUSED(_start_col_offset); // Only used by EXTRA macro + { // "move" move_source + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> move_expr[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "\"move\" move_source")); + expr_ty _keyword; + expr_ty b; + if ( + (_keyword = _PyPegen_expect_soft_keyword(p, "move")) // soft_keyword='"move"' + && + (b = move_source_rule(p)) // move_source + ) + { + D(fprintf(stderr, "%*c+ move_expr[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "\"move\" move_source")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + p->level--; + return NULL; + } + int _end_lineno = _token->end_lineno; + 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 = _PyAST_Move ( _PyPegen_set_expr_context ( p , b , Del ) , EXTRA ) ;; + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s move_expr[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "\"move\" move_source")); + } + _res = NULL; + done: + p->level--; + return _res; +} + +// move_source: +// | t_primary '.' NAME !t_lookahead +// | t_primary '[' slices ']' !t_lookahead +// | NAME +static expr_ty +move_source_rule(Parser *p) +{ + if (p->level++ == MAXSTACK) { + _Pypegen_stack_overflow(p); + } + if (p->error_indicator) { + p->level--; + return NULL; + } + expr_ty _res = NULL; + int _mark = p->mark; + if (p->mark == p->fill && _PyPegen_fill_token(p) < 0) { + p->error_indicator = 1; + p->level--; + return NULL; + } + int _start_lineno = p->tokens[_mark]->lineno; + UNUSED(_start_lineno); // Only used by EXTRA macro + int _start_col_offset = p->tokens[_mark]->col_offset; + UNUSED(_start_col_offset); // Only used by EXTRA macro + { // t_primary '.' NAME !t_lookahead + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> move_source[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME !t_lookahead")); + Token * _literal; + expr_ty a; + expr_ty b; + if ( + (a = t_primary_rule(p)) // t_primary + && + (_literal = _PyPegen_expect_token(p, 23)) // token='.' + && + (b = _PyPegen_name_token(p)) // NAME + && + _PyPegen_lookahead(0, t_lookahead_rule, p) + ) + { + D(fprintf(stderr, "%*c+ move_source[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '.' NAME !t_lookahead")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + p->level--; + return NULL; + } + int _end_lineno = _token->end_lineno; + 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 = _PyAST_Attribute ( a , b -> v . Name . id , Del , EXTRA ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s move_source[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "t_primary '.' NAME !t_lookahead")); + } + { // t_primary '[' slices ']' !t_lookahead + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> move_source[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' !t_lookahead")); + Token * _literal; + Token * _literal_1; + expr_ty a; + expr_ty b; + if ( + (a = t_primary_rule(p)) // t_primary + && + (_literal = _PyPegen_expect_token(p, 9)) // token='[' + && + (b = slices_rule(p)) // slices + && + (_literal_1 = _PyPegen_expect_token(p, 10)) // token=']' + && + _PyPegen_lookahead(0, t_lookahead_rule, p) + ) + { + D(fprintf(stderr, "%*c+ move_source[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "t_primary '[' slices ']' !t_lookahead")); + Token *_token = _PyPegen_get_last_nonnwhitespace_token(p); + if (_token == NULL) { + p->level--; + return NULL; + } + int _end_lineno = _token->end_lineno; + 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 = _PyAST_Subscript ( a , b , Del , EXTRA ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s move_source[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "t_primary '[' slices ']' !t_lookahead")); + } + { // NAME + if (p->error_indicator) { + p->level--; + return NULL; + } + D(fprintf(stderr, "%*c> move_source[%d-%d]: %s\n", p->level, ' ', _mark, p->mark, "NAME")); + expr_ty a; + if ( + (a = _PyPegen_name_token(p)) // NAME + ) + { + D(fprintf(stderr, "%*c+ move_source[%d-%d]: %s succeeded!\n", p->level, ' ', _mark, p->mark, "NAME")); + _res = _PyPegen_set_expr_context ( p , a , Del ); + if (_res == NULL && PyErr_Occurred()) { + p->error_indicator = 1; + p->level--; + return NULL; + } + goto done; + } + p->mark = _mark; + D(fprintf(stderr, "%*c%s move_source[%d-%d]: %s failed!\n", p->level, ' ', + p->error_indicator ? "ERROR!" : "-", _mark, p->mark, "NAME")); + } + _res = NULL; + done: + p->level--; + return _res; +} + // fstring_middle: fstring_replacement_field | FSTRING_MIDDLE static expr_ty fstring_middle_rule(Parser *p) diff --git a/Python/Python-ast.c b/Python/Python-ast.c index f8bb6124fa4878..57a6592171f73c 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -118,6 +118,7 @@ void _PyAST_Fini(PyInterpreterState *interp) Py_CLEAR(state->Mod_singleton); Py_CLEAR(state->Mod_type); Py_CLEAR(state->Module_type); + Py_CLEAR(state->Move_type); Py_CLEAR(state->Mult_singleton); Py_CLEAR(state->Mult_type); Py_CLEAR(state->Name_type); @@ -250,6 +251,7 @@ void _PyAST_Fini(PyInterpreterState *interp) Py_CLEAR(state->right); Py_CLEAR(state->simple); Py_CLEAR(state->slice); + Py_CLEAR(state->source); Py_CLEAR(state->step); Py_CLEAR(state->stmt_type); Py_CLEAR(state->subject); @@ -353,6 +355,7 @@ static int init_identifiers(struct ast_state *state) if ((state->right = PyUnicode_InternFromString("right")) == NULL) return 0; if ((state->simple = PyUnicode_InternFromString("simple")) == NULL) return 0; if ((state->slice = PyUnicode_InternFromString("slice")) == NULL) return 0; + if ((state->source = PyUnicode_InternFromString("source")) == NULL) return 0; if ((state->step = PyUnicode_InternFromString("step")) == NULL) return 0; if ((state->subject = PyUnicode_InternFromString("subject")) == NULL) return 0; if ((state->tag = PyUnicode_InternFromString("tag")) == NULL) return 0; @@ -592,6 +595,9 @@ static const char * const GeneratorExp_fields[]={ "elt", "generators", }; +static const char * const Move_fields[]={ + "source", +}; static const char * const Await_fields[]={ "value", }; @@ -1364,6 +1370,7 @@ init_types(struct ast_state *state) " | SetComp(expr elt, comprehension* generators)\n" " | DictComp(expr key, expr value, comprehension* generators)\n" " | GeneratorExp(expr elt, comprehension* generators)\n" + " | Move(expr source)\n" " | Await(expr value)\n" " | Yield(expr? value)\n" " | YieldFrom(expr value)\n" @@ -1434,6 +1441,10 @@ init_types(struct ast_state *state) 2, "GeneratorExp(expr elt, comprehension* generators)"); if (!state->GeneratorExp_type) return 0; + state->Move_type = make_type(state, "Move", state->expr_type, Move_fields, + 1, + "Move(expr source)"); + if (!state->Move_type) return 0; state->Await_type = make_type(state, "Await", state->expr_type, Await_fields, 1, "Await(expr value)"); @@ -2994,6 +3005,28 @@ _PyAST_GeneratorExp(expr_ty elt, asdl_comprehension_seq * generators, int return p; } +expr_ty +_PyAST_Move(expr_ty source, int lineno, int col_offset, int end_lineno, int + end_col_offset, PyArena *arena) +{ + expr_ty p; + if (!source) { + PyErr_SetString(PyExc_ValueError, + "field 'source' is required for Move"); + return NULL; + } + p = (expr_ty)_PyArena_Malloc(arena, sizeof(*p)); + if (!p) + return NULL; + p->kind = Move_kind; + p->v.Move.source = source; + p->lineno = lineno; + p->col_offset = col_offset; + p->end_lineno = end_lineno; + p->end_col_offset = end_col_offset; + return p; +} + expr_ty _PyAST_Await(expr_ty value, int lineno, int col_offset, int end_lineno, int end_col_offset, PyArena *arena) @@ -4667,6 +4700,16 @@ ast2obj_expr(struct ast_state *state, void* _o) goto failed; Py_DECREF(value); break; + case Move_kind: + tp = (PyTypeObject *)state->Move_type; + result = PyType_GenericNew(tp, NULL, NULL); + if (!result) goto failed; + value = ast2obj_expr(state, o->v.Move.source); + if (!value) goto failed; + if (PyObject_SetAttr(result, state->source, value) == -1) + goto failed; + Py_DECREF(value); + break; case Await_kind: tp = (PyTypeObject *)state->Await_type; result = PyType_GenericNew(tp, NULL, NULL); @@ -9506,6 +9549,36 @@ obj2ast_expr(struct ast_state *state, PyObject* obj, expr_ty* out, PyArena* if (*out == NULL) goto failed; return 0; } + tp = state->Move_type; + isinstance = PyObject_IsInstance(obj, tp); + if (isinstance == -1) { + return 1; + } + if (isinstance) { + expr_ty source; + + if (_PyObject_LookupAttr(obj, state->source, &tmp) < 0) { + return 1; + } + if (tmp == NULL) { + PyErr_SetString(PyExc_TypeError, "required field \"source\" missing from Move"); + return 1; + } + else { + int res; + if (_Py_EnterRecursiveCall(" while traversing 'Move' node")) { + goto failed; + } + res = obj2ast_expr(state, tmp, &source, arena); + _Py_LeaveRecursiveCall(); + if (res != 0) goto failed; + Py_CLEAR(tmp); + } + *out = _PyAST_Move(source, lineno, col_offset, end_lineno, + end_col_offset, arena); + if (*out == NULL) goto failed; + return 0; + } tp = state->Await_type; isinstance = PyObject_IsInstance(obj, tp); if (isinstance == -1) { @@ -12804,6 +12877,9 @@ astmodule_exec(PyObject *m) { return -1; } + if (PyModule_AddObjectRef(m, "Move", state->Move_type) < 0) { + return -1; + } if (PyModule_AddObjectRef(m, "Await", state->Await_type) < 0) { return -1; } diff --git a/Python/ast.c b/Python/ast.c index a3acf78ac95844..42dffd09b21906 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -280,6 +280,14 @@ validate_expr(struct validator *state, expr_ty exp, expr_context_ty ctx) ret = validate_arguments(state, exp->v.Lambda.args) && validate_expr(state, exp->v.Lambda.body, Load); break; + case Move_kind: + // An expression can only have one context, but the `move` keyword + // uses the source expression both to read and delete it. This + // only checks for the deletion context since that's the more + // intrusive one. If we can delete the data, we should also + // be allowed to read it (In the Python world). + ret = validate_expr(state, exp->v.Move.source, Del); + break; case IfExp_kind: ret = validate_expr(state, exp->v.IfExp.test, Load) && validate_expr(state, exp->v.IfExp.body, Load) && diff --git a/Python/ast_opt.c b/Python/ast_opt.c index f8c4a9513236b9..d9f13e488dde62 100644 --- a/Python/ast_opt.c +++ b/Python/ast_opt.c @@ -733,6 +733,9 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) CALL(astfold_arguments, arguments_ty, node_->v.Lambda.args); CALL(astfold_expr, expr_ty, node_->v.Lambda.body); break; + case Move_kind: + CALL(astfold_expr, expr_ty, node_->v.Move.source); + break; case IfExp_kind: CALL(astfold_expr, expr_ty, node_->v.IfExp.test); CALL(astfold_expr, expr_ty, node_->v.IfExp.body); diff --git a/Python/ast_unparse.c b/Python/ast_unparse.c index 8aff045101cc72..34a1442c8134d7 100644 --- a/Python/ast_unparse.c +++ b/Python/ast_unparse.c @@ -297,6 +297,14 @@ append_ast_lambda(_PyUnicodeWriter *writer, expr_ty e, int level) return 0; } +static int +append_ast_move(_PyUnicodeWriter *writer, expr_ty e, int level) +{ + APPEND_STR("move "); + APPEND_EXPR(e->v.Move.source, PR_ATOM); + return 0; +} + static int append_ast_ifexp(_PyUnicodeWriter *writer, expr_ty e, int level) { @@ -857,6 +865,8 @@ append_ast_expr(_PyUnicodeWriter *writer, expr_ty e, int level) return append_ast_unaryop(writer, e, level); case Lambda_kind: return append_ast_lambda(writer, e, level); + case Move_kind: + return append_ast_move(writer, e, level); case IfExp_kind: return append_ast_ifexp(writer, e, level); case Dict_kind: diff --git a/Python/compile.c b/Python/compile.c index ddd7b5c795b9ef..0449eb68584422 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -4688,6 +4688,114 @@ check_index(struct compiler *c, expr_ty e, expr_ty s) } } +// If an attribute access spans multiple lines, update the current start +// location to point to the attribute name. +static location +update_start_location_to_match_attr(struct compiler *c, location loc, + expr_ty attr) +{ + assert(attr->kind == Attribute_kind); + if (loc.lineno != attr->end_lineno) { + loc.lineno = attr->end_lineno; + int len = (int)PyUnicode_GET_LENGTH(attr->v.Attribute.attr); + if (len <= attr->end_col_offset) { + loc.col_offset = attr->end_col_offset - len; + } + else { + // GH-94694: Somebody's compiling weird ASTs. Just drop the columns: + loc.col_offset = -1; + loc.end_col_offset = -1; + } + // Make sure the end position still follows the start position, even for + // weird ASTs: + loc.end_lineno = Py_MAX(loc.lineno, loc.end_lineno); + if (loc.lineno == loc.end_lineno) { + loc.end_col_offset = Py_MAX(loc.col_offset, loc.end_col_offset); + } + } + return loc; +} + +static bool +is_two_element_slice(expr_ty s) +{ + return s->kind == Slice_kind && + s->v.Slice.step == NULL; +} + +// This function implements the `move` keyword. The keyword works for variables, +// attributes and containers via index operations. All implementations follow +// the same schema: +// 1. Setup the stack to access the data +// 2. Copy the relevant stack items +// 3. Load the value into the top +// 4. Move the loaded value to the bottom +// 5. Call delete +// 6. Only the loaded value is left on the stack +static int +compiler_move(struct compiler *c, expr_ty e) +{ + expr_ty source = e->v.Move.source; + assert(e->kind == Move_kind); + + location loc = LOC(e); + location source_loc = LOC(source); + if (source->kind == Name_kind) { + // First load the value + RETURN_IF_ERROR( + compiler_nameop(c, source_loc, source->v.Name.id, Load)); + // Now delete the old reference + RETURN_IF_ERROR( + compiler_nameop(c, source_loc, source->v.Name.id, Del)); + } else if (source->kind == Attribute_kind) { + location attr_loc = update_start_location_to_match_attr(c, loc, source); + + // Get the source value + VISIT(c, expr, source->v.Attribute.value); + + // Duplicate the attribute source to be used for the load + // and delete + ADDOP_I(c, loc, COPY, 1); + + // Load the value + ADDOP_NAME(c, attr_loc, LOAD_ATTR, source->v.Attribute.attr, names); + ADDOP_I(c, loc, SWAP, 2); + + // Delete the value + ADDOP_NAME(c, loc, DELETE_ATTR, source->v.Attribute.attr, names); + } else if (source->kind == Subscript_kind) { + // Check taken from `compiler_subscript` + RETURN_IF_ERROR(check_subscripter(c, source->v.Subscript.value)); + RETURN_IF_ERROR(check_index(c, source->v.Subscript.value, source->v.Subscript.slice)); + + // Push `None` as a swap target for the load + ADDOP_LOAD_CONST(c, loc, Py_None); + + // Setup the operant and key + VISIT(c, expr, source->v.Subscript.value); + VISIT(c, expr, source->v.Subscript.slice); + // Duplicate for Load + ADDOP_I(c, loc, COPY, 2); + ADDOP_I(c, loc, COPY, 2); + + // Load + ADDOP(c, source_loc, BINARY_SUBSCR); + + // Swap the loaded value to the bottom of the move stack + ADDOP_I(c, loc, SWAP, 4); + ADDOP(c, loc, POP_TOP); + + // Delete + ADDOP(c, source_loc, DELETE_SUBSCR); + } else { + // The parser should only allow names, slices and attributes + // This is just a sanity check. + assert(false); + } + + return SUCCESS; +} + static int is_import_originated(struct compiler *c, expr_ty e) { @@ -4788,34 +4896,6 @@ load_args_for_super(struct compiler *c, expr_ty e) { return SUCCESS; } -// If an attribute access spans multiple lines, update the current start -// location to point to the attribute name. -static location -update_start_location_to_match_attr(struct compiler *c, location loc, - expr_ty attr) -{ - assert(attr->kind == Attribute_kind); - if (loc.lineno != attr->end_lineno) { - loc.lineno = attr->end_lineno; - int len = (int)PyUnicode_GET_LENGTH(attr->v.Attribute.attr); - if (len <= attr->end_col_offset) { - loc.col_offset = attr->end_col_offset - len; - } - else { - // GH-94694: Somebody's compiling weird ASTs. Just drop the columns: - loc.col_offset = -1; - loc.end_col_offset = -1; - } - // Make sure the end position still follows the start position, even for - // weird ASTs: - loc.end_lineno = Py_MAX(loc.lineno, loc.end_lineno); - if (loc.lineno == loc.end_lineno) { - loc.end_col_offset = Py_MAX(loc.col_offset, loc.end_col_offset); - } - } - return loc; -} - // Return 1 if the method call was optimized, 0 if not, and -1 on error. static int maybe_optimize_method_call(struct compiler *c, expr_ty e) @@ -6065,6 +6145,8 @@ compiler_visit_expr1(struct compiler *c, expr_ty e) break; case Lambda_kind: return compiler_lambda(c, e); + case Move_kind: + return compiler_move(c, e); case IfExp_kind: return compiler_ifexp(c, e); case Dict_kind: @@ -6199,13 +6281,6 @@ compiler_visit_expr(struct compiler *c, expr_ty e) return res; } -static bool -is_two_element_slice(expr_ty s) -{ - return s->kind == Slice_kind && - s->v.Slice.step == NULL; -} - static int compiler_augassign(struct compiler *c, stmt_ty s) { diff --git a/Python/symtable.c b/Python/symtable.c index 4989e03d4bf591..24781b50ca5c09 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -1994,6 +1994,9 @@ symtable_visit_expr(struct symtable *st, expr_ty e) VISIT_QUIT(st, 0); break; } + case Move_kind: + VISIT(st, expr, e->v.Move.source); + break; case IfExp_kind: VISIT(st, expr, e->v.IfExp.test); VISIT(st, expr, e->v.IfExp.body);