diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-24 03:56:26 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-24 03:57:46 +0900 |
| commit | f1af14b1cc503ac20f56a79a96c7780d02bdfe75 (patch) | |
| tree | 3f8661e1dc4d2092ac16b45c574c90ec2d8bec2a /crates/shirabe-external-packages/src/symfony | |
| parent | c5fc34a106a706ac12925c4df273a926811d260b (diff) | |
| download | php-shirabe-f1af14b1cc503ac20f56a79a96c7780d02bdfe75.tar.gz php-shirabe-f1af14b1cc503ac20f56a79a96c7780d02bdfe75.tar.zst php-shirabe-f1af14b1cc503ac20f56a79a96c7780d02bdfe75.zip | |
fix(console): make Command/BaseCommand methods take &self
The Command trait and Composer's BaseCommand took &mut self, so dispatch
held a borrow_mut on the command's RefCell for the whole call. A command
re-entering itself (e.g. the help command describing itself) then panicked
with "RefCell already borrowed".
All Command/BaseCommand methods now take &self and the command state is
interior-mutable (Cell/RefCell). Shared borrows coexist, so re-entrant
describe paths no longer conflict. Getters that returned references now
return Ref guards; the descriptor describe_* methods take &dyn Command;
mixin accessors return Ref/RefMut.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-external-packages/src/symfony')
10 files changed, 231 insertions, 214 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/console/command/command.rs b/crates/shirabe-external-packages/src/symfony/console/command/command.rs index 59aa915..652463f 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/command.rs @@ -12,7 +12,7 @@ use crate::symfony::console::input::input_option::InputOption; use crate::symfony::console::output::output_interface::{self, OutputInterface}; use indexmap::IndexMap; use shirabe_php_shim::PhpMixed; -use std::cell::RefCell; +use std::cell::{Cell, Ref, RefCell}; use std::rc::Rc; /// The base-class state of the PHP `Command` class. @@ -22,22 +22,28 @@ use std::rc::Rc; /// which holds the base-class fields and provides their canonical behavior via /// `impl Command for CommandData`. Subclasses embed a `CommandData` (directly, or /// transitively through `BaseCommandData`) and forward the state methods to it. +/// +/// The mutable fields use interior mutability (`Cell`/`RefCell`) so that the `Command` +/// trait methods take `&self`, mirroring PHP's reference semantics: calling a method on a +/// command does not lock the object, so a command can be re-entered (e.g. the help command +/// describing itself) without the borrow conflicts a `&mut self` design would cause. pub struct CommandData { - application: Option<Rc<RefCell<dyn Application>>>, - name: Option<String>, - process_title: Option<String>, - aliases: Vec<String>, - definition: Option<InputDefinition>, - hidden: bool, - help: String, - description: String, - full_definition: Option<InputDefinition>, - ignore_validation_errors: bool, + application: RefCell<Option<Rc<RefCell<dyn Application>>>>, + name: RefCell<Option<String>>, + process_title: RefCell<Option<String>>, + aliases: RefCell<Vec<String>>, + definition: RefCell<Option<InputDefinition>>, + hidden: Cell<bool>, + help: RefCell<String>, + description: RefCell<String>, + full_definition: RefCell<Option<InputDefinition>>, + ignore_validation_errors: Cell<bool>, // A callable(InputInterface, OutputInterface) -> i64. - code: Option<Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>>, - synopsis: IndexMap<String, String>, - usages: Vec<String>, - helper_set: Option<Rc<RefCell<HelperSet>>>, + code: + RefCell<Option<Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>>>, + synopsis: RefCell<IndexMap<String, String>>, + usages: RefCell<Vec<String>>, + helper_set: RefCell<Option<Rc<RefCell<HelperSet>>>>, } impl CommandData { @@ -74,23 +80,23 @@ impl CommandData { /// command's `new()` calls `configure()` after embedding the data, mirroring the /// virtual dispatch of `$this->configure()` from the parent constructor. pub fn new(name: Option<String>) -> Self { - let mut this = CommandData { - application: None, - name: None, - process_title: None, - aliases: Vec::new(), - definition: Some( + let this = CommandData { + application: RefCell::new(None), + name: RefCell::new(None), + process_title: RefCell::new(None), + aliases: RefCell::new(Vec::new()), + definition: RefCell::new(Some( InputDefinition::new(Vec::new()).expect("an empty InputDefinition cannot fail"), - ), - hidden: false, - help: String::new(), - description: String::new(), - full_definition: None, - ignore_validation_errors: false, - code: None, - synopsis: IndexMap::new(), - usages: Vec::new(), - helper_set: None, + )), + hidden: Cell::new(false), + help: RefCell::new(String::new()), + description: RefCell::new(String::new()), + full_definition: RefCell::new(None), + ignore_validation_errors: Cell::new(false), + code: RefCell::new(None), + synopsis: RefCell::new(IndexMap::new()), + usages: RefCell::new(Vec::new()), + helper_set: RefCell::new(None), }; // PHP's __construct also derives the name from getDefaultName() when null and @@ -98,7 +104,7 @@ impl CommandData { // (get_default_name/get_default_description are todo!()), and concrete commands // always set their name in configure(), so only an explicit name is honored here. if let Some(name) = name { - this.name = Some(name); + *this.name.borrow_mut() = Some(name); } this @@ -107,7 +113,7 @@ impl CommandData { /// Applies a `$defaultName`-style name (PHP `Command::__construct` when `$name` is null and a /// `static $defaultName` exists). The string is `|`-separated; a leading empty segment marks the /// command hidden, the next segment is the name, and the rest are aliases. - pub fn apply_default_name(&mut self, default_name: &str) -> anyhow::Result<()> { + pub fn apply_default_name(&self, default_name: &str) -> anyhow::Result<()> { let mut aliases: Vec<String> = default_name.split('|').map(|s| s.to_string()).collect(); let mut name = aliases.remove(0); if name.is_empty() { @@ -144,17 +150,22 @@ impl CommandData { /// Sets an array of argument and option instances (the Symfony-typed entry point; /// `BaseCommand::set_definition` adapts the Composer-typed arguments to this). - pub fn set_definition(&mut self, definition: SetDefinitionArg) -> &mut Self { + pub fn set_definition(&self, definition: SetDefinitionArg) -> &Self { match definition { SetDefinitionArg::Definition(definition) => { - self.definition = Some(definition); + *self.definition.borrow_mut() = Some(definition); } SetDefinitionArg::Array(definition) => { - let _ = self.definition.as_mut().unwrap().set_definition(definition); + let _ = self + .definition + .borrow_mut() + .as_mut() + .unwrap() + .set_definition(definition); } } - self.full_definition = None; + *self.full_definition.borrow_mut() = None; self } @@ -163,13 +174,14 @@ impl CommandData { /// /// Throws InvalidArgumentException when argument mode is not valid. pub fn add_argument( - &mut self, + &self, name: &str, mode: Option<i64>, description: &str, default: PhpMixed, - ) -> anyhow::Result<&mut Self> { + ) -> anyhow::Result<&Self> { self.definition + .borrow_mut() .as_mut() .unwrap() .add_argument(InputArgument::new( @@ -178,7 +190,7 @@ impl CommandData { description.to_string(), default.clone(), )?)?; - if let Some(full_definition) = self.full_definition.as_mut() { + if let Some(full_definition) = self.full_definition.borrow_mut().as_mut() { full_definition.add_argument(InputArgument::new( name.to_string(), mode, @@ -194,14 +206,15 @@ impl CommandData { /// /// Throws InvalidArgumentException if option mode is invalid or incompatible. pub fn add_option( - &mut self, + &self, name: &str, shortcut: PhpMixed, mode: Option<i64>, description: &str, default: PhpMixed, - ) -> anyhow::Result<&mut Self> { + ) -> anyhow::Result<&Self> { self.definition + .borrow_mut() .as_mut() .unwrap() .add_option(InputOption::new( @@ -211,7 +224,7 @@ impl CommandData { description.to_string(), default.clone(), )?)?; - if let Some(full_definition) = self.full_definition.as_mut() { + if let Some(full_definition) = self.full_definition.borrow_mut().as_mut() { full_definition.add_option(InputOption::new( name, shortcut, @@ -239,21 +252,22 @@ pub enum SetDefinitionArg { /// Each `Command`/`BaseCommand` implementer spells out the methods it delegates, one /// `delegate_to_inner!` per method, alongside the few methods it overrides by hand. /// The first argument names the field to forward to; the second is the method's -/// signature. Fluent setters returning `&mut Self` (optionally wrapped in -/// `anyhow::Result`) are handled specially so the returned reference is re-rooted at -/// the outer `self` rather than the inner field. +/// signature. Every method takes `&self` (the command state is interior-mutable); +/// fluent setters returning `&Self` (optionally wrapped in `anyhow::Result`) are handled +/// specially so the returned reference is re-rooted at the outer `self` rather than the +/// inner field. #[macro_export] macro_rules! delegate_to_inner { - // fluent fallible: -> anyhow::Result<&mut Self> - ($field:ident, fn $name:ident(&mut self $(, $arg:ident : $ty:ty )* $(,)?) -> anyhow::Result<&mut Self>) => { - fn $name(&mut self $(, $arg: $ty)*) -> anyhow::Result<&mut Self> { + // fluent fallible: -> anyhow::Result<&Self> + ($field:ident, fn $name:ident(&self $(, $arg:ident : $ty:ty )* $(,)?) -> anyhow::Result<&Self>) => { + fn $name(&self $(, $arg: $ty)*) -> anyhow::Result<&Self> { self.$field.$name($($arg),*)?; Ok(self) } }; - // fluent infallible: -> &mut Self - ($field:ident, fn $name:ident(&mut self $(, $arg:ident : $ty:ty )* $(,)?) -> &mut Self) => { - fn $name(&mut self $(, $arg: $ty)*) -> &mut Self { + // fluent infallible: -> &Self + ($field:ident, fn $name:ident(&self $(, $arg:ident : $ty:ty )* $(,)?) -> &Self) => { + fn $name(&self $(, $arg: $ty)*) -> &Self { self.$field.$name($($arg),*); self } @@ -270,18 +284,6 @@ macro_rules! delegate_to_inner { self.$field.$name($($arg),*) } }; - // &mut self with a return type - ($field:ident, fn $name:ident(&mut self $(, $arg:ident : $ty:ty )* $(,)?) -> $ret:ty) => { - fn $name(&mut self $(, $arg: $ty)*) -> $ret { - self.$field.$name($($arg),*) - } - }; - // &mut self without a return type - ($field:ident, fn $name:ident(&mut self $(, $arg:ident : $ty:ty )* $(,)?)) => { - fn $name(&mut self $(, $arg: $ty)*) { - self.$field.$name($($arg),*) - } - }; } /// Forwards every `Command` state method (the setters/getters whose canonical impl lives on @@ -293,33 +295,33 @@ macro_rules! delegate_to_inner { macro_rules! delegate_command_trait_impls_to_inner { ($field:ident) => { $crate::delegate_to_inner!($field, fn is_enabled(&self) -> bool); - $crate::delegate_to_inner!($field, fn set_application(&mut self, application: Option<std::rc::Rc<std::cell::RefCell<dyn $crate::symfony::console::application::Application>>>)); + $crate::delegate_to_inner!($field, fn set_application(&self, application: Option<std::rc::Rc<std::cell::RefCell<dyn $crate::symfony::console::application::Application>>>)); $crate::delegate_to_inner!($field, fn get_application(&self) -> Option<std::rc::Rc<std::cell::RefCell<dyn $crate::symfony::console::application::Application>>>); - $crate::delegate_to_inner!($field, fn set_helper_set(&mut self, helper_set: std::rc::Rc<std::cell::RefCell<$crate::symfony::console::helper::helper_set::HelperSet>>)); + $crate::delegate_to_inner!($field, fn set_helper_set(&self, helper_set: std::rc::Rc<std::cell::RefCell<$crate::symfony::console::helper::helper_set::HelperSet>>)); $crate::delegate_to_inner!($field, fn get_helper_set(&self) -> Option<std::rc::Rc<std::cell::RefCell<$crate::symfony::console::helper::helper_set::HelperSet>>>); - $crate::delegate_to_inner!($field, fn merge_application_definition(&mut self, merge_args: bool)); - $crate::delegate_to_inner!($field, fn get_definition(&self) -> &$crate::symfony::console::input::input_definition::InputDefinition); - $crate::delegate_to_inner!($field, fn get_native_definition(&self) -> &$crate::symfony::console::input::input_definition::InputDefinition); - $crate::delegate_to_inner!($field, fn set_name(&mut self, name: &str) -> anyhow::Result<()>); + $crate::delegate_to_inner!($field, fn merge_application_definition(&self, merge_args: bool)); + $crate::delegate_to_inner!($field, fn get_definition(&self) -> std::cell::Ref<'_, $crate::symfony::console::input::input_definition::InputDefinition>); + $crate::delegate_to_inner!($field, fn get_native_definition(&self) -> std::cell::Ref<'_, $crate::symfony::console::input::input_definition::InputDefinition>); + $crate::delegate_to_inner!($field, fn set_name(&self, name: &str) -> anyhow::Result<()>); $crate::delegate_to_inner!($field, fn get_name(&self) -> Option<String>); - $crate::delegate_to_inner!($field, fn set_process_title(&mut self, title: &str)); + $crate::delegate_to_inner!($field, fn set_process_title(&self, title: &str)); $crate::delegate_to_inner!($field, fn get_process_title(&self) -> Option<String>); - $crate::delegate_to_inner!($field, fn set_hidden(&mut self, hidden: bool)); + $crate::delegate_to_inner!($field, fn set_hidden(&self, hidden: bool)); $crate::delegate_to_inner!($field, fn is_hidden(&self) -> bool); - $crate::delegate_to_inner!($field, fn set_description(&mut self, description: &str)); + $crate::delegate_to_inner!($field, fn set_description(&self, description: &str)); $crate::delegate_to_inner!($field, fn get_description(&self) -> String); - $crate::delegate_to_inner!($field, fn set_help(&mut self, help: &str)); + $crate::delegate_to_inner!($field, fn set_help(&self, help: &str)); $crate::delegate_to_inner!($field, fn get_help(&self) -> String); $crate::delegate_to_inner!($field, fn get_processed_help(&self) -> String); - $crate::delegate_to_inner!($field, fn set_aliases(&mut self, aliases: Vec<String>) -> anyhow::Result<()>); + $crate::delegate_to_inner!($field, fn set_aliases(&self, aliases: Vec<String>) -> anyhow::Result<()>); $crate::delegate_to_inner!($field, fn get_aliases(&self) -> Vec<String>); - $crate::delegate_to_inner!($field, fn get_synopsis(&mut self, short: bool) -> String); - $crate::delegate_to_inner!($field, fn add_usage(&mut self, usage: &str)); + $crate::delegate_to_inner!($field, fn get_synopsis(&self, short: bool) -> String); + $crate::delegate_to_inner!($field, fn add_usage(&self, usage: &str)); $crate::delegate_to_inner!($field, fn get_usages(&self) -> Vec<String>); $crate::delegate_to_inner!($field, fn get_helper(&self, name: &str) -> anyhow::Result<Result<shirabe_php_shim::PhpMixed, $crate::symfony::console::exception::logic_exception::LogicException>>); - $crate::delegate_to_inner!($field, fn set_code(&mut self, code: Box<dyn Fn(&mut dyn $crate::symfony::console::input::InputInterface, &mut dyn $crate::symfony::console::output::OutputInterface) -> shirabe_php_shim::PhpMixed>)); - $crate::delegate_to_inner!($field, fn get_code(&self) -> Option<&Box<dyn Fn(&mut dyn $crate::symfony::console::input::InputInterface, &mut dyn $crate::symfony::console::output::OutputInterface) -> shirabe_php_shim::PhpMixed>>); - $crate::delegate_to_inner!($field, fn ignore_validation_errors(&mut self)); + $crate::delegate_to_inner!($field, fn set_code(&self, code: Box<dyn Fn(&mut dyn $crate::symfony::console::input::InputInterface, &mut dyn $crate::symfony::console::output::OutputInterface) -> shirabe_php_shim::PhpMixed>)); + $crate::delegate_to_inner!($field, fn get_code(&self) -> std::cell::Ref<'_, Option<Box<dyn Fn(&mut dyn $crate::symfony::console::input::InputInterface, &mut dyn $crate::symfony::console::output::OutputInterface) -> shirabe_php_shim::PhpMixed>>>); + $crate::delegate_to_inner!($field, fn ignore_validation_errors(&self)); $crate::delegate_to_inner!($field, fn get_ignore_validation_errors(&self) -> bool); }; } @@ -329,8 +331,8 @@ macro_rules! delegate_command_trait_impls_to_inner { /// /// The canonical behavior lives in `impl Command for CommandData`; subclasses forward /// the state methods there and override the behavior hooks (`configure`/`execute`/...). -/// Object-safe so `dyn Command` works; the fluent `where Self: Sized` setters are only -/// called from `configure()` on a concrete command, never through `dyn Command`. +/// Object-safe so `dyn Command` works. All methods take `&self`; the command's mutable +/// state is interior-mutable (see [`CommandData`]). pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { fn clone_box(&self) -> Box<dyn Command> { todo!() @@ -339,7 +341,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { // --- behavior hooks (PHP-overridable; defaults match the PHP `Command` class) --- /// Configures the current command. - fn configure(&mut self) -> anyhow::Result<()> { + fn configure(&self) -> anyhow::Result<()> { Ok(()) } @@ -348,7 +350,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { /// Concrete commands override this; reaching the default means a command class /// forgot to implement it (PHP throws LogicException — a programming error here). fn execute( - &mut self, + &self, _input: Rc<RefCell<dyn InputInterface>>, _output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { @@ -357,7 +359,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { /// Interacts with the user before the InputDefinition is validated. fn interact( - &mut self, + &self, _input: Rc<RefCell<dyn InputInterface>>, _output: Rc<RefCell<dyn OutputInterface>>, ) { @@ -365,7 +367,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { /// Initializes the command after the input has been bound and before it is validated. fn initialize( - &mut self, + &self, _input: Rc<RefCell<dyn InputInterface>>, _output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<()> { @@ -390,7 +392,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { /// not be overridden (except by proxy commands like `GlobalCommand`) nor delegated, /// or that late binding breaks. fn run( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { @@ -402,7 +404,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { /// matching PHP's `parent::run($input, $output)`. It must not be overridden, or the late /// binding of `initialize`/`interact`/`execute` to the concrete command breaks. fn base_run( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { @@ -410,7 +412,7 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { self.merge_application_definition(true); // bind the input against the command specific arguments/options - match input.borrow_mut().bind(self.get_definition()) { + match input.borrow_mut().bind(&*self.get_definition()) { Ok(()) => {} Err(e) => { if !self.get_ignore_validation_errors() { @@ -463,7 +465,9 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { input.borrow_mut().validate()?; let status_code: PhpMixed; - if let Some(code) = self.get_code() { + if self.get_code().is_some() { + let code = self.get_code(); + let code = code.as_ref().unwrap(); status_code = code(&mut *input.borrow_mut(), &mut *output.borrow_mut()); } else { let executed = self.execute(input.clone(), output.clone())?; @@ -480,49 +484,49 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { fn is_enabled(&self) -> bool; - fn set_application(&mut self, application: Option<Rc<RefCell<dyn Application>>>); + fn set_application(&self, application: Option<Rc<RefCell<dyn Application>>>); fn get_application(&self) -> Option<Rc<RefCell<dyn Application>>>; - fn set_helper_set(&mut self, helper_set: Rc<RefCell<HelperSet>>); + fn set_helper_set(&self, helper_set: Rc<RefCell<HelperSet>>); fn get_helper_set(&self) -> Option<Rc<RefCell<HelperSet>>>; - fn merge_application_definition(&mut self, merge_args: bool); + fn merge_application_definition(&self, merge_args: bool); - fn get_definition(&self) -> &InputDefinition; + fn get_definition(&self) -> Ref<'_, InputDefinition>; - fn get_native_definition(&self) -> &InputDefinition; + fn get_native_definition(&self) -> Ref<'_, InputDefinition>; - fn set_name(&mut self, name: &str) -> anyhow::Result<()>; + fn set_name(&self, name: &str) -> anyhow::Result<()>; fn get_name(&self) -> Option<String>; - fn set_process_title(&mut self, title: &str); + fn set_process_title(&self, title: &str); fn get_process_title(&self) -> Option<String>; - fn set_hidden(&mut self, hidden: bool); + fn set_hidden(&self, hidden: bool); fn is_hidden(&self) -> bool; - fn set_description(&mut self, description: &str); + fn set_description(&self, description: &str); fn get_description(&self) -> String; - fn set_help(&mut self, help: &str); + fn set_help(&self, help: &str); fn get_help(&self) -> String; fn get_processed_help(&self) -> String; - fn set_aliases(&mut self, aliases: Vec<String>) -> anyhow::Result<()>; + fn set_aliases(&self, aliases: Vec<String>) -> anyhow::Result<()>; fn get_aliases(&self) -> Vec<String>; - fn get_synopsis(&mut self, short: bool) -> String; + fn get_synopsis(&self, short: bool) -> String; - fn add_usage(&mut self, usage: &str); + fn add_usage(&self, usage: &str); fn get_usages(&self) -> Vec<String>; @@ -534,15 +538,15 @@ pub trait Command: std::fmt::Debug + shirabe_php_shim::AsAny { >; fn set_code( - &mut self, + &self, code: Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>, ); fn get_code( &self, - ) -> Option<&Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>>; + ) -> Ref<'_, Option<Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>>>; - fn ignore_validation_errors(&mut self); + fn ignore_validation_errors(&self); fn get_ignore_validation_errors(&self) -> bool; } @@ -552,32 +556,32 @@ impl Command for CommandData { true } - fn set_application(&mut self, application: Option<Rc<RefCell<dyn Application>>>) { - self.application = application.clone(); + fn set_application(&self, application: Option<Rc<RefCell<dyn Application>>>) { + *self.application.borrow_mut() = application.clone(); if let Some(application) = application { self.set_helper_set(application.borrow_mut().get_helper_set()); } else { - self.helper_set = None; + *self.helper_set.borrow_mut() = None; } - self.full_definition = None; + *self.full_definition.borrow_mut() = None; } fn get_application(&self) -> Option<Rc<RefCell<dyn Application>>> { - self.application.clone() + self.application.borrow().clone() } - fn set_helper_set(&mut self, helper_set: Rc<RefCell<HelperSet>>) { - self.helper_set = Some(helper_set); + fn set_helper_set(&self, helper_set: Rc<RefCell<HelperSet>>) { + *self.helper_set.borrow_mut() = Some(helper_set); } fn get_helper_set(&self) -> Option<Rc<RefCell<HelperSet>>> { - self.helper_set.clone() + self.helper_set.borrow().clone() } /// Merges the application definition with the command definition. - fn merge_application_definition(&mut self, merge_args: bool) { - let application = match &self.application { + fn merge_application_definition(&self, merge_args: bool) { + let application = match &*self.application.borrow() { None => return, Some(application) => application.clone(), }; @@ -591,6 +595,7 @@ impl Command for CommandData { let own_options: Vec<InputOption> = self .definition + .borrow() .as_ref() .unwrap() .get_options() @@ -624,6 +629,7 @@ impl Command for CommandData { let own_arguments: Vec<InputArgument> = self .definition + .borrow() .as_ref() .unwrap() .get_arguments() @@ -636,6 +642,7 @@ impl Command for CommandData { } else { let own_arguments: Vec<InputArgument> = self .definition + .borrow() .as_ref() .unwrap() .get_arguments() @@ -647,18 +654,22 @@ impl Command for CommandData { .expect("the command's own arguments are already valid"); } - self.full_definition = Some(full_definition); + *self.full_definition.borrow_mut() = Some(full_definition); } - fn get_definition(&self) -> &InputDefinition { - match &self.full_definition { - Some(full_definition) => full_definition, - None => self.get_native_definition(), + fn get_definition(&self) -> Ref<'_, InputDefinition> { + if self.full_definition.borrow().is_some() { + Ref::map(self.full_definition.borrow(), |full_definition| { + full_definition.as_ref().unwrap() + }) + } else { + self.get_native_definition() } } - fn get_native_definition(&self) -> &InputDefinition { - match &self.definition { + fn get_native_definition(&self) -> Ref<'_, InputDefinition> { + Ref::map(self.definition.borrow(), |definition| match definition { + Some(definition) => definition, None => { // PHP throws LogicException; `definition` is set in `new()`, so None is a // programming error (forgot to call the parent constructor). @@ -666,59 +677,58 @@ impl Command for CommandData { "Command class is not correctly initialized. You probably forgot to call the parent constructor." ); } - Some(definition) => definition, - } + }) } - fn set_name(&mut self, name: &str) -> anyhow::Result<()> { + fn set_name(&self, name: &str) -> anyhow::Result<()> { if let Err(e) = self.validate_name(name)? { return Err(e.into()); } - self.name = Some(name.to_string()); + *self.name.borrow_mut() = Some(name.to_string()); Ok(()) } fn get_name(&self) -> Option<String> { - self.name.clone() + self.name.borrow().clone() } - fn set_process_title(&mut self, title: &str) { - self.process_title = Some(title.to_string()); + fn set_process_title(&self, title: &str) { + *self.process_title.borrow_mut() = Some(title.to_string()); } fn get_process_title(&self) -> Option<String> { - self.process_title.clone() + self.process_title.borrow().clone() } - fn set_hidden(&mut self, hidden: bool) { - self.hidden = hidden; + fn set_hidden(&self, hidden: bool) { + self.hidden.set(hidden); } fn is_hidden(&self) -> bool { - self.hidden + self.hidden.get() } - fn set_description(&mut self, description: &str) { - self.description = description.to_string(); + fn set_description(&self, description: &str) { + *self.description.borrow_mut() = description.to_string(); } fn get_description(&self) -> String { - self.description.clone() + self.description.borrow().clone() } - fn set_help(&mut self, help: &str) { - self.help = help.to_string(); + fn set_help(&self, help: &str) { + *self.help.borrow_mut() = help.to_string(); } fn get_help(&self) -> String { - self.help.clone() + self.help.borrow().clone() } fn get_processed_help(&self) -> String { - let name = self.name.clone(); - let is_single_command = match &self.application { + let name = self.name.borrow().clone(); + let is_single_command = match &*self.application.borrow() { Some(application) => application.borrow().is_single_command(), None => false, }; @@ -747,7 +757,7 @@ impl Command for CommandData { shirabe_php_shim::str_replace_array(&placeholders, &replacements, &subject) } - fn set_aliases(&mut self, aliases: Vec<String>) -> anyhow::Result<()> { + fn set_aliases(&self, aliases: Vec<String>) -> anyhow::Result<()> { let mut list = Vec::new(); for alias in &aliases { @@ -759,44 +769,48 @@ impl Command for CommandData { // PHP: `\is_array($aliases) ? $aliases : $list`. Here `aliases` is always an // array (Vec), so the result is `aliases`; `list` mirrors the validation loop. - self.aliases = aliases; + *self.aliases.borrow_mut() = aliases; Ok(()) } fn get_aliases(&self) -> Vec<String> { - self.aliases.clone() + self.aliases.borrow().clone() } - fn get_synopsis(&mut self, short: bool) -> String { + fn get_synopsis(&self, short: bool) -> String { let key = if short { "short" } else { "long" }.to_string(); - if !self.synopsis.contains_key(&key) { + if !self.synopsis.borrow().contains_key(&key) { let value = format!( "{} {}", - self.name.clone().unwrap_or_default(), - self.definition.as_ref().unwrap().get_synopsis(short) + self.name.borrow().clone().unwrap_or_default(), + self.definition + .borrow() + .as_ref() + .unwrap() + .get_synopsis(short) ) .trim() .to_string(); - self.synopsis.insert(key.clone(), value); + self.synopsis.borrow_mut().insert(key.clone(), value); } - self.synopsis[&key].clone() + self.synopsis.borrow()[&key].clone() } - fn add_usage(&mut self, usage: &str) { + fn add_usage(&self, usage: &str) { let mut usage = usage.to_string(); - let name = self.name.clone().unwrap_or_default(); + let name = self.name.borrow().clone().unwrap_or_default(); if !usage.starts_with(&name) { usage = format!("{} {}", name, usage); } - self.usages.push(usage); + self.usages.borrow_mut().push(usage); } fn get_usages(&self) -> Vec<String> { - self.usages.clone() + self.usages.borrow().clone() } fn get_helper( @@ -805,7 +819,8 @@ impl Command for CommandData { ) -> anyhow::Result< Result<PhpMixed, crate::symfony::console::exception::logic_exception::LogicException>, > { - let helper_set = match &self.helper_set { + let helper_set_ref = self.helper_set.borrow(); + let helper_set = match &*helper_set_ref { None => { return Ok(Err( crate::symfony::console::exception::logic_exception::LogicException( @@ -832,37 +847,38 @@ impl Command for CommandData { } fn set_code( - &mut self, + &self, code: Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>, ) { // TODO: PHP rebinds an unbound Closure's $this to the command instance via // ReflectionFunction/Closure::bind. Rust closures have no `$this` rebinding; // the closure is stored as-is. - self.code = Some(code); + *self.code.borrow_mut() = Some(code); } fn get_code( &self, - ) -> Option<&Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>> { - self.code.as_ref() + ) -> Ref<'_, Option<Box<dyn Fn(&mut dyn InputInterface, &mut dyn OutputInterface) -> PhpMixed>>> + { + self.code.borrow() } - fn ignore_validation_errors(&mut self) { - self.ignore_validation_errors = true; + fn ignore_validation_errors(&self) { + self.ignore_validation_errors.set(true); } fn get_ignore_validation_errors(&self) -> bool { - self.ignore_validation_errors + self.ignore_validation_errors.get() } } impl std::fmt::Debug for CommandData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("CommandData") - .field("name", &self.name) - .field("aliases", &self.aliases) - .field("hidden", &self.hidden) - .field("description", &self.description) + .field("name", &self.name.borrow()) + .field("aliases", &self.aliases.borrow()) + .field("hidden", &self.hidden.get()) + .field("description", &self.description.borrow()) .finish_non_exhaustive() } } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs index 1bae4ad..cc700dc 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/complete_command.rs @@ -22,7 +22,7 @@ use crate::symfony::console::output::output_interface::OutputInterface; pub struct CompleteCommand { inner: CommandData, completion_outputs: IndexMap<String, PhpMixed>, - is_debug: bool, + is_debug: std::cell::Cell<bool>, } impl Deref for CompleteCommand { @@ -59,10 +59,10 @@ impl CompleteCommand { ) }); - let mut this = Self { + let this = Self { inner: CommandData::new(None), completion_outputs, - is_debug: false, + is_debug: std::cell::Cell::new(false), }; // PHP: static $defaultName = '|_complete' / $defaultDescription, applied by the parent // constructor before configure(). @@ -121,7 +121,7 @@ impl CompleteCommand { } fn log_many(&self, messages: Vec<String>) { - if !self.is_debug { + if !self.is_debug.get() { return; } @@ -159,7 +159,7 @@ fn instantiate_completion_output(_class: &PhpMixed) -> Box<dyn CompletionOutputI } impl Command for CompleteCommand { - fn configure(&mut self) -> anyhow::Result<()> { + fn configure(&self) -> anyhow::Result<()> { let shells = self .completion_outputs .keys() @@ -200,20 +200,20 @@ impl Command for CompleteCommand { } fn initialize( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<()> { let _ = (input, output); - self.is_debug = shirabe_php_shim::filter_var_boolean( + self.is_debug.set(shirabe_php_shim::filter_var_boolean( &shirabe_php_shim::getenv("SYMFONY_COMPLETION_DEBUG").unwrap_or_default(), - ); + )); Ok(()) } fn execute( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { @@ -307,8 +307,8 @@ impl Command for CompleteCommand { ); } Some(command) => { - command.borrow_mut().merge_application_definition(false); - completion_input.bind(command.borrow().get_definition())?; + command.borrow().merge_application_definition(false); + completion_input.bind(&*command.borrow().get_definition())?; if CompletionInput::TYPE_OPTION_NAME == completion_input.get_completion_type() { self.log(&format!( diff --git a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs index cb76ff8..306bd59 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/dump_completion_command.rs @@ -45,7 +45,7 @@ impl DumpCompletionCommand { pub const DEFAULT_DESCRIPTION: &'static str = "Dump the shell completion script"; pub fn new() -> Self { - let mut command = DumpCompletionCommand { + let command = DumpCompletionCommand { inner: CommandData::new(None), }; // PHP: static $defaultName = 'completion' / $defaultDescription, applied by the parent @@ -111,7 +111,7 @@ impl DumpCompletionCommand { } impl Command for DumpCompletionCommand { - fn configure(&mut self) -> anyhow::Result<()> { + fn configure(&self) -> anyhow::Result<()> { let full_command = shirabe_php_shim::server_php_self(); let command_name = shirabe_php_shim::basename(&full_command); // @realpath($fullCommand) ?: $fullCommand @@ -166,7 +166,7 @@ impl Command for DumpCompletionCommand { } fn execute( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { diff --git a/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs index 2346258..8ef488f 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/help_command.rs @@ -22,7 +22,7 @@ use std::rc::Rc; #[derive(Debug)] pub struct HelpCommand { inner: CommandData, - command: Option<Rc<RefCell<dyn Command>>>, + command: RefCell<Option<Rc<RefCell<dyn Command>>>>, } impl Deref for HelpCommand { @@ -47,9 +47,9 @@ impl Default for HelpCommand { impl HelpCommand { pub fn new() -> Self { - let mut command = HelpCommand { + let command = HelpCommand { inner: CommandData::new(None), - command: None, + command: RefCell::new(None), }; command .configure() @@ -57,8 +57,8 @@ impl HelpCommand { command } - pub fn set_command(&mut self, command: Rc<RefCell<dyn Command>>) { - self.command = Some(command); + pub fn set_command(&self, command: Rc<RefCell<dyn Command>>) { + *self.command.borrow_mut() = Some(command); } pub fn complete_impl(&self, input: &CompletionInput, suggestions: &mut CompletionSuggestions) { @@ -91,7 +91,7 @@ impl HelpCommand { } impl Command for HelpCommand { - fn configure(&mut self) -> anyhow::Result<()> { + fn configure(&self) -> anyhow::Result<()> { self.inner.ignore_validation_errors(); self.inner.set_name("help")?; @@ -134,24 +134,25 @@ impl Command for HelpCommand { } fn execute( - &mut self, + &self, input: Rc<RefCell<dyn InputInterface>>, output: Rc<RefCell<dyn OutputInterface>>, ) -> anyhow::Result<i64> { - if self.command.is_none() { + if self.command.borrow().is_none() { let application = self.get_application().unwrap(); let command_name = input.borrow().get_argument("command_name")?.to_string(); - self.command = Some(application.borrow_mut().find(&command_name)?); + let found = application.borrow_mut().find(&command_name)?; + *self.command.borrow_mut() = Some(found); } let mut helper = DescriptorHelper::new(); - let object = DescribableObject::Command(self.command.clone().unwrap()); + let object = DescribableObject::Command(self.command.borrow().clone().unwrap()); let mut options = indexmap::IndexMap::new(); options.insert("format".to_string(), input.borrow().get_option("format")?); options.insert("raw_text".to_string(), input.borrow().get_option("raw")?); helper.describe2(output.clone(), object, options)?; - self.command = None; + *self.command.borrow_mut() = None; Ok(0) } diff --git a/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs b/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs index 1d467ae..4f6e923 100644 --- a/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs +++ b/crates/shirabe-external-packages/src/symfony/console/command/list_command.rs @@ -46,7 +46,7 @@ impl Default for ListCommand { impl ListCommand { pub fn new() -> Self { - let mut command = ListCommand { + let command = ListCommand { inner: CommandData::new(None), }; command @@ -85,7 +85,7 @@ impl ListCommand { } impl Command for ListCommand { - fn configure(&mut self) -> anyhow::Result<()> { + fn configure(&self) -> anyhow::Result<()> { self.inner.set_name("list")?; self.inner.set_definition(SetDefinitionArg::Array(vec