phase29ai(p9): planner-first pattern7 split-scan subset
This commit is contained in:
@ -19,6 +19,7 @@ pub(in crate::mir::builder) struct LoopFacts {
|
||||
pub condition_shape: ConditionShape,
|
||||
pub step_shape: StepShape,
|
||||
pub scan_with_init: Option<ScanWithInitFacts>,
|
||||
pub split_scan: Option<SplitScanFacts>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -29,6 +30,15 @@ pub(in crate::mir::builder) struct ScanWithInitFacts {
|
||||
pub step_lit: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct SplitScanFacts {
|
||||
pub s_var: String,
|
||||
pub sep_var: String,
|
||||
pub result_var: String,
|
||||
pub i_var: String,
|
||||
pub start_var: String,
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn try_build_loop_facts(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
@ -36,15 +46,13 @@ pub(in crate::mir::builder) fn try_build_loop_facts(
|
||||
// Phase 29ai P4/P7: keep Facts conservative; only return Some when we can
|
||||
// build a concrete pattern fact set (no guesses / no hardcoded names).
|
||||
|
||||
let Some(condition_shape) = try_extract_condition_shape(condition)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(step_shape) = try_extract_step_shape(body)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
let condition_shape =
|
||||
try_extract_condition_shape(condition)?.unwrap_or(ConditionShape::Unknown);
|
||||
let step_shape = try_extract_step_shape(body)?.unwrap_or(StepShape::Unknown);
|
||||
let scan_with_init = try_extract_scan_with_init_facts(body, &condition_shape, &step_shape)?;
|
||||
let split_scan = try_extract_split_scan_facts(condition, body)?;
|
||||
|
||||
if scan_with_init.is_none() {
|
||||
if scan_with_init.is_none() && split_scan.is_none() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@ -52,6 +60,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts(
|
||||
condition_shape,
|
||||
step_shape,
|
||||
scan_with_init,
|
||||
split_scan,
|
||||
}))
|
||||
}
|
||||
|
||||
@ -265,6 +274,308 @@ fn try_extract_scan_with_init_facts(
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn try_extract_split_scan_facts(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
) -> Result<Option<SplitScanFacts>, Freeze> {
|
||||
let Some((i_var, s_var, sep_var)) = try_extract_split_scan_condition_vars(condition) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let Some(if_stmt) = body.iter().find_map(|stmt| match stmt {
|
||||
ASTNode::If {
|
||||
condition,
|
||||
then_body,
|
||||
else_body,
|
||||
..
|
||||
} => Some((condition.as_ref(), then_body, else_body.as_ref())),
|
||||
_ => None,
|
||||
}) else {
|
||||
return Ok(None);
|
||||
};
|
||||
let (match_condition, then_body, else_body) = if_stmt;
|
||||
let Some(else_body) = else_body else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
if !is_split_scan_match_condition(match_condition, &s_var, &i_var, &sep_var) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let Some((result_var, start_var)) =
|
||||
then_body
|
||||
.iter()
|
||||
.find_map(|stmt| extract_split_scan_then_push(stmt, &s_var, &i_var))
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let has_start_update = then_body
|
||||
.iter()
|
||||
.any(|stmt| is_start_update(stmt, &start_var, &i_var, &sep_var));
|
||||
if !has_start_update {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let has_i_set_to_start = then_body
|
||||
.iter()
|
||||
.any(|stmt| is_i_set_to_start(stmt, &i_var, &start_var));
|
||||
if !has_i_set_to_start {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let has_else_increment = else_body
|
||||
.iter()
|
||||
.any(|stmt| is_i_increment_by_one(stmt, &i_var));
|
||||
if !has_else_increment {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some(SplitScanFacts {
|
||||
s_var,
|
||||
sep_var,
|
||||
result_var,
|
||||
i_var,
|
||||
start_var,
|
||||
}))
|
||||
}
|
||||
|
||||
fn try_extract_split_scan_condition_vars(condition: &ASTNode) -> Option<(String, String, String)> {
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::LessEqual,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = condition
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let ASTNode::Variable { name: i_var, .. } = left.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: minus_left,
|
||||
right: minus_right,
|
||||
..
|
||||
} = right.as_ref()
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
let s_var = match minus_left.as_ref() {
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} if method == "length" && arguments.is_empty() => match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
let sep_var = match minus_right.as_ref() {
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} if method == "length" && arguments.is_empty() => match object.as_ref() {
|
||||
ASTNode::Variable { name, .. } => name.clone(),
|
||||
_ => return None,
|
||||
},
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((i_var.clone(), s_var, sep_var))
|
||||
}
|
||||
|
||||
fn is_split_scan_match_condition(
|
||||
condition: &ASTNode,
|
||||
s_var: &str,
|
||||
i_var: &str,
|
||||
sep_var: &str,
|
||||
) -> bool {
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = condition
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let matches_left = is_substring_i_plus_sep_len(left.as_ref(), s_var, i_var, sep_var)
|
||||
&& matches!(
|
||||
right.as_ref(),
|
||||
ASTNode::Variable { name, .. } if name == sep_var
|
||||
);
|
||||
let matches_right = is_substring_i_plus_sep_len(right.as_ref(), s_var, i_var, sep_var)
|
||||
&& matches!(
|
||||
left.as_ref(),
|
||||
ASTNode::Variable { name, .. } if name == sep_var
|
||||
);
|
||||
matches_left || matches_right
|
||||
}
|
||||
|
||||
fn is_substring_i_plus_sep_len(
|
||||
expr: &ASTNode,
|
||||
s_var: &str,
|
||||
i_var: &str,
|
||||
sep_var: &str,
|
||||
) -> bool {
|
||||
let ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} = expr
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if method != "substring" || arguments.len() != 2 {
|
||||
return false;
|
||||
}
|
||||
let ASTNode::Variable { name: obj, .. } = object.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
if obj != s_var {
|
||||
return false;
|
||||
}
|
||||
|
||||
match &arguments[0] {
|
||||
ASTNode::Variable { name, .. } if name == i_var => {}
|
||||
_ => return false,
|
||||
}
|
||||
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = &arguments[1]
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } if name == i_var => {}
|
||||
_ => return false,
|
||||
}
|
||||
matches!(
|
||||
right.as_ref(),
|
||||
ASTNode::MethodCall { object, method, arguments, .. }
|
||||
if method == "length"
|
||||
&& arguments.is_empty()
|
||||
&& matches!(object.as_ref(), ASTNode::Variable { name, .. } if name == sep_var)
|
||||
)
|
||||
}
|
||||
|
||||
fn extract_split_scan_then_push(
|
||||
stmt: &ASTNode,
|
||||
s_var: &str,
|
||||
i_var: &str,
|
||||
) -> Option<(String, String)> {
|
||||
let ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} = stmt
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if method != "push" || arguments.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
let ASTNode::Variable { name: result_var, .. } = object.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
let ASTNode::MethodCall {
|
||||
object: substr_object,
|
||||
method: substr_method,
|
||||
arguments: substr_args,
|
||||
..
|
||||
} = &arguments[0]
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if substr_method != "substring" || substr_args.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let ASTNode::Variable { name: substr_obj, .. } = substr_object.as_ref() else {
|
||||
return None;
|
||||
};
|
||||
if substr_obj != s_var {
|
||||
return None;
|
||||
}
|
||||
let ASTNode::Variable { name: start_var, .. } = &substr_args[0] else {
|
||||
return None;
|
||||
};
|
||||
match &substr_args[1] {
|
||||
ASTNode::Variable { name, .. } if name == i_var => {}
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
Some((result_var.clone(), start_var.clone()))
|
||||
}
|
||||
|
||||
fn is_start_update(stmt: &ASTNode, start_var: &str, i_var: &str, sep_var: &str) -> bool {
|
||||
let ASTNode::Assignment { target, value, .. } = stmt else {
|
||||
return false;
|
||||
};
|
||||
match target.as_ref() {
|
||||
ASTNode::Variable { name, .. } if name == start_var => {}
|
||||
_ => return false,
|
||||
}
|
||||
let ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} = value.as_ref()
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
match left.as_ref() {
|
||||
ASTNode::Variable { name, .. } if name == i_var => {}
|
||||
_ => return false,
|
||||
}
|
||||
matches!(
|
||||
right.as_ref(),
|
||||
ASTNode::MethodCall { object, method, arguments, .. }
|
||||
if method == "length"
|
||||
&& arguments.is_empty()
|
||||
&& matches!(object.as_ref(), ASTNode::Variable { name, .. } if name == sep_var)
|
||||
)
|
||||
}
|
||||
|
||||
fn is_i_set_to_start(stmt: &ASTNode, i_var: &str, start_var: &str) -> bool {
|
||||
let ASTNode::Assignment { target, value, .. } = stmt else {
|
||||
return false;
|
||||
};
|
||||
matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == i_var)
|
||||
&& matches!(value.as_ref(), ASTNode::Variable { name, .. } if name == start_var)
|
||||
}
|
||||
|
||||
fn is_i_increment_by_one(stmt: &ASTNode, i_var: &str) -> bool {
|
||||
let ASTNode::Assignment { target, value, .. } = stmt else {
|
||||
return false;
|
||||
};
|
||||
let target_ok = matches!(target.as_ref(), ASTNode::Variable { name, .. } if name == i_var);
|
||||
let value_ok = matches!(
|
||||
value.as_ref(),
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left,
|
||||
right,
|
||||
..
|
||||
} if matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == i_var)
|
||||
&& matches!(right.as_ref(), ASTNode::Literal { value: LiteralValue::Integer(1), .. })
|
||||
);
|
||||
target_ok && value_ok
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -375,4 +686,180 @@ mod tests {
|
||||
let facts = try_build_loop_facts(&condition, &[step]).expect("Ok");
|
||||
assert!(facts.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopfacts_ok_some_for_canonical_split_scan_minimal() {
|
||||
let condition = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::LessEqual,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("separator")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let if_stmt = ASTNode::If {
|
||||
condition: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
v("i"),
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("separator")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(v("separator")),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![
|
||||
ASTNode::MethodCall {
|
||||
object: Box::new(v("result")),
|
||||
method: "push".to_string(),
|
||||
arguments: vec![ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![v("start"), v("i")],
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(v("start")),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("separator")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Assignment {
|
||||
target: Box::new(v("i")),
|
||||
value: Box::new(v("start")),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
else_body: Some(vec![ASTNode::Assignment {
|
||||
target: Box::new(v("i")),
|
||||
value: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}]),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let facts = try_build_loop_facts(&condition, &[if_stmt]).expect("Ok");
|
||||
let split_scan = facts.and_then(|facts| facts.split_scan);
|
||||
let split_scan = split_scan.expect("SplitScan facts");
|
||||
assert_eq!(split_scan.s_var, "s");
|
||||
assert_eq!(split_scan.sep_var, "separator");
|
||||
assert_eq!(split_scan.result_var, "result");
|
||||
assert_eq!(split_scan.i_var, "i");
|
||||
assert_eq!(split_scan.start_var, "start");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loopfacts_ok_none_when_split_scan_missing_else() {
|
||||
let condition = ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::LessEqual,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Subtract,
|
||||
left: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("separator")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let if_stmt = ASTNode::If {
|
||||
condition: Box::new(ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Equal,
|
||||
left: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
v("i"),
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(v("i")),
|
||||
right: Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(v("separator")),
|
||||
method: "length".to_string(),
|
||||
arguments: vec![],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(v("separator")),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
then_body: vec![ASTNode::MethodCall {
|
||||
object: Box::new(v("result")),
|
||||
method: "push".to_string(),
|
||||
arguments: vec![ASTNode::MethodCall {
|
||||
object: Box::new(v("s")),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![v("start"), v("i")],
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
else_body: None,
|
||||
span: Span::unknown(),
|
||||
};
|
||||
|
||||
let facts = try_build_loop_facts(&condition, &[if_stmt]).expect("Ok");
|
||||
assert!(facts.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,9 @@ use crate::mir::builder::control_flow::plan::normalize::{canonicalize_loop_facts
|
||||
|
||||
use super::candidates::{CandidateSet, PlanCandidate};
|
||||
use super::Freeze;
|
||||
use crate::mir::builder::control_flow::plan::{DomainPlan, ScanDirection, ScanWithInitPlan};
|
||||
use crate::mir::builder::control_flow::plan::{
|
||||
DomainPlan, ScanDirection, ScanWithInitPlan, SplitScanPlan,
|
||||
};
|
||||
|
||||
/// Phase 29ai P0: External-ish SSOT entrypoint (skeleton)
|
||||
///
|
||||
@ -55,5 +57,95 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(split_scan) = &facts.facts.split_scan {
|
||||
candidates.push(PlanCandidate {
|
||||
plan: DomainPlan::SplitScan(SplitScanPlan {
|
||||
s_var: split_scan.s_var.clone(),
|
||||
sep_var: split_scan.sep_var.clone(),
|
||||
result_var: split_scan.result_var.clone(),
|
||||
i_var: split_scan.i_var.clone(),
|
||||
start_var: split_scan.start_var.clone(),
|
||||
}),
|
||||
rule: "loop/split_scan",
|
||||
});
|
||||
}
|
||||
|
||||
candidates.finalize()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::builder::control_flow::plan::facts::scan_shapes::{
|
||||
ConditionShape, StepShape,
|
||||
};
|
||||
use crate::mir::builder::control_flow::plan::facts::loop_facts::{
|
||||
LoopFacts, ScanWithInitFacts, SplitScanFacts,
|
||||
};
|
||||
use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts;
|
||||
|
||||
#[test]
|
||||
fn planner_builds_split_scan_plan_from_facts() {
|
||||
let facts = LoopFacts {
|
||||
condition_shape: ConditionShape::Unknown,
|
||||
step_shape: StepShape::Unknown,
|
||||
scan_with_init: None,
|
||||
split_scan: Some(SplitScanFacts {
|
||||
s_var: "s".to_string(),
|
||||
sep_var: "separator".to_string(),
|
||||
result_var: "result".to_string(),
|
||||
i_var: "i".to_string(),
|
||||
start_var: "start".to_string(),
|
||||
}),
|
||||
};
|
||||
let canonical = canonicalize_loop_facts(facts);
|
||||
let plan = build_plan_from_facts(canonical).expect("Ok");
|
||||
|
||||
match plan {
|
||||
Some(DomainPlan::SplitScan(plan)) => {
|
||||
assert_eq!(plan.s_var, "s");
|
||||
assert_eq!(plan.sep_var, "separator");
|
||||
assert_eq!(plan.result_var, "result");
|
||||
assert_eq!(plan.i_var, "i");
|
||||
assert_eq!(plan.start_var, "start");
|
||||
}
|
||||
other => panic!("expected split scan plan, got {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planner_prefers_none_when_no_candidates() {
|
||||
let facts = LoopFacts {
|
||||
condition_shape: ConditionShape::Unknown,
|
||||
step_shape: StepShape::Unknown,
|
||||
scan_with_init: None,
|
||||
split_scan: None,
|
||||
};
|
||||
let canonical = canonicalize_loop_facts(facts);
|
||||
let plan = build_plan_from_facts(canonical).expect("Ok");
|
||||
assert!(plan.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn planner_retains_scan_with_init_plan_path() {
|
||||
let facts = LoopFacts {
|
||||
condition_shape: ConditionShape::Unknown,
|
||||
step_shape: StepShape::Unknown,
|
||||
scan_with_init: Some(ScanWithInitFacts {
|
||||
loop_var: "i".to_string(),
|
||||
haystack: "s".to_string(),
|
||||
needle: "ch".to_string(),
|
||||
step_lit: 1,
|
||||
}),
|
||||
split_scan: None,
|
||||
};
|
||||
let canonical = canonicalize_loop_facts(facts);
|
||||
let plan = build_plan_from_facts(canonical).expect("Ok");
|
||||
match plan {
|
||||
Some(DomainPlan::ScanWithInit(plan)) => {
|
||||
assert_eq!(plan.loop_var, "i");
|
||||
}
|
||||
other => panic!("expected scan_with_init plan, got {:?}", other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,14 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
|
||||
None => (legacy_rules::pattern6::extract(ctx)?, true),
|
||||
}
|
||||
}
|
||||
RuleKind::Pattern7 => (legacy_rules::pattern7::extract(ctx)?, true),
|
||||
RuleKind::Pattern7 => {
|
||||
let from_planner = planner::build_plan(ctx.condition, ctx.body)
|
||||
.map_err(|freeze| freeze.to_string())?;
|
||||
match from_planner {
|
||||
Some(plan) => (Some(plan), false),
|
||||
None => (legacy_rules::pattern7::extract(ctx)?, true),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(domain_plan) = plan_opt {
|
||||
|
||||
Reference in New Issue
Block a user