Files
hakorune/docs/development/current/main/phase224b-methodcall-lowerer.md
nyash-codex d4f90976da refactor(joinir): Phase 244 - ConditionLoweringBox trait unification
Unify condition lowering logic across Pattern 2/4 with trait-based API.

New infrastructure:
- condition_lowering_box.rs: ConditionLoweringBox trait + ConditionContext (293 lines)
- ExprLowerer implements ConditionLoweringBox trait (+51 lines)

Pattern migrations:
- Pattern 2 (loop_with_break_minimal.rs): Use trait API
- Pattern 4 (loop_with_continue_minimal.rs): Use trait API

Benefits:
- Unified condition lowering interface
- Extensible for future lowering strategies
- Clean API boundary between patterns and lowering logic
- Zero code duplication

Test results: 911/911 PASS (+2 new tests)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-11 02:35:31 +09:00

7.0 KiB
Raw Blame History

Phase 224-B: MethodCallLowerer + CoreMethodId Extension

Status: Complete Date: 2025-12-10 Purpose: Metadata-driven MethodCall lowering for JoinIR loop conditions


🎯 Overview

Phase 224-B implements generic MethodCall lowering infrastructure for JoinIR loop conditions. This enables s.length(), indexOf(), etc. to be used in loop conditions with full type safety.

Key Achievement

  • Metadata-Driven Design: No method name hardcoding - all decisions based on CoreMethodId
  • Box-First Architecture: MethodCallLowerer as independent box answering one question
  • Type-Safe: Uses existing CoreMethodId infrastructure (box_id, arity, return_type)

📦 Implementation

224-B-1: CoreMethodId Extension

File: src/runtime/core_box_ids.rs (+114 lines)

Added three new methods to CoreMethodId:

impl CoreMethodId {
    /// Pure function (no side effects, deterministic)
    pub fn is_pure(&self) -> bool { ... }

    /// Allowed in loop condition expressions
    pub fn allowed_in_condition(&self) -> bool { ... }

    /// Allowed in loop body init expressions
    pub fn allowed_in_init(&self) -> bool { ... }
}

Whitelist Design (Conservative Fail-Fast):

  • Condition: StringLength, ArrayLength, MapHas, ResultIsOk (4 methods)
  • Init: More permissive - includes substring, indexOf, MapGet, etc. (13 methods)
  • Pure but not whitelisted: Still rejected to avoid surprises

Tests: 3 new unit tests (16 total CoreMethodId tests, all pass)


224-B-2: MethodCallLowerer Box

File: src/mir/join_ir/lowering/method_call_lowerer.rs (+362 lines, new)

Single-responsibility box:

pub struct MethodCallLowerer;

impl MethodCallLowerer {
    /// Lower MethodCall for loop condition
    pub fn lower_for_condition<F>(
        recv_val: ValueId,
        method_name: &str,
        args: &[ASTNode],
        alloc_value: &mut F,
        instructions: &mut Vec<JoinInst>,
    ) -> Result<ValueId, String> { ... }

    /// Lower MethodCall for LoopBodyLocal init
    pub fn lower_for_init<F>(...) -> Result<ValueId, String> { ... }
}

Phase 224-B P0 Restrictions:

  • Zero-argument methods only (s.length())
  • Methods with arguments (s.substring(0, 1)) - Phase 224-C

JoinIR BoxCall Emission:

// Input: s.length()
// Output:
JoinInst::Compute(MirLikeInst::BoxCall {
    dst: Some(ValueId(100)),
    box_name: "StringBox",  // From CoreMethodId.box_id().name()
    method: "length",
    args: vec![recv_val],   // Receiver is first arg
})

Tests: 6 unit tests covering:

  • Resolve string.length → CoreMethodId::StringLength
  • Lower for condition (allowed)
  • Lower for init (more permissive)
  • Not allowed in condition (Fail-Fast)
  • Unknown method (Fail-Fast)
  • P0 restriction (no arguments)

224-B-3: Integration with condition_lowerer

File: src/mir/join_ir/lowering/condition_lowerer.rs (+17 lines)

