From dd7f923b8820840e3633caf8a9e22cf6df32a928 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Tue, 30 Dec 2025 15:28:40 +0900 Subject: [PATCH] phase29ao(p32): strict adopt pattern2 realworld from facts --- docs/development/current/main/10-Now.md | 4 +- docs/development/current/main/30-Backlog.md | 2 +- .../design/coreplan-migration-roadmap-ssot.md | 2 +- .../main/phases/phase-29ao/10-Now-archive.md | 9 +- .../phases/phase-29ao/30-Backlog-archive.md | 4 +- .../phases/phase-29ao/CURRENT_TASK-archive.md | 11 +- .../current/main/phases/phase-29ao/README.md | 7 +- .../plan/facts/pattern2_break_facts.rs | 560 +++++++++++++++++- .../phase263_pattern2_seg_realworld_min_vm.sh | 5 + 9 files changed, 587 insertions(+), 17 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 5940acbe..58d1996c 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,8 +3,8 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next: Phase 29ao P32(Pattern2 real-world strict/dev shadow adopt) - - 指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` +- Next: Phase 29ao P33(TBD) + - 指示書: TBD ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 83535dd4..bfa6669c 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: P32) +- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: P33) ## Near-Term Candidates diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index 31de8b6b..40b2ffec 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next step: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` +- Next step: Phase 29ao P33(TBD) - After P32: TBD ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ao/10-Now-archive.md b/docs/development/current/main/phases/phase-29ao/10-Now-archive.md index 5f231f42..173421e8 100644 --- a/docs/development/current/main/phases/phase-29ao/10-Now-archive.md +++ b/docs/development/current/main/phases/phase-29ao/10-Now-archive.md @@ -2,12 +2,17 @@ ## Current Focus: Phase 29ao(CorePlan composition) -Next: Phase 29ao P32(Pattern2 real-world strict/dev shadow adopt) -指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` +Next: Phase 29ao P33(TBD) +指示書: TBD 運用ルール: integration filter で phase143_* は回さない(JoinIR 回帰は phase29ae pack のみ) 運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う 移行道筋 SSOT: `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` +**2025-12-30: Phase 29ao P32 完了** ✅ +- 目的: Pattern2 real-world を planner subset に引き上げ、strict/dev で Facts→CorePlan shadow adopt を踏ませる(仕様不変) +- 変更: `src/mir/builder/control_flow/plan/facts/pattern2_break_facts.rs` / `tools/smokes/v2/profiles/integration/apps/phase263_pattern2_seg_realworld_min_vm.sh` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md` / `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + **2025-12-30: Phase 29ao P31 完了** ✅ - 目的: shadow adopt の判定/Fail-Fast/タグを composer 側に集約し、router を薄くする(仕様不変) - 変更: `src/mir/builder/control_flow/plan/composer/shadow_adopt.rs` / `src/mir/builder/control_flow/plan/composer/mod.rs` / `src/mir/builder/control_flow/joinir/patterns/router.rs` / `docs/development/current/main/phases/phase-29ao/README.md` / `docs/development/current/main/10-Now.md` / `docs/development/current/main/30-Backlog.md` / `CURRENT_TASK.md` / `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` diff --git a/docs/development/current/main/phases/phase-29ao/30-Backlog-archive.md b/docs/development/current/main/phases/phase-29ao/30-Backlog-archive.md index ccd2d192..446bbdf0 100644 --- a/docs/development/current/main/phases/phase-29ao/30-Backlog-archive.md +++ b/docs/development/current/main/phases/phase-29ao/30-Backlog-archive.md @@ -15,8 +15,8 @@ Related: - **Phase 29ao(active): CorePlan composition from Skeleton/Feature** - 入口: `docs/development/current/main/phases/phase-29ao/README.md` - - 状況: P0–P31 ✅ 完了 / Next: P32(Pattern2 real-world strict/dev shadow adopt) - - Next 指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` + - 状況: P0–P32 ✅ 完了 / Next: P33(TBD) + - Next 指示書: TBD - **Phase 29af(✅ COMPLETE): Boundary hygiene / regression entrypoint / carrier layout SSOT** - 入口: `docs/development/current/main/phases/phase-29af/README.md` diff --git a/docs/development/current/main/phases/phase-29ao/CURRENT_TASK-archive.md b/docs/development/current/main/phases/phase-29ao/CURRENT_TASK-archive.md index 75c1645d..9830e112 100644 --- a/docs/development/current/main/phases/phase-29ao/CURRENT_TASK-archive.md +++ b/docs/development/current/main/phases/phase-29ao/CURRENT_TASK-archive.md @@ -18,10 +18,13 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu **CorePlan migration 道筋 SSOT** `docs/development/current/main/design/coreplan-migration-roadmap-ssot.md` が移行タスクの Done 判定の入口。 -**Next implementation (Phase 29ao P32)** -- 目的: Pattern2 real-world strict/dev shadow adopt -- 指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` -- After P32: TBD +**Next implementation (Phase 29ao P33)** +- 目的: TBD +- 指示書: TBD +- After P33: TBD + +**2025-12-30: Phase 29ao P32 COMPLETE (Pattern2 real-world strict/dev shadow adopt)** +Pattern2 real-world を planner subset に引き上げ、strict/dev で Facts→CorePlan shadow adopt を踏ませた(仕様不変)。 **2025-12-30: Phase 29ao P31 COMPLETE (shadow adopt routing SSOT)** shadow adopt の判定/Fail-Fast/タグを composer 側に集約し、router を薄くした(仕様不変)。 diff --git a/docs/development/current/main/phases/phase-29ao/README.md b/docs/development/current/main/phases/phase-29ao/README.md index db5390bf..d2b4ae0e 100644 --- a/docs/development/current/main/phases/phase-29ao/README.md +++ b/docs/development/current/main/phases/phase-29ao/README.md @@ -181,13 +181,12 @@ Gate(SSOT): - 指示書: `docs/development/current/main/phases/phase-29ao/P31-REFactor-SHADOW-ADOPT-ROUTER-TO-COMPOSER-SSOT-INSTRUCTIONS.md` -## P32: Pattern2 real-world strict/dev shadow adopt(phase263 をタグ必須で固定) +## P32: Pattern2 real-world strict/dev shadow adopt(phase263 をタグ必須で固定)✅ - 指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` - ねらい: `phase263_pattern2_*` が strict/dev で Facts→CorePlan shadow adopt を踏むことを “タグ必須” で固定し、CorePlan 完全移行の回帰穴を塞ぐ(仕様不変) ## Next(planned) -- Next: P32(Pattern2 real-world strict/dev shadow adopt) - - 指示書: `docs/development/current/main/phases/phase-29ao/P32-STRICT-ADOPT-PATTERN2-REALWORLD-FROM-FACTS-INSTRUCTIONS.md` -- After P32: TBD +- Next: P33(TBD) +- After P33: TBD diff --git a/src/mir/builder/control_flow/plan/facts/pattern2_break_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern2_break_facts.rs index f077a35b..4f94e536 100644 --- a/src/mir/builder/control_flow/plan/facts/pattern2_break_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/pattern2_break_facts.rs @@ -1,10 +1,11 @@ //! Phase 29ai P11: Pattern2BreakFacts (Facts SSOT) -use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use crate::mir::builder::control_flow::plan::planner::Freeze; use crate::mir::builder::control_flow::plan::extractors::common_helpers::{ count_control_flow, has_continue_statement as common_has_continue, has_return_statement as common_has_return, ControlFlowDetector, extract_loop_increment_plan, + is_true_literal, }; #[derive(Debug, Clone)] @@ -22,6 +23,10 @@ pub(in crate::mir::builder) fn try_extract_pattern2_break_facts( condition: &ASTNode, body: &[ASTNode], ) -> Result, Freeze> { + if let Some(realworld) = try_extract_pattern2_break_realworld_subset(condition, body) { + return Ok(Some(realworld)); + } + let Some(loop_var) = extract_loop_var_for_plan_subset(condition) else { return Ok(None); }; @@ -74,6 +79,79 @@ pub(in crate::mir::builder) fn try_extract_pattern2_break_facts( })) } +fn try_extract_pattern2_break_realworld_subset( + condition: &ASTNode, + body: &[ASTNode], +) -> Option { + if !is_true_literal(condition) { + return None; + } + + let counts = count_control_flow(body, ControlFlowDetector::default()); + if counts.break_count != 1 || counts.continue_count > 0 || counts.return_count > 0 { + return None; + } + + if body.len() != 5 { + return None; + } + + let (j_var, haystack_var, sep_lit, loop_var) = + match_indexof_local(&body[0])?; + let seg_var = match_local_empty_string(&body[1])?; + + if !match_seg_if_else( + &body[2], + &j_var, + &seg_var, + &haystack_var, + &loop_var, + )? { + return None; + } + + if !match_break_if(&body[3], &seg_var)? { + return None; + } + + let sep_len = sep_lit.len() as i64; + if !match_loop_increment(&body[4], &loop_var, &j_var, sep_len)? { + return None; + } + + let loop_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(var(&loop_var)), + right: Box::new(length_call(&haystack_var)), + span: Span::unknown(), + }; + + let index_expr = index_of_call(&haystack_var, &sep_lit, &loop_var); + let break_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(substring_call(&haystack_var, var(&loop_var), index_expr.clone())), + right: Box::new(lit_str("")), + span: Span::unknown(), + }; + + let loop_increment = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(index_expr), + right: Box::new(lit_int(sep_len)), + span: Span::unknown(), + }; + + Some(Pattern2BreakFacts { + loop_var: loop_var.clone(), + carrier_var: loop_var, + loop_condition, + break_condition, + carrier_update_in_break: None, + carrier_update_in_body: loop_increment.clone(), + loop_increment, + }) +} + fn extract_loop_var_for_plan_subset(condition: &ASTNode) -> Option { let ASTNode::BinaryOp { operator: BinaryOperator::Less, @@ -136,6 +214,338 @@ fn extract_break_if_parts(stmt: &ASTNode) -> Option<(ASTNode, Option)> Some((condition.as_ref().clone(), carrier_update_in_break)) } +fn match_indexof_local(stmt: &ASTNode) -> Option<(String, String, String, String)> { + let ASTNode::Local { + variables, + initial_values, + .. + } = stmt + else { + return None; + }; + if variables.len() != 1 || initial_values.len() != 1 { + return None; + } + let j_var = variables[0].clone(); + let Some(expr) = initial_values[0].as_ref() else { + return None; + }; + let ASTNode::MethodCall { + object, + method, + arguments, + .. + } = expr.as_ref() + else { + return None; + }; + if method != "indexOf" || arguments.len() != 2 { + return None; + } + let ASTNode::Variable { name: haystack_var, .. } = object.as_ref() else { + return None; + }; + let ASTNode::Literal { + value: LiteralValue::String(sep_lit), + .. + } = &arguments[0] + else { + return None; + }; + let ASTNode::Variable { name: loop_var, .. } = &arguments[1] else { + return None; + }; + + Some(( + j_var, + haystack_var.clone(), + sep_lit.clone(), + loop_var.clone(), + )) +} + +fn match_local_empty_string(stmt: &ASTNode) -> Option { + let ASTNode::Local { + variables, + initial_values, + .. + } = stmt + else { + return None; + }; + if variables.len() != 1 || initial_values.len() != 1 { + return None; + } + let seg_var = variables[0].clone(); + let Some(expr) = initial_values[0].as_ref() else { + return None; + }; + let ASTNode::Literal { + value: LiteralValue::String(value), + .. + } = expr.as_ref() + else { + return None; + }; + if value != "" { + return None; + } + Some(seg_var) +} + +fn match_seg_if_else( + stmt: &ASTNode, + j_var: &str, + seg_var: &str, + haystack_var: &str, + loop_var: &str, +) -> Option { + let ASTNode::If { + condition, + then_body, + else_body, + .. + } = stmt + else { + return None; + }; + let else_body = else_body.as_ref()?; + if then_body.len() != 1 || else_body.len() != 1 { + return None; + } + if !matches_ge_zero(condition.as_ref(), j_var) { + return None; + } + + let then_expr = extract_substring_assignment(&then_body[0], seg_var, haystack_var)?; + let else_expr = extract_substring_assignment(&else_body[0], seg_var, haystack_var)?; + + if !matches_substring_args(&then_expr, loop_var, Some(j_var), None) { + return None; + } + if !matches_substring_args(&else_expr, loop_var, None, Some(haystack_var)) { + return None; + } + + Some(true) +} + +fn extract_substring_assignment( + stmt: &ASTNode, + seg_var: &str, + haystack_var: &str, +) -> Option { + let ASTNode::Assignment { target, value, .. } = stmt else { + return None; + }; + let ASTNode::Variable { name, .. } = target.as_ref() else { + return None; + }; + if name != seg_var { + return None; + } + let ASTNode::MethodCall { + object, + method, + arguments, + .. + } = value.as_ref() + else { + return None; + }; + if method != "substring" || arguments.len() != 2 { + return None; + } + let ASTNode::Variable { name: obj_name, .. } = object.as_ref() else { + return None; + }; + if obj_name != haystack_var { + return None; + } + Some(value.as_ref().clone()) +} + +fn matches_substring_args( + expr: &ASTNode, + loop_var: &str, + end_var: Option<&str>, + end_length_of: Option<&str>, +) -> bool { + let ASTNode::MethodCall { arguments, .. } = expr else { + return false; + }; + if arguments.len() != 2 { + return false; + } + let ASTNode::Variable { name: start_var, .. } = &arguments[0] else { + return false; + }; + if start_var != loop_var { + return false; + } + + match (&arguments[1], end_var, end_length_of) { + (ASTNode::Variable { name, .. }, Some(var), None) => name == var, + (ASTNode::MethodCall { object, method, arguments, .. }, None, Some(owner)) => { + if method != "length" || !arguments.is_empty() { + return false; + } + matches!(object.as_ref(), ASTNode::Variable { name, .. } if name == owner) + } + _ => false, + } +} + +fn match_break_if(stmt: &ASTNode, seg_var: &str) -> Option { + let ASTNode::If { + condition, + then_body, + else_body, + .. + } = stmt + else { + return None; + }; + if else_body.is_some() { + return None; + } + if then_body.len() != 1 || !matches!(then_body[0], ASTNode::Break { .. }) { + return None; + } + if !matches_eq_empty_string(condition.as_ref(), seg_var) { + return None; + } + Some(true) +} + +fn match_loop_increment( + stmt: &ASTNode, + loop_var: &str, + j_var: &str, + sep_len: i64, +) -> Option { + let ASTNode::Assignment { target, value, .. } = stmt else { + return None; + }; + let ASTNode::Variable { name, .. } = target.as_ref() else { + return None; + }; + if name != loop_var { + return None; + } + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + right, + .. + } = value.as_ref() + else { + return None; + }; + if !matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == j_var) { + return None; + } + if !matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(v), .. } if *v == sep_len) { + return None; + } + Some(true) +} + +fn matches_eq_empty_string(node: &ASTNode, var_name: &str) -> bool { + let ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left, + right, + .. + } = node + else { + return false; + }; + matches_eq_empty_string_sides(left.as_ref(), right.as_ref(), var_name) + || matches_eq_empty_string_sides(right.as_ref(), left.as_ref(), var_name) +} + +fn matches_eq_empty_string_sides(var_node: &ASTNode, lit_node: &ASTNode, var_name: &str) -> bool { + if !matches!(var_node, ASTNode::Variable { name, .. } if name == var_name) { + return false; + } + matches!( + lit_node, + ASTNode::Literal { + value: LiteralValue::String(value), + .. + } if value.is_empty() + ) +} + +fn matches_ge_zero(node: &ASTNode, var_name: &str) -> bool { + let ASTNode::BinaryOp { + operator: BinaryOperator::GreaterEqual, + left, + right, + .. + } = node + else { + return false; + }; + matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == var_name) + && matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(0), + .. + } + ) +} + +fn var(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } +} + +fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } +} + +fn lit_str(value: &str) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::String(value.to_string()), + span: Span::unknown(), + } +} + +fn length_call(obj: &str) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(var(obj)), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + } +} + +fn index_of_call(haystack: &str, sep: &str, loop_var: &str) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(var(haystack)), + method: "indexOf".to_string(), + arguments: vec![lit_str(sep), var(loop_var)], + span: Span::unknown(), + } +} + +fn substring_call(haystack: &str, start: ASTNode, end: ASTNode) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(var(haystack)), + method: "substring".to_string(), + arguments: vec![start, end], + span: Span::unknown(), + } +} + fn has_continue_statement(body: &[ASTNode]) -> bool { common_has_continue(body) } @@ -143,3 +553,151 @@ fn has_continue_statement(body: &[ASTNode]) -> bool { fn has_return_statement(body: &[ASTNode]) -> bool { common_has_return(body) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::Span; + + fn v(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } + } + + fn lit_bool(value: bool) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Bool(value), + span: Span::unknown(), + } + } + + fn lit_str(value: &str) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::String(value.to_string()), + span: Span::unknown(), + } + } + + fn local(name: &str, value: ASTNode) -> ASTNode { + ASTNode::Local { + variables: vec![name.to_string()], + initial_values: vec![Some(Box::new(value))], + span: Span::unknown(), + } + } + + fn assign(name: &str, value: ASTNode) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(name)), + value: Box::new(value), + span: Span::unknown(), + } + } + + fn method_call(obj: &str, method: &str, args: Vec) -> ASTNode { + ASTNode::MethodCall { + object: Box::new(v(obj)), + method: method.to_string(), + arguments: args, + span: Span::unknown(), + } + } + + fn binop(operator: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode { + ASTNode::BinaryOp { + operator, + left: Box::new(left), + right: Box::new(right), + span: Span::unknown(), + } + } + + #[test] + fn extract_pattern2_break_realworld_subset() { + let condition = lit_bool(true); + let body = vec![ + local( + "j", + method_call("table", "indexOf", vec![lit_str("|||"), v("i")]), + ), + local("seg", lit_str("")), + ASTNode::If { + condition: Box::new(binop( + BinaryOperator::GreaterEqual, + v("j"), + lit_int(0), + )), + then_body: vec![assign( + "seg", + method_call("table", "substring", vec![v("i"), v("j")]), + )], + else_body: Some(vec![assign( + "seg", + method_call("table", "substring", vec![v("i"), method_call("table", "length", vec![])]), + )]), + span: Span::unknown(), + }, + ASTNode::If { + condition: Box::new(binop(BinaryOperator::Equal, v("seg"), lit_str(""))), + then_body: vec![ASTNode::Break { span: Span::unknown() }], + else_body: None, + span: Span::unknown(), + }, + assign("i", binop(BinaryOperator::Add, v("j"), lit_int(3))), + ]; + + let facts = try_extract_pattern2_break_facts(&condition, &body) + .expect("Ok") + .expect("Some facts"); + assert_eq!(facts.loop_var, "i"); + assert_eq!(facts.carrier_var, "i"); + + match facts.loop_condition { + ASTNode::BinaryOp { operator: BinaryOperator::Less, left, right, .. } => { + assert!(matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == "i")); + assert!(matches!( + right.as_ref(), + ASTNode::MethodCall { method, .. } if method == "length" + )); + } + other => panic!("unexpected loop_condition: {:?}", other), + } + + match facts.break_condition { + ASTNode::BinaryOp { operator: BinaryOperator::Equal, left, right, .. } => { + assert!(matches!( + right.as_ref(), + ASTNode::Literal { value: LiteralValue::String(value), .. } if value.is_empty() + )); + assert!(matches!( + left.as_ref(), + ASTNode::MethodCall { method, .. } if method == "substring" + )); + } + other => panic!("unexpected break_condition: {:?}", other), + } + + match facts.loop_increment { + ASTNode::BinaryOp { operator: BinaryOperator::Add, left, right, .. } => { + assert!(matches!( + left.as_ref(), + ASTNode::MethodCall { method, .. } if method == "indexOf" + )); + assert!(matches!( + right.as_ref(), + ASTNode::Literal { value: LiteralValue::Integer(3), .. } + )); + } + other => panic!("unexpected loop_increment: {:?}", other), + } + } +} diff --git a/tools/smokes/v2/profiles/integration/apps/phase263_pattern2_seg_realworld_min_vm.sh b/tools/smokes/v2/profiles/integration/apps/phase263_pattern2_seg_realworld_min_vm.sh index 6967915d..558f4288 100644 --- a/tools/smokes/v2/profiles/integration/apps/phase263_pattern2_seg_realworld_min_vm.sh +++ b/tools/smokes/v2/profiles/integration/apps/phase263_pattern2_seg_realworld_min_vm.sh @@ -21,6 +21,11 @@ fi OUTPUT_CLEAN=$(echo "$OUTPUT" | filter_noise) +if ! echo "$OUTPUT" | grep -q "\[coreplan/shadow_adopt:pattern2_break_subset\]"; then + test_fail "phase263_pattern2_seg_realworld_min_vm: missing shadow adopt tag" + exit 1 +fi + if echo "$OUTPUT_CLEAN" | grep -q "^4$" || echo "$OUTPUT" | grep -q "^RC: 4$"; then test_pass "phase263_pattern2_seg_realworld_min_vm: Derived slot promotion succeeded (output: 4)" exit 0