Files
hakorune/src/mir/join_ir/lowering/debug_output_box.rs
nyash-codex d2972c1437 feat(joinir): Phase 92完了 - ConditionalStep + body-local変数サポート
## Phase 92全体の成果

**Phase 92 P0-P2**: ConditionalStep JoinIR生成とbody-local変数サポート
- ConditionalStep(条件付きキャリア更新)のJoinIR生成実装
- Body-local変数(ch等)の条件式での参照サポート
- 変数解決優先度: ConditionEnv → LoopBodyLocalEnv

**Phase 92 P3**: BodyLocalPolicyBox + 安全ガード
- BodyLocalPolicyDecision実装(Accept/Reject判定)
- BodyLocalSlot + DualValueRewriter(JoinIR/MIR二重書き込み)
- Fail-Fast契約(Cannot promote LoopBodyLocal検出)

**Phase 92 P4**: E2E固定+回帰最小化 (本コミット)
- Unit test 3本追加(body-local変数解決検証)
- Integration smoke追加(phase92_pattern2_baseline.sh、2ケースPASS)
- P4-E2E-PLAN.md、P4-COMPLETION.md作成

## 主要な実装

### ConditionalStep(条件付きキャリア更新)
- `conditional_step_emitter.rs`: JoinIR Select命令生成
- `loop_with_break_minimal.rs`: ConditionalStep検出と統合
- `loop_with_continue_minimal.rs`: Pattern4対応

### Body-local変数サポート
- `condition_lowerer.rs`: body-local変数解決機能
  - `lower_condition_to_joinir`: body_local_env パラメータ追加
  - 変数解決優先度実装(ConditionEnv優先)
  - Unit test 3本追加: 変数解決/優先度/エラー
- `header_break_lowering.rs`: break条件でbody-local変数参照
- 7ファイルで後方互換ラッパー(lower_condition_to_joinir_no_body_locals)

### Body-local Policy & Safety
- `body_local_policy.rs`: BodyLocalPolicyDecision(Accept/Reject)
- `body_local_slot.rs`: JoinIR/MIR二重書き込み
- `dual_value_rewriter.rs`: ValueId書き換えヘルパー

## テスト体制

### Unit Tests (+3)
- `test_body_local_variable_resolution`: body-local変数解決
- `test_variable_resolution_priority`: 変数解決優先度(ConditionEnv優先)
- `test_undefined_variable_error`: 未定義変数エラー
- 全7テストPASS(cargo test --release condition_lowerer::tests)

### Integration Smoke (+1)
- `phase92_pattern2_baseline.sh`:
  - Case A: loop_min_while.hako (Pattern2 baseline)
  - Case B: phase92_conditional_step_minimal.hako (条件付きインクリメント)
  - 両ケースPASS、integration profileで発見可能

### 退行確認
-  既存Pattern2Breakテスト正常(退行なし)
-  Phase 135 smoke正常(MIR検証PASS)

## アーキテクチャ設計

### 変数解決メカニズム
```rust
// Priority 1: ConditionEnv (loop params, captured)
if let Some(value_id) = env.get(name) { return Ok(value_id); }
// Priority 2: LoopBodyLocalEnv (body-local like `ch`)
if let Some(body_env) = body_local_env {
    if let Some(value_id) = body_env.get(name) { return Ok(value_id); }
}
```

### Fail-Fast契約
- Delta equality check (conditional_step_emitter.rs)
- Variable resolution error messages (ConditionEnv)
- Body-local promotion rejection (BodyLocalPolicyDecision::Reject)

## ドキュメント

- `P4-E2E-PLAN.md`: 3レベルテスト戦略(Level 1-2完了、Level 3延期)
- `P4-COMPLETION.md`: Phase 92完了報告
- `README.md`: Phase 92全体のまとめ

## 将来の拡張(Phase 92スコープ外)

- Body-local promotionシステム拡張
- P5bパターン認識の汎化(flagベース条件サポート)
- 完全なP5b E2Eテスト(body-local promotion実装後)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-16 21:37:07 +09:00

187 lines
5.4 KiB
Rust

