forked from gentoo-utils/gentoo-utils
Merge commit '6eba9cd92c295c5389944f6adda1f1e83b2cb008' as 'subprojects/thiserror'
This commit is contained in:
248
subprojects/thiserror/impl/src/valid.rs
Normal file
248
subprojects/thiserror/impl/src/valid.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use crate::ast::{Enum, Field, Input, Struct, Variant};
|
||||
use crate::attr::Attrs;
|
||||
use syn::{Error, GenericArgument, PathArguments, Result, Type};
|
||||
|
||||
impl Input<'_> {
|
||||
pub(crate) fn validate(&self) -> Result<()> {
|
||||
match self {
|
||||
Input::Struct(input) => input.validate(),
|
||||
Input::Enum(input) => input.validate(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Struct<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
if let Some(transparent) = self.attrs.transparent {
|
||||
if self.fields.len() != 1 {
|
||||
return Err(Error::new_spanned(
|
||||
transparent.original,
|
||||
"#[error(transparent)] requires exactly one field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"transparent error struct can't contain #[source]",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(fmt) = &self.attrs.fmt {
|
||||
return Err(Error::new_spanned(
|
||||
fmt.original,
|
||||
"#[error(fmt = ...)] is only supported in enums; for a struct, handwrite your own Display impl",
|
||||
));
|
||||
}
|
||||
check_field_attrs(&self.fields)?;
|
||||
for field in &self.fields {
|
||||
field.validate()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Enum<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
let has_display = self.has_display();
|
||||
for variant in &self.variants {
|
||||
variant.validate()?;
|
||||
if has_display
|
||||
&& variant.attrs.display.is_none()
|
||||
&& variant.attrs.transparent.is_none()
|
||||
&& variant.attrs.fmt.is_none()
|
||||
{
|
||||
return Err(Error::new_spanned(
|
||||
variant.original,
|
||||
"missing #[error(\"...\")] display attribute",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Variant<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
check_non_field_attrs(&self.attrs)?;
|
||||
if self.attrs.transparent.is_some() {
|
||||
if self.fields.len() != 1 {
|
||||
return Err(Error::new_spanned(
|
||||
self.original,
|
||||
"#[error(transparent)] requires exactly one field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = self.fields.iter().find_map(|f| f.attrs.source) {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"transparent variant can't contain #[source]",
|
||||
));
|
||||
}
|
||||
}
|
||||
check_field_attrs(&self.fields)?;
|
||||
for field in &self.fields {
|
||||
field.validate()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Field<'_> {
|
||||
fn validate(&self) -> Result<()> {
|
||||
if let Some(unexpected_display_attr) = if let Some(display) = &self.attrs.display {
|
||||
Some(display.original)
|
||||
} else if let Some(fmt) = &self.attrs.fmt {
|
||||
Some(fmt.original)
|
||||
} else {
|
||||
None
|
||||
} {
|
||||
return Err(Error::new_spanned(
|
||||
unexpected_display_attr,
|
||||
"not expected here; the #[error(...)] attribute belongs on top of a struct or an enum variant",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check_non_field_attrs(attrs: &Attrs) -> Result<()> {
|
||||
if let Some(from) = &attrs.from {
|
||||
return Err(Error::new_spanned(
|
||||
from.original,
|
||||
"not expected here; the #[from] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if let Some(source) = &attrs.source {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"not expected here; the #[source] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if let Some(backtrace) = &attrs.backtrace {
|
||||
return Err(Error::new_spanned(
|
||||
backtrace,
|
||||
"not expected here; the #[backtrace] attribute belongs on a specific field",
|
||||
));
|
||||
}
|
||||
if attrs.transparent.is_some() {
|
||||
if let Some(display) = &attrs.display {
|
||||
return Err(Error::new_spanned(
|
||||
display.original,
|
||||
"cannot have both #[error(transparent)] and a display attribute",
|
||||
));
|
||||
}
|
||||
if let Some(fmt) = &attrs.fmt {
|
||||
return Err(Error::new_spanned(
|
||||
fmt.original,
|
||||
"cannot have both #[error(transparent)] and #[error(fmt = ...)]",
|
||||
));
|
||||
}
|
||||
} else if let (Some(display), Some(_)) = (&attrs.display, &attrs.fmt) {
|
||||
return Err(Error::new_spanned(
|
||||
display.original,
|
||||
"cannot have both #[error(fmt = ...)] and a format arguments attribute",
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn check_field_attrs(fields: &[Field]) -> Result<()> {
|
||||
let mut from_field = None;
|
||||
let mut source_field = None;
|
||||
let mut backtrace_field = None;
|
||||
let mut has_backtrace = false;
|
||||
for field in fields {
|
||||
if let Some(from) = field.attrs.from {
|
||||
if from_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
from.original,
|
||||
"duplicate #[from] attribute",
|
||||
));
|
||||
}
|
||||
from_field = Some(field);
|
||||
}
|
||||
if let Some(source) = field.attrs.source {
|
||||
if source_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
source.original,
|
||||
"duplicate #[source] attribute",
|
||||
));
|
||||
}
|
||||
source_field = Some(field);
|
||||
}
|
||||
if let Some(backtrace) = field.attrs.backtrace {
|
||||
if backtrace_field.is_some() {
|
||||
return Err(Error::new_spanned(
|
||||
backtrace,
|
||||
"duplicate #[backtrace] attribute",
|
||||
));
|
||||
}
|
||||
backtrace_field = Some(field);
|
||||
has_backtrace = true;
|
||||
}
|
||||
if let Some(transparent) = field.attrs.transparent {
|
||||
return Err(Error::new_spanned(
|
||||
transparent.original,
|
||||
"#[error(transparent)] needs to go outside the enum or struct, not on an individual field",
|
||||
));
|
||||
}
|
||||
has_backtrace |= field.is_backtrace();
|
||||
}
|
||||
if let (Some(from_field), Some(source_field)) = (from_field, source_field) {
|
||||
if from_field.member != source_field.member {
|
||||
return Err(Error::new_spanned(
|
||||
from_field.attrs.from.unwrap().original,
|
||||
"#[from] is only supported on the source field, not any other field",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(from_field) = from_field {
|
||||
let max_expected_fields = match backtrace_field {
|
||||
Some(backtrace_field) => 1 + (from_field.member != backtrace_field.member) as usize,
|
||||
None => 1 + has_backtrace as usize,
|
||||
};
|
||||
if fields.len() > max_expected_fields {
|
||||
return Err(Error::new_spanned(
|
||||
from_field.attrs.from.unwrap().original,
|
||||
"deriving From requires no fields other than source and backtrace",
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(source_field) = source_field.or(from_field) {
|
||||
if contains_non_static_lifetime(source_field.ty) {
|
||||
return Err(Error::new_spanned(
|
||||
&source_field.original.ty,
|
||||
"non-static lifetimes are not allowed in the source of an error, because std::error::Error requires the source is dyn Error + 'static",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn contains_non_static_lifetime(ty: &Type) -> bool {
|
||||
match ty {
|
||||
Type::Path(ty) => {
|
||||
let bracketed = match &ty.path.segments.last().unwrap().arguments {
|
||||
PathArguments::AngleBracketed(bracketed) => bracketed,
|
||||
_ => return false,
|
||||
};
|
||||
for arg in &bracketed.args {
|
||||
match arg {
|
||||
GenericArgument::Type(ty) if contains_non_static_lifetime(ty) => return true,
|
||||
GenericArgument::Lifetime(lifetime) if lifetime.ident != "static" => {
|
||||
return true
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
Type::Reference(ty) => ty
|
||||
.lifetime
|
||||
.as_ref()
|
||||
.map_or(false, |lifetime| lifetime.ident != "static"),
|
||||
_ => false, // maybe implement later if there are common other cases
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user