Files
hakorune/docs/archive/cleanup/PHASE1_IMPLEMENTATION_GUIDE.md
nyash-codex a7dbc15878 feat(joinir): Phase 240-EX - Pattern2 header condition ExprLowerer integration
Implementation:
- Add make_pattern2_scope_manager() helper for DRY
- Header conditions use ExprLowerer for supported patterns
- Legacy fallback for unsupported patterns
- Fail-Fast on supported patterns that fail

Tests:
- 4 new tests (all pass)
- test_expr_lowerer_supports_simple_header_condition_i_less_literal
- test_expr_lowerer_supports_header_condition_var_less_var
- test_expr_lowerer_header_condition_generates_expected_instructions
- test_pattern2_header_condition_via_exprlowerer

Also: Archive old phase documentation (34k lines removed)

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-11 00:33:04 +09:00

14 KiB
Raw Blame History

Phase 1実装ガイド - 低リスク・高効果ヘルパー関数

概要

Phase 1では以下3つのヘルパー関数を実装し、約270-380行のコード削減を目指します。

  1. Destination書き込みヘルパー150-200行削減
  2. 引数検証ヘルパー100-150行削減
  3. Receiver変換ヘルパー20-30行削減

実装時間見込み: 5-8時間 リスク: 低 優先度: 最高


実装手順

Step 1: ユーティリティモジュール作成

# ディレクトリ構造作成
mkdir -p src/backend/mir_interpreter/utils
touch src/backend/mir_interpreter/utils/mod.rs
touch src/backend/mir_interpreter/utils/register_ops.rs
touch src/backend/mir_interpreter/utils/validation.rs
touch src/backend/mir_interpreter/utils/conversions.rs

Step 2: register_ops.rs 実装

ファイル: src/backend/mir_interpreter/utils/register_ops.rs

use crate::backend::mir_interpreter::MirInterpreter;
use crate::box_trait::NyashBox;
use crate::mir::ValueId;
use crate::backend::mir_interpreter::VMValue;

impl MirInterpreter {
    /// Write a VMValue result to destination register if present.
    ///
    /// # Example
    /// ```rust
    /// self.write_result(dst, VMValue::Integer(42));
    /// ```
    pub(crate) fn write_result(&mut self, dst: Option<ValueId>, value: VMValue) {
        if let Some(d) = dst {
            self.regs.insert(d, value);
        }
    }

    /// Write a NyashBox result to destination register (converted to VMValue::BoxRef).
    ///
    /// # Example
    /// ```rust
    /// let result_box = string_box.trim();
    /// self.write_box_result(dst, result_box);
    /// ```
    pub(crate) fn write_box_result(&mut self, dst: Option<ValueId>, boxed: Box<dyn NyashBox>) {
        self.write_result(dst, VMValue::from_nyash_box(boxed));
    }

    /// Write VMValue::Void to destination register.
    ///
    /// # Example
    /// ```rust
    /// arr.push(item);
    /// self.write_void(dst);  // push returns void
    /// ```
    pub(crate) fn write_void(&mut self, dst: Option<ValueId>) {
        self.write_result(dst, VMValue::Void);
    }

    /// Write integer value to destination register.
    pub(crate) fn write_integer(&mut self, dst: Option<ValueId>, value: i64) {
        self.write_result(dst, VMValue::Integer(value));
    }

    /// Write boolean value to destination register.
    pub(crate) fn write_bool(&mut self, dst: Option<ValueId>, value: bool) {
        self.write_result(dst, VMValue::Bool(value));
    }

    /// Write string value to destination register.
    pub(crate) fn write_string(&mut self, dst: Option<ValueId>, value: String) {
        self.write_result(dst, VMValue::String(value));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    #[test]
    fn test_write_result_with_dst() {
        let mut interp = MirInterpreter {
            regs: HashMap::new(),
            // ... other fields
        };
        let dst = Some(ValueId(42));
        interp.write_result(dst, VMValue::Integer(100));
        assert_eq!(interp.regs.get(&ValueId(42)), Some(&VMValue::Integer(100)));
    }

    #[test]
    fn test_write_result_without_dst() {
        let mut interp = MirInterpreter {
            regs: HashMap::new(),
            // ... other fields
        };
        interp.write_result(None, VMValue::Integer(100));
        assert!(interp.regs.is_empty());
    }

    #[test]
    fn test_write_void() {
        let mut interp = MirInterpreter {
            regs: HashMap::new(),
            // ... other fields
        };
        let dst = Some(ValueId(10));
        interp.write_void(dst);
        assert_eq!(interp.regs.get(&ValueId(10)), Some(&VMValue::Void));
    }
}

Step 3: validation.rs 実装

ファイル: src/backend/mir_interpreter/utils/validation.rs

use crate::backend::mir_interpreter::{MirInterpreter, VMError};
use crate::mir::ValueId;

impl MirInterpreter {
    /// Validate that the method call has exactly the expected number of arguments.
    ///
    /// # Example
    /// ```rust
    /// self.validate_args_exact("push", args, 1)?;
    /// let val = self.reg_load(args[0])?;
    /// ```
    ///
    /// # Errors
    /// Returns `VMError::InvalidInstruction` if argument count doesn't match.
    pub(crate) fn validate_args_exact(
        &self,
        method: &str,
        args: &[ValueId],
        expected: usize,
    ) -> Result<(), VMError> {
        let actual = args.len();
        if actual != expected {
            return Err(VMError::InvalidInstruction(format!(
                "{} expects exactly {} argument(s), but got {}",
                method, expected, actual
            )));
        }
        Ok(())
    }

