feat(joinir): Phase 224-B - MethodCallLowerer + CoreMethodId extension
- Extend CoreMethodId with is_pure(), allowed_in_condition(), allowed_in_init() - New MethodCallLowerer box for metadata-driven MethodCall lowering - Integrate MethodCall handling in condition_lowerer - P0: Zero-argument methods (length) supported - Design principle: NO method name hardcoding, CoreMethodId metadata only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -17,6 +17,7 @@ use crate::mir::join_ir::{BinOpKind, CompareOp, ConstValue, JoinInst, MirLikeIns
|
||||
use crate::mir::ValueId;
|
||||
|
||||
use super::condition_env::ConditionEnv;
|
||||
use super::method_call_lowerer::MethodCallLowerer;
|
||||
|
||||
/// Lower an AST condition to JoinIR instructions
|
||||
///
|
||||
@ -305,6 +306,20 @@ where
|
||||
..
|
||||
} => lower_arithmetic_binop(operator, left, right, alloc_value, env, instructions),
|
||||
|
||||
// Phase 224-B: MethodCall support (e.g., s.length())
|
||||
ASTNode::MethodCall {
|
||||
object,
|
||||
method,
|
||||
arguments,
|
||||
..
|
||||
} => {
|
||||
// 1. Lower receiver (object) to ValueId
|
||||
let recv_val = lower_value_expression(object, alloc_value, env, instructions)?;
|
||||
|
||||
// 2. Lower method call using MethodCallLowerer
|
||||
MethodCallLowerer::lower_for_condition(recv_val, method, arguments, alloc_value, instructions)
|
||||
}
|
||||
|
||||
_ => Err(format!(
|
||||
"Unsupported expression in value context: {:?}",
|
||||
expr
|
||||
|
||||
363
src/mir/join_ir/lowering/method_call_lowerer.rs
Normal file
363
src/mir/join_ir/lowering/method_call_lowerer.rs
Normal file
@ -0,0 +1,363 @@
|
||||
//! 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;
|
||||
|
||||
/// 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-B P0 Restrictions
|
||||
///
|
||||
/// - Only zero-argument methods supported (e.g., `s.length()`)
|
||||
/// - Only whitelisted methods (StringLength, ArrayLength, etc.)
|
||||
/// - Arguments must be empty slice
|
||||
///
|
||||
/// # 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,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String>
|
||||
where
|
||||
F: FnMut() -> ValueId,
|
||||
{
|
||||
// Phase 224-B P0: Only zero-argument methods
|
||||
if !args.is_empty() {
|
||||
return Err(format!(
|
||||
"Phase 224-B P0: MethodCall with arguments not supported yet: {}.{}({} args)",
|
||||
recv_val.0,
|
||||
method_name,
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
|
||||
// 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
|
||||
));
|
||||
}
|
||||
|
||||
// Emit BoxCall instruction
|
||||
let dst = alloc_value();
|
||||
let box_name = method_id.box_id().name().to_string();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_name,
|
||||
method: method_name.to_string(),
|
||||
args: vec![recv_val], // First arg is the receiver
|
||||
}));
|
||||
|
||||
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-B P0 Restrictions
|
||||
///
|
||||
/// - Only zero-argument methods supported
|
||||
/// - Arguments will be supported in Phase 224-C
|
||||
pub fn lower_for_init<F>(
|
||||
recv_val: ValueId,
|
||||
method_name: &str,
|
||||
args: &[ASTNode],
|
||||
alloc_value: &mut F,
|
||||
instructions: &mut Vec<JoinInst>,
|
||||
) -> Result<ValueId, String>
|
||||
where
|
||||
F: FnMut() -> ValueId,
|
||||
{
|
||||
// Phase 224-B P0: Only zero-argument methods
|
||||
if !args.is_empty() {
|
||||
return Err(format!(
|
||||
"Phase 224-B P0: MethodCall with arguments not supported yet: {}.{}({} args)",
|
||||
recv_val.0,
|
||||
method_name,
|
||||
args.len()
|
||||
));
|
||||
}
|
||||
|
||||
// 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
|
||||
));
|
||||
}
|
||||
|
||||
// Emit BoxCall instruction
|
||||
let dst = alloc_value();
|
||||
let box_name = method_id.box_id().name().to_string();
|
||||
instructions.push(JoinInst::Compute(MirLikeInst::BoxCall {
|
||||
dst: Some(dst),
|
||||
box_name,
|
||||
method: method_name.to_string(),
|
||||
args: vec![recv_val], // First arg is the receiver
|
||||
}));
|
||||
|
||||
Ok(dst)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::join_ir::JoinInst;
|
||||
|
||||
#[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();
|
||||
|
||||
let result = MethodCallLowerer::lower_for_condition(
|
||||
recv_val,
|
||||
"length",
|
||||
&[],
|
||||
&mut alloc_value,
|
||||
&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 result = MethodCallLowerer::lower_for_condition(
|
||||
recv_val,
|
||||
"upper",
|
||||
&[],
|
||||
&mut alloc_value,
|
||||
&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 result = MethodCallLowerer::lower_for_condition(
|
||||
recv_val,
|
||||
"unknownMethod",
|
||||
&[],
|
||||
&mut alloc_value,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result
|
||||
.unwrap_err()
|
||||
.contains("not recognized as CoreMethodId"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lower_substring_for_init() {
|
||||
// Test: s.substring() in LoopBodyLocal init (allowed in init, not 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();
|
||||
|
||||
// substring is allowed in init but NOT in condition
|
||||
let cond_result = MethodCallLowerer::lower_for_condition(
|
||||
recv_val,
|
||||
"substring",
|
||||
&[],
|
||||
&mut alloc_value,
|
||||
&mut instructions,
|
||||
);
|
||||
assert!(cond_result.is_err());
|
||||
|
||||
// But allowed in init context
|
||||
instructions.clear();
|
||||
let init_result = MethodCallLowerer::lower_for_init(
|
||||
recv_val,
|
||||
"substring",
|
||||
&[],
|
||||
&mut alloc_value,
|
||||
&mut instructions,
|
||||
);
|
||||
assert!(init_result.is_ok());
|
||||
assert_eq!(instructions.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phase224b_p0_no_arguments() {
|
||||
// Test: P0 restriction - no arguments supported yet
|
||||
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();
|
||||
|
||||
// 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,
|
||||
&mut instructions,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
assert!(result.unwrap_err().contains("P0: MethodCall with arguments not supported"));
|
||||
}
|
||||
}
|
||||
@ -30,6 +30,7 @@ pub mod loop_body_local_env; // Phase 184: Body-local variable environment
|
||||
pub mod loop_body_local_init; // Phase 186: Body-local init expression lowering
|
||||
pub(crate) mod condition_lowerer; // Phase 171-fix: Core condition lowering logic
|
||||
pub mod condition_to_joinir; // Phase 169: JoinIR condition lowering orchestrator (refactored)
|
||||
pub mod method_call_lowerer; // Phase 224-B: MethodCall lowering (metadata-driven)
|
||||
pub(crate) mod condition_var_extractor; // Phase 171-fix: Variable extraction from condition AST
|
||||
pub mod continue_branch_normalizer; // Phase 33-19: Continue branch normalization for Pattern B
|
||||
pub mod loop_update_analyzer; // Phase 197: Update expression analyzer for carrier semantics
|
||||
|
||||
@ -344,6 +344,122 @@ impl CoreMethodId {
|
||||
pub fn from_box_and_method(box_id: CoreBoxId, method: &str) -> Option<CoreMethodId> {
|
||||
Self::iter().find(|m| m.box_id() == box_id && m.name() == method)
|
||||
}
|
||||
|
||||
/// Phase 224-B: Pure function (no side effects, deterministic)
|
||||
///
|
||||
/// Pure functions can be safely:
|
||||
/// - Used in loop conditions
|
||||
/// - Called multiple times without changing behavior
|
||||
/// - Eliminated by dead code elimination if result unused
|
||||
///
|
||||
/// Examples:
|
||||
/// - `StringLength`: Pure - always returns same length for same string
|
||||
/// - `ArrayPush`: Not pure - mutates the array (side effect)
|
||||
pub fn is_pure(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String methods (pure - return new values, don't mutate)
|
||||
StringLength | StringUpper | StringLower |
|
||||
StringConcat | StringSubstring | StringReplace |
|
||||
StringTrim | StringSplit => true,
|
||||
|
||||
// Integer/Bool methods (pure - mathematical operations)
|
||||
IntegerAbs | IntegerMin | IntegerMax |
|
||||
BoolNot | BoolAnd | BoolOr => true,
|
||||
|
||||
// Array/Map read operations (pure - don't mutate)
|
||||
ArrayLength | ArrayGet => true,
|
||||
MapGet | MapHas => true,
|
||||
MapKeys => true,
|
||||
|
||||
// ResultBox read operations (pure)
|
||||
ResultIsOk | ResultGetValue => true,
|
||||
|
||||
// Impure - mutate state or have side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 224-B: Allowed in loop condition expressions
|
||||
///
|
||||
/// Methods allowed in loop conditions must be:
|
||||
/// 1. Pure (no side effects)
|
||||
/// 2. Cheap to compute (no expensive I/O)
|
||||
/// 3. Deterministic (same input → same output)
|
||||
///
|
||||
/// This is a whitelist approach - default to false for safety.
|
||||
pub fn allowed_in_condition(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String read operations - allowed
|
||||
StringLength => true,
|
||||
|
||||
// Array read operations - allowed
|
||||
ArrayLength | ArrayGet => true,
|
||||
|
||||
// Map read operations - allowed
|
||||
MapHas => true,
|
||||
|
||||
// ResultBox operations - allowed
|
||||
ResultIsOk => true,
|
||||
|
||||
// Not yet whitelisted - be conservative
|
||||
StringUpper | StringLower | StringConcat |
|
||||
StringSubstring | StringReplace | StringTrim | StringSplit => false,
|
||||
|
||||
IntegerAbs | IntegerMin | IntegerMax => false,
|
||||
BoolNot | BoolAnd | BoolOr => false,
|
||||
|
||||
MapGet => false,
|
||||
MapKeys => false,
|
||||
ResultGetValue => false,
|
||||
|
||||
// Obviously disallowed - side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 224-B: Allowed in loop body init expressions
|
||||
///
|
||||
/// Methods allowed for LoopBodyLocal initialization.
|
||||
/// Similar to condition requirements but slightly more permissive.
|
||||
pub fn allowed_in_init(&self) -> bool {
|
||||
use CoreMethodId::*;
|
||||
match self {
|
||||
// String operations - allowed
|
||||
StringLength | StringSubstring => true,
|
||||
|
||||
// String transformations - allowed for init
|
||||
StringUpper | StringLower | StringTrim => true,
|
||||
|
||||
// Array operations - allowed
|
||||
ArrayLength | ArrayGet => true,
|
||||
|
||||
// Map operations - allowed
|
||||
MapGet | MapHas | MapKeys => true,
|
||||
|
||||
// ResultBox operations - allowed
|
||||
ResultIsOk | ResultGetValue => true,
|
||||
|
||||
// String operations that create new strings - allowed
|
||||
StringConcat | StringReplace | StringSplit => true,
|
||||
|
||||
// Math operations - allowed
|
||||
IntegerAbs | IntegerMin | IntegerMax => true,
|
||||
|
||||
// Not allowed - side effects
|
||||
ArrayPush | ArrayPop | MapSet => false,
|
||||
ConsolePrintln | ConsoleLog | ConsoleError => false,
|
||||
FileRead | FileWrite | FileOpen => false,
|
||||
|
||||
// Bool operations - technically pure but unusual in init
|
||||
BoolNot | BoolAnd | BoolOr => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -474,4 +590,55 @@ mod tests {
|
||||
let count = CoreMethodId::iter().count();
|
||||
assert!(count >= 27); // Phase 87: 27個以上のメソッド
|
||||
}
|
||||
|
||||
// ===== Phase 224-B tests =====
|
||||
|
||||
#[test]
|
||||
fn test_core_method_id_is_pure() {
|
||||
// Pure string methods
|
||||
assert!(CoreMethodId::StringLength.is_pure());
|
||||
assert!(CoreMethodId::StringUpper.is_pure());
|
||||
assert!(CoreMethodId::StringSubstring.is_pure());
|
||||
|
||||
// Pure array read methods
|
||||
assert!(CoreMethodId::ArrayLength.is_pure());
|
||||
assert!(CoreMethodId::ArrayGet.is_pure());
|
||||
|
||||
// Impure - side effects
|
||||
assert!(!CoreMethodId::ArrayPush.is_pure());
|
||||
assert!(!CoreMethodId::ConsolePrintln.is_pure());
|
||||
assert!(!CoreMethodId::FileWrite.is_pure());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_method_id_allowed_in_condition() {
|
||||
// Allowed - cheap and pure
|
||||
assert!(CoreMethodId::StringLength.allowed_in_condition());
|
||||
assert!(CoreMethodId::ArrayLength.allowed_in_condition());
|
||||
assert!(CoreMethodId::MapHas.allowed_in_condition());
|
||||
|
||||
// Not allowed - not whitelisted (conservative)
|
||||
assert!(!CoreMethodId::StringUpper.allowed_in_condition());
|
||||
assert!(!CoreMethodId::StringSubstring.allowed_in_condition());
|
||||
|
||||
// Not allowed - side effects
|
||||
assert!(!CoreMethodId::ArrayPush.allowed_in_condition());
|
||||
assert!(!CoreMethodId::ConsolePrintln.allowed_in_condition());
|
||||
assert!(!CoreMethodId::FileRead.allowed_in_condition());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_core_method_id_allowed_in_init() {
|
||||
// Allowed - useful for LoopBodyLocal init
|
||||
assert!(CoreMethodId::StringLength.allowed_in_init());
|
||||
assert!(CoreMethodId::StringSubstring.allowed_in_init());
|
||||
assert!(CoreMethodId::StringUpper.allowed_in_init());
|
||||
assert!(CoreMethodId::ArrayGet.allowed_in_init());
|
||||
assert!(CoreMethodId::MapGet.allowed_in_init());
|
||||
|
||||
// Not allowed - side effects
|
||||
assert!(!CoreMethodId::ArrayPush.allowed_in_init());
|
||||
assert!(!CoreMethodId::ConsolePrintln.allowed_in_init());
|
||||
assert!(!CoreMethodId::FileWrite.allowed_in_init());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user