Skip to main content

tsz_binder/modules/
resolution_debug.rs

1//! Module Resolution Debugging
2//!
3//! This module provides debugging infrastructure for symbol table operations,
4//! module scope lookups, and cross-file symbol resolution.
5//!
6//! Enable debug logging by setting `debug_enabled: true` in `ModuleResolutionDebugger`.
7
8use crate::{SymbolId, symbol_flags};
9use rustc_hash::FxHashMap;
10use std::fmt::Write;
11use std::sync::atomic::{AtomicBool, Ordering};
12use tracing::debug;
13
14/// Global flag to enable/disable module resolution debugging.
15/// Use `set_debug_enabled(true)` to turn on logging.
16static DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
17
18/// Enable or disable module resolution debugging globally.
19pub fn set_debug_enabled(enabled: bool) {
20    DEBUG_ENABLED.store(enabled, Ordering::SeqCst);
21}
22
23/// Check if module resolution debugging is enabled.
24pub fn is_debug_enabled() -> bool {
25    DEBUG_ENABLED.load(Ordering::Relaxed)
26}
27
28/// A record of a symbol declaration event.
29#[derive(Debug, Clone)]
30pub struct SymbolDeclarationEvent {
31    /// The symbol's name
32    pub name: String,
33    /// The symbol ID
34    pub symbol_id: SymbolId,
35    /// The symbol's flags (decoded)
36    pub flags_description: String,
37    /// The file where this symbol was declared
38    pub file_name: String,
39    /// Whether this was a merge with an existing symbol
40    pub is_merge: bool,
41    /// The number of declarations after this event
42    pub declaration_count: usize,
43}
44
45/// A record of a symbol lookup event.
46#[derive(Debug, Clone)]
47pub struct SymbolLookupEvent {
48    /// The name being looked up
49    pub name: String,
50    /// The scope path searched (from innermost to outermost)
51    pub scope_path: Vec<String>,
52    /// Whether the lookup was successful
53    pub found: bool,
54    /// The symbol ID if found
55    pub symbol_id: Option<SymbolId>,
56    /// The file where the symbol was found (if any)
57    pub found_in_file: Option<String>,
58}
59
60/// A record of a symbol merge operation.
61#[derive(Debug, Clone)]
62pub struct SymbolMergeEvent {
63    /// The symbol's name
64    pub name: String,
65    /// The symbol ID
66    pub symbol_id: SymbolId,
67    /// The existing flags before merge
68    pub existing_flags: String,
69    /// The new flags being merged
70    pub new_flags: String,
71    /// The combined flags after merge
72    pub combined_flags: String,
73    /// The file contributing the new declaration
74    pub contributing_file: String,
75}
76
77/// Debugger for module resolution operations.
78#[derive(Debug, Default)]
79pub struct ModuleResolutionDebugger {
80    /// All symbol declaration events
81    pub declaration_events: Vec<SymbolDeclarationEvent>,
82    /// All symbol lookup events
83    pub lookup_events: Vec<SymbolLookupEvent>,
84    /// All symbol merge events
85    pub merge_events: Vec<SymbolMergeEvent>,
86    /// Symbol origins: maps `SymbolId` to the file name where it was first declared
87    pub symbol_origins: FxHashMap<SymbolId, String>,
88    /// Current file being processed
89    pub current_file: String,
90}
91
92impl ModuleResolutionDebugger {
93    /// Create a new debugger instance.
94    #[must_use]
95    pub fn new() -> Self {
96        Self::default()
97    }
98
99    /// Set the current file being processed.
100    pub fn set_current_file(&mut self, file_name: &str) {
101        self.current_file = file_name.to_string();
102    }
103
104    /// Record a symbol declaration.
105    pub fn record_declaration(
106        &mut self,
107        name: &str,
108        symbol_id: SymbolId,
109        flags: u32,
110        declaration_count: usize,
111        is_merge: bool,
112    ) {
113        if !is_debug_enabled() {
114            return;
115        }
116
117        let event = SymbolDeclarationEvent {
118            name: name.to_string(),
119            symbol_id,
120            flags_description: flags_to_string(flags),
121            file_name: self.current_file.clone(),
122            is_merge,
123            declaration_count,
124        };
125
126        // Track symbol origin (only for first declaration)
127        if !is_merge && !self.symbol_origins.contains_key(&symbol_id) {
128            self.symbol_origins
129                .insert(symbol_id, self.current_file.clone());
130        }
131
132        if is_debug_enabled() {
133            debug!(
134                "[MODULE_DEBUG] {} symbol '{}' (id={}) with flags [{}] in {} (decls={})",
135                if is_merge { "MERGED" } else { "DECLARED" },
136                event.name,
137                symbol_id.0,
138                event.flags_description,
139                event.file_name,
140                event.declaration_count
141            );
142        }
143
144        self.declaration_events.push(event);
145    }
146
147    /// Record a symbol merge operation.
148    pub fn record_merge(
149        &mut self,
150        name: &str,
151        symbol_id: SymbolId,
152        existing_flags: u32,
153        new_flags: u32,
154        combined_flags: u32,
155    ) {
156        if !is_debug_enabled() {
157            return;
158        }
159
160        let event = SymbolMergeEvent {
161            name: name.to_string(),
162            symbol_id,
163            existing_flags: flags_to_string(existing_flags),
164            new_flags: flags_to_string(new_flags),
165            combined_flags: flags_to_string(combined_flags),
166            contributing_file: self.current_file.clone(),
167        };
168
169        debug!(
170            "[MODULE_DEBUG] MERGE '{}' (id={}): [{}] + [{}] = [{}] (from {})",
171            event.name,
172            symbol_id.0,
173            event.existing_flags,
174            event.new_flags,
175            event.combined_flags,
176            event.contributing_file
177        );
178
179        self.merge_events.push(event);
180    }
181
182    /// Record a symbol lookup.
183    pub fn record_lookup(&mut self, name: &str, scope_path: &[String], result: Option<SymbolId>) {
184        if !is_debug_enabled() {
185            return;
186        }
187
188        let lookup_message = match result {
189            Some(id) => format!("FOUND (id={})", id.0),
190            None => "NOT FOUND".to_string(),
191        };
192        let found_in_file = result.and_then(|id| self.symbol_origins.get(&id).cloned());
193
194        let event = SymbolLookupEvent {
195            name: name.to_string(),
196            scope_path: scope_path.to_owned(),
197            found: result.is_some(),
198            symbol_id: result,
199            found_in_file: found_in_file.clone(),
200        };
201
202        debug!(
203            "[MODULE_DEBUG] LOOKUP '{}': scopes=[{}] -> {} (file: {})",
204            event.name,
205            scope_path.join(" -> "),
206            lookup_message,
207            found_in_file.unwrap_or_else(|| "unknown".to_string())
208        );
209
210        self.lookup_events.push(event);
211    }
212
213    /// Get a summary of all recorded events.
214    #[must_use]
215    pub fn get_summary(&self) -> String {
216        let mut summary = String::new();
217        summary.push_str("=== Module Resolution Debug Summary ===\n\n");
218
219        let _ = writeln!(
220            summary,
221            "Total declarations: {}",
222            self.declaration_events.len()
223        );
224        let _ = writeln!(summary, "Total merges: {}", self.merge_events.len());
225        let _ = writeln!(summary, "Total lookups: {}\n", self.lookup_events.len());
226
227        // Symbol origins by file
228        summary.push_str("Symbol Origins by File:\n");
229        let mut by_file: FxHashMap<String, Vec<SymbolId>> = FxHashMap::default();
230        for (sym_id, file) in &self.symbol_origins {
231            by_file.entry(file.clone()).or_default().push(*sym_id);
232        }
233        for (file, symbols) in &by_file {
234            let _ = writeln!(summary, "  {}: {} symbols", file, symbols.len());
235        }
236
237        // Merge operations
238        if !self.merge_events.is_empty() {
239            summary.push_str("\nMerge Operations:\n");
240            for event in &self.merge_events {
241                let _ = writeln!(
242                    summary,
243                    "  {} (id={}): [{}] + [{}] = [{}] from {}",
244                    event.name,
245                    event.symbol_id.0,
246                    event.existing_flags,
247                    event.new_flags,
248                    event.combined_flags,
249                    event.contributing_file
250                );
251            }
252        }
253
254        // Failed lookups
255        let failed_lookups: Vec<_> = self.lookup_events.iter().filter(|e| !e.found).collect();
256        if !failed_lookups.is_empty() {
257            summary.push_str("\nFailed Lookups:\n");
258            for event in failed_lookups {
259                let _ = writeln!(
260                    summary,
261                    "  '{}': searched [{}]",
262                    event.name,
263                    event.scope_path.join(" -> ")
264                );
265            }
266        }
267
268        summary
269    }
270
271    /// Clear all recorded events.
272    pub fn clear(&mut self) {
273        self.declaration_events.clear();
274        self.lookup_events.clear();
275        self.merge_events.clear();
276        self.symbol_origins.clear();
277    }
278}
279
280/// Convert symbol flags to a human-readable string.
281#[must_use]
282pub fn flags_to_string(flags: u32) -> String {
283    let mut parts = Vec::new();
284
285    if flags & symbol_flags::FUNCTION_SCOPED_VARIABLE != 0 {
286        parts.push("VAR");
287    }
288    if flags & symbol_flags::BLOCK_SCOPED_VARIABLE != 0 {
289        parts.push("LET/CONST");
290    }
291    if flags & symbol_flags::PROPERTY != 0 {
292        parts.push("PROPERTY");
293    }
294    if flags & symbol_flags::ENUM_MEMBER != 0 {
295        parts.push("ENUM_MEMBER");
296    }
297    if flags & symbol_flags::FUNCTION != 0 {
298        parts.push("FUNCTION");
299    }
300    if flags & symbol_flags::CLASS != 0 {
301        parts.push("CLASS");
302    }
303    if flags & symbol_flags::INTERFACE != 0 {
304        parts.push("INTERFACE");
305    }
306    if flags & symbol_flags::CONST_ENUM != 0 {
307        parts.push("CONST_ENUM");
308    }
309    if flags & symbol_flags::REGULAR_ENUM != 0 {
310        parts.push("ENUM");
311    }
312    if flags & symbol_flags::VALUE_MODULE != 0 {
313        parts.push("VALUE_MODULE");
314    }
315    if flags & symbol_flags::NAMESPACE_MODULE != 0 {
316        parts.push("NAMESPACE");
317    }
318    if flags & symbol_flags::TYPE_LITERAL != 0 {
319        parts.push("TYPE_LITERAL");
320    }
321    if flags & symbol_flags::OBJECT_LITERAL != 0 {
322        parts.push("OBJECT_LITERAL");
323    }
324    if flags & symbol_flags::METHOD != 0 {
325        parts.push("METHOD");
326    }
327    if flags & symbol_flags::CONSTRUCTOR != 0 {
328        parts.push("CONSTRUCTOR");
329    }
330    if flags & symbol_flags::GET_ACCESSOR != 0 {
331        parts.push("GETTER");
332    }
333    if flags & symbol_flags::SET_ACCESSOR != 0 {
334        parts.push("SETTER");
335    }
336    if flags & symbol_flags::TYPE_PARAMETER != 0 {
337        parts.push("TYPE_PARAM");
338    }
339    if flags & symbol_flags::TYPE_ALIAS != 0 {
340        parts.push("TYPE_ALIAS");
341    }
342    if flags & symbol_flags::ALIAS != 0 {
343        parts.push("ALIAS");
344    }
345    if flags & symbol_flags::EXPORT_VALUE != 0 {
346        parts.push("EXPORT");
347    }
348
349    if parts.is_empty() {
350        "NONE".to_string()
351    } else {
352        parts.join("|")
353    }
354}
355
356#[cfg(test)]
357#[path = "../../tests/module_resolution_debug.rs"]
358mod tests;