]> git.proxmox.com Git - rustc.git/blobdiff - vendor/thiserror-impl/src/valid.rs
Update upstream source from tag 'upstream/1.52.1+dfsg1'
[rustc.git] / vendor / thiserror-impl / src / valid.rs
diff --git a/vendor/thiserror-impl/src/valid.rs b/vendor/thiserror-impl/src/valid.rs
new file mode 100644 (file)
index 0000000..7246d0c
--- /dev/null
@@ -0,0 +1,233 @@
+use crate::ast::{Enum, Field, Input, Struct, Variant};
+use crate::attr::Attrs;
+use quote::ToTokens;
+use std::collections::BTreeSet as Set;
+use syn::{Error, GenericArgument, Member, 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,
+                    "transparent error struct can't contain #[source]",
+                ));
+            }
+        }
+        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()
+            {
+                return Err(Error::new_spanned(
+                    variant.original,
+                    "missing #[error(\"...\")] display attribute",
+                ));
+            }
+        }
+        let mut from_types = Set::new();
+        for variant in &self.variants {
+            if let Some(from_field) = variant.from_field() {
+                let repr = from_field.ty.to_token_stream().to_string();
+                if !from_types.insert(repr) {
+                    return Err(Error::new_spanned(
+                        from_field.original,
+                        "cannot derive From because another variant has the same source type",
+                    ));
+                }
+            }
+        }
+        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,
+                    "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(display) = &self.attrs.display {
+            return Err(Error::new_spanned(
+                display.original,
+                "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,
+            "not expected here; the #[from] attribute belongs on a specific field",
+        ));
+    }
+    if let Some(source) = &attrs.source {
+        return Err(Error::new_spanned(
+            source,
+            "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 let Some(display) = &attrs.display {
+        if attrs.transparent.is_some() {
+            return Err(Error::new_spanned(
+                display.original,
+                "cannot have both #[error(transparent)] and a display 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, "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, "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 !same_member(from_field, source_field) {
+            return Err(Error::new_spanned(
+                from_field.attrs.from,
+                "#[from] is only supported on the source field, not any other field",
+            ));
+        }
+    }
+    if let Some(from_field) = from_field {
+        if fields.len() > 1 + has_backtrace as usize {
+            return Err(Error::new_spanned(
+                from_field.attrs.from,
+                "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 same_member(one: &Field, two: &Field) -> bool {
+    match (&one.member, &two.member) {
+        (Member::Named(one), Member::Named(two)) => one == two,
+        (Member::Unnamed(one), Member::Unnamed(two)) => one.index == two.index,
+        _ => unreachable!(),
+    }
+}
+
+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
+    }
+}