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/package/loader/array_loader.rs | 94 +++++++++------------- .../package/loader/invalid_package_exception.rs | 14 ++-- crates/shirabe/src/package/loader/json_loader.rs | 5 +- .../src/package/loader/root_package_loader.rs | 30 +++---- .../src/package/loader/validating_array_loader.rs | 5 +- 5 files changed, 55 insertions(+), 93 deletions(-) (limited to 'crates/shirabe/src/package/loader') diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs index 0940183c..2c6d3971 100644 --- a/crates/shirabe/src/package/loader/array_loader.rs +++ b/crates/shirabe/src/package/loader/array_loader.rs @@ -69,23 +69,17 @@ impl ArrayLoader { class: &str, ) -> anyhow::Result { if !config.contains_key("name") { - return Err(UnexpectedValueException { - message: format!( - "Unknown package has no name defined ({}).", - json_encode(&PhpMixed::Array(config.clone())).unwrap_or_default() - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Unknown package has no name defined ({}).", + json_encode(&PhpMixed::Array(config.clone())).unwrap_or_default() + )) .into()); } if !config.contains_key("version") || !is_scalar(config.get("version").unwrap()) { - return Err(UnexpectedValueException { - message: format!( - "Package {} has no version defined.", - config.get("name").and_then(|v| v.as_string()).unwrap_or("") - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {} has no version defined.", + config.get("name").and_then(|v| v.as_string()).unwrap_or("") + )) .into()); } let mut config_version = config.get("version").cloned().unwrap_or(PhpMixed::Null); @@ -118,14 +112,11 @@ impl ArrayLoader { { Ok(v) => version = v, Err(e) => { - return Err(UnexpectedValueException { - message: format!( - "Failed to normalize version for package \"{}\": {}", - config.get("name").and_then(|v| v.as_string()).unwrap_or(""), - e - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Failed to normalize version for package \"{}\": {}", + config.get("name").and_then(|v| v.as_string()).unwrap_or(""), + e + )) .into()); } } @@ -226,18 +217,14 @@ impl ArrayLoader { }) .unwrap_or(false); if !has_required { - return Err(UnexpectedValueException { - message: format!( - "Package {}'s source key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ...}},\n{} given.", - - config - .get("name") - .and_then(|v| v.as_string()) - .unwrap_or(""), - json_encode(&source).unwrap_or_default(), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {}'s source key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ...}},\n{} given.", + config + .get("name") + .and_then(|v| v.as_string()) + .unwrap_or(""), + json_encode(&source).unwrap_or_default(), + )) .into()); } let source_map = source_map.unwrap(); @@ -270,18 +257,14 @@ impl ArrayLoader { .map(|m| m.contains_key("type") && m.contains_key("url")) .unwrap_or(false); if !has_required { - return Err(UnexpectedValueException { - message: format!( - "Package {}'s dist key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...}},\n{} given.", - - config - .get("name") - .and_then(|v| v.as_string()) - .unwrap_or(""), - json_encode(&dist).unwrap_or_default(), - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Package {}'s dist key should be specified as {{\"type\": ..., \"url\": ..., \"reference\": ..., \"shasum\": ...}},\n{} given.", + config + .get("name") + .and_then(|v| v.as_string()) + .unwrap_or(""), + json_encode(&dist).unwrap_or_default(), + )) .into()); } let dist_map = dist_map.unwrap(); @@ -672,13 +655,10 @@ impl ArrayLoader { let parsed_constraint = match self.version_parser.parse_constraints(&constraint) { Ok(c) => c, Err(_e) => { - return Err(UnexpectedValueException { - message: format!( - "Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"", - source, description, target, constraint - ), - code: 0, - } + return Err(UnexpectedValueException::new(format!( + "Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"", + source, description, target, constraint + )) .into()); } }; @@ -702,11 +682,9 @@ impl ArrayLoader { config: &IndexMap, ) -> anyhow::Result> { if !config.contains_key("version") || !is_scalar(config.get("version").unwrap()) { - return Err(UnexpectedValueException { - message: "no/invalid version defined".to_string(), - code: 0, - } - .into()); + return Err( + UnexpectedValueException::new("no/invalid version defined".to_string()).into(), + ); } let mut config_version = config.get("version").cloned().unwrap_or(PhpMixed::Null); if !is_string(&config_version) { diff --git a/crates/shirabe/src/package/loader/invalid_package_exception.rs b/crates/shirabe/src/package/loader/invalid_package_exception.rs index 2bd22c61..23251994 100644 --- a/crates/shirabe/src/package/loader/invalid_package_exception.rs +++ b/crates/shirabe/src/package/loader/invalid_package_exception.rs @@ -27,7 +27,7 @@ impl InvalidPackageException { .join("\n") ); Self { - inner: Exception { message, code: 0 }, + inner: Exception::new(message), errors, warnings, data, @@ -47,10 +47,8 @@ impl InvalidPackageException { } } -impl std::fmt::Display for InvalidPackageException { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.inner.message) - } -} - -impl std::error::Error for InvalidPackageException {} +shirabe_php_shim::impl_php_exception!( + InvalidPackageException, + inner, + r"Composer\Package\Loader\InvalidPackageException" +); diff --git a/crates/shirabe/src/package/loader/json_loader.rs b/crates/shirabe/src/package/loader/json_loader.rs index c30fbe07..d9e7100a 100644 --- a/crates/shirabe/src/package/loader/json_loader.rs +++ b/crates/shirabe/src/package/loader/json_loader.rs @@ -34,10 +34,7 @@ impl JsonLoader { let config: IndexMap = match config { PhpMixed::Array(m) => m, _ => { - return Err(TypeError { - message: "Composer\\Package\\Loader\\LoaderInterface::load(): Argument #1 ($config) must be of type array".to_string(), - code: 0, - } + return Err(TypeError::new("Composer\\Package\\Loader\\LoaderInterface::load(): Argument #1 ($config) must be of type array".to_string()) .into()); } }; diff --git a/crates/shirabe/src/package/loader/root_package_loader.rs b/crates/shirabe/src/package/loader/root_package_loader.rs index 284bb175..e26a7e95 100644 --- a/crates/shirabe/src/package/loader/root_package_loader.rs +++ b/crates/shirabe/src/package/loader/root_package_loader.rs @@ -78,10 +78,7 @@ impl RootPackageLoader { config["name"].as_string().unwrap_or(""), false, ) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("Your package name {}", err), - code: 0, - })); + return Err(RuntimeException::new(format!("Your package name {}", err)).into()); } let mut auto_versioned = false; @@ -197,13 +194,10 @@ impl RootPackageLoader { let package_name = config["name"].as_string().unwrap_or("").to_string(); if links.contains_key(&package_name) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!( - "Root package '{}' cannot require itself in its composer.json\nDid you accidentally name your root package after an external package?", - package_name - ), - code: 0, - })); + return Err(RuntimeException::new(format!( + "Root package '{}' cannot require itself in its composer.json\nDid you accidentally name your root package after an external package?", + package_name + )).into()); } } } @@ -216,10 +210,7 @@ impl RootPackageLoader { if let Some(err) = ValidatingArrayLoader::has_package_naming_error(link_name, true) { - return Err(anyhow::anyhow!(RuntimeException { - message: format!("{}.{}", link_type, err), - code: 0, - })); + return Err(RuntimeException::new(format!("{}.{}", link_type, err)).into()); } } } @@ -291,14 +282,11 @@ impl RootPackageLoader { return { panic!( "{}", - UnexpectedValueException { - message: format!( + UnexpectedValueException::new(format!( "Invalid alias definition in \"{}\": \"{}\". Aliases should be in the form \"exact-version as other-exact-version\".", req_name, req_version - ), - code: 0, - } - .message + )) + .get_message() ) }; } diff --git a/crates/shirabe/src/package/loader/validating_array_loader.rs b/crates/shirabe/src/package/loader/validating_array_loader.rs index a050f2d7..fc8eed00 100644 --- a/crates/shirabe/src/package/loader/validating_array_loader.rs +++ b/crates/shirabe/src/package/loader/validating_array_loader.rs @@ -1583,11 +1583,12 @@ impl LoaderInterface for ValidatingArrayLoader { } if !self.errors.borrow().is_empty() { - return Err(anyhow::anyhow!(InvalidPackageException::new( + return Err(InvalidPackageException::new( self.errors.borrow().clone(), self.warnings.borrow().clone(), config.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), - ))); + ) + .into()); } let package = self.loader.load( -- cgit v1.3.1-4-g156e