diff --git a/src/interpreter/core.rs b/src/interpreter/core.rs index 37163ee5..f37768a8 100644 --- a/src/interpreter/core.rs +++ b/src/interpreter/core.rs @@ -547,12 +547,29 @@ impl NyashInterpreter { /// local変数を宣言(関数内でのみ有効) pub(super) fn declare_local_variable(&mut self, name: &str, value: Box) { - self.local_vars.insert(name.to_string(), Arc::from(value)); + // Pass-by-share for plugin handle types; by-value (clone) semantics can be applied at call sites + #[allow(unused_mut)] + let mut store_value = value; + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if store_value.as_any().downcast_ref::().is_some() { + store_value = store_value.share_box(); + } + } + self.local_vars.insert(name.to_string(), Arc::from(store_value)); } /// outbox変数を宣言(static関数内で所有権移転) pub(super) fn declare_outbox_variable(&mut self, name: &str, value: Box) { - self.outbox_vars.insert(name.to_string(), Arc::from(value)); + #[allow(unused_mut)] + let mut store_value = value; + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if store_value.as_any().downcast_ref::().is_some() { + store_value = store_value.share_box(); + } + } + self.outbox_vars.insert(name.to_string(), Arc::from(store_value)); } /// local変数スタックを保存・復元(関数呼び出し時) diff --git a/src/interpreter/statements.rs b/src/interpreter/statements.rs index 28df28ed..cbc3e9e4 100644 --- a/src/interpreter/statements.rs +++ b/src/interpreter/statements.rs @@ -282,7 +282,22 @@ impl NyashInterpreter { } } - self.set_variable(name, val.clone_box())?; + // Assign-by-share for plugin handle types; clone for others + let assigned = { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if val.as_any().downcast_ref::().is_some() { + val.share_box() + } else { + val.clone_box() + } + } + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + { + val.clone_box() + } + }; + self.set_variable(name, assigned)?; Ok(val) } @@ -330,7 +345,18 @@ impl NyashInterpreter { // 🚨 フィールド差し替え時の自動finiは削除(Nyashの明示的哲学) // プログラマーが必要なら明示的にfini()を呼ぶべき - instance.set_field(field, Arc::from(val.clone_box())) + // Store-by-share for plugin handle types; clone for others + let stored = { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if val.as_any().downcast_ref::().is_some() { + val.share_box() + } else { val.clone_box() } + } + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + { val.clone_box() } + }; + instance.set_field(field, Arc::from(stored)) .map_err(|e| RuntimeError::InvalidOperation { message: e })?; Ok(val) } else { @@ -354,7 +380,17 @@ impl NyashInterpreter { // 🚨 フィールド差し替え時の自動finiは削除(Nyashの明示的哲学) // プログラマーが必要なら明示的にfini()を呼ぶべき - instance.set_field(field, Arc::from(val.clone_box())) + let stored = { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if val.as_any().downcast_ref::().is_some() { + val.share_box() + } else { val.clone_box() } + } + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + { val.clone_box() } + }; + instance.set_field(field, Arc::from(stored)) .map_err(|e| RuntimeError::InvalidOperation { message: e })?; Ok(val) } else { @@ -378,7 +414,17 @@ impl NyashInterpreter { // 🚨 フィールド差し替え時の自動finiは削除(Nyashの明示的哲学) // プログラマーが必要なら明示的にfini()を呼ぶべき - instance.set_field(field, Arc::from(val.clone_box())) + let stored = { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if val.as_any().downcast_ref::().is_some() { + val.share_box() + } else { val.clone_box() } + } + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + { val.clone_box() } + }; + instance.set_field(field, Arc::from(stored)) .map_err(|e| RuntimeError::InvalidOperation { message: e })?; Ok(val) } else { diff --git a/tests/e2e_plugin_counterbox.rs b/tests/e2e_plugin_counterbox.rs index a6a6b2f0..fad48bac 100644 --- a/tests/e2e_plugin_counterbox.rs +++ b/tests/e2e_plugin_counterbox.rs @@ -50,7 +50,7 @@ v2 } #[test] -fn e2e_counter_assignment_clones_not_shares() { +fn e2e_counter_assignment_shares_handle() { if !try_init_plugins() { return; } let code = r#" @@ -66,10 +66,9 @@ v match interpreter.execute(ast) { Ok(result) => { - // Current semantics: assignment clones (not shares), so c remains 0 - assert_eq!(result.to_string_box().value, "0"); + // New semantics: plugin handle assign shares, so c reflects x.inc() + assert_eq!(result.to_string_box().value, "1"); } Err(e) => panic!("Counter assignment test failed: {:?}", e), } } -