Phase 33 NORM canon test: enforce normalized dev route for P1/P2/JP mini
This commit is contained in:
@ -27,13 +27,13 @@
|
||||
//! - **Testability**: Can test conversion independently
|
||||
//! - **Reduces duplication**: Eliminates 120 lines across Pattern 1-4
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
|
||||
use crate::mir::join_ir::JoinModule;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
pub struct JoinIRConversionPipeline;
|
||||
pub(crate) struct JoinIRConversionPipeline;
|
||||
|
||||
impl JoinIRConversionPipeline {
|
||||
/// Execute unified conversion pipeline
|
||||
@ -82,41 +82,28 @@ impl JoinIRConversionPipeline {
|
||||
pattern_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta;
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use super::super::trace;
|
||||
use crate::mir::join_ir::frontend::JoinFuncMetaMap;
|
||||
use crate::mir::join_ir_vm_bridge::bridge_joinir_to_mir_with_meta;
|
||||
|
||||
// Step 1: Log JoinIR stats (functions and blocks)
|
||||
trace::trace().joinir_stats(
|
||||
pattern_name,
|
||||
join_module.functions.len(),
|
||||
join_module
|
||||
.functions
|
||||
.values()
|
||||
.map(|f| f.body.len())
|
||||
.sum(),
|
||||
join_module.functions.values().map(|f| f.body.len()).sum(),
|
||||
);
|
||||
|
||||
// Step 2: JoinModule → MirModule conversion
|
||||
// Pass empty meta map since minimal lowerers don't use metadata
|
||||
let empty_meta: JoinFuncMetaMap = BTreeMap::new();
|
||||
let mir_module = convert_join_module_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| {
|
||||
format!(
|
||||
"[{}/pipeline] MIR conversion failed: {:?}",
|
||||
pattern_name, e
|
||||
)
|
||||
})?;
|
||||
let mir_module = bridge_joinir_to_mir_with_meta(&join_module, &empty_meta)
|
||||
.map_err(|e| format!("[{}/pipeline] MIR conversion failed: {:?}", pattern_name, e))?;
|
||||
|
||||
// Step 3: Log MIR stats (functions and blocks)
|
||||
trace::trace().joinir_stats(
|
||||
pattern_name,
|
||||
mir_module.functions.len(),
|
||||
mir_module
|
||||
.functions
|
||||
.values()
|
||||
.map(|f| f.blocks.len())
|
||||
.sum(),
|
||||
mir_module.functions.values().map(|f| f.blocks.len()).sum(),
|
||||
);
|
||||
|
||||
// Step 4: Merge into current function
|
||||
|
||||
@ -10,17 +10,17 @@
|
||||
//!
|
||||
//! This orchestrator coordinates the three modules for a complete workflow.
|
||||
|
||||
use crate::mir::ValueId;
|
||||
use crate::mir::join_ir::lowering::inline_boundary::{
|
||||
JoinInlineBoundary, LoopExitBinding,
|
||||
};
|
||||
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::{JoinInlineBoundary, LoopExitBinding};
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
|
||||
// Phase 222.5-C: Import modular components
|
||||
use super::exit_binding_validator::validate_exit_binding;
|
||||
use super::exit_binding_applicator::{
|
||||
apply_exit_bindings_to_boundary, create_loop_var_exit_binding,
|
||||
};
|
||||
use super::exit_binding_constructor::build_loop_exit_bindings;
|
||||
use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loop_var_exit_binding};
|
||||
use super::exit_binding_validator::validate_exit_binding;
|
||||
|
||||
/// Builder for generating loop exit bindings
|
||||
///
|
||||
@ -29,7 +29,7 @@ use super::exit_binding_applicator::{apply_exit_bindings_to_boundary, create_loo
|
||||
///
|
||||
/// Eliminates hardcoded variable names and ValueId plumbing scattered across lowerers.
|
||||
#[allow(dead_code)]
|
||||
pub struct ExitBindingBuilder<'a> {
|
||||
pub(crate) struct ExitBindingBuilder<'a> {
|
||||
carrier_info: &'a CarrierInfo,
|
||||
exit_meta: &'a ExitMeta,
|
||||
variable_map: &'a mut BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
@ -60,7 +60,7 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
///
|
||||
/// ExitBindingBuilder instance, or error if metadata is inconsistent
|
||||
#[allow(dead_code)]
|
||||
pub fn new(
|
||||
pub(crate) fn new(
|
||||
carrier_info: &'a CarrierInfo,
|
||||
exit_meta: &'a ExitMeta,
|
||||
variable_map: &'a mut BTreeMap<String, ValueId>, // Phase 222.5-D: HashMap → BTreeMap for determinism
|
||||
@ -86,7 +86,7 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
///
|
||||
/// Vec of LoopExitBinding, one per carrier, sorted by carrier name
|
||||
#[allow(dead_code)]
|
||||
pub fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String> {
|
||||
pub(crate) fn build_loop_exit_bindings(&mut self) -> Result<Vec<LoopExitBinding>, String> {
|
||||
// Phase 222.5-C: Delegate to constructor module
|
||||
build_loop_exit_bindings(self.carrier_info, self.exit_meta, self.variable_map)
|
||||
}
|
||||
@ -106,7 +106,7 @@ impl<'a> ExitBindingBuilder<'a> {
|
||||
///
|
||||
/// Success or error if boundary cannot be updated
|
||||
#[allow(dead_code)]
|
||||
pub fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> {
|
||||
pub(crate) fn apply_to_boundary(&self, boundary: &mut JoinInlineBoundary) -> Result<(), String> {
|
||||
// Phase 222.5-C: Delegate to applicator module
|
||||
apply_exit_bindings_to_boundary(
|
||||
self.carrier_info,
|
||||
@ -160,7 +160,9 @@ mod tests {
|
||||
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
|
||||
.expect("Failed to create builder");
|
||||
|
||||
let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings");
|
||||
let bindings = builder
|
||||
.build_loop_exit_bindings()
|
||||
.expect("Failed to build bindings");
|
||||
|
||||
assert_eq!(bindings.len(), 1);
|
||||
assert_eq!(bindings[0].carrier_name, "sum");
|
||||
@ -213,7 +215,9 @@ mod tests {
|
||||
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
|
||||
.expect("Failed to create builder");
|
||||
|
||||
let bindings = builder.build_loop_exit_bindings().expect("Failed to build bindings");
|
||||
let bindings = builder
|
||||
.build_loop_exit_bindings()
|
||||
.expect("Failed to build bindings");
|
||||
|
||||
assert_eq!(bindings.len(), 2);
|
||||
// Bindings should be sorted by carrier name
|
||||
@ -345,24 +349,27 @@ mod tests {
|
||||
let mut builder = ExitBindingBuilder::new(&carrier_info, &exit_meta, &mut variable_map)
|
||||
.expect("Failed to create builder");
|
||||
|
||||
let _ = builder.build_loop_exit_bindings().expect("Failed to build bindings");
|
||||
let _ = builder
|
||||
.build_loop_exit_bindings()
|
||||
.expect("Failed to build bindings");
|
||||
|
||||
let mut boundary = JoinInlineBoundary {
|
||||
host_inputs: vec![],
|
||||
join_inputs: vec![],
|
||||
exit_bindings: vec![], // Phase 171: Add missing field
|
||||
exit_bindings: vec![], // Phase 171: Add missing field
|
||||
#[allow(deprecated)]
|
||||
host_outputs: vec![], // legacy, unused in new assertions
|
||||
host_outputs: vec![], // legacy, unused in new assertions
|
||||
join_outputs: vec![],
|
||||
#[allow(deprecated)]
|
||||
condition_inputs: vec![], // Phase 171: Add missing field
|
||||
condition_bindings: vec![], // Phase 171-fix: Add missing field
|
||||
expr_result: None, // Phase 33-14: Add missing field
|
||||
loop_var_name: None, // Phase 33-16: Add missing field
|
||||
carrier_info: None, // Phase 228: Add missing field
|
||||
condition_inputs: vec![], // Phase 171: Add missing field
|
||||
condition_bindings: vec![], // Phase 171-fix: Add missing field
|
||||
expr_result: None, // Phase 33-14: Add missing field
|
||||
loop_var_name: None, // Phase 33-16: Add missing field
|
||||
carrier_info: None, // Phase 228: Add missing field
|
||||
};
|
||||
|
||||
builder.apply_to_boundary(&mut boundary)
|
||||
builder
|
||||
.apply_to_boundary(&mut boundary)
|
||||
.expect("Failed to apply to boundary");
|
||||
|
||||
// Should have loop_var + sum carrier in exit_bindings
|
||||
|
||||
@ -38,7 +38,7 @@ use crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape;
|
||||
use crate::mir::BasicBlockId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
pub struct LoopScopeShapeBuilder;
|
||||
pub(crate) struct LoopScopeShapeBuilder;
|
||||
|
||||
impl LoopScopeShapeBuilder {
|
||||
/// Create LoopScopeShape with empty body_locals
|
||||
|
||||
@ -46,20 +46,20 @@ pub(in crate::mir::builder) mod common_init;
|
||||
pub(in crate::mir::builder) mod condition_env_builder;
|
||||
pub(in crate::mir::builder) mod conversion_pipeline;
|
||||
pub(in crate::mir::builder) mod exit_binding;
|
||||
pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod exit_binding_applicator; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod exit_binding_constructor; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C
|
||||
pub(in crate::mir::builder) mod loop_scope_shape_builder;
|
||||
pub(in crate::mir::builder) mod pattern_pipeline;
|
||||
pub(in crate::mir::builder) mod pattern1_minimal;
|
||||
pub(in crate::mir::builder) mod pattern2_with_break;
|
||||
pub(in crate::mir::builder) mod pattern3_with_if_phi;
|
||||
pub(in crate::mir::builder) mod pattern4_carrier_analyzer;
|
||||
pub(in crate::mir::builder) mod pattern4_with_continue;
|
||||
pub(in crate::mir::builder) mod pattern_pipeline;
|
||||
pub(in crate::mir::builder) mod router;
|
||||
pub(in crate::mir::builder) mod trim_loop_lowering; // Phase 180: Dedicated Trim/P5 lowering module
|
||||
pub(in crate::mir::builder) mod trim_pattern_validator;
|
||||
pub(in crate::mir::builder) mod trim_pattern_lowerer;
|
||||
pub(in crate::mir::builder) mod trim_pattern_validator;
|
||||
|
||||
// Re-export router for convenience
|
||||
pub(in crate::mir::builder) use router::{route_loop_pattern, LoopPatternContext};
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
//! - Trim break condition generation
|
||||
//! - Carrier binding setup in ConditionEnv
|
||||
|
||||
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
|
||||
use crate::ast::{ASTNode, Span, UnaryOperator};
|
||||
use crate::mir::join_ir::lowering::condition_env::ConditionBinding;
|
||||
use crate::mir::loop_pattern_detection::trim_loop_helper::TrimLoopHelper;
|
||||
use crate::mir::ValueId;
|
||||
use crate::ast::{ASTNode, UnaryOperator, Span};
|
||||
|
||||
pub(in crate::mir::builder::control_flow::joinir::patterns) struct TrimPatternLowerer;
|
||||
|
||||
@ -61,8 +61,12 @@ impl TrimPatternLowerer {
|
||||
let carrier_name = &trim_helper.carrier_name;
|
||||
|
||||
// Get host ValueId for carrier
|
||||
let host_value_id = get_host_value(carrier_name)
|
||||
.ok_or_else(|| format!("[TrimPatternLowerer] Carrier '{}' not in variable_map", carrier_name))?;
|
||||
let host_value_id = get_host_value(carrier_name).ok_or_else(|| {
|
||||
format!(
|
||||
"[TrimPatternLowerer] Carrier '{}' not in variable_map",
|
||||
carrier_name
|
||||
)
|
||||
})?;
|
||||
|
||||
// Allocate JoinIR ValueId
|
||||
let joinir_value_id = alloc_join_value();
|
||||
@ -94,7 +98,8 @@ impl TrimPatternLowerer {
|
||||
insert_to_env: impl FnOnce(String, ValueId),
|
||||
alloc_join_value: &mut dyn FnMut() -> ValueId,
|
||||
) -> Result<ConditionBinding, String> {
|
||||
let binding = Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?;
|
||||
let binding =
|
||||
Self::setup_trim_carrier_binding(trim_helper, get_host_value, alloc_join_value)?;
|
||||
|
||||
// Insert into env
|
||||
insert_to_env(binding.name.clone(), binding.join_value);
|
||||
@ -122,7 +127,9 @@ mod tests {
|
||||
|
||||
// Should be UnaryOp::Not
|
||||
match result {
|
||||
ASTNode::UnaryOp { operator, operand, .. } => {
|
||||
ASTNode::UnaryOp {
|
||||
operator, operand, ..
|
||||
} => {
|
||||
assert_eq!(operator, UnaryOperator::Not);
|
||||
// Operand should be Variable with carrier name
|
||||
match *operand {
|
||||
@ -151,11 +158,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let get_value = |name: &str| variable_map.get(name).copied();
|
||||
let result = TrimPatternLowerer::setup_trim_carrier_binding(
|
||||
&helper,
|
||||
get_value,
|
||||
&mut alloc,
|
||||
);
|
||||
let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let binding = result.unwrap();
|
||||
@ -178,11 +181,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let get_value = |name: &str| variable_map.get(name).copied();
|
||||
let result = TrimPatternLowerer::setup_trim_carrier_binding(
|
||||
&helper,
|
||||
get_value,
|
||||
&mut alloc,
|
||||
);
|
||||
let result = TrimPatternLowerer::setup_trim_carrier_binding(&helper, get_value, &mut alloc);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("not in variable_map"));
|
||||
@ -209,12 +208,8 @@ mod tests {
|
||||
env.insert(name, value);
|
||||
};
|
||||
|
||||
let result = TrimPatternLowerer::add_to_condition_env(
|
||||
&helper,
|
||||
get_value,
|
||||
insert,
|
||||
&mut alloc,
|
||||
);
|
||||
let result =
|
||||
TrimPatternLowerer::add_to_condition_env(&helper, get_value, insert, &mut alloc);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let binding = result.unwrap();
|
||||
|
||||
@ -31,10 +31,10 @@ impl TrimPatternValidator {
|
||||
ch_value: ValueId,
|
||||
whitespace_chars: &[String],
|
||||
) -> Result<ValueId, String> {
|
||||
use crate::mir::builder::emission::constant::emit_string;
|
||||
use crate::mir::builder::emission::compare::emit_eq_to;
|
||||
use crate::mir::types::BinaryOp;
|
||||
use crate::mir::builder::emission::constant::emit_string;
|
||||
use crate::mir::instruction::MirInstruction;
|
||||
use crate::mir::types::BinaryOp;
|
||||
|
||||
if whitespace_chars.is_empty() {
|
||||
return Err("[emit_whitespace_check] Empty whitespace_chars".to_string());
|
||||
@ -89,18 +89,32 @@ impl TrimPatternValidator {
|
||||
) -> Option<(String, Box<ASTNode>)> {
|
||||
for stmt in loop_body {
|
||||
// Look for: local ch = ...
|
||||
if let ASTNode::Local { variables, initial_values, .. } = stmt {
|
||||
if let ASTNode::Local {
|
||||
variables,
|
||||
initial_values,
|
||||
..
|
||||
} = stmt
|
||||
{
|
||||
for (i, var) in variables.iter().enumerate() {
|
||||
if var == var_name {
|
||||
if let Some(Some(init_expr_box)) = initial_values.get(i) {
|
||||
// Check if it's a substring method call
|
||||
if let ASTNode::MethodCall { object, method, arguments, .. } = init_expr_box.as_ref() {
|
||||
if let ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} = init_expr_box.as_ref()
|
||||
{
|
||||
if method == "substring" && arguments.len() == 2 {
|
||||
// Extract object name
|
||||
if let ASTNode::Variable { name, .. } = object.as_ref() {
|
||||
// Return object name and start expression
|
||||
// (We assume second arg is start+1, first arg is start)
|
||||
return Some((name.clone(), Box::new(arguments[0].clone())));
|
||||
return Some((
|
||||
name.clone(),
|
||||
Box::new(arguments[0].clone()),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,45 +130,41 @@ impl TrimPatternValidator {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{LiteralValue, Span, BinaryOperator};
|
||||
use crate::ast::{BinaryOperator, LiteralValue, Span};
|
||||
|
||||
#[test]
|
||||
fn test_extract_substring_args_valid() {
|
||||
// Create: local ch = s.substring(start, start+1)
|
||||
let body = vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::BinaryOp {
|
||||
operator: BinaryOperator::Add,
|
||||
left: Box::new(ASTNode::Variable {
|
||||
name: "start".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
right: Box::new(ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
|
||||
assert!(result.is_some());
|
||||
@ -173,32 +183,28 @@ mod tests {
|
||||
#[test]
|
||||
fn test_extract_substring_args_wrong_var() {
|
||||
// local other_var = s.substring(0, 1)
|
||||
let body = vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["other_var".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["other_var".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "substring".to_string(),
|
||||
arguments: vec![
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
},
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(1),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
|
||||
assert!(result.is_none());
|
||||
@ -207,28 +213,22 @@ mod tests {
|
||||
#[test]
|
||||
fn test_extract_substring_args_wrong_method() {
|
||||
// local ch = s.charAt(0)
|
||||
let body = vec![
|
||||
ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![
|
||||
Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "charAt".to_string(),
|
||||
arguments: vec![
|
||||
ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
],
|
||||
span: Span::unknown(),
|
||||
})),
|
||||
],
|
||||
let body = vec![ASTNode::Local {
|
||||
variables: vec!["ch".to_string()],
|
||||
initial_values: vec![Some(Box::new(ASTNode::MethodCall {
|
||||
object: Box::new(ASTNode::Variable {
|
||||
name: "s".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
method: "charAt".to_string(),
|
||||
arguments: vec![ASTNode::Literal {
|
||||
value: LiteralValue::Integer(0),
|
||||
span: Span::unknown(),
|
||||
}],
|
||||
span: Span::unknown(),
|
||||
},
|
||||
];
|
||||
}))],
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
|
||||
let result = TrimPatternValidator::extract_substring_args(&body, "ch");
|
||||
assert!(result.is_none());
|
||||
|
||||
Reference in New Issue
Block a user