1use std::collections::{HashMap, HashSet};
6use std::path::PathBuf;
7
8use syn::visit::Visit;
9use syn::{GenericArgument, Item, ItemMod, ItemStruct, PathArguments, Type, parse_file};
10
11pub const KNOWN_STD_VALUE_TYPES: &[&str] = &[
12 "Vec", "HashMap", "HashSet", "BTreeMap", "BTreeSet", "VecDeque", "String", "Option", "Cell",
13 "RefCell",
14];
15
16pub fn field_type_head(ty: &Type) -> String {
17 if let Type::Path(type_path) = ty {
18 type_path
19 .path
20 .segments
21 .last()
22 .map(|s| s.ident.to_string())
23 .unwrap_or_default()
24 } else {
25 String::new()
26 }
27}
28
29pub(crate) fn self_ty_name(ty: &Type) -> String {
30 if let Type::Path(type_path) = ty {
31 type_path
32 .path
33 .segments
34 .first()
35 .map(|s| s.ident.to_string())
36 .unwrap_or_default()
37 } else {
38 String::new()
39 }
40}
41
42pub(crate) fn is_trait_object_type(ty: &Type) -> bool {
43 match ty {
44 Type::TraitObject(_) => true,
45 Type::Reference(r) => is_trait_object_type(&r.elem),
46 Type::Path(p) => p.path.segments.iter().any(|seg| match &seg.arguments {
47 PathArguments::AngleBracketed(args) => args.args.iter().any(|arg| match arg {
48 GenericArgument::Type(t) => is_trait_object_type(t),
49 _ => false,
50 }),
51 _ => false,
52 }),
53 _ => false,
54 }
55}
56
57#[derive(Debug, Default)]
58pub struct StructRegistry {
59 fields_by_struct: HashMap<String, Vec<String>>,
60 concrete_fields_by_struct: HashMap<String, HashMap<String, String>>,
61}
62
63impl StructRegistry {
64 #[must_use]
65 pub fn build(files: &[(PathBuf, String)]) -> Self {
66 let mut registry = Self::default();
67 for (_, source) in files {
68 if let Ok(syntax) = parse_file(source) {
69 for item in &syntax.items {
70 registry.visit_item(item);
71 }
72 }
73 }
74 registry
75 }
76
77 #[must_use]
78 pub fn is_transitive_value_type(&self, type_name: &str) -> bool {
79 self.resolve(type_name, &mut HashSet::new())
80 }
81
82 #[must_use]
83 pub fn concrete_fields_of(&self, type_name: &str) -> HashMap<String, String> {
84 self.concrete_fields_by_struct
85 .get(type_name)
86 .cloned()
87 .unwrap_or_default()
88 }
89
90 fn resolve(&self, type_name: &str, visiting: &mut HashSet<String>) -> bool {
91 if KNOWN_STD_VALUE_TYPES.contains(&type_name) {
92 return true;
93 }
94 if !visiting.insert(type_name.to_string()) {
95 return false;
96 }
97 let result = self
98 .fields_by_struct
99 .get(type_name)
100 .is_some_and(|fields| fields.iter().all(|field| self.resolve(field, visiting)));
101 visiting.remove(type_name);
102 result
103 }
104}
105
106impl<'ast> Visit<'ast> for StructRegistry {
107 fn visit_item(&mut self, item: &'ast Item) {
108 match item {
109 Item::Struct(item_struct) => self.record_struct(item_struct),
110 Item::Mod(item_mod) => self.visit_inline_mod(item_mod),
111 _ => {}
112 }
113 }
114}
115
116impl StructRegistry {
117 fn record_struct(&mut self, item_struct: &ItemStruct) {
118 let name = item_struct.ident.to_string();
119 let mut all_field_types = Vec::new();
120 let mut concrete_fields = HashMap::new();
121 for f in &item_struct.fields {
122 let type_head = field_type_head(&f.ty);
123 all_field_types.push(type_head.clone());
124 if !is_trait_object_type(&f.ty) {
125 if let Some(field_name) = f.ident.as_ref().map(ToString::to_string) {
126 concrete_fields.insert(field_name, type_head);
127 }
128 }
129 }
130 self.fields_by_struct.insert(name.clone(), all_field_types);
131 self.concrete_fields_by_struct.insert(name, concrete_fields);
132 }
133
134 fn visit_inline_mod(&mut self, item_mod: &ItemMod) {
135 if let Some((_, items)) = &item_mod.content {
136 for inner in items {
137 self.visit_item(inner);
138 }
139 }
140 }
141}