- Add ConditionAlias type to CarrierInfo (old_name → carrier_name) - Record aliases in DigitPosPromoter and TrimPatternInfo - Resolve aliases in Pattern2 ConditionEnv building - digit_pos now correctly resolves to is_digit_pos carrier Fixes "Variable 'digit_pos' not bound in ConditionEnv" error. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
13 KiB
Phase 224: A-4 DigitPos Promoter - Implementation Summary
Date: 2025-12-10 Status: Core Implementation Complete(Phase 224-D まで反映済み、init MethodCall は別 Phase) Branch: main Commits: TBD
Executive Summary
Phase 224 successfully implemented the DigitPosPromoter Box for A-4 pattern (cascading indexOf) promotion, achieving:
✅ Complete: DigitPosPromoter implementation with full unit test coverage (6/6 tests passing)
✅ Complete: Integration into LoopBodyCondPromoter orchestrator
✅ Complete: Two-tier promotion strategy (A-3 Trim → A-4 DigitPos fallback)
✅ Verified: Promotion detection working correctly in Pattern2/4 pipeline
✅ Complete (Phase 224-D): ConditionEnv alias bridge(digit_pos → is_digit_pos)実装
⚠️ Partial: Full E2E flowは body-local init の MethodCall 制約で一部ブロック中
Accomplishments
1. Design Document (224-2) ✅
File: docs/development/current/main/phase224-digitpos-promoter-design.md
Key Design Decisions:
- One Box, One Question: DigitPosPromoter handles ONLY A-4 pattern (indexOf-based)
- Separation of Concerns: Trim patterns remain in LoopBodyCarrierPromoter
- Orchestrator Pattern: LoopBodyCondPromoter delegates to specialized promoters
- Bool Carrier: Promote to
is_digit_pos(bool) for consistency with A-3 Trim
2. DigitPosPromoter Implementation (224-3) ✅
File: src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs (467 lines)
Features:
- Pattern Detection: Identifies cascading substring() → indexOf() → comparison
- Comparison Operators: Supports
<,>,<=,>=,!=(not equality) - Dependency Validation: Verifies indexOf() depends on another LoopBodyLocal
- Comprehensive Tests: 6 unit tests covering normal/edge cases
Test Results:
cargo test --release --lib digitpos
# running 6 tests
# test result: ok. 6 passed; 0 failed; 0 ignored
3. LoopBodyCondPromoter Integration (224-4) ✅
File: src/mir/loop_pattern_detection/loop_body_cond_promoter.rs
Two-Tier Strategy:
Step 1: Try A-3 Trim promotion (LoopBodyCarrierPromoter)
↓ (if fails)
Step 2: Try A-4 DigitPos promotion (DigitPosPromoter)
↓ (if fails)
Step 3: Fail-Fast with clear error message
Logs Verify Success(昇格フェーズ):
[cond_promoter] A-3 Trim promotion failed: No promotable Trim pattern detected
[cond_promoter] Trying A-4 DigitPos promotion...
[digitpos_promoter] Phase 224: Found 1 LoopBodyLocal variables: ["digit_pos"]
[digitpos_promoter] A-4 DigitPos pattern promoted: digit_pos → is_digit_pos
[cond_promoter] A-4 DigitPos pattern promoted: 'digit_pos' → carrier 'is_digit_pos'
Current Limitation: Lowerer Integration Gap
Problem Statement(当初) / Phase 224-D での一部解消
Symptom(224 実装直後): E2E test fails despite successful promotion
Root Cause: lower_loop_with_break_minimal performs independent LoopBodyLocal check
Result: Promoted variables are detected as "unsupported" by the lowerer
Error Flow
Phase 223.5 (Pattern2) → LoopBodyCondPromoter.try_promote() → SUCCESS ✅
↓
Phase 180-3 (Pattern2) → TrimLoopLowerer.try_lower() → SKIP (not Trim)
↓
Pattern2 Lowerer → lower_loop_with_break_minimal() → Analyze break condition
↓
LoopConditionScopeBox.analyze() → Detects "digit_pos" as LoopBodyLocal
↓
ERROR ❌: "Unsupported condition: uses loop-body-local variables: [\"digit_pos\"]"
Why A-3 Trim Patterns Work
For A-3 Trim patterns, TrimLoopLowerer rewrites the break condition to remove LoopBodyLocal references before passing to lower_loop_with_break_minimal:
// TrimLoopLowerer returns trim_result.condition (rewritten)
let effective_break_condition = trim_result.condition; // No LoopBodyLocal!
But for A-4 DigitPos (Phase 223.5), we:
- Successfully promote to carrier:
digit_pos→is_digit_pos✅ - Merge carrier into CarrierInfo ✅
- BUT: Break condition AST still contains
digit_pos❌
Root Cause Analysis(Phase 224 時点)
The break condition is an AST node containing:
if digit_pos < 0 { break }
After promotion(224 時点):
- CarrierInfo knows about
is_digit_poscarrier ✅ - LoopBodyCondPromoter recorded the promotion ✅
- But: AST node still says
digit_pos, notis_digit_pos→ ConditionEnv からdigit_posが見えない ❌
Phase 224-D では AST を直接書き換えるのではなく、
ConditionAlias(old_name → carrier_name)を CarrierInfo/ConditionEnv に導入することで
「digit_pos という名前で条件式から参照された場合も、内部的には is_digit_pos carrier を読む」
というブリッジを追加している。
これにより:
- LoopBodyLocal 昇格後に
digit_pos < 0のような条件があっても、 ConditionEnvBuilder が ConditionAlias を介してis_digit_posの ValueId に解決できるようになった。 - 「LoopBodyLocal が条件にあることによる not‑bound エラー」は解消され、 現時点の Blocker は body‑local init MethodCall(substring など)の lowering 制約だけになった。
Solution Options (Phase 224-continuation 時点の整理)
Option A: AST Rewriting (Comprehensive)
Approach: Rewrite break condition AST to replace promoted variables with carrier references
Implementation:
- After LoopBodyCondPromoter.try_promote() succeeds
- Create ASTRewriter to traverse break_condition_node
- Replace Variable("digit_pos") → Variable("is_digit_pos")
- Pass rewritten condition to lower_loop_with_break_minimal
Pros: Clean, consistent with Trim pattern flow Cons: AST rewriting is complex, error-prone Effort: ~2-3 hours
Option B: Promoted Variable Tracking + ConditionAlias(採用済み)
Approach(決定案):
LoopBodyLocal を carrier に昇格した事実を CarrierInfo(promoted_loopbodylocals + ConditionAlias) に
メタデータとして記録し、ConditionEnvBuilder 側で「元の変数名 → carrier 名」のエイリアスを解決する。
実装(Phase 224-cont / 224-D):
CarrierInfoにpromoted_loopbodylocals: Vec<String>とcondition_aliases: Vec<ConditionAlias>を追加。- LoopBodyCondPromoter(Trim/DigitPos の両方)で昇格成功時に
promoted_loopbodylocals.push("digit_pos")と
condition_aliases.push(ConditionAlias { old_name: "digit_pos", carrier_name: "is_digit_pos" })を記録。 - Pattern2/4 lowerer は ConditionEnvBuilder v2 を呼ぶ際に
&carrier_info.condition_aliasesを渡す。 - ConditionEnvBuilder は
var_name == "digit_pos"のような未解決変数を見つけた場合、 ConditionAlias を使ってcarrier_name == "is_digit_pos"に解決し、その ValueId を Condition 役の Param としてバインド。
効果:
- LoopBodyLocal 条件パターン(A-3 Trim/A-4 DigitPos)は、 AST 書き換えなしで「条件式から見える名前」と「carrier 実体」を橋渡しできる。
- Pattern2/4 や LoopConditionScopeBox は「昇格済み LoopBodyLocal」とそれ以外を区別できるようになり、 不要な Fail‑Fast を避けつつ、未昇格の LoopBodyLocal には引き続き厳格に対応できる。
Option C: DigitPosLoopHelper Metadata (Consistent)
Approach: Create DigitPosLoopHelper similar to TrimLoopHelper
Implementation:
- Create
loop_body_digitpos_helper.rs(similar to trim_loop_helper.rs) - Attach DigitPosLoopHelper to CarrierInfo in DigitPosPromoter
- In Phase 223.5, check for digitpos_helper (like trim_helper check at line 321)
- In lower_loop_with_break_minimal, check CarrierInfo for helpers before error
Pros: Consistent with Trim pattern architecture Cons: More boilerplate Effort: ~2-3 hours
Recommended Next Steps
Immediate (P0 - Phase 224 Completion)
Goal: Unblock phase2235_p2_digit_pos_min.hako test
Approach: Implement Option B (Promoted Variable Tracking)
Tasks:
- Add
promoted_loopbodylocalsfield to CarrierInfo - Record promoted_var in Phase 223.5 (pattern2_with_break.rs:303)
- Pass promoted list to lower_loop_with_break_minimal
- Modify LoopConditionScopeBox to exclude promoted vars
- Verify E2E test passes
Estimated Time: 1-2 hours Risk: Low (surgical change)
Short-term (P1 - Phase 225)
Goal: Extend to Pattern4 (with continue)
Tasks:
- Apply same promoted variable tracking to Pattern4
- Test with
parser_box_minimal.hako(skip_ws pattern) - Verify no regression in existing Trim patterns
Estimated Time: 1 hour Risk: Low (reuse Option B infrastructure)
Medium-term (P2 - Phase 226)
Goal: A-5/A-6 patterns (multi-variable, cascading)
Tasks:
- Extend DigitPosPromoter to handle multiple indexOf() calls
- Support range check patterns (A-6:
ch < "0" || ch > "9") - Add multi-variable promotion support
Estimated Time: 3-4 hours Risk: Medium (more complex patterns)
Files Modified
New Files (3)
docs/development/current/main/phase224-digitpos-promoter-design.md(design doc)src/mir/loop_pattern_detection/loop_body_digitpos_promoter.rs(promoter implementation)docs/development/current/main/PHASE_224_SUMMARY.md(this file)
Modified Files (2)
src/mir/loop_pattern_detection/mod.rs(add digitpos_promoter module)src/mir/loop_pattern_detection/loop_body_cond_promoter.rs(two-tier integration)
Test Coverage
- Unit tests: 6/6 passing (100%)
- E2E test: 0/1 passing (blocked by lowerer integration)
Build & Test Status
Build
cargo build --release
# Finished `release` profile [optimized] target(s) in 1m 11s
# 7 warnings (snake_case naming, visibility)
Unit Tests
cargo test --release --lib digitpos
# running 6 tests
# test result: ok. 6 passed; 0 failed; 0 ignored
E2E Test (Current State)
./target/release/hakorune apps/tests/phase2235_p2_digit_pos_min.hako
# [digitpos_promoter] A-4 DigitPos pattern promoted: digit_pos → is_digit_pos ✅
# [cond_promoter] A-4 DigitPos pattern promoted ✅
# ERROR: Unsupported condition: uses loop-body-local variables: ["digit_pos"] ❌
Technical Debt & Future Work
Code Quality Improvements
- Fix snake_case warnings in digitpos_promoter.rs (is_indexOf → is_index_of)
- Add LoopScopeShape visibility annotation (pub(crate) or pub)
- Extract common AST traversal logic (find_definition_in_body) to shared module
Documentation
- Add DigitPosPromoter to
joinir-architecture-overview.mdbox catalog - Update
phase223-loopbodylocal-condition-inventory.mdwith Phase 224 status - Create integration guide for future pattern promoters
Testing
- Add integration tests for Pattern2+DigitPos combination
- Add regression tests for Trim patterns (ensure no impact)
- Add performance benchmarks for promotion detection
Appendix: Key Design Insights
Why Two Promoters Instead of One?
Question: Why not extend LoopBodyCarrierPromoter to handle A-4 patterns?
Answer: Separation of Concerns
-
LoopBodyCarrierPromoter: Equality-based patterns (A-3 Trim)
ch == " " || ch == "\t"(OR chain)- Single LoopBodyLocal
- Well-tested, stable
-
DigitPosPromoter: Comparison-based patterns (A-4 DigitPos)
digit_pos < 0(comparison)- Cascading dependencies (ch → digit_pos)
- New, experimental
Box-First Principle: "One Box = One Question"
Why Bool Carrier Instead of Int?
Question: Why not preserve digit_pos as int carrier?
Answer: Consistency with Existing Architecture
- A-3 Trim patterns use bool carriers (
is_whitespace) - Pattern2/Pattern4 lowerers expect bool carriers in conditions
- Bool carrier simplifies condition rewriting (just a flag)
Future: Int carrier variant can be added for downstream use (Phase 226+)
Why Not Rewrite AST Immediately?
Question: Why defer AST rewriting to Phase 224-continuation?
Answer: Incremental Development
- AST rewriting is complex and error-prone
- Surgical fix (Option B) unblocks test faster
- Learn from A-3 Trim pattern experience first
- Can refactor to AST rewriting later if needed
Conclusion
Phase 224 successfully implemented the core promotion logic for A-4 DigitPos patterns, achieving:
- ✅ Comprehensive design document
- ✅ Robust promoter implementation with full test coverage
- ✅ Clean integration into orchestrator pattern
- ✅ Verified promotion detection in Pattern2 pipeline
Remaining Work: Lowerer integration (1-2 hours, Option B approach)
Next Session: Implement Option B (promoted variable tracking) to complete Phase 224 and unblock phase2235_p2_digit_pos_min.hako test.
References
- Phase 223 Inventory - A-4 pattern specification
- Phase 224 Design - Detailed design document
- JoinIR Architecture - Overall system architecture
- Test File - Minimal A-4 test case