    /// Validate that the method call has arguments within the expected range (inclusive).
    ///
    /// # Example
    /// ```rust
    /// // substring accepts 1 or 2 arguments
    /// self.validate_args_range("substring", args, 1, 2)?;
    /// ```
    ///
    /// # Errors
    /// Returns `VMError::InvalidInstruction` if argument count is out of range.
    pub(crate) fn validate_args_range(
        &self,
        method: &str,
        args: &[ValueId],
        min: usize,
        max: usize,
    ) -> Result<(), VMError> {
        let actual = args.len();
        if actual < min || actual > max {
            return Err(VMError::InvalidInstruction(format!(
                "{} expects {}-{} argument(s), but got {}",
                method, min, max, actual
            )));
        }
        Ok(())
    }

    /// Validate that the method call has at least the minimum number of arguments.
    ///
    /// # Example
    /// ```rust
    /// self.validate_args_min("varargs_method", args, 2)?;
    /// ```
    pub(crate) fn validate_args_min(
        &self,
        method: &str,
        args: &[ValueId],
        min: usize,
    ) -> Result<(), VMError> {
        let actual = args.len();
        if actual < min {
            return Err(VMError::InvalidInstruction(format!(
                "{} expects at least {} argument(s), but got {}",
                method, min, actual
            )));
        }
        Ok(())
    }

