Add ErrSpan extension trait for Result (#12626)

# Description
This adds an extension trait to `Result` that wraps errors in `Spanned`,
saving the effort of calling `.map_err(|err| err.into_spanned(span))`
every time. This will hopefully make it even more likely that someone
will want to use a spanned `io::Error` and make it easier to remove the
impl for `From<io::Error> for ShellError` because that doesn't have span
information.
This commit is contained in:
Devyn Cairns 2024-04-23 01:39:55 -07:00 committed by GitHub
parent b0acc1d890
commit 5c7f7883c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 47 additions and 21 deletions

View File

@ -117,7 +117,7 @@ impl Command for Do {
None,
)
})
.map_err(|e| e.into_spanned(call.head))
.err_span(call.head)
})
.transpose()?;

View File

@ -106,9 +106,7 @@ apparent the next time `nu` is next launched with that plugin cache file.
let shell_expanded = shell
.as_ref()
.map(|s| {
nu_path::canonicalize_with(&s.item, &cwd).map_err(|err| err.into_spanned(s.span))
})
.map(|s| nu_path::canonicalize_with(&s.item, &cwd).err_span(s.span))
.transpose()?;
// Parse the plugin filename so it can be used to spawn the plugin

View File

@ -30,7 +30,7 @@ pub(crate) fn modify_plugin_file(
// Try to read the plugin file if it exists
let mut contents = if fs::metadata(&plugin_cache_file_path).is_ok_and(|m| m.len() > 0) {
PluginCacheFile::read_from(
File::open(&plugin_cache_file_path).map_err(|err| err.into_spanned(span))?,
File::open(&plugin_cache_file_path).err_span(span)?,
Some(span),
)?
} else {
@ -42,7 +42,7 @@ pub(crate) fn modify_plugin_file(
// Save the modified file on success
contents.write_to(
File::create(&plugin_cache_file_path).map_err(|err| err.into_spanned(span))?,
File::create(&plugin_cache_file_path).err_span(span)?,
Some(span),
)?;

View File

@ -133,7 +133,7 @@ impl Command for Save {
.spawn(move || stderr.drain()),
})
.transpose()
.map_err(|e| e.into_spanned(span))?;
.err_span(span)?;
let res = stream_to_file(stdout, file, span, progress);
if let Some(h) = handler {

View File

@ -125,8 +125,7 @@ use it in your pipeline."#
if use_stderr {
let stderr = stderr
.map(|stderr| {
let iter = tee(stderr.stream, with_stream)
.map_err(|e| e.into_spanned(call.head))?;
let iter = tee(stderr.stream, with_stream).err_span(call.head)?;
Ok::<_, ShellError>(RawStream::new(
Box::new(iter.map(flatten_result)),
stderr.ctrlc,
@ -146,8 +145,7 @@ use it in your pipeline."#
} else {
let stdout = stdout
.map(|stdout| {
let iter = tee(stdout.stream, with_stream)
.map_err(|e| e.into_spanned(call.head))?;
let iter = tee(stdout.stream, with_stream).err_span(call.head)?;
Ok::<_, ShellError>(RawStream::new(
Box::new(iter.map(flatten_result)),
stdout.ctrlc,
@ -189,7 +187,7 @@ use it in your pipeline."#
// Make sure to drain any iterator produced to avoid unexpected behavior
result.and_then(|data| data.drain())
})
.map_err(|e| e.into_spanned(call.head))?
.err_span(call.head)?
.map(move |result| result.unwrap_or_else(|err| Value::error(err, closure_span)))
.into_pipeline_data_with_metadata(metadata, engine_state.ctrlc.clone());

View File

@ -62,7 +62,7 @@ impl Command for Complete {
}
})
.map(|handle| (handle, stderr_span))
.map_err(|err| err.into_spanned(call.head))
.err_span(call.head)
})
.transpose()?;

View File

@ -495,7 +495,7 @@ impl ExternalCommand {
Ok(())
})
.map_err(|e| e.into_spanned(head))?;
.err_span(head)?;
}
}
@ -580,7 +580,7 @@ impl ExternalCommand {
Ok(())
}
})
.map_err(|e| e.into_spanned(head))?;
.err_span(head)?;
let exit_code_receiver = ValueReceiver::new(exit_code_rx);

View File

@ -2,6 +2,7 @@ pub use crate::CallExt;
pub use nu_protocol::{
ast::{Call, CellPath},
engine::{Command, EngineState, Stack},
record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned,
PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
record, Category, ErrSpan, Example, IntoInterruptiblePipelineData, IntoPipelineData,
IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type,
Value,
};

View File

@ -3550,8 +3550,7 @@ pub fn parse_where(working_set: &mut StateWorkingSet, lite_command: &LiteCommand
pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteCommand) -> Pipeline {
use nu_plugin::{get_signature, PersistentPlugin, PluginDeclaration};
use nu_protocol::{
engine::Stack, IntoSpanned, PluginCacheItem, PluginIdentity, PluginSignature,
RegisteredPlugin,
engine::Stack, ErrSpan, PluginCacheItem, PluginIdentity, PluginSignature, RegisteredPlugin,
};
let spans = &lite_command.parts;
@ -3694,8 +3693,7 @@ pub fn parse_register(working_set: &mut StateWorkingSet, lite_command: &LiteComm
let path = path.path_buf();
// Create the plugin identity. This validates that the plugin name starts with `nu_plugin_`
let identity =
PluginIdentity::new(path, shell).map_err(|err| err.into_spanned(path_span))?;
let identity = PluginIdentity::new(path, shell).err_span(path_span)?;
// Find garbage collection config
let gc_config = working_set

View File

@ -147,3 +147,34 @@ pub fn span(spans: &[Span]) -> Span {
Span::new(spans[0].start, end)
}
}
/// An extension trait for `Result`, which adds a span to the error type.
pub trait ErrSpan {
type Result;
/// Add the given span to the error type `E`, turning it into a `Spanned<E>`.
///
/// Some auto-conversion methods to `ShellError` from other error types are available on spanned
/// errors, to give users better information about where an error came from. For example, it is
/// preferred when working with `std::io::Error`:
///
/// ```no_run
/// use nu_protocol::{ErrSpan, ShellError, Span};
/// use std::io::Read;
///
/// fn read_from(mut reader: impl Read, span: Span) -> Result<Vec<u8>, ShellError> {
/// let mut vec = vec![];
/// reader.read_to_end(&mut vec).err_span(span)?;
/// Ok(vec)
/// }
/// ```
fn err_span(self, span: Span) -> Self::Result;
}
impl<T, E> ErrSpan for Result<T, E> {
type Result = Result<T, Spanned<E>>;
fn err_span(self, span: Span) -> Self::Result {
self.map_err(|err| err.into_spanned(span))
}
}