@@ -330,6 +330,14 @@ _PyPegen_set_expr_context(Parser *p, expr_ty expr, expr_context_ty ctx)
330330 case Starred_kind :
331331 new = _set_starred_context (p , expr , ctx );
332332 break ;
333+ case DictUnpack_kind :
334+ new = _PyAST_DictUnpack (
335+ expr -> v .DictUnpack .keys ,
336+ _set_seq_context (p , expr -> v .DictUnpack .targets , ctx ),
337+ expr -> v .DictUnpack .rest ? _PyPegen_set_expr_context (p , expr -> v .DictUnpack .rest , ctx ) : NULL ,
338+ ctx ,
339+ EXTRA_EXPR (expr , expr ));
340+ break ;
333341 default :
334342 new = expr ;
335343 }
@@ -381,6 +389,117 @@ _PyPegen_get_values(Parser *p, asdl_seq *seq)
381389 return new_seq ;
382390}
383391
392+ /* Dict destructuring helpers */
393+
394+ /* Shorthand: {name} -> key=Constant("name"), target=Name("name", Store) */
395+ KeyValuePair *
396+ _PyPegen_dict_unpack_shorthand (Parser * p , expr_ty name )
397+ {
398+ assert (name -> kind == Name_kind );
399+ KeyValuePair * a = _PyArena_Malloc (p -> arena , sizeof (KeyValuePair ));
400+ if (!a ) {
401+ return NULL ;
402+ }
403+ a -> key = _PyAST_Constant (name -> v .Name .id , NULL , EXTRA_EXPR (name , name ));
404+ if (!a -> key ) {
405+ return NULL ;
406+ }
407+ a -> value = _PyAST_Name (name -> v .Name .id , Store , EXTRA_EXPR (name , name ));
408+ if (!a -> value ) {
409+ return NULL ;
410+ }
411+ return a ;
412+ }
413+
414+ /* Explicit string key: {'key': target} */
415+ KeyValuePair *
416+ _PyPegen_dict_unpack_kv (Parser * p , expr_ty string_expr , expr_ty target )
417+ {
418+ KeyValuePair * a = _PyArena_Malloc (p -> arena , sizeof (KeyValuePair ));
419+ if (!a ) {
420+ return NULL ;
421+ }
422+ /* string_expr is a Constant from the STRING token */
423+ a -> key = string_expr ;
424+ a -> value = target ;
425+ return a ;
426+ }
427+
428+ /* Name as key: {name: target} -> key=Constant("name"), target=target */
429+ KeyValuePair *
430+ _PyPegen_dict_unpack_kv_name (Parser * p , expr_ty name , expr_ty target )
431+ {
432+ assert (name -> kind == Name_kind );
433+ KeyValuePair * a = _PyArena_Malloc (p -> arena , sizeof (KeyValuePair ));
434+ if (!a ) {
435+ return NULL ;
436+ }
437+ a -> key = _PyAST_Constant (name -> v .Name .id , NULL , EXTRA_EXPR (name , name ));
438+ if (!a -> key ) {
439+ return NULL ;
440+ }
441+ a -> value = target ;
442+ return a ;
443+ }
444+
445+ /* Build a DictUnpack AST node from a sequence of KeyValuePair* */
446+ expr_ty
447+ _PyPegen_make_dict_unpack (Parser * p , asdl_seq * seq ,
448+ int lineno , int col_offset ,
449+ int end_lineno , int end_col_offset ,
450+ PyArena * arena )
451+ {
452+ Py_ssize_t len = asdl_seq_LEN (seq );
453+ asdl_expr_seq * keys = _Py_asdl_expr_seq_new (len , arena );
454+ if (!keys ) {
455+ return NULL ;
456+ }
457+ asdl_expr_seq * targets = _Py_asdl_expr_seq_new (len , arena );
458+ if (!targets ) {
459+ return NULL ;
460+ }
461+ for (Py_ssize_t i = 0 ; i < len ; i ++ ) {
462+ KeyValuePair * pair = asdl_seq_GET_UNTYPED (seq , i );
463+ asdl_seq_SET (keys , i , pair -> key );
464+ asdl_seq_SET (targets , i , pair -> value );
465+ }
466+ return _PyAST_DictUnpack (keys , targets , NULL , Store ,
467+ lineno , col_offset , end_lineno , end_col_offset ,
468+ arena );
469+ }
470+
471+ /* Build a DictUnpack AST node with **rest from a sequence of KeyValuePair* */
472+ expr_ty
473+ _PyPegen_make_dict_unpack_rest (Parser * p , asdl_seq * seq , expr_ty rest_name ,
474+ int lineno , int col_offset ,
475+ int end_lineno , int end_col_offset ,
476+ PyArena * arena )
477+ {
478+ Py_ssize_t len = seq ? asdl_seq_LEN (seq ) : 0 ;
479+ asdl_expr_seq * keys = _Py_asdl_expr_seq_new (len , arena );
480+ if (!keys ) {
481+ return NULL ;
482+ }
483+ asdl_expr_seq * targets = _Py_asdl_expr_seq_new (len , arena );
484+ if (!targets ) {
485+ return NULL ;
486+ }
487+ for (Py_ssize_t i = 0 ; i < len ; i ++ ) {
488+ KeyValuePair * pair = asdl_seq_GET_UNTYPED (seq , i );
489+ asdl_seq_SET (keys , i , pair -> key );
490+ asdl_seq_SET (targets , i , pair -> value );
491+ }
492+ /* rest_name is a Name expr in Load context; convert to Store */
493+ expr_ty rest = _PyAST_Name (rest_name -> v .Name .id , Store ,
494+ EXTRA_EXPR (rest_name , rest_name ));
495+ if (!rest ) {
496+ return NULL ;
497+ }
498+ return _PyAST_DictUnpack (keys , targets , rest , Store ,
499+ lineno , col_offset , end_lineno , end_col_offset ,
500+ arena );
501+ }
502+
384503/* Constructs a KeyPatternPair that is used when parsing mapping & class patterns */
385504KeyPatternPair *
386505_PyPegen_key_pattern_pair (Parser * p , expr_ty key , pattern_ty pattern )
@@ -1083,6 +1202,8 @@ _PyPegen_get_expr_name(expr_ty e)
10831202 return "list" ;
10841203 case Tuple_kind :
10851204 return "tuple" ;
1205+ case DictUnpack_kind :
1206+ return "dict destructuring" ;
10861207 case Lambda_kind :
10871208 return "lambda" ;
10881209 case Call_kind :
@@ -1221,6 +1342,17 @@ _PyPegen_get_invalid_target(expr_ty e, TARGETS_TYPE targets_type)
12211342 case Tuple_kind :
12221343 VISIT_CONTAINER (e , Tuple );
12231344 return NULL ;
1345+ case DictUnpack_kind : {
1346+ Py_ssize_t len = asdl_seq_LEN (e -> v .DictUnpack .targets );
1347+ for (Py_ssize_t i = 0 ; i < len ; i ++ ) {
1348+ expr_ty other = asdl_seq_GET (e -> v .DictUnpack .targets , i );
1349+ expr_ty child = _PyPegen_get_invalid_target (other , targets_type );
1350+ if (child != NULL ) {
1351+ return child ;
1352+ }
1353+ }
1354+ return NULL ;
1355+ }
12241356 case Starred_kind :
12251357 if (targets_type == DEL_TARGETS ) {
12261358 return e ;
0 commit comments