aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-08 22:37:08 +0900
committernsfisis <nsfisis@gmail.com>2026-08-09 00:14:09 +0900
commitc74f4314853c0e7283691fdd4d0dec27c1199537 (patch)
tree2dd1c0a593498da1ac38cd1e61a92cce6dc78aa5
parentf4cad2123b2af0de72bda4ce039e16e74f163f4e (diff)
downloadphp-shirabe-c74f4314853c0e7283691fdd4d0dec27c1199537.tar.gz
php-shirabe-c74f4314853c0e7283691fdd4d0dec27c1199537.tar.zst
php-shirabe-c74f4314853c0e7283691fdd4d0dec27c1199537.zip
fix(exception): carry PHP's $previous through the ported throw sites
Composer hands the exception it caught to the one it throws in its place, so `getPrevious()` reaches the cause and Application's renderer prints the whole chain. Every ported site dropped it, because the flat exception structs had nowhere to put one. `AnyThrowable::into_previous` turns the caught error into that argument, and the 13 sites now pass it. `getCode()` came along for the ride at the four sites that derive the new exception's code from the caught one (PharArchiver, ArrayLoader x2), and ComposerRepository's message now names the caught exception's class instead of the literal "Exception". GitHubDriver::attemptCloneFallback took the previous exception's message and appended it to its own, which no `\RuntimeException('Fallback to git driver disabled')` in Composer ever says; it now chains it instead. Git::syncMirror restores what PHP's `finally` does to an exception in flight: the `git remote set-url` that scrubs credentials back out of the URL runs in a `finally`, and when it fails PHP propagates *its* exception over the one already leaving, chaining the displaced one as previous. The port discarded the finally's result, so a failure to scrub the URL was reported as a successful mirror sync. `AnyThrowable::set_previous` models the engine-level chaining. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
-rw-r--r--crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs2
-rw-r--r--crates/shirabe-php-shim/src/exception.rs70
-rw-r--r--crates/shirabe/src/command/exec_command.rs9
-rw-r--r--crates/shirabe/src/command/global_command.rs8
-rw-r--r--crates/shirabe/src/command/require_command.rs12
-rw-r--r--crates/shirabe/src/config/json_config_source.rs9
-rw-r--r--crates/shirabe/src/downloader/zip_downloader.rs6
-rw-r--r--crates/shirabe/src/json/json_file.rs8
-rw-r--r--crates/shirabe/src/package/archiver/phar_archiver.rs6
-rw-r--r--crates/shirabe/src/package/loader/array_loader.rs27
-rw-r--r--crates/shirabe/src/repository/artifact_repository.rs14
-rw-r--r--crates/shirabe/src/repository/composer_repository.rs18
-rw-r--r--crates/shirabe/src/repository/path_repository.rs9
-rw-r--r--crates/shirabe/src/repository/vcs/github_driver.rs18
-rw-r--r--crates/shirabe/src/util/git.rs17
15 files changed, 170 insertions, 63 deletions
diff --git a/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs b/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs
index ffe80ea1..07a189c3 100644
--- a/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs
+++ b/crates/shirabe-external-packages/src/symfony/filesystem/exception/io_exception.rs
@@ -12,7 +12,7 @@ impl IOException {
pub fn new(
message: String,
code: i64,
- previous: Option<std::sync::Arc<shirabe_php_shim::AnyThrowable>>,
+ previous: Option<std::sync::Arc<anyhow::Error>>,
path: Option<String>,
) -> Self {
Self {
diff --git a/crates/shirabe-php-shim/src/exception.rs b/crates/shirabe-php-shim/src/exception.rs
index 803d5b64..fd5e4a53 100644
--- a/crates/shirabe-php-shim/src/exception.rs
+++ b/crates/shirabe-php-shim/src/exception.rs
@@ -1,12 +1,16 @@
use crate::PhpClass;
-/// The fields a PHP `\Throwable` carries: its message and code, and the exception it wraps. Ported
+/// The fields a PHP `\Throwable` carries: its message and code, and the error it wraps. Ported
/// exception types embed this, either directly or through the parent exception they extend.
+///
+/// `previous` is any error rather than an [`AnyThrowable`], because the port reaches a `catch`
+/// carrying errors PHP would have raised as exception objects and the port raises as itself. One
+/// that does carry an exception is still reachable as such, through [`Catch`].
#[derive(Debug, Clone)]
pub struct ThrowableFields {
message: String,
code: i64,
- previous: Option<std::sync::Arc<AnyThrowable>>,
+ previous: Option<std::sync::Arc<anyhow::Error>>,
}
impl ThrowableFields {
@@ -23,7 +27,7 @@ impl ThrowableFields {
self.code = code;
}
- pub fn get_previous(&self) -> Option<&AnyThrowable> {
+ pub fn get_previous(&self) -> Option<&anyhow::Error> {
self.previous.as_deref()
}
}
@@ -97,6 +101,15 @@ impl AnyThrowable {
error.downcast_ref::<Self>()
}
+ /// PHP has no `setPrevious`: an exception gets its `previous` from its constructor. The one
+ /// exception is a `finally` throwing over an exception already on its way out — the one the
+ /// `finally` threw propagates, and the engine makes the one it displaced its `previous`.
+ pub fn set_previous(&mut self, previous: std::sync::Arc<anyhow::Error>) {
+ self.downcast_mut::<ThrowableFields>()
+ .expect("every exception bottoms out at the ThrowableFields")
+ .previous = Some(previous);
+ }
+
/// PHP's `catch (T $e)`: the exception seen as an instance of `T`, or `None` if it is not one.
/// A subclass answers through the instance of `T` it embeds, so `T`'s own state is reachable
/// the way PHP reaches an inherited property.
@@ -135,7 +148,7 @@ impl AnyThrowable {
self.0.fields().get_code()
}
- pub fn get_previous(&self) -> Option<&AnyThrowable> {
+ pub fn get_previous(&self) -> Option<&anyhow::Error> {
self.0.fields().get_previous()
}
}
@@ -149,7 +162,7 @@ impl std::fmt::Display for AnyThrowable {
impl std::error::Error for AnyThrowable {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.get_previous()
- .map(|previous| previous as &(dyn std::error::Error + 'static))
+ .map(|previous| &**previous as &(dyn std::error::Error + 'static))
}
}
@@ -277,7 +290,7 @@ macro_rules! impl_php_exception {
self.$field.set_code(code);
}
- pub fn get_previous(&self) -> Option<&$crate::AnyThrowable> {
+ pub fn get_previous(&self) -> Option<&::anyhow::Error> {
self.$field.get_previous()
}
}
@@ -306,7 +319,7 @@ macro_rules! define_php_exception {
pub fn with_code_and_previous(
message: String,
code: i64,
- previous: Option<std::sync::Arc<AnyThrowable>>,
+ previous: Option<std::sync::Arc<anyhow::Error>>,
) -> Self {
Self {
inner: ThrowableFields {
@@ -325,7 +338,7 @@ macro_rules! define_php_exception {
pub fn with_code_and_previous(
message: String,
code: i64,
- previous: Option<std::sync::Arc<AnyThrowable>>,
+ previous: Option<std::sync::Arc<anyhow::Error>>,
) -> Self {
Self {
inner: <$parent>::with_code_and_previous(message, code, previous),
@@ -399,7 +412,7 @@ impl ErrorException {
severity: i64,
filename: String,
lineno: i64,
- previous: Option<std::sync::Arc<AnyThrowable>>,
+ previous: Option<std::sync::Arc<anyhow::Error>>,
) -> Self {
Self {
inner: Exception::with_code_and_previous(message, code, previous),
@@ -518,8 +531,45 @@ mod tests {
}
#[test]
+ fn the_previous_is_any_error_a_catch_can_reach() {
+ let previous = std::sync::Arc::new(anyhow::Error::new(std::io::Error::other("io")));
+ let error: anyhow::Error =
+ Exception::with_code_and_previous("boom".to_string(), 0, Some(previous)).into();
+
+ assert_eq!(
+ error
+ .catch::<Exception>()
+ .and_then(|e| e.get_previous())
+ .map(ToString::to_string),
+ Some("io".to_string())
+ );
+ }
+
+ #[test]
+ fn set_previous_reaches_the_superclass_holding_it() {
+ let pending = std::sync::Arc::new(anyhow::Error::from(RuntimeException::new(
+ "first".to_string(),
+ )));
+ let mut error: anyhow::Error = Subclass::new(7).into();
+
+ error
+ .downcast_mut::<AnyThrowable>()
+ .unwrap()
+ .set_previous(pending);
+
+ assert_eq!(
+ error
+ .catch::<Subclass>()
+ .and_then(|e| e.get_previous())
+ .and_then(|previous| previous.catch::<RuntimeException>())
+ .map(|previous| previous.get_message().to_string()),
+ Some("first".to_string())
+ );
+ }
+
+ #[test]
fn the_previous_exception_is_the_error_source() {
- let previous = std::sync::Arc::new(AnyThrowable::new(RuntimeException::new(
+ let previous = std::sync::Arc::new(anyhow::Error::from(RuntimeException::new(
"cause".to_string(),
)));
let error: anyhow::Error =
diff --git a/crates/shirabe/src/command/exec_command.rs b/crates/shirabe/src/command/exec_command.rs
index a67c234e..61391117 100644
--- a/crates/shirabe/src/command/exec_command.rs
+++ b/crates/shirabe/src/command/exec_command.rs
@@ -220,10 +220,11 @@ impl Command for ExecCommand {
&& getcwd().as_deref() != Some(iwd.as_str())
{
chdir(iwd).map_err(|e| {
- RuntimeException::new(format!(
- "Could not switch back to working directory \"{}\"",
- iwd
- ))
+ RuntimeException::with_code_and_previous(
+ format!("Could not switch back to working directory \"{}\"", iwd),
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
})?;
}
diff --git a/crates/shirabe/src/command/global_command.rs b/crates/shirabe/src/command/global_command.rs
index 78b6f3e6..df9798a3 100644
--- a/crates/shirabe/src/command/global_command.rs
+++ b/crates/shirabe/src/command/global_command.rs
@@ -90,8 +90,12 @@ impl GlobalCommand {
}
}
- chdir(&home).map_err(|_e| {
- RuntimeException::new(format!("Could not switch to home directory \"{}\"", home))
+ chdir(&home).map_err(|e| {
+ RuntimeException::with_code_and_previous(
+ format!("Could not switch to home directory \"{}\"", home),
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
})?;
if !quiet {
diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs
index 84b4a6e4..88916175 100644
--- a/crates/shirabe/src/command/require_command.rs
+++ b/crates/shirabe/src/command/require_command.rs
@@ -977,10 +977,14 @@ impl Command for RequireCommand {
if self.newly_created.get() {
self.revert_composer_file();
- return Err(RuntimeException::new(format!(
- "No composer.json present in the current directory ({}), this may be the cause of the following exception.",
- self.file.borrow()
- ))
+ return Err(RuntimeException::with_code_and_previous(
+ format!(
+ "No composer.json present in the current directory ({}), this may be the cause of the following exception.",
+ self.file.borrow()
+ ),
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
.into());
}
diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs
index 0257ee31..59146630 100644
--- a/crates/shirabe/src/config/json_config_source.rs
+++ b/crates/shirabe/src/config/json_config_source.rs
@@ -148,11 +148,16 @@ impl JsonConfigSource {
};
// restore contents to the original state
file_put_contents(self.file.borrow().get_path(), contents.as_bytes());
- return Err(RuntimeException::new(format!(
+ let message = format!(
"Failed to update composer.json with a valid format, reverting to the original content. Please report an issue to us with details (command you run and a copy of your composer.json). {}{}",
PHP_EOL,
implode(PHP_EOL, jve.get_errors()),
- ))
+ );
+ return Err(RuntimeException::with_code_and_previous(
+ message,
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
.into());
}
}
diff --git a/crates/shirabe/src/downloader/zip_downloader.rs b/crates/shirabe/src/downloader/zip_downloader.rs
index 84ed4c9e..c481f13f 100644
--- a/crates/shirabe/src/downloader/zip_downloader.rs
+++ b/crates/shirabe/src/downloader/zip_downloader.rs
@@ -361,11 +361,13 @@ impl ZipDownloader {
result.map_err(|e| {
if let Some(err) = e.catch::<ErrorException>() {
- RuntimeException::new(format!(
+ let message = format!(
"The archive for \"{}\" may contain identical file names with different capitalization (which fails on case insensitive filesystems): {}",
package.get_name(),
err.get_message(),
- )).into()
+ );
+ RuntimeException::with_code_and_previous(message, 0, Some(std::sync::Arc::new(e)))
+ .into()
} else {
e
}
diff --git a/crates/shirabe/src/json/json_file.rs b/crates/shirabe/src/json/json_file.rs
index 2d061ced..da6370e4 100644
--- a/crates/shirabe/src/json/json_file.rs
+++ b/crates/shirabe/src/json/json_file.rs
@@ -174,7 +174,13 @@ impl JsonFile {
// TransportException keeps its message verbatim; any other exception is wrapped
// with the "Could not read" prefix.
if let Some(te) = e.catch::<TransportException>() {
- return Err(RuntimeException::new(te.get_message().to_string()).into());
+ let message = te.get_message().to_string();
+ return Err(RuntimeException::with_code_and_previous(
+ message,
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
+ .into());
}
return Err(RuntimeException::new(format!(
"Could not read {}\n\n{}",
diff --git a/crates/shirabe/src/package/archiver/phar_archiver.rs b/crates/shirabe/src/package/archiver/phar_archiver.rs
index 0968f2a4..4a41d5a4 100644
--- a/crates/shirabe/src/package/archiver/phar_archiver.rs
+++ b/crates/shirabe/src/package/archiver/phar_archiver.rs
@@ -5,7 +5,7 @@ use crate::package::archiver::ArchivableFilesFinder;
use crate::package::archiver::ArchiverInterface;
use indexmap::IndexMap;
use shirabe_php_shim::{
- FilesystemIterator, Phar, PharData, RuntimeException, bzcompress, file_exists,
+ AnyThrowable, FilesystemIterator, Phar, PharData, RuntimeException, bzcompress, file_exists,
file_put_contents, function_exists, gzcompress, str_repeat, strrpos, unlink,
};
@@ -147,7 +147,9 @@ impl ArchiverInterface for PharArchiver {
"Could not create archive '{}' from '{}': {}",
target_outer, sources, e
);
- RuntimeException::new(message).into()
+ let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code);
+ RuntimeException::with_code_and_previous(message, code, Some(std::sync::Arc::new(e)))
+ .into()
})
}
diff --git a/crates/shirabe/src/package/loader/array_loader.rs b/crates/shirabe/src/package/loader/array_loader.rs
index 2c6d3971..106f094e 100644
--- a/crates/shirabe/src/package/loader/array_loader.rs
+++ b/crates/shirabe/src/package/loader/array_loader.rs
@@ -19,8 +19,9 @@ use chrono::Utc;
use indexmap::IndexMap;
use shirabe_external_packages::composer::pcre::Preg;
use shirabe_php_shim::{
- E_USER_DEPRECATED, PhpMixed, UnexpectedValueException, is_scalar, is_string, json_encode,
- ltrim, php_regex, stripos, strpos, strtolower, strval, substr, trigger_error, trim,
+ AnyThrowable, E_USER_DEPRECATED, PhpMixed, UnexpectedValueException, is_scalar, is_string,
+ json_encode, ltrim, php_regex, stripos, strpos, strtolower, strval, substr, trigger_error,
+ trim,
};
#[derive(Debug)]
@@ -112,11 +113,17 @@ impl ArrayLoader {
{
Ok(v) => version = v,
Err(e) => {
- return Err(UnexpectedValueException::new(format!(
+ let message = format!(
"Failed to normalize version for package \"{}\": {}",
config.get("name").and_then(|v| v.as_string()).unwrap_or(""),
e
- ))
+ );
+ let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code);
+ return Err(UnexpectedValueException::with_code_and_previous(
+ message,
+ code,
+ Some(std::sync::Arc::new(e)),
+ )
.into());
}
}
@@ -654,11 +661,17 @@ impl ArrayLoader {
let parsed_constraint = match self.version_parser.parse_constraints(&constraint) {
Ok(c) => c,
- Err(_e) => {
- return Err(UnexpectedValueException::new(format!(
+ Err(e) => {
+ let message = format!(
"Link constraint in {} {} > {} should be a valid version constraint, got \"{}\"",
source, description, target, constraint
- ))
+ );
+ let code = AnyThrowable::of(e.as_ref()).map_or(0, AnyThrowable::get_code);
+ return Err(UnexpectedValueException::with_code_and_previous(
+ message,
+ code,
+ Some(std::sync::Arc::new(e)),
+ )
.into());
}
};
diff --git a/crates/shirabe/src/repository/artifact_repository.rs b/crates/shirabe/src/repository/artifact_repository.rs
index 2d16f137..75ad134b 100644
--- a/crates/shirabe/src/repository/artifact_repository.rs
+++ b/crates/shirabe/src/repository/artifact_repository.rs
@@ -224,11 +224,15 @@ impl ArtifactRepository {
.unwrap_or_default();
match self.loader.load(cfg, None) {
Ok(package) => Ok(Some(package)),
- Err(exception) => Err(UnexpectedValueException::new(format!(
- "Failed loading package in {}: {}",
- pathname, exception
- ))
- .into()),
+ Err(exception) => {
+ let message = format!("Failed loading package in {}: {}", pathname, exception);
+ Err(UnexpectedValueException::with_code_and_previous(
+ message,
+ 0,
+ Some(std::sync::Arc::new(exception)),
+ )
+ .into())
+ }
}
}
}
diff --git a/crates/shirabe/src/repository/composer_repository.rs b/crates/shirabe/src/repository/composer_repository.rs
index 5371c2c3..b53374aa 100644
--- a/crates/shirabe/src/repository/composer_repository.rs
+++ b/crates/shirabe/src/repository/composer_repository.rs
@@ -40,9 +40,9 @@ use shirabe_external_packages::composer::pcre::{CaptureKey, Preg};
use shirabe_metadata_minifier::MetadataMinifier;
use shirabe_php_shim::Catch as _;
use shirabe_php_shim::{
- CmpOp, InvalidArgumentException, LogicException, PHP_EOL, PhpMixed, RuntimeException,
- UnexpectedValueException, extension_loaded, hash, http_build_query, json_decode, parse_url_all,
- php_regex, realpath, strtolower, strtr, urlencode, var_export,
+ AnyThrowable, CmpOp, InvalidArgumentException, LogicException, PHP_EOL, PhpMixed,
+ RuntimeException, UnexpectedValueException, extension_loaded, hash, http_build_query,
+ json_decode, parse_url_all, php_regex, realpath, strtolower, strtr, urlencode, var_export,
};
use shirabe_semver::CompilingMatcher;
use shirabe_semver::constraint::AnyConstraint;
@@ -2695,17 +2695,21 @@ impl ComposerRepository {
})();
result.map_err(|e| {
- RuntimeException::new(format!(
+ let message = format!(
"Could not load packages in {}{}: [{}] {}",
self.get_repo_name(),
source
.as_ref()
.map(|s| format!(" from {}", s))
.unwrap_or_default(),
- "Exception",
+ AnyThrowable::of(e.as_ref()).map_or_else(
+ || "Exception".to_string(),
+ shirabe_php_shim::PhpClass::php_class_name,
+ ),
e
- ))
- .into()
+ );
+ RuntimeException::with_code_and_previous(message, 0, Some(std::sync::Arc::new(e)))
+ .into()
})
}
diff --git a/crates/shirabe/src/repository/path_repository.rs b/crates/shirabe/src/repository/path_repository.rs
index 313fc75e..46b7f95f 100644
--- a/crates/shirabe/src/repository/path_repository.rs
+++ b/crates/shirabe/src/repository/path_repository.rs
@@ -350,10 +350,11 @@ impl PathRepository {
self.inner
.add_package(self.loader.load(package.clone(), None).map_err(|e| {
- RuntimeException::new(format!(
- "Failed loading the package in {}",
- composer_file_path
- ))
+ RuntimeException::with_code_and_previous(
+ format!("Failed loading the package in {}", composer_file_path),
+ 0,
+ Some(std::sync::Arc::new(e)),
+ )
})?);
}
diff --git a/crates/shirabe/src/repository/vcs/github_driver.rs b/crates/shirabe/src/repository/vcs/github_driver.rs
index 72bcc8ae..ffdf04ec 100644
--- a/crates/shirabe/src/repository/vcs/github_driver.rs
+++ b/crates/shirabe/src/repository/vcs/github_driver.rs
@@ -1038,7 +1038,7 @@ impl GitHubDriver {
}
if !self.inner.io.is_interactive() {
- self.attempt_clone_fallback(Some(&e))
+ self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into())))
.map_err(|err| TransportException::new(err.to_string(), 0))?;
return Ok(Response::new(
@@ -1090,7 +1090,7 @@ impl GitHubDriver {
}
if !self.inner.io.is_interactive() && fetching_repo_data {
- self.attempt_clone_fallback(Some(&e))
+ self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into())))
.map_err(|err| TransportException::new(err.to_string(), 0))?;
return Ok(Response::new(
@@ -1177,7 +1177,7 @@ impl GitHubDriver {
}
Err(e) => {
if e.get_code() == 499 {
- self.attempt_clone_fallback(Some(&e))?;
+ self.attempt_clone_fallback(Some(std::sync::Arc::new((*e).into())))?;
} else {
return Err((*e).into());
}
@@ -1227,14 +1227,14 @@ impl GitHubDriver {
/// @throws \RuntimeException
pub(crate) fn attempt_clone_fallback(
&mut self,
- e: Option<&TransportException>,
+ e: Option<std::sync::Arc<anyhow::Error>>,
) -> anyhow::Result<bool> {
if !self.allow_git_fallback {
- return Err(RuntimeException::new(format!(
- "Fallback to git driver disabled{}",
- e.map(|e| format!(": {}", e.get_message()))
- .unwrap_or_default()
- ))
+ return Err(RuntimeException::with_code_and_previous(
+ "Fallback to git driver disabled".to_string(),
+ 0,
+ e,
+ )
.into());
}
diff --git a/crates/shirabe/src/util/git.rs b/crates/shirabe/src/util/git.rs
index 3eec0947..d7bd3685 100644
--- a/crates/shirabe/src/util/git.rs
+++ b/crates/shirabe/src/util/git.rs
@@ -16,7 +16,7 @@ use crate::util::{AuthHelper, StoreAuth};
use indexmap::IndexMap;
use shirabe_external_packages::composer::pcre::{CaptureKey, Preg};
use shirabe_php_shim::{
- CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map,
+ AnyThrowable, CmpOp, InvalidArgumentException, PHP_EOL, PhpMixed, RuntimeException, array_map,
clearstatcache, explode, implode, in_array_loose, in_array_strict, is_dir, php_regex,
preg_quote, rawurldecode, rawurlencode, str_contains, str_ends_with, str_replace_array, strlen,
strpos, substr, trim, version_compare,
@@ -839,7 +839,7 @@ impl Git {
Ok(())
})();
// finally
- let _ = self.run_commands(
+ let finally_result = self.run_commands(
vec![vec![
"git".to_string(),
"remote".to_string(),
@@ -853,8 +853,19 @@ impl Git {
false,
(),
);
+ let outcome = match finally_result {
+ Ok(()) => try_result,
+ Err(mut thrown) => {
+ if let Some(pending) = try_result.err().map(std::sync::Arc::new)
+ && let Some(exception) = thrown.downcast_mut::<AnyThrowable>()
+ {
+ exception.set_previous(pending);
+ }
+ Err(thrown)
+ }
+ };
- if let Err(e) = try_result {
+ if let Err(e) = outcome {
self.io.write_error3(
&format!("<error>Sync mirror failed: {}</error>", e),
true,