    /// Validate that the method call has no arguments.
    ///
    /// # Example
    /// ```rust
    /// self.validate_args_empty("pop", args)?;
    /// ```
    pub(crate) fn validate_args_empty(&self, method: &str, args: &[ValueId]) -> Result<(), VMError> {
        self.validate_args_exact(method, args, 0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn dummy_interp() -> MirInterpreter {
        // Create minimal interpreter for testing
        MirInterpreter {
            // ... minimal fields
        }
    }

    #[test]
    fn test_validate_args_exact_ok() {
        let interp = dummy_interp();
        let args = vec![ValueId(1)];
        assert!(interp.validate_args_exact("push", &args, 1).is_ok());
    }

    #[test]
    fn test_validate_args_exact_too_many() {
        let interp = dummy_interp();
        let args = vec![ValueId(1), ValueId(2)];
        let result = interp.validate_args_exact("push", &args, 1);
        assert!(result.is_err());
        assert!(result.unwrap_err().to_string().contains("expects exactly 1"));
    }

    #[test]
    fn test_validate_args_range_ok() {
        let interp = dummy_interp();
        let args = vec![ValueId(1), ValueId(2)];
        assert!(interp.validate_args_range("substring", &args, 1, 2).is_ok());
    }

    #[test]
    fn test_validate_args_range_too_few() {
        let interp = dummy_interp();
        let args = vec![];
        let result = interp.validate_args_range("substring", &args, 1, 2);
        assert!(result.is_err());
    }
}

Step 4: conversions.rs 実装

ファイル: src/backend/mir_interpreter/utils/conversions.rs

use crate::backend::mir_interpreter::{MirInterpreter, VMValue};
use crate::box_trait::NyashBox;

impl MirInterpreter {
    /// Convert a VMValue to Box<dyn NyashBox>, handling both BoxRef and primitive types.
    ///
    /// # Example
    /// ```rust
    /// let recv = self.reg_load(box_val)?;
    /// let recv_box = self.convert_to_box(&recv);
    /// ```
    pub(crate) fn convert_to_box(&self, value: &VMValue) -> Box<dyn NyashBox> {
        match value.clone() {
            VMValue::BoxRef(b) => b.share_box(),
            other => other.to_nyash_box(),
        }
    }

    /// Convert and downcast to a specific box type.
    ///
    /// # Example
    /// ```rust
    /// if let Some(arr) = self.downcast_box::<ArrayBox>(&recv)? {
    ///     // Handle ArrayBox methods
    /// }
    /// ```
    pub(crate) fn downcast_box<T: NyashBox + 'static>(
        &self,
        value: &VMValue,
    ) -> Option<&T> {
        let boxed = self.convert_to_box(value);
        boxed.as_any().downcast_ref::<T>()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::boxes::array::ArrayBox;

    #[test]
    fn test_convert_to_box_from_primitive() {
        let interp = dummy_interp();
        let value = VMValue::Integer(42);
        let boxed = interp.convert_to_box(&value);
        // Should convert to IntegerBox
        assert!(boxed.as_any().is::<crate::boxes::integer::IntegerBox>());
    }

    #[test]
    fn test_convert_to_box_from_boxref() {
        let interp = dummy_interp();
        let arr = Box::new(ArrayBox::new());
        let value = VMValue::BoxRef(arr.into());
        let boxed = interp.convert_to_box(&value);
        assert!(boxed.as_any().is::<ArrayBox>());
    }
}

Step 5: mod.rs 設定

ファイル: src/backend/mir_interpreter/utils/mod.rs

//! Utility functions for MIR interpreter handlers.
//!
//! This module provides common patterns used across handler implementations:
//! - Register operations (writing results)
//! - Argument validation
//! - Type conversions

mod conversions;
mod register_ops;
mod validation;

// Re-export for convenience (optional)
pub(crate) use conversions::*;
pub(crate) use register_ops::*;
pub(crate) use validation::*;

Step 6: ハンドラー更新 - 段階的移行

例: boxes_array.rs の更新

Before:

"push" => {
    if args.len() != 1 {
        return Err(VMError::InvalidInstruction("push expects 1 arg".into()));
    }
    let val = this.reg_load(args[0])?.to_nyash_box();
    let _ = ab.push(val);
    if let Some(d) = dst {
        this.regs.insert(d, VMValue::Void);
    }
    return Ok(true);
}

After:

"push" => {
    this.validate_args_exact("push", args, 1)?;
    let val = this.reg_load(args[0])?.to_nyash_box();
    let _ = ab.push(val);
    this.write_void(dst);
    return Ok(true);
}

削減: 6行 → 4行2行削減


移行チェックリスト

Phase 1-A: インフラ構築1-2時間

  • utils/ ディレクトリ作成
  • mod.rs, register_ops.rs, validation.rs, conversions.rs 実装
  • ユニットテスト追加
  • 全テストが通ることを確認

Phase 1-B: Handler更新3-5時間

ファイル単位で更新し、都度テストを実行:

  1. boxes_array.rs 更新(最も小さいファイルから開始)

    • 引数検証をvalidate_args_*に置き換え
    • Destination書き込みをwrite_*に置き換え
    • Receiver変換をconvert_to_boxに置き換え
    • テスト実行
  2. boxes_map.rs 更新

  3. boxes_string.rs 更新

  4. boxes_plugin.rs 更新

  5. boxes_instance.rs 更新

  6. boxes_object_fields.rs 更新

  7. boxes.rs 更新

  8. calls.rs 更新(大きいファイルは最後に)

Phase 1-C: 検証1時間

  • 全ハンドラーファイルでパターン検索
    # 残存する古いパターンを確認
    grep -rn "if let Some(d) = dst { this.regs.insert" src/backend/mir_interpreter/handlers/
    grep -rn "args.len() !=" src/backend/mir_interpreter/handlers/
    grep -rn "match recv.clone()" src/backend/mir_interpreter/handlers/
    
  • スモークテスト実行
    ./tools/jit_smoke.sh
    
  • ドキュメント更新

テスト戦略

単体テスト

各ユーティリティ関数に対して:

  • 正常系(期待通りの動作)
  • 異常系(エラーケース)
  • 境界値0引数、大量引数など

回帰テスト

既存のスモークテストを活用:

# 変更前にベースライン取得
./tools/jit_smoke.sh > /tmp/before.log 2>&1

# 変更適用

# 変更後に結果比較
./tools/jit_smoke.sh > /tmp/after.log 2>&1
diff /tmp/before.log /tmp/after.log

トラブルシューティング

問題: コンパイルエラー「method not found」

原因: utils/mod.rs のインポートが不足 解決: src/backend/mir_interpreter/mod.rs に以下を追加

mod utils;

問題: テスト実行時のパニック

原因: MirInterpreter の最小構成が不完全 解決: テスト用のビルダー関数を用意

#[cfg(test)]
fn test_interpreter() -> MirInterpreter {
    MirInterpreter::new_for_test()
}

問題: 既存テストが失敗

原因: エラーメッセージの形式変更 解決: テストの期待値を更新(より良いメッセージに改善された)


期待される結果

コード削減

  • boxes_array.rs: 63行 → 約50行13行削減、21%減)
  • boxes_map.rs: 134行 → 約110行24行削減、18%減)
  • boxes_string.rs: 208行 → 約170行38行削減、18%減)
  • 全体: 約270-380行削減8-11%減)

品質向上

  • エラーメッセージの統一
  • テストカバレッジの向上
  • 新機能追加の容易性

次のステップPhase 2

Phase 1完了後、以下を実施

  1. Phase 2計画エラー生成、PHIヘルパー
  2. 追加の共通化機会の調査
  3. Phase 3Box Handler統合の詳細設計

実装者: (名前) 開始日: (日付) 完了予定: (日付) 実績: (完了日)