Extend CapturedEnv to include function parameters used in loop conditions, enabling ExprLowerer to resolve variables like `s` in `loop(p < s.length())`. Phase 245C changes: - function_scope_capture.rs: Add collect_names_in_loop_parts() helper - function_scope_capture.rs: Extend analyze_captured_vars_v2() with param capture logic - function_scope_capture.rs: Add 4 new comprehensive tests Test fix: - expr_lowerer/ast_support.rs: Accept all MethodCall nodes for syntax support (validation happens during lowering in MethodCallLowerer) Problem solved: "Variable not found: s" errors in loop conditions Test results: 924/924 PASS (+13 from baseline 911) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
640 lines
20 KiB
Rust
640 lines
20 KiB
Rust
//! Phase 224-B: MethodCall Lowering Box
|
|
//!
|
|
//! This box provides metadata-driven lowering of MethodCall AST nodes to JoinIR.
|
|
//!
|
|
//! ## Design Philosophy
|
|
//!
|
|
//! **Box-First Design**: MethodCallLowerer is a single-responsibility box that
|
|
//! answers one question: "Can this MethodCall be lowered to JoinIR, and if so, how?"
|
|
//!
|
|
//! **Metadata-Driven**: Uses CoreMethodId metadata exclusively - NO method name hardcoding.
|
|
//! All decisions based on `is_pure()`, `allowed_in_condition()`, `allowed_in_init()`.
|
|
//!
|
|
//! **Fail-Fast**: If a method is not whitelisted, immediately returns Err.
|
|
//! No silent fallbacks or guessing.
|
|
//!
|
|
//! ## Supported Contexts
|
|
//!
|
|
//! - **Condition context**: Methods allowed in loop conditions (e.g., `s.length()`)
|
|
//! - **Init context**: Methods allowed in LoopBodyLocal initialization (e.g., `s.substring(0, 1)`)
|
|
//!
|
|
//! ## Example Usage
|
|
//!
|
|
//! ```ignore
|
|
//! // Loop condition: loop(i < s.length())
|
|
//! let recv_val = ValueId(0); // 's'
|
|
//! let result = MethodCallLowerer::lower_for_condition(
|
|
//! recv_val,
|
|
//! "length",
|
|
//! &[],
|
|
//! &mut alloc_value,
|
|
//! &mut instructions,
|
|
//! )?;
|
|
//! // Result: BoxCall instruction emitted, returns result ValueId
|
|
//! ```
|
|
|
|
use crate::ast::ASTNode;
|
|
use crate::mir::join_ir::{JoinInst, MirLikeInst};
|
|
use crate::mir::ValueId;
|
|
use crate::runtime::core_box_ids::CoreMethodId;
|
|
|
|
use super::condition_env::ConditionEnv;
|
|
|
|
/// Phase 224-B: MethodCall Lowerer Box
|
|
///
|
|
/// Provides metadata-driven lowering of MethodCall AST nodes to JoinIR instructions.
|
|
pub struct MethodCallLowerer;
|
|
|
|
impl MethodCallLowerer {
|
|
/// Lower a MethodCall for use in loop condition expressions
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `recv_val` - Receiver ValueId (already lowered)
|
|
/// * `method_name` - Method name from AST (e.g., "length")
|
|
/// * `args` - Argument AST nodes (not yet supported in P0)
|
|
/// * `alloc_value` - ValueId allocator function
|
|
/// * `instructions` - Instruction buffer to append to
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(ValueId)` - Result of method call
|
|
/// * `Err(String)` - If method not found or not allowed in condition
|
|
///
|
|
/// # Phase 224-C: Argument Support
|
|
///
|
|
/// - Supports zero-argument methods (e.g., `s.length()`)
|
|
/// - Supports methods with arguments (e.g., `s.substring(0, 5)`, `s.indexOf("x")`)
|
|
/// - Only whitelisted methods (StringLength, ArrayLength, StringIndexOf, etc.)
|
|
/// - Arity is checked against CoreMethodId metadata
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```ignore
|
|
/// // Loop condition: loop(i < s.length())
|
|
/// let recv_val = env.get("s").unwrap();
|
|
/// let result = MethodCallLowerer::lower_for_condition(
|
|
/// recv_val,
|
|
/// "length",
|
|
/// &[],
|
|
/// &mut alloc_value,
|
|
/// &mut instructions,
|
|
/// )?;
|
|
/// ```
|
|
pub fn lower_for_condition<F>(
|
|
recv_val: ValueId,
|
|
method_name: &str,
|
|
args: &[ASTNode],
|
|
alloc_value: &mut F,
|
|
env: &ConditionEnv,
|
|
instructions: &mut Vec<JoinInst>,
|
|
) -> Result<ValueId, String>
|
|
where
|
|
F: FnMut() -> ValueId,
|
|
{
|
|
// Resolve method name to CoreMethodId
|
|
// Note: We don't know receiver type at this point, so we try all methods
|
|
let method_id = CoreMethodId::iter()
|
|
.find(|m| m.name() == method_name)
|
|
.ok_or_else(|| {
|
|
format!(
|
|
"MethodCall not recognized as CoreMethodId: {}.{}()",
|
|
recv_val.0, method_name
|
|
)
|
|
})?;
|
|
|
|
// Check if allowed in condition context
|
|
if !method_id.allowed_in_condition() {
|
|
return Err(format!(
|
|
"MethodCall not allowed in loop condition: {}.{}() (not whitelisted)",
|
|
recv_val.0, method_name
|
|
));
|
|
}
|
|
|
|
// Phase 224-C: Check arity
|
|
let expected_arity = method_id.arity();
|
|
if args.len() != expected_arity {
|
|
return Err(format!(
|
|
"Arity mismatch: {}.{}() expects {} args, got {}",
|
|
recv_val.0, method_name, expected_arity, args.len()
|
|
));
|
|
}
|
|
|
|
// Phase 224-C: Lower arguments using condition lowerer
|
|
let mut lowered_args = Vec::new();
|
|
for arg_ast in args {
|
|
let arg_val = super::condition_lowerer::lower_value_expression(
|
|
arg_ast,
|
|
alloc_value,
|
|
env,
|
|
instructions
|
|
)?;
|
|
lowered_args.push(arg_val);
|
|
}
|
|
|
|
// Emit BoxCall instruction
|
|
let dst = alloc_value();
|
|
let box_name = method_id.box_id().name().to_string();
|
|
|
|
// Build complete args: receiver + method args
|
|
let mut full_args = vec![recv_val];
|
|
full_args.extend(lowered_args);
|
|
|
|
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
|
dst: Some(dst),
|
|
box_name,
|
|
method: method_name.to_string(),
|
|
args: full_args,
|
|
}));
|
|
|
|
Ok(dst)
|
|
}
|
|
|
|
/// Lower a MethodCall for use in LoopBodyLocal initialization
|
|
///
|
|
/// Similar to `lower_for_condition` but uses `allowed_in_init()` whitelist.
|
|
/// More permissive - allows methods like `substring`, `indexOf`, etc.
|
|
///
|
|
/// # Phase 224-C: Argument Support
|
|
///
|
|
/// - Supports zero-argument methods
|
|
/// - Supports methods with arguments (e.g., `substring(0, 5)`, `indexOf(ch)`)
|
|
/// - Arity is checked against CoreMethodId metadata
|
|
///
|
|
/// # Phase 226: Cascading LoopBodyLocal Support
|
|
///
|
|
/// - Arguments can reference previously defined body-local variables
|
|
/// - Checks `body_local_env` first, then `cond_env` for variable resolution
|
|
/// - Example: `local digit_pos = digits.indexOf(ch)` where `ch` is body-local
|
|
pub fn lower_for_init<F>(
|
|
recv_val: ValueId,
|
|
method_name: &str,
|
|
args: &[ASTNode],
|
|
alloc_value: &mut F,
|
|
cond_env: &ConditionEnv,
|
|
body_local_env: &super::loop_body_local_env::LoopBodyLocalEnv,
|
|
instructions: &mut Vec<JoinInst>,
|
|
) -> Result<ValueId, String>
|
|
where
|
|
F: FnMut() -> ValueId,
|
|
{
|
|
// Resolve method name to CoreMethodId
|
|
let method_id = CoreMethodId::iter()
|
|
.find(|m| m.name() == method_name)
|
|
.ok_or_else(|| {
|
|
format!(
|
|
"MethodCall not recognized as CoreMethodId: {}.{}()",
|
|
recv_val.0, method_name
|
|
)
|
|
})?;
|
|
|
|
// Check if allowed in init context
|
|
if !method_id.allowed_in_init() {
|
|
return Err(format!(
|
|
"MethodCall not allowed in LoopBodyLocal init: {}.{}() (not whitelisted)",
|
|
recv_val.0, method_name
|
|
));
|
|
}
|
|
|
|
// Phase 224-C: Check arity
|
|
let expected_arity = method_id.arity();
|
|
if args.len() != expected_arity {
|
|
return Err(format!(
|
|
"Arity mismatch: {}.{}() expects {} args, got {}",
|
|
recv_val.0, method_name, expected_arity, args.len()
|
|
));
|
|
}
|
|
|
|
// Phase 226: Lower arguments with cascading LoopBodyLocal support
|
|
// Check body_local_env first, then cond_env
|
|
let mut lowered_args = Vec::new();
|
|
for arg_ast in args {
|
|
let arg_val = Self::lower_arg_with_cascading(
|
|
arg_ast,
|
|
alloc_value,
|
|
cond_env,
|
|
body_local_env,
|
|
instructions
|
|
)?;
|
|
lowered_args.push(arg_val);
|
|
}
|
|
|
|
// Emit BoxCall instruction
|
|
let dst = alloc_value();
|
|
let box_name = method_id.box_id().name().to_string();
|
|
|
|
// Build complete args: receiver + method args
|
|
let mut full_args = vec![recv_val];
|
|
full_args.extend(lowered_args);
|
|
|
|
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
|
dst: Some(dst),
|
|
box_name,
|
|
method: method_name.to_string(),
|
|
args: full_args,
|
|
}));
|
|
|
|
Ok(dst)
|
|
}
|
|
|
|
/// Phase 226: Lower an argument expression with cascading LoopBodyLocal support
|
|
///
|
|
/// This function extends `condition_lowerer::lower_value_expression` to support
|
|
/// cascading LoopBodyLocal variables. It checks both environments:
|
|
/// 1. LoopBodyLocalEnv first (for previously defined body-local variables)
|
|
/// 2. ConditionEnv as fallback (for loop condition variables)
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```nyash
|
|
/// local ch = s.substring(p, p+1) // ch stored in body_local_env
|
|
/// local digit_pos = digits.indexOf(ch) // ch resolved from body_local_env
|
|
/// ```
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `expr` - Argument AST node
|
|
/// * `alloc_value` - ValueId allocator
|
|
/// * `cond_env` - Condition environment (fallback)
|
|
/// * `body_local_env` - LoopBodyLocal environment (priority)
|
|
/// * `instructions` - Instruction buffer
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// * `Ok(ValueId)` - Lowered argument value
|
|
/// * `Err(String)` - If variable not found in either environment
|
|
fn lower_arg_with_cascading<F>(
|
|
expr: &ASTNode,
|
|
alloc_value: &mut F,
|
|
cond_env: &ConditionEnv,
|
|
body_local_env: &super::loop_body_local_env::LoopBodyLocalEnv,
|
|
instructions: &mut Vec<JoinInst>,
|
|
) -> Result<ValueId, String>
|
|
where
|
|
F: FnMut() -> ValueId,
|
|
{
|
|
match expr {
|
|
// Variables - check body_local_env first, then cond_env
|
|
ASTNode::Variable { name, .. } => {
|
|
if let Some(vid) = body_local_env.get(name) {
|
|
eprintln!(
|
|
"[method_call_lowerer] Arg '{}' found in LoopBodyLocalEnv → {:?}",
|
|
name, vid
|
|
);
|
|
Ok(vid)
|
|
} else if let Some(vid) = cond_env.get(name) {
|
|
eprintln!(
|
|
"[method_call_lowerer] Arg '{}' found in ConditionEnv → {:?}",
|
|
name, vid
|
|
);
|
|
Ok(vid)
|
|
} else {
|
|
Err(format!(
|
|
"Variable '{}' not found in LoopBodyLocalEnv or ConditionEnv",
|
|
name
|
|
))
|
|
}
|
|
}
|
|
|
|
// For non-variables (literals, binops, etc.), delegate to condition_lowerer
|
|
// These don't need cascading lookup since they don't reference variables directly
|
|
_ => super::condition_lowerer::lower_value_expression(
|
|
expr,
|
|
alloc_value,
|
|
cond_env,
|
|
instructions,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::mir::join_ir::JoinInst;
|
|
use crate::mir::join_ir::lowering::loop_body_local_env::LoopBodyLocalEnv;
|
|
|
|
#[test]
|
|
fn test_resolve_string_length() {
|
|
// Test: "length" → CoreMethodId::StringLength
|
|
let method_id = CoreMethodId::iter().find(|m| m.name() == "length");
|
|
assert!(method_id.is_some());
|
|
assert!(method_id.unwrap().allowed_in_condition());
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_string_length_for_condition() {
|
|
// Test: s.length() in loop condition
|
|
let recv_val = ValueId(10);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
|
|
// Phase 224-C: Use ConditionEnv
|
|
let env = ConditionEnv::new();
|
|
|
|
let result = MethodCallLowerer::lower_for_condition(
|
|
recv_val,
|
|
"length",
|
|
&[],
|
|
&mut alloc_value,
|
|
&env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
let result_val = result.unwrap();
|
|
assert_eq!(result_val, ValueId(100));
|
|
assert_eq!(instructions.len(), 1);
|
|
|
|
match &instructions[0] {
|
|
JoinInst::Compute(MirLikeInst::BoxCall {
|
|
dst,
|
|
box_name,
|
|
method,
|
|
args,
|
|
}) => {
|
|
assert_eq!(*dst, Some(ValueId(100)));
|
|
assert_eq!(box_name, "StringBox");
|
|
assert_eq!(method, "length");
|
|
assert_eq!(args.len(), 1); // Receiver is first arg
|
|
assert_eq!(args[0], ValueId(10));
|
|
}
|
|
_ => panic!("Expected BoxCall instruction"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_not_allowed_in_condition() {
|
|
// Test: s.upper() not whitelisted for conditions
|
|
let recv_val = ValueId(10);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
let env = ConditionEnv::new();
|
|
|
|
let result = MethodCallLowerer::lower_for_condition(
|
|
recv_val,
|
|
"upper",
|
|
&[],
|
|
&mut alloc_value,
|
|
&env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().contains("not allowed in loop condition"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_unknown_method() {
|
|
// Test: s.unknownMethod() not in CoreMethodId
|
|
let recv_val = ValueId(10);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
let env = ConditionEnv::new();
|
|
|
|
let result = MethodCallLowerer::lower_for_condition(
|
|
recv_val,
|
|
"unknownMethod",
|
|
&[],
|
|
&mut alloc_value,
|
|
&env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_err());
|
|
assert!(result
|
|
.unwrap_err()
|
|
.contains("not recognized as CoreMethodId"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_substring_for_init() {
|
|
// Phase 224-C: substring is not allowed in condition but IS allowed in init
|
|
let recv_val = ValueId(10);
|
|
let i_val = ValueId(11);
|
|
let j_val = ValueId(12);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
|
|
// Create ConditionEnv with i and j variables
|
|
let mut env = ConditionEnv::new();
|
|
env.insert("i".to_string(), i_val);
|
|
env.insert("j".to_string(), j_val);
|
|
|
|
// Create argument ASTs for substring(i, j)
|
|
let arg1_ast = ASTNode::Variable {
|
|
name: "i".to_string(),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
let arg2_ast = ASTNode::Variable {
|
|
name: "j".to_string(),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
|
|
// substring is NOT in condition whitelist
|
|
let cond_result = MethodCallLowerer::lower_for_condition(
|
|
recv_val,
|
|
"substring",
|
|
&[arg1_ast.clone(), arg2_ast.clone()],
|
|
&mut alloc_value,
|
|
&env,
|
|
&mut instructions,
|
|
);
|
|
assert!(cond_result.is_err());
|
|
assert!(cond_result.unwrap_err().contains("not allowed in loop condition"));
|
|
|
|
// But IS allowed in init context
|
|
// Phase 226: Create empty LoopBodyLocalEnv
|
|
let body_local_env = LoopBodyLocalEnv::new();
|
|
instructions.clear();
|
|
let init_result = MethodCallLowerer::lower_for_init(
|
|
recv_val,
|
|
"substring",
|
|
&[arg1_ast, arg2_ast],
|
|
&mut alloc_value,
|
|
&env,
|
|
&body_local_env,
|
|
&mut instructions,
|
|
);
|
|
assert!(init_result.is_ok());
|
|
assert_eq!(instructions.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn test_phase224c_arity_mismatch() {
|
|
// Phase 224-C Test: Arity mismatch error
|
|
let recv_val = ValueId(10);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
let env = ConditionEnv::new();
|
|
|
|
// Create dummy argument
|
|
let dummy_arg = ASTNode::Literal {
|
|
value: crate::ast::LiteralValue::Integer(1),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
|
|
let result = MethodCallLowerer::lower_for_condition(
|
|
recv_val,
|
|
"length",
|
|
&[dummy_arg],
|
|
&mut alloc_value,
|
|
&env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().contains("Arity mismatch"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_index_of_with_arg() {
|
|
// Phase 226 Test: s.indexOf(ch) with 1 argument (cascading support)
|
|
let recv_val = ValueId(10);
|
|
let ch_val = ValueId(11);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
|
|
// Phase 226: Create empty LoopBodyLocalEnv
|
|
let body_local_env = LoopBodyLocalEnv::new();
|
|
|
|
// Create ConditionEnv with ch variable
|
|
let mut env = ConditionEnv::new();
|
|
env.insert("ch".to_string(), ch_val);
|
|
|
|
// Create argument AST
|
|
let arg_ast = ASTNode::Variable {
|
|
name: "ch".to_string(),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
|
|
let result = MethodCallLowerer::lower_for_init(
|
|
recv_val,
|
|
"indexOf",
|
|
&[arg_ast],
|
|
&mut alloc_value,
|
|
&env,
|
|
&body_local_env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
let result_val = result.unwrap();
|
|
assert_eq!(result_val, ValueId(100));
|
|
assert_eq!(instructions.len(), 1);
|
|
|
|
match &instructions[0] {
|
|
JoinInst::Compute(MirLikeInst::BoxCall {
|
|
dst,
|
|
box_name,
|
|
method,
|
|
args,
|
|
}) => {
|
|
assert_eq!(*dst, Some(ValueId(100)));
|
|
assert_eq!(box_name, "StringBox");
|
|
assert_eq!(method, "indexOf");
|
|
assert_eq!(args.len(), 2); // Receiver + 1 arg
|
|
assert_eq!(args[0], ValueId(10)); // Receiver
|
|
assert_eq!(args[1], ValueId(11)); // ch argument
|
|
}
|
|
_ => panic!("Expected BoxCall instruction"),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_lower_substring_with_args() {
|
|
// Phase 226 Test: s.substring(i, j) with 2 arguments (cascading support)
|
|
let recv_val = ValueId(10);
|
|
let i_val = ValueId(11);
|
|
let j_val = ValueId(12);
|
|
let mut value_counter = 100u32;
|
|
let mut alloc_value = || {
|
|
let id = ValueId(value_counter);
|
|
value_counter += 1;
|
|
id
|
|
};
|
|
let mut instructions = Vec::new();
|
|
|
|
// Phase 226: Create empty LoopBodyLocalEnv
|
|
let body_local_env = LoopBodyLocalEnv::new();
|
|
|
|
// Create ConditionEnv with i and j variables
|
|
let mut env = ConditionEnv::new();
|
|
env.insert("i".to_string(), i_val);
|
|
env.insert("j".to_string(), j_val);
|
|
|
|
// Create argument ASTs
|
|
let arg1_ast = ASTNode::Variable {
|
|
name: "i".to_string(),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
let arg2_ast = ASTNode::Variable {
|
|
name: "j".to_string(),
|
|
span: crate::ast::Span::unknown(),
|
|
};
|
|
|
|
let result = MethodCallLowerer::lower_for_init(
|
|
recv_val,
|
|
"substring",
|
|
&[arg1_ast, arg2_ast],
|
|
&mut alloc_value,
|
|
&env,
|
|
&body_local_env,
|
|
&mut instructions,
|
|
);
|
|
|
|
assert!(result.is_ok());
|
|
let result_val = result.unwrap();
|
|
assert_eq!(result_val, ValueId(100));
|
|
assert_eq!(instructions.len(), 1);
|
|
|
|
match &instructions[0] {
|
|
JoinInst::Compute(MirLikeInst::BoxCall {
|
|
dst,
|
|
box_name,
|
|
method,
|
|
args,
|
|
}) => {
|
|
assert_eq!(*dst, Some(ValueId(100)));
|
|
assert_eq!(box_name, "StringBox");
|
|
assert_eq!(method, "substring");
|
|
assert_eq!(args.len(), 3); // Receiver + 2 args
|
|
assert_eq!(args[0], ValueId(10)); // Receiver
|
|
assert_eq!(args[1], ValueId(11)); // i argument
|
|
assert_eq!(args[2], ValueId(12)); // j argument
|
|
}
|
|
_ => panic!("Expected BoxCall instruction"),
|
|
}
|
|
}
|
|
}
|