Files
hakorune/docs/development/current/main/phases/phase-274/P3-DECISIONS.md
tomoaki 757193891f feat(llvm/phi): Phase 277 P1 - fail-fast validation for PHI strict mode
## 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>
2025-12-22 14:48:37 +09:00

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

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 → itself
  • Integer0 false, non-zero true
  • Float0.0 false, non-zero true
  • String → 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
    • VoidBox is treated as Void → TypeError

Recommended explicit patterns:

  • existence check: x != Void
  • type check: x.is("T") / x.as("T")
  • explicit conversion (if we add it): bool(x) (but bool(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.
  • IntFloat comparisons are allowed (Number-only).
  • Bool is not a number: BoolInt/Float has 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):

  1. If Float is NaN → false
  2. If Float is finite, integral (fractional part is 0), and within i64 exact range:
    • convert Float → Int exactly, then compare Ints
  3. 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 to false if 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 + IntInt
  • Float + FloatFloat
  • Int + Float / Float + IntFloat (promote Int→Float)
  • String + String → concat
  • String + non-string / non-string + StringTypeError (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

6.2 equality

  • same-kind primitives
  • Int↔Float:
    • 1 == 1.0 true
    • 1 == 1.1 false
    • NaN == NaN false
  • 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:

  1. Compatibility freeze (Phase 274)
  • Document current behavior (already in types.md)
  • Add warnings / diagnostics where possible (no new env sprawl)
  1. 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.)