From 1b38bdb5a1de127ca8040f5a132d08c73ccb3c67 Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 20 Jun 2026 15:37:24 +0900 Subject: refactor(console): propagate getComposer's exit(1) as ExitException Composer's Application::getComposer() calls exit(1) when a required Composer instance fails to build under areExceptionsCaught(). Replace the deep std::process::exit with a php-shim ExitException that mirrors PHP's `exit` construct: it bypasses parent::doRun()'s catch and Symfony's renderThrowable (excluded at both broad catches), so the already-written plain error message is not re-rendered and the process exits with code 1. Co-Authored-By: Claude Opus 4.8 (1M context) --- crates/shirabe-php-shim/src/lib.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'crates/shirabe-php-shim') diff --git a/crates/shirabe-php-shim/src/lib.rs b/crates/shirabe-php-shim/src/lib.rs index 4f7b4d2..949c5e2 100644 --- a/crates/shirabe-php-shim/src/lib.rs +++ b/crates/shirabe-php-shim/src/lib.rs @@ -3150,6 +3150,25 @@ pub fn exit(status: i64) -> ! { std::process::exit(status as i32); } +/// Models PHP's `exit`/`die` language construct propagated as a recoverable error so the actual +/// process termination happens at a single top-level site instead of deep in the call stack. +/// +/// Like PHP's `exit`, this must NOT be caught by ported `try`/`catch` blocks: any broad catch on +/// the propagation path has to re-raise it untouched, and only the outermost handler converts it +/// into the process exit code. +#[derive(Debug)] +pub struct ExitException { + pub code: i64, +} + +impl std::fmt::Display for ExitException { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "exit({})", self.code) + } +} + +impl std::error::Error for ExitException {} + pub fn is_resource_value(_resource: &PhpResource) -> bool { true } -- cgit v1.3.1