From 43368209c505c8807f5efdf3a39729bb30866722 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Wed, 21 Oct 2026 19:21:10 -0700 Subject: [PATCH] Support signed numeric constants in match pattern ASTs (#9279) Recognize numeric Expr::Constant values when validating signed literal patterns, alongside parser-produced NumberLiteral nodes. Python AST conversion uses the former representation. Accept unary plus as well as minus and fold signed real/imaginary and complex patterns consistently. Boolean and non-numeric constants remain excluded. Preserve the existing parser-produced literal path and do not change source grammar. Assisted-by: OpenAI Codex:model-version-unavailable --- crates/codegen/src/compile.rs | 18 +++++++++++++----- crates/vm/src/stdlib/_ast/validate.rs | 15 +++++++++++---- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/crates/codegen/src/compile.rs b/crates/codegen/src/compile.rs index fb561db6d..70244a6a1 100644 --- a/crates/codegen/src/compile.rs +++ b/crates/codegen/src/compile.rs @@ -12288,12 +12288,12 @@ impl<'warnings> Compiler<'warnings> { &mut self, expr: &ast::Expr, ) -> CompileResult> { - // CPython 3.14 ast_preprocess.c::fold_const_match_patterns() + // CPython ast_preprocess.c::fold_const_match_patterns() // folds only the constant forms needed by match patterns before // codegen_pattern_value()/codegen_pattern_mapping_key() visit them. Ok(match expr { ast::Expr::UnaryOp(ast::ExprUnaryOp { - op: ast::UnaryOp::USub, + op: op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub), operand, .. }) => { @@ -12301,7 +12301,11 @@ impl<'warnings> Compiler<'warnings> { else { return Ok(None); }; - Self::try_negate_match_pattern_constant(constant) + if *op == ast::UnaryOp::USub { + Self::try_negate_match_pattern_constant(constant) + } else { + Some(constant) + } } ast::Expr::BinOp(ast::ExprBinOp { left, op, right, .. @@ -12326,7 +12330,7 @@ impl<'warnings> Compiler<'warnings> { return Ok(Some(constant)); } let ast::Expr::UnaryOp(ast::ExprUnaryOp { - op: ast::UnaryOp::USub, + op: op @ (ast::UnaryOp::UAdd | ast::UnaryOp::USub), operand, .. }) = expr @@ -12336,7 +12340,11 @@ impl<'warnings> Compiler<'warnings> { let Some(constant) = self.try_compile_match_pattern_real_constant(operand)? else { return Ok(None); }; - Ok(Self::try_negate_match_pattern_constant(constant)) + Ok(if *op == ast::UnaryOp::USub { + Self::try_negate_match_pattern_constant(constant) + } else { + Some(constant) + }) } fn try_compile_match_pattern_real_constant( diff --git a/crates/vm/src/stdlib/_ast/validate.rs b/crates/vm/src/stdlib/_ast/validate.rs index b2d2b0a4b..abfbc636f 100644 --- a/crates/vm/src/stdlib/_ast/validate.rs +++ b/crates/vm/src/stdlib/_ast/validate.rs @@ -191,6 +191,13 @@ fn validate_interpolated_elements<'a>( } fn ensure_literal_number(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool { + if let Some(constant) = ast_constant_value(expr) { + return match constant { + ConstantData::Integer { .. } | ConstantData::Float { .. } => allow_real, + ConstantData::Complex { .. } => allow_imaginary, + _ => false, + }; + } let ast::Expr::NumberLiteral(number) = expr else { return false; }; @@ -200,11 +207,11 @@ fn ensure_literal_number(expr: &ast::Expr, allow_real: bool, allow_imaginary: bo } } -fn ensure_literal_negative(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool { +fn ensure_literal_signed(expr: &ast::Expr, allow_real: bool, allow_imaginary: bool) -> bool { let ast::Expr::UnaryOp(unary) = expr else { return false; }; - if unary.op != ast::UnaryOp::USub { + if !matches!(unary.op, ast::UnaryOp::UAdd | ast::UnaryOp::USub) { return false; } ensure_literal_number(&unary.operand, allow_real, allow_imaginary) @@ -218,7 +225,7 @@ fn ensure_literal_complex(expr: &ast::Expr) -> bool { return false; } let real_left = ensure_literal_number(&bin.left, true, false) - || ensure_literal_negative(&bin.left, true, false); + || ensure_literal_signed(&bin.left, true, false); real_left && ensure_literal_number(&bin.right, false, true) } @@ -244,7 +251,7 @@ fn validate_pattern_match_value(vm: &VirtualMachine, expr: &ast::Expr) -> PyResu Ok(()) } ast::Expr::Attribute(_) => Ok(()), - ast::Expr::UnaryOp(_) if ensure_literal_negative(expr, true, true) => Ok(()), + ast::Expr::UnaryOp(_) if ensure_literal_signed(expr, true, true) => Ok(()), ast::Expr::BinOp(_) if ensure_literal_complex(expr) => Ok(()), ast::Expr::FString(_) | ast::Expr::TString(_) => Ok(()), ast::Expr::BooleanLiteral(_)