refactor(joinir): Refactor-A+B - Null literal + ContinueReturn 一般化
## Refactor-A: Null literal 対応 - expr.rs に "Null" case 追加 - ConstValue::Null → JoinValue::Unit マッピング ## Refactor-B: ContinueReturn 一般化 - 複数 return-if 許可(同値のみ) - json_values_equal() ヘルパー追加 - Fail-Fast: 異なる値は即エラー ## Fixtures & Tests - null_literal_min.program.json (return null) - continue_return_multi_min.program.json (複数 return null) - +1 test (vm_bridge 検証) Impact: - normalized_dev: 63→64 passed (+1) - lib: 993 passed (回帰なし) - 箱化スコア: 5/5 (全項目満点) - 実ループ(_parse_array/_parse_object) 準備完了 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Submodule docs/private updated: 983e5143d4...0866b96a85
@ -300,6 +300,17 @@ impl AstToJoinIrLowerer {
|
|||||||
(dst, insts)
|
(dst, insts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refactor-A: Null literal 対応(実ループ基盤整備)
|
||||||
|
"Null" => {
|
||||||
|
let dst = ctx.alloc_var();
|
||||||
|
let inst = JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Const {
|
||||||
|
dst,
|
||||||
|
value: ConstValue::Null,
|
||||||
|
});
|
||||||
|
|
||||||
|
(dst, vec![inst])
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 56: Call(関数呼び出し、pred(v) 等)
|
// Phase 56: Call(関数呼び出し、pred(v) 等)
|
||||||
// 変数参照の関数呼び出しは MethodCall で代替(receiver=func_var, method="__call__")
|
// 変数参照の関数呼び出しは MethodCall で代替(receiver=func_var, method="__call__")
|
||||||
"Call" => {
|
"Call" => {
|
||||||
|
|||||||
@ -41,6 +41,11 @@ use super::{AstToJoinIrLowerer, JoinModule, LoweringError};
|
|||||||
use crate::mir::join_ir::{CompareOp, ConstValue, JoinFunction, JoinInst, MirLikeInst};
|
use crate::mir::join_ir::{CompareOp, ConstValue, JoinFunction, JoinInst, MirLikeInst};
|
||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
|
|
||||||
|
/// Refactor-B: JSON 値の等価性チェック(serde_json::Value の PartialEq を使用)
|
||||||
|
fn json_values_equal(a: &serde_json::Value, b: &serde_json::Value) -> bool {
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
|
||||||
/// ContinueReturn パターンを JoinModule に変換
|
/// ContinueReturn パターンを JoinModule に変換
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@ -70,7 +75,7 @@ pub fn lower(
|
|||||||
message: "Loop must have 'body' array".to_string(),
|
message: "Loop must have 'body' array".to_string(),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// 5-1. Return If を探す(Fail-Fast: 複数あればエラー)
|
// 5-1. Return If を探す(Refactor-B: 複数許可、値の等価性チェック)
|
||||||
let return_if_stmts: Vec<_> = loop_body
|
let return_if_stmts: Vec<_> = loop_body
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|stmt| {
|
.filter(|stmt| {
|
||||||
@ -87,17 +92,38 @@ pub fn lower(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refactor-B: 複数の return-if がある場合、返す値が全て同一かチェック
|
||||||
if return_if_stmts.len() > 1 {
|
if return_if_stmts.len() > 1 {
|
||||||
|
// 各 return-if の return 値を抽出
|
||||||
|
let return_values: Vec<&serde_json::Value> = return_if_stmts
|
||||||
|
.iter()
|
||||||
|
.map(|stmt| {
|
||||||
|
let then = stmt["then"].as_array().expect("then must be array");
|
||||||
|
let ret = then
|
||||||
|
.iter()
|
||||||
|
.find(|s| s["type"].as_str() == Some("Return"))
|
||||||
|
.expect("return must exist");
|
||||||
|
&ret["expr"]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// 最初の値と他の値を比較
|
||||||
|
let first_value = return_values[0];
|
||||||
|
for (i, value) in return_values.iter().enumerate().skip(1) {
|
||||||
|
if !json_values_equal(first_value, value) {
|
||||||
return Err(LoweringError::InvalidLoopBody {
|
return Err(LoweringError::InvalidLoopBody {
|
||||||
message: format!(
|
message: format!(
|
||||||
"ContinueReturn pattern validation failed: multiple Return statements found.\n\
|
"ContinueReturn pattern validation failed: multiple return-if statements with different values.\n\
|
||||||
Expected: Exactly one 'if {{ return ... }}' statement in loop body.\n\
|
Expected: All early returns must have identical values.\n\
|
||||||
Found: {} Return statements.\n\
|
First return: {:?}\n\
|
||||||
Hint: Combine multiple return conditions into a single if statement.",
|
Return #{}: {:?}\n\
|
||||||
return_if_stmts.len()
|
Hint: Ensure all early returns have identical values, or combine conditions.",
|
||||||
|
first_value, i + 1, value
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let return_if_stmt = return_if_stmts[0];
|
let return_if_stmt = return_if_stmts[0];
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,9 @@ pub enum NormalizedDevFixture {
|
|||||||
PatternContinueReturnMin,
|
PatternContinueReturnMin,
|
||||||
/// Parse String Composite minimal (Phase 90 P0)
|
/// Parse String Composite minimal (Phase 90 P0)
|
||||||
ParseStringCompositeMin,
|
ParseStringCompositeMin,
|
||||||
|
/// Refactor-B: ContinueReturn multi minimal (multiple return-if with same value)
|
||||||
|
/// Note: This also tests Null literal support from Refactor-A
|
||||||
|
ContinueReturnMultiMin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NormalizedDevFixture {
|
impl NormalizedDevFixture {
|
||||||
@ -36,6 +39,7 @@ impl NormalizedDevFixture {
|
|||||||
}
|
}
|
||||||
Self::PatternContinueReturnMin => "pattern_continue_return_minimal",
|
Self::PatternContinueReturnMin => "pattern_continue_return_minimal",
|
||||||
Self::ParseStringCompositeMin => "parse_string_composite_minimal",
|
Self::ParseStringCompositeMin => "parse_string_composite_minimal",
|
||||||
|
Self::ContinueReturnMultiMin => "continue_return_multi_minimal",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,6 +61,9 @@ impl NormalizedDevFixture {
|
|||||||
Self::ParseStringCompositeMin => {
|
Self::ParseStringCompositeMin => {
|
||||||
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/parse_string_composite_min.program.json"
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/parse_string_composite_min.program.json"
|
||||||
}
|
}
|
||||||
|
Self::ContinueReturnMultiMin => {
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/continue_return_multi_min.program.json"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +75,8 @@ impl NormalizedDevFixture {
|
|||||||
| Self::Pattern4JsonParserParseArrayContinueSkipWs
|
| Self::Pattern4JsonParserParseArrayContinueSkipWs
|
||||||
| Self::Pattern4JsonParserParseObjectContinueSkipWs
|
| Self::Pattern4JsonParserParseObjectContinueSkipWs
|
||||||
| Self::PatternContinueReturnMin
|
| Self::PatternContinueReturnMin
|
||||||
| Self::ParseStringCompositeMin => FunctionRoute::LoopFrontend,
|
| Self::ParseStringCompositeMin
|
||||||
|
| Self::ContinueReturnMultiMin => FunctionRoute::LoopFrontend,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,6 +101,9 @@ impl NormalizedDevFixture {
|
|||||||
Self::ParseStringCompositeMin => include_str!(
|
Self::ParseStringCompositeMin => include_str!(
|
||||||
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/parse_string_composite_min.program.json"
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/parse_string_composite_min.program.json"
|
||||||
),
|
),
|
||||||
|
Self::ContinueReturnMultiMin => include_str!(
|
||||||
|
"../../../../docs/private/roadmap2/phases/normalized_dev/fixtures/continue_return_multi_min.program.json"
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +133,7 @@ pub const ALL_DEV_FIXTURES: &[NormalizedDevFixture] = &[
|
|||||||
NormalizedDevFixture::Pattern4JsonParserParseObjectContinueSkipWs,
|
NormalizedDevFixture::Pattern4JsonParserParseObjectContinueSkipWs,
|
||||||
NormalizedDevFixture::PatternContinueReturnMin,
|
NormalizedDevFixture::PatternContinueReturnMin,
|
||||||
NormalizedDevFixture::ParseStringCompositeMin,
|
NormalizedDevFixture::ParseStringCompositeMin,
|
||||||
|
NormalizedDevFixture::ContinueReturnMultiMin,
|
||||||
];
|
];
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -156,7 +168,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pattern4_fixtures_route_to_loop_frontend() {
|
fn test_all_fixtures_route_to_loop_frontend() {
|
||||||
for fixture in ALL_DEV_FIXTURES {
|
for fixture in ALL_DEV_FIXTURES {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fixture.route(),
|
fixture.route(),
|
||||||
|
|||||||
@ -599,3 +599,20 @@ fn test_parse_string_composite_min_expected_output() {
|
|||||||
"Expected acc=5 for n=10 (i=0,1,2,5,6 increments acc, i=3 escape continue, i=7 close quote return)"
|
"Expected acc=5 for n=10 (i=0,1,2,5,6 increments acc, i=3 escape continue, i=7 close quote return)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Refactor-A+B: ContinueReturn multi minimal - tests both Null literal and multiple return-if
|
||||||
|
#[test]
|
||||||
|
fn test_continue_return_multi_min_returns_null_at_first_match() {
|
||||||
|
use nyash_rust::mir::join_ir::normalized::dev_fixtures::NormalizedDevFixture;
|
||||||
|
|
||||||
|
let _ctx = normalized_dev_test_ctx();
|
||||||
|
let structured = NormalizedDevFixture::ContinueReturnMultiMin.load_and_lower();
|
||||||
|
let entry = structured.entry.expect("entry required");
|
||||||
|
|
||||||
|
let input = [JoinValue::Int(10)];
|
||||||
|
let result = run_joinir_vm_bridge(&structured, entry, &input, true);
|
||||||
|
// Tests:
|
||||||
|
// - Refactor-A: Null literal support (returns ConstValue::Null → JoinValue::Unit)
|
||||||
|
// - Refactor-B: Multiple return-if with same value (i==3, i==7 both return null)
|
||||||
|
assert_eq!(result, JoinValue::Unit, "Expected Unit (null) from first return-if at i=3");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user