Files
hakorune/src/box_factory/mod.rs
tomoaki 80f3403049 refactor(config): Phase 286A/B/287 - Config system consolidation
Phase 286A: Macro environment variable consolidation
- src/config/env/macro_flags.rs: NYASH_MACRO_* flags centralized
- Removed duplicate ny_compiler_* functions (keep in selfhost_flags.rs)
- Fixed deprecation warning logic (return None when env var not set)
- Updated callers: src/macro/{ctx,engine,macro_box,macro_box_ny,mod}.rs

Phase 286B: Box Factory environment variable consolidation
- src/config/env/box_factory_flags.rs: NYASH_BOX_FACTORY_* flags centralized
- Updated callers: src/box_factory/mod.rs, src/runtime/plugin_loader*.rs

Phase 287: Config Catalog implementation
- src/config/env/catalog.rs: New catalog for all config modules

Fixes:
- Type mismatches: Added .unwrap_or(false) for Option<bool> returns (7 locations)
- Deprecation warnings: Fixed macro_toplevel_allow() & macro_box_child_runner() logic
- Module organization: Added module declarations in src/config/env.rs

Note: Files force-added with git add -f due to .gitignore env/ pattern

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 08:43:48 +09:00

728 lines
26 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*!
* Unified Box Factory Architecture
*
* Phase 9.78: 統合BoxFactoryアーキテクチャ
* すべてのBox生成ビルトイン、ユーザー定義、プラグインを統一的に扱う
*
* Design principles:
* - "Everything is Box" 哲学の実装レベルでの体現
* - birth/finiライフサイクルの明確な責務分離
* - 保守性と拡張性の劇的向上
*/
use crate::box_trait::NyashBox;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
/// Factory Priority Policy for Box creation (Phase 15.5 "Everything is Plugin")
///
/// Determines the order in which different Box factories are consulted
/// during Box creation to solve the StringBox/IntegerBox plugin priority issue.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactoryPolicy {
/// Strict Plugin Priority: plugins > user > builtin
/// ⚡ SOLVES THE CORE PROBLEM: Plugins have highest priority
/// Use when plugins should completely replace builtins (Phase 15.5)
StrictPluginFirst,
/// Compatible Plugin Priority: plugins > builtin > user
/// 🔧 Compatibility mode: Plugins first, but builtins before user-defined
/// Use for gradual migration scenarios
CompatPluginFirst,
/// Legacy Builtin Priority: builtin > user > plugin (CURRENT DEFAULT)
/// ⚠️ PROBLEMATIC: Plugins can never override builtins
/// Only use for compatibility with existing setups
BuiltinFirst,
}
/// Factory type classification for policy-based ordering
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FactoryType {
/// Built-in factory (StringBox, IntegerBox, etc.)
Builtin,
/// User-defined Box factory
User,
/// Plugin-provided Box factory
Plugin,
}
/// Runtime error types for Box operations
#[derive(Debug, thiserror::Error)]
pub enum RuntimeError {
#[error("invalid operation: {message}")]
InvalidOperation { message: String },
#[error("type error: {message}")]
TypeError { message: String },
}
/// Shared state for interpreter context (legacy compatibility)
#[derive(Debug, Default, Clone)]
pub struct SharedState;
impl SharedState {
pub fn new() -> Self {
Self
}
}
/// Unified interface for all Box creation
pub trait BoxFactory: Send + Sync {
/// Create a new Box instance with given arguments
fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError>;
/// Check if this factory is currently available
fn is_available(&self) -> bool {
true
}
/// Get list of Box types this factory can create
fn box_types(&self) -> Vec<&str>;
/// Check if this factory supports birth/fini lifecycle
fn supports_birth(&self) -> bool {
true
}
/// Identify builtin factory to enforce reserved-name protections
fn is_builtin_factory(&self) -> bool {
false
}
/// Identify factory type for policy-based priority ordering
fn factory_type(&self) -> FactoryType {
if self.is_builtin_factory() {
FactoryType::Builtin
} else {
FactoryType::Plugin // Default assumption for external factories
}
}
}
/// Registry that manages all BoxFactory implementations
pub struct UnifiedBoxRegistry {
/// Ordered list of factories with policy-based priority
pub factories: Vec<Arc<dyn BoxFactory>>,
/// Quick lookup cache for performance
type_cache: RwLock<HashMap<String, usize>>, // maps type name to factory index
/// Factory priority policy (Phase 15.5: Everything is Plugin)
policy: FactoryPolicy,
}
impl UnifiedBoxRegistry {
/// Create a new empty registry with default policy
/// Phase 86: Default changed to StrictPluginFirst via with_env_policy()
pub fn new() -> Self {
Self::with_env_policy()
}
/// Create a new empty registry with specified policy
pub fn with_policy(policy: FactoryPolicy) -> Self {
Self {
factories: Vec::new(),
type_cache: RwLock::new(HashMap::new()),
policy,
}
}
/// Create registry with policy from environment variable (Phase 15.5 setup)
pub fn with_env_policy() -> Self {
let policy = match crate::config::env::box_factory_policy().as_deref() {
Some("compat_plugin_first") => FactoryPolicy::CompatPluginFirst,
Some("builtin_first") => FactoryPolicy::BuiltinFirst,
Some("strict_plugin_first") | _ => FactoryPolicy::StrictPluginFirst, // Phase 15.5: Plugin First DEFAULT!
};
eprintln!(
"[UnifiedBoxRegistry] 🎯 Factory Policy: {:?} (Phase 15.5: Everything is Plugin!)",
policy
);
Self::with_policy(policy)
}
/// Get current factory policy
pub fn get_policy(&self) -> FactoryPolicy {
self.policy
}
/// Set factory policy and rebuild cache to reflect new priorities
pub fn set_policy(&mut self, policy: FactoryPolicy) {
if self.policy != policy {
self.policy = policy;
self.rebuild_cache();
}
}
/// Rebuild type cache based on current policy
fn rebuild_cache(&mut self) {
// Clear existing cache
let mut cache = self.type_cache.write().unwrap();
cache.clear();
// Get factory priority order based on policy
let factory_order = self.get_factory_order_by_policy();
// Re-register types with policy-based priority
for &factory_index in factory_order.iter() {
if let Some(factory) = self.factories.get(factory_index) {
let types = factory.box_types();
// Phase 87: Reserved core types using CoreBoxId (型安全化)
fn is_reserved_type(name: &str) -> bool {
use crate::runtime::CoreBoxId;
// Phase 15.5: 環境変数でプラグイン優先モード時は保護解除
if crate::config::env::use_plugin_builtins() {
if let Some(types) = crate::config::env::plugin_override_types() {
if types.iter().any(|t| t == name) {
return false; // 予約型として扱わない
}
}
}
// Phase 87: CoreBoxId による型安全な判定
// core_required (6個) + 特殊型の一部を予約型として保護
CoreBoxId::from_name(name)
.map(|id| {
id.is_core_required()
|| matches!(id, CoreBoxId::Result | CoreBoxId::Method)
})
.unwrap_or(false)
}
for type_name in types {
// Enforce reserved names: only builtin factory may claim them
if is_reserved_type(type_name) && !factory.is_builtin_factory() {
eprintln!(
"[UnifiedBoxRegistry] ❌ Rejecting registration of reserved type '{}' by non-builtin factory #{}",
type_name, factory_index
);
continue;
}
// Policy-based priority: first in order wins
let entry = cache.entry(type_name.to_string());
use std::collections::hash_map::Entry;
match entry {
Entry::Occupied(existing) => {
// Collision: type already claimed by higher-priority factory
eprintln!("[UnifiedBoxRegistry] ⚠️ Policy '{}': type '{}' kept by higher priority factory #{}, ignoring factory #{}",
format!("{:?}", self.policy), existing.key(), existing.get(), factory_index);
}
Entry::Vacant(v) => {
v.insert(factory_index);
}
}
}
}
}
}
/// Get factory indices ordered by current policy priority
fn get_factory_order_by_policy(&self) -> Vec<usize> {
let mut factory_indices: Vec<usize> = (0..self.factories.len()).collect();
// Sort by factory type according to policy
factory_indices.sort_by_key(|&index| {
if let Some(factory) = self.factories.get(index) {
let factory_type = factory.factory_type();
match self.policy {
FactoryPolicy::StrictPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::User => 1, // Medium priority
FactoryType::Builtin => 2, // Lowest priority
},
FactoryPolicy::CompatPluginFirst => match factory_type {
FactoryType::Plugin => 0, // Highest priority
FactoryType::Builtin => 1, // Medium priority
FactoryType::User => 2, // Lowest priority
},
FactoryPolicy::BuiltinFirst => match factory_type {
FactoryType::Builtin => 0, // Highest priority (current default)
FactoryType::User => 1, // Medium priority
FactoryType::Plugin => 2, // Lowest priority
},
}
} else {
999 // Invalid factory index, put at end
}
});
factory_indices
}
/// Register a new factory (policy-aware)
pub fn register(&mut self, factory: Arc<dyn BoxFactory>) {
// Simply add to the factory list
self.factories.push(factory);
// Rebuild cache to apply policy-based priority ordering
// This ensures new factory is properly integrated with current policy
self.rebuild_cache();
}
/// Create a Box using the unified interface
pub fn create_box(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
// Prefer plugin-builtins when enabled and provider is available in v2 registry
// BUT: Skip if plugins are explicitly disabled
let plugins_disabled = crate::config::env::disable_plugins();
if !plugins_disabled && crate::config::env::use_plugin_builtins() {
use crate::runtime::{get_global_registry, BoxProvider};
// Allowlist types for override: env NYASH_PLUGIN_OVERRIDE_TYPES="ArrayBox,MapBox" (default: none)
let allow: Vec<String> = crate::config::env::plugin_override_types()
.unwrap_or_default();
if allow.iter().any(|t| t == name) {
let v2 = get_global_registry();
if let Some(provider) = v2.get_provider(name) {
if let BoxProvider::Plugin(_lib) = provider {
return v2.create_box(name, args).map_err(|e| {
RuntimeError::InvalidOperation {
message: format!("Plugin Box creation failed: {}", e),
}
});
}
}
}
}
// Check cache first
let cache = self.type_cache.read().unwrap();
if let Some(&factory_index) = cache.get(name) {
if let Some(factory) = self.factories.get(factory_index) {
if factory.is_available() {
return factory.create_box(name, args);
}
}
}
drop(cache);
// Linear search through all factories with fallback support
let mut last_error: Option<RuntimeError> = None;
let factory_order = self.get_factory_order_by_policy();
for &factory_index in factory_order.iter() {
if let Some(factory) = self.factories.get(factory_index) {
if !factory.is_available() {
continue;
}
// For factories that advertise types, check if they support this type
let box_types = factory.box_types();
if !box_types.is_empty() && !box_types.contains(&name) {
continue;
}
// Try to create the box
if crate::config::env::debug_plugin() {
eprintln!(
"[UnifiedBoxRegistry] try factory#{} {:?} for {}",
factory_index,
factory.factory_type(),
name
);
}
match factory.create_box(name, args) {
Ok(boxed) => return Ok(boxed),
Err(e) => {
// FileBox special case: handle fallback based on mode
if name == "FileBox" {
if let Some(fallback_result) = self.try_filebox_fallback(name, args, &e)
{
return fallback_result;
}
}
// For other boxes or if FileBox fallback not applicable, continue
last_error = Some(e);
continue;
}
}
}
}
// Final fallback: if v2 plugin registry has a provider for this name, try it once
{
let v2 = crate::runtime::get_global_registry();
if let Some(_prov) = v2.get_provider(name) {
if let Ok(b) = v2.create_box(name, args) {
return Ok(b);
}
}
}
// Return the last error if we have one, otherwise generic error
if let Some(err) = last_error {
Err(err)
} else {
Err(RuntimeError::InvalidOperation {
message: format!("Unknown Box type: {}", name),
})
}
}
/// Try FileBox fallback based on NYASH_FILEBOX_MODE
/// Returns Some(result) if fallback is applicable, None if should continue trying other factories
fn try_filebox_fallback(
&self,
name: &str,
args: &[Box<dyn NyashBox>],
original_error: &RuntimeError,
) -> Option<Result<Box<dyn NyashBox>, RuntimeError>> {
use crate::runner::modes::common_util::provider_registry;
let mode = provider_registry::read_filebox_mode_from_env();
match mode {
provider_registry::FileBoxMode::PluginOnly => {
// Fail-Fast: return the original error immediately
eprintln!(
"[FileBox] Plugin creation failed in plugin-only mode: {}",
original_error
);
Some(Err(RuntimeError::InvalidOperation {
message: format!(
"FileBox plugin creation failed (plugin-only mode): {}",
original_error
),
}))
}
provider_registry::FileBoxMode::Auto => {
// Auto mode: try fallback to builtin/core-ro
eprintln!(
"[FileBox] Plugin creation failed, falling back to builtin/core-ro: {}",
original_error
);
// Try builtin factory if available
for factory in &self.factories {
if factory.is_builtin_factory() && factory.box_types().contains(&name) {
match factory.create_box(name, args) {
Ok(boxed) => {
eprintln!("[FileBox] Successfully created with builtin factory");
return Some(Ok(boxed));
}
Err(e) => {
eprintln!("[FileBox] Builtin factory also failed: {}", e);
}
}
}
}
// If builtin failed, return None to continue with other factories
None
}
provider_registry::FileBoxMode::CoreRo => {
// Core-ro mode: try builtin factory
eprintln!("[FileBox] Using core-ro mode, trying builtin factory");
for factory in &self.factories {
if factory.is_builtin_factory() && factory.box_types().contains(&name) {
match factory.create_box(name, args) {
Ok(boxed) => return Some(Ok(boxed)),
Err(e) => {
return Some(Err(RuntimeError::InvalidOperation {
message: format!("FileBox core-ro creation failed: {}", e),
}));
}
}
}
}
None
}
}
}
/// Check whether a type name is known to the registry
pub fn has_type(&self, name: &str) -> bool {
// Check cache first
{
let cache = self.type_cache.read().unwrap();
if let Some(&idx) = cache.get(name) {
if let Some(factory) = self.factories.get(idx) {
if factory.is_available() {
return true;
}
}
}
}
// Fallback: scan factories that can enumerate types
for factory in &self.factories {
if !factory.is_available() {
continue;
}
let types = factory.box_types();
if !types.is_empty() && types.contains(&name) {
return true;
}
}
false
}
/// Get all available Box types
pub fn available_types(&self) -> Vec<String> {
let mut types = Vec::new();
for factory in &self.factories {
if factory.is_available() {
for type_name in factory.box_types() {
types.push(type_name.to_string());
}
}
}
types.sort();
types.dedup();
types
}
}
pub mod builtin;
pub mod plugin;
/// Re-export submodules
#[cfg(feature = "interpreter-legacy")]
pub mod user_defined;
// Phase 15.5: Separated builtin implementations for easy deletion
pub mod builtin_impls;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_creation() {
let registry = UnifiedBoxRegistry::new();
assert_eq!(registry.available_types().len(), 0);
}
// Phase 86: BoxFactory Priority Tests
#[test]
fn test_default_policy_is_strict_plugin_first() {
let prev = crate::config::env::box_factory_policy();
// Ensure NYASH_BOX_FACTORY_POLICY is not set
crate::config::env::reset_box_factory_policy();
let registry = UnifiedBoxRegistry::new();
assert_eq!(
registry.get_policy(),
FactoryPolicy::StrictPluginFirst,
"Default policy should be StrictPluginFirst"
);
if let Some(v) = prev {
crate::config::env::set_box_factory_policy(&v);
}
}
#[test]
fn test_env_policy_override() {
let prev = crate::config::env::box_factory_policy();
// Test builtin_first override
crate::config::env::set_box_factory_policy("builtin_first");
let registry = UnifiedBoxRegistry::with_env_policy();
assert_eq!(registry.get_policy(), FactoryPolicy::BuiltinFirst);
// Test compat_plugin_first override
crate::config::env::set_box_factory_policy("compat_plugin_first");
let registry = UnifiedBoxRegistry::with_env_policy();
assert_eq!(registry.get_policy(), FactoryPolicy::CompatPluginFirst);
// Test strict_plugin_first explicit
crate::config::env::set_box_factory_policy("strict_plugin_first");
let registry = UnifiedBoxRegistry::with_env_policy();
assert_eq!(registry.get_policy(), FactoryPolicy::StrictPluginFirst);
// Cleanup
if let Some(v) = prev {
crate::config::env::set_box_factory_policy(&v);
} else {
crate::config::env::reset_box_factory_policy();
}
}
#[test]
fn test_reserved_type_protection() {
// Ensure env vars are cleared
// Note: can't clear env vars directly; tests rely on default behavior
// Create a mock non-builtin factory that claims a reserved type
struct MockPluginFactory;
impl BoxFactory for MockPluginFactory {
fn create_box(
&self,
name: &str,
_args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
// This should never be called for StringBox since it's rejected
Err(RuntimeError::InvalidOperation {
message: format!("Mock factory attempted to create: {}", name),
})
}
fn box_types(&self) -> Vec<&str> {
vec!["StringBox", "CustomBox"] // Claims a reserved type
}
fn is_builtin_factory(&self) -> bool {
false // Non-builtin
}
fn factory_type(&self) -> FactoryType {
FactoryType::Plugin
}
}
let mut registry = UnifiedBoxRegistry::new();
registry.register(Arc::new(MockPluginFactory));
// Test that create_box fails for StringBox (not registered in cache)
let result = registry.create_box("StringBox", &[]);
assert!(
result.is_err(),
"StringBox creation should fail when only non-builtin factory provides it"
);
// Verify the error message indicates it's unknown (not in cache)
if let Err(e) = result {
let err_msg = format!("{}", e);
assert!(
err_msg.contains("Unknown Box type") || err_msg.contains("Mock factory"),
"Error message should indicate StringBox is not properly registered: {}",
err_msg
);
}
}
#[test]
fn test_plugin_override_with_env() {
// This test verifies that NYASH_USE_PLUGIN_BUILTINS or
// NYASH_PLUGIN_OVERRIDE_TYPES allows plugins to override reserved types
// Create a mock plugin factory
struct MockPluginFactory;
impl BoxFactory for MockPluginFactory {
fn create_box(
&self,
name: &str,
_args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
if name == "StringBox" {
// Return a mock box for testing
Err(RuntimeError::InvalidOperation {
message: "Mock plugin StringBox".to_string(),
})
} else {
Err(RuntimeError::InvalidOperation {
message: "Unknown".to_string(),
})
}
}
fn box_types(&self) -> Vec<&str> {
vec!["StringBox"]
}
fn is_builtin_factory(&self) -> bool {
false
}
fn factory_type(&self) -> FactoryType {
FactoryType::Plugin
}
}
// Test with NYASH_PLUGIN_OVERRIDE_TYPES
crate::config::env::set_box_factory_policy("strict_plugin_first"); // ensure plugin first
std::env::set_var("NYASH_PLUGIN_OVERRIDE_TYPES", "StringBox");
let mut registry = UnifiedBoxRegistry::new();
registry.register(Arc::new(MockPluginFactory));
// With override enabled, StringBox should not be rejected
// (Note: has_type will be false because create_box fails, but registration shouldn't be rejected)
std::env::remove_var("NYASH_PLUGIN_OVERRIDE_TYPES");
}
#[test]
fn test_non_reserved_plugin_priority() {
// Test that non-reserved types (like FileBox) can be overridden by plugins
struct MockBuiltinFactory;
impl BoxFactory for MockBuiltinFactory {
fn create_box(
&self,
_name: &str,
_args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
Err(RuntimeError::InvalidOperation {
message: "Builtin FileBox".to_string(),
})
}
fn box_types(&self) -> Vec<&str> {
vec!["FileBox"]
}
fn is_builtin_factory(&self) -> bool {
true
}
fn factory_type(&self) -> FactoryType {
FactoryType::Builtin
}
}
struct MockPluginFactory;
impl BoxFactory for MockPluginFactory {
fn create_box(
&self,
_name: &str,
_args: &[Box<dyn NyashBox>],
) -> Result<Box<dyn NyashBox>, RuntimeError> {
Err(RuntimeError::InvalidOperation {
message: "Plugin FileBox".to_string(),
})
}
fn box_types(&self) -> Vec<&str> {
vec!["FileBox"]
}
fn is_builtin_factory(&self) -> bool {
false
}
fn factory_type(&self) -> FactoryType {
FactoryType::Plugin
}
}
let mut registry = UnifiedBoxRegistry::new();
// Register builtin first, then plugin
registry.register(Arc::new(MockBuiltinFactory));
registry.register(Arc::new(MockPluginFactory));
// With StrictPluginFirst policy, plugin should have priority
// Both fail, but the error message tells us which was tried first
let result = registry.create_box("FileBox", &[]);
assert!(result.is_err());
// The error should be from plugin (tried first) or builtin (fallback)
// This test just verifies the mechanism works
}
}