`sys.version_info` and `sys.implementation` are two different versioning
schemes. `version_info` refers to the target Python version while
`implementation` refers to the implementation version.
For RustPython, `version_info` should be 3.14 because it currently
targets Python 3.14. This was correct and remains unchanged.
`implementation`, however, should be set to RustPython's version. I
updated the code to correctly get RustPython's version from the
environment. I also ensured values are hard coded at build time which
reduces the overall code that needs to be run at runtime as well as
removes some unneeded allocations.
Closes: #7770
* Remove unused rust impl for formatting dis output
* remove `examples/dis.rs`
* Added tests
* Update lock
* Try to set snapshot dir
* Remove verbose flag
* Regenerate snapshots after #7711
* Revert "Bump insta from 1.46.3 to 1.47.2 (#7706)"
This reverts commit e6d9ea6bfe.
* Debug info
* Show diff as well
* Show debug faster
* CI: true env
* Recert CI
* Add `CI: true` in ci emv
* Reapply "Bump insta from 1.46.3 to 1.47.2 (#7706)"
This reverts commit 693ca8cbe4d7885a81162a9be31e8bb567db885a.
* simplify macro
* trim on function side
* Force insta workspace root
* fix merge
CPython convention: top-level module / interactive / expression code
does not carry CO_NEWLOCALS or CO_OPTIMIZED. The per-scope mapping at
enter_scope::CompilerScope::Module already returns empty flags, but
Compiler::new seeded the root CodeInfo with CodeFlags::NEWLOCALS,
forcing module code into the NEWLOCALS arm of frame.rs:725-731 so
locals were allocated as a fresh empty dict instead of being bound to
globals (the correct semantics for exec(code, globals)).
Restore the seed to empty() so it matches the per-scope mapping and
CPython's compiler_enter_scope for module scope.
* Align CFG cleanup bytecode with CPython
* Bytecode parity: fblock unwind, fstring join, folding, scope
- compile.rs: unwind_fblock_stack returns whether a finally ran so
return-statement emission can adjust location handling; restructure
try/except/finally cleanup to preserve or drop boundary NOPs based on
whether the body falls through; rework f-string lowering with
count/join helpers; remove the per-collection-type heuristic for
AST-level folding and defer to flowgraph passes; add several folding
helpers and a ComprehensionLoopControl enum.
- ir.rs: re-run unary/binop folding around tuple folding, add
reorder_conditional_scope_exit_and_jump_back_blocks and several block
classification helpers, add MAX_STR_SIZE, change is_exit_without_lineno
to take the block list.
- symboltable.rs: in analyze_cells, remove names owned as cells in
function-like scopes from the parent's free set; mark lambda scope
type explicitly.
* Refine CFG scope-exit backedge ordering
Five related CPython parity gaps in `str` formatting and construction:
1. **`str(bytes, errors=...)` triggers decode mode.** Previously, only
`encoding=` triggered decode; passing only `errors=` fell back to
`repr()`. CPython's behavior: presence of `encoding` OR `errors`
triggers decode mode (default UTF-8 when only `errors` is given).
2. **`'{...}'.format() IndexError wording.** Generic Rust "tuple index
out of range" replaced with CPython's "Replacement index N out of
range for positional args tuple".
3. **`{0:3.2s}.format('abc')` → 'ab '.** String format spec applied
precision after width padding; CPython truncates BEFORE padding.
Reorder the operations.
4. **`%x` / `%o` / `%X` / `%c` accept `__index__` objects.** Previously
only `PyInt` downcast was attempted. Mirror CPython's
PyNumber_Index dispatch via `try_index_opt`.
5. **`%d` / `%u` / `%i` error wording.** "a number is required" →
"a real number is required" (matches CPython).
Also adds `not <type>` suffix to `%c` error messages so the type is
visible in TypeError text (matches CPython structure even without
fully-qualified names).
Verified byte-identical with CPython 3.14.4 across 25+ probes covering
the format/spec/constructor combinations. Unmasks
`test_str.test_constructor_keyword_args` and
`test_str.test_constructor_defaults`. test_str/test_bytes/test_format/
test_codecs/test_io/test_unicode_identifiers — 1,429 tests pass, 0
regressions. All 188 `extra_tests/snippets/*.py` pass under the CI
feature set.
`test_str.test_format` and `test_str.test_formatting` markers retained:
`test_format` still trips on `'{0:08s}'.format('result')` (numeric
zero-pad treated as fill+left-align by CPython for str type — separate
format-spec parser concern). `test_formatting` still trips on
`%c` error message expecting fully qualified `module.qualname` (RP
returns bare class name — separate broader concern).
Two CPython parity gaps in `compile()` argument handling:
1. **filename rejects only non-buffer-protocol types.** CPython's
`PyUnicode_FSDecoder` accepts `str`, `bytes`, and objects with
`__fspath__` only — `bytearray` / `memoryview` / `array.array` raise
TypeError. RustPython's `FsPath::TryFromObject` impl falls back to
any buffer-protocol object and silently converts to bytes, accepting
them.
```python
>>> compile('pass', bytearray(b'file.py'), 'exec')
# CPython 3.14.4: TypeError
# RustPython main: <code object> ❌
```
Change `CompileArgs::filename` from `FsPath` (which uses the
permissive `TryFromObject` impl) to `PyObjectRef`, then call the
strict `FsPath::try_from_path_like` at the top of `compile()`. Other
`FsPath` consumers are unchanged.
2. **dont_inherit strict-checks the `bool` type.** CPython routes
`dont_inherit` through `PyObject_IsTrue`, calling `__bool__` on
arbitrary objects and propagating exceptions raised there. RustPython
typed it `OptionalArg<bool>`, which rejects subclass-less objects at
binding time before `__bool__` runs.
```python
>>> class EvilBool:
... def __bool__(self): raise ValueError('hi')
>>> compile('pass', 'f', 'exec', dont_inherit=EvilBool())
# CPython 3.14.4: ValueError('hi')
# RustPython main: TypeError('Expected type bool, not EvilBool') ❌
```
Switch to `OptionalArg<ArgIntoBool>` — already used elsewhere in
`builtins` (`all`, `any`, `print(..., flush=...)`).
Verified byte-identical with CPython 3.14.4 across:
- 6 filename types (str, bytes, bytearray, memoryview, list, int)
- 7 dont_inherit values (True/False/1/0/""/"yes"/None) plus the
`__bool__`-raises-ValueError case
Unmasks `Lib/test/test_compile.py`:
- `test_compile_filename`
- `test_compile_filename_refleak`
`cargo run --release -- -m test test_compile test_builtin test_compileall
test_codeop test_ast` — 703 tests pass, 0 regressions. All 188
`extra_tests/snippets/*.py` pass under the CI feature set.
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::<u8>()`, 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<i32>
conversion layer, before this check).
CPython raises `OverflowError("cannot convert float infinity to integer")`
and `ValueError("cannot convert float NaN to integer")` from
`Objects/floatobject.c::float___trunc___impl` and friends. The exception
type name is added by Python's traceback display layer; the message
itself should not duplicate it.
`try_to_bigint` was producing
`OverflowError("OverflowError: cannot convert ...")` etc., which made
`repr(e)` and any code path that inspects `str(e)` diverge from CPython.
Affects all 5 callers of `try_to_bigint`: `__int__`, `__floor__`,
`__ceil__`, `__round__` (no-arg), `__trunc__` — i.e. `int(x)`,
`math.floor/ceil/trunc(x)`, `round(x)` for non-finite floats.
Verified byte-identical with CPython 3.14.4 across 14 affected sites.
* Round float at the decimal level to match CPython's _Py_dg_dtoa
CPython's `float.__round__` (Objects/floatobject.c) routes through
`_Py_dg_dtoa` and rounds at the decimal level. The previous
`round_float_digits` multiplied by 10**ndigits and rounded at the
IEEE 754 binary level, which diverges for values that aren't exactly
representable. For example, 2.675 stores as 2.67499...; dtoa correctly
rounds it down to 2.67, but `(2.675 * 100.0).round() / 100.0` lands on
2.68 because the multiplication produces a phantom 267.5 tie that
round-half-to-even snaps up.
Rust's `{:.*}` float formatting uses dtoa-style algorithms (Grisu3 +
Dragon4 fallback) and matches CPython's `_Py_dg_dtoa` byte-for-byte.
Replace the multiply-then-round step with `format!` + `parse` for
ndigits >= 0. The ndigits < 0 path is unchanged because dividing
typical inputs by 10**|ndigits| produces genuine ties rather than
synthesizing them.
Verified byte-identical with CPython 3.14.4 over a 108-case random
fuzz plus targeted half-tie probes. Unmasks
`test_float.RoundTestCase.test_matches_float_format` and
`test_previous_round_bugs`.
* Use #[expect] with reason for float_cmp suppression
Co-authored-by: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com>
---------
Co-authored-by: ShaharNaveh <50263213+ShaharNaveh@users.noreply.github.com>
* Only run `cargo check` when rust code is changed
* Test
* Another test
* Revert "Test"
This reverts commit 1f23cc33f5.
* Revert "Another test"
This reverts commit 50a51d5e56.
* Add extra rust related files
* Convert parsing logic to python
* Trigger job. (DROP ME BEFORE MERGE!)
* Missing EOF
* Add found modules to set
* Suggestion by @fanninpm
* Fix missing trailing slash
* Ensure no `.py` at module name
* Strip any file auffix
* Revert "Trigger job. (DROP ME BEFORE MERGE!)"
This reverts commit 8cf9651a24.
* Handle quoted args
* Fix function attribute tests: __defaults__ del, __code__ free vars check, bound method dir()
- Support `del func.__defaults__` and `del func.__kwdefaults__` by changing
setter signatures to `PySetterValue<Option<...>>` so Delete is treated as
setting the value to None (matching CPython behaviour)
- Validate free-variable count when assigning to `func.__code__`: raise
ValueError when the new code object's freevars count doesn't match the
existing closure size
- Add `__dir__` to `PyBoundMethod` that delegates to the underlying
function's dir(), so attributes set on the function show up in
`dir(bound_method)`
- Remove `@unittest.expectedFailure` from the four tests that now pass:
test_blank_func_defaults, test_func_default_args, test___code__,
test_dir_includes_correct_attrs
* Remove expectedFailure markers for now-passing tests
* Update `test_unicodedata.py` to 3.14.4
* Add test asset
* Mark failing tests
* Add `expectedSuccess` docorator
* Add support for nee decorator in patch_spec
* Reapply patches
The uppercase/titlecase branch of PyStr::title() pushed characters
unchanged when starting a new word, which left Latin Extended-B
digraphs (U+01F1 'DZ', U+01C4 'DŽ', etc.) in their uppercase form
instead of mapping them to their distinct titlecase counterparts
(U+01F2 'Dz', U+01C5 'Dž'). For ASCII letters and characters where
to_titlecase is identity this had no effect, hiding the bug for the
common case.
Mirror the lowercase branch — which already calls to_titlecase()
when starting a new word — so both branches symmetrically apply
the titlecase mapping. char::to_titlecase is identity for already-
titlecase and ASCII-uppercase characters, so existing cases stay
correct.
Also unmasks test_unicodedata.UnicodeMiscTest.test_bug_4971, which
asserts exactly this behavior (`'DŽ'.title() == 'Dž'` etc.)
and was marked expectedFailure with reason `+ Dž`.
Closes#7527 (the only example from that issue still failing on
3.14.4; the other four examples already pass on current main).