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:
@ -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