## Summary Implemented fail-fast validation for PHI ordering and value resolution in strict mode. ## Changes ### P1-1: Strict mode for "PHI after terminator" - File: `src/llvm_py/phi_wiring/wiring.py::ensure_phi` - Behavior: `NYASH_LLVM_PHI_STRICT=1` → RuntimeError if PHI created after terminator - Default: Warning only (no regression) ### P1-2: Strict mode for "fallback 0" - File: `src/llvm_py/phi_wiring/wiring.py::wire_incomings` - Behavior: Strict mode forbids silent fallback to 0 (2 locations) - Location 1: Unresolvable incoming value - Location 2: Type coercion failure - Error messages point to next debug file: `llvm_builder.py::_value_at_end_i64` ### P1-3: Connect verify_phi_ordering() to execution path - File: `src/llvm_py/builders/function_lower.py` - Behavior: Verify PHI ordering after all instructions emitted - Debug mode: Shows "✅ All N blocks have correct PHI ordering" - Strict mode: Raises RuntimeError with block list if violations found ## Testing ✅ Test 1: strict=OFF - passes without errors ✅ Test 2: strict=ON - passes without errors (no violations in test fixtures) ✅ Test 3: debug mode - verify_phi_ordering() connected and running ## Scope - LLVM harness (Python) changes only - No new environment variables (uses existing 3 from Phase 277 P2) - No JoinIR/Rust changes (root fix is Phase 279) - Default behavior unchanged (strict mode opt-in) ## Next Steps - Phase 278: Remove deprecated env var support - Phase 279: Root fix - unify "2本のコンパイラ" pipelines 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
4.7 KiB
Phase 274 P3 (decision): Coercion SSOT (truthiness / == / +)
Status: accepted (2025-12-22) / pending implementation
This document freezes what coercions mean at the language level, so runtime behavior cannot “emerge” from resolver/type-facts.
SSOT anchor (current executable behavior): docs/reference/language/types.md
Phase overview: docs/development/current/main/phases/phase-274/README.md
Implementation phase: docs/development/current/main/phases/phase-275/README.md
1) Terms
In this doc, “coercion” means: when operands/types differ, do we:
- convert implicitly,
- return a deterministic result (e.g.
false), - or fail-fast (
TypeError)?
Target constraints:
- Fail-Fast where it prevents silent bugs
- No “JS-style surprise coercion”
- Dynamic runtime remains (no static type system required)
- Backend parity (VM/LLVM) or explicit divergence, never accidental drift
2) Proposed SSOT (recommended)
Based on the project philosophy, the recommended SSOT choice is:
- truthiness: A1
==: B2 (Number-only)+: C2 (Number-only promotion)
The sections below define each choice precisely.
3) truthiness (boolean context)
Decision: A1 (Fail-Fast)
Void in condition is TypeError.
Allowed in boolean context:
Bool→ itselfInteger→0false, non-zero trueFloat→0.0false, non-zero trueString→ empty false, otherwise true
Disallowed (TypeError):
Void(always error)BoxRef(by default)- Exception: only explicit bridge boxes may be unboxed to the corresponding primitive for truthiness:
BoolBox/IntegerBox/StringBox
VoidBoxis treated asVoid→ TypeError
- Exception: only explicit bridge boxes may be unboxed to the corresponding primitive for truthiness:
Recommended explicit patterns:
- existence check:
x != Void - type check:
x.is("T")/x.as("T") - explicit conversion (if we add it):
bool(x)(butbool(Void)remains TypeError)
Implementation impact (where to change):
- Rust VM:
src/backend/abi_util.rs::to_bool_vm - LLVM harness: must match the VM semantics used for branch conditions
4) == (equality)
Decision: B2 (Number-only)
Rules:
- Same-kind primitives compare normally.
Int↔Floatcomparisons are allowed (Number-only).Boolis not a number:Bool↔Int/Floathas no coercion.- Other mixed kinds: deterministic
false(not an error). BoxRef == BoxRef: identity only.
Precise rule for Int == Float (avoid “accidental true”)
To avoid float rounding making true incorrectly:
For Int == Float (or Float == Int):
- If Float is NaN →
false - If Float is finite, integral (fractional part is 0), and within
i64exact range:- convert Float → Int exactly, then compare Ints
- Otherwise →
false
Migration note:
- If legacy behavior existed for
1 == true, prefer a transition phase where it becomes TypeError first (to surface bugs), then settle tofalseif desired.
Implementation impact:
- Rust VM:
src/backend/abi_util.rs::eq_vm(and any helpers) - LLVM harness: must mirror the same decision for
compare ==lowering
5) + (add / concat)
Decision: C2 (Number-only promotion)
Rules:
Int + Int→IntFloat + Float→FloatInt + Float/Float + Int→Float(promote Int→Float)String + String→ concatString + non-string/non-string + String→ TypeError (no implicit stringify)- Other combos → TypeError
Implementation impact:
- Rust VM:
src/backend/mir_interpreter/helpers.rs::eval_binop(BinaryOp::Add) - LLVM harness: binop
+lowering must follow the same coercion rules
6) Minimum test matrix (SSOT lock)
6.1 truthiness
- Bool:
if true,if false - Int:
if 0,if 1,if -1 - Float:
if 0.0,if 0.5,if NaN(define if NaN counts as truthy) - String:
if "",if "a" - Void:
if Void→ TypeError (A1) - BoxRef:
- bridge:
BoolBox(true),IntegerBox(0),StringBox("") - non-bridge:
Foo()→ TypeError
- bridge:
6.2 equality
- same-kind primitives
- Int↔Float:
1 == 1.0true1 == 1.1falseNaN == NaNfalse
- Bool↔Int:
true == 1(explicitly decide: TypeError during migration vs final false)
- BoxRef identity:
- same handle true, different handles false
6.3 plus
- Int/Float add
- Int+Float promotion
- String+String concat
- String mixed TypeError
7) Migration plan (if changing behavior)
Recommended two-step approach:
- Compatibility freeze (Phase 274)
- Document current behavior (already in
types.md) - Add warnings / diagnostics where possible (no new env sprawl)
- Switch semantics (Phase 275 or later)
- Implement A1/B2/C2 in VM and LLVM
- Add fixtures to lock the SSOT
- Ensure error messages provide “fix-it” guidance (
str(x),x != Void, etc.)