mago_codex/
reference.rs

1use ahash::HashMap;
2use ahash::HashSet;
3use serde::Deserialize;
4use serde::Serialize;
5
6use mago_interner::StringIdentifier;
7
8use crate::context::ScopeContext;
9use crate::diff::CodebaseDiff;
10use crate::identifier::function_like::FunctionLikeIdentifier;
11use crate::identifier::method::MethodIdentifier;
12use crate::symbol::SymbolIdentifier;
13
14/// Represents the source of a reference, distinguishing between top-level symbols
15/// and members within a class-like structure.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
17pub enum ReferenceSource {
18    /// A reference from a top-level symbol (function, class, enum, trait, interface, constant).
19    /// The bool indicates if the reference occurs within a signature context (true) or body (false).
20    /// The StringIdentifier is the name (FQCN or FQN) of the referencing symbol.
21    Symbol(bool, StringIdentifier),
22    /// A reference from a member within a class-like structure (method, property, class constant, enum case).
23    /// The bool indicates if the reference occurs within a signature context (true) or body (false).
24    /// The first StringIdentifier is the FQCN of the class-like structure.
25    /// The second StringIdentifier is the name of the member.
26    ClassLikeMember(bool, StringIdentifier, StringIdentifier),
27}
28
29/// Holds sets of symbols and members identified as invalid during analysis,
30/// often due to changes detected in `CodebaseDiff`.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
32pub struct InvalidSymbols {
33    /// Set of (Symbol, Member) pairs whose *signatures* are considered invalid.
34    /// An empty member name usually indicates the symbol itself.
35    invalid_symbol_and_member_signatures: HashSet<SymbolIdentifier>,
36    /// Set of (Symbol, Member) pairs whose *bodies* are considered invalid.
37    /// An empty member name usually indicates the symbol itself.
38    invalid_symbol_and_member_bodies: HashSet<SymbolIdentifier>,
39    /// Set of top-level symbols (class FQCN, function FQN) that are partially invalid,
40    /// meaning at least one member's signature or body is invalid, but not necessarily the whole symbol.
41    partially_invalid_symbols: HashSet<StringIdentifier>,
42}
43
44/// Stores various maps tracking references between symbols (classes, functions, etc.)
45/// and class-like members (methods, properties, constants, etc.) within the codebase.
46///
47/// This is primarily used for dependency analysis, understanding code structure,
48/// and potentially for tasks like dead code detection or impact analysis.
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)] // Added PartialEq
50pub struct SymbolReferences {
51    /// Maps a referencing symbol/member `(RefSymbol, RefMember)` to a set of referenced symbols/members `(Symbol, Member)`
52    /// found within the *body* of the referencing context.
53    /// `RefMember` or `Member` being empty usually signifies the symbol itself.
54    symbol_references_to_symbols: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
55
56    /// Maps a referencing symbol/member `(RefSymbol, RefMember)` to a set of referenced symbols/members `(Symbol, Member)`
57    /// found within the *signature* (e.g., type hints, attributes) of the referencing context.
58    symbol_references_to_symbols_in_signature: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
59
60    /// Maps a referencing symbol/member `(RefSymbol, RefMember)` to a set of *overridden* members `(ParentSymbol, Member)`
61    /// that it directly references (e.g., via `parent::method()`).
62    symbol_references_to_overridden_members: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
63
64    /// Maps a referencing function/method (`FunctionLikeIdentifier`) to a set of functions/methods (`FunctionLikeIdentifier`)
65    /// whose return values it references/uses. Used for dead code analysis on return values.
66    functionlike_references_to_functionlike_returns: HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>>,
67}
68
69impl SymbolReferences {
70    /// Creates a new, empty `SymbolReferences` collection.
71    #[inline]
72    pub fn new() -> Self {
73        Self {
74            symbol_references_to_symbols: HashMap::default(),
75            symbol_references_to_symbols_in_signature: HashMap::default(),
76            symbol_references_to_overridden_members: HashMap::default(),
77            functionlike_references_to_functionlike_returns: HashMap::default(),
78        }
79    }
80
81    /// Records that a top-level symbol (e.g., a function) references a class member.
82    ///
83    /// Automatically adds a reference from the referencing symbol to the member's class.
84    ///
85    /// # Arguments
86    /// * `referencing_symbol`: The FQN of the function or global const making the reference.
87    /// * `class_member`: A tuple `(ClassName, MemberName)` being referenced.
88    /// * `in_signature`: `true` if the reference occurs in a signature context, `false` if in the body.
89    #[inline]
90    pub fn add_symbol_reference_to_class_member(
91        &mut self,
92        referencing_symbol: StringIdentifier,
93        class_member: SymbolIdentifier,
94        in_signature: bool,
95    ) {
96        // Reference the class itself implicitly (in body context)
97        self.add_symbol_reference_to_symbol(referencing_symbol, class_member.0, false);
98
99        // Use empty member for the referencing symbol key
100        let key = (referencing_symbol, StringIdentifier::empty());
101        if in_signature {
102            self.symbol_references_to_symbols_in_signature.entry(key).or_default().insert(class_member);
103        } else {
104            self.symbol_references_to_symbols.entry(key).or_default().insert(class_member);
105        }
106    }
107
108    /// Records that a top-level symbol references another top-level symbol.
109    ///
110    /// Skips self-references. Skips body references if already referenced in signature.
111    ///
112    /// # Arguments
113    /// * `referencing_symbol`: The FQN of the symbol making the reference.
114    /// * `symbol`: The FQN of the symbol being referenced.
115    /// * `in_signature`: `true` if the reference occurs in a signature context, `false` if in the body.
116    #[inline]
117    pub fn add_symbol_reference_to_symbol(
118        &mut self,
119        referencing_symbol: StringIdentifier,
120        symbol: StringIdentifier,
121        in_signature: bool,
122    ) {
123        if referencing_symbol == symbol {
124            return;
125        }
126
127        // Represent top-level symbols with an empty member identifier
128        let referencing_key = (referencing_symbol, StringIdentifier::empty());
129        let referenced_key = (symbol, StringIdentifier::empty());
130
131        if in_signature {
132            self.symbol_references_to_symbols_in_signature.entry(referencing_key).or_default().insert(referenced_key);
133        } else {
134            // If it's already referenced in the signature, don't add as a body reference
135            if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_key)
136                && sig_refs.contains(&referenced_key)
137            {
138                return;
139            }
140            self.symbol_references_to_symbols.entry(referencing_key).or_default().insert(referenced_key);
141        }
142    }
143
144    /// Records that a class member references another class member.
145    ///
146    /// Automatically adds references from the referencing member's class to the referenced member's class,
147    /// and from the referencing member to the referenced member's class. Skips self-references.
148    ///
149    /// # Arguments
150    /// * `referencing_class_member`: Tuple `(ClassName, MemberName)` making the reference.
151    /// * `class_member`: Tuple `(ClassName, MemberName)` being referenced.
152    /// * `in_signature`: `true` if the reference occurs in a signature context, `false` if in the body.
153    #[inline]
154    pub fn add_class_member_reference_to_class_member(
155        &mut self,
156        referencing_class_member: SymbolIdentifier,
157        class_member: SymbolIdentifier,
158        in_signature: bool,
159    ) {
160        if referencing_class_member == class_member {
161            return;
162        }
163
164        // Add implicit references between the classes/symbols involved
165        self.add_symbol_reference_to_symbol(referencing_class_member.0, class_member.0, false);
166        self.add_class_member_reference_to_symbol(referencing_class_member, class_member.0, false);
167
168        // Add the direct member-to-member reference
169        if in_signature {
170            self.symbol_references_to_symbols_in_signature
171                .entry(referencing_class_member)
172                .or_default()
173                .insert(class_member);
174        } else {
175            // Check signature refs first? (Consistency with add_symbol_reference_to_symbol might be needed)
176            // Current logic adds to body refs regardless of signature refs for member->member.
177            self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(class_member);
178        }
179    }
180
181    /// Records that a class member references a top-level symbol.
182    ///
183    /// Automatically adds a reference from the referencing member's class to the referenced symbol.
184    /// Skips references to the member's own class. Skips body references if already referenced in signature.
185    ///
186    /// # Arguments
187    /// * `referencing_class_member`: Tuple `(ClassName, MemberName)` making the reference.
188    /// * `symbol`: The FQN of the symbol being referenced.
189    /// * `in_signature`: `true` if the reference occurs in a signature context, `false` if in the body.
190    #[inline]
191    pub fn add_class_member_reference_to_symbol(
192        &mut self,
193        referencing_class_member: SymbolIdentifier,
194        symbol: StringIdentifier,
195        in_signature: bool,
196    ) {
197        if referencing_class_member.0 == symbol {
198            return;
199        }
200
201        // Add implicit reference from the class to the symbol
202        self.add_symbol_reference_to_symbol(referencing_class_member.0, symbol, false);
203
204        // Represent the referenced symbol with an empty member identifier
205        let referenced_key = (symbol, StringIdentifier::empty());
206
207        if in_signature {
208            self.symbol_references_to_symbols_in_signature
209                .entry(referencing_class_member)
210                .or_default()
211                .insert(referenced_key);
212        } else {
213            // If already referenced in signature, don't add as body reference
214            if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_class_member)
215                && sig_refs.contains(&referenced_key)
216            {
217                return;
218            }
219            self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(referenced_key);
220        }
221    }
222
223    /// Convenience method to add a reference *from* the current function context *to* a class member.
224    /// Delegates to appropriate `add_*` methods based on the function context.
225    #[inline]
226    pub fn add_reference_to_class_member(
227        &mut self,
228        scope: &ScopeContext<'_>,
229        class_member: SymbolIdentifier,
230        in_signature: bool,
231    ) {
232        if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
233            match referencing_functionlike {
234                FunctionLikeIdentifier::Function(function_name) => {
235                    self.add_symbol_reference_to_class_member(function_name, class_member, in_signature)
236                }
237                FunctionLikeIdentifier::Method(class_name, function_name) => self
238                    .add_class_member_reference_to_class_member(
239                        (class_name, function_name),
240                        class_member,
241                        in_signature,
242                    ),
243                _ => {
244                    // A reference from a closure can be ignored for now.
245                }
246            }
247        } else if let Some(calling_class) = scope.get_class_like_name() {
248            // Reference from the class scope itself (e.g., property default)
249            self.add_symbol_reference_to_class_member(*calling_class, class_member, in_signature)
250        }
251        // If no context, the reference source is unknown/untracked in this map
252    }
253
254    #[inline]
255    pub fn add_reference_for_method_call(&mut self, scope: &ScopeContext<'_>, method: &MethodIdentifier) {
256        self.add_reference_to_class_member(scope, (*method.get_class_name(), *method.get_method_name()), false);
257    }
258
259    #[inline]
260    pub fn add_reference_for_property_access(
261        &mut self,
262        scope: &ScopeContext<'_>,
263        class_name: StringIdentifier,
264        property_name: StringIdentifier,
265    ) {
266        self.add_reference_to_class_member(scope, (class_name, property_name), false);
267    }
268
269    /// Convenience method to add a reference *from* the current function context *to* an overridden class member (e.g., `parent::foo`).
270    /// Delegates based on the function context.
271    #[inline]
272    pub fn add_reference_to_overridden_class_member(&mut self, scope: &ScopeContext, class_member: SymbolIdentifier) {
273        let referencing_key = if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
274            match referencing_functionlike {
275                FunctionLikeIdentifier::Function(function_name) => (StringIdentifier::empty(), function_name),
276                FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
277                _ => {
278                    // A reference from a closure can be ignored for now.
279                    return;
280                }
281            }
282        } else if let Some(calling_class) = scope.get_class_like_name() {
283            (*calling_class, StringIdentifier::empty())
284        } else {
285            return; // Cannot record reference without a source context
286        };
287
288        self.symbol_references_to_overridden_members.entry(referencing_key).or_default().insert(class_member);
289    }
290
291    /// Convenience method to add a reference *from* the current function context *to* a top-level symbol.
292    /// Delegates to appropriate `add_*` methods based on the function context.
293    #[inline]
294    pub fn add_reference_to_symbol(&mut self, scope: &ScopeContext, symbol: StringIdentifier, in_signature: bool) {
295        if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
296            match referencing_functionlike {
297                FunctionLikeIdentifier::Function(function_name) => {
298                    self.add_symbol_reference_to_symbol(function_name, symbol, in_signature)
299                }
300                FunctionLikeIdentifier::Method(class_name, function_name) => {
301                    self.add_class_member_reference_to_symbol((class_name, function_name), symbol, in_signature)
302                }
303                _ => {
304                    // Ignore references from closures.
305                }
306            }
307        } else if let Some(calling_class) = scope.get_class_like_name() {
308            self.add_symbol_reference_to_symbol(*calling_class, symbol, in_signature)
309        }
310    }
311
312    /// Records that one function/method references the return value of another. Used for dead code analysis.
313    #[inline]
314    pub fn add_reference_to_functionlike_return(
315        &mut self,
316        referencing_functionlike: FunctionLikeIdentifier,
317        referenced_functionlike: FunctionLikeIdentifier,
318    ) {
319        if referencing_functionlike == referenced_functionlike {
320            return;
321        }
322
323        self.functionlike_references_to_functionlike_returns
324            .entry(referencing_functionlike)
325            .or_default()
326            .insert(referenced_functionlike);
327    }
328
329    /// Merges references from another `SymbolReferences` instance into this one.
330    /// Existing references are extended, not replaced.
331    #[inline]
332    pub fn extend(&mut self, other: Self) {
333        for (k, v) in other.symbol_references_to_symbols {
334            self.symbol_references_to_symbols.entry(k).or_default().extend(v);
335        }
336        for (k, v) in other.symbol_references_to_symbols_in_signature {
337            self.symbol_references_to_symbols_in_signature.entry(k).or_default().extend(v);
338        }
339        for (k, v) in other.symbol_references_to_overridden_members {
340            self.symbol_references_to_overridden_members.entry(k).or_default().extend(v);
341        }
342        for (k, v) in other.functionlike_references_to_functionlike_returns {
343            self.functionlike_references_to_functionlike_returns.entry(k).or_default().extend(v);
344        }
345    }
346
347    /// Computes the set of all unique symbols and members that are referenced *by* any symbol/member
348    /// tracked in the body or signature reference maps.
349    ///
350    /// # Returns
351    ///
352    /// A `HashSet` containing `&(SymbolName, MemberName)` tuples of all referenced items.
353    #[inline]
354    pub fn get_referenced_symbols_and_members(&self) -> HashSet<&SymbolIdentifier> {
355        let mut referenced_items = HashSet::default();
356        for refs in self.symbol_references_to_symbols.values() {
357            referenced_items.extend(refs.iter());
358        }
359        for refs in self.symbol_references_to_symbols_in_signature.values() {
360            referenced_items.extend(refs.iter());
361        }
362
363        referenced_items
364    }
365
366    /// Computes the inverse of the body and signature reference maps.
367    ///
368    /// # Returns
369    ///
370    /// A `HashMap` where the key is the referenced symbol/member `(Symbol, Member)` and the value
371    /// is a `HashSet` of referencing symbols/members `(RefSymbol, RefMember)`.
372    #[inline]
373    pub fn get_back_references(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
374        let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
375
376        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
377            for referenced_item in referenced_items {
378                back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
379            }
380        }
381        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
382            for referenced_item in referenced_items {
383                back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
384            }
385        }
386        back_refs
387    }
388
389    /// Finds all symbols/members that reference a specific target symbol/member.
390    /// Checks both body and signature references.
391    ///
392    /// # Arguments
393    ///
394    /// * `target_symbol`: The `(SymbolName, MemberName)` tuple being referenced.
395    ///
396    /// # Returns
397    ///
398    /// A `HashSet` containing `&(RefSymbol, RefMember)` tuples of all items referencing the target.
399    #[inline]
400    pub fn get_references_to_symbol(&self, target_symbol: SymbolIdentifier) -> HashSet<&SymbolIdentifier> {
401        let mut referencing_items = HashSet::default();
402        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
403            if referenced_items.contains(&target_symbol) {
404                referencing_items.insert(referencing_item);
405            }
406        }
407        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
408            if referenced_items.contains(&target_symbol) {
409                referencing_items.insert(referencing_item);
410            }
411        }
412        referencing_items
413    }
414
415    /// Computes the count of references for each unique symbol/member referenced in bodies or signatures.
416    ///
417    /// # Returns
418    ///
419    /// A `HashMap` where the key is the referenced symbol/member `(Symbol, Member)` and the value
420    /// is the total count (`u32`) of references to it.
421    #[inline]
422    pub fn get_referenced_symbols_and_members_with_counts(&self) -> HashMap<SymbolIdentifier, u32> {
423        let mut counts = HashMap::default();
424        for referenced_items in self.symbol_references_to_symbols.values() {
425            for referenced_item in referenced_items {
426                *counts.entry(*referenced_item).or_insert(0) += 1;
427            }
428        }
429        for referenced_items in self.symbol_references_to_symbols_in_signature.values() {
430            for referenced_item in referenced_items {
431                *counts.entry(*referenced_item).or_insert(0) += 1;
432            }
433        }
434        counts
435    }
436
437    /// Computes the inverse of the overridden member reference map.
438    ///
439    /// # Returns
440    ///
441    /// A `HashMap` where the key is the overridden member `(ParentSymbol, Member)` and the value
442    /// is a `HashSet` of referencing symbols/members `(RefSymbol, RefMember)` that call it via `parent::`.
443    #[inline]
444    pub fn get_referenced_overridden_class_members(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
445        let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
446
447        for (referencing_item, referenced_items) in &self.symbol_references_to_overridden_members {
448            for referenced_item in referenced_items {
449                back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
450            }
451        }
452        back_refs
453    }
454
455    /// Calculates sets of invalid symbols and members based on detected code changes (`CodebaseDiff`).
456    /// Propagates invalidation through the dependency graph stored in signature references.
457    /// Limits propagation expense to avoid excessive computation on large changes.
458    ///
459    /// # Arguments
460    ///
461    /// * `codebase_diff`: Information about added, deleted, or modified symbols/signatures.
462    ///
463    /// # Returns
464    ///
465    /// `Some((invalid_signatures, partially_invalid))` on success, where `invalid_signatures` contains
466    /// all symbol/member pairs whose signature is invalid (including propagated ones), and `partially_invalid`
467    /// contains symbols with at least one invalid member.
468    /// Returns `None` if the propagation exceeds an expense limit (currently 5000 steps).
469    #[inline]
470    pub fn get_invalid_symbols(
471        &self,
472        codebase_diff: &CodebaseDiff,
473    ) -> Option<(HashSet<SymbolIdentifier>, HashSet<StringIdentifier>)> {
474        let mut invalid_signatures = HashSet::default();
475        let mut partially_invalid_symbols = HashSet::default();
476
477        for sig_ref_key in self.symbol_references_to_symbols_in_signature.keys() {
478            // Represent the containing symbol (ignore member part for diff check)
479            let containing_symbol = (sig_ref_key.0, StringIdentifier::empty());
480
481            if codebase_diff.contains_add_or_delete_entry(&containing_symbol) {
482                invalid_signatures.insert(*sig_ref_key);
483                partially_invalid_symbols.insert(sig_ref_key.0);
484            }
485        }
486
487        // Start with symbols directly added/deleted in the diff.
488        let mut symbols_to_process = codebase_diff.get_add_or_delete().iter().copied().collect::<Vec<_>>();
489        let mut processed_symbols = HashSet::default();
490        let mut expense_counter = 0;
491
492        const EXPENSE_LIMIT: usize = 5000;
493        while let Some(invalidated_item) = symbols_to_process.pop() {
494            if processed_symbols.contains(&invalidated_item) {
495                continue;
496            }
497
498            expense_counter += 1;
499            if expense_counter > EXPENSE_LIMIT {
500                return None;
501            }
502
503            // Mark this item as invalid (signature) and processed
504            invalid_signatures.insert(invalidated_item);
505            processed_symbols.insert(invalidated_item);
506            if !invalidated_item.1.is_empty() {
507                // If it's a member...
508                partially_invalid_symbols.insert(invalidated_item.0);
509            }
510
511            // Find all items that reference this now-invalid item *in their signature*
512            for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
513                if referenced_items.contains(&invalidated_item) {
514                    // If referencing item not already processed, add it to the processing queue
515                    if !processed_symbols.contains(referencing_item) {
516                        symbols_to_process.push(*referencing_item);
517                    }
518
519                    // Mark the referencing item itself as invalid (signature)
520                    invalid_signatures.insert(*referencing_item);
521                    if !referencing_item.1.is_empty() {
522                        // If it's a member...
523                        partially_invalid_symbols.insert(referencing_item.0);
524                    }
525                }
526            }
527
528            // Simple check against limit within loop might be slightly faster
529            if expense_counter > EXPENSE_LIMIT {
530                return None;
531            }
532        }
533
534        // An item's body is invalid if it references (anywhere, body or sig) an item with an invalid signature,
535        // OR if its own signature was kept but its body might have changed (keep_signature diff).
536        let mut invalid_bodies = HashSet::default();
537
538        // Check references from body map
539        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
540            // Does this item reference *any* item with an invalid signature?
541            if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
542                invalid_bodies.insert(*referencing_item);
543                if !referencing_item.1.is_empty() {
544                    // If it's a member...
545                    partially_invalid_symbols.insert(referencing_item.0);
546                }
547            }
548        }
549
550        // Check references from signature map (redundant with propagation? Maybe not entirely)
551        // If item A's signature references item B (invalid signature), A's signature becomes invalid (handled above).
552        // But A's *body* might also be considered invalid due to the signature dependency.
553        for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
554            if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
555                invalid_bodies.insert(*referencing_item);
556                if !referencing_item.1.is_empty() {
557                    partially_invalid_symbols.insert(referencing_item.0);
558                }
559            }
560        }
561
562        // Add items whose signatures were kept, but bodies might have changed
563        for keep_sig_item in codebase_diff.get_keep_signature() {
564            invalid_bodies.insert(*keep_sig_item);
565            if !keep_sig_item.1.is_empty() {
566                partially_invalid_symbols.insert(keep_sig_item.0);
567            }
568        }
569
570        // Combine results: invalid_signatures includes items whose definition changed or depend on changed signatures.
571        // partially_invalid_symbols includes symbols containing members from either invalid_signatures or invalid_bodies.
572        Some((invalid_signatures, partially_invalid_symbols))
573    }
574
575    /// Removes all references *originating from* symbols/members that are marked as invalid.
576    ///
577    /// # Arguments
578    ///
579    /// * `invalid_symbols_and_members`: A set containing `(SymbolName, MemberName)` tuples for invalid items.
580    #[inline]
581    pub fn remove_references_from_invalid_symbols(&mut self, invalid_symbols_and_members: &HashSet<SymbolIdentifier>) {
582        // Retain only entries where the key (referencing item) is NOT in the invalid set.
583        self.symbol_references_to_symbols
584            .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
585        self.symbol_references_to_symbols_in_signature
586            .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
587        self.symbol_references_to_overridden_members
588            .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
589    }
590
591    /// Returns a reference to the map tracking references within symbol/member bodies.
592    #[inline]
593    pub fn get_symbol_references_to_symbols(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
594        &self.symbol_references_to_symbols
595    }
596
597    /// Returns a reference to the map tracking references within symbol/member signatures.
598    #[inline]
599    pub fn get_symbol_references_to_symbols_in_signature(
600        &self,
601    ) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
602        &self.symbol_references_to_symbols_in_signature
603    }
604
605    /// Returns a reference to the map tracking references to overridden members.
606    #[inline]
607    pub fn get_symbol_references_to_overridden_members(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
608        &self.symbol_references_to_overridden_members
609    }
610
611    /// Returns a reference to the map tracking references to function-like return values.
612    #[inline]
613    pub fn get_functionlike_references_to_functionlike_returns(
614        &self,
615    ) -> &HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>> {
616        &self.functionlike_references_to_functionlike_returns
617    }
618}