From 52d2e25601fd5a036d58859ca48911fa2b3d980f Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 13:01:59 +0900 Subject: [PATCH 01/13] Fix dict spreading in dict literal --- ast/src/ast_gen.rs | 2 +- parser/python.lalrpop | 72 ++++++++--------- parser/src/parser.rs | 6 ++ ...parser__tests__dict_containing_spread.snap | 81 +++++++++++++++++++ 4 files changed, 124 insertions(+), 37 deletions(-) create mode 100644 parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap diff --git a/ast/src/ast_gen.rs b/ast/src/ast_gen.rs index 241ebe1..32dd89c 100644 --- a/ast/src/ast_gen.rs +++ b/ast/src/ast_gen.rs @@ -195,7 +195,7 @@ pub enum ExprKind { orelse: Box>, }, Dict { - keys: Vec>, + keys: Vec>>, values: Vec>, }, Set { diff --git a/parser/python.lalrpop b/parser/python.lalrpop index 90f4ce2..7007905 100644 --- a/parser/python.lalrpop +++ b/parser/python.lalrpop @@ -1138,42 +1138,42 @@ Atom: ast::Expr = { "{" "}" => { let pairs = e.unwrap_or_default(); - let (keys, values) = match pairs.iter().position(|(k,_)| k.is_none()) { - Some(unpack_idx) => { - let mut pairs = pairs; - let (keys, mut values): (_, Vec<_>) = pairs.drain(..unpack_idx).map(|(k, v)| (*k.unwrap(), v)).unzip(); - - fn build_map(items: &mut Vec<(ast::Expr, ast::Expr)>) -> ast::Expr { - let location = items[0].0.location; - let end_location = items[0].0.end_location; - let (keys, values) = items.drain(..).unzip(); - ast::Expr { - location, - end_location, - custom: (), - node: ast::ExprKind::Dict { keys, values } - } - } - - let mut items = Vec::new(); - for (key, value) in pairs.into_iter() { - if let Some(key) = key { - items.push((*key, value)); - continue; - } - if !items.is_empty() { - values.push(build_map(&mut items)); - } - values.push(value); - } - if !items.is_empty() { - values.push(build_map(&mut items)); - } - (keys, values) - }, - None => pairs.into_iter().map(|(k, v)| (*k.unwrap(), v)).unzip() - }; - + // let (keys, values) = match pairs.iter().position(|(k,_)| k.is_none()) { + // Some(unpack_idx) => { + // let mut pairs = pairs; + // let (keys, mut values): (_, Vec<_>) = pairs.drain(..unpack_idx).map(|(k, v)| (*k.unwrap(), v)).unzip(); + // + // fn build_map(items: &mut Vec<(ast::Expr, ast::Expr)>) -> ast::Expr { + // let location = items[0].0.location; + // let end_location = items[0].0.end_location; + // let (keys, values) = items.drain(..).unzip(); + // ast::Expr { + // location, + // end_location, + // custom: (), + // node: ast::ExprKind::Dict { keys, values } + // } + // } + // + // let mut items = Vec::new(); + // for (key, value) in pairs.into_iter() { + // if let Some(key) = key { + // items.push((*key, value)); + // continue; + // } + // if !items.is_empty() { + // values.push(build_map(&mut items)); + // } + // values.push(value); + // } + // if !items.is_empty() { + // values.push(build_map(&mut items)); + // } + // (keys, values) + // }, + // None => pairs.into_iter().map(|(k, v)| (k, v)).unzip() + // }; + let (keys, values) = pairs.into_iter().map(|(k, v)| (k.map(|x| *x), v)).unzip(); ast::Expr { location, end_location: Some(end_location), diff --git a/parser/src/parser.rs b/parser/src/parser.rs index fb18155..ced5d9b 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -309,4 +309,10 @@ with (0 as a, 1 as b,): pass assert!(parse_program(source, "").is_err()); } } + + #[test] + fn test_dict_containing_spread() { + let parse_ast = parse_expression(r#"{"k": "v", **d}"#, "").unwrap(); + insta::assert_debug_snapshot!(parse_ast); + } } diff --git a/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap b/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap new file mode 100644 index 0000000..29eb084 --- /dev/null +++ b/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap @@ -0,0 +1,81 @@ +--- +source: compiler/parser/src/parser.rs +expression: parse_ast +--- +Located { + location: Location { + row: 1, + column: 0, + }, + end_location: Some( + Location { + row: 1, + column: 15, + }, + ), + custom: (), + node: Dict { + keys: [ + Some( + Located { + location: Location { + row: 1, + column: 1, + }, + end_location: Some( + Location { + row: 1, + column: 4, + }, + ), + custom: (), + node: Constant { + value: Str( + "k", + ), + kind: None, + }, + }, + ), + None, + ], + values: [ + Located { + location: Location { + row: 1, + column: 6, + }, + end_location: Some( + Location { + row: 1, + column: 9, + }, + ), + custom: (), + node: Constant { + value: Str( + "v", + ), + kind: None, + }, + }, + Located { + location: Location { + row: 1, + column: 13, + }, + end_location: Some( + Location { + row: 1, + column: 14, + }, + ), + custom: (), + node: Name { + id: "d", + ctx: Load, + }, + }, + ], + }, +} From 5e7b90955274ad4c216d8d736a59d2d1460f0698 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 13:11:55 +0900 Subject: [PATCH 02/13] Fix unparse --- ast/src/unparse.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ast/src/unparse.rs b/ast/src/unparse.rs index 551283c..b8b3420 100644 --- a/ast/src/unparse.rs +++ b/ast/src/unparse.rs @@ -152,7 +152,11 @@ impl<'a> Unparser<'a> { let (packed, unpacked) = values.split_at(keys.len()); for (k, v) in keys.iter().zip(packed) { self.p_delim(&mut first, ", ")?; - write!(self, "{}: {}", *k, *v)?; + if let Some(k) = k { + write!(self, "{}: {}", *k, *v)?; + } else { + write!(self, "**{}", *v)?; + } } for d in unpacked { self.p_delim(&mut first, ", ")?; From 375d592562325789f180b5e5b83d1b78aec95f47 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 13:22:05 +0900 Subject: [PATCH 03/13] Fix scan_expression and compile_dict --- codegen/src/compile.rs | 10 ++++++++-- codegen/src/symboltable.rs | 4 +++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/codegen/src/compile.rs b/codegen/src/compile.rs index 2da3aba..c245c62 100644 --- a/codegen/src/compile.rs +++ b/codegen/src/compile.rs @@ -1986,12 +1986,18 @@ impl Compiler { Ok(()) } - fn compile_dict(&mut self, keys: &[ast::Expr], values: &[ast::Expr]) -> CompileResult<()> { + fn compile_dict( + &mut self, + keys: &[Option], + values: &[ast::Expr], + ) -> CompileResult<()> { let mut size = 0; let (packed_values, unpacked_values) = values.split_at(keys.len()); for (key, value) in keys.iter().zip(packed_values) { - self.compile_expression(key)?; + if let Some(key) = key { + self.compile_expression(key)?; + } self.compile_expression(value)?; size += 1; } diff --git a/codegen/src/symboltable.rs b/codegen/src/symboltable.rs index a12ad5f..c12de9f 100644 --- a/codegen/src/symboltable.rs +++ b/codegen/src/symboltable.rs @@ -887,7 +887,9 @@ impl SymbolTableBuilder { Dict { keys, values } => { let (packed, unpacked) = values.split_at(keys.len()); for (key, value) in keys.iter().zip(packed) { - self.scan_expression(key, context)?; + if let Some(key) = key { + self.scan_expression(key, context)?; + } self.scan_expression(value, context)?; } for value in unpacked { From cb3578dbf255c073f833e9164d9f21b5d91efe04 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 14:54:58 +0900 Subject: [PATCH 04/13] Fix scan_expression and compile_dict --- codegen/src/compile.rs | 14 +++++++------- codegen/src/symboltable.rs | 13 +++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/codegen/src/compile.rs b/codegen/src/compile.rs index c245c62..193aad4 100644 --- a/codegen/src/compile.rs +++ b/codegen/src/compile.rs @@ -1992,18 +1992,18 @@ impl Compiler { values: &[ast::Expr], ) -> CompileResult<()> { let mut size = 0; - - let (packed_values, unpacked_values) = values.split_at(keys.len()); - for (key, value) in keys.iter().zip(packed_values) { - if let Some(key) = key { - self.compile_expression(key)?; - } + let (packed, unpacked): (Vec<_>, Vec<_>) = keys + .iter() + .zip(values.iter()) + .partition(|(k, _)| k.is_some()); + for (key, value) in packed { + self.compile_expression(&key.as_ref().unwrap())?; self.compile_expression(value)?; size += 1; } emit!(self, Instruction::BuildMap { size }); - for value in unpacked_values { + for (_, value) in unpacked { self.compile_expression(value)?; emit!(self, Instruction::DictUpdate); } diff --git a/codegen/src/symboltable.rs b/codegen/src/symboltable.rs index c12de9f..86a3e35 100644 --- a/codegen/src/symboltable.rs +++ b/codegen/src/symboltable.rs @@ -885,14 +885,15 @@ impl SymbolTableBuilder { self.scan_expression(value, ExpressionContext::Load)?; } Dict { keys, values } => { - let (packed, unpacked) = values.split_at(keys.len()); - for (key, value) in keys.iter().zip(packed) { - if let Some(key) = key { - self.scan_expression(key, context)?; - } + let (packed, unpacked): (Vec<_>, Vec<_>) = keys + .iter() + .zip(values.iter()) + .partition(|(key, _)| key.is_some()); + for (key, value) in packed { + self.scan_expression(&key.as_ref().unwrap(), context)?; self.scan_expression(value, context)?; } - for value in unpacked { + for (_, value) in unpacked { // dict unpacking marker self.scan_expression(value, context)?; } From f8ae56c75b15baa333bc33551cab7ecedde2f6a2 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 15:04:15 +0900 Subject: [PATCH 05/13] Fix clippy errors --- codegen/src/compile.rs | 2 +- codegen/src/symboltable.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codegen/src/compile.rs b/codegen/src/compile.rs index 193aad4..c64cfb8 100644 --- a/codegen/src/compile.rs +++ b/codegen/src/compile.rs @@ -1997,7 +1997,7 @@ impl Compiler { .zip(values.iter()) .partition(|(k, _)| k.is_some()); for (key, value) in packed { - self.compile_expression(&key.as_ref().unwrap())?; + self.compile_expression(key.as_ref().unwrap())?; self.compile_expression(value)?; size += 1; } diff --git a/codegen/src/symboltable.rs b/codegen/src/symboltable.rs index 86a3e35..125c1ea 100644 --- a/codegen/src/symboltable.rs +++ b/codegen/src/symboltable.rs @@ -890,7 +890,7 @@ impl SymbolTableBuilder { .zip(values.iter()) .partition(|(key, _)| key.is_some()); for (key, value) in packed { - self.scan_expression(&key.as_ref().unwrap(), context)?; + self.scan_expression(key.as_ref().unwrap(), context)?; self.scan_expression(value, context)?; } for (_, value) in unpacked { From 2f4fe8b3807d9cd244dd783aa81965550fee4a84 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 16:10:51 +0900 Subject: [PATCH 06/13] Remove commented-out code --- parser/python.lalrpop | 43 +++++-------------------------------------- 1 file changed, 5 insertions(+), 38 deletions(-) diff --git a/parser/python.lalrpop b/parser/python.lalrpop index 7007905..df5057c 100644 --- a/parser/python.lalrpop +++ b/parser/python.lalrpop @@ -1136,44 +1136,11 @@ Atom: ast::Expr = { }.into()) }, "{" "}" => { - let pairs = e.unwrap_or_default(); - - // let (keys, values) = match pairs.iter().position(|(k,_)| k.is_none()) { - // Some(unpack_idx) => { - // let mut pairs = pairs; - // let (keys, mut values): (_, Vec<_>) = pairs.drain(..unpack_idx).map(|(k, v)| (*k.unwrap(), v)).unzip(); - // - // fn build_map(items: &mut Vec<(ast::Expr, ast::Expr)>) -> ast::Expr { - // let location = items[0].0.location; - // let end_location = items[0].0.end_location; - // let (keys, values) = items.drain(..).unzip(); - // ast::Expr { - // location, - // end_location, - // custom: (), - // node: ast::ExprKind::Dict { keys, values } - // } - // } - // - // let mut items = Vec::new(); - // for (key, value) in pairs.into_iter() { - // if let Some(key) = key { - // items.push((*key, value)); - // continue; - // } - // if !items.is_empty() { - // values.push(build_map(&mut items)); - // } - // values.push(value); - // } - // if !items.is_empty() { - // values.push(build_map(&mut items)); - // } - // (keys, values) - // }, - // None => pairs.into_iter().map(|(k, v)| (k, v)).unzip() - // }; - let (keys, values) = pairs.into_iter().map(|(k, v)| (k.map(|x| *x), v)).unzip(); + let (keys, values) = e + .unwrap_or_default() + .into_iter() + .map(|(k, v)| (k.map(|x| *x), v)) + .unzip(); ast::Expr { location, end_location: Some(end_location), From d7f5dadf47ed4bfefd4b04b4fc9208ca99f052f4 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 16:43:13 +0900 Subject: [PATCH 07/13] Add Option to Dict.keys field --- ast/asdl_rs.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ast/asdl_rs.py b/ast/asdl_rs.py index ed5800f..a18ec65 100755 --- a/ast/asdl_rs.py +++ b/ast/asdl_rs.py @@ -227,12 +227,12 @@ class StructVisitor(TypeInfoEmitVisitor): if cons.fields: self.emit(f"{cons.name} {{", depth) for f in cons.fields: - self.visit(f, parent, "", depth + 1) + self.visit(f, parent, "", depth + 1, cons.name) self.emit("},", depth) else: self.emit(f"{cons.name},", depth) - def visitField(self, field, parent, vis, depth): + def visitField(self, field, parent, vis, depth, constructor=None): typ = get_rust_type(field.type) fieldtype = self.typeinfo.get(field.type) if fieldtype and fieldtype.has_userdata: @@ -240,7 +240,11 @@ class StructVisitor(TypeInfoEmitVisitor): # don't box if we're doing Vec, but do box if we're doing Vec>> if fieldtype and fieldtype.boxed and (not (parent.product or field.seq) or field.opt): typ = f"Box<{typ}>" - if field.opt: + if field.opt or ( + # Add `Option` to allow `Dict.keys` to contain `None` for dictionary unpacking + # in a dict literal. + constructor == "Dict" and field.name == "keys" + ): typ = f"Option<{typ}>" if field.seq: typ = f"Vec<{typ}>" From a7f1904564e97fbb54f1b2c9902c5745eafbef54 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 16:53:13 +0900 Subject: [PATCH 08/13] Improve test --- parser/src/parser.rs | 2 +- ...parser__tests__dict_containing_spread.snap | 48 +++++++++++++++++-- 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/parser/src/parser.rs b/parser/src/parser.rs index ced5d9b..41945f2 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -312,7 +312,7 @@ with (0 as a, 1 as b,): pass #[test] fn test_dict_containing_spread() { - let parse_ast = parse_expression(r#"{"k": "v", **d}"#, "").unwrap(); + let parse_ast = parse_expression(r#"{"a": "b", **c, "d": "e"}"#, "").unwrap(); insta::assert_debug_snapshot!(parse_ast); } } diff --git a/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap b/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap index 29eb084..212f737 100644 --- a/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap +++ b/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap @@ -10,7 +10,7 @@ Located { end_location: Some( Location { row: 1, - column: 15, + column: 25, }, ), custom: (), @@ -31,13 +31,34 @@ Located { custom: (), node: Constant { value: Str( - "k", + "a", ), kind: None, }, }, ), None, + Some( + Located { + location: Location { + row: 1, + column: 16, + }, + end_location: Some( + Location { + row: 1, + column: 19, + }, + ), + custom: (), + node: Constant { + value: Str( + "d", + ), + kind: None, + }, + }, + ), ], values: [ Located { @@ -54,7 +75,7 @@ Located { custom: (), node: Constant { value: Str( - "v", + "b", ), kind: None, }, @@ -72,10 +93,29 @@ Located { ), custom: (), node: Name { - id: "d", + id: "c", ctx: Load, }, }, + Located { + location: Location { + row: 1, + column: 21, + }, + end_location: Some( + Location { + row: 1, + column: 24, + }, + ), + custom: (), + node: Constant { + value: Str( + "e", + ), + kind: None, + }, + }, ], }, } From 64736d05419a7b306d99d5f35b6458c7ab656743 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 22:08:19 +0900 Subject: [PATCH 09/13] Simplify scan_expression --- codegen/src/symboltable.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/codegen/src/symboltable.rs b/codegen/src/symboltable.rs index 125c1ea..dd2d295 100644 --- a/codegen/src/symboltable.rs +++ b/codegen/src/symboltable.rs @@ -885,16 +885,10 @@ impl SymbolTableBuilder { self.scan_expression(value, ExpressionContext::Load)?; } Dict { keys, values } => { - let (packed, unpacked): (Vec<_>, Vec<_>) = keys - .iter() - .zip(values.iter()) - .partition(|(key, _)| key.is_some()); - for (key, value) in packed { - self.scan_expression(key.as_ref().unwrap(), context)?; - self.scan_expression(value, context)?; - } - for (_, value) in unpacked { - // dict unpacking marker + for (key, value) in keys.iter().zip(values.iter()) { + if let Some(key) = key { + self.scan_expression(key, context)?; + } self.scan_expression(value, context)?; } } From 6bda1a9e9e275a12c280e9cd35d0e74a041a671f Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 23:20:00 +0900 Subject: [PATCH 10/13] Simplify compile_dict --- codegen/src/compile.rs | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/codegen/src/compile.rs b/codegen/src/compile.rs index c64cfb8..d5ae26d 100644 --- a/codegen/src/compile.rs +++ b/codegen/src/compile.rs @@ -1991,23 +1991,17 @@ impl Compiler { keys: &[Option], values: &[ast::Expr], ) -> CompileResult<()> { - let mut size = 0; - let (packed, unpacked): (Vec<_>, Vec<_>) = keys - .iter() - .zip(values.iter()) - .partition(|(k, _)| k.is_some()); - for (key, value) in packed { - self.compile_expression(key.as_ref().unwrap())?; - self.compile_expression(value)?; - size += 1; + emit!(self, Instruction::BuildMap { size: 0 }); + for (key, value) in keys.iter().zip(values.iter()) { + if let Some(key) = key { + self.compile_expression(key)?; + self.compile_expression(value)?; + emit!(self, Instruction::MapAdd { i: 0 }); + } else { + self.compile_expression(value)?; + emit!(self, Instruction::DictUpdate); + } } - emit!(self, Instruction::BuildMap { size }); - - for (_, value) in unpacked { - self.compile_expression(value)?; - emit!(self, Instruction::DictUpdate); - } - Ok(()) } From 4f32bacf9c4408efd7c68d47350e6787a6c4a923 Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 15 Jan 2023 23:36:07 +0900 Subject: [PATCH 11/13] Rename test --- parser/src/parser.rs | 2 +- ...ap => rustpython_parser__parser__tests__dict_unpacking.snap} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename parser/src/snapshots/{rustpython_parser__parser__tests__dict_containing_spread.snap => rustpython_parser__parser__tests__dict_unpacking.snap} (100%) diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 41945f2..9761e9e 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -311,7 +311,7 @@ with (0 as a, 1 as b,): pass } #[test] - fn test_dict_containing_spread() { + fn test_dict_unpacking() { let parse_ast = parse_expression(r#"{"a": "b", **c, "d": "e"}"#, "").unwrap(); insta::assert_debug_snapshot!(parse_ast); } diff --git a/parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap b/parser/src/snapshots/rustpython_parser__parser__tests__dict_unpacking.snap similarity index 100% rename from parser/src/snapshots/rustpython_parser__parser__tests__dict_containing_spread.snap rename to parser/src/snapshots/rustpython_parser__parser__tests__dict_unpacking.snap From d83e3ee8cd4744c4a2fc3cacfb1d328621389392 Mon Sep 17 00:00:00 2001 From: harupy Date: Mon, 16 Jan 2023 01:27:54 +0900 Subject: [PATCH 12/13] Revert "Simplify compile_dict" This reverts commit b31b08a30eb24e246b7f0a0d69b2b5e56a7e7a40. --- codegen/src/compile.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/codegen/src/compile.rs b/codegen/src/compile.rs index d5ae26d..c64cfb8 100644 --- a/codegen/src/compile.rs +++ b/codegen/src/compile.rs @@ -1991,17 +1991,23 @@ impl Compiler { keys: &[Option], values: &[ast::Expr], ) -> CompileResult<()> { - emit!(self, Instruction::BuildMap { size: 0 }); - for (key, value) in keys.iter().zip(values.iter()) { - if let Some(key) = key { - self.compile_expression(key)?; - self.compile_expression(value)?; - emit!(self, Instruction::MapAdd { i: 0 }); - } else { - self.compile_expression(value)?; - emit!(self, Instruction::DictUpdate); - } + let mut size = 0; + let (packed, unpacked): (Vec<_>, Vec<_>) = keys + .iter() + .zip(values.iter()) + .partition(|(k, _)| k.is_some()); + for (key, value) in packed { + self.compile_expression(key.as_ref().unwrap())?; + self.compile_expression(value)?; + size += 1; } + emit!(self, Instruction::BuildMap { size }); + + for (_, value) in unpacked { + self.compile_expression(value)?; + emit!(self, Instruction::DictUpdate); + } + Ok(()) } From c157dff45447d90b2bc1fa902dda3ce8f6a0b28e Mon Sep 17 00:00:00 2001 From: harupy Date: Sun, 22 Jan 2023 00:06:52 +0900 Subject: [PATCH 13/13] Fix comment --- ast/asdl_rs.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ast/asdl_rs.py b/ast/asdl_rs.py index a18ec65..b217e62 100755 --- a/ast/asdl_rs.py +++ b/ast/asdl_rs.py @@ -241,8 +241,9 @@ class StructVisitor(TypeInfoEmitVisitor): if fieldtype and fieldtype.boxed and (not (parent.product or field.seq) or field.opt): typ = f"Box<{typ}>" if field.opt or ( - # Add `Option` to allow `Dict.keys` to contain `None` for dictionary unpacking - # in a dict literal. + # When a dictionary literal contains dictionary unpacking (e.g., `{**d}`), + # the expression to be unpacked goes in `values` with a `None` at the corresponding + # position in `keys`. To handle this, the type of `keys` needs to be `Option>`. constructor == "Dict" and field.name == "keys" ): typ = f"Option<{typ}>"