diff --git a/tests/normalized_joinir_min/shapes.rs b/tests/normalized_joinir_min/shapes.rs index 737f5eca..cbb84a7b 100644 --- a/tests/normalized_joinir_min/shapes.rs +++ b/tests/normalized_joinir_min/shapes.rs @@ -313,6 +313,65 @@ fn test_phase88_jsonparser_unescape_string_step2_min_canonical_matches_structure assert_eq!(canonical, JoinValue::Int(5)); } +/// Phase 88: Continue 側の `i` 更新は `i = i + const` のみに限定して Fail-Fast する。 +#[test] +fn test_phase88_jsonparser_unescape_string_step2_min_rejects_non_const_then_i_update() { + use std::any::Any; + + fn panic_message(payload: Box) -> String { + if let Some(s) = payload.downcast_ref::<&str>() { + return (*s).to_string(); + } + if let Some(s) = payload.downcast_ref::() { + return s.clone(); + } + "".to_string() + } + + // then 側の `i = i + n`(const ではない)を入れて、Fail-Fast を確認する。 + let program_json = serde_json::json!({ + "version": 0, + "kind": "Program", + "defs": [{ + "type": "FunctionDef", + "name": "jsonparser_unescape_string_step2_min", + "params": ["n"], + "body": { "type": "Block", "body": [ + { "type": "Local", "name": "i", "expr": { "type": "Int", "value": 0 } }, + { "type": "Local", "name": "acc", "expr": { "type": "Int", "value": 0 } }, + { "type": "Loop", + "cond": { "type": "Compare", "op": "<", "lhs": { "type": "Var", "name": "i" }, "rhs": { "type": "Var", "name": "n" } }, + "body": [ + { "type": "If", + "cond": { "type": "Compare", "op": "<", "lhs": { "type": "Var", "name": "i" }, "rhs": { "type": "Var", "name": "n" } }, + "then": [ + { "type": "Local", "name": "i", "expr": { "type": "Binary", "op": "+", "lhs": { "type": "Var", "name": "i" }, "rhs": { "type": "Var", "name": "n" } } }, + { "type": "Continue" } + ], + "else": [] + }, + { "type": "Local", "name": "acc", "expr": { "type": "Binary", "op": "+", "lhs": { "type": "Var", "name": "acc" }, "rhs": { "type": "Int", "value": 0 } } }, + { "type": "Local", "name": "i", "expr": { "type": "Binary", "op": "+", "lhs": { "type": "Var", "name": "i" }, "rhs": { "type": "Int", "value": 1 } } } + ] + }, + { "type": "Return", "expr": { "type": "Var", "name": "acc" } } + ]} + }] + }); + + let res = std::panic::catch_unwind(|| { + let mut lowerer = nyash_rust::mir::join_ir::frontend::AstToJoinIrLowerer::new(); + lowerer.lower_program_json(&program_json); + }); + assert!(res.is_err(), "expected fail-fast panic"); + let msg = panic_message(res.err().unwrap()); + assert!( + msg.contains("then i update of form (i + const)"), + "unexpected panic message: {}", + msg + ); +} + /// 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()