//! The `darling::Error` type, the multiple error `Accumulator`, and their internals. //! //! Error handling is one of the core values of `darling`; creating great errors is hard and //! never the reason that a proc-macro author started writing their crate. As a result, the //! `Error` type in `darling` tries to make adding span information, suggestions, and other //! help content easy when manually implementing `darling` traits, and automatic when deriving //! them.
use proc_macro2::{Span, TokenStream}; use std::error::Error as StdError; use std::fmt; use std::iter::{self, Iterator}; use std::string::ToString; use std::vec; use syn::spanned::Spanned; use syn::{Expr, Lit, LitStr, Path};
#[cfg(feature = "diagnostics")] mod child; mod kind;
usecrate::util::path_to_string;
useself::kind::{ErrorKind, ErrorUnknownField};
/// An alias of `Result` specific to attribute parsing. pubtype Result<T> = ::std::result::Result<T, Error>;
/// An error encountered during attribute parsing. /// /// Given that most errors darling encounters represent code bugs in dependent crates, /// the internal structure of the error is deliberately opaque. /// /// # Usage /// Proc-macro expansion happens very infrequently compared to runtime tasks such as /// deserialization, and it happens in the context of an expensive compilation taks. /// For that reason, darling prefers not to fail on the first error it encounters, instead /// doing as much work as it can, accumulating errors into a single report. /// /// As a result, `darling::Error` is more of guaranteed-non-empty error collection /// than a single problem. These errors also have some notion of hierarchy, stemming from /// the hierarchical nature of darling's input. /// /// These characteristics make for great experiences when using darling-powered crates, /// provided crates using darling adhere to some best practices: /// /// 1. Do not attempt to simplify a `darling::Error` into some other error type, such as /// `syn::Error`. To surface compile errors, instead use `darling::Error::write_errors`. /// This preserves all span information, suggestions, etc. Wrapping a `darling::Error` in /// a custom error enum works as-expected and does not force any loss of fidelity. /// 2. Do not use early return (e.g. the `?` operator) for custom validations. Instead, /// create an [`error::Accumulator`](Accumulator) to collect errors as they are encountered. Then use /// [`Accumulator::finish`] to return your validated result; it will give `Ok` if and only if /// no errors were encountered. This can create very complex custom validation functions; /// in those cases, split independent "validation chains" out into their own functions to /// keep the main validator manageable. /// 3. Use `darling::Error::custom` to create additional errors as-needed, then call `with_span` /// to ensure those errors appear in the right place. Use `darling::util::SpannedValue` to keep /// span information around on parsed fields so that custom diagnostics can point to the correct /// parts of the input AST. #[derive(Debug, Clone)] pubstruct Error {
kind: ErrorKind,
locations: Vec<String>, /// The span to highlight in the emitted diagnostic.
span: Option<Span>, /// Additional diagnostic messages to show with the error. #[cfg(feature = "diagnostics")]
children: Vec<child::ChildDiagnostic>,
}
/// Creates a new error with a custom message. pubfn custom<T: fmt::Display>(msg: T) -> Self {
Error::new(ErrorKind::Custom(msg.to_string()))
}
/// Creates a new error for a field that appears twice in the input. pubfn duplicate_field(name: &str) -> Self {
Error::new(ErrorKind::DuplicateField(name.into()))
}
/// Creates a new error for a field that appears twice in the input. Helper to avoid repeating /// the syn::Path to String conversion. pubfn duplicate_field_path(path: &Path) -> Self {
Error::duplicate_field(&path_to_string(path))
}
/// Creates a new error for a non-optional field that does not appear in the input. pubfn missing_field(name: &str) -> Self {
Error::new(ErrorKind::MissingField(name.into()))
}
/// Creates a new error for a field name that appears in the input but does not correspond /// to a known field. pubfn unknown_field(name: &str) -> Self {
Error::new(ErrorKind::UnknownField(name.into()))
}
/// Creates a new error for a field name that appears in the input but does not correspond /// to a known field. Helper to avoid repeating the syn::Path to String conversion. pubfn unknown_field_path(path: &Path) -> Self {
Error::unknown_field(&path_to_string(path))
}
/// Creates a new error for a field name that appears in the input but does not correspond to /// a known attribute. The second argument is the list of known attributes; if a similar name /// is found that will be shown in the emitted error message. pubfn unknown_field_with_alts<'a, T, I>(field: &str, alternates: I) -> Self where
T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>,
{
Error::new(ErrorUnknownField::with_alts(field, alternates).into())
}
/// Creates a new error for a field name that appears in the input but does not correspond to /// a known attribute. The second argument is the list of known attributes; if a similar name /// is found that will be shown in the emitted error message. pubfn unknown_field_path_with_alts<'a, T, I>(field: &Path, alternates: I) -> Self where
T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>,
{
Error::new(ErrorUnknownField::with_alts(&path_to_string(field), alternates).into())
}
/// Creates a new error for a struct or variant that does not adhere to the supported shape. pubfn unsupported_shape(shape: &str) -> Self {
Error::new(ErrorKind::UnsupportedShape {
observed: shape.into(),
expected: None,
})
}
/// Creates a new error for a field which has an unexpected literal type. pubfn unexpected_type(ty: &str) -> Self {
Error::new(ErrorKind::UnexpectedType(ty.into()))
}
/// Creates a new error for a field which has an unexpected literal type. This will automatically /// extract the literal type name from the passed-in `Lit` and set the span to encompass only the /// literal value. /// /// # Usage /// This is most frequently used in overrides of the `FromMeta::from_value` method. /// /// ```rust /// # // pretend darling_core is darling so the doc example looks correct. /// # extern crate darling_core as darling; /// # extern crate syn; /// /// use darling::{FromMeta, Error, Result}; /// use syn::{Lit, LitStr}; /// /// pub struct Foo(String); /// /// impl FromMeta for Foo { /// fn from_value(value: &Lit) -> Result<Self> { /// if let Lit::Str(ref lit_str) = *value { /// Ok(Foo(lit_str.value())) /// } else { /// Err(Error::unexpected_lit_type(value)) /// } /// } /// } /// /// # fn main() {} /// ``` pubfn unexpected_lit_type(lit: &Lit) -> Self {
Error::unexpected_type(match *lit {
Lit::Str(_) => "string",
Lit::ByteStr(_) => "byte string",
Lit::Byte(_) => "byte",
Lit::Char(_) => "char",
Lit::Int(_) => "int",
Lit::Float(_) => "float",
Lit::Bool(_) => "bool",
Lit::Verbatim(_) => "verbatim", // non-exhaustive enum
_ => "unknown",
})
.with_span(lit)
}
/// Creates a new error for a value which doesn't match a set of expected literals. pubfn unknown_value(value: &str) -> Self {
Error::new(ErrorKind::UnknownValue(value.into()))
}
/// Creates a new error for a list which did not get enough items to proceed. pubfn too_few_items(min: usize) -> Self {
Error::new(ErrorKind::TooFewItems(min))
}
/// Creates a new error when a list got more items than it supports. The `max` argument /// is the largest number of items the receiver could accept. pubfn too_many_items(max: usize) -> Self {
Error::new(ErrorKind::TooManyItems(max))
}
/// Bundle a set of multiple errors into a single `Error` instance. /// /// Usually it will be more convenient to use an [`error::Accumulator`](Accumulator). /// /// # Panics /// This function will panic if `errors.is_empty() == true`. pubfn multiple(mut errors: Vec<Error>) -> Self { match errors.len() { 1 => errors
.pop()
.expect("Error array of length 1 has a first item"), 0 => panic!("Can't deal with 0 errors"),
_ => Error::new(ErrorKind::Multiple(errors)),
}
}
/// Creates an error collector, for aggregating multiple errors /// /// See [`Accumulator`] for details. pubfn accumulator() -> Accumulator {
Default::default()
}
}
impl Error { /// Create a new error about a literal string that doesn't match a set of known /// or permissible values. This function can be made public if the API proves useful /// beyond impls for `syn` types. pub(crate) fn unknown_lit_str_value(value: &LitStr) -> Self {
Error::unknown_value(&value.value()).with_span(value)
}
}
/// Error instance methods #[allow(clippy::len_without_is_empty)] // Error can never be empty impl Error { /// Check if this error is associated with a span in the token stream. pubfn has_span(&self) -> bool { self.span.is_some()
}
/// Tie a span to the error if none is already present. This is used in `darling::FromMeta` /// and other traits to attach errors to the most specific possible location in the input /// source code. /// /// All `darling`-built impls, either from the crate or from the proc macro, will call this /// when appropriate during parsing, so it should not be necessary to call this unless you have /// overridden: /// /// * `FromMeta::from_meta` /// * `FromMeta::from_nested_meta` /// * `FromMeta::from_value` pubfn with_span<T: Spanned>(mutself, node: &T) -> Self { if !self.has_span() { self.span = Some(node.span());
}
self
}
/// Get a span for the error. /// /// # Return Value /// This function will return [`Span::call_site()`](proc_macro2::Span) if [`Self::has_span`] is `false`. /// To get the span only if one has been explicitly set for `self`, instead use [`Error::explicit_span`]. pubfn span(&self) -> Span { self.span.unwrap_or_else(Span::call_site)
}
/// Get the span for `self`, if one has been set. pubfn explicit_span(&self) -> Option<Span> { self.span
}
/// Recursively converts a tree of errors to a flattened list. /// /// # Child Diagnostics /// If the `diagnostics` feature is enabled, any child diagnostics on `self` /// will be cloned down to all the errors within `self`. pubfn flatten(self) -> Self {
Error::multiple(self.into_vec())
}
#[cfg(feature = "diagnostics")] let children = self.children;
errors
.into_iter()
.flat_map(|error| { // This is mutated if the diagnostics feature is enabled #[allow(unused_mut)] letmut error = error.prepend_at(locations.clone());
// Any child diagnostics in `self` are cloned down to all the distinct // errors contained in `self`. #[cfg(feature = "diagnostics")]
error.children.extend(children.iter().cloned());
/// Adds a location to the error, such as a field or variant. /// Locations must be added in reverse order of specificity. pubfn at<T: fmt::Display>(mutself, location: T) -> Self { self.locations.insert(0, location.to_string()); self
}
/// Adds a location to the error, such as a field or variant. /// Locations must be added in reverse order of specificity. This is a helper function to avoid /// repeating path to string logic. pubfn at_path(self, path: &Path) -> Self { self.at(path_to_string(path))
}
/// Gets the number of individual errors in this error. /// /// This function never returns `0`, as it's impossible to construct /// a multi-error from an empty `Vec`. pubfn len(&self) -> usize { self.kind.len()
}
/// Consider additional field names as "did you mean" suggestions for /// unknown field errors **if and only if** the caller appears to be operating /// at error's origin (meaning no calls to [`Self::at`] have yet taken place). /// /// # Usage /// `flatten` fields in derived trait implementations rely on this method to offer correct /// "did you mean" suggestions in errors. /// /// Because the `flatten` field receives _all_ unknown fields, if a user mistypes a field name /// that is present on the outer struct but not the flattened struct, they would get an incomplete /// or inferior suggestion unless this method was invoked. pubfn add_sibling_alts_for_unknown_field<'a, T, I>(mut self, alternates: I) -> Self where
T: AsRef<str> + 'a,
I: IntoIterator<Item = &'a T>,
{ // The error may have bubbled up before this method was called, // and in those cases adding alternates would be incorrect. if !self.locations.is_empty() { returnself;
}
iflet ErrorKind::UnknownField(unknown_field) = &mutself.kind {
unknown_field.add_alts(alternates);
} elseiflet ErrorKind::Multiple(errors) = self.kind { let alternates = alternates.into_iter().collect::<Vec<_>>(); self.kind = ErrorKind::Multiple(
errors
.into_iter()
.map(|err| {
err.add_sibling_alts_for_unknown_field( // This clone seems like it shouldn't be necessary. // Attempting to borrow alternates here leads to the following compiler error: // // error: reached the recursion limit while instantiating `darling::Error::add_sibling_alts_for_unknown_field::<'_, &&&&..., ...>`
alternates.clone(),
)
})
.collect(),
)
}
self
}
/// Adds a location chain to the head of the error's existing locations. fn prepend_at(mutself, mut locations: Vec<String>) -> Self { if !locations.is_empty() {
locations.extend(self.locations); self.locations = locations;
}
/// Write this error and any children as compile errors into a `TokenStream` to /// be returned by the proc-macro. /// /// The behavior of this method will be slightly different if the `diagnostics` feature /// is enabled: In that case, the diagnostics will be emitted immediately by this call, /// and an empty `TokenStream` will be returned. /// /// Return these tokens unmodified to avoid disturbing the attached span information. /// /// # Usage /// ```rust,ignore /// // in your proc-macro function /// let opts = match MyOptions::from_derive_input(&ast) { /// Ok(val) => val, /// Err(err) => { /// return err.write_errors(); /// } /// } /// ``` pubfn write_errors(self) -> TokenStream { #[cfg(feature = "diagnostics")]
{ self.emit();
TokenStream::default()
}
// Delegate to dedicated error formatters when applicable. // // If span information is available, don't include the error property path // since it's redundant and not consistent with native compiler diagnostics. let diagnostic = matchself.kind {
ErrorKind::UnknownField(euf) => euf.into_diagnostic(self.span),
_ => matchself.span {
Some(span) => span.unwrap().error(self.kind.to_string()),
None => Diagnostic::new(Level::Error, self.to_string()),
},
};
/// Transform this error and its children into a list of compiler diagnostics /// and emit them. If the `Error` has associated span information, the diagnostics /// will identify the correct location in source code automatically. /// /// # Stability /// This is only available on `nightly` until the compiler `proc_macro_diagnostic` /// feature stabilizes. Until then, it may break at any time. #[cfg(feature = "diagnostics")] pubfn emit(self) { for error inself.flatten() {
error.single_to_diagnostic().emit()
}
}
/// Transform the error into a compiler diagnostic and - if the diagnostic points to /// a specific code location - add a spanned help child diagnostic that points to the /// parent derived trait. /// /// This is experimental and therefore not exposed outside the crate. #[cfg(feature = "diagnostics")] #[allow(dead_code)] fn emit_with_macro_help_span(self) { use proc_macro::Diagnostic;
for error inself.flatten() { let needs_help = error.has_span(); let diagnostic = error.single_to_diagnostic();
Diagnostic::emit(if needs_help {
diagnostic.span_help(
Span::call_site().unwrap(), "Encountered as part of this derive-mode-macro",
)
} else {
diagnostic
})
}
}
}
impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.kind)?; if !self.locations.is_empty() {
write!(f, " at {}", self.locations.join("/"))?;
}
Ok(())
}
}
impl From<syn::Error> for Error { fn from(e: syn::Error) -> Self { // This impl assumes there is nothing but the message and span that needs to be preserved // from the passed-in error. If this changes at some point, a new ErrorKind should be made // to hold the syn::Error, and this impl should preserve it unmodified while setting its own // span to be a copy of the passed-in error. Self {
span: Some(e.span()),
..Self::custom(e)
}
}
}
impl From<Error> for syn::Error { fn from(e: Error) -> Self { if e.len() == 1 { iflet Some(span) = e.explicit_span() { // Don't include the location path if the error has an explicit span, // since it will be redundant and isn't consistent with how rustc // exposes errors.
syn::Error::new(span, e.kind)
} else { // If the error's span is going to be the macro call site, include // the location information to try and help the user pinpoint the issue.
syn::Error::new(e.span(), e)
}
} else { letmut syn_errors = e.flatten().into_iter().map(syn::Error::from); letmut error = syn_errors
.next()
.expect("darling::Error can never be empty");
for next_error in syn_errors {
error.combine(next_error);
}
error
}
}
}
// Don't want to publicly commit to Error supporting equality yet, but // not having it makes testing very difficult. Note that spans are not // considered for equality since that would break testing in most cases. #[cfg(test)] impl PartialEq for Error { fn eq(&self, other: &Self) -> bool { self.kind == other.kind && self.locations == other.locations
}
}
#[cfg(test)] impl Eq for Error {}
impl IntoIterator for Error { type Item = Error; type IntoIter = IntoIter;
/// Accumulator for errors, for helping call [`Error::multiple`]. /// /// See the docs for [`darling::Error`](Error) for more discussion of error handling with darling. /// /// # Panics /// /// `Accumulator` panics on drop unless [`finish`](Self::finish), [`finish_with`](Self::finish_with), /// or [`into_inner`](Self::into_inner) has been called, **even if it contains no errors**. /// If you want to discard an `Accumulator` that you know to be empty, use `accumulator.finish().unwrap()`. /// /// # Example /// /// ``` /// # extern crate darling_core as darling; /// # struct Thing; /// # struct Output; /// # impl Thing { fn validate(self) -> darling::Result<Output> { Ok(Output) } } /// fn validate_things(inputs: Vec<Thing>) -> darling::Result<Vec<Output>> { /// let mut errors = darling::Error::accumulator(); /// /// let outputs = inputs /// .into_iter() /// .filter_map(|thing| errors.handle_in(|| thing.validate())) /// .collect::<Vec<_>>(); /// /// errors.finish()?; /// Ok(outputs) /// } /// ``` #[derive(Debug)] #[must_use = "Accumulator will panic on drop if not defused."] pubstruct Accumulator(Option<Vec<Error>>);
impl Accumulator { /// Runs a closure, returning the successful value as `Some`, or collecting the error /// /// The closure's return type is `darling::Result`, so inside it one can use `?`. pubfn handle_in<T, F: FnOnce() -> Result<T>>(&mutself, f: F) -> Option<T> { self.handle(f())
}
/// Handles a possible error. /// /// Returns a successful value as `Some`, or collects the error and returns `None`. pubfn handle<T>(&mutself, result: Result<T>) -> Option<T> { match result {
Ok(y) => Some(y),
Err(e) => { self.push(e);
None
}
}
}
/// Stop accumulating errors, producing `Ok` if there are no errors or producing /// an error with all those encountered by the accumulator. pubfn finish(self) -> Result<()> { self.finish_with(())
}
/// Bundles the collected errors if there were any, or returns the success value /// /// Call this at the end of your input processing. /// /// If there were no errors recorded, returns `Ok(success)`. /// Otherwise calls [`Error::multiple`] and returns the result as an `Err`. pubfn finish_with<T>(self, success: T) -> Result<T> { let errors = self.into_inner(); if errors.is_empty() {
Ok(success)
} else {
Err(Error::multiple(errors))
}
}
/// Returns the accumulated errors as a `Vec`. /// /// This function defuses the drop bomb. #[must_use = "Accumulated errors should be handled or propagated to the caller"] pubfn into_inner(mutself) -> Vec<Error> { matchself.0.take() {
Some(errors) => errors,
None => panic!("darling internal error: Accumulator accessed after defuse"),
}
}
/// Add one error to the collection. pubfn push(&mutself, error: Error) { self.errors().push(error)
}
/// Finish the current accumulation, and if there are no errors create a new `Self` so processing may continue. /// /// This is shorthand for: /// /// ```rust,ignore /// errors.finish()?; /// errors = Error::accumulator(); /// ``` /// /// # Drop Behavior /// This function returns a new [`Accumulator`] in the success case. /// This new accumulator is "armed" and will detonate if dropped without being finished. /// /// # Example /// /// ``` /// # extern crate darling_core as darling; /// # struct Thing; /// # struct Output; /// # impl Thing { fn validate(&self) -> darling::Result<Output> { Ok(Output) } } /// fn validate(lorem_inputs: &[Thing], ipsum_inputs: &[Thing]) /// -> darling::Result<(Vec<Output>, Vec<Output>)> { /// let mut errors = darling::Error::accumulator(); /// /// let lorems = lorem_inputs.iter().filter_map(|l| { /// errors.handle(l.validate()) /// }).collect(); /// /// errors = errors.checkpoint()?; /// /// let ipsums = ipsum_inputs.iter().filter_map(|l| { /// errors.handle(l.validate()) /// }).collect(); /// /// errors.finish_with((lorems, ipsums)) /// } /// # validate(&[], &[]).unwrap(); /// ``` pubfn checkpoint(self) -> Result<Accumulator> { // The doc comment says on success we "return the Accumulator for future use". // Actually, we have consumed it by feeding it to finish so we make a fresh one. // This is OK since by definition of the success path, it was empty on entry. self.finish()?;
Ok(Self::default())
}
}
impl Extend<Error> for Accumulator { fn extend<I>(&mutself, iter: I) where
I: IntoIterator<Item = Error>,
{ self.errors().extend(iter)
}
}
impl Drop for Accumulator { fn drop(&mutself) { // don't try to panic if we are currently unwinding a panic // otherwise we end up with an unhelful "thread panicked while panicking. aborting." message if !std::thread::panicking() { iflet Some(errors) = &mutself.0 { match errors.len() { 0 => panic!("darling::error::Accumulator dropped without being finished"),
error_count => panic!("darling::error::Accumulator dropped without being finished. {} errors were lost.", error_count)
}
}
}
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.