Merge pull request #75 from moe-charm/copilot/fix-74

🔧 Fix SocketBox state preservation in "Everything is Box" design through Arc reference sharing
This commit is contained in:
moe-charm
2025-08-14 20:05:03 +09:00
committed by GitHub
15 changed files with 407 additions and 59 deletions

99
SOCKETBOX_FIX_SUMMARY.md Normal file
View File

@ -0,0 +1,99 @@
# SocketBox State Preservation Fix - Implementation Summary
## Problem Description
In the "Everything is Box" design, stateful boxes like SocketBox were losing their state across field accesses due to `clone_box()` creating new instances instead of sharing references.
**Original Issue**:
```nyash
me.server.bind("127.0.0.1", 8080) // ✅ SocketBox ID=10, is_server=true
me.server.isServer() // ❌ SocketBox ID=19, is_server=false (別インスタンス!)
```
## Root Cause Analysis
The issue was in three key locations where `clone_box()` was called:
1. `src/interpreter/core.rs:366` - `resolve_variable()`
2. `src/instance.rs:275` - `get_field()`
3. `src/interpreter/expressions.rs:779` - `execute_field_access()`
Each access to `me.server` was creating a new SocketBox instance via `clone_box()`, so state changes made to one instance weren't visible in subsequent accesses.
## Solution Implemented
### Phase 1: Type Infrastructure
- Added `SharedNyashBox = Arc<dyn NyashBox>` type alias
- Added `clone_arc()` method to NyashBox trait for Arc conversion
### Phase 2: Data Structure Updates
- Updated `InstanceBox.fields` from `HashMap<String, Box<dyn NyashBox>>` to `HashMap<String, SharedNyashBox>`
- Updated `NyashInterpreter.local_vars` and `outbox_vars` to use `SharedNyashBox`
- Modified save/restore methods to convert between Arc and Box appropriately
### Phase 3: Core Reference Sharing
- **`resolve_variable()`**: Now returns `SharedNyashBox` and uses `Arc::clone()` instead of `clone_box()`
- **`get_field()`**: Now returns `SharedNyashBox` and uses `Arc::clone()` instead of `clone_box()`
- **`execute_field_access()`**: Now returns `SharedNyashBox` to preserve sharing
### Phase 4: Target Fix - SocketBox Clone Implementation
**Key Innovation**: Modified SocketBox Clone to share mutable state:
```rust
// Before (problematic):
is_server: Arc::new(Mutex::new(current_is_server)), // New state container
is_connected: Arc::new(Mutex::new(current_is_connected)), // New state container
// After (fixed):
is_server: Arc::clone(&self.is_server), // Share the same state container
is_connected: Arc::clone(&self.is_connected), // Share the same state container
```
This ensures that even when SocketBox instances are cloned, they share the same underlying state containers.
### Phase 5: Interface Compatibility
- Fixed all callers of `resolve_variable()` to handle `SharedNyashBox` return type
- Updated method calls and field access to properly dereference Arc references
- Maintained Box-based external interfaces while using Arc internally
## How the Fix Works
1. **Storage**: Variables and fields are stored as `Arc<dyn NyashBox>` internally
2. **Access**: Field access returns `Arc::clone()` of the stored reference
3. **Cloning**: When Arc is converted to Box via `clone_box()`, SocketBox creates a new instance BUT shares the same state containers
4. **State Sharing**: All clones of the same original SocketBox share `is_server` and `is_connected` state
**Result**:
```nyash
me.server.bind("127.0.0.1", 8080) // ✅ SocketBox clone A, is_server=true (shared state)
me.server.isServer() // ✅ SocketBox clone B, is_server=true (same shared state!)
```
## Testing
### Test Files Created:
- `test_final_validation.nyash` - Replicates the exact issue scenario
- `test_complete_socketbox_fix.nyash` - Comprehensive SocketBox testing
- `test_multiple_stateful_boxes.nyash` - Tests multiple stateful box types
- `test_arc_fix.nyash` - Original issue test case
### Expected Results:
All tests should show that `isServer()` returns `true` after `bind()` is called, confirming that state is preserved across field accesses.
## Impact on Other Stateful Boxes
The fix benefits all stateful boxes:
- **SocketBox**: Fixed with custom Clone implementation
- **HTTPServerBox**: Already uses `Arc::clone()` correctly
- **FutureBox**: Already uses `Arc::clone()` correctly
- **DebugBox**: Uses derived Clone with Arc fields (works correctly)
- **FileBox**: Uses derived Clone with Arc fields (works correctly)
- **P2PBox**: Designed as `Arc<Mutex<...>>` wrapper (already works correctly)
## Compatibility
- **External Interfaces**: Preserved Box-based APIs for backward compatibility
- **Internal Storage**: Enhanced with Arc-based sharing for stateful objects
- **Performance**: Minimal overhead from Arc reference counting
- **Memory Safety**: Maintains Rust's ownership guarantees
## Conclusion
This fix solves the fundamental issue in the "Everything is Box" design where stateful boxes were losing state due to unnecessary cloning. The hybrid approach maintains interface compatibility while enabling true reference sharing for stateful objects.

