From 40f208b8e39c91fb65c4f3f99604e3471b594615 Mon Sep 17 00:00:00 2001 From: changjoon-park Date: Sat, 2 May 2026 21:12:56 +0900 Subject: [PATCH] Match CPython wording for compile() optimize and flags validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CPython's `builtin_compile_impl` (Python/bltinmodule.c) accepts only optimize ∈ {-1, 0, 1, 2}; anything else raises `ValueError("compile(): invalid optimize value")`. The previous logic only validated via `i32::try_into::()`, which silently accepts every value in [0, 255], so `compile(..., optimize=3)`, `optimize=99`, etc. were silently truncated to a u8. The error wording also had the wrong word order. Replace the cast-based check with a `match` against the spec range. Adjacent: the unrecognised-flags message used American spelling ("unrecognized") and missed the colon separator. CPython uses British "unrecognised" with a colon — match it. Verified byte-identical with CPython 3.14.4 across 12 boundary values for optimize and 4 cases for flags. Preserves the existing OverflowError path for `optimize=1 << 1000` (raised at the ArgPrimitiveIndex conversion layer, before this check). --- crates/vm/src/stdlib/builtins.rs | 12 +++---- extra_tests/snippets/builtin_compile.py | 46 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 extra_tests/snippets/builtin_compile.py diff --git a/crates/vm/src/stdlib/builtins.rs b/crates/vm/src/stdlib/builtins.rs index fd35b287211..9afd4cf761c 100644 --- a/crates/vm/src/stdlib/builtins.rs +++ b/crates/vm/src/stdlib/builtins.rs @@ -268,12 +268,10 @@ mod builtins { let mode_str = args.mode.as_str(); let optimize: i32 = args.optimize.map_or(-1, |v| v.value); - let optimize: u8 = if optimize == -1 { - vm.state.config.settings.optimize - } else { - optimize - .try_into() - .map_err(|_| vm.new_value_error("compile() optimize value invalid"))? + let optimize: u8 = match optimize { + -1 => vm.state.config.settings.optimize, + 0..=2 => optimize as u8, + _ => return Err(vm.new_value_error("compile(): invalid optimize value")), }; if args @@ -348,7 +346,7 @@ mod builtins { let flags: i32 = args.flags.map_or(0, |v| v.value); if !(flags & !_ast::PY_COMPILE_FLAGS_MASK).is_zero() { - return Err(vm.new_value_error("compile() unrecognized flags")); + return Err(vm.new_value_error("compile(): unrecognised flags")); } let allow_incomplete = !(flags & _ast::PY_CF_ALLOW_INCOMPLETE_INPUT).is_zero(); diff --git a/extra_tests/snippets/builtin_compile.py b/extra_tests/snippets/builtin_compile.py new file mode 100644 index 00000000000..15095c0eede --- /dev/null +++ b/extra_tests/snippets/builtin_compile.py @@ -0,0 +1,46 @@ +from testutils import assert_raises + +# compile() basic mode acceptance +assert isinstance( + compile("x = 1", "", "exec"), type(compile("", "", "exec")) +) +assert compile("1 + 1", "", "eval") is not None +assert compile("1", "", "single") is not None + +# `optimize` accepts -1 (use config default), 0, 1, 2 only. +# Anything else raises ValueError with CPython's exact wording. +for ok in (-1, 0, 1, 2): + compile("x = 1", "", "exec", optimize=ok) + + +def _check_optimize_error(value): + try: + compile("x = 1", "", "exec", optimize=value) + except ValueError as e: + assert str(e) == "compile(): invalid optimize value", repr(e) + else: + raise AssertionError(f"expected ValueError for optimize={value!r}") + + +for bad in (3, 4, 99, 255, 256, 1000, -2, -99, -128): + _check_optimize_error(bad) + +# Huge `optimize` values raise OverflowError during argument conversion, +# not ValueError. The exact wording differs from CPython here (Rust i32 +# vs C int) — checking the type only, matching test_compile.py. +assert_raises(OverflowError, compile, "x = 1", "", "exec", optimize=1 << 1000) + + +# Unrecognised `flags` bits raise ValueError. CPython uses British spelling +# ("unrecognised") so the message must match exactly. +def _check_flags_error(flags): + try: + compile("x = 1", "", "exec", flags=flags) + except ValueError as e: + assert str(e) == "compile(): unrecognised flags", repr(e) + else: + raise AssertionError(f"expected ValueError for flags={flags!r}") + + +_check_flags_error(99999) +_check_flags_error(0x10000)