diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-08-30 23:01:25 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-08-30 23:09:56 +0900 |
| commit | d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49 (patch) | |
| tree | a745ecc3403104d5a34f29964362e239e8f5675b /crates/shirabe-php-rpc/src | |
| parent | 057f3b8de26293319e265c1d86d9a1153124f3c7 (diff) | |
| download | php-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.tar.gz php-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.tar.zst php-shirabe-d3bc3354c9705dfc6dc5e9b9adb5eb64d41e4c49.zip | |
feat(plugin): serve ProcessExecutor as a proxy stub
Composer reaches this class two ways: the object graph hands one out through
Composer::getLoop()->getProcessExecutor(), and plugins write
`new ProcessExecutor($io)` freely. Both bind to a Rust-side entity, so the
timeout the run shares -- seeded from process-timeout and rewritten while the
run is in flight -- has one value instead of one per world, and the executor
can still be passed to the classes that take one (`new Filesystem($process)`).
Three things the stub generator was missing came with it:
- By-ref parameters. The call carries their positions and the answer carries
what each holds afterwards; a position the answer omits was never assigned
to, which is what PHP does with an untouched by-ref parameter.
ProcessExecutor::execute is the only one on a proxied class.
- Argument arity, reproduced where the real body reads func_num_args().
execute($cmd) forwards the child's output and execute($cmd, $out) captures
it, and nothing but the argument count separates the two.
- Static methods that cannot run in the worker. One that reads a static
property the Rust side owns, or that reaches a guarded class, forwards
through __shirabeCallStatic instead of being materialized. That also fixes
Filesystem::isLocalPath and getPlatformPath, whose materialized bodies
called the guarded Composer\Util\Platform.
The async surface stays an explicit error. executeAsync resolves its promise
with a Symfony Process, whose proc_open() resource and pipes belong to
whichever process called start(), so a Rust-side spawn has none to hand back;
running the real start() in the worker needs a promise representation that
crosses the boundary unresolved.
The fixture project drives the whole synchronous surface from plugin code and
compares the trace against upstream Composer byte for byte.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe-php-rpc/src')
| -rw-r--r-- | crates/shirabe-php-rpc/src/lib.rs | 24 |
1 files changed, 20 insertions, 4 deletions
diff --git a/crates/shirabe-php-rpc/src/lib.rs b/crates/shirabe-php-rpc/src/lib.rs index b2bc2c28..dfd8fa67 100644 --- a/crates/shirabe-php-rpc/src/lib.rs +++ b/crates/shirabe-php-rpc/src/lib.rs @@ -643,6 +643,12 @@ impl std::error::Error for PhpThrow {} /// Handles `CallRustMethod` requests arriving while a Rust-initiated call is waiting for its /// `Return` (the cooperative reentrancy loop). A handler may itself issue nested RPC calls. +/// +/// `out_param_positions` names the by-ref parameters of the called method, as the calling stub +/// declared them. A handler that assigns to one writes the resulting value into `out_params` under +/// the same position, and the stub copies it back into the caller's variable; leaving a position +/// out means the method never assigned to it, which is what PHP does with an untouched by-ref +/// parameter. pub trait RustMethodDispatcher { fn dispatch( &mut self, @@ -650,6 +656,7 @@ pub trait RustMethodDispatcher { method_name: &str, args: Vec<PluginValue>, out_param_positions: &[u32], + out_params: &mut IndexMap<u32, PluginValue>, ) -> Result<PluginValue, PhpThrow>; } @@ -812,10 +819,15 @@ fn rpc_call( args, out_param_positions, } => { + let mut out_params = IndexMap::new(); let outcome = match dispatcher.as_deref_mut() { - Some(dispatcher) => { - dispatcher.dispatch(rhandle, &method_name, args, &out_param_positions) - } + Some(dispatcher) => dispatcher.dispatch( + rhandle, + &method_name, + args, + &out_param_positions, + &mut out_params, + ), // Never fall back to a silent null: an unroutable callback is reported as an // explicit error on the PHP side. None => Err(PhpThrow::runtime(format!( @@ -827,7 +839,7 @@ fn rpc_call( Ok(value) => Frame::Return { corr_id, value, - out_params: IndexMap::new(), + out_params, }, Err(throw) => Frame::Throw { corr_id, @@ -1011,6 +1023,10 @@ const STUB_FILES: &[(&str, &str)] = &[ "Composer/Util/Filesystem.php", include_str!("../php/stubs/Composer/Util/Filesystem.php"), ), + ( + "Composer/Util/ProcessExecutor.php", + include_str!("../php/stubs/Composer/Util/ProcessExecutor.php"), + ), ]; /// Hand-written worker-side classes (two-world implementations with behavior of their own, not |
