debtmap/analyzers/
type_registry.rs

1use std::collections::HashMap;
2use std::path::PathBuf;
3use syn::{Field, Fields, Item, ItemStruct, Path, PathSegment, Type, TypePath, TypeReference};
4
5/// Global type registry for tracking struct definitions across the codebase
6#[derive(Debug, Clone)]
7pub struct GlobalTypeRegistry {
8    /// Map from fully-qualified type name to type definition
9    pub types: HashMap<String, TypeDefinition>,
10    /// Map from module path to exported types
11    pub module_exports: HashMap<Vec<String>, Vec<String>>,
12    /// Type alias mappings
13    pub type_aliases: HashMap<String, String>,
14    /// Import mappings for each file
15    pub imports: HashMap<PathBuf, ImportScope>,
16}
17
18/// Definition of a type (struct, enum, etc.)
19#[derive(Debug, Clone)]
20pub struct TypeDefinition {
21    pub name: String,
22    pub kind: TypeKind,
23    pub fields: Option<FieldRegistry>,
24    pub methods: Vec<MethodSignature>,
25    pub generic_params: Vec<String>,
26    pub module_path: Vec<String>,
27}
28
29/// Registry of fields for a struct
30#[derive(Debug, Clone)]
31pub struct FieldRegistry {
32    /// Named fields for structs
33    pub named_fields: HashMap<String, ResolvedFieldType>,
34    /// Positional fields for tuple structs
35    pub tuple_fields: Vec<ResolvedFieldType>,
36}
37
38/// A resolved field type
39#[derive(Debug, Clone)]
40pub struct ResolvedFieldType {
41    pub type_name: String,
42    pub is_reference: bool,
43    pub is_mutable: bool,
44    pub generic_args: Vec<String>,
45}
46
47/// Method signature information
48#[derive(Debug, Clone)]
49pub struct MethodSignature {
50    pub name: String,
51    pub self_param: Option<SelfParam>,
52    pub return_type: Option<String>,
53    pub param_types: Vec<String>,
54}
55
56/// Self parameter information
57#[derive(Debug, Clone)]
58pub struct SelfParam {
59    pub is_reference: bool,
60    pub is_mutable: bool,
61}
62
63/// Kind of type definition
64#[derive(Debug, Clone, PartialEq)]
65pub enum TypeKind {
66    Struct,
67    Enum,
68    Trait,
69    TypeAlias,
70    TupleStruct,
71    UnitStruct,
72}
73
74/// Import scope for a file
75#[derive(Debug, Clone)]
76pub struct ImportScope {
77    /// Direct imports (use statements)
78    pub imports: HashMap<String, String>, // local name -> fully qualified name
79    /// Module imports (use module::*)
80    pub wildcard_imports: Vec<Vec<String>>,
81}
82
83impl Default for GlobalTypeRegistry {
84    fn default() -> Self {
85        Self::new()
86    }
87}
88
89impl GlobalTypeRegistry {
90    pub fn new() -> Self {
91        Self {
92            types: HashMap::new(),
93            module_exports: HashMap::new(),
94            type_aliases: HashMap::new(),
95            imports: HashMap::new(),
96        }
97    }
98
99    /// Register a struct definition
100    pub fn register_struct(&mut self, module_path: Vec<String>, item: &ItemStruct) {
101        let name = item.ident.to_string();
102        let full_name = if module_path.is_empty() {
103            name.clone()
104        } else {
105            format!("{}::{}", module_path.join("::"), name)
106        };
107
108        let fields = self.extract_fields(&item.fields);
109        let generic_params = item
110            .generics
111            .params
112            .iter()
113            .filter_map(|param| match param {
114                syn::GenericParam::Type(type_param) => Some(type_param.ident.to_string()),
115                _ => None,
116            })
117            .collect();
118
119        let kind = match &item.fields {
120            Fields::Named(_) => TypeKind::Struct,
121            Fields::Unnamed(_) => TypeKind::TupleStruct,
122            Fields::Unit => TypeKind::UnitStruct,
123        };
124
125        let type_def = TypeDefinition {
126            name: full_name.clone(),
127            kind,
128            fields: Some(fields),
129            methods: Vec::new(),
130            generic_params,
131            module_path: module_path.clone(),
132        };
133
134        self.types.insert(full_name, type_def);
135
136        // Update module exports
137        self.module_exports
138            .entry(module_path)
139            .or_default()
140            .push(name);
141    }
142
143    /// Extract fields from a struct
144    fn extract_fields(&self, fields: &Fields) -> FieldRegistry {
145        match fields {
146            Fields::Named(named_fields) => {
147                let mut named = HashMap::new();
148                for field in &named_fields.named {
149                    if let Some(ident) = &field.ident {
150                        let field_type = self.extract_field_type(field);
151                        named.insert(ident.to_string(), field_type);
152                    }
153                }
154                FieldRegistry {
155                    named_fields: named,
156                    tuple_fields: Vec::new(),
157                }
158            }
159            Fields::Unnamed(unnamed_fields) => {
160                let tuple_fields = unnamed_fields
161                    .unnamed
162                    .iter()
163                    .map(|field| self.extract_field_type(field))
164                    .collect();
165                FieldRegistry {
166                    named_fields: HashMap::new(),
167                    tuple_fields,
168                }
169            }
170            Fields::Unit => FieldRegistry {
171                named_fields: HashMap::new(),
172                tuple_fields: Vec::new(),
173            },
174        }
175    }
176
177    /// Extract type name from a syn::Path
178    #[allow(dead_code)]
179    fn extract_type_name_from_path(path: &syn::Path) -> String {
180        path.segments
181            .iter()
182            .map(|seg| seg.ident.to_string())
183            .collect::<Vec<_>>()
184            .join("::")
185    }
186
187    /// Extract type information from a field
188    fn extract_field_type(&self, field: &Field) -> ResolvedFieldType {
189        match &field.ty {
190            Type::Path(type_path) => Self::extract_path_type(type_path),
191            Type::Reference(type_ref) => Self::extract_reference_type(type_ref),
192            _ => Self::unknown_field_type(),
193        }
194    }
195
196    /// Extract type information from a path type
197    fn extract_path_type(type_path: &TypePath) -> ResolvedFieldType {
198        let type_name = Self::build_type_name(&type_path.path);
199        let generic_args = Self::extract_generic_args(&type_path.path);
200
201        ResolvedFieldType {
202            type_name,
203            is_reference: false,
204            is_mutable: false,
205            generic_args,
206        }
207    }
208
209    /// Extract type information from a reference type
210    fn extract_reference_type(type_ref: &TypeReference) -> ResolvedFieldType {
211        let base_type = match &*type_ref.elem {
212            Type::Path(type_path) => ResolvedFieldType {
213                type_name: Self::build_type_name(&type_path.path),
214                is_reference: true,
215                is_mutable: type_ref.mutability.is_some(),
216                generic_args: Self::extract_generic_args(&type_path.path),
217            },
218            _ => Self::unknown_field_type(),
219        };
220
221        ResolvedFieldType {
222            is_reference: true,
223            is_mutable: type_ref.mutability.is_some(),
224            ..base_type
225        }
226    }
227
228    /// Build a type name from a path
229    fn build_type_name(path: &Path) -> String {
230        path.segments
231            .iter()
232            .map(|seg| seg.ident.to_string())
233            .collect::<Vec<_>>()
234            .join("::")
235    }
236
237    /// Extract generic arguments from a path
238    fn extract_generic_args(path: &Path) -> Vec<String> {
239        path.segments
240            .last()
241            .and_then(Self::extract_args_from_segment)
242            .unwrap_or_default()
243    }
244
245    /// Extract arguments from a path segment
246    fn extract_args_from_segment(segment: &PathSegment) -> Option<Vec<String>> {
247        match &segment.arguments {
248            syn::PathArguments::AngleBracketed(args) => Some(
249                args.args
250                    .iter()
251                    .filter_map(Self::extract_type_name_from_arg)
252                    .collect(),
253            ),
254            _ => None,
255        }
256    }
257
258    /// Extract type name from a generic argument
259    fn extract_type_name_from_arg(arg: &syn::GenericArgument) -> Option<String> {
260        match arg {
261            syn::GenericArgument::Type(Type::Path(type_path)) => type_path
262                .path
263                .segments
264                .last()
265                .map(|seg| seg.ident.to_string()),
266            _ => None,
267        }
268    }
269
270    /// Create an unknown field type
271    fn unknown_field_type() -> ResolvedFieldType {
272        ResolvedFieldType {
273            type_name: "Unknown".to_string(),
274            is_reference: false,
275            is_mutable: false,
276            generic_args: Vec::new(),
277        }
278    }
279
280    /// Get a type definition by name
281    pub fn get_type(&self, name: &str) -> Option<&TypeDefinition> {
282        self.types.get(name)
283    }
284
285    /// Resolve a field on a type
286    pub fn resolve_field(&self, type_name: &str, field_name: &str) -> Option<ResolvedFieldType> {
287        let type_def = self.get_type(type_name)?;
288        let fields = type_def.fields.as_ref()?;
289        fields.named_fields.get(field_name).cloned()
290    }
291
292    /// Get field by index for tuple structs
293    pub fn resolve_tuple_field(&self, type_name: &str, index: usize) -> Option<ResolvedFieldType> {
294        let type_def = self.get_type(type_name)?;
295        let fields = type_def.fields.as_ref()?;
296        fields.tuple_fields.get(index).cloned()
297    }
298
299    /// Add a method to a type
300    pub fn add_method(&mut self, type_name: &str, method: MethodSignature) {
301        if let Some(type_def) = self.types.get_mut(type_name) {
302            type_def.methods.push(method);
303        }
304    }
305
306    /// Register a type alias
307    pub fn register_type_alias(&mut self, alias: String, target: String) {
308        self.type_aliases.insert(alias, target);
309    }
310
311    /// Resolve a type alias
312    pub fn resolve_type_alias(&self, alias: &str) -> Option<&String> {
313        self.type_aliases.get(alias)
314    }
315
316    /// Register imports for a file
317    pub fn register_imports(&mut self, file: PathBuf, imports: ImportScope) {
318        self.imports.insert(file, imports);
319    }
320
321    /// Get imports for a file
322    pub fn get_imports(&self, file: &PathBuf) -> Option<&ImportScope> {
323        self.imports.get(file)
324    }
325
326    /// Resolve a type name using imports
327    pub fn resolve_type_with_imports(&self, file: &PathBuf, name: &str) -> Option<String> {
328        // First check if it's already fully qualified
329        if self.types.contains_key(name) {
330            return Some(name.to_string());
331        }
332
333        // Check imports for this file
334        if let Some(import_scope) = self.get_imports(file) {
335            // Check direct imports
336            if let Some(full_name) = import_scope.imports.get(name) {
337                return Some(full_name.clone());
338            }
339
340            // Check wildcard imports
341            for module_path in &import_scope.wildcard_imports {
342                let potential_name = format!("{}::{}", module_path.join("::"), name);
343                if self.types.contains_key(&potential_name) {
344                    return Some(potential_name);
345                }
346            }
347        }
348
349        // Check if it's a type alias
350        if let Some(target) = self.resolve_type_alias(name) {
351            return Some(target.clone());
352        }
353
354        None
355    }
356}
357
358/// Extract type definitions from a parsed file
359pub fn extract_type_definitions(
360    file: &syn::File,
361    module_path: Vec<String>,
362    registry: &mut GlobalTypeRegistry,
363) {
364    for item in &file.items {
365        match item {
366            Item::Struct(item_struct) => {
367                registry.register_struct(module_path.clone(), item_struct);
368            }
369            Item::Mod(item_mod) => {
370                if let Some((_, items)) = &item_mod.content {
371                    let mut nested_path = module_path.clone();
372                    nested_path.push(item_mod.ident.to_string());
373                    for item in items {
374                        if let Item::Struct(item_struct) = item {
375                            registry.register_struct(nested_path.clone(), item_struct);
376                        }
377                    }
378                }
379            }
380            _ => {}
381        }
382    }
383}
384
385#[cfg(test)]
386mod tests {
387    use super::*;
388    use syn::{parse_quote, Field};
389
390    #[test]
391    fn test_extract_type_name_from_simple_path() {
392        let path: syn::Path = parse_quote!(String);
393        assert_eq!(
394            GlobalTypeRegistry::extract_type_name_from_path(&path),
395            "String"
396        );
397    }
398
399    #[test]
400    fn test_extract_type_name_from_qualified_path() {
401        let path: syn::Path = parse_quote!(std::collections::HashMap);
402        assert_eq!(
403            GlobalTypeRegistry::extract_type_name_from_path(&path),
404            "std::collections::HashMap"
405        );
406    }
407
408    #[test]
409    fn test_extract_generic_args_none() {
410        let path: syn::Path = parse_quote!(String);
411        assert_eq!(
412            GlobalTypeRegistry::extract_generic_args(&path),
413            Vec::<String>::new()
414        );
415    }
416
417    #[test]
418    fn test_extract_generic_args_single() {
419        let path: syn::Path = parse_quote!(Option<String>);
420        assert_eq!(
421            GlobalTypeRegistry::extract_generic_args(&path),
422            vec!["String"]
423        );
424    }
425
426    #[test]
427    fn test_extract_generic_args_multiple() {
428        let path: syn::Path = parse_quote!(HashMap<String, Value>);
429        let args = GlobalTypeRegistry::extract_generic_args(&path);
430        assert_eq!(args.len(), 2);
431        assert!(args.contains(&"String".to_string()));
432        assert!(args.contains(&"Value".to_string()));
433    }
434
435    #[test]
436    fn test_extract_field_type_simple() {
437        let registry = GlobalTypeRegistry::new();
438        let field: Field = parse_quote!(pub name: String);
439        let field_type = registry.extract_field_type(&field);
440
441        assert_eq!(field_type.type_name, "String");
442        assert!(!field_type.is_reference);
443        assert!(!field_type.is_mutable);
444        assert!(field_type.generic_args.is_empty());
445    }
446
447    #[test]
448    fn test_extract_field_type_reference() {
449        let registry = GlobalTypeRegistry::new();
450        let field: Field = parse_quote!(pub name: &str);
451        let field_type = registry.extract_field_type(&field);
452
453        assert_eq!(field_type.type_name, "str");
454        assert!(field_type.is_reference);
455        assert!(!field_type.is_mutable);
456    }
457
458    #[test]
459    fn test_extract_field_type_mutable_reference() {
460        let registry = GlobalTypeRegistry::new();
461        let field: Field = parse_quote!(pub name: &mut String);
462        let field_type = registry.extract_field_type(&field);
463
464        assert_eq!(field_type.type_name, "String");
465        assert!(field_type.is_reference);
466        assert!(field_type.is_mutable);
467    }
468
469    #[test]
470    fn test_extract_field_type_with_generics() {
471        let registry = GlobalTypeRegistry::new();
472        let field: Field = parse_quote!(pub items: Vec<Item>);
473        let field_type = registry.extract_field_type(&field);
474
475        assert_eq!(field_type.type_name, "Vec");
476        assert_eq!(field_type.generic_args, vec!["Item"]);
477    }
478
479    #[test]
480    fn test_extract_field_type_unknown() {
481        let registry = GlobalTypeRegistry::new();
482        let field: Field = parse_quote!(pub callback: fn());
483        let field_type = registry.extract_field_type(&field);
484
485        assert_eq!(field_type.type_name, "Unknown");
486    }
487}