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:
99
SOCKETBOX_FIX_SUMMARY.md
Normal file
99
SOCKETBOX_FIX_SUMMARY.md
Normal 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.
|
||||
@ -13,6 +13,9 @@ use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
// 🔥 新しい型エイリアス - 将来的にBox<dyn NyashBox>を全て置き換える
|
||||
pub type SharedNyashBox = Arc<dyn NyashBox>;
|
||||
|
||||
/// 🔥 BoxBase + BoxCore革命 - 統一ID生成システム
|
||||
/// CharmFlow教訓を活かした互換性保証の基盤
|
||||
pub fn next_box_id() -> u64 {
|
||||
@ -84,6 +87,11 @@ pub trait NyashBox: BoxCore + Debug {
|
||||
/// Clone this box (equivalent to Python's copy())
|
||||
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
|
||||
// Everything is Box極限実現 - 型情報もBoxとして取得!
|
||||
// TODO: 次のステップで完全実装
|
||||
|
||||
@ -56,18 +56,14 @@ pub struct SocketBox {
|
||||
|
||||
impl Clone for SocketBox {
|
||||
fn clone(&self) -> Self {
|
||||
// Read the current state values atomically
|
||||
let current_is_server = *self.is_server.lock().unwrap();
|
||||
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
|
||||
// 🔧 FIX: Share state containers for "Everything is Box" reference sharing
|
||||
// This ensures that clones of the same SocketBox share mutable state
|
||||
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
|
||||
stream: Arc::clone(&self.stream), // Share the same stream
|
||||
is_server: Arc::new(Mutex::new(current_is_server)), // New Arc with current value
|
||||
is_connected: Arc::new(Mutex::new(current_is_connected)), // New Arc with current value
|
||||
is_server: Arc::clone(&self.is_server), // 🔧 Share the same state container
|
||||
is_connected: Arc::clone(&self.is_connected), // 🔧 Share the same state container
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
* 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::value::NyashValue;
|
||||
use crate::interpreter::NyashInterpreter;
|
||||
@ -20,8 +20,8 @@ pub struct InstanceBox {
|
||||
/// クラス名
|
||||
pub class_name: String,
|
||||
|
||||
/// フィールド値 (Legacy compatibility)
|
||||
pub fields: Arc<Mutex<HashMap<String, Box<dyn NyashBox>>>>,
|
||||
/// フィールド値 (Updated to use Arc for reference sharing)
|
||||
pub fields: Arc<Mutex<HashMap<String, SharedNyashBox>>>,
|
||||
|
||||
/// 🔗 Next-generation fields (weak reference capable)
|
||||
pub fields_ng: Arc<Mutex<HashMap<String, NyashValue>>>,
|
||||
@ -49,9 +49,9 @@ pub struct InstanceBox {
|
||||
impl InstanceBox {
|
||||
pub fn new(class_name: String, fields: Vec<String>, methods: HashMap<String, ASTNode>) -> Self {
|
||||
// フィールドをVoidBoxで初期化
|
||||
let mut field_map = HashMap::new();
|
||||
let mut field_map: HashMap<String, SharedNyashBox> = HashMap::new();
|
||||
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 {
|
||||
@ -270,14 +270,24 @@ impl InstanceBox {
|
||||
}
|
||||
|
||||
/// フィールドの値を取得
|
||||
pub fn get_field(&self, field_name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
self.fields.lock().unwrap().get(field_name).map(|v| v.clone_box())
|
||||
pub fn get_field(&self, field_name: &str) -> Option<SharedNyashBox> {
|
||||
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();
|
||||
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);
|
||||
Ok(())
|
||||
} else {
|
||||
@ -286,7 +296,7 @@ impl InstanceBox {
|
||||
}
|
||||
|
||||
/// 🌍 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();
|
||||
fields.insert(field_name, value);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
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::parser::ParseError;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
@ -192,10 +192,10 @@ pub struct NyashInterpreter {
|
||||
pub(super) shared: SharedState,
|
||||
|
||||
/// 📦 local変数スタック(関数呼び出し時の一時変数)
|
||||
pub(super) local_vars: HashMap<String, Box<dyn NyashBox>>,
|
||||
pub(super) local_vars: HashMap<String, SharedNyashBox>,
|
||||
|
||||
/// 📤 outbox変数スタック(static関数内の所有権移転変数)
|
||||
pub(super) outbox_vars: HashMap<String, Box<dyn NyashBox>>,
|
||||
pub(super) outbox_vars: HashMap<String, SharedNyashBox>,
|
||||
|
||||
/// 制御フロー状態
|
||||
pub(super) control_flow: ControlFlow,
|
||||
@ -322,7 +322,7 @@ impl NyashInterpreter {
|
||||
// ========== 🌍 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={:?}",
|
||||
name, self.local_vars.keys().collect::<Vec<_>>());
|
||||
debug_log(&log_msg);
|
||||
@ -331,13 +331,27 @@ impl NyashInterpreter {
|
||||
// 1. outbox変数を最初にチェック(static関数内で優先)
|
||||
if let Some(outbox_value) = self.outbox_vars.get(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変数をチェック
|
||||
if let Some(local_value) = self.local_vars.get(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のフィールドをチェック
|
||||
@ -357,15 +371,17 @@ impl NyashInterpreter {
|
||||
|
||||
/// 🔥 厳密変数設定: 明示的宣言のみ許可 - Everything is Box哲学
|
||||
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変数が存在する場合は更新
|
||||
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(());
|
||||
}
|
||||
|
||||
// 2. local変数が存在する場合は更新
|
||||
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(());
|
||||
}
|
||||
|
||||
@ -375,7 +391,7 @@ impl NyashInterpreter {
|
||||
if global_box.get_field(name).is_some() {
|
||||
drop(global_box); // lockを解放
|
||||
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(());
|
||||
}
|
||||
}
|
||||
@ -391,34 +407,38 @@ impl NyashInterpreter {
|
||||
|
||||
/// local変数を宣言(関数内でのみ有効)
|
||||
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関数内で所有権移転)
|
||||
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変数スタックを保存・復元(関数呼び出し時)
|
||||
pub(super) fn save_local_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
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()
|
||||
}
|
||||
|
||||
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関数呼び出し時)
|
||||
pub(super) fn save_outbox_vars(&self) -> HashMap<String, Box<dyn NyashBox>> {
|
||||
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()
|
||||
}
|
||||
|
||||
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のメソッドとして登録 - 🔥 暗黙オーバーライド禁止対応
|
||||
@ -453,7 +473,8 @@ impl NyashInterpreter {
|
||||
|
||||
/// 🌍 革命的変数取得(テスト用):GlobalBoxのフィールドから取得
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,11 +25,12 @@ impl NyashInterpreter {
|
||||
|
||||
ASTNode::Variable { name, .. } => {
|
||||
// 🌍 革命的変数解決:local変数 → GlobalBoxフィールド → エラー
|
||||
self.resolve_variable(name)
|
||||
let shared_var = self.resolve_variable(name)
|
||||
.map_err(|_| RuntimeError::UndefinedVariableAt {
|
||||
name: name.clone(),
|
||||
span: expression.span()
|
||||
})
|
||||
})?;
|
||||
Ok((*shared_var).clone_box()) // Convert for external interface
|
||||
}
|
||||
|
||||
ASTNode::BinaryOp { operator, left, right, .. } => {
|
||||
@ -50,7 +51,8 @@ impl NyashInterpreter {
|
||||
}
|
||||
|
||||
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, .. } => {
|
||||
@ -59,21 +61,22 @@ impl NyashInterpreter {
|
||||
|
||||
ASTNode::This { .. } => {
|
||||
// 🌍 革命的this解決:local変数から取得
|
||||
self.resolve_variable("me")
|
||||
let shared_this = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'this' is only available inside methods".to_string(),
|
||||
})
|
||||
})?;
|
||||
Ok((**shared_this).clone_box()) // Convert for external interface
|
||||
}
|
||||
|
||||
ASTNode::Me { .. } => {
|
||||
|
||||
// 🌍 革命的me解決:local変数から取得(thisと同じ)
|
||||
let result = self.resolve_variable("me")
|
||||
let shared_me = self.resolve_variable("me")
|
||||
.map_err(|_| RuntimeError::InvalidOperation {
|
||||
message: "'me' is only available inside methods".to_string(),
|
||||
});
|
||||
})?;
|
||||
|
||||
result
|
||||
Ok((**shared_me).clone_box()) // Convert for external interface
|
||||
}
|
||||
|
||||
ASTNode::ThisField { field, .. } => {
|
||||
@ -83,11 +86,12 @@ impl NyashInterpreter {
|
||||
message: "'this' is not bound in the current context".to_string(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = this_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
instance.get_field(field)
|
||||
if let Some(instance) = (**this_value).as_any().downcast_ref::<InstanceBox>() {
|
||||
let shared_field = instance.get_field(field)
|
||||
.ok_or_else(|| RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found on this", field)
|
||||
})
|
||||
})?;
|
||||
Ok((**shared_field).clone_box()) // Convert for external interface
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
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(),
|
||||
})?;
|
||||
|
||||
if let Some(instance) = me_value.as_any().downcast_ref::<InstanceBox>() {
|
||||
instance.get_field(field)
|
||||
if let Some(instance) = (**me_value).as_any().downcast_ref::<InstanceBox>() {
|
||||
let shared_field = instance.get_field(field)
|
||||
.ok_or_else(|| RuntimeError::InvalidOperation {
|
||||
message: format!("Field '{}' not found on me", field)
|
||||
})
|
||||
})?;
|
||||
Ok((**shared_field).clone_box()) // Convert for external interface
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
message: "'this' is not an instance".to_string(),
|
||||
@ -549,7 +554,7 @@ impl NyashInterpreter {
|
||||
if name == "me" {
|
||||
// Get current instance to check if field is weak
|
||||
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) {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
message: format!(
|
||||
@ -671,7 +676,7 @@ impl NyashInterpreter {
|
||||
|
||||
/// フィールドアクセスを実行 - Field access processing with weak reference support
|
||||
pub(super) fn execute_field_access(&mut self, object: &ASTNode, field: &str)
|
||||
-> Result<Box<dyn NyashBox>, RuntimeError> {
|
||||
-> Result<SharedNyashBox, RuntimeError> {
|
||||
|
||||
// 🔥 Static Boxアクセスチェック
|
||||
if let ASTNode::Variable { name, .. } = object {
|
||||
@ -702,6 +707,8 @@ impl NyashInterpreter {
|
||||
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
|
||||
let box_decls = self.shared.box_declarations.read().unwrap();
|
||||
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)
|
||||
} else {
|
||||
Err(RuntimeError::TypeError {
|
||||
@ -858,7 +865,7 @@ impl NyashInterpreter {
|
||||
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 {
|
||||
message: "'from' requires current instance to be InstanceBox".to_string(),
|
||||
})?;
|
||||
|
||||
@ -718,20 +718,21 @@ impl NyashInterpreter {
|
||||
.or_else(|| final_box_decl.constructors.get(&init_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() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
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
|
||||
pub(super) fn execute_constructor(
|
||||
&mut self,
|
||||
instance: &Box<dyn NyashBox>,
|
||||
instance: &SharedNyashBox,
|
||||
constructor: &ASTNode,
|
||||
arguments: &[ASTNode],
|
||||
box_decl: &BoxDeclaration
|
||||
|
||||
@ -321,7 +321,7 @@ impl NyashInterpreter {
|
||||
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
|
||||
if instance.is_finalized() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
@ -354,7 +354,7 @@ impl NyashInterpreter {
|
||||
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
|
||||
if instance.is_finalized() {
|
||||
return Err(RuntimeError::InvalidOperation {
|
||||
|
||||
17
test_arc_fix.nyash
Normal file
17
test_arc_fix.nyash
Normal 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
20
test_basic_sharing.nyash
Normal 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
21
test_box_id_sharing.nyash
Normal 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
|
||||
}
|
||||
}
|
||||
33
test_complete_socketbox_fix.nyash
Normal file
33
test_complete_socketbox_fix.nyash
Normal 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()
|
||||
}
|
||||
}
|
||||
51
test_final_validation.nyash
Normal file
51
test_final_validation.nyash
Normal 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()
|
||||
}
|
||||
}
|
||||
40
test_multiple_stateful_boxes.nyash
Normal file
40
test_multiple_stateful_boxes.nyash
Normal 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
24
test_simple_arc_fix.nyash
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user