View File

@ -13,6 +13,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::fs; use std::fs;
use std::path::Path; use std::path::Path;
// 🔥 新しい型エイリアス - 将来的にBox<dyn NyashBox>を全て置き換える
pub type SharedNyashBox = Arc<dyn NyashBox>;
/// 🔥 BoxBase + BoxCore革命 - 統一ID生成システム /// 🔥 BoxBase + BoxCore革命 - 統一ID生成システム
/// CharmFlow教訓を活かした互換性保証の基盤 /// CharmFlow教訓を活かした互換性保証の基盤
pub fn next_box_id() -> u64 { pub fn next_box_id() -> u64 {
@ -84,6 +87,11 @@ pub trait NyashBox: BoxCore + Debug {
/// Clone this box (equivalent to Python's copy()) /// Clone this box (equivalent to Python's copy())
fn clone_box(&self) -> Box<dyn NyashBox>; fn clone_box(&self) -> Box<dyn NyashBox>;
/// Arc参照を返す新しいcloneメソッド参照共有
fn clone_arc(&self) -> SharedNyashBox {
Arc::from(self.clone_box())
}
// 🌟 TypeBox革命: Get type information as a Box // 🌟 TypeBox革命: Get type information as a Box
// Everything is Box極限実現 - 型情報もBoxとして取得 // Everything is Box極限実現 - 型情報もBoxとして取得
// TODO: 次のステップで完全実装 // TODO: 次のステップで完全実装

View File

@ -56,18 +56,14 @@ pub struct SocketBox {
impl Clone for SocketBox { impl Clone for SocketBox {
fn clone(&self) -> Self { fn clone(&self) -> Self {
// Read the current state values atomically // 🔧 FIX: Share state containers for "Everything is Box" reference sharing
let current_is_server = *self.is_server.lock().unwrap(); // This ensures that clones of the same SocketBox share mutable state
let current_is_connected = *self.is_connected.lock().unwrap();
// For listener and stream, we can't clone them, so we'll share them
// but create new Arc instances with the current state
Self { Self {
base: BoxBase::new(), // New unique ID for clone base: BoxBase::new(), // New unique ID for clone (for debugging/identity)
listener: Arc::clone(&self.listener), // Share the same listener listener: Arc::clone(&self.listener), // Share the same listener
stream: Arc::clone(&self.stream), // Share the same stream stream: Arc::clone(&self.stream), // Share the same stream
is_server: Arc::new(Mutex::new(current_is_server)), // New Arc with current value is_server: Arc::clone(&self.is_server), // 🔧 Share the same state container
is_connected: Arc::new(Mutex::new(current_is_connected)), // New Arc with current value is_connected: Arc::clone(&self.is_connected), // 🔧 Share the same state container
} }
} }
} }

View File

@ -5,7 +5,7 @@
* Everything is Box哲学に基づくオブジェクト指向システム * Everything is Box哲学に基づくオブジェクト指向システム
*/ */
use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase}; use crate::box_trait::{NyashBox, StringBox, BoolBox, VoidBox, BoxCore, BoxBase, SharedNyashBox};
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::value::NyashValue; use crate::value::NyashValue;
use crate::interpreter::NyashInterpreter; use crate::interpreter::NyashInterpreter;
@ -20,8 +20,8 @@ pub struct InstanceBox {
/// クラス名 /// クラス名
pub class_name: String, pub class_name: String,
/// フィールド値 (Legacy compatibility) /// フィールド値 (Updated to use Arc for reference sharing)
pub fields: Arc<Mutex<HashMap<String, Box<dyn NyashBox>>>>, pub fields: Arc<Mutex<HashMap<String, SharedNyashBox>>>,
/// 🔗 Next-generation fields (weak reference capable) /// 🔗 Next-generation fields (weak reference capable)
pub fields_ng: Arc<Mutex<HashMap<String, NyashValue>>>, pub fields_ng: Arc<Mutex<HashMap<String, NyashValue>>>,
@ -49,9 +49,9 @@ pub struct InstanceBox {
impl InstanceBox { impl InstanceBox {
pub fn new(class_name: String, fields: Vec<String>, methods: HashMap<String, ASTNode>) -> Self { pub fn new(class_name: String, fields: Vec<String>, methods: HashMap<String, ASTNode>) -> Self {
// フィールドをVoidBoxで初期化 // フィールドをVoidBoxで初期化
let mut field_map = HashMap::new(); let mut field_map: HashMap<String, SharedNyashBox> = HashMap::new();
for field in &fields { for field in &fields {
field_map.insert(field.clone(), Box::new(VoidBox::new()) as Box<dyn NyashBox>); field_map.insert(field.clone(), Arc::new(VoidBox::new()));
} }
Self { Self {
@ -270,14 +270,24 @@ impl InstanceBox {
} }
/// フィールドの値を取得 /// フィールドの値を取得
pub fn get_field(&self, field_name: &str) -> Option<Box<dyn NyashBox>> { pub fn get_field(&self, field_name: &str) -> Option<SharedNyashBox> {
self.fields.lock().unwrap().get(field_name).map(|v| v.clone_box()) eprintln!("✅ FIX: get_field('{}') returning shared Arc reference", field_name);
// 🔧 修正v.clone_box() → Arc::clone(v) で参照共有
self.fields.lock().unwrap().get(field_name).map(Arc::clone)
} }
/// フィールドに値を設定 /// フィールドに値を設定
pub fn set_field(&self, field_name: &str, value: Box<dyn NyashBox>) -> Result<(), String> { pub fn set_field(&self, field_name: &str, value: SharedNyashBox) -> Result<(), String> {
eprintln!("🔧 INSTANCE: set_field('{}') with shared Arc reference id={}",
field_name, value.box_id());
let mut fields = self.fields.lock().unwrap(); let mut fields = self.fields.lock().unwrap();
if fields.contains_key(field_name) { if fields.contains_key(field_name) {
if let Some(old_value) = fields.get(field_name) {
eprintln!("🔧 INSTANCE: Replacing field '{}': old_id={} -> new_id={}",
field_name, old_value.box_id(), value.box_id());
}
fields.insert(field_name.to_string(), value); fields.insert(field_name.to_string(), value);
Ok(()) Ok(())
} else { } else {
@ -286,7 +296,7 @@ impl InstanceBox {
} }
/// 🌍 GlobalBox用フィールドを動的に追加・設定 /// 🌍 GlobalBox用フィールドを動的に追加・設定
pub fn set_field_dynamic(&mut self, field_name: String, value: Box<dyn NyashBox>) { pub fn set_field_dynamic(&mut self, field_name: String, value: SharedNyashBox) {
let mut fields = self.fields.lock().unwrap(); let mut fields = self.fields.lock().unwrap();
fields.insert(field_name, value); fields.insert(field_name, value);
} }

View File

@ -6,7 +6,7 @@
*/ */
use crate::ast::{ASTNode, Span}; use crate::ast::{ASTNode, Span};
use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox}; use crate::box_trait::{NyashBox, StringBox, IntegerBox, BoolBox, VoidBox, SharedNyashBox};
use crate::instance::InstanceBox; use crate::instance::InstanceBox;
use crate::parser::ParseError; use crate::parser::ParseError;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
@ -192,10 +192,10 @@ pub struct NyashInterpreter {
pub(super) shared: SharedState, pub(super) shared: SharedState,
/// 📦 local変数スタック関数呼び出し時の一時変数 /// 📦 local変数スタック関数呼び出し時の一時変数
pub(super) local_vars: HashMap<String, Box<dyn NyashBox>>, pub(super) local_vars: HashMap<String, SharedNyashBox>,
/// 📤 outbox変数スタックstatic関数内の所有権移転変数 /// 📤 outbox変数スタックstatic関数内の所有権移転変数
pub(super) outbox_vars: HashMap<String, Box<dyn NyashBox>>, pub(super) outbox_vars: HashMap<String, SharedNyashBox>,
/// 制御フロー状態 /// 制御フロー状態
pub(super) control_flow: ControlFlow, pub(super) control_flow: ControlFlow,
@ -322,7 +322,7 @@ impl NyashInterpreter {
// ========== 🌍 GlobalBox変数解決システム ========== // ========== 🌍 GlobalBox変数解決システム ==========
/// 革命的変数解決: local変数 → GlobalBoxフィールド → エラー /// 革命的変数解決: local変数 → GlobalBoxフィールド → エラー
pub(super) fn resolve_variable(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> { pub(super) fn resolve_variable(&self, name: &str) -> Result<SharedNyashBox, RuntimeError> {
let log_msg = format!("resolve_variable: name='{}', local_vars={:?}", let log_msg = format!("resolve_variable: name='{}', local_vars={:?}",
name, self.local_vars.keys().collect::<Vec<_>>()); name, self.local_vars.keys().collect::<Vec<_>>());
debug_log(&log_msg); debug_log(&log_msg);
@ -331,13 +331,27 @@ impl NyashInterpreter {
// 1. outbox変数を最初にチェックstatic関数内で優先 // 1. outbox変数を最初にチェックstatic関数内で優先
if let Some(outbox_value) = self.outbox_vars.get(name) { if let Some(outbox_value) = self.outbox_vars.get(name) {
eprintln!("🔍 DEBUG: Found '{}' in outbox_vars", name); eprintln!("🔍 DEBUG: Found '{}' in outbox_vars", name);
return Ok(outbox_value.clone_box());
// 🔧 修正clone_box() → Arc::clone() で参照共有
let shared_value = Arc::clone(outbox_value);
eprintln!("✅ RESOLVE_VARIABLE shared reference: {} id={}",
name, shared_value.box_id());
return Ok(shared_value);
} }
// 2. local変数をチェック // 2. local変数をチェック
if let Some(local_value) = self.local_vars.get(name) { if let Some(local_value) = self.local_vars.get(name) {
eprintln!("🔍 DEBUG: Found '{}' in local_vars", name); eprintln!("🔍 DEBUG: Found '{}' in local_vars", name);
return Ok(local_value.clone_box());
// 🔧 修正clone_box() → Arc::clone() で参照共有
let shared_value = Arc::clone(local_value);
eprintln!("✅ RESOLVE_VARIABLE shared reference: {} id={}",
name, shared_value.box_id());
return Ok(shared_value);
} }
// 3. GlobalBoxのフィールドをチェック // 3. GlobalBoxのフィールドをチェック
@ -357,15 +371,17 @@ impl NyashInterpreter {
/// 🔥 厳密変数設定: 明示的宣言のみ許可 - Everything is Box哲学 /// 🔥 厳密変数設定: 明示的宣言のみ許可 - Everything is Box哲学
pub(super) fn set_variable(&mut self, name: &str, value: Box<dyn NyashBox>) -> Result<(), RuntimeError> { pub(super) fn set_variable(&mut self, name: &str, value: Box<dyn NyashBox>) -> Result<(), RuntimeError> {
let shared_value = Arc::from(value); // Convert Box to Arc
// 1. outbox変数が存在する場合は更新 // 1. outbox変数が存在する場合は更新
if self.outbox_vars.contains_key(name) { if self.outbox_vars.contains_key(name) {
self.outbox_vars.insert(name.to_string(), value); self.outbox_vars.insert(name.to_string(), shared_value);
return Ok(()); return Ok(());
} }
// 2. local変数が存在する場合は更新 // 2. local変数が存在する場合は更新
if self.local_vars.contains_key(name) { if self.local_vars.contains_key(name) {
self.local_vars.insert(name.to_string(), value); self.local_vars.insert(name.to_string(), shared_value);
return Ok(()); return Ok(());
} }
@ -375,7 +391,7 @@ impl NyashInterpreter {
if global_box.get_field(name).is_some() { if global_box.get_field(name).is_some() {
drop(global_box); // lockを解放 drop(global_box); // lockを解放
let mut global_box = self.shared.global_box.lock().unwrap(); let mut global_box = self.shared.global_box.lock().unwrap();
global_box.set_field_dynamic(name.to_string(), value); global_box.set_field_dynamic(name.to_string(), shared_value);
return Ok(()); return Ok(());
} }
} }
@ -391,34 +407,38 @@ impl NyashInterpreter {
/// local変数を宣言関数内でのみ有効 /// local変数を宣言関数内でのみ有効
pub(super) fn declare_local_variable(&mut self, name: &str, value: Box<dyn NyashBox>) { pub(super) fn declare_local_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
self.local_vars.insert(name.to_string(), value); self.local_vars.insert(name.to_string(), Arc::from(value));
} }
/// outbox変数を宣言static関数内で所有権移転 /// outbox変数を宣言static関数内で所有権移転
pub(super) fn declare_outbox_variable(&mut self, name: &str, value: Box<dyn NyashBox>) { pub(super) fn declare_outbox_variable(&mut self, name: &str, value: Box<dyn NyashBox>) {
self.outbox_vars.insert(name.to_string(), value); self.outbox_vars.insert(name.to_string(), Arc::from(value));
} }
/// local変数スタックを保存・復元関数呼び出し時 /// local変数スタックを保存・復元関数呼び出し時
pub(super) fn save_local_vars(&self) -> HashMap<String, Box<dyn NyashBox>> { pub(super) fn save_local_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
self.local_vars.iter() self.local_vars.iter()
.map(|(k, v)| (k.clone(), v.clone_box())) .map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box
.collect() .collect()
} }
pub(super) fn restore_local_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) { pub(super) fn restore_local_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) {
self.local_vars = saved; self.local_vars = saved.into_iter()
.map(|(k, v)| (k, Arc::from(v))) // Convert Box to Arc
.collect();
} }
/// outbox変数スタックを保存・復元static関数呼び出し時 /// outbox変数スタックを保存・復元static関数呼び出し時
pub(super) fn save_outbox_vars(&self) -> HashMap<String, Box<dyn NyashBox>> { pub(super) fn save_outbox_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
self.outbox_vars.iter() self.outbox_vars.iter()
.map(|(k, v)| (k.clone(), v.clone_box())) .map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box
.collect() .collect()
} }
pub(super) fn restore_outbox_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) { pub(super) fn restore_outbox_vars(&mut self, saved: HashMap<String, Box<dyn NyashBox>>) {
self.outbox_vars = saved; self.outbox_vars = saved.into_iter()
.map(|(k, v)| (k, Arc::from(v))) // Convert Box to Arc
.collect();
} }
/// トップレベル関数をGlobalBoxのメソッドとして登録 - 🔥 暗黙オーバーライド禁止対応 /// トップレベル関数をGlobalBoxのメソッドとして登録 - 🔥 暗黙オーバーライド禁止対応
@ -453,7 +473,8 @@ impl NyashInterpreter {
/// 🌍 革命的変数取得テスト用GlobalBoxのフィールドから取得 /// 🌍 革命的変数取得テスト用GlobalBoxのフィールドから取得
pub fn get_variable(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> { pub fn get_variable(&self, name: &str) -> Result<Box<dyn NyashBox>, RuntimeError> {
self.resolve_variable(name) let shared_var = self.resolve_variable(name)?;
Ok((*shared_var).clone_box()) // Convert Arc back to Box for external interface
} }
} }

View File

@ -25,11 +25,12 @@ impl NyashInterpreter {
ASTNode::Variable { name, .. } => { ASTNode::Variable { name, .. } => {
// 🌍 革命的変数解決local変数 → GlobalBoxフィールド → エラー // 🌍 革命的変数解決local変数 → GlobalBoxフィールド → エラー
self.resolve_variable(name) let shared_var = self.resolve_variable(name)
.map_err(|_| RuntimeError::UndefinedVariableAt { .map_err(|_| RuntimeError::UndefinedVariableAt {
name: name.clone(), name: name.clone(),
span: expression.span() span: expression.span()
}) })?;
Ok((*shared_var).clone_box()) // Convert for external interface
} }
ASTNode::BinaryOp { operator, left, right, .. } => { ASTNode::BinaryOp { operator, left, right, .. } => {
@ -50,7 +51,8 @@ impl NyashInterpreter {
} }
ASTNode::FieldAccess { object, field, .. } => { ASTNode::FieldAccess { object, field, .. } => {
self.execute_field_access(object, field) let shared_result = self.execute_field_access(object, field)?;
Ok((*shared_result).clone_box()) // Convert Arc to Box for external interface
} }
ASTNode::New { class, arguments, type_arguments, .. } => { ASTNode::New { class, arguments, type_arguments, .. } => {
@ -59,21 +61,22 @@ impl NyashInterpreter {
ASTNode::This { .. } => { ASTNode::This { .. } => {
// 🌍 革命的this解決local変数から取得 // 🌍 革命的this解決local変数から取得
self.resolve_variable("me") let shared_this = self.resolve_variable("me")
.map_err(|_| RuntimeError::InvalidOperation { .map_err(|_| RuntimeError::InvalidOperation {
message: "'this' is only available inside methods".to_string(), message: "'this' is only available inside methods".to_string(),
}) })?;
Ok((**shared_this).clone_box()) // Convert for external interface
} }
ASTNode::Me { .. } => { ASTNode::Me { .. } => {
// 🌍 革命的me解決local変数から取得thisと同じ // 🌍 革命的me解決local変数から取得thisと同じ
let result = self.resolve_variable("me") let shared_me = self.resolve_variable("me")
.map_err(|_| RuntimeError::InvalidOperation { .map_err(|_| RuntimeError::InvalidOperation {
message: "'me' is only available inside methods".to_string(), message: "'me' is only available inside methods".to_string(),
}); })?;
result Ok((**shared_me).clone_box()) // Convert for external interface
} }
ASTNode::ThisField { field, .. } => { ASTNode::ThisField { field, .. } => {
@ -83,11 +86,12 @@ impl NyashInterpreter {
message: "'this' is not bound in the current context".to_string(), message: "'this' is not bound in the current context".to_string(),
})?; })?;
if let Some(instance) = this_value.as_any().downcast_ref::<InstanceBox>() { if let Some(instance) = (**this_value).as_any().downcast_ref::<InstanceBox>() {
instance.get_field(field) let shared_field = instance.get_field(field)
.ok_or_else(|| RuntimeError::InvalidOperation { .ok_or_else(|| RuntimeError::InvalidOperation {
message: format!("Field '{}' not found on this", field) message: format!("Field '{}' not found on this", field)
}) })?;
Ok((**shared_field).clone_box()) // Convert for external interface
} else { } else {
Err(RuntimeError::TypeError { Err(RuntimeError::TypeError {
message: "'this' is not an instance".to_string(), message: "'this' is not an instance".to_string(),
@ -102,11 +106,12 @@ impl NyashInterpreter {
message: "'this' is not bound in the current context".to_string(), message: "'this' is not bound in the current context".to_string(),
})?; })?;
if let Some(instance) = me_value.as_any().downcast_ref::<InstanceBox>() { if let Some(instance) = (**me_value).as_any().downcast_ref::<InstanceBox>() {
instance.get_field(field) let shared_field = instance.get_field(field)
.ok_or_else(|| RuntimeError::InvalidOperation { .ok_or_else(|| RuntimeError::InvalidOperation {
message: format!("Field '{}' not found on me", field) message: format!("Field '{}' not found on me", field)
}) })?;
Ok((**shared_field).clone_box()) // Convert for external interface
} else { } else {
Err(RuntimeError::TypeError { Err(RuntimeError::TypeError {
message: "'this' is not an instance".to_string(), message: "'this' is not an instance".to_string(),
@ -549,7 +554,7 @@ impl NyashInterpreter {
if name == "me" { if name == "me" {
// Get current instance to check if field is weak // Get current instance to check if field is weak
if let Ok(current_me) = self.resolve_variable("me") { if let Ok(current_me) = self.resolve_variable("me") {
if let Some(current_instance) = current_me.as_any().downcast_ref::<InstanceBox>() { if let Some(current_instance) = (**current_me).as_any().downcast_ref::<InstanceBox>() {
if current_instance.is_weak_field(field) { if current_instance.is_weak_field(field) {
return Err(RuntimeError::InvalidOperation { return Err(RuntimeError::InvalidOperation {
message: format!( message: format!(
@ -671,7 +676,7 @@ impl NyashInterpreter {
/// フィールドアクセスを実行 - Field access processing with weak reference support /// フィールドアクセスを実行 - Field access processing with weak reference support
pub(super) fn execute_field_access(&mut self, object: &ASTNode, field: &str) pub(super) fn execute_field_access(&mut self, object: &ASTNode, field: &str)
-> Result<Box<dyn NyashBox>, RuntimeError> { -> Result<SharedNyashBox, RuntimeError> {
// 🔥 Static Boxアクセスチェック // 🔥 Static Boxアクセスチェック
if let ASTNode::Variable { name, .. } = object { if let ASTNode::Variable { name, .. } = object {
@ -702,6 +707,8 @@ impl NyashInterpreter {
message: format!("Field '{}' not found in {}", field, instance.class_name), message: format!("Field '{}' not found in {}", field, instance.class_name),
})?; })?;
eprintln!("✅ FIELD ACCESS: Returning shared reference id={}", field_value.box_id());
// 🔗 Weak Reference Check: Use unified accessor for weak fields // 🔗 Weak Reference Check: Use unified accessor for weak fields
let box_decls = self.shared.box_declarations.read().unwrap(); let box_decls = self.shared.box_declarations.read().unwrap();
if let Some(box_decl) = box_decls.get(&instance.class_name) { if let Some(box_decl) = box_decls.get(&instance.class_name) {
@ -731,7 +738,7 @@ impl NyashInterpreter {
} }
} }
// Normal field access for now // Return the shared Arc reference directly
Ok(field_value) Ok(field_value)
} else { } else {
Err(RuntimeError::TypeError { Err(RuntimeError::TypeError {
@ -858,7 +865,7 @@ impl NyashInterpreter {
message: "'from' can only be used inside methods".to_string(), message: "'from' can only be used inside methods".to_string(),
})?; })?;
let current_instance = current_instance_val.as_any().downcast_ref::<InstanceBox>() let current_instance = (**current_instance_val).as_any().downcast_ref::<InstanceBox>()
.ok_or(RuntimeError::TypeError { .ok_or(RuntimeError::TypeError {
message: "'from' requires current instance to be InstanceBox".to_string(), message: "'from' requires current instance to be InstanceBox".to_string(),
})?; })?;

View File

@ -718,20 +718,21 @@ impl NyashInterpreter {
.or_else(|| final_box_decl.constructors.get(&init_key)) .or_else(|| final_box_decl.constructors.get(&init_key))
.or_else(|| final_box_decl.constructors.get(&box_name_key)) { .or_else(|| final_box_decl.constructors.get(&box_name_key)) {
// コンストラクタを実行 // コンストラクタを実行
self.execute_constructor(&instance_box, constructor, arguments, &final_box_decl)?; let instance_arc = Arc::from(instance_box);
self.execute_constructor(&instance_arc, constructor, arguments, &final_box_decl)?;
} else if !arguments.is_empty() { } else if !arguments.is_empty() {
return Err(RuntimeError::InvalidOperation { return Err(RuntimeError::InvalidOperation {
message: format!("No constructor found for {} with {} arguments", class, arguments.len()), message: format!("No constructor found for {} with {} arguments", class, arguments.len()),
}); });
} }
Ok(instance_box) Ok((*instance_arc).clone_box()) // Convert Arc back to Box for external interface
} }
/// コンストラクタを実行 - Constructor execution /// コンストラクタを実行 - Constructor execution
pub(super) fn execute_constructor( pub(super) fn execute_constructor(
&mut self, &mut self,
instance: &Box<dyn NyashBox>, instance: &SharedNyashBox,
constructor: &ASTNode, constructor: &ASTNode,
arguments: &[ASTNode], arguments: &[ASTNode],
box_decl: &BoxDeclaration box_decl: &BoxDeclaration

View File

@ -321,7 +321,7 @@ impl NyashInterpreter {
message: "'this' is not bound in the current context".to_string(), message: "'this' is not bound in the current context".to_string(),
})?; })?;
if let Some(instance) = this_value.as_any().downcast_ref::<InstanceBox>() { if let Some(instance) = (**this_value).as_any().downcast_ref::<InstanceBox>() {
// 🔥 Usage prohibition guard - check if instance is finalized // 🔥 Usage prohibition guard - check if instance is finalized
if instance.is_finalized() { if instance.is_finalized() {
return Err(RuntimeError::InvalidOperation { return Err(RuntimeError::InvalidOperation {
@ -354,7 +354,7 @@ impl NyashInterpreter {
message: "'this' is not bound in the current context".to_string(), message: "'this' is not bound in the current context".to_string(),
})?; })?;
if let Some(instance) = me_value.as_any().downcast_ref::<InstanceBox>() { if let Some(instance) = (**me_value).as_any().downcast_ref::<InstanceBox>() {
// 🔥 Usage prohibition guard - check if instance is finalized // 🔥 Usage prohibition guard - check if instance is finalized
if instance.is_finalized() { if instance.is_finalized() {
return Err(RuntimeError::InvalidOperation { return Err(RuntimeError::InvalidOperation {

17
test_arc_fix.nyash Normal file
View File

@ -0,0 +1,17 @@
static box Main {
init { server }
main() {
me.server = new SocketBox()
print("=== Before bind ===")
print("isServer: " + me.server.isServer())
me.server.bind("127.0.0.1", 8080)
print("=== After bind ===")
print("isServer: " + me.server.isServer()) // This should be true if fix works!
return me.server.isServer()
}
}

20
test_basic_sharing.nyash Normal file
View File

@ -0,0 +1,20 @@
// Test basic field access sharing
static box Main {
init { mybox }
main() {
me.mybox = new IntegerBox(100)
print("=== Test 1: Basic field access ===")
local temp1
temp1 = me.mybox
print("temp1 ID: " + temp1.toString())
local temp2
temp2 = me.mybox
print("temp2 ID: " + temp2.toString())
// The IDs should be the same if sharing works
return temp1
}
}

21
test_box_id_sharing.nyash Normal file
View File

@ -0,0 +1,21 @@
// Test for Arc sharing - checking if same Box ID is returned
static box Main {
init { server }
main() {
me.server = new SocketBox()
print("=== First access ===")
local temp1
temp1 = me.server
print("Box ID: " + temp1.toString())
print("=== Second access ===")
local temp2
temp2 = me.server
print("Box ID: " + temp2.toString())
// If Arc sharing works, both should have same ID
return temp1
}
}

View File

@ -0,0 +1,33 @@
// Complete test for the SocketBox state preservation fix
static box Main {
init { server }
main() {
print("=== SocketBox State Preservation Test ===")
// 1. Create new SocketBox
me.server = new SocketBox()
print("=== Before bind ===")
print("isServer: " + me.server.isServer())
// 2. Call bind() which should set is_server=true
me.server.bind("127.0.0.1", 8080)
print("=== After bind ===")
print("isServer: " + me.server.isServer())
// 3. Test multiple accesses
print("=== Multiple access test ===")
local temp1
temp1 = me.server
print("temp1.isServer(): " + temp1.isServer())
local temp2
temp2 = me.server
print("temp2.isServer(): " + temp2.isServer())
// If the fix works, all should return true
return me.server.isServer()
}
}

View File

@ -0,0 +1,51 @@
// Final validation test for the SocketBox state preservation fix
// This test replicates the exact scenario described in issue #74
static box Main {
init { server }
main() {
print("=== Issue #74 Validation Test ===")
print("Testing SocketBox state preservation across field accesses")
print("")
// Create SocketBox and store in field
me.server = new SocketBox()
print("Step 1: Initial state")
print("me.server.isServer(): " + me.server.isServer()) // Should be false
print("")
print("Step 2: Calling bind() to set server state")
me.server.bind("127.0.0.1", 8080) // This should set is_server=true
print("bind() completed")
print("")
print("Step 3: Checking state after bind()")
print("me.server.isServer(): " + me.server.isServer()) // Should be true if fix works!
print("")
// Additional tests: multiple accesses should all return same state
print("Step 4: Multiple field access test")
local server1
server1 = me.server
print("server1.isServer(): " + server1.isServer()) // Should be true
local server2
server2 = me.server
print("server2.isServer(): " + server2.isServer()) // Should be true
print("")
print("=== Test Summary ===")
if (me.server.isServer()) {
print("✅ SUCCESS: SocketBox state preserved across field accesses!")
print(" The 'Everything is Box' design now correctly shares state")
print(" for stateful boxes like SocketBox, P2PBox, etc.")
} else {
print("❌ FAILURE: SocketBox state was not preserved")
print(" The fix did not work as expected")
}
return me.server.isServer()
}
}

View File

@ -0,0 +1,40 @@
// Comprehensive test for multiple stateful boxes
static box Main {
init { socket, debug_tracker }
main() {
print("=== Comprehensive Stateful Box Test ===")
// Test 1: SocketBox (the main issue)
print("=== Test 1: SocketBox ===")
me.socket = new SocketBox()
print("Before bind - isServer: " + me.socket.isServer())
me.socket.bind("127.0.0.1", 8080)
print("After bind - isServer: " + me.socket.isServer())
// Test multiple field accesses
local socket1
socket1 = me.socket
local socket2
socket2 = me.socket
print("socket1.isServer(): " + socket1.isServer())
print("socket2.isServer(): " + socket2.isServer())
// Test 2: DebugBox (should already work)
print("=== Test 2: DebugBox ===")
me.debug_tracker = new DebugBox()
me.debug_tracker.enableTracking()
print("Tracking enabled: " + me.debug_tracker.isTrackingEnabled())
// Test multiple accesses
local debug1
debug1 = me.debug_tracker
local debug2
debug2 = me.debug_tracker
print("debug1.isTrackingEnabled(): " + debug1.isTrackingEnabled())
print("debug2.isTrackingEnabled(): " + debug2.isTrackingEnabled())
print("=== All tests completed ===")
return me.socket.isServer()
}
}

24
test_simple_arc_fix.nyash Normal file
View File

@ -0,0 +1,24 @@
// Simple test case to verify Arc sharing fix
static box Main {
init { counter }
main() {
me.counter = new IntegerBox(42)
print("=== Initial value ===")
print("counter: " + me.counter.to_string())
// Test if getting the same field returns the same object
local temp1
temp1 = me.counter
local temp2
temp2 = me.counter
print("=== Testing shared reference ===")
print("temp1: " + temp1.to_string())
print("temp2: " + temp2.to_string())
return me.counter
}
}