From 90d9b1dc0035a70dab76d520b9dcd14ec57273ad Mon Sep 17 00:00:00 2001 From: nsfisis Date: Fri, 24 Jul 2026 20:23:42 +0900 Subject: fix(event-dispatcher): invoke Closure listeners instead of always failing is_callable RequireCommand registers an inline listener on InstallerEvents::PRE_OPERATIONS_EXEC to track dependency_resolution_completed, mirroring PHP's `function () use (&$dependencyResolutionCompleted) { ... }`. This is Composer's own code, not a Plugin subscriber, but it went through the shared non-string-callable path, which checked is_callable() against a hardcoded PhpMixed::Null and always failed, breaking every `require` that reaches the install step. Callable::Closure now carries the actual Rc instead of being a data-less placeholder, and is invoked directly (Closures are always callable in PHP). The ArrayCallable path used by future Plugin subscribers is untouched. Un-ignoring the two require_command_test cases that cited this bug reveals two separate, pre-existing issues (a missing ext-requirement warning message, and a RefCell re-entrancy panic in ConsoleIO::ask_question); their #[ignore] reasons are updated to describe the real current blocker instead of the now-fixed one. --- crates/shirabe/src/command/require_command.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'crates/shirabe/src/command/require_command.rs') diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs index 05f86480..c1730942 100644 --- a/crates/shirabe/src/command/require_command.rs +++ b/crates/shirabe/src/command/require_command.rs @@ -53,7 +53,7 @@ pub struct RequireCommand { lock: std::cell::RefCell, /// contents before modification if the lock file exists lock_backup: std::cell::RefCell>, - dependency_resolution_completed: std::cell::Cell, + dependency_resolution_completed: std::rc::Rc>, repos: std::cell::RefCell>, repository_sets: std::cell::RefCell>>>, @@ -76,7 +76,7 @@ impl RequireCommand { composer_backup: std::cell::RefCell::new(String::new()), lock: std::cell::RefCell::new(String::new()), lock_backup: std::cell::RefCell::new(None), - dependency_resolution_completed: std::cell::Cell::new(false), + dependency_resolution_completed: std::rc::Rc::new(std::cell::Cell::new(false)), repos: std::cell::RefCell::new(None), repository_sets: std::cell::RefCell::new(IndexMap::new()), }; @@ -759,14 +759,13 @@ impl RequireCommand { self.dependency_resolution_completed.set(false); // PHP: $composer->getEventDispatcher()->addListener(InstallerEvents::PRE_OPERATIONS_EXEC, // function () use (&$dependencyResolutionCompleted) { $dependencyResolutionCompleted = true; }, 10000); - // TODO(phase-c): the event dispatcher's Callable::Closure is a placeholder variant that - // stores no actual closure, so the listener that flips dependency_resolution_completed - // cannot be registered. Resolving needs the closure model (Callable holding an Rc) - // plus dependency_resolution_completed shared (Rc>) into both the listener - // and this command. + let dependency_resolution_completed = self.dependency_resolution_completed.clone(); composer.get_event_dispatcher().borrow_mut().add_listener( InstallerEvents::PRE_OPERATIONS_EXEC, - crate::event_dispatcher::Callable::Closure, + crate::event_dispatcher::Callable::Closure(std::rc::Rc::new(move |_event| { + dependency_resolution_completed.set(true); + PhpMixed::Null + })), 10000, ); -- cgit v1.3.1