Raise ValueError for None in required AST scalar fields#7680
Conversation
Match CPython's "field 'X' is required for Y" error format when required scalar AST fields receive None during ast_from_object conversion. Previously these produced TypeError or a generic "None disallowed in expression list" ValueError. Fields covered (Alias.name, Arg.arg, Comprehension.target/iter, Keyword.value, MatchCase.pattern, WithItem.context_expr, YieldFrom.value): all now raise the CPython-compatible message verbatim. Add a get_node_field_required helper alongside the existing get_node_field / get_node_field_opt and switch the eight call sites that read these required scalar fields. The helper rejects None with the proper ValueError before the value flows into the type converter where Rust's strong typing would otherwise force a generic catch-all error. Optional fields (read via get_node_field_opt) and fields where None is valid (e.g. Constant.value) are unaffected. This complements the existing AST validator pass at crates/vm/src/stdlib/_ast/validate.rs (hooked at _ast.rs:763): the validator handles post-conversion semantic invariants (ExprContext, empty body, cross-field rules) but cannot handle required-scalar-field None because Rust's non-Option types reject None at conversion time, before validation runs. Verified byte-identical with CPython 3.14.4 for all seven probe cases. test.test_ast.test_ast: 227 tests, expected failures 38 -> 36 (test_empty_yield_from and test_none_checks unmasked, no regressions). test.test_compile: 0 regressions.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThe changes introduce a new validation helper Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📦 Library DependenciesThe following Lib/ modules were modified. Here are their dependencies: [x] lib: cpython/Lib/ast.py dependencies:
dependent tests: (136 tests)
Legend:
|
Summary
When required scalar AST fields receive
None(e.g.comprehension.target = Noneafter manually constructing an AST),compile()raises mismatched errors compared to CPython:alias.nameTypeError: Expected type 'str' but 'NoneType' found.ValueError: field 'name' is required for aliasarg.argValueError: field 'arg' is required for argcomprehension.target/iterValueError: None disallowed in expression listValueError: field '<target/iter>' is required for comprehensionkeyword.valueValueError: field 'value' is required for keywordmatch_case.patternTypeError: expected some sort of pattern, but got NoneValueError: field 'pattern' is required for match_casewithitem.context_exprValueError: field 'context_expr' is required for withitemYieldFrom.valueValueError: field 'value' is required for YieldFromThese slip past the existing
validate.rssemantic validator because Rust's non-Optionfield types (e.g.Alias.name: Identifier,Comprehension.target: Expr,MatchCase.pattern: Pattern) forceNode::ast_from_objectto fail at conversion time, before the validator pass can run. CPython's single-passPyAST_Validateworks because every AST attribute is aPyObject*and toleratesNoneuntil the validator decides; RustPython's strong typing forces this work to happen at conversion.Fix
Add
get_node_field_requirednext to the existingget_node_field/get_node_field_optincrates/vm/src/stdlib/_ast.rs:Switch the eight call sites that read required scalar fields:
alias.name,withitem.context_expr(other.rs)arg.arg,keyword.value(parameter.rs)comprehension.target,comprehension.iter,YieldFrom.value(expression.rs)match_case.pattern(pattern.rs)Optional fields (read via
get_node_field_opt:alias.asname,arg.annotation,Yield.value,Return.value,WithItem.optional_vars,ExceptHandler.type, etc.) and fields whereNoneis legitimate (Constant.valuerepresenting theNoneliteral) are unchanged.Verification
Targeted probes (CPython 3.14.4 byte-identical)
Identical to CPython 3.14.4 output for every case.
Test suites
Was 38 expected failures; this PR unmasks
test_none_checksandtest_empty_yield_from.No regressions.
Optional
Nonepaths still compileVerified that legitimate
NoneAST positions are unaffected:import x(alias.asname=None)def f(x): pass(arg.annotation=None,returns=None)def f(): yield(Yield.value=None)def f(): return(Return.value=None)with x: pass(WithItem.optional_vars=None)try: pass\nexcept: pass(ExceptHandler.type=None)Constant(value=None)(Noneliteral)All compile cleanly.
Pre-push
cargo fmt --all --checkcleancargo clippy -p rustpython-vm --all-targets -- -D warningscleanprek run --all-files— all 8 hooks passScope
6 files, +35/-10 lines. Additive in spirit — no behaviour change on success paths.
Summary by CodeRabbit