2025-12-11 20:54:33 +09:00
|
|
|
|
#![cfg(all(feature = "normalized_dev", debug_assertions))]
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
use nyash_rust::backend::{mir_interpreter::MirInterpreter, VMValue};
|
2025-12-11 20:54:33 +09:00
|
|
|
|
use nyash_rust::mir::join_ir::{
|
|
|
|
|
|
normalize_pattern1_minimal, normalize_pattern2_minimal, normalized_pattern1_to_structured,
|
|
|
|
|
|
normalized_pattern2_to_structured, BinOpKind, ConstValue, JoinContId, JoinFuncId,
|
|
|
|
|
|
JoinFunction, JoinInst, JoinIrPhase, JoinModule, MirLikeInst,
|
|
|
|
|
|
};
|
2025-12-12 03:15:45 +09:00
|
|
|
|
use nyash_rust::mir::join_ir::normalized::dev_env::{
|
|
|
|
|
|
normalized_dev_enabled, test_ctx, NormalizedDevEnvGuard, NormalizedTestContext,
|
|
|
|
|
|
};
|
2025-12-11 20:54:33 +09:00
|
|
|
|
use nyash_rust::mir::join_ir::normalized::fixtures::{
|
2025-12-11 22:12:46 +09:00
|
|
|
|
build_jsonparser_atoi_structured_for_normalized_dev,
|
2025-12-12 03:15:45 +09:00
|
|
|
|
build_jsonparser_atoi_real_structured_for_normalized_dev,
|
2025-12-12 16:40:20 +09:00
|
|
|
|
build_jsonparser_parse_array_continue_skip_ws_structured_for_normalized_dev,
|
2025-12-12 03:15:45 +09:00
|
|
|
|
build_jsonparser_parse_number_real_structured_for_normalized_dev,
|
2025-12-12 16:40:20 +09:00
|
|
|
|
build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev,
|
2025-12-12 03:15:45 +09:00
|
|
|
|
build_jsonparser_skip_ws_real_structured_for_normalized_dev,
|
2025-12-11 20:54:33 +09:00
|
|
|
|
build_jsonparser_skip_ws_structured_for_normalized_dev,
|
|
|
|
|
|
build_pattern2_break_fixture_structured, build_pattern2_minimal_structured,
|
2025-12-12 05:07:01 +09:00
|
|
|
|
build_pattern3_if_sum_min_structured_for_normalized_dev,
|
2025-12-12 07:13:34 +09:00
|
|
|
|
build_pattern3_if_sum_multi_min_structured_for_normalized_dev,
|
|
|
|
|
|
build_pattern3_json_if_sum_min_structured_for_normalized_dev,
|
2025-12-12 06:31:13 +09:00
|
|
|
|
build_pattern4_continue_min_structured_for_normalized_dev,
|
2025-12-12 16:40:20 +09:00
|
|
|
|
build_selfhost_args_parse_p2_structured_for_normalized_dev,
|
|
|
|
|
|
build_selfhost_if_sum_p3_ext_structured_for_normalized_dev,
|
|
|
|
|
|
build_selfhost_if_sum_p3_structured_for_normalized_dev,
|
|
|
|
|
|
build_selfhost_stmt_count_p3_structured_for_normalized_dev,
|
|
|
|
|
|
build_selfhost_token_scan_p2_accum_structured_for_normalized_dev,
|
|
|
|
|
|
build_selfhost_token_scan_p2_structured_for_normalized_dev,
|
2025-12-11 20:54:33 +09:00
|
|
|
|
};
|
|
|
|
|
|
use nyash_rust::mir::join_ir_runner::run_joinir_function;
|
|
|
|
|
|
use nyash_rust::mir::join_ir_ops::JoinValue;
|
2025-12-12 16:40:20 +09:00
|
|
|
|
use nyash_rust::mir::join_ir_vm_bridge::{
|
|
|
|
|
|
convert_join_module_to_mir_with_meta, run_joinir_via_vm,
|
|
|
|
|
|
};
|
2025-12-11 20:54:33 +09:00
|
|
|
|
use nyash_rust::mir::ValueId;
|
2025-12-12 16:40:20 +09:00
|
|
|
|
use std::collections::BTreeMap;
|
2025-12-12 03:15:45 +09:00
|
|
|
|
fn normalized_dev_test_ctx() -> NormalizedTestContext<'static> {
|
|
|
|
|
|
let ctx = test_ctx();
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
normalized_dev_enabled(),
|
|
|
|
|
|
"Phase 40: normalized_dev must be enabled for normalized_* tests (feature + NYASH_JOINIR_NORMALIZED_DEV_RUN=1)"
|
|
|
|
|
|
);
|
|
|
|
|
|
ctx
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
fn assert_normalized_dev_ready() {
|
|
|
|
|
|
assert!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
normalized_dev_enabled(),
|
|
|
|
|
|
"Phase 40: normalized_dev must be enabled for normalized_* tests (feature + NYASH_JOINIR_NORMALIZED_DEV_RUN=1)"
|
2025-12-11 20:54:33 +09:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn run_joinir_runner(
|
|
|
|
|
|
module: &JoinModule,
|
|
|
|
|
|
entry: JoinFuncId,
|
|
|
|
|
|
args: &[JoinValue],
|
|
|
|
|
|
normalized: bool,
|
|
|
|
|
|
) -> JoinValue {
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let _guard = NormalizedDevEnvGuard::new(normalized);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
if normalized {
|
|
|
|
|
|
assert_normalized_dev_ready();
|
|
|
|
|
|
}
|
|
|
|
|
|
let mut vm = MirInterpreter::new();
|
|
|
|
|
|
run_joinir_function(&mut vm, module, entry, args).expect("JoinIR runner should succeed")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn run_joinir_vm_bridge(
|
|
|
|
|
|
module: &JoinModule,
|
|
|
|
|
|
entry: JoinFuncId,
|
|
|
|
|
|
args: &[JoinValue],
|
|
|
|
|
|
normalized: bool,
|
|
|
|
|
|
) -> JoinValue {
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let _guard = NormalizedDevEnvGuard::new(normalized);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
if normalized {
|
|
|
|
|
|
assert_normalized_dev_ready();
|
|
|
|
|
|
}
|
|
|
|
|
|
run_joinir_via_vm(module, entry, args).expect("JoinIR→MIR execution should succeed")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
fn run_joinir_vm_bridge_structured_only(
|
|
|
|
|
|
module: &JoinModule,
|
|
|
|
|
|
entry: JoinFuncId,
|
|
|
|
|
|
args: &[JoinValue],
|
|
|
|
|
|
) -> JoinValue {
|
|
|
|
|
|
let mir =
|
|
|
|
|
|
convert_join_module_to_mir_with_meta(module, &BTreeMap::new()).expect("structured bridge");
|
|
|
|
|
|
let mut vm = MirInterpreter::new();
|
|
|
|
|
|
let entry_name = format!("join_func_{}", entry.0);
|
|
|
|
|
|
let vm_args: Vec<VMValue> = args.iter().cloned().map(|v| v.into_vm_value()).collect();
|
|
|
|
|
|
let result = vm
|
|
|
|
|
|
.execute_function_with_args(&mir, &entry_name, &vm_args)
|
|
|
|
|
|
.expect("VM execution should succeed");
|
|
|
|
|
|
JoinValue::from_vm_value(&result).expect("result conversion")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
fn build_structured_pattern1() -> JoinModule {
|
|
|
|
|
|
let mut module = JoinModule::new();
|
|
|
|
|
|
let mut loop_fn = JoinFunction::new(
|
|
|
|
|
|
JoinFuncId::new(1),
|
|
|
|
|
|
"loop_step".to_string(),
|
|
|
|
|
|
vec![ValueId(10)],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
|
dst: ValueId(11),
|
|
|
|
|
|
value: ConstValue::Integer(0),
|
|
|
|
|
|
}));
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
|
|
|
|
|
dst: ValueId(12),
|
|
|
|
|
|
op: BinOpKind::Add,
|
|
|
|
|
|
lhs: ValueId(10),
|
|
|
|
|
|
rhs: ValueId(11),
|
|
|
|
|
|
}));
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Jump {
|
|
|
|
|
|
cont: JoinContId(2),
|
|
|
|
|
|
args: vec![ValueId(12)],
|
|
|
|
|
|
cond: None, // 単純経路: 無条件で k_exit に渡して終了
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let mut k_exit = JoinFunction::new(JoinFuncId::new(2), "k_exit".to_string(), vec![ValueId(12)]);
|
|
|
|
|
|
k_exit.body.push(JoinInst::Ret {
|
|
|
|
|
|
value: Some(ValueId(12)),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.entry = Some(loop_fn.id);
|
|
|
|
|
|
module.add_function(loop_fn);
|
|
|
|
|
|
module.add_function(k_exit);
|
|
|
|
|
|
module
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_minimal_smoke() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
|
|
|
|
|
assert!(!normalized.env_layouts.is_empty());
|
|
|
|
|
|
assert!(!normalized.functions.is_empty());
|
|
|
|
|
|
|
|
|
|
|
|
// Structured バックアップが保持されていることを確認
|
|
|
|
|
|
let restored = normalized
|
|
|
|
|
|
.to_structured()
|
|
|
|
|
|
.expect("structured backup should exist");
|
|
|
|
|
|
assert!(restored.is_structured());
|
|
|
|
|
|
assert_eq!(restored.functions.len(), structured.functions.len());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_roundtrip_structured_equivalent() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(reconstructed.is_structured());
|
|
|
|
|
|
assert_eq!(reconstructed.functions.len(), structured.functions.len());
|
|
|
|
|
|
|
|
|
|
|
|
for (fid, func) in &structured.functions {
|
|
|
|
|
|
let recon = reconstructed
|
|
|
|
|
|
.functions
|
|
|
|
|
|
.get(fid)
|
|
|
|
|
|
.expect("function missing after reconstruction");
|
|
|
|
|
|
assert_eq!(recon.params.len(), func.params.len());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_exec_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
let entry = structured.entry.unwrap_or(JoinFuncId::new(1));
|
|
|
|
|
|
let input = [JoinValue::Int(0)];
|
|
|
|
|
|
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let result_structured = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let result_norm = run_joinir_vm_bridge(&reconstructed, entry, &input, false);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(result_structured, result_norm);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_exec_matches_structured_roundtrip_backup() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
|
|
|
|
|
let restored_backup = normalized
|
|
|
|
|
|
.to_structured()
|
|
|
|
|
|
.expect("structured backup should be present");
|
|
|
|
|
|
|
|
|
|
|
|
let entry = structured.entry.unwrap_or(JoinFuncId::new(1));
|
|
|
|
|
|
let input = [JoinValue::Int(0)];
|
|
|
|
|
|
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false);
|
|
|
|
|
|
let restored = run_joinir_vm_bridge(&restored_backup, entry, &input, false);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, recon);
|
|
|
|
|
|
assert_eq!(base, restored);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_roundtrip_structure() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_minimal_structured();
|
|
|
|
|
|
let normalized = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
|
|
|
|
|
|
|
|
|
|
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
|
|
|
|
|
assert!(reconstructed.is_structured());
|
|
|
|
|
|
assert_eq!(reconstructed.functions.len(), structured.functions.len());
|
|
|
|
|
|
|
|
|
|
|
|
for name in ["main", "loop_step", "k_exit"] {
|
|
|
|
|
|
let original_has = structured.functions.values().any(|f| f.name == name);
|
|
|
|
|
|
let reconstructed_has = reconstructed.functions.values().any(|f| f.name == name);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
original_has && reconstructed_has,
|
|
|
|
|
|
"expected function '{}' to exist in both modules",
|
|
|
|
|
|
name
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_jsonparser_parse_number_real_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_jsonparser_parse_number_real_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [
|
|
|
|
|
|
("42", 0, "42"),
|
|
|
|
|
|
("123abc", 0, "123"),
|
|
|
|
|
|
("9", 0, "9"),
|
|
|
|
|
|
("abc", 0, ""),
|
|
|
|
|
|
("xx7yy", 2, "7"),
|
|
|
|
|
|
("007", 0, "007"),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (s, pos, expected) in cases {
|
|
|
|
|
|
let input = [JoinValue::Str(s.to_string()), JoinValue::Int(pos)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Str(expected.to_string()),
|
|
|
|
|
|
"unexpected result for input '{}' (pos={}) (expected num_str)",
|
|
|
|
|
|
s,
|
|
|
|
|
|
pos
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_exec_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_minimal_structured();
|
|
|
|
|
|
let normalized = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
let entry = structured.entry.unwrap_or(JoinFuncId::new(0));
|
|
|
|
|
|
let input = [JoinValue::Int(0)];
|
|
|
|
|
|
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, recon);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[should_panic(expected = "normalize_pattern2_minimal")]
|
|
|
|
|
|
fn normalized_pattern2_rejects_non_pattern2_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
// Pattern1 Structured module should be rejected by Pattern2 normalizer.
|
|
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let _ = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_real_loop_roundtrip_structure() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_break_fixture_structured();
|
|
|
|
|
|
let normalized = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(reconstructed.is_structured());
|
|
|
|
|
|
assert_eq!(structured.functions.len(), reconstructed.functions.len());
|
|
|
|
|
|
assert_eq!(structured.entry, reconstructed.entry);
|
|
|
|
|
|
|
|
|
|
|
|
let original_names: Vec<_> = structured
|
|
|
|
|
|
.functions
|
|
|
|
|
|
.values()
|
|
|
|
|
|
.map(|f| f.name.clone())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
for name in original_names {
|
|
|
|
|
|
let reconstructed_has = reconstructed.functions.values().any(|f| f.name == name);
|
|
|
|
|
|
assert!(reconstructed_has, "function '{}' missing after roundtrip", name);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_real_loop_exec_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_break_fixture_structured();
|
|
|
|
|
|
let normalized = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern2_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 3, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
2025-12-11 22:50:23 +09:00
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, recon, "mismatch at n={}", n);
|
|
|
|
|
|
let expected_sum = n * (n.saturating_sub(1)) / 2;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
base,
|
|
|
|
|
|
JoinValue::Int(expected_sum),
|
|
|
|
|
|
"unexpected loop result at n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_runner_dev_switch_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let input = [JoinValue::Int(7)];
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_runner(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_runner(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev);
|
|
|
|
|
|
assert_eq!(base, JoinValue::Int(7));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_runner_dev_switch_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_break_fixture_structured();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 3, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_runner(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_runner(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "runner mismatch at n={}", n);
|
|
|
|
|
|
let expected_sum = n * (n.saturating_sub(1)) / 2;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(expected_sum),
|
|
|
|
|
|
"runner result mismatch at n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_jsonparser_runner_dev_switch_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 2, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for len in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(len)];
|
|
|
|
|
|
let base = run_joinir_runner(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_runner(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "runner mismatch at len={}", len);
|
|
|
|
|
|
assert_eq!(dev, JoinValue::Int(len), "unexpected result at len={}", len);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-12-11 22:12:46 +09:00
|
|
|
|
fn normalized_pattern2_vm_bridge_direct_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_pattern2_break_fixture_structured();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 3, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch at n={}", n);
|
|
|
|
|
|
let expected_sum = n * (n.saturating_sub(1)) / 2;
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(expected_sum),
|
|
|
|
|
|
"vm bridge result mismatch at n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-12-11 22:12:46 +09:00
|
|
|
|
fn normalized_pattern1_vm_bridge_direct_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 5, 7];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch at n={}", n);
|
|
|
|
|
|
assert_eq!(dev, JoinValue::Int(n), "unexpected result at n={}", n);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
2025-12-11 22:12:46 +09:00
|
|
|
|
fn normalized_pattern2_jsonparser_vm_bridge_direct_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
let structured = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 2, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for len in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(len)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch at len={}", len);
|
|
|
|
|
|
assert_eq!(dev, JoinValue::Int(len), "unexpected result at len={}", len);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_jsonparser_skip_ws_real_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_jsonparser_skip_ws_real_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [
|
|
|
|
|
|
(" abc", 0, 3),
|
|
|
|
|
|
("abc", 0, 0),
|
|
|
|
|
|
(" \t\nx", 0, 3),
|
|
|
|
|
|
(" \t\nx", 2, 3),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (s, pos, expected) in cases {
|
|
|
|
|
|
let input = [JoinValue::Str(s.to_string()), JoinValue::Int(pos)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(expected),
|
|
|
|
|
|
"unexpected result for input '{}' (pos={})",
|
|
|
|
|
|
s,
|
|
|
|
|
|
pos
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 22:12:46 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_jsonparser_atoi_vm_bridge_direct_matches_structured() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let structured = build_jsonparser_atoi_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [
|
|
|
|
|
|
("42", 2, 42),
|
|
|
|
|
|
("123abc", 6, 123),
|
|
|
|
|
|
("007", 3, 7),
|
|
|
|
|
|
("", 0, 0),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (s, len, expected) in cases {
|
|
|
|
|
|
let input = [JoinValue::Str(s.to_string()), JoinValue::Int(len)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(expected),
|
|
|
|
|
|
"unexpected result for input '{}'",
|
|
|
|
|
|
s
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 03:15:45 +09:00
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern2_jsonparser_atoi_real_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_jsonparser_atoi_real_structured_for_normalized_dev();
|
|
|
|
|
|
if nyash_rust::config::env::joinir_test_debug_enabled() {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[joinir/normalized-dev/test] structured jsonparser_atoi_real: {:#?}",
|
|
|
|
|
|
structured
|
|
|
|
|
|
);
|
|
|
|
|
|
let normalized = normalize_pattern2_minimal(&structured);
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[joinir/normalized-dev/test] normalized jsonparser_atoi_real: {:#?}",
|
|
|
|
|
|
normalized
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [
|
|
|
|
|
|
("42", 42),
|
|
|
|
|
|
("123abc", 123),
|
|
|
|
|
|
("007", 7),
|
|
|
|
|
|
("", 0),
|
|
|
|
|
|
("abc", 0),
|
|
|
|
|
|
("-42", -42),
|
|
|
|
|
|
("+7", 7),
|
|
|
|
|
|
("-0", 0),
|
|
|
|
|
|
("-12x", -12),
|
|
|
|
|
|
("+", 0),
|
|
|
|
|
|
("-", 0),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
for (s, expected) in cases {
|
|
|
|
|
|
let input = [JoinValue::Str(s.to_string())];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for input '{}'", s);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(expected),
|
|
|
|
|
|
"unexpected result for input '{}'",
|
|
|
|
|
|
s
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 04:40:46 +09:00
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_token_scan_p2_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_token_scan_p2_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 3, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for n={}", n);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(n),
|
|
|
|
|
|
"unexpected result for selfhost_token_scan_p2 n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_token_scan_p2_accum_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_token_scan_p2_accum_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let cases = [0, 1, 3, 5];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for selfhost_token_scan_p2_accum n={}", n);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 05:07:01 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern3_if_sum_minimal_runner_dev_switch_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_pattern3_if_sum_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
// phase212_if_sum_min.hako 相当: sum=2 になることを期待
|
|
|
|
|
|
let input: [JoinValue; 0] = [];
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_runner(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_runner(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "runner mismatch for P3 minimal if-sum");
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(2),
|
|
|
|
|
|
"unexpected result for P3 minimal if-sum (expected sum=2)",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 07:13:34 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern3_if_sum_multi_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_pattern3_if_sum_multi_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let input = [JoinValue::Int(0)];
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for P3 if-sum multi");
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(2),
|
|
|
|
|
|
"unexpected result for P3 if-sum multi (expected sum=2)"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern3_json_if_sum_min_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_pattern3_json_if_sum_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
let input = [JoinValue::Int(0)];
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for P3 json if-sum");
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(10),
|
|
|
|
|
|
"unexpected result for P3 json if-sum (expected sum=10)"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_if_sum_p3_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_if_sum_p3_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
2025-12-12 22:15:41 +09:00
|
|
|
|
let cases = [0, 1, 3, 4, 5];
|
2025-12-12 16:40:20 +09:00
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for selfhost_if_sum_p3 n={}", n);
|
2025-12-12 22:15:41 +09:00
|
|
|
|
assert_eq!(dev, JoinValue::Int(expected_selfhost_if_sum_p3(n)));
|
2025-12-12 16:40:20 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_if_sum_p3_ext_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_if_sum_p3_ext_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
2025-12-12 22:15:41 +09:00
|
|
|
|
let cases = [0, 1, 3, 4, 5];
|
2025-12-12 16:40:20 +09:00
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for selfhost_if_sum_p3_ext n={}", n);
|
2025-12-12 22:15:41 +09:00
|
|
|
|
assert_eq!(dev, JoinValue::Int(expected_selfhost_if_sum_p3_ext(n)));
|
2025-12-12 16:40:20 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 22:15:41 +09:00
|
|
|
|
fn expected_selfhost_if_sum_p3(n: i64) -> i64 {
|
|
|
|
|
|
if n <= 1 {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
let sum = (n - 1) * n / 2;
|
|
|
|
|
|
let count = n - 1;
|
|
|
|
|
|
sum + count
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn expected_selfhost_if_sum_p3_ext(n: i64) -> i64 {
|
|
|
|
|
|
if n <= 0 {
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
// i=0: sum += 1
|
|
|
|
|
|
// i=1..n-1: sum += i, count += 1
|
|
|
|
|
|
let sum = 1 + (n - 1) * n / 2;
|
|
|
|
|
|
let count = n - 1;
|
|
|
|
|
|
sum + count
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
/// Phase 53: selfhost args-parse P2 (practical variation with string carrier)
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_args_parse_p2_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_args_parse_p2_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
// Test different argc values: 0, 1, 2, 3
|
|
|
|
|
|
let cases = [0, 1, 2, 3];
|
|
|
|
|
|
|
|
|
|
|
|
for argc in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(argc)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for selfhost_args_parse_p2 argc={}", argc);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 53: selfhost stmt-count P3 (practical variation with multi-branch if-else)
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_selfhost_stmt_count_p3_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_selfhost_stmt_count_p3_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
// Test different statement counts: 0, 5, 10, 15
|
|
|
|
|
|
let cases = [0, 5, 10, 15];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let input = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for selfhost_stmt_count_p3 n={}", n);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 04:40:46 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase46_canonical_set_includes_p2_mid() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::normalized::shape_guard::{
|
|
|
|
|
|
is_canonical_shape, NormalizedDevShape,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 46: Verify P2-Mid patterns are canonical
|
|
|
|
|
|
assert!(is_canonical_shape(&NormalizedDevShape::JsonparserAtoiReal));
|
|
|
|
|
|
assert!(is_canonical_shape(
|
|
|
|
|
|
&NormalizedDevShape::JsonparserParseNumberReal
|
|
|
|
|
|
));
|
2025-12-12 07:13:34 +09:00
|
|
|
|
assert!(is_canonical_shape(
|
|
|
|
|
|
&NormalizedDevShape::Pattern3IfSumMinimal
|
|
|
|
|
|
));
|
|
|
|
|
|
assert!(is_canonical_shape(
|
|
|
|
|
|
&NormalizedDevShape::Pattern3IfSumMulti
|
|
|
|
|
|
));
|
|
|
|
|
|
assert!(is_canonical_shape(
|
|
|
|
|
|
&NormalizedDevShape::Pattern3IfSumJson
|
|
|
|
|
|
));
|
2025-12-12 04:40:46 +09:00
|
|
|
|
|
|
|
|
|
|
// Verify P2-Core patterns still canonical
|
|
|
|
|
|
assert!(is_canonical_shape(&NormalizedDevShape::Pattern2Mini));
|
|
|
|
|
|
assert!(is_canonical_shape(&NormalizedDevShape::JsonparserSkipWsMini));
|
|
|
|
|
|
assert!(is_canonical_shape(&NormalizedDevShape::JsonparserSkipWsReal));
|
|
|
|
|
|
assert!(is_canonical_shape(&NormalizedDevShape::JsonparserAtoiMini));
|
|
|
|
|
|
}
|
2025-12-12 05:53:23 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 47-A: Test P3 minimal normalization
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase47a_pattern3_if_sum_minimal_normalization() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal;
|
|
|
|
|
|
|
|
|
|
|
|
let module = build_pattern3_if_sum_min_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Test that normalization succeeds (includes shape detection internally)
|
|
|
|
|
|
let result = normalize_pattern3_if_sum_minimal(&module);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
|
"P3 normalization should succeed (shape detection + normalization): {:?}",
|
|
|
|
|
|
result.err()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let normalized = result.unwrap();
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
normalized.functions.len(),
|
|
|
|
|
|
module.functions.len(),
|
|
|
|
|
|
"Normalized function count should match Structured"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify normalized module has proper phase
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
normalized.phase,
|
|
|
|
|
|
nyash_rust::mir::join_ir::JoinIrPhase::Normalized,
|
|
|
|
|
|
"Normalized module should have Normalized phase"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 47-A: Test P3 VM execution (basic smoke test)
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase47a_pattern3_if_sum_minimal_runner() {
|
|
|
|
|
|
let module = build_pattern3_if_sum_min_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Basic test: module should be runnable through JoinIR runner
|
|
|
|
|
|
// This test verifies the P3 fixture is valid and generates proper JoinIR
|
|
|
|
|
|
assert_eq!(module.functions.len(), 3, "P3 should have 3 functions");
|
|
|
|
|
|
|
|
|
|
|
|
let entry = module.entry.expect("P3 should have entry function");
|
|
|
|
|
|
assert_eq!(entry.0, 0, "Entry should be function 0");
|
|
|
|
|
|
}
|
2025-12-12 06:31:13 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 48-A: Test P4 minimal normalization
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase48a_pattern4_continue_minimal_normalization() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::normalized::normalize_pattern4_continue_minimal;
|
|
|
|
|
|
|
|
|
|
|
|
let module = build_pattern4_continue_min_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Test that normalization succeeds (includes shape detection internally)
|
|
|
|
|
|
let result = normalize_pattern4_continue_minimal(&module);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
|
"P4 normalization should succeed (shape detection + normalization): {:?}",
|
|
|
|
|
|
result.err()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let normalized = result.unwrap();
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
normalized.functions.len(),
|
|
|
|
|
|
module.functions.len(),
|
|
|
|
|
|
"Normalized function count should match Structured"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify normalized module has proper phase
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
normalized.phase,
|
|
|
|
|
|
nyash_rust::mir::join_ir::JoinIrPhase::Normalized,
|
|
|
|
|
|
"Normalized module should have Normalized phase"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-A: Test P4 VM execution (basic smoke test)
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase48a_pattern4_continue_minimal_runner() {
|
|
|
|
|
|
let module = build_pattern4_continue_min_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Basic test: module should be runnable through JoinIR runner
|
|
|
|
|
|
// This test verifies the P4 fixture is valid and generates proper JoinIR
|
|
|
|
|
|
assert_eq!(module.functions.len(), 3, "P4 should have 3 functions");
|
|
|
|
|
|
|
|
|
|
|
|
let entry = module.entry.expect("P4 should have entry function");
|
|
|
|
|
|
assert_eq!(entry.0, 0, "Entry should be function 0");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-A: Test P4 minimal Runner dev switch matches Structured
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_continue_minimal_runner_dev_switch_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_pattern4_continue_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
// pattern4_continue_min fixture: acc=4 (skipped i==2, so counted 0,1,3,4)
|
|
|
|
|
|
let input = [JoinValue::Int(5)]; // n = 5
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_runner(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_runner(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "runner mismatch for P4 minimal continue");
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(4),
|
|
|
|
|
|
"unexpected result for P4 minimal continue (expected acc=4, skipped i==2)",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-A: Test P4 minimal VM Bridge direct matches Structured
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_continue_minimal_vm_bridge_direct_matches_structured() {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_pattern4_continue_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
// pattern4_continue_min fixture: acc=4 (skipped i==2)
|
|
|
|
|
|
let input = [JoinValue::Int(5)]; // n = 5
|
|
|
|
|
|
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &input, true);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(base, dev, "vm bridge mismatch for P4 minimal continue");
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
dev,
|
|
|
|
|
|
JoinValue::Int(4),
|
|
|
|
|
|
"unexpected result for P4 minimal continue (expected acc=4)",
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-12 16:40:20 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 48-C: P4 minimal should use canonical normalized route even without env
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_continue_minimal_canonical_matches_structured() {
|
|
|
|
|
|
let structured = build_pattern4_continue_min_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
let input = [JoinValue::Int(5)];
|
|
|
|
|
|
let structured_res = run_joinir_vm_bridge_structured_only(&structured, entry, &input);
|
|
|
|
|
|
let canonical = run_joinir_vm_bridge(&structured, entry, &input, false);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
structured_res, canonical,
|
|
|
|
|
|
"canonical P4 minimal result mismatch"
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(canonical, JoinValue::Int(4));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-B: JsonParser _parse_array continue skip_ws (dev-only) VM Bridge comparison
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_jsonparser_parse_array_continue_skip_ws_vm_bridge_direct_matches_structured(
|
|
|
|
|
|
) {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_jsonparser_parse_array_continue_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
// Fixture mirrors pattern4_continue_min: skip i == 2
|
|
|
|
|
|
let cases = [3, 5, 7];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let args = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &args, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &args, true);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
base, dev,
|
|
|
|
|
|
"vm bridge mismatch for array continue case n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-C: JsonParser _parse_array continue skip_ws canonical route should match Structured
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_jsonparser_parse_array_continue_skip_ws_canonical_matches_structured()
|
|
|
|
|
|
{
|
|
|
|
|
|
let structured = build_jsonparser_parse_array_continue_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
let cases = [3, 5, 7];
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let args = [JoinValue::Int(n)];
|
|
|
|
|
|
let structured_res = run_joinir_vm_bridge_structured_only(&structured, entry, &args);
|
|
|
|
|
|
let canonical = run_joinir_vm_bridge(&structured, entry, &args, false);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
structured_res, canonical,
|
|
|
|
|
|
"canonical array continue mismatch n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-B: JsonParser _parse_object continue skip_ws (dev-only) VM Bridge comparison
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_vm_bridge_direct_matches_structured(
|
|
|
|
|
|
) {
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
let structured = build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
// Fixture mirrors pattern4_continue_min: skip i == 2
|
|
|
|
|
|
let cases = [4, 6, 8];
|
|
|
|
|
|
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let args = [JoinValue::Int(n)];
|
|
|
|
|
|
let base = run_joinir_vm_bridge(&structured, entry, &args, false);
|
|
|
|
|
|
let dev = run_joinir_vm_bridge(&structured, entry, &args, true);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
base, dev,
|
|
|
|
|
|
"vm bridge mismatch for object continue case n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-C: JsonParser _parse_object continue skip_ws canonical route should match Structured
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_normalized_pattern4_jsonparser_parse_object_continue_skip_ws_canonical_matches_structured()
|
|
|
|
|
|
{
|
|
|
|
|
|
let structured = build_jsonparser_parse_object_continue_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
let entry = structured.entry.expect("structured entry required");
|
|
|
|
|
|
|
|
|
|
|
|
let cases = [4, 6, 8];
|
|
|
|
|
|
for n in cases {
|
|
|
|
|
|
let args = [JoinValue::Int(n)];
|
|
|
|
|
|
let structured_res = run_joinir_vm_bridge_structured_only(&structured, entry, &args);
|
|
|
|
|
|
let canonical = run_joinir_vm_bridge(&structured, entry, &args, false);
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
structured_res, canonical,
|
|
|
|
|
|
"canonical object continue mismatch n={}",
|
|
|
|
|
|
n
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 17:12:58 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 54: False positive observation test - P2 structural axis discrimination
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test validates that structural detection can discriminate between
|
|
|
|
|
|
/// canonical P2 and selfhost P2 shapes using structural features alone.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase54_structural_axis_discrimination_p2() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::normalized::shape_guard::{
|
|
|
|
|
|
detect_shapes, is_canonical_shape, NormalizedDevShape,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Canonical P2 shapes
|
|
|
|
|
|
let canonical_p2_shapes = vec![
|
|
|
|
|
|
build_pattern2_minimal_structured(),
|
|
|
|
|
|
build_jsonparser_skip_ws_structured_for_normalized_dev(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Selfhost P2 shapes (Phase 53)
|
|
|
|
|
|
let selfhost_p2_shapes = vec![
|
|
|
|
|
|
build_selfhost_args_parse_p2_structured_for_normalized_dev(),
|
|
|
|
|
|
build_selfhost_token_scan_p2_structured_for_normalized_dev(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Canonical P2 should be detected as canonical, NOT selfhost
|
|
|
|
|
|
for canonical in &canonical_p2_shapes {
|
|
|
|
|
|
let shapes = detect_shapes(canonical);
|
|
|
|
|
|
let has_canonical = shapes.iter().any(|s| is_canonical_shape(s));
|
|
|
|
|
|
let has_selfhost_p2 = shapes.iter().any(|s| matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::SelfhostArgsParseP2
|
|
|
|
|
|
| NormalizedDevShape::SelfhostTokenScanP2
|
|
|
|
|
|
| NormalizedDevShape::SelfhostTokenScanP2Accum
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
assert!(has_canonical, "canonical P2 should be detected as canonical: {:?}", shapes);
|
|
|
|
|
|
assert!(!has_selfhost_p2, "canonical P2 should NOT be detected as selfhost: {:?}", shapes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Selfhost P2 should be detected as selfhost, NOT canonical
|
|
|
|
|
|
for selfhost in &selfhost_p2_shapes {
|
|
|
|
|
|
let shapes = detect_shapes(selfhost);
|
|
|
|
|
|
let has_canonical = shapes.iter().any(|s| is_canonical_shape(s));
|
|
|
|
|
|
let has_selfhost_p2 = shapes.iter().any(|s| matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::SelfhostArgsParseP2
|
|
|
|
|
|
| NormalizedDevShape::SelfhostTokenScanP2
|
|
|
|
|
|
| NormalizedDevShape::SelfhostTokenScanP2Accum
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!has_canonical, "selfhost P2 should NOT be detected as canonical: {:?}", shapes);
|
|
|
|
|
|
assert!(has_selfhost_p2, "selfhost P2 should be detected as selfhost (with name guard): {:?}", shapes);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 54: False positive observation test - P3 structural axis discrimination
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test validates that structural detection can discriminate between
|
|
|
|
|
|
/// canonical P3 and selfhost P3 shapes using structural features alone.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase54_structural_axis_discrimination_p3() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::normalized::shape_guard::{
|
|
|
|
|
|
detect_shapes, is_canonical_shape, NormalizedDevShape,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Canonical P3 shapes
|
|
|
|
|
|
let canonical_p3_shapes = vec![
|
|
|
|
|
|
build_pattern3_if_sum_min_structured_for_normalized_dev(),
|
|
|
|
|
|
build_pattern3_if_sum_multi_min_structured_for_normalized_dev(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Selfhost P3 shapes (Phase 53)
|
|
|
|
|
|
let selfhost_p3_shapes = vec![
|
|
|
|
|
|
build_selfhost_stmt_count_p3_structured_for_normalized_dev(),
|
|
|
|
|
|
build_selfhost_if_sum_p3_structured_for_normalized_dev(),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// Canonical P3 should be detected as canonical, NOT selfhost
|
|
|
|
|
|
for canonical in &canonical_p3_shapes {
|
|
|
|
|
|
let shapes = detect_shapes(canonical);
|
|
|
|
|
|
let has_canonical = shapes.iter().any(|s| is_canonical_shape(s));
|
|
|
|
|
|
let has_selfhost_p3 = shapes.iter().any(|s| matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::SelfhostStmtCountP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3Ext
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
assert!(has_canonical, "canonical P3 should be detected as canonical: {:?}", shapes);
|
|
|
|
|
|
assert!(!has_selfhost_p3, "canonical P3 should NOT be detected as selfhost: {:?}", shapes);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Selfhost P3 should be detected as selfhost, NOT canonical
|
|
|
|
|
|
for selfhost in &selfhost_p3_shapes {
|
|
|
|
|
|
let shapes = detect_shapes(selfhost);
|
|
|
|
|
|
let has_canonical = shapes.iter().any(|s| is_canonical_shape(s));
|
|
|
|
|
|
let has_selfhost_p3 = shapes.iter().any(|s| matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::SelfhostStmtCountP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3Ext
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
assert!(!has_canonical, "selfhost P3 should NOT be detected as canonical: {:?}", shapes);
|
|
|
|
|
|
assert!(has_selfhost_p3, "selfhost P3 should be detected as selfhost (with name guard): {:?}", shapes);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 18:08:00 +09:00
|
|
|
|
|
2025-12-12 22:15:41 +09:00
|
|
|
|
/// Phase 60: Ownership relay threading helpers for P2 (analysis + contract)
|
2025-12-12 18:08:00 +09:00
|
|
|
|
///
|
2025-12-12 22:15:41 +09:00
|
|
|
|
/// plan_to_p2_inputs remains Fail-Fast on relay_writes (legacy contract),
|
|
|
|
|
|
/// while plan_to_p2_inputs_with_relay accepts single-hop relay and promotes them
|
|
|
|
|
|
/// to carriers (dev-only).
|
2025-12-12 18:08:00 +09:00
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
2025-12-12 22:15:41 +09:00
|
|
|
|
fn test_phase60_ownership_p2_with_relay_conversion() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{
|
|
|
|
|
|
plan_to_p2_inputs, plan_to_p2_inputs_with_relay, OwnershipAnalyzer,
|
|
|
|
|
|
};
|
2025-12-12 18:08:00 +09:00
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
|
|
// Create a simple P2 fixture JSON (loop with i and sum)
|
|
|
|
|
|
let json = json!({
|
|
|
|
|
|
"functions": [{
|
|
|
|
|
|
"name": "main",
|
|
|
|
|
|
"params": [],
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Loop",
|
|
|
|
|
|
"condition": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Lt",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 3}
|
|
|
|
|
|
},
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "If",
|
|
|
|
|
|
"condition": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Ge",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 2}
|
|
|
|
|
|
},
|
|
|
|
|
|
"then": {"kind": "Break"}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Assign",
|
|
|
|
|
|
"target": "sum",
|
|
|
|
|
|
"value": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "sum"},
|
|
|
|
|
|
"rhs": {"kind": "Var", "name": "i"}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Assign",
|
|
|
|
|
|
"target": "i",
|
|
|
|
|
|
"value": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 1}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Run ownership analyzer
|
|
|
|
|
|
let mut analyzer = OwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer.analyze_json(&json).expect("analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find loop plan (the one that has relay writes to function-owned sum/i)
|
|
|
|
|
|
let loop_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| !p.relay_writes.is_empty())
|
|
|
|
|
|
.expect("should have a loop plan with relay writes");
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase58/test] Loop plan: relay_writes={:?}",
|
|
|
|
|
|
loop_plan.relay_writes
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-12 22:15:41 +09:00
|
|
|
|
// Legacy Fail-Fast: relay_writes should be rejected
|
2025-12-12 18:08:00 +09:00
|
|
|
|
let result = plan_to_p2_inputs(loop_plan, "i");
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_err(),
|
2025-12-12 22:15:41 +09:00
|
|
|
|
"Legacy contract: relay_writes should be rejected"
|
2025-12-12 18:08:00 +09:00
|
|
|
|
);
|
|
|
|
|
|
let err = result.unwrap_err();
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
err.contains("relay_writes not yet supported"),
|
|
|
|
|
|
"Error should mention relay limitation, got: {}",
|
|
|
|
|
|
err
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-12 22:15:41 +09:00
|
|
|
|
// Phase 60 dev-only: with_relay should accept and include relay vars as carriers
|
|
|
|
|
|
let inputs_with_relay =
|
|
|
|
|
|
plan_to_p2_inputs_with_relay(loop_plan, "i").expect("with_relay should accept");
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
inputs_with_relay.carriers.iter().any(|c| c.name == "sum"),
|
|
|
|
|
|
"relay carrier sum should be promoted"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase60/test] with_relay carriers={:?}",
|
|
|
|
|
|
inputs_with_relay
|
|
|
|
|
|
.carriers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|c| &c.name)
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
);
|
2025-12-12 18:08:00 +09:00
|
|
|
|
|
|
|
|
|
|
// Also test the case where variables ARE owned by loop (future scenario)
|
|
|
|
|
|
// This would work once we support loop-local carriers
|
|
|
|
|
|
let loop_local_json = json!({
|
|
|
|
|
|
"functions": [{
|
|
|
|
|
|
"name": "main",
|
|
|
|
|
|
"params": [],
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Loop",
|
|
|
|
|
|
"condition": {"kind": "Const", "value": true},
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Assign",
|
|
|
|
|
|
"target": "sum",
|
|
|
|
|
|
"value": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "sum"},
|
|
|
|
|
|
"rhs": {"kind": "Var", "name": "i"}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Assign",
|
|
|
|
|
|
"target": "i",
|
|
|
|
|
|
"value": {
|
|
|
|
|
|
"kind": "BinaryOp",
|
|
|
|
|
|
"op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 1}
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{"kind": "Break"}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let mut analyzer2 = OwnershipAnalyzer::new();
|
|
|
|
|
|
let plans2 = analyzer2
|
|
|
|
|
|
.analyze_json(&loop_local_json)
|
|
|
|
|
|
.expect("loop-local analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find loop plan (variables owned by loop, not function)
|
|
|
|
|
|
let loop_plan2 = plans2
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| !p.owned_vars.is_empty())
|
|
|
|
|
|
.expect("should have a loop plan with owned vars");
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase58/test] Loop-local plan: owned_vars={:?}",
|
|
|
|
|
|
loop_plan2
|
|
|
|
|
|
.owned_vars
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|v| &v.name)
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Convert to P2 inputs (should succeed - no relay)
|
|
|
|
|
|
let inputs = plan_to_p2_inputs(loop_plan2, "i").expect("should convert successfully");
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase58/test] P2 inputs: {:?}", inputs);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: i is skipped (loop var), sum becomes carrier
|
|
|
|
|
|
assert_eq!(inputs.carriers.len(), 1, "Should have 1 carrier (sum)");
|
|
|
|
|
|
assert_eq!(inputs.carriers[0].name, "sum");
|
|
|
|
|
|
|
2025-12-12 22:15:41 +09:00
|
|
|
|
eprintln!("[phase60/test] Loop-local conversion verified");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 60: P2 dev-only ownership relay route matches legacy Break lowering.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase60_break_lowering_ownership_matches_legacy() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::frontend::ast_lowerer::lower_break_legacy_for_comparison;
|
|
|
|
|
|
use nyash_rust::mir::join_ir::frontend::ast_lowerer::AstToJoinIrLowerer;
|
|
|
|
|
|
|
|
|
|
|
|
let _ctx = normalized_dev_test_ctx();
|
|
|
|
|
|
|
|
|
|
|
|
let program_json: serde_json::Value = serde_json::from_str(include_str!(
|
|
|
|
|
|
"../docs/private/roadmap2/phases/phase-34-joinir-frontend/fixtures/loop_frontend_break.program.json"
|
|
|
|
|
|
))
|
|
|
|
|
|
.expect("fixture json");
|
|
|
|
|
|
|
|
|
|
|
|
let mut lowerer_new = AstToJoinIrLowerer::new();
|
|
|
|
|
|
let structured_new = lowerer_new.lower_program_json(&program_json);
|
|
|
|
|
|
|
|
|
|
|
|
let mut lowerer_old = AstToJoinIrLowerer::new();
|
|
|
|
|
|
let structured_old =
|
|
|
|
|
|
lower_break_legacy_for_comparison(&mut lowerer_old, &program_json);
|
|
|
|
|
|
|
|
|
|
|
|
let entry_new = structured_new.entry.expect("new entry");
|
|
|
|
|
|
let entry_old = structured_old.entry.expect("old entry");
|
|
|
|
|
|
let input = vec![JoinValue::Int(5)];
|
|
|
|
|
|
|
|
|
|
|
|
let out_old = run_joinir_vm_bridge(&structured_old, entry_old, &input, false);
|
|
|
|
|
|
let out_new = run_joinir_vm_bridge(&structured_new, entry_new, &input, false);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
out_old, out_new,
|
|
|
|
|
|
"ownership relay dev route must match legacy output"
|
|
|
|
|
|
);
|
2025-12-12 18:08:00 +09:00
|
|
|
|
}
|
2025-12-12 18:45:08 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 59: P3 with outer-owned carriers (relay case) should fail-fast
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase59_ownership_p3_relay_failfast() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{plan_to_p3_inputs, OwnershipAnalyzer};
|
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
|
|
// P3 where sum/count are defined OUTSIDE the loop -> relay
|
|
|
|
|
|
let json = json!({
|
|
|
|
|
|
"functions": [{
|
|
|
|
|
|
"name": "main",
|
|
|
|
|
|
"params": [],
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "count", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "Loop",
|
|
|
|
|
|
"condition": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Lt",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 10}
|
|
|
|
|
|
},
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "If",
|
|
|
|
|
|
"condition": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Gt",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 0}
|
|
|
|
|
|
},
|
|
|
|
|
|
"then": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Assign", "target": "sum", "value": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "sum"},
|
|
|
|
|
|
"rhs": {"kind": "Var", "name": "i"}
|
|
|
|
|
|
}},
|
|
|
|
|
|
{"kind": "Assign", "target": "count", "value": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "count"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 1}
|
|
|
|
|
|
}}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{"kind": "Assign", "target": "i", "value": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 1}
|
|
|
|
|
|
}}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let mut analyzer = OwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer.analyze_json(&json).expect("analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find loop plan
|
|
|
|
|
|
let loop_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| !p.relay_writes.is_empty())
|
|
|
|
|
|
.expect("loop should have relay_writes for sum/count");
|
|
|
|
|
|
|
|
|
|
|
|
// Verify relay_writes contains sum and count
|
|
|
|
|
|
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "sum"));
|
|
|
|
|
|
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "count"));
|
|
|
|
|
|
|
|
|
|
|
|
// plan_to_p3_inputs should fail
|
|
|
|
|
|
let result = plan_to_p3_inputs(loop_plan, "i");
|
|
|
|
|
|
assert!(result.is_err(), "Should fail-fast on relay_writes");
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.unwrap_err().contains("relay_writes not yet supported for P3"),
|
|
|
|
|
|
"Error should mention P3 relay limitation"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase59/test] P3 relay fail-fast verified");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 59: P3 with loop-local carriers should succeed
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase59_ownership_p3_loop_local_success() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{plan_to_p3_inputs, OwnershipAnalyzer};
|
|
|
|
|
|
use serde_json::json;
|
|
|
|
|
|
|
|
|
|
|
|
// P3 where sum/count are defined INSIDE the loop -> no relay
|
|
|
|
|
|
let json = json!({
|
|
|
|
|
|
"functions": [{
|
|
|
|
|
|
"name": "main",
|
|
|
|
|
|
"params": [],
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Loop",
|
|
|
|
|
|
"condition": {"kind": "Const", "value": true},
|
|
|
|
|
|
"body": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Local", "name": "i", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "sum", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{"kind": "Local", "name": "count", "init": {"kind": "Const", "value": 0}},
|
|
|
|
|
|
{
|
|
|
|
|
|
"kind": "If",
|
|
|
|
|
|
"condition": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Gt",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "i"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 0}
|
|
|
|
|
|
},
|
|
|
|
|
|
"then": {
|
|
|
|
|
|
"kind": "Block",
|
|
|
|
|
|
"statements": [
|
|
|
|
|
|
{"kind": "Assign", "target": "sum", "value": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "sum"},
|
|
|
|
|
|
"rhs": {"kind": "Var", "name": "i"}
|
|
|
|
|
|
}},
|
|
|
|
|
|
{"kind": "Assign", "target": "count", "value": {
|
|
|
|
|
|
"kind": "BinaryOp", "op": "Add",
|
|
|
|
|
|
"lhs": {"kind": "Var", "name": "count"},
|
|
|
|
|
|
"rhs": {"kind": "Const", "value": 1}
|
|
|
|
|
|
}}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
{"kind": "Break"}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}]
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let mut analyzer = OwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer.analyze_json(&json).expect("analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find loop plan with owned vars
|
|
|
|
|
|
let loop_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| p.owned_vars.iter().any(|v| v.name == "sum"))
|
|
|
|
|
|
.expect("loop should own sum");
|
|
|
|
|
|
|
|
|
|
|
|
// No relay
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
loop_plan.relay_writes.is_empty(),
|
|
|
|
|
|
"No relay for loop-local vars"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// plan_to_p3_inputs should succeed
|
|
|
|
|
|
let inputs = plan_to_p3_inputs(loop_plan, "i").expect("Should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase59/test] P3 inputs: {:?}", inputs);
|
|
|
|
|
|
|
|
|
|
|
|
// sum and count should be carriers
|
|
|
|
|
|
assert!(inputs.carriers.iter().any(|c| c.name == "sum"));
|
|
|
|
|
|
assert!(inputs.carriers.iter().any(|c| c.name == "count"));
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
inputs.carriers.len(),
|
|
|
|
|
|
2,
|
|
|
|
|
|
"Should have 2 carriers (sum and count)"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase59/test] P3 loop-local conversion verified: sum and count correctly extracted as carriers");
|
|
|
|
|
|
}
|
2025-12-12 22:15:41 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 60: Program(JSON v0) fixture (selfhost_if_sum_p3) should produce relay_writes and convert with single-hop relay.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase60_ownership_p3_program_json_fixture_with_relay() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{plan_to_p3_inputs_with_relay, OwnershipAnalyzer};
|
|
|
|
|
|
|
|
|
|
|
|
let program_json: serde_json::Value = serde_json::from_str(include_str!(
|
|
|
|
|
|
"../docs/private/roadmap2/phases/normalized_dev/fixtures/selfhost_if_sum_p3.program.json"
|
|
|
|
|
|
))
|
|
|
|
|
|
.expect("fixture json");
|
|
|
|
|
|
|
|
|
|
|
|
let mut analyzer = OwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer
|
|
|
|
|
|
.analyze_json(&program_json)
|
|
|
|
|
|
.expect("Program(JSON v0) analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
let loop_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| !p.relay_writes.is_empty())
|
|
|
|
|
|
.expect("expected a loop plan with relay_writes");
|
|
|
|
|
|
|
|
|
|
|
|
// i/sum/count are defined outside the loop but updated in the loop body -> relay_writes
|
|
|
|
|
|
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "i"));
|
|
|
|
|
|
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "sum"));
|
|
|
|
|
|
assert!(loop_plan.relay_writes.iter().any(|r| r.name == "count"));
|
|
|
|
|
|
|
|
|
|
|
|
let inputs = plan_to_p3_inputs_with_relay(loop_plan, "i").expect("with_relay should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
let mut carriers: Vec<&str> = inputs.carriers.iter().map(|c| c.name.as_str()).collect();
|
|
|
|
|
|
carriers.sort();
|
|
|
|
|
|
assert_eq!(carriers, vec!["count", "sum"]);
|
|
|
|
|
|
|
|
|
|
|
|
// n is read-only in loop condition -> capture + condition_capture
|
|
|
|
|
|
assert!(inputs.captures.iter().any(|n| n == "n"));
|
|
|
|
|
|
assert!(inputs.condition_captures.iter().any(|n| n == "n"));
|
|
|
|
|
|
}
|
2025-12-12 23:02:40 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 64: P3 production route with ownership analysis (dev-only integration test)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test verifies that `analyze_loop()` API works for simple P3 loops and that
|
|
|
|
|
|
/// multi-hop relay is correctly rejected with Fail-Fast error.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase64_p3_ownership_prod_integration() {
|
|
|
|
|
|
use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::analyze_loop;
|
|
|
|
|
|
|
|
|
|
|
|
// Helper: Create literal integer node
|
|
|
|
|
|
fn lit_i(i: i64) -> ASTNode {
|
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
|
value: LiteralValue::Integer(i),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Helper: Create variable node
|
|
|
|
|
|
fn var(name: &str) -> ASTNode {
|
|
|
|
|
|
ASTNode::Variable {
|
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Simple P3 loop: loop(i < 10) { local sum=0; local i=0; sum = sum + i; i = i + 1 }
|
|
|
|
|
|
let condition = ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Less,
|
|
|
|
|
|
left: Box::new(var("i")),
|
|
|
|
|
|
right: Box::new(lit_i(10)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let body = vec![
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["sum".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["i".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("sum")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("sum")),
|
|
|
|
|
|
right: Box::new(var("i")),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("i")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("i")),
|
|
|
|
|
|
right: Box::new(lit_i(1)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
// No parent-defined variables (both sum and i are loop-local)
|
|
|
|
|
|
let parent_defined = vec![];
|
|
|
|
|
|
|
|
|
|
|
|
// Analyze the loop
|
|
|
|
|
|
let plan = analyze_loop(&condition, &body, &parent_defined)
|
|
|
|
|
|
.expect("P3 analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Verify basic plan structure
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
!plan.owned_vars.is_empty(),
|
|
|
|
|
|
"Should have owned vars (sum, i)"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Find sum and i in owned_vars
|
|
|
|
|
|
let sum_var = plan
|
|
|
|
|
|
.owned_vars
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|v| v.name == "sum")
|
|
|
|
|
|
.expect("sum should be owned");
|
|
|
|
|
|
let i_var = plan
|
|
|
|
|
|
.owned_vars
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|v| v.name == "i")
|
|
|
|
|
|
.expect("i should be owned");
|
|
|
|
|
|
|
|
|
|
|
|
// Both should be written
|
|
|
|
|
|
assert!(sum_var.is_written, "sum should be written");
|
|
|
|
|
|
assert!(i_var.is_written, "i should be written");
|
|
|
|
|
|
|
|
|
|
|
|
// i is used in condition -> condition_only
|
|
|
|
|
|
assert!(i_var.is_condition_only, "i should be condition_only");
|
|
|
|
|
|
|
|
|
|
|
|
// sum is NOT used in condition
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
!sum_var.is_condition_only,
|
|
|
|
|
|
"sum should NOT be condition_only"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// No relay writes (all variables are loop-local)
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
plan.relay_writes.is_empty(),
|
|
|
|
|
|
"No relay writes for loop-local variables"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify single-hop relay constraint: if relay_writes is non-empty, verify single-hop
|
|
|
|
|
|
for relay in &plan.relay_writes {
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
relay.relay_path.len() <= 1,
|
|
|
|
|
|
"Multi-hop relay should be rejected (got relay_path.len = {})",
|
|
|
|
|
|
relay.relay_path.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase64/test] P3 ownership analysis succeeded: {} owned vars, {} relay writes",
|
|
|
|
|
|
plan.owned_vars.len(),
|
|
|
|
|
|
plan.relay_writes.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 64: Multi-hop relay detection test
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Verifies that `analyze_loop()` correctly identifies multi-hop relay patterns.
|
|
|
|
|
|
/// The actual rejection (Fail-Fast) happens in `check_ownership_plan_consistency()`,
|
|
|
|
|
|
/// not in `analyze_loop()` itself.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase64_p3_multihop_relay_detection() {
|
|
|
|
|
|
use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::AstOwnershipAnalyzer;
|
|
|
|
|
|
|
|
|
|
|
|
// Helper functions
|
|
|
|
|
|
fn lit_i(i: i64) -> ASTNode {
|
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
|
value: LiteralValue::Integer(i),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn var(name: &str) -> ASTNode {
|
|
|
|
|
|
ASTNode::Variable {
|
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Function with nested loops:
|
|
|
|
|
|
// function test() {
|
|
|
|
|
|
// local sum = 0;
|
|
|
|
|
|
// local i = 0;
|
|
|
|
|
|
// loop(i < 5) {
|
|
|
|
|
|
// local j = 0;
|
|
|
|
|
|
// loop(j < 3) {
|
|
|
|
|
|
// sum = sum + 1; // Multi-hop relay: sum defined in function scope
|
|
|
|
|
|
// j = j + 1;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// i = i + 1;
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
let inner_condition = ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Less,
|
|
|
|
|
|
left: Box::new(var("j")),
|
|
|
|
|
|
right: Box::new(lit_i(3)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let inner_body = vec![
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("sum")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("sum")),
|
|
|
|
|
|
right: Box::new(lit_i(1)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("j")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("j")),
|
|
|
|
|
|
right: Box::new(lit_i(1)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let outer_condition = ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Less,
|
|
|
|
|
|
left: Box::new(var("i")),
|
|
|
|
|
|
right: Box::new(lit_i(5)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let outer_body = vec![
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["j".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Loop {
|
|
|
|
|
|
condition: Box::new(inner_condition),
|
|
|
|
|
|
body: inner_body,
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("i")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("i")),
|
|
|
|
|
|
right: Box::new(lit_i(1)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let function_body = vec![
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["sum".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["i".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Loop {
|
|
|
|
|
|
condition: Box::new(outer_condition),
|
|
|
|
|
|
body: outer_body,
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
let function = ASTNode::FunctionDeclaration {
|
|
|
|
|
|
name: "test".to_string(),
|
|
|
|
|
|
params: vec![],
|
|
|
|
|
|
body: function_body,
|
|
|
|
|
|
is_static: false,
|
|
|
|
|
|
is_override: false,
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Analyze the entire function to detect nested loop relays
|
|
|
|
|
|
let mut analyzer = AstOwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer
|
|
|
|
|
|
.analyze_ast(&function)
|
|
|
|
|
|
.expect("Function analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find the inner loop plan (should have multi-hop relay for 'sum')
|
|
|
|
|
|
let inner_loop_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| {
|
|
|
|
|
|
// Inner loop should have relay write for 'sum' with relay_path.len() > 1
|
|
|
|
|
|
p.relay_writes
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.any(|r| r.name == "sum" && r.relay_path.len() > 1)
|
|
|
|
|
|
})
|
|
|
|
|
|
.expect("Expected inner loop plan with multi-hop relay for 'sum'");
|
|
|
|
|
|
|
|
|
|
|
|
let sum_relay = inner_loop_plan
|
|
|
|
|
|
.relay_writes
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|r| r.name == "sum")
|
|
|
|
|
|
.expect("sum should be a relay write in inner loop");
|
|
|
|
|
|
|
|
|
|
|
|
// Verify multi-hop relay (relay_path should include both inner and outer loop scopes)
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
sum_relay.relay_path.len() > 1,
|
|
|
|
|
|
"sum should have multi-hop relay (got relay_path.len = {})",
|
|
|
|
|
|
sum_relay.relay_path.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase64/test] Multi-hop relay detected: sum relay_path.len = {}",
|
|
|
|
|
|
sum_relay.relay_path.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
eprintln!("[phase64/test] This pattern would be rejected by check_ownership_plan_consistency()");
|
|
|
|
|
|
}
|
2025-12-13 02:22:29 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 70-A: Verify that multihop relay runtime unsupported error has standardized tag.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test builds an OwnershipPlan with multihop relay and verifies that
|
|
|
|
|
|
/// `check_ownership_plan_consistency()` returns an error with the standard tag
|
|
|
|
|
|
/// `[ownership/relay:runtime_unsupported]`.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase70a_multihop_relay_runtime_unsupported_tag() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{
|
|
|
|
|
|
CapturedVar, OwnershipPlan, RelayVar, ScopeId, ScopeOwnedVar,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Build a plan with multihop relay (relay_path.len() == 2)
|
|
|
|
|
|
let mut plan = OwnershipPlan::new(ScopeId(2)); // Inner loop scope
|
|
|
|
|
|
plan.owned_vars.push(ScopeOwnedVar {
|
|
|
|
|
|
name: "j".to_string(),
|
|
|
|
|
|
is_written: true,
|
|
|
|
|
|
is_condition_only: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
plan.relay_writes.push(RelayVar {
|
|
|
|
|
|
name: "sum".to_string(),
|
|
|
|
|
|
owner_scope: ScopeId(0), // Function scope
|
|
|
|
|
|
relay_path: vec![ScopeId(2), ScopeId(1)], // 2 hops: inner → outer
|
|
|
|
|
|
});
|
|
|
|
|
|
plan.captures.push(CapturedVar {
|
|
|
|
|
|
name: "i".to_string(),
|
|
|
|
|
|
owner_scope: ScopeId(0),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Call the plan layer function (Phase 66 accepts multihop)
|
|
|
|
|
|
// The runtime check (check_ownership_plan_consistency) is private in pattern3_with_if_phi.rs,
|
|
|
|
|
|
// so we test the plan layer acceptance here.
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::plan_to_p3_inputs_with_relay;
|
|
|
|
|
|
|
|
|
|
|
|
let result = plan_to_p3_inputs_with_relay(&plan, "j");
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 66: plan_to_p3_inputs_with_relay NOW ACCEPTS multihop (relay_path.len() > 1)
|
|
|
|
|
|
// So this should PASS (not Err)
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
|
"Phase 66: plan_to_p3_inputs_with_relay should accept multihop relay"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify the relay is in the output
|
|
|
|
|
|
let inputs = result.unwrap();
|
|
|
|
|
|
let relay = inputs
|
|
|
|
|
|
.carriers
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|c| c.name == "sum")
|
|
|
|
|
|
.expect("sum should be in carriers (via relay conversion)");
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[phase70a/test] Multihop relay accepted in plan layer: sum role={:?}",
|
|
|
|
|
|
relay.role
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// The RUNTIME check (check_ownership_plan_consistency in pattern3_with_if_phi.rs)
|
|
|
|
|
|
// is what produces [ownership/relay:runtime_unsupported].
|
|
|
|
|
|
// That function is private, so we document that the tag exists and
|
|
|
|
|
|
// will be hit when P3 lowering encounters this plan at runtime.
|
|
|
|
|
|
eprintln!("[phase70a/test] Runtime would fail with [ownership/relay:runtime_unsupported] tag");
|
|
|
|
|
|
}
|
2025-12-13 03:41:20 +09:00
|
|
|
|
|
|
|
|
|
|
/// Phase 70-B: Test that simple passthrough multihop relay is accepted.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test verifies the structural detection logic in OwnershipPlanValidator
|
|
|
|
|
|
/// correctly identifies supported multihop patterns (pure passthrough, no self-updates).
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase70b_multihop_relay_simple_passthrough_succeeds() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{
|
|
|
|
|
|
OwnershipPlan, OwnershipPlanValidator, RelayVar, ScopeId, ScopeOwnedVar,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Build a plan for innermost loop (L3) with multihop relay
|
|
|
|
|
|
// L3 writes to 'counter' owned by L1, relayed through L2
|
|
|
|
|
|
let mut plan_l3 = OwnershipPlan::new(ScopeId(3)); // Inner loop scope
|
|
|
|
|
|
plan_l3.owned_vars.push(ScopeOwnedVar {
|
|
|
|
|
|
name: "i".to_string(), // loop variable
|
|
|
|
|
|
is_written: true,
|
|
|
|
|
|
is_condition_only: true,
|
|
|
|
|
|
});
|
|
|
|
|
|
plan_l3.relay_writes.push(RelayVar {
|
|
|
|
|
|
name: "counter".to_string(),
|
|
|
|
|
|
owner_scope: ScopeId(1), // L1 owns counter
|
|
|
|
|
|
relay_path: vec![ScopeId(3), ScopeId(2)], // 2 hops: L3 → L2 → L1
|
|
|
|
|
|
});
|
|
|
|
|
|
// No owned_vars for 'counter' - pure passthrough from L3's perspective
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 70-B: This should be accepted (passthrough pattern)
|
|
|
|
|
|
let result = OwnershipPlanValidator::validate_relay_support(&plan_l3);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_ok(),
|
|
|
|
|
|
"Phase 70-B: Simple passthrough multihop should be accepted, got: {:?}",
|
|
|
|
|
|
result
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase70b/test] Simple passthrough multihop relay accepted (3-layer loop)");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 70-B: Test that unsupported multihop patterns are still rejected.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test verifies that complex patterns (e.g., self-conflict where a scope
|
|
|
|
|
|
/// both owns and relays the same variable) are still rejected with the standard tag.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase70b_multihop_relay_self_conflict_rejected() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{
|
|
|
|
|
|
OwnershipPlan, OwnershipPlanValidator, RelayVar, ScopeId, ScopeOwnedVar,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Build a plan where L3 both owns and relays 'counter' (conflict)
|
|
|
|
|
|
// L3 → L2 → L1 (multihop with self-conflict at L3)
|
|
|
|
|
|
let mut plan_l3 = OwnershipPlan::new(ScopeId(3)); // Inner loop scope
|
|
|
|
|
|
plan_l3.owned_vars.push(ScopeOwnedVar {
|
|
|
|
|
|
name: "counter".to_string(), // L3 owns counter
|
|
|
|
|
|
is_written: true,
|
|
|
|
|
|
is_condition_only: false,
|
|
|
|
|
|
});
|
|
|
|
|
|
plan_l3.relay_writes.push(RelayVar {
|
|
|
|
|
|
name: "counter".to_string(), // L3 also relays counter (conflict)
|
|
|
|
|
|
owner_scope: ScopeId(1),
|
|
|
|
|
|
relay_path: vec![ScopeId(3), ScopeId(2)], // L3 → L2 → L1 (multihop)
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 70-B: This should be rejected (self-conflict)
|
|
|
|
|
|
let result = OwnershipPlanValidator::validate_relay_support(&plan_l3);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result.is_err(),
|
|
|
|
|
|
"Phase 70-B: Self-conflict multihop should be rejected"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let err = result.unwrap_err();
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
err.contains("[ownership/relay:runtime_unsupported]"),
|
|
|
|
|
|
"Error should contain standard tag: {}",
|
|
|
|
|
|
err
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase70b/test] Self-conflict multihop relay correctly rejected");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 70-C: Merge Relay (multiple inner loops → same owner)
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test verifies that OwnershipAnalyzer correctly detects the "merge relay"
|
|
|
|
|
|
/// pattern where multiple inner loops update the same owner variable.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Structure:
|
|
|
|
|
|
/// ```text
|
|
|
|
|
|
/// loop L1 {
|
|
|
|
|
|
/// local total = 0 // owned by L1
|
|
|
|
|
|
/// loop L2_A {
|
|
|
|
|
|
/// total++ // L2_A → L1 relay
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// loop L2_B {
|
|
|
|
|
|
/// total += 10 // L2_B → L1 relay
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// }
|
|
|
|
|
|
/// // L1 exit: merge both L2_A and L2_B's updates to 'total'
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase70c_merge_relay_multiple_inner_loops_detected() {
|
|
|
|
|
|
use nyash_rust::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::AstOwnershipAnalyzer;
|
|
|
|
|
|
|
|
|
|
|
|
// Build AST: loop L1 { local total=0; loop L2_A { total++ } loop L2_B { total+=10 } }
|
|
|
|
|
|
fn var(name: &str) -> ASTNode {
|
|
|
|
|
|
ASTNode::Variable {
|
|
|
|
|
|
name: name.to_string(),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
fn lit_i(i: i64) -> ASTNode {
|
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
|
value: LiteralValue::Integer(i),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
fn lit_true() -> ASTNode {
|
|
|
|
|
|
ASTNode::Literal {
|
|
|
|
|
|
value: LiteralValue::Bool(true),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let ast = ASTNode::FunctionDeclaration {
|
|
|
|
|
|
name: "test_merge_relay".to_string(),
|
|
|
|
|
|
params: vec![],
|
|
|
|
|
|
body: vec![
|
|
|
|
|
|
// L1: outer loop
|
|
|
|
|
|
ASTNode::Loop {
|
|
|
|
|
|
condition: Box::new(lit_true()),
|
|
|
|
|
|
body: vec![
|
|
|
|
|
|
// local total = 0 (L1 owns)
|
|
|
|
|
|
ASTNode::Local {
|
|
|
|
|
|
variables: vec!["total".to_string()],
|
|
|
|
|
|
initial_values: vec![Some(Box::new(lit_i(0)))],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
// L2_A: first inner loop (total++)
|
|
|
|
|
|
ASTNode::Loop {
|
|
|
|
|
|
condition: Box::new(lit_true()),
|
|
|
|
|
|
body: vec![
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("total")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("total")),
|
|
|
|
|
|
right: Box::new(lit_i(1)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Break {
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
// L2_B: second inner loop (total += 10)
|
|
|
|
|
|
ASTNode::Loop {
|
|
|
|
|
|
condition: Box::new(lit_true()),
|
|
|
|
|
|
body: vec![
|
|
|
|
|
|
ASTNode::Assignment {
|
|
|
|
|
|
target: Box::new(var("total")),
|
|
|
|
|
|
value: Box::new(ASTNode::BinaryOp {
|
|
|
|
|
|
operator: BinaryOperator::Add,
|
|
|
|
|
|
left: Box::new(var("total")),
|
|
|
|
|
|
right: Box::new(lit_i(10)),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
}),
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Break {
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
ASTNode::Break {
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
},
|
|
|
|
|
|
],
|
|
|
|
|
|
is_static: false,
|
|
|
|
|
|
is_override: false,
|
|
|
|
|
|
span: Span::unknown(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let mut analyzer = AstOwnershipAnalyzer::new();
|
|
|
|
|
|
let plans = analyzer.analyze_ast(&ast).expect("analysis should succeed");
|
|
|
|
|
|
|
|
|
|
|
|
// Find L1 (owner of 'total')
|
|
|
|
|
|
let l1_plan = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|p| p.owned_vars.iter().any(|v| v.name == "total"))
|
|
|
|
|
|
.expect("expected L1 plan with owned total");
|
|
|
|
|
|
let l1_scope_id = l1_plan.scope_id;
|
|
|
|
|
|
|
|
|
|
|
|
// Find L2_A and L2_B (both relay 'total' to L1)
|
|
|
|
|
|
let relay_plans: Vec<_> = plans
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter(|p| {
|
|
|
|
|
|
p.relay_writes.iter().any(|r| r.name == "total")
|
|
|
|
|
|
&& p.scope_id != l1_scope_id
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: two inner loops relay to the same owner
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
relay_plans.len(),
|
|
|
|
|
|
2,
|
|
|
|
|
|
"Phase 70-C: Expected 2 inner loops relaying 'total' to L1, got {}",
|
|
|
|
|
|
relay_plans.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
for (idx, plan) in relay_plans.iter().enumerate() {
|
|
|
|
|
|
let relay = plan
|
|
|
|
|
|
.relay_writes
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|r| r.name == "total")
|
|
|
|
|
|
.expect("expected 'total' in relay_writes");
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: both relay to the same owner (L1)
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
relay.owner_scope, l1_scope_id,
|
|
|
|
|
|
"Phase 70-C: relay {} should have owner_scope = L1",
|
|
|
|
|
|
idx
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: single-hop relay (L2 → L1)
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
relay.relay_path.len(),
|
|
|
|
|
|
1,
|
|
|
|
|
|
"Phase 70-C: relay {} should have single-hop path",
|
|
|
|
|
|
idx
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: relay_path[0] is this scope (L2_A or L2_B)
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
relay.relay_path[0], plan.scope_id,
|
|
|
|
|
|
"Phase 70-C: relay {} relay_path[0] must be this scope",
|
|
|
|
|
|
idx
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Verify: L1 owned_vars contains 'total' and is_written=true
|
|
|
|
|
|
let total_var = l1_plan
|
|
|
|
|
|
.owned_vars
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.find(|v| v.name == "total")
|
|
|
|
|
|
.expect("expected 'total' in L1 owned_vars");
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
total_var.is_written,
|
|
|
|
|
|
"Phase 70-C: L1's 'total' should be is_written=true"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase70c/test] Merge relay pattern detected: 2 inner loops → same owner variable");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 70-C: Merge Relay validation acceptance
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This test verifies that OwnershipPlanValidator ACCEPTS merge relay patterns
|
|
|
|
|
|
/// (multiple inner loops → same owner) because they are PERMITTED with owner merge.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn test_phase70c_merge_relay_same_owner_accepted() {
|
|
|
|
|
|
use nyash_rust::mir::join_ir::ownership::{
|
|
|
|
|
|
OwnershipPlan, OwnershipPlanValidator, RelayVar, ScopeId,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Build two plans for L2_A and L2_B, both relaying to L1
|
|
|
|
|
|
// L2_A plan: relay 'total' to L1
|
|
|
|
|
|
let mut plan_l2a = OwnershipPlan::new(ScopeId(2));
|
|
|
|
|
|
plan_l2a.relay_writes.push(RelayVar {
|
|
|
|
|
|
name: "total".to_string(),
|
|
|
|
|
|
owner_scope: ScopeId(1), // L1 owns total
|
|
|
|
|
|
relay_path: vec![ScopeId(2)], // Single hop: L2_A → L1
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// L2_B plan: relay 'total' to L1
|
|
|
|
|
|
let mut plan_l2b = OwnershipPlan::new(ScopeId(3));
|
|
|
|
|
|
plan_l2b.relay_writes.push(RelayVar {
|
|
|
|
|
|
name: "total".to_string(),
|
|
|
|
|
|
owner_scope: ScopeId(1), // L1 owns total (same owner)
|
|
|
|
|
|
relay_path: vec![ScopeId(3)], // Single hop: L2_B → L1
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Phase 70-C: Both should be accepted (single-hop relay to same owner)
|
|
|
|
|
|
let result_a = OwnershipPlanValidator::validate_relay_support(&plan_l2a);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result_a.is_ok(),
|
|
|
|
|
|
"Phase 70-C: L2_A relay to L1 should be accepted, got: {:?}",
|
|
|
|
|
|
result_a
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let result_b = OwnershipPlanValidator::validate_relay_support(&plan_l2b);
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
result_b.is_ok(),
|
|
|
|
|
|
"Phase 70-C: L2_B relay to L1 should be accepted, got: {:?}",
|
|
|
|
|
|
result_b
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
eprintln!("[phase70c/test] Merge relay validation accepted: multiple inner loops → same owner");
|
|
|
|
|
|
}
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
/// Phase 80-D (P3): Test Pattern3 BindingId lookup works
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
///
|
2025-12-13 18:05:14 +09:00
|
|
|
|
/// Verifies that Pattern3 (if-sum) BindingId registration is operational.
|
|
|
|
|
|
/// For manual fallback detection, run with: NYASH_JOINIR_DEBUG=1
|
|
|
|
|
|
/// Expected logs: [phase80/p3] Registered ... + [binding_pilot/hit]
|
|
|
|
|
|
/// No [binding_pilot/fallback] should appear.
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
#[test]
|
2025-12-13 18:05:14 +09:00
|
|
|
|
fn test_phase80_p3_bindingid_lookup_works() {
|
|
|
|
|
|
let module = build_pattern3_if_sum_min_structured_for_normalized_dev();
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
// Basic test: Pattern3 should compile and run with BindingId registration
|
|
|
|
|
|
assert_eq!(module.functions.len(), 3, "P3 should have 3 functions");
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
let entry = module.entry.expect("P3 should have entry function");
|
|
|
|
|
|
assert_eq!(entry.0, 0, "Entry should be function 0");
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
// The fact that this compiles and runs means BindingId registration didn't break anything
|
|
|
|
|
|
// Manual verification: NYASH_JOINIR_DEBUG=1 cargo test test_phase80_p3_bindingid_lookup_works
|
|
|
|
|
|
// Should show [phase80/p3] logs and [binding_pilot/hit], NO [binding_pilot/fallback]
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
/// Phase 80-D (P3): Test Pattern4 BindingId lookup works
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
///
|
2025-12-13 18:05:14 +09:00
|
|
|
|
/// Verifies that Pattern4 (continue/Trim) BindingId registration is operational.
|
|
|
|
|
|
/// For manual fallback detection, run with: NYASH_JOINIR_DEBUG=1
|
|
|
|
|
|
/// Expected logs: [phase80/p4] Registered ... + [binding_pilot/hit]
|
|
|
|
|
|
/// No [binding_pilot/fallback] should appear.
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
#[test]
|
2025-12-13 18:05:14 +09:00
|
|
|
|
fn test_phase80_p4_bindingid_lookup_works() {
|
|
|
|
|
|
let module = build_pattern4_continue_min_structured_for_normalized_dev();
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
// Basic test: Pattern4 should compile and run with BindingId registration
|
|
|
|
|
|
assert_eq!(module.functions.len(), 3, "P4 should have 3 functions");
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
let entry = module.entry.expect("P4 should have entry function");
|
|
|
|
|
|
assert_eq!(entry.0, 0, "Entry should be function 0");
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
|
2025-12-13 18:05:14 +09:00
|
|
|
|
// The fact that this compiles and runs means BindingId registration didn't break anything
|
|
|
|
|
|
// Manual verification: NYASH_JOINIR_DEBUG=1 cargo test test_phase80_p4_bindingid_lookup_works
|
|
|
|
|
|
// Should show [phase80/p4] logs and [binding_pilot/hit], NO [binding_pilot/fallback]
|
feat(joinir): Phase 79 Activation - BindingId wiring operational (dev-only)
Phase 79 の最終段階:BindingId を ExprLowerer に配線し、end-to-end で動作確認。
Key changes:
- ExprLowerer wiring:
- scope_resolution.rs: build_condition_env_from_scope_with_binding() (BindingId-aware)
- expr_lowerer.rs: lower_condition() uses BindingId priority lookup
- ConditionEnv extension:
- register_loop_var_binding(): Loop var BindingId→ValueId registration
- register_condition_binding(): Condition-only carrier BindingId→ValueId registration
- Pattern2 integration:
- pattern2_with_break.rs: Loop var registration (Line 116-128)
- pattern2_with_break.rs: Carrier role-based registration (Line 381-425)
- Debug logging: [phase79] tags for registration, [binding_pilot/hit] for lookup success
- E2E tests (normalized_joinir_min.rs, debug-only):
- test_phase79_digitpos_bindingid_lookup_works(): DigitPos pattern verification
- test_phase79_trim_bindingid_lookup_works(): Trim pattern verification
Result: BindingId lookup actually works end-to-end
- Promoted carriers resolve via BindingId, not string hacks
- Debug: NYASH_JOINIR_DEBUG=1 shows [phase79] Registered loop var BindingId, [binding_pilot/hit] BindingId(1) -> ValueId(100)
Tests: 1025/1025 lib PASS
Design: dev-only feature-gated, dual-path maintained (BindingId priority + name fallback)
Phase 74-79 complete: BindingId migration fully operational
- Infrastructure (74-76): BindingId type, binding_map, 3-tier lookup, promoted_bindings map
- Implementation (77): DigitPos/Trim populate, legacy deprecate
- Refactoring (78-79 Refactoring): PromotedBindingRecorder, Detector/Recorder split
- Activation (80 Foundation + 79 Activation): CarrierBindingAssigner + ExprLowerer wiring
2025-12-13 16:48:41 +09:00
|
|
|
|
}
|
2025-12-13 18:05:14 +09:00
|
|
|
|
|
|
|
|
|
|
// Phase 79 の "BindingId lookup の E2E(subprocess)" は、Pattern2(DigitPos/Trim)の安定化と一緒に
|
|
|
|
|
|
// Phase 80-D 以降で復活させる(このファイルは Normalized JoinIR の SSOT テストに集中させる)。
|
2025-12-13 18:31:02 +09:00
|
|
|
|
|
|
|
|
|
|
// ========== Phase 81: Pattern2 ExitLine Contract Verification ==========
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 81-B: DigitPos pattern ExitLine contract verification
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Tests that promoted `is_digit_pos` carrier (ConditionOnly) is correctly
|
|
|
|
|
|
/// excluded from Exit PHI while LoopState carriers (result, i) are included.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase81_digitpos_exitline_contract() {
|
|
|
|
|
|
// Use existing JsonParser _atoi fixture (DigitPos pattern with indexOf)
|
|
|
|
|
|
let module = build_jsonparser_atoi_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Verify compilation succeeds (ExitLine reconnection works)
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
!module.functions.is_empty(),
|
|
|
|
|
|
"DigitPos pattern should compile successfully"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify module structure
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
module.functions.len(),
|
|
|
|
|
|
3,
|
|
|
|
|
|
"DigitPos pattern should have 3 functions (k_entry, k_loop, k_body)"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify entry function exists
|
|
|
|
|
|
let entry = module
|
|
|
|
|
|
.entry
|
|
|
|
|
|
.expect("DigitPos pattern should have entry function");
|
|
|
|
|
|
|
|
|
|
|
|
// Execute to verify correctness (DigitPos: "123" → 123)
|
|
|
|
|
|
let result = run_joinir_vm_bridge_structured_only(
|
|
|
|
|
|
&module,
|
|
|
|
|
|
entry,
|
|
|
|
|
|
&[
|
|
|
|
|
|
JoinValue::Str("123".to_string()),
|
|
|
|
|
|
JoinValue::Int(3),
|
|
|
|
|
|
],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
result,
|
|
|
|
|
|
JoinValue::Int(123),
|
|
|
|
|
|
"DigitPos pattern should parse '123' correctly"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Manual verification: Check that is_digit_pos is NOT in exit_bindings
|
|
|
|
|
|
// NYASH_JOINIR_DEBUG=1 cargo test test_phase81_digitpos_exitline_contract -- --nocapture 2>&1 | grep exit-line
|
|
|
|
|
|
// Should show: [joinir/exit-line] skip ConditionOnly carrier 'is_digit_pos'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 81-B: Trim pattern ExitLine contract verification
|
|
|
|
|
|
///
|
|
|
|
|
|
/// Tests that promoted `is_ch_match` carrier (ConditionOnly) is correctly
|
|
|
|
|
|
/// excluded from Exit PHI while LoopState carriers (i) are included.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_phase81_trim_exitline_contract() {
|
|
|
|
|
|
// Use existing JsonParser skip_ws fixture (Trim pattern with whitespace check)
|
|
|
|
|
|
let module = build_jsonparser_skip_ws_structured_for_normalized_dev();
|
|
|
|
|
|
|
|
|
|
|
|
// Verify compilation succeeds (ExitLine reconnection works)
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
!module.functions.is_empty(),
|
|
|
|
|
|
"Trim pattern should compile successfully"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify module structure
|
|
|
|
|
|
assert!(
|
|
|
|
|
|
module.functions.len() >= 3,
|
|
|
|
|
|
"Trim pattern should have at least 3 functions"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Verify entry function exists
|
|
|
|
|
|
let entry = module
|
|
|
|
|
|
.entry
|
|
|
|
|
|
.expect("Trim pattern should have entry function");
|
|
|
|
|
|
|
|
|
|
|
|
// Execute to verify correctness (skip_ws fixture)
|
|
|
|
|
|
// The skip_ws fixture takes a single int parameter (test size)
|
|
|
|
|
|
let result = run_joinir_vm_bridge_structured_only(
|
|
|
|
|
|
&module,
|
|
|
|
|
|
entry,
|
|
|
|
|
|
&[JoinValue::Int(5)],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// skip_ws fixture returns the input value (identity function for testing)
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
result,
|
|
|
|
|
|
JoinValue::Int(5),
|
|
|
|
|
|
"Trim pattern fixture should return input value"
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Manual verification: Check that is_ch_match is NOT in exit_bindings
|
|
|
|
|
|
// NYASH_JOINIR_DEBUG=1 cargo test test_phase81_trim_exitline_contract -- --nocapture 2>&1 | grep exit-line
|
|
|
|
|
|
// Should show: [joinir/exit-line] skip ConditionOnly carrier 'is_ch_match'
|
|
|
|
|
|
}
|