diff --git a/Cargo.toml b/Cargo.toml index 6f0af53..14c80bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/rust-lang/rustdoc-types" serde = {version="1.0.186"} serde_derive = {version="1.0.186"} rustc-hash = {version="2", optional=true} +serde_json = "1.0" [features] default = [] diff --git a/src/lib.rs b/src/lib.rs index ad9ea96..12c03cb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,7 +22,7 @@ use std::path::PathBuf; #[cfg(feature = "rustc-hash")] use rustc_hash::FxHashMap as HashMap; use serde_derive::{Deserialize, Serialize}; - +use serde::de::Error; /// The version of JSON output that this crate represents. /// @@ -201,7 +201,7 @@ pub struct Item { pub inner: ItemEnum, } -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] #[serde(rename_all = "snake_case")] /// An attribute, e.g. `#[repr(C)]` /// @@ -245,6 +245,105 @@ pub enum Attribute { Other(String), } +impl<'de> serde::Deserialize<'de> for Attribute { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + + // Strip #[ and ] if present + let cleaned = s.trim_start_matches("#[") + .trim_end_matches("]") + .trim(); + + // Now match on the cleaned string + Ok(match cleaned { + "non_exhaustive" => Attribute::NonExhaustive, + s if s.starts_with("must_use") => { + // Parse reason if present + let reason = if s.len() > "must_use".len() { + Some(s["must_use".len()..].trim_matches(|c| c == '(' || c == ')' || c == '"').to_string()) + } else { + None + }; + Attribute::MustUse { reason } + }, + s if s.starts_with("export_name") => { + let name = s["export_name".len()..].trim_matches(|c| c == '=' || c == ' ' || c == '"').to_string(); + Attribute::ExportName(name) + }, + + s if s.starts_with("export_name") => { + let name = s["export_name".len()..].trim_matches(|c| c == '=' || c == ' ' || c == '"').to_string(); + Attribute::ExportName(name) + }, + + s if s.starts_with("link_section") => { + let section = s["link_section".len()..].trim_matches(|c| c == '=' || c == ' ' || c == '"').to_string(); + Attribute::LinkSection(section) + }, + + "automatically_derived" => Attribute::AutomaticallyDerived, + + s if s.starts_with("repr") => { + let repr_str = s["repr".len()..].trim_matches(|c| c == '(' || c == ')').trim(); + let parts: Vec<&str> = repr_str.split(',').map(str::trim).collect(); + + let mut repr = AttributeRepr { + kind: ReprKind::C, // Will be overwritten + align: None, + packed: None, + int: None, + }; + + for part in parts { + if part.starts_with("align(") { + let align_str = part.trim_start_matches("align(").trim_end_matches(')'); + repr.align = Some(align_str.parse().map_err(D::Error::custom)?); + } else if part.starts_with("packed(") { + let packed_str = part.trim_start_matches("packed(").trim_end_matches(')'); + repr.packed = Some(packed_str.parse().map_err(D::Error::custom)?); + } else if part == "C" { + repr.kind = ReprKind::C; + } else if part == "transparent" { + repr.kind = ReprKind::Transparent; + } else if ["i8", "i16", "i32", "i64", "i128", "isize", + "u8", "u16", "u32", "u64", "u128", "usize"].contains(&part) { + repr.int = Some(part.to_string()); + } + // Add other ReprKind variants as needed + } + + Attribute::Repr(repr) + }, + + "no_mangle" => Attribute::NoMangle, + + s if s.starts_with("target_feature") => { + let features_str = s["target_feature".len()..].trim_matches(|c| c == '(' || c == ')').trim(); + let enable: Vec = features_str + .split(',') + .filter_map(|feature| { + let feature = feature.trim(); + if feature.starts_with("enable = ") { + Some(feature["enable = ".len()..].trim_matches('"').to_string()) + } else { + None + } + }) + .collect(); + Attribute::TargetFeature { enable } + }, + + + // Default case + _ => Attribute::Other(s.to_string()), + }) + } +} + + #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] /// The contents of a `#[repr(...)]` attribute. /// @@ -878,6 +977,8 @@ pub enum Abi { /// Can be specified as `extern "system"`. System { unwind: bool }, /// Any other ABI, including unstable ones. + // XXX: This variant must be last in the enum because it is untagged. + #[serde(untagged)] Other(String), } diff --git a/src/tests.rs b/src/tests.rs index b9363fc..0000411 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -3,8 +3,14 @@ use super::*; #[test] fn test_struct_info_roundtrip() { let s = ItemEnum::Struct(Struct { - generics: Generics { params: vec![], where_predicates: vec![] }, - kind: StructKind::Plain { fields: vec![], has_stripped_fields: false }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, + kind: StructKind::Plain { + fields: vec![], + has_stripped_fields: false, + }, impls: vec![], }); @@ -22,7 +28,10 @@ fn test_struct_info_roundtrip() { #[test] fn test_union_info_roundtrip() { let u = ItemEnum::Union(Union { - generics: Generics { params: vec![], where_predicates: vec![] }, + generics: Generics { + params: vec![], + where_predicates: vec![], + }, has_stripped_fields: false, fields: vec![], impls: vec![],