//! Phase 85: DebugOutputBox - Centralized debug output management for JoinIR
//!
//! ## Purpose
//! Provides structured debug output with automatic flag checking to eliminate
//! scattered `if is_joinir_debug() { eprintln!(...) }` patterns.
//!
//! ## Usage
//! ```rust,ignore
//! // Before:
//! if is_joinir_debug() {
//! eprintln!("[phase80/p3] Registered loop var...");
//! }
//!
//! // After:
//! let debug = DebugOutputBox::new("phase80/p3");
//! debug.log("register", "Registered loop var...");
//! ```
//!
//! ## Benefits
//! - Centralized debug output control
//! - Consistent log formatting
//! - Feature-gated (no-op in production)
//! - Zero runtime cost when disabled
use crate::config::env::{is_joinir_debug, joinir_dev_enabled};
/// DebugOutputBox: Centralized debug output for JoinIR lowering
///
/// Automatically checks HAKO_JOINIR_DEBUG flag and formats output consistently.
#[derive(Debug)]
pub struct DebugOutputBox {
enabled: bool,
context_tag: String,
}
impl DebugOutputBox {
/// Create a new DebugOutputBox with the given context tag
///
/// # Arguments
/// * `context_tag` - Identifies the subsystem (e.g., "phase80/p3", "carrier_info")
///
/// # Example
/// ```rust,ignore
/// let debug = DebugOutputBox::new("phase80/p3");
/// ```
pub fn new(context_tag: impl Into<String>) -> Self {
Self {
enabled: is_joinir_debug(),
context_tag: context_tag.into(),
}
}
/// Create a DebugOutputBox with an explicit enabled flag.
///
/// Use this when the caller already has a higher-level gate (e.g. a `verbose` flag)
/// and wants consistent formatting without re-checking env vars.
pub fn new_with_enabled(context_tag: impl Into<String>, enabled: bool) -> Self {
Self {
enabled,
context_tag: context_tag.into(),
}
}
/// Create a DebugOutputBox enabled by JoinIR dev mode (NYASH_JOINIR_DEV=1).
///
/// This is useful for "developer convenience" logs that should not require
/// explicitly setting HAKO_JOINIR_DEBUG, but still must stay opt-in.
pub fn new_dev(context_tag: impl Into<String>) -> Self {
Self {
enabled: joinir_dev_enabled(),
context_tag: context_tag.into(),
}
}
/// Log a debug message with category
///
/// Output format: `[context_tag/category] message`
///
/// # Arguments
/// * `category` - Sub-category (e.g., "register", "promote", "bind")
/// * `message` - Debug message
///
/// # Example
/// ```rust,ignore
/// debug.log("register", "loop var 'i' BindingId(1) -> ValueId(5)");
/// // Output: [phase80/p3/register] loop var 'i' BindingId(1) -> ValueId(5)
/// ```
pub fn log(&self, category: &str, message: &str) {
if self.enabled {
eprintln!("[{}/{}] {}", self.context_tag, category, message);
}
}
/// Log a message without category
///
/// Output format: `[context_tag] message`
///
/// # Example
/// ```rust,ignore
/// debug.log_simple("Processing loop body");
/// // Output: [phase80/p3] Processing loop body
/// ```
pub fn log_simple(&self, message: &str) {
if self.enabled {
eprintln!("[{}] {}", self.context_tag, message);
}
}
/// Log only if enabled (with lazy message generation)
///
/// Useful when message construction is expensive.
///
/// # Example
/// ```rust,ignore
/// debug.log_if_enabled(|| {
/// format!("Complex value: {:?}", expensive_computation())
/// });
/// ```
pub fn log_if_enabled(&self, f: impl FnOnce() -> String) {
if self.enabled {
let msg = f();
eprintln!("[{}] {}", self.context_tag, msg);
}
}
/// Check if debug output is enabled
///
/// Useful for conditional code that shouldn't run in production.
///
/// # Example
/// ```rust,ignore
/// if debug.is_enabled() {
/// // Expensive debug-only validation
/// }
/// ```
pub fn is_enabled(&self) -> bool {
self.enabled
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_debug_output_box_creation() {
let debug = DebugOutputBox::new("test/context");
assert_eq!(debug.context_tag, "test/context");
// Note: is_enabled() depends on env var HAKO_JOINIR_DEBUG
}
#[test]
fn test_log_methods_dont_panic() {
let debug = DebugOutputBox::new("test");
// These should never panic, even if disabled
debug.log("category", "message");
debug.log_simple("simple message");
debug.log_if_enabled(|| "lazy message".to_string());
}
#[test]
fn test_is_enabled_returns_bool() {
let debug = DebugOutputBox::new("test");
let enabled = debug.is_enabled();
// Should return a boolean (either true or false)
assert!(enabled == true || enabled == false);
}
#[test]
fn test_lazy_message_only_called_if_enabled() {
let debug = DebugOutputBox::new("test");
let mut called = false;
debug.log_if_enabled(|| {
called = true;
"message".to_string()
});
// If debug is disabled, called should still be false
// If debug is enabled, called will be true
// Either outcome is valid - we just verify no panic
let _ = called; // Use the variable to avoid warning
}
}