feat(joinir): Phase 184 - Body-local MIR Lowering Infrastructure
Phase 184 implements the foundation for body-local variable support in update expressions, completing the three-box architecture: LoopBodyLocalEnv (storage), UpdateEnv (composition), and CarrierUpdateEmitter (emission). ## Implementation Summary ### Task 184-1: Design Document - Created phase184-body-local-mir-lowering.md - Two-Environment System design (ConditionEnv + LoopBodyLocalEnv) - Box-First design principles documented ### Task 184-2: LoopBodyLocalEnv Implementation - New file: src/mir/join_ir/lowering/loop_body_local_env.rs (216 lines) - Storage box for body-local variable name → ValueId mappings - BTreeMap for deterministic ordering (PHI consistency) - 7 unit tests: empty env, single/multiple locals, get/contains, iteration ### Task 184-3: UpdateEnv Implementation - New file: src/mir/join_ir/lowering/update_env.rs (237 lines) - Composition box for unified variable resolution - Priority order: ConditionEnv (condition vars) → LoopBodyLocalEnv (body-local) - 8 unit tests: priority, fallback, not found, combined lookup ### Task 184-4: CarrierUpdateEmitter Integration - Modified: src/mir/join_ir/lowering/carrier_update_emitter.rs - Added emit_carrier_update_with_env() (UpdateEnv version) - Kept emit_carrier_update() for backward compatibility - 4 new unit tests: body-local variable, priority, not found, const update - Total 10 tests PASS (6 existing + 4 new) ### Task 184-5: Representative Test Cases - apps/tests/phase184_body_local_update.hako (Pattern1 baseline) - apps/tests/phase184_body_local_with_break.hako (Pattern2, Phase 185 target) ### Task 184-6: Documentation Updates - Updated: docs/development/current/main/joinir-architecture-overview.md - Updated: CURRENT_TASK.md (Phase 184 completion record) ## Test Results All 25 unit tests PASS: - LoopBodyLocalEnv: 7 tests - UpdateEnv: 8 tests - CarrierUpdateEmitter: 10 tests (6 existing + 4 new) Build: ✅ Success (0 errors) ## Design Constraints Following 箱理論 (Box Theory) principles: - Single Responsibility: Each box has one clear purpose - Deterministic: BTreeMap ensures consistent ordering - Conservative: Pattern5 (Trim) integration deferred to Phase 185 - Fail-Fast: Explicit errors for unsupported patterns ## Scope Limitation Phase 184 provides the **infrastructure only**: - ✅ Storage box (LoopBodyLocalEnv) - ✅ Composition box (UpdateEnv) - ✅ Emission support (CarrierUpdateEmitter) - ❌ Pattern2/4 integration (requires body-local collection, Phase 185) ## Next Steps Phase 185: Pattern2/4 Integration - Integrate LoopBodyLocalEnv into Pattern2/4 lowerers - Add body-local variable collection from loop body AST - Enable full E2E body-local variable support in update expressions 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -169,8 +169,32 @@
|
|||||||
- **成果**: LoopBodyLocal 役割分離完了、body-only 変数は Trim promotion されない
|
- **成果**: LoopBodyLocal 役割分離完了、body-only 変数は Trim promotion されない
|
||||||
- **残課題**: body-local 変数の MIR lowering 対応(Phase 184+ で対応予定)
|
- **残課題**: body-local 変数の MIR lowering 対応(Phase 184+ で対応予定)
|
||||||
- **発見**: Pattern1 実行問題(別途対応必要、Phase 183 スコープ外)
|
- **発見**: Pattern1 実行問題(別途対応必要、Phase 183 スコープ外)
|
||||||
- [ ] Phase 184+: Body-local 変数 MIR lowering + String ops 有効化
|
- [x] **Phase 184: Body-local MIR Lowering Infrastructure** ✅ (2025-12-08)
|
||||||
- body-local 変数(`local temp` in loop body)の MIR 生成対応
|
- Task 184-1: 設計ドキュメント作成 ✅
|
||||||
|
- phase184-body-local-mir-lowering.md(Two-Environment System 設計)
|
||||||
|
- 箱理論: LoopBodyLocalEnv(Storage Box)+ UpdateEnv(Composition Box)
|
||||||
|
- Task 184-2: LoopBodyLocalEnv 実装 ✅
|
||||||
|
- loop_body_local_env.rs(216行)新規作成
|
||||||
|
- BTreeMap 決定性保証、7 unit tests 全 PASS
|
||||||
|
- Task 184-3: UpdateEnv 実装 ✅
|
||||||
|
- update_env.rs(237行)新規作成
|
||||||
|
- Priority order: ConditionEnv → LoopBodyLocalEnv、8 unit tests 全 PASS
|
||||||
|
- Task 184-4: CarrierUpdateEmitter 統合 ✅
|
||||||
|
- emit_carrier_update_with_env() 新規追加(UpdateEnv 版)
|
||||||
|
- emit_carrier_update() 後方互換維持、4 新規 unit tests 全 PASS(全 10 tests PASS)
|
||||||
|
- Task 184-5: 代表テストケース作成 ✅
|
||||||
|
- phase184_body_local_update.hako(Pattern1 検証)
|
||||||
|
- phase184_body_local_with_break.hako(Pattern2 統合未実施、Phase 185 予定)
|
||||||
|
- Task 184-6: ドキュメント更新 ✅
|
||||||
|
- joinir-architecture-overview.md(Phase 184 セクション追加)
|
||||||
|
- CURRENT_TASK.md(本項目追加)
|
||||||
|
- **成果**: Body-local 変数の MIR lowering インフラ完成
|
||||||
|
- 3つの小箱(LoopBodyLocalEnv/UpdateEnv/CarrierUpdateEmitter)が単一責任で独立
|
||||||
|
- 全 25 unit tests PASS(決定性・優先順位・後方互換性検証済み)
|
||||||
|
- **制約**: Pattern2/4 への統合は Phase 185 で実施予定(body-local 収集機能必要)
|
||||||
|
- [ ] Phase 185+: Body-local Pattern2/4 統合 + String ops 有効化
|
||||||
|
- Pattern2/4 lowerer に LoopBodyLocalEnv 統合
|
||||||
|
- body-local 変数(`local temp` in loop body)の完全 MIR 生成対応
|
||||||
- String concat フィルタの段階的緩和
|
- String concat フィルタの段階的緩和
|
||||||
- _parse_number, _atoi 完全実装
|
- _parse_number, _atoi 完全実装
|
||||||
- [ ] Phase 183+: JsonParser 中級パターン実装
|
- [ ] Phase 183+: JsonParser 中級パターン実装
|
||||||
|
|||||||
26
apps/tests/phase184_body_local_update.hako
Normal file
26
apps/tests/phase184_body_local_update.hako
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Phase 184: Body-local MIR Lowering Test
|
||||||
|
//
|
||||||
|
// This test validates that body-local variables can be used in update expressions.
|
||||||
|
//
|
||||||
|
// Expected behavior:
|
||||||
|
// - loop(i < 5) iterates 5 times
|
||||||
|
// - Each iteration:
|
||||||
|
// - local temp = i * 2 (body-local variable)
|
||||||
|
// - sum = sum + temp (update expression uses body-local)
|
||||||
|
// - Expected sum: 0 + (0*2) + (1*2) + (2*2) + (3*2) + (4*2) = 0+0+2+4+6+8 = 20
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local sum = 0
|
||||||
|
local i = 0
|
||||||
|
|
||||||
|
loop(i < 5) {
|
||||||
|
local temp = i * 2 // Body-local variable
|
||||||
|
sum = sum + temp // Use body-local in update expression
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print(sum) // Expected: 20
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
}
|
||||||
37
apps/tests/phase184_body_local_with_break.hako
Normal file
37
apps/tests/phase184_body_local_with_break.hako
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Phase 184: Body-local MIR Lowering with Break (Pattern 2)
|
||||||
|
//
|
||||||
|
// This test validates body-local variables in Pattern 2 (loop with break).
|
||||||
|
//
|
||||||
|
// Expected behavior:
|
||||||
|
// - loop(i < 10) with break when sum >= 15
|
||||||
|
// - Each iteration:
|
||||||
|
// - local temp = i * 3 (body-local variable)
|
||||||
|
// - sum = sum + temp (update expression uses body-local)
|
||||||
|
// - if (sum >= 15) break
|
||||||
|
// - Expected iterations:
|
||||||
|
// - i=0: temp=0, sum=0+0=0 (continue)
|
||||||
|
// - i=1: temp=3, sum=0+3=3 (continue)
|
||||||
|
// - i=2: temp=6, sum=3+6=9 (continue)
|
||||||
|
// - i=3: temp=9, sum=9+9=18 (>= 15, break)
|
||||||
|
// - Expected sum: 18
|
||||||
|
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local sum = 0
|
||||||
|
local i = 0
|
||||||
|
|
||||||
|
loop(i < 10) {
|
||||||
|
local temp = i * 3 // Body-local variable
|
||||||
|
sum = sum + temp // Use body-local in update expression
|
||||||
|
|
||||||
|
if (sum >= 15) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
print(sum) // Expected: 18
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -169,6 +169,23 @@ JoinIR ラインで守るべきルールを先に書いておくよ:
|
|||||||
- else-continue を then-continue へ正規化し、Select ベースの continue を簡潔にする。
|
- else-continue を then-continue へ正規化し、Select ベースの continue を簡潔にする。
|
||||||
- ループ本体で実際に更新されるキャリアだけを抽出(Pattern 4 で不要キャリアを排除)。
|
- ループ本体で実際に更新されるキャリアだけを抽出(Pattern 4 で不要キャリアを排除)。
|
||||||
|
|
||||||
|
- **LoopBodyLocalEnv / UpdateEnv / CarrierUpdateEmitter(Phase 184)**
|
||||||
|
- ファイル:
|
||||||
|
- `src/mir/join_ir/lowering/loop_body_local_env.rs`
|
||||||
|
- `src/mir/join_ir/lowering/update_env.rs`
|
||||||
|
- `src/mir/join_ir/lowering/carrier_update_emitter.rs`
|
||||||
|
- 責務:
|
||||||
|
- **LoopBodyLocalEnv**: ループ本体で宣言された body-local 変数の名前→ValueId マッピングを管理(箱化設計)。
|
||||||
|
- **UpdateEnv**: 条件変数(ConditionEnv)と body-local 変数(LoopBodyLocalEnv)を統合した変数解決層。
|
||||||
|
- Priority order: 1. Condition variables(高優先度) → 2. Body-local variables(フォールバック)
|
||||||
|
- **CarrierUpdateEmitter**: UpdateExpr を JoinIR 命令に変換する際、UpdateEnv を使用して body-local 変数をサポート。
|
||||||
|
- `emit_carrier_update_with_env()`: UpdateEnv 版(Phase 184 新規)
|
||||||
|
- `emit_carrier_update()`: ConditionEnv 版(後方互換)
|
||||||
|
- 設計原則:
|
||||||
|
- **箱理論**: 各 Box が単一責任を持ち、境界明確。
|
||||||
|
- **決定性**: BTreeMap 使用で一貫した順序保証(PHI 生成の決定性)。
|
||||||
|
- **保守的**: Pattern5 (Trim) は対象外、Phase 185 で統合予定。
|
||||||
|
|
||||||
### 2.3 キャリア / Exit / Boundary ライン
|
### 2.3 キャリア / Exit / Boundary ライン
|
||||||
|
|
||||||
- **CarrierInfo / LoopUpdateAnalyzer**
|
- **CarrierInfo / LoopUpdateAnalyzer**
|
||||||
|
|||||||
@ -0,0 +1,374 @@
|
|||||||
|
# Phase 184: Body-local MIR Lowering Design
|
||||||
|
|
||||||
|
## Status: In Progress (2025-12-08)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Phase 183 completed **LoopBodyLocal role separation** (condition vs body-only).
|
||||||
|
Phase 184 implements **body-local MIR lowering** - the ability to use body-only local variables in update expressions safely.
|
||||||
|
|
||||||
|
### Problem Statement
|
||||||
|
|
||||||
|
**Current limitation**:
|
||||||
|
```nyash
|
||||||
|
local sum = 0
|
||||||
|
local i = 0
|
||||||
|
loop(i < 5) {
|
||||||
|
local temp = i * 2 // body-local variable
|
||||||
|
sum = sum + temp // ❌ ERROR: temp not found in ConditionEnv
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Root cause**:
|
||||||
|
- `CarrierUpdateEmitter` only has access to `ConditionEnv`
|
||||||
|
- `ConditionEnv` only contains condition variables (loop parameters)
|
||||||
|
- Body-local variables (`temp`) are not in `ConditionEnv`
|
||||||
|
- Update expression `sum = sum + temp` fails to resolve `temp`
|
||||||
|
|
||||||
|
**Goal**: Enable body-local variables in update expressions while maintaining architectural clarity.
|
||||||
|
|
||||||
|
## Design Solution
|
||||||
|
|
||||||
|
### Architecture: Two-Environment System
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ ConditionEnv │ Condition variables (loop parameters)
|
||||||
|
│ - i → ValueId(0) │ Priority: HIGH
|
||||||
|
│ - end → ValueId(1) │
|
||||||
|
└─────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ LoopBodyLocalEnv │ Body-local variables
|
||||||
|
│ - temp → ValueId(5)│ Priority: LOW (only if not in ConditionEnv)
|
||||||
|
│ - digit → ValueId(6)│
|
||||||
|
└─────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────┐
|
||||||
|
│ UpdateEnv │ Unified resolution layer
|
||||||
|
│ resolve(name): │ 1. Try ConditionEnv first
|
||||||
|
│ cond.get(name) │ 2. Fallback to LoopBodyLocalEnv
|
||||||
|
│ .or(body.get()) │
|
||||||
|
└─────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Box-First Design
|
||||||
|
|
||||||
|
Following **箱理論 (Box Theory)** principles:
|
||||||
|
|
||||||
|
#### Box 1: LoopBodyLocalEnv (Storage Box)
|
||||||
|
|
||||||
|
**Single Responsibility**: Collect and manage body-local variable mappings (name → ValueId).
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct LoopBodyLocalEnv {
|
||||||
|
locals: BTreeMap<String, ValueId>, // Deterministic ordering
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoopBodyLocalEnv {
|
||||||
|
/// Scan loop body AST and collect local definitions
|
||||||
|
pub fn from_loop_body(
|
||||||
|
body: &[ASTNode],
|
||||||
|
builder: &mut JoinIrBuilder
|
||||||
|
) -> Self;
|
||||||
|
|
||||||
|
/// Resolve a body-local variable name to JoinIR ValueId
|
||||||
|
pub fn get(&self, name: &str) -> Option<ValueId>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Design rationale**:
|
||||||
|
- **BTreeMap**: Ensures deterministic iteration (PHI ordering consistency)
|
||||||
|
- **from_loop_body()**: Separates collection logic from lowering logic
|
||||||
|
- **get()**: Simple lookup, no side effects
|
||||||
|
|
||||||
|
#### Box 2: UpdateEnv (Composition Box)
|
||||||
|
|
||||||
|
**Single Responsibility**: Unified variable resolution for update expressions.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub struct UpdateEnv<'a> {
|
||||||
|
condition_env: &'a ConditionEnv, // Priority 1: Condition vars
|
||||||
|
body_local_env: &'a LoopBodyLocalEnv, // Priority 2: Body-local vars
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UpdateEnv<'a> {
|
||||||
|
/// Resolve variable name with priority order
|
||||||
|
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||||||
|
self.condition_env.get(name)
|
||||||
|
.or_else(|| self.body_local_env.get(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Design rationale**:
|
||||||
|
- **Composition**: Combines two environments without owning them
|
||||||
|
- **Priority order**: Condition variables take precedence (shadowing prevention)
|
||||||
|
- **Lightweight**: No allocation, just references
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
|
||||||
|
#### Current Flow (Pattern2 Example)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Pattern2Lowerer::lower()
|
||||||
|
let condition_env = /* ... */;
|
||||||
|
|
||||||
|
// Emit carrier update
|
||||||
|
let update_value = emit_carrier_update(
|
||||||
|
&carrier,
|
||||||
|
&update_expr,
|
||||||
|
&mut alloc_value,
|
||||||
|
&condition_env, // ❌ Only has condition variables
|
||||||
|
&mut instructions,
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Phase 184 Flow
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// Pattern2Lowerer::lower()
|
||||||
|
let condition_env = /* ... */;
|
||||||
|
let body_local_env = LoopBodyLocalEnv::from_loop_body(&body_nodes, builder);
|
||||||
|
|
||||||
|
// Create unified environment
|
||||||
|
let update_env = UpdateEnv {
|
||||||
|
condition_env: &condition_env,
|
||||||
|
body_local_env: &body_local_env,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Emit carrier update (now with body-local support)
|
||||||
|
let update_value = emit_carrier_update_with_env(
|
||||||
|
&carrier,
|
||||||
|
&update_expr,
|
||||||
|
&mut alloc_value,
|
||||||
|
&update_env, // ✅ Has both condition and body-local variables
|
||||||
|
&mut instructions,
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scope and Constraints
|
||||||
|
|
||||||
|
### In Scope (Phase 184)
|
||||||
|
|
||||||
|
1. **Body-local variable collection**: `LoopBodyLocalEnv::from_loop_body()`
|
||||||
|
2. **Unified resolution**: `UpdateEnv::resolve()`
|
||||||
|
3. **CarrierUpdateEmitter integration**: Use `UpdateEnv` instead of `ConditionEnv`
|
||||||
|
4. **Pattern2/4 integration**: Pass `body_local_env` to update lowering
|
||||||
|
|
||||||
|
### Out of Scope
|
||||||
|
|
||||||
|
1. **Condition variable usage**: Body-locals still cannot be used in conditions
|
||||||
|
2. **Pattern5 (Trim) integration**: Defer to Phase 185
|
||||||
|
3. **Complex expressions**: Only simple variable references in updates
|
||||||
|
4. **Type checking**: Assume type correctness (existing type inference handles this)
|
||||||
|
|
||||||
|
### Design Constraints
|
||||||
|
|
||||||
|
Following **Phase 178 Fail-Fast** and **箱理論** principles:
|
||||||
|
|
||||||
|
1. **Single Responsibility**: Each Box has one clear purpose
|
||||||
|
2. **Deterministic**: BTreeMap for consistent ordering
|
||||||
|
3. **Conservative**: No changes to Trim/Pattern5 logic
|
||||||
|
4. **Explicit errors**: If body-local used in condition → Fail loudly
|
||||||
|
|
||||||
|
## Implementation Tasks
|
||||||
|
|
||||||
|
### Task 184-1: Design Document ✅ (This document)
|
||||||
|
|
||||||
|
### Task 184-2: LoopBodyLocalEnv Implementation
|
||||||
|
|
||||||
|
**File**: `src/mir/join_ir/lowering/loop_body_local_env.rs` (new)
|
||||||
|
|
||||||
|
**Core logic**:
|
||||||
|
```rust
|
||||||
|
impl LoopBodyLocalEnv {
|
||||||
|
pub fn from_loop_body(body: &[ASTNode], builder: &mut JoinIrBuilder) -> Self {
|
||||||
|
let mut locals = BTreeMap::new();
|
||||||
|
|
||||||
|
for node in body {
|
||||||
|
if let ASTNode::LocalDecl { name, init_value } = node {
|
||||||
|
// Lower init_value to JoinIR
|
||||||
|
let value_id = builder.lower_expr(init_value)?;
|
||||||
|
locals.insert(name.clone(), value_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { locals }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unit tests** (3-5 tests):
|
||||||
|
- `test_empty_body`: No locals → empty env
|
||||||
|
- `test_single_local`: One local → one mapping
|
||||||
|
- `test_multiple_locals`: Multiple locals → sorted keys
|
||||||
|
- `test_get_existing`: Lookup succeeds
|
||||||
|
- `test_get_nonexistent`: Lookup returns None
|
||||||
|
|
||||||
|
### Task 184-3: UpdateEnv Implementation
|
||||||
|
|
||||||
|
**File**: `src/mir/join_ir/lowering/update_env.rs` (new)
|
||||||
|
|
||||||
|
**Core logic**:
|
||||||
|
```rust
|
||||||
|
pub struct UpdateEnv<'a> {
|
||||||
|
condition_env: &'a ConditionEnv,
|
||||||
|
body_local_env: &'a LoopBodyLocalEnv,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UpdateEnv<'a> {
|
||||||
|
pub fn new(
|
||||||
|
condition_env: &'a ConditionEnv,
|
||||||
|
body_local_env: &'a LoopBodyLocalEnv,
|
||||||
|
) -> Self {
|
||||||
|
Self { condition_env, body_local_env }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||||||
|
self.condition_env.get(name)
|
||||||
|
.or_else(|| self.body_local_env.get(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Unit tests** (2-3 tests):
|
||||||
|
- `test_resolve_condition_priority`: Condition var found first
|
||||||
|
- `test_resolve_body_local_fallback`: Body-local found when condition absent
|
||||||
|
- `test_resolve_not_found`: Neither env has variable → None
|
||||||
|
|
||||||
|
### Task 184-4: CarrierUpdateEmitter Integration
|
||||||
|
|
||||||
|
**File**: `src/mir/join_ir/lowering/carrier_update_emitter.rs` (modify)
|
||||||
|
|
||||||
|
**Changes**:
|
||||||
|
1. Add new function variant accepting `UpdateEnv`:
|
||||||
|
```rust
|
||||||
|
pub fn emit_carrier_update_with_env(
|
||||||
|
carrier: &CarrierVar,
|
||||||
|
update: &UpdateExpr,
|
||||||
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
|
env: &UpdateEnv, // New: UpdateEnv instead of ConditionEnv
|
||||||
|
instructions: &mut Vec<JoinInst>,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
// Use env.resolve() instead of env.get()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Keep existing `emit_carrier_update()` for backward compatibility
|
||||||
|
3. Update Pattern2/4 callers to use new variant
|
||||||
|
|
||||||
|
**Validation**: Existing tests must pass (backward compatibility).
|
||||||
|
|
||||||
|
### Task 184-5: Representative Tests
|
||||||
|
|
||||||
|
**File**: `apps/tests/phase184_body_local_update.hako` (new)
|
||||||
|
|
||||||
|
```nyash
|
||||||
|
// Body-local used in update expression
|
||||||
|
static box Main {
|
||||||
|
main() {
|
||||||
|
local sum = 0
|
||||||
|
local i = 0
|
||||||
|
loop(i < 5) {
|
||||||
|
local temp = i * 2 // Body-local variable
|
||||||
|
sum = sum + temp // Use in update expression
|
||||||
|
i = i + 1
|
||||||
|
}
|
||||||
|
print(sum) // Expected: 0+2+4+6+8 = 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Test commands**:
|
||||||
|
```bash
|
||||||
|
# Structure trace
|
||||||
|
NYASH_JOINIR_STRUCTURE_ONLY=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||||
|
|
||||||
|
# Full execution
|
||||||
|
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 184-6: Documentation Updates
|
||||||
|
|
||||||
|
1. **Update**: `docs/development/current/main/joinir-architecture-overview.md`
|
||||||
|
- Add LoopBodyLocalEnv section
|
||||||
|
- Update UpdateEnv integration diagram
|
||||||
|
|
||||||
|
2. **Update**: `CURRENT_TASK.md`
|
||||||
|
- Mark Phase 184 complete
|
||||||
|
- Add Phase 185 preview
|
||||||
|
|
||||||
|
## Validation Strategy
|
||||||
|
|
||||||
|
### Success Criteria
|
||||||
|
|
||||||
|
1. **Unit tests pass**: LoopBodyLocalEnv and UpdateEnv tests green
|
||||||
|
2. **Backward compatibility**: Existing Pattern2/4 tests still pass
|
||||||
|
3. **Representative test**: phase184_body_local_update.hako executes correctly (output: 20)
|
||||||
|
4. **No regression**: Trim patterns (Pattern5) unaffected
|
||||||
|
|
||||||
|
### Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Unit tests
|
||||||
|
cargo test --release --lib loop_body_local_env
|
||||||
|
cargo test --release --lib update_env
|
||||||
|
cargo test --release --lib carrier_update
|
||||||
|
|
||||||
|
# Integration test
|
||||||
|
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase184_body_local_update.hako
|
||||||
|
|
||||||
|
# Regression check (Trim pattern)
|
||||||
|
NYASH_JOINIR_CORE=1 ./target/release/hakorune apps/tests/phase172_trim_while.hako
|
||||||
|
```
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
### Not Supported (Explicit Design Decision)
|
||||||
|
|
||||||
|
1. **Body-local in conditions**:
|
||||||
|
```nyash
|
||||||
|
loop(i < 5) {
|
||||||
|
local temp = i * 2
|
||||||
|
if (temp > 6) break // ❌ ERROR: Body-local in condition not allowed
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Reason**: Condition variables must be loop parameters (JoinIR entry signature constraint).
|
||||||
|
|
||||||
|
2. **Shadowing**:
|
||||||
|
```nyash
|
||||||
|
loop(i < 5) {
|
||||||
|
local i = 10 // ❌ ERROR: Shadows condition variable
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Reason**: `UpdateEnv` prioritizes condition variables - shadowing forbidden.
|
||||||
|
|
||||||
|
3. **Complex expressions**:
|
||||||
|
```nyash
|
||||||
|
loop(i < 5) {
|
||||||
|
local temp = obj.method() // ⚠️ May not work yet
|
||||||
|
}
|
||||||
|
```
|
||||||
|
**Reason**: Limited to expressions `JoinIrBuilder::lower_expr()` supports.
|
||||||
|
|
||||||
|
## Future Work (Phase 185+)
|
||||||
|
|
||||||
|
### Phase 185: Trim Pattern Integration
|
||||||
|
|
||||||
|
- Extend LoopBodyLocalEnv to handle Trim carrier variables
|
||||||
|
- Update TrimLoopLowerer to use UpdateEnv
|
||||||
|
|
||||||
|
### Phase 186: Condition Expression Support
|
||||||
|
|
||||||
|
- Allow body-local variables in break/continue conditions
|
||||||
|
- Requires inline expression evaluation in condition lowering
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- **Phase 183**: LoopBodyLocal role separation (condition vs body-only)
|
||||||
|
- **Phase 178**: Fail-Fast error handling principles
|
||||||
|
- **Phase 171-C**: LoopBodyCarrierPromoter original design
|
||||||
|
- **carrier_update_emitter.rs**: Current update emission logic
|
||||||
|
- **condition_env.rs**: Condition variable environment design
|
||||||
@ -1,21 +1,159 @@
|
|||||||
//! Phase 176-2 / Phase 179: Carrier Update Emission
|
//! Phase 176-2 / Phase 179 / Phase 184: Carrier Update Emission
|
||||||
//!
|
//!
|
||||||
//! Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
//! Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
||||||
//! that compute the updated carrier value.
|
//! that compute the updated carrier value.
|
||||||
//!
|
//!
|
||||||
//! This module is extracted from loop_with_break_minimal.rs to improve
|
//! This module is extracted from loop_with_break_minimal.rs to improve
|
||||||
//! modularity and single responsibility.
|
//! modularity and single responsibility.
|
||||||
|
//!
|
||||||
|
//! Phase 184: Added UpdateEnv support for body-local variable resolution.
|
||||||
|
|
||||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||||
use crate::mir::join_ir::lowering::condition_to_joinir::ConditionEnv;
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||||
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
|
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
|
||||||
|
use crate::mir::join_ir::lowering::update_env::UpdateEnv;
|
||||||
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
|
use crate::mir::join_ir::{BinOpKind, ConstValue, JoinInst, MirLikeInst};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
/// Emit JoinIR instructions for a single carrier update
|
/// Emit JoinIR instructions for a single carrier update (Phase 184: UpdateEnv version)
|
||||||
///
|
///
|
||||||
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
/// Converts UpdateExpr (from LoopUpdateAnalyzer) into JoinIR instructions
|
||||||
/// that compute the updated carrier value.
|
/// that compute the updated carrier value. Supports both condition variables
|
||||||
|
/// and body-local variables through UpdateEnv.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `carrier` - Carrier variable information (name, ValueId)
|
||||||
|
/// * `update` - Update expression (e.g., CounterLike, AccumulationLike)
|
||||||
|
/// * `alloc_value` - ValueId allocator closure
|
||||||
|
/// * `env` - UpdateEnv for unified variable resolution
|
||||||
|
/// * `instructions` - Output vector to append instructions to
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// ValueId of the computed update result
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// // For "count = count + temp":
|
||||||
|
/// let count_next = emit_carrier_update_with_env(
|
||||||
|
/// &count_carrier,
|
||||||
|
/// &UpdateExpr::BinOp { lhs: "count", op: Add, rhs: Variable("temp") },
|
||||||
|
/// &mut alloc_value,
|
||||||
|
/// &update_env, // Has both condition and body-local vars
|
||||||
|
/// &mut instructions,
|
||||||
|
/// )?;
|
||||||
|
/// // Generates:
|
||||||
|
/// // count_next = BinOp(Add, count_param, temp_value)
|
||||||
|
/// ```
|
||||||
|
pub fn emit_carrier_update_with_env(
|
||||||
|
carrier: &CarrierVar,
|
||||||
|
update: &UpdateExpr,
|
||||||
|
alloc_value: &mut dyn FnMut() -> ValueId,
|
||||||
|
env: &UpdateEnv,
|
||||||
|
instructions: &mut Vec<JoinInst>,
|
||||||
|
) -> Result<ValueId, String> {
|
||||||
|
match update {
|
||||||
|
UpdateExpr::Const(step) => {
|
||||||
|
// CounterLike: carrier = carrier + step
|
||||||
|
// Allocate const ValueId
|
||||||
|
let const_id = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: const_id,
|
||||||
|
value: ConstValue::Integer(*step),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Get carrier parameter ValueId from env
|
||||||
|
let carrier_param = env
|
||||||
|
.resolve(&carrier.name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Carrier '{}' not found in UpdateEnv",
|
||||||
|
carrier.name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Allocate result ValueId
|
||||||
|
let result = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: result,
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
lhs: carrier_param,
|
||||||
|
rhs: const_id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateExpr::BinOp { lhs, op, rhs } => {
|
||||||
|
// General binary operation: carrier = carrier op rhs
|
||||||
|
// Verify lhs matches carrier name
|
||||||
|
if lhs != &carrier.name {
|
||||||
|
return Err(format!(
|
||||||
|
"Update expression LHS '{}' doesn't match carrier '{}'",
|
||||||
|
lhs, carrier.name
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get carrier parameter ValueId from env
|
||||||
|
let carrier_param = env
|
||||||
|
.resolve(&carrier.name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Carrier '{}' not found in UpdateEnv",
|
||||||
|
carrier.name
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// Resolve RHS (Phase 184: Now supports body-local variables!)
|
||||||
|
let rhs_id = match rhs {
|
||||||
|
UpdateRhs::Const(n) => {
|
||||||
|
let const_id = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::Const {
|
||||||
|
dst: const_id,
|
||||||
|
value: ConstValue::Integer(*n),
|
||||||
|
}));
|
||||||
|
const_id
|
||||||
|
}
|
||||||
|
UpdateRhs::Variable(var_name) => {
|
||||||
|
env.resolve(var_name).ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"Update RHS variable '{}' not found in UpdateEnv (neither condition nor body-local)",
|
||||||
|
var_name
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
// Phase 178: String updates detected but not lowered to JoinIR yet
|
||||||
|
// The Rust MIR path handles string concatenation
|
||||||
|
// For JoinIR: just pass through the carrier param (no JoinIR update)
|
||||||
|
UpdateRhs::StringLiteral(_) | UpdateRhs::Other => {
|
||||||
|
eprintln!(
|
||||||
|
"[joinir/pattern2] Phase 178: Carrier '{}' has string/complex update - skipping JoinIR emit, using param passthrough",
|
||||||
|
carrier.name
|
||||||
|
);
|
||||||
|
return Ok(carrier_param); // Pass-through: no JoinIR update
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Allocate result ValueId
|
||||||
|
let result = alloc_value();
|
||||||
|
instructions.push(JoinInst::Compute(MirLikeInst::BinOp {
|
||||||
|
dst: result,
|
||||||
|
op: *op,
|
||||||
|
lhs: carrier_param,
|
||||||
|
rhs: rhs_id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Emit JoinIR instructions for a single carrier update (backward compatibility version)
|
||||||
|
///
|
||||||
|
/// This function is kept for backward compatibility with existing Pattern2/4 code
|
||||||
|
/// that only needs ConditionEnv. New code should prefer `emit_carrier_update_with_env`.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
@ -151,6 +289,7 @@ pub fn emit_carrier_update(
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
use crate::mir::join_ir::lowering::carrier_info::CarrierVar;
|
||||||
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||||
|
|
||||||
// Helper: Create a test ConditionEnv
|
// Helper: Create a test ConditionEnv
|
||||||
fn test_env() -> ConditionEnv {
|
fn test_env() -> ConditionEnv {
|
||||||
@ -161,6 +300,19 @@ mod tests {
|
|||||||
env
|
env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper: Create a test LoopBodyLocalEnv
|
||||||
|
fn test_body_local_env() -> LoopBodyLocalEnv {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
env.insert("temp".to_string(), ValueId(50));
|
||||||
|
env.insert("digit".to_string(), ValueId(60));
|
||||||
|
env
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Create a test UpdateEnv
|
||||||
|
fn test_update_env() -> (ConditionEnv, LoopBodyLocalEnv) {
|
||||||
|
(test_env(), test_body_local_env())
|
||||||
|
}
|
||||||
|
|
||||||
// Helper: Create a test CarrierVar
|
// Helper: Create a test CarrierVar
|
||||||
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
|
fn test_carrier(name: &str, host_id: u32) -> CarrierVar {
|
||||||
CarrierVar {
|
CarrierVar {
|
||||||
@ -413,4 +565,168 @@ mod tests {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
|
assert!(result.unwrap_err().contains("Update RHS variable 'unknown_var' not found"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Phase 184: UpdateEnv version tests
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_update_with_env_body_local_variable() {
|
||||||
|
// Phase 184: Test using body-local variable in update expression
|
||||||
|
// sum = sum + temp (temp is body-local)
|
||||||
|
let carrier = test_carrier("sum", 200);
|
||||||
|
let update = UpdateExpr::BinOp {
|
||||||
|
lhs: "sum".to_string(),
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
rhs: UpdateRhs::Variable("temp".to_string()), // Body-local variable
|
||||||
|
};
|
||||||
|
|
||||||
|
let (cond_env, body_env) = test_update_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
let mut value_counter = 110u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_carrier_update_with_env(
|
||||||
|
&carrier,
|
||||||
|
&update,
|
||||||
|
&mut alloc_value,
|
||||||
|
&update_env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
let result_id = result.unwrap();
|
||||||
|
|
||||||
|
// Should generate 1 instruction: BinOp(Add, sum, temp)
|
||||||
|
assert_eq!(instructions.len(), 1);
|
||||||
|
|
||||||
|
match &instructions[0] {
|
||||||
|
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
|
||||||
|
assert_eq!(*dst, ValueId(110));
|
||||||
|
assert_eq!(*op, BinOpKind::Add);
|
||||||
|
assert_eq!(*lhs, ValueId(20)); // sum from condition env
|
||||||
|
assert_eq!(*rhs, ValueId(50)); // temp from body-local env
|
||||||
|
}
|
||||||
|
_ => panic!("Expected BinOp instruction"),
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(result_id, ValueId(110));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_update_with_env_condition_priority() {
|
||||||
|
// Phase 184: Test condition variable takes priority over body-local
|
||||||
|
// If both envs have "x", condition env should win
|
||||||
|
let mut cond_env = ConditionEnv::new();
|
||||||
|
cond_env.insert("x".to_string(), ValueId(100)); // Condition: x=100
|
||||||
|
cond_env.insert("sum".to_string(), ValueId(20));
|
||||||
|
|
||||||
|
let mut body_env = LoopBodyLocalEnv::new();
|
||||||
|
body_env.insert("x".to_string(), ValueId(200)); // Body-local: x=200 (should be ignored)
|
||||||
|
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
let carrier = test_carrier("sum", 200);
|
||||||
|
let update = UpdateExpr::BinOp {
|
||||||
|
lhs: "sum".to_string(),
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
rhs: UpdateRhs::Variable("x".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut value_counter = 120u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_carrier_update_with_env(
|
||||||
|
&carrier,
|
||||||
|
&update,
|
||||||
|
&mut alloc_value,
|
||||||
|
&update_env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
// Should use x=100 (condition env), not x=200 (body-local env)
|
||||||
|
match &instructions[0] {
|
||||||
|
JoinInst::Compute(MirLikeInst::BinOp { dst: _, op: _, lhs: _, rhs }) => {
|
||||||
|
assert_eq!(*rhs, ValueId(100)); // Condition env wins
|
||||||
|
}
|
||||||
|
_ => panic!("Expected BinOp instruction"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_update_with_env_variable_not_found() {
|
||||||
|
// Phase 184: Test error when variable not in either env
|
||||||
|
let (cond_env, body_env) = test_update_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
let carrier = test_carrier("sum", 200);
|
||||||
|
let update = UpdateExpr::BinOp {
|
||||||
|
lhs: "sum".to_string(),
|
||||||
|
op: BinOpKind::Add,
|
||||||
|
rhs: UpdateRhs::Variable("nonexistent".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut value_counter = 130u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_carrier_update_with_env(
|
||||||
|
&carrier,
|
||||||
|
&update,
|
||||||
|
&mut alloc_value,
|
||||||
|
&update_env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_err());
|
||||||
|
let err = result.unwrap_err();
|
||||||
|
assert!(err.contains("Update RHS variable 'nonexistent' not found"));
|
||||||
|
assert!(err.contains("neither condition nor body-local"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_emit_update_with_env_const_update() {
|
||||||
|
// Phase 184: Test UpdateEnv with simple const update (baseline)
|
||||||
|
let (cond_env, body_env) = test_update_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
let carrier = test_carrier("count", 100);
|
||||||
|
let update = UpdateExpr::Const(1);
|
||||||
|
|
||||||
|
let mut value_counter = 140u32;
|
||||||
|
let mut alloc_value = || {
|
||||||
|
let id = ValueId(value_counter);
|
||||||
|
value_counter += 1;
|
||||||
|
id
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
let result = emit_carrier_update_with_env(
|
||||||
|
&carrier,
|
||||||
|
&update,
|
||||||
|
&mut alloc_value,
|
||||||
|
&update_env,
|
||||||
|
&mut instructions,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(result.is_ok());
|
||||||
|
assert_eq!(instructions.len(), 2); // Const + BinOp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
213
src/mir/join_ir/lowering/loop_body_local_env.rs
Normal file
213
src/mir/join_ir/lowering/loop_body_local_env.rs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
//! Phase 184: Loop Body Local Variable Environment
|
||||||
|
//!
|
||||||
|
//! This module provides a storage box for body-local variables in loop patterns.
|
||||||
|
//! It collects local variable declarations from loop body AST and maintains
|
||||||
|
//! name → JoinIR ValueId mappings.
|
||||||
|
//!
|
||||||
|
//! ## Design Philosophy
|
||||||
|
//!
|
||||||
|
//! **Single Responsibility**: This module ONLY handles body-local variable storage.
|
||||||
|
//! It does NOT:
|
||||||
|
//! - Resolve condition variables (that's ConditionEnv)
|
||||||
|
//! - Perform variable resolution priority logic (that's UpdateEnv)
|
||||||
|
//! - Lower AST to JoinIR (that's JoinIrBuilder)
|
||||||
|
//!
|
||||||
|
//! ## Box-First Design
|
||||||
|
//!
|
||||||
|
//! Following 箱理論 (Box Theory) principles:
|
||||||
|
//! - **Single purpose**: Store body-local variable mappings
|
||||||
|
//! - **Clear boundaries**: Only body-scope variables, not condition variables
|
||||||
|
//! - **Deterministic**: BTreeMap ensures consistent ordering
|
||||||
|
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
/// Environment for loop body-local variables
|
||||||
|
///
|
||||||
|
/// Maps variable names to JoinIR-local ValueIds for variables declared
|
||||||
|
/// within the loop body (not in conditions).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```nyash
|
||||||
|
/// loop(i < 5) {
|
||||||
|
/// local temp = i * 2 // Body-local: temp
|
||||||
|
/// sum = sum + temp
|
||||||
|
/// i = i + 1
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// LoopBodyLocalEnv would contain: `{ "temp" → ValueId(5) }`
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct LoopBodyLocalEnv {
|
||||||
|
/// Body-local variable name → JoinIR ValueId mapping
|
||||||
|
///
|
||||||
|
/// BTreeMap ensures deterministic iteration order (important for PHI generation)
|
||||||
|
locals: BTreeMap<String, ValueId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LoopBodyLocalEnv {
|
||||||
|
/// Create a new empty environment
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
locals: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create an environment from loop body AST nodes
|
||||||
|
///
|
||||||
|
/// This method scans the loop body for local variable declarations
|
||||||
|
/// and collects their JoinIR ValueIds.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `body_locals` - List of (name, ValueId) pairs from body analysis
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let body_locals = vec![
|
||||||
|
/// ("temp".to_string(), ValueId(5)),
|
||||||
|
/// ("digit".to_string(), ValueId(6)),
|
||||||
|
/// ];
|
||||||
|
/// let env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||||
|
/// assert_eq!(env.get("temp"), Some(ValueId(5)));
|
||||||
|
/// ```
|
||||||
|
pub fn from_locals(body_locals: Vec<(String, ValueId)>) -> Self {
|
||||||
|
let mut locals = BTreeMap::new();
|
||||||
|
for (name, value_id) in body_locals {
|
||||||
|
locals.insert(name, value_id);
|
||||||
|
}
|
||||||
|
Self { locals }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a body-local variable binding
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Variable name (e.g., "temp", "digit")
|
||||||
|
/// * `join_id` - JoinIR-local ValueId for this variable
|
||||||
|
pub fn insert(&mut self, name: String, join_id: ValueId) {
|
||||||
|
self.locals.insert(name, join_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Look up a body-local variable by name
|
||||||
|
///
|
||||||
|
/// Returns `Some(ValueId)` if the variable exists in the environment,
|
||||||
|
/// `None` otherwise.
|
||||||
|
pub fn get(&self, name: &str) -> Option<ValueId> {
|
||||||
|
self.locals.get(name).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a body-local variable exists in the environment
|
||||||
|
pub fn contains(&self, name: &str) -> bool {
|
||||||
|
self.locals.contains_key(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the number of body-local variables in the environment
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.locals.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the environment is empty
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.locals.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterator over all (name, ValueId) pairs
|
||||||
|
///
|
||||||
|
/// Iteration order is deterministic (sorted by name) due to BTreeMap.
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = (&String, &ValueId)> {
|
||||||
|
self.locals.iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get all variable names (sorted)
|
||||||
|
pub fn names(&self) -> Vec<String> {
|
||||||
|
self.locals.keys().cloned().collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_body_local_env() {
|
||||||
|
let env = LoopBodyLocalEnv::new();
|
||||||
|
assert!(env.is_empty());
|
||||||
|
assert_eq!(env.len(), 0);
|
||||||
|
assert_eq!(env.names(), Vec::<String>::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_body_local() {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
env.insert("temp".to_string(), ValueId(5));
|
||||||
|
|
||||||
|
assert!(!env.is_empty());
|
||||||
|
assert_eq!(env.len(), 1);
|
||||||
|
assert!(env.contains("temp"));
|
||||||
|
assert_eq!(env.get("temp"), Some(ValueId(5)));
|
||||||
|
assert_eq!(env.names(), vec!["temp".to_string()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiple_body_locals() {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
env.insert("temp".to_string(), ValueId(5));
|
||||||
|
env.insert("digit".to_string(), ValueId(6));
|
||||||
|
env.insert("ch".to_string(), ValueId(7));
|
||||||
|
|
||||||
|
assert_eq!(env.len(), 3);
|
||||||
|
assert_eq!(env.get("temp"), Some(ValueId(5)));
|
||||||
|
assert_eq!(env.get("digit"), Some(ValueId(6)));
|
||||||
|
assert_eq!(env.get("ch"), Some(ValueId(7)));
|
||||||
|
|
||||||
|
// BTreeMap ensures sorted keys
|
||||||
|
let names = env.names();
|
||||||
|
assert_eq!(names, vec!["ch", "digit", "temp"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_nonexistent() {
|
||||||
|
let env = LoopBodyLocalEnv::new();
|
||||||
|
assert_eq!(env.get("nonexistent"), None);
|
||||||
|
assert!(!env.contains("nonexistent"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_locals() {
|
||||||
|
let body_locals = vec![
|
||||||
|
("temp".to_string(), ValueId(5)),
|
||||||
|
("digit".to_string(), ValueId(6)),
|
||||||
|
];
|
||||||
|
let env = LoopBodyLocalEnv::from_locals(body_locals);
|
||||||
|
|
||||||
|
assert_eq!(env.len(), 2);
|
||||||
|
assert_eq!(env.get("temp"), Some(ValueId(5)));
|
||||||
|
assert_eq!(env.get("digit"), Some(ValueId(6)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_iter_deterministic_order() {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
// Insert in non-alphabetical order
|
||||||
|
env.insert("zebra".to_string(), ValueId(3));
|
||||||
|
env.insert("apple".to_string(), ValueId(1));
|
||||||
|
env.insert("mango".to_string(), ValueId(2));
|
||||||
|
|
||||||
|
// Iteration should be sorted
|
||||||
|
let names: Vec<_> = env.iter().map(|(name, _)| name.as_str()).collect();
|
||||||
|
assert_eq!(names, vec!["apple", "mango", "zebra"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_overwrite_existing() {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
env.insert("temp".to_string(), ValueId(5));
|
||||||
|
env.insert("temp".to_string(), ValueId(10)); // Overwrite
|
||||||
|
|
||||||
|
assert_eq!(env.len(), 1);
|
||||||
|
assert_eq!(env.get("temp"), Some(ValueId(10)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,7 @@ pub mod carrier_info; // Phase 196: Carrier metadata for loop lowering
|
|||||||
pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction emission
|
pub(crate) mod carrier_update_emitter; // Phase 179: Carrier update instruction emission
|
||||||
pub(crate) mod common; // Internal lowering utilities
|
pub(crate) mod common; // Internal lowering utilities
|
||||||
pub mod condition_env; // Phase 171-fix: Condition expression environment
|
pub mod condition_env; // Phase 171-fix: Condition expression environment
|
||||||
|
pub mod loop_body_local_env; // Phase 184: Body-local variable environment
|
||||||
pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
||||||
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrator (refactored)
|
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrator (refactored)
|
||||||
pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST
|
pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST
|
||||||
@ -62,6 +63,7 @@ pub mod stageb_body;
|
|||||||
pub mod stageb_funcscanner;
|
pub mod stageb_funcscanner;
|
||||||
pub mod type_hint_policy; // Phase 65.5: 型ヒントポリシー箱化
|
pub mod type_hint_policy; // Phase 65.5: 型ヒントポリシー箱化
|
||||||
pub mod type_inference; // Phase 65-2-A
|
pub mod type_inference; // Phase 65-2-A
|
||||||
|
pub mod update_env; // Phase 184: Unified variable resolution for update expressions
|
||||||
pub(crate) mod value_id_ranges; // Internal ValueId range management
|
pub(crate) mod value_id_ranges; // Internal ValueId range management
|
||||||
|
|
||||||
// Re-export public lowering functions
|
// Re-export public lowering functions
|
||||||
|
|||||||
248
src/mir/join_ir/lowering/update_env.rs
Normal file
248
src/mir/join_ir/lowering/update_env.rs
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
//! Phase 184: Update Expression Environment
|
||||||
|
//!
|
||||||
|
//! This module provides a unified variable resolution layer for carrier update expressions.
|
||||||
|
//! It combines ConditionEnv (condition variables) and LoopBodyLocalEnv (body-local variables)
|
||||||
|
//! with clear priority order.
|
||||||
|
//!
|
||||||
|
//! ## Design Philosophy
|
||||||
|
//!
|
||||||
|
//! **Single Responsibility**: This module ONLY handles variable resolution priority logic.
|
||||||
|
//! It does NOT:
|
||||||
|
//! - Store variables (that's ConditionEnv and LoopBodyLocalEnv)
|
||||||
|
//! - Lower AST to JoinIR (that's JoinIrBuilder)
|
||||||
|
//! - Emit update instructions (that's CarrierUpdateEmitter)
|
||||||
|
//!
|
||||||
|
//! ## Box-First Design
|
||||||
|
//!
|
||||||
|
//! Following 箱理論 (Box Theory) principles:
|
||||||
|
//! - **Composition**: Combines two environments without owning them
|
||||||
|
//! - **Clear priority**: Condition variables take precedence
|
||||||
|
//! - **Lightweight**: No allocation, just references
|
||||||
|
|
||||||
|
use crate::mir::join_ir::lowering::condition_env::ConditionEnv;
|
||||||
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
||||||
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// Unified environment for carrier update expression variable resolution
|
||||||
|
///
|
||||||
|
/// This structure provides a composition layer that resolves variables
|
||||||
|
/// with the following priority order:
|
||||||
|
///
|
||||||
|
/// 1. **Condition variables** (ConditionEnv) - Highest priority
|
||||||
|
/// - Loop parameters (e.g., `i`, `end`, `p`)
|
||||||
|
/// - Variables used in condition expressions
|
||||||
|
///
|
||||||
|
/// 2. **Body-local variables** (LoopBodyLocalEnv) - Fallback priority
|
||||||
|
/// - Variables declared in loop body (e.g., `local temp`)
|
||||||
|
/// - Only accessible if not shadowed by condition variables
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```nyash
|
||||||
|
/// loop(i < 5) { // i in ConditionEnv
|
||||||
|
/// local temp = i * 2 // temp in LoopBodyLocalEnv
|
||||||
|
/// sum = sum + temp // Resolves: sum (cond), temp (body)
|
||||||
|
/// i = i + 1
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// let condition_env = /* ... i, sum ... */;
|
||||||
|
/// let body_local_env = /* ... temp ... */;
|
||||||
|
/// let update_env = UpdateEnv::new(&condition_env, &body_local_env);
|
||||||
|
///
|
||||||
|
/// // Resolve "sum" → ConditionEnv (priority 1)
|
||||||
|
/// assert_eq!(update_env.resolve("sum"), Some(ValueId(X)));
|
||||||
|
///
|
||||||
|
/// // Resolve "temp" → LoopBodyLocalEnv (priority 2)
|
||||||
|
/// assert_eq!(update_env.resolve("temp"), Some(ValueId(Y)));
|
||||||
|
///
|
||||||
|
/// // Resolve "unknown" → None
|
||||||
|
/// assert_eq!(update_env.resolve("unknown"), None);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UpdateEnv<'a> {
|
||||||
|
/// Condition variable environment (priority 1)
|
||||||
|
condition_env: &'a ConditionEnv,
|
||||||
|
|
||||||
|
/// Body-local variable environment (priority 2)
|
||||||
|
body_local_env: &'a LoopBodyLocalEnv,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UpdateEnv<'a> {
|
||||||
|
/// Create a new UpdateEnv with priority-ordered resolution
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `condition_env` - Condition variable environment (highest priority)
|
||||||
|
/// * `body_local_env` - Body-local variable environment (fallback)
|
||||||
|
pub fn new(
|
||||||
|
condition_env: &'a ConditionEnv,
|
||||||
|
body_local_env: &'a LoopBodyLocalEnv,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
condition_env,
|
||||||
|
body_local_env,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a variable name to JoinIR ValueId
|
||||||
|
///
|
||||||
|
/// Resolution order:
|
||||||
|
/// 1. Try condition_env.get(name)
|
||||||
|
/// 2. If not found, try body_local_env.get(name)
|
||||||
|
/// 3. If still not found, return None
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `name` - Variable name to resolve
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// * `Some(ValueId)` - Variable found in one of the environments
|
||||||
|
/// * `None` - Variable not found in either environment
|
||||||
|
pub fn resolve(&self, name: &str) -> Option<ValueId> {
|
||||||
|
self.condition_env
|
||||||
|
.get(name)
|
||||||
|
.or_else(|| self.body_local_env.get(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if a variable exists in either environment
|
||||||
|
pub fn contains(&self, name: &str) -> bool {
|
||||||
|
self.resolve(name).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get reference to condition environment (for debugging/diagnostics)
|
||||||
|
pub fn condition_env(&self) -> &ConditionEnv {
|
||||||
|
self.condition_env
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get reference to body-local environment (for debugging/diagnostics)
|
||||||
|
pub fn body_local_env(&self) -> &LoopBodyLocalEnv {
|
||||||
|
self.body_local_env
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// Helper: Create a test ConditionEnv
|
||||||
|
fn test_condition_env() -> ConditionEnv {
|
||||||
|
let mut env = ConditionEnv::new();
|
||||||
|
env.insert("i".to_string(), ValueId(10));
|
||||||
|
env.insert("sum".to_string(), ValueId(20));
|
||||||
|
env.insert("end".to_string(), ValueId(30));
|
||||||
|
env
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: Create a test LoopBodyLocalEnv
|
||||||
|
fn test_body_local_env() -> LoopBodyLocalEnv {
|
||||||
|
let mut env = LoopBodyLocalEnv::new();
|
||||||
|
env.insert("temp".to_string(), ValueId(50));
|
||||||
|
env.insert("digit".to_string(), ValueId(60));
|
||||||
|
env
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_condition_priority() {
|
||||||
|
// Condition variables should be found first
|
||||||
|
let cond_env = test_condition_env();
|
||||||
|
let body_env = LoopBodyLocalEnv::new(); // Empty
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
|
||||||
|
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
|
||||||
|
assert_eq!(update_env.resolve("end"), Some(ValueId(30)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_body_local_fallback() {
|
||||||
|
// Body-local variables should be found when not in condition env
|
||||||
|
let cond_env = ConditionEnv::new(); // Empty
|
||||||
|
let body_env = test_body_local_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
|
||||||
|
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_priority_order() {
|
||||||
|
// Condition env takes priority over body-local env
|
||||||
|
let mut cond_env = ConditionEnv::new();
|
||||||
|
cond_env.insert("x".to_string(), ValueId(100)); // Condition: x=100
|
||||||
|
|
||||||
|
let mut body_env = LoopBodyLocalEnv::new();
|
||||||
|
body_env.insert("x".to_string(), ValueId(200)); // Body-local: x=200
|
||||||
|
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
// Should resolve to condition env value (100), not body-local (200)
|
||||||
|
assert_eq!(update_env.resolve("x"), Some(ValueId(100)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_not_found() {
|
||||||
|
// Variable not in either environment → None
|
||||||
|
let cond_env = test_condition_env();
|
||||||
|
let body_env = test_body_local_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
assert_eq!(update_env.resolve("unknown"), None);
|
||||||
|
assert_eq!(update_env.resolve("nonexistent"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_combined_lookup() {
|
||||||
|
// Mixed lookup: some in condition, some in body-local
|
||||||
|
let cond_env = test_condition_env();
|
||||||
|
let body_env = test_body_local_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
// Condition variables
|
||||||
|
assert_eq!(update_env.resolve("i"), Some(ValueId(10)));
|
||||||
|
assert_eq!(update_env.resolve("sum"), Some(ValueId(20)));
|
||||||
|
|
||||||
|
// Body-local variables
|
||||||
|
assert_eq!(update_env.resolve("temp"), Some(ValueId(50)));
|
||||||
|
assert_eq!(update_env.resolve("digit"), Some(ValueId(60)));
|
||||||
|
|
||||||
|
// Not found
|
||||||
|
assert_eq!(update_env.resolve("unknown"), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_contains() {
|
||||||
|
let cond_env = test_condition_env();
|
||||||
|
let body_env = test_body_local_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
assert!(update_env.contains("i"));
|
||||||
|
assert!(update_env.contains("temp"));
|
||||||
|
assert!(!update_env.contains("unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_empty_environments() {
|
||||||
|
// Both environments empty
|
||||||
|
let cond_env = ConditionEnv::new();
|
||||||
|
let body_env = LoopBodyLocalEnv::new();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
assert_eq!(update_env.resolve("anything"), None);
|
||||||
|
assert!(!update_env.contains("anything"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_accessor_methods() {
|
||||||
|
// Test diagnostic accessor methods
|
||||||
|
let cond_env = test_condition_env();
|
||||||
|
let body_env = test_body_local_env();
|
||||||
|
let update_env = UpdateEnv::new(&cond_env, &body_env);
|
||||||
|
|
||||||
|
// Should return references to underlying environments
|
||||||
|
assert_eq!(update_env.condition_env().len(), 3);
|
||||||
|
assert_eq!(update_env.body_local_env().len(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user