Releases: paiml/xpile
v0.1.376 — float format spec width+precision (PMAT-677)
Capability / correctness (PMAT-677): a float format spec combining width (and/or zero-pad, sign, fill+align) WITH precision — f"{x:8.3f}", {:06.2f}, {:+8.2f}, {:10.4f}, {:>8.2f}, {:*>8.2f} — now transpiles (it was rejected; only pure .Nf worked). This is the most common numeric-table formatting idiom. Rust's {:8.3}/{:06.2}/{:>8.2} match Python exactly for floats — the .Nf precision forces the decimal count, avoiding the whole-float repr divergence that defers bare float widths. A new float_pad_prec helper translates the [0][width].Nf core; the fix covers both f-string and .format(). Found by the HUNT-V6 differential hunt; verified vs python3. 434 e2e fixtures.
v0.1.375 — str.format bare-radix negatives are sign-magnitude (PMAT-676)
Correctness (PMAT-676): a bare-radix str.format spec over a negative int ("{:x}".format(-255), {:X}/{:o}/{:b}) now emits sign-magnitude (-ff) like Python, not Rust's two's-complement (ffffffffffffff01). The f-string path already did this (PMAT-613); .format() now shares the IntRadixStr lowering (sign + radix of unsigned_abs). Applied when the arg is referenced exactly once (a pre-scan ref-count keeps the in-place rewrite safe); a multi-reference arg keeps the existing embed (correct for non-negatives — no regression). Found by the HUNT-V5 differential hunt; verified vs python3 (hex/oct/bin/HEX, mixed fields, literal-surrounded). 433 e2e fixtures.
v0.1.374 — str find/count with start/end slice bounds (PMAT-675)
Capability / correctness (PMAT-675): s.find(sub, start[, end]) and s.count(sub, start[, end]) now transpile (they were rejected — "expected exactly 1" arg). Python searches within the char-slice s[start:end]: find returns the CHAR index in the original string (or -1); count the number of non-overlapping occurrences within the slice. The codegen (rust + ruchy) char-slices the receiver to a fresh String of the selected chars, applies Python clamping to start/end (negative → +len, then clamp to [0, len]; end defaults to len for the 2-arg form), and keeps the byte→char conversion and result offset correct for non-ASCII. Found by the HUNT-V5 differential hunt; verified vs python3 (negative start, miss, non-ASCII char indexing). 432 e2e fixtures.
v0.1.373 — len(s.encode()) → UTF-8 byte length (PMAT-674)
Capability / correctness (PMAT-674): len(s.encode()) now transpiles to the UTF-8 byte length of a string (s.len()). s.encode() defaults to UTF-8, so len(s.encode()) equals Rust String::len() — distinct from len(s), which counts Unicode code points (.chars().count()). .encode() is otherwise an unsupported method call, so the whole idiom rejected; it's now detected in the len() intercept and routed to Expr::Len over the receiver. Accepts a bare .encode() or an explicit utf-8/utf8 literal arg. Found by the HUNT-V5 differential hunt; verified vs python3 (café → 5 bytes / 4 chars, € → 3 bytes). 431 e2e fixtures.
v0.1.372 — tuple concatenation a + b (PMAT-673)
Capability / correctness (PMAT-673): a + b over two tuples now transpiles as concatenation ((1, 2) + (3, 4) → (1, 2, 3, 4)). Rust tuples have no +, so the old path fell through to the i64-arith lowering — the result mis-typed as I64 and was rejected ("body produces I64") or would have emitted (a).checked_add(b) (E0599). Fixed by building a fresh tuple of all fields of both operands (via TupleIndex over the original operand, so it type-infers correctly for any element type). Restricted to side-effect-free operands (a name or tuple literal); a call-result operand rejects cleanly rather than re-evaluating it per field. Found by the HUNT-V5 differential hunt; verified vs python3 (int/str tuples, chained a + b + c). Extends the tuple cluster. 430 e2e fixtures.
v0.1.371 — chained comparison short-circuits like Python (PMAT-672)
Correctness (PMAT-672): a chained comparison a < b < c now SHORT-CIRCUITS like Python — it stops at the first false sub-comparison and never evaluates the trailing operands. PMAT-576's flat let __cmpN + && fold hoisted every operand up front, so a panic-prone or side-effecting trailing operand (10 < n < (100 // dv) with dv == 0) ran even when an earlier compare was false — a divide-by-zero panic / spurious side effect where Python returns False. Fixed by emitting a right-nested form (each shared operand bound inside the prior compare's if), which evaluates each operand exactly once (preserving the PMAT-576 single-eval guarantee) AND only when Python would. Found by the HUNT-V5 differential hunt; verified vs python3. 429 e2e fixtures.
v0.1.370 — x in tuple → chained-OR membership (PMAT-671)
Correctness (PMAT-671): x in t / x not in t over a fixed-arity tuple now works (it was rejected as "unsupported comparison operator: In") — Rust tuples have no .contains, so it's lowered to a chained-OR of equalities x == t.0 || x == t.1 || … (not in negated). Homogeneous tuple matching the needle type; empty tuple → False. Found by the HUNT-V5 differential hunt; verified vs python3. Completes the tuple cluster (sum/min/max + negative index + membership). 428 e2e fixtures.
v0.1.369 — negative constant tuple index t[-k] (PMAT-670)
Correctness (PMAT-670): a negative constant tuple index t[-1] now resolves to the field access t.(arity-1) at compile time (Python from-the-end) instead of emitting list-style .len()/[] indexing (E0599 — Rust tuples have neither). Works for heterogeneous tuples too. An out-of-range literal and a runtime-variable tuple index now reject cleanly (a heterogeneous fixed-arity tuple can't be runtime-indexed). Found by the HUNT-V5 differential hunt; verified vs python3. 427 e2e fixtures.
v0.1.368 — sum/min/max over a tuple (PMAT-669)
Correctness (PMAT-669): sum(t) / min(t) / max(t) over a fixed-arity tuple now work (e.g. sum((3, 7, 2)) → 12) instead of emitting an undefined sum(t) free call (E0425). A tuple is iterable in Python; the builtin argument-materializer now treats a tuple as a list of its elements (a literal uses its elements directly; a variable indexes each field). The shared materializer makes sum/min/max over a homogeneous tuple all work. Found by the HUNT-V5 differential hunt; verified vs python3. 426 e2e fixtures.
v0.1.367 — sep.join(d) over a dict joins its keys (PMAT-668)
Correctness (PMAT-668): ",".join(d) over a dict now joins its keys (like Python) instead of emitting d.join(...) on a HashMap (E0599); the join argument is materialized to the dict's keys, the same as ",".join(d.keys()). Set-join also works as a bonus. Found by the HUNT-V4 differential hunt; verified vs python3. (Multi-key join order is the deferred PMAT-537 dict-iteration-order limitation.) 425 e2e fixtures.