Added MethodCall case to lower_value_expression:

ASTNode::MethodCall { object, method, arguments, .. } => {
    // 1. Lower receiver to ValueId
    let recv_val = lower_value_expression(object, alloc_value, env, instructions)?;

    // 2. Lower method call using MethodCallLowerer
    MethodCallLowerer::lower_for_condition(recv_val, method, arguments, alloc_value, instructions)
}

Previously: Err("Unsupported expression in value context: MethodCall") Now: Full MethodCall lowering with type safety!


224-B-4: Module Registration

File: src/mir/join_ir/lowering/mod.rs (+1 line)

pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)

Test Results

Unit Tests

cargo test --release --lib method_call_lowerer
# test result: ok. 6 passed; 0 failed

cargo test --release --lib core_box_ids::tests
# test result: ok. 16 passed; 0 failed

Build Status

cargo build --release
# Finished `release` profile [optimized] target(s) in 1m 13s
# 0 errors, 7 warnings (pre-existing)

🎯 Usage Patterns

Pattern 2: Loop with MethodCall Condition

// Phase 224-B: s.length() in condition now supported!
loop(i < s.length()) {
    // MethodCall lowered to BoxCall in JoinIR
    i = i + 1
}

Before Phase 224-B:

[ERROR] Unsupported expression in value context: MethodCall

After Phase 224-B:

✅ MethodCall lowered to BoxCall("StringBox", "length", [s_val])

🔍 Design Principles Demonstrated

1. Box-First Architecture

  • MethodCallLowerer: Standalone box, no dependencies on specific patterns
  • Single Responsibility: "Can this MethodCall be lowered?" - that's it
  • Composable: Used by condition_lowerer, body_local_init, etc.

2. Metadata-Driven

NO method name hardcoding:

// ❌ Bad (hardcoded):
if method_name == "length" { ... }

// ✅ Good (metadata-driven):
let method_id = CoreMethodId::iter().find(|m| m.name() == method_name)?;
if method_id.allowed_in_condition() { ... }

3. Fail-Fast

  • Unknown method → Immediate error
  • Not whitelisted → Immediate error
  • Arguments in P0 → Immediate error with clear message

📊 Code Metrics

Component Lines Tests Status
CoreMethodId extension +114 3
MethodCallLowerer box +362 6
condition_lowerer integration +17 (covered)
Total +493 9

Net Impact: +493 lines, 9 new tests, 0 regressions


🚀 Next Steps

Phase 224-C: MethodCall Arguments Support

// Phase 224-C target:
local ch = s.substring(i, i+1)  // 2-argument MethodCall
local pos = digits.indexOf(ch)  // 1-argument MethodCall

Requirements:

  1. Extend MethodCallLowerer to handle arguments
  2. Lower argument AST nodes to ValueIds
  3. Pass argument ValueIds in BoxCall.args (after receiver)

Phase 224-D: Option B - Promoted Variable Tracking

Fix the remaining issue in phase2235_p2_digit_pos_min.hako:

[ERROR] Variable 'digit_pos' not bound in ConditionEnv

Root Cause: Promotion system promotes digit_posis_digit_pos, but condition lowerer still expects digit_pos.

Solution: Track promoted variables in CarrierInfo, remap during lowering.


📚 References

  • CoreBoxId/CoreMethodId: src/runtime/core_box_ids.rs (Phase 87)
  • JoinIR BoxCall: src/mir/join_ir/mod.rs (MirLikeInst enum)
  • Condition Lowering: src/mir/join_ir/lowering/condition_lowerer.rs (Phase 171)
  • Phase 224 Summary: docs/development/current/main/PHASE_224_SUMMARY.md

Key Takeaway

Phase 224-B establishes the foundation for generic MethodCall lowering in JoinIR.

  • No more "Unsupported expression" errors for common methods
  • Type-safe, metadata-driven, extensible architecture
  • Ready for Phase 224-C (arguments) and Phase 224-D (variable remapping)

Everything is a Box. Everything is metadata-driven. Everything Fail-Fast. Status: Active
Scope: methodcall lowerer 設計ExprLowerer ライン)