From f4cad2123b2af0de72bda4ce039e16e74f163f4e Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sat, 8 Aug 2026 22:14:12 +0900 Subject: feat(php-shim): give ported exceptions PHP's class hierarchy Ported exceptions were flat structs reached with `downcast_ref`, so Composer's `catch (\RuntimeException $e)` only matched the exact leaf type and `get_class($e)` had nothing to report. Each exception now embeds an instance of the class it extends and travels inside an `AnyThrowable`; `Catch::catch`/`catch_mut` walk that chain, and `PhpClass::php_class_name` yields the PHP FQCN. Dropping the `std::error::Error` impls from the exception types leaves `AnyThrowable` as the only route into an `anyhow::Error`, so the walk cannot be bypassed. A `no_exception_downcast` linter catches the `downcast::()` calls that would now silently answer `None`. Three sites change behavior as a result: the `TransportException` exit-code override reaches `MaxFileSizeExceededException`, the `catch (\LogicException)` in findSimilar() reaches its subclasses, and rendered exception titles carry the real class name rather than a guess. `get_class_err()` is no longer a `todo!()`, which re-enables FilesystemRepositoryTest::testCorruptedRepositoryFile. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe/src/downloader/vcs_downloader.rs | 78 ++++++++++++++----------- 1 file changed, 44 insertions(+), 34 deletions(-) (limited to 'crates/shirabe/src/downloader/vcs_downloader.rs') diff --git a/crates/shirabe/src/downloader/vcs_downloader.rs b/crates/shirabe/src/downloader/vcs_downloader.rs index 45ca0e3a..56bdea60 100644 --- a/crates/shirabe/src/downloader/vcs_downloader.rs +++ b/crates/shirabe/src/downloader/vcs_downloader.rs @@ -18,8 +18,9 @@ use crate::util::Filesystem; use crate::util::ProcessExecutor; use indexmap::IndexMap; use shirabe_php_shim::{ - InvalidArgumentException, PhpMixed, RuntimeException, array_map, array_shift, explode, - get_class_err, implode, rawurldecode, realpath, str_replace, strlen, strpos, substr, trim, + AnyThrowable, InvalidArgumentException, PhpClass as _, PhpMixed, RuntimeException, array_map, + array_shift, explode, implode, rawurldecode, realpath, str_replace, strlen, strpos, substr, + trim, }; #[derive(Debug)] @@ -129,13 +130,10 @@ pub trait VcsDownloader: prev_package: Option, ) -> anyhow::Result> { if package.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - package.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + package.get_pretty_name(), + )) .into()); } @@ -157,7 +155,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -232,13 +236,10 @@ pub trait VcsDownloader: path: &str, ) -> anyhow::Result> { if package.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - package.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + package.get_pretty_name(), + )) .into()); } @@ -264,7 +265,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -292,13 +299,10 @@ pub trait VcsDownloader: path: &str, ) -> anyhow::Result> { if target.get_source_reference().is_none() { - return Err(InvalidArgumentException { - message: format!( - "Package {} is missing reference information", - target.get_pretty_name(), - ), - code: 0, - } + return Err(InvalidArgumentException::new(format!( + "Package {} is missing reference information", + target.get_pretty_name(), + )) .into()); } @@ -333,7 +337,13 @@ pub trait VcsDownloader: } if self.io().is_debug() { self.io().write_error3( - &format!("Failed: [{}] {}", get_class_err(&e), e), + &format!( + "Failed: [{}] {}", + AnyThrowable::of(e.as_ref()) + .expect("PHP reaches this only with a caught \\Throwable") + .php_class_name(), + e + ), true, io_interface::NORMAL, ); @@ -400,10 +410,10 @@ pub trait VcsDownloader: let result = Filesystem::remove_directory_async_via(self.filesystem(), path).await?; if !result { - return Err(RuntimeException { - message: format!("Could not completely delete {}, aborting.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Could not completely delete {}, aborting.", + path + )) .into()); } @@ -441,10 +451,10 @@ pub trait VcsDownloader: ) -> anyhow::Result> { // the default implementation just fails if there are any changes, override in child classes to provide stash-ability if self.get_local_changes(package, path)?.is_some() { - return Err(RuntimeException { - message: format!("Source directory {} has uncommitted changes.", path), - code: 0, - } + return Err(RuntimeException::new(format!( + "Source directory {} has uncommitted changes.", + path + )) .into()); } -- cgit v1.3.1-4-g156e