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:
nyash-codex
2025-12-10 17:59:24 +09:00
parent 4cddca1cae
commit 250555bfc0
8 changed files with 881 additions and 0 deletions

View File

@ -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());
}
}