1use foldhash::HashMap;
2use foldhash::HashSet;
3use mago_word::ascii_lowercase_word;
4use mago_word::empty_word;
5
6use mago_word::Word;
7use mago_word::WordSet;
8
9use crate::context::ScopeContext;
10use crate::diff::CodebaseDiff;
11use crate::identifier::function_like::FunctionLikeIdentifier;
12use crate::identifier::method::MethodIdentifier;
13use crate::symbol::SymbolIdentifier;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum ReferenceSource {
20 Symbol(bool, Word),
24 ClassLikeMember(bool, Word, Word),
29}
30
31#[derive(Debug, Clone, PartialEq, Eq, Default)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35#[allow(clippy::struct_field_names)]
36pub struct InvalidSymbols {
37 invalid_symbol_and_member_signatures: HashSet<SymbolIdentifier>,
40 invalid_symbol_and_member_bodies: HashSet<SymbolIdentifier>,
43 partially_invalid_symbols: WordSet,
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Default)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[allow(clippy::struct_field_names)]
56pub struct SymbolReferences {
57 symbol_references_to_symbols: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
61
62 symbol_references_to_symbols_in_signature: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
65
66 symbol_references_to_overridden_members: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
69
70 functionlike_references_to_functionlike_returns: HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>>,
73
74 file_references_to_symbols: HashMap<Word, HashSet<SymbolIdentifier>>,
78
79 file_references_to_symbols_in_signature: HashMap<Word, HashSet<SymbolIdentifier>>,
82
83 property_write_references: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
87
88 property_read_references: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
92}
93
94impl SymbolReferences {
95 #[inline]
97 #[must_use]
98 pub fn new() -> Self {
99 Self {
100 symbol_references_to_symbols: HashMap::default(),
101 symbol_references_to_symbols_in_signature: HashMap::default(),
102 symbol_references_to_overridden_members: HashMap::default(),
103 functionlike_references_to_functionlike_returns: HashMap::default(),
104 file_references_to_symbols: HashMap::default(),
105 file_references_to_symbols_in_signature: HashMap::default(),
106 property_write_references: HashMap::default(),
107 property_read_references: HashMap::default(),
108 }
109 }
110
111 #[inline]
113 pub fn count_body_references(&self) -> usize {
114 self.symbol_references_to_symbols.values().map(std::collections::HashSet::len).sum()
115 }
116
117 #[inline]
119 pub fn count_signature_references(&self) -> usize {
120 self.symbol_references_to_symbols_in_signature.values().map(std::collections::HashSet::len).sum()
121 }
122
123 #[inline]
127 #[must_use]
128 pub fn total_map_entries(&self) -> usize {
129 self.symbol_references_to_symbols.len()
130 + self.symbol_references_to_symbols_in_signature.len()
131 + self.symbol_references_to_overridden_members.len()
132 + self.functionlike_references_to_functionlike_returns.len()
133 + self.file_references_to_symbols.len()
134 + self.file_references_to_symbols_in_signature.len()
135 + self.property_write_references.len()
136 + self.property_read_references.len()
137 }
138
139 #[inline]
148 #[must_use]
149 pub fn count_referencing_symbols(&self, symbol: &SymbolIdentifier, in_signature: bool) -> usize {
150 let map = if in_signature {
151 &self.symbol_references_to_symbols_in_signature
152 } else {
153 &self.symbol_references_to_symbols
154 };
155
156 map.values().filter(|referenced_set| referenced_set.contains(symbol)).count()
157 }
158
159 #[inline]
169 #[must_use]
170 pub fn count_property_reads(&self, property: &SymbolIdentifier) -> usize {
171 self.property_read_references.values().filter(|read_set| read_set.contains(property)).count()
172 }
173
174 #[inline]
184 #[must_use]
185 pub fn count_property_writes(&self, property: &SymbolIdentifier) -> usize {
186 self.property_write_references.values().filter(|write_set| write_set.contains(property)).count()
187 }
188
189 #[inline]
199 pub fn add_symbol_reference_to_class_member(
200 &mut self,
201 referencing_symbol: Word,
202 class_member: SymbolIdentifier,
203 in_signature: bool,
204 ) {
205 self.add_symbol_reference_to_symbol(referencing_symbol, class_member.0, false);
207
208 let key = (referencing_symbol, empty_word());
210 if in_signature {
211 self.symbol_references_to_symbols_in_signature.entry(key).or_default().insert(class_member);
212 } else {
213 self.symbol_references_to_symbols.entry(key).or_default().insert(class_member);
214 }
215 }
216
217 #[inline]
226 pub fn add_symbol_reference_to_symbol(&mut self, referencing_symbol: Word, symbol: Word, in_signature: bool) {
227 if referencing_symbol == symbol {
228 return;
229 }
230
231 let referencing_key = (referencing_symbol, empty_word());
233 let referenced_key = (symbol, empty_word());
234
235 if in_signature {
236 self.symbol_references_to_symbols_in_signature.entry(referencing_key).or_default().insert(referenced_key);
237 } else {
238 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_key)
240 && sig_refs.contains(&referenced_key)
241 {
242 return;
243 }
244 self.symbol_references_to_symbols.entry(referencing_key).or_default().insert(referenced_key);
245 }
246 }
247
248 #[inline]
258 pub fn add_class_member_reference_to_class_member(
259 &mut self,
260 referencing_class_member: SymbolIdentifier,
261 class_member: SymbolIdentifier,
262 in_signature: bool,
263 ) {
264 if referencing_class_member == class_member {
265 return;
266 }
267
268 self.add_symbol_reference_to_symbol(referencing_class_member.0, class_member.0, false);
270 self.add_class_member_reference_to_symbol(referencing_class_member, class_member.0, false);
271
272 if in_signature {
274 self.symbol_references_to_symbols_in_signature
275 .entry(referencing_class_member)
276 .or_default()
277 .insert(class_member);
278 } else {
279 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(class_member);
282 }
283 }
284
285 #[inline]
295 pub fn add_class_member_reference_to_symbol(
296 &mut self,
297 referencing_class_member: SymbolIdentifier,
298 symbol: Word,
299 in_signature: bool,
300 ) {
301 if referencing_class_member.0 == symbol {
302 return;
303 }
304
305 self.add_symbol_reference_to_symbol(referencing_class_member.0, symbol, false);
307
308 let referenced_key = (symbol, empty_word());
310
311 if in_signature {
312 self.symbol_references_to_symbols_in_signature
313 .entry(referencing_class_member)
314 .or_default()
315 .insert(referenced_key);
316 } else {
317 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_class_member)
319 && sig_refs.contains(&referenced_key)
320 {
321 return;
322 }
323 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(referenced_key);
324 }
325 }
326
327 #[inline]
330 pub fn add_file_reference_to_class_member(
331 &mut self,
332 file_hash: Word,
333 class_member: SymbolIdentifier,
334 in_signature: bool,
335 ) {
336 if in_signature {
337 self.file_references_to_symbols_in_signature.entry(file_hash).or_default().insert(class_member);
338 } else {
339 if let Some(sig_refs) = self.file_references_to_symbols_in_signature.get(&file_hash)
341 && sig_refs.contains(&class_member)
342 {
343 return;
344 }
345 self.file_references_to_symbols.entry(file_hash).or_default().insert(class_member);
346 }
347 }
348
349 #[inline]
352 pub fn add_reference_to_class_member(
353 &mut self,
354 scope: &ScopeContext<'_>,
355 class_member: SymbolIdentifier,
356 in_signature: bool,
357 ) {
358 self.add_reference_to_class_member_with_file(scope, class_member, in_signature, None);
359 }
360
361 #[inline]
371 pub fn add_reference_to_class_member_with_file(
372 &mut self,
373 scope: &ScopeContext<'_>,
374 class_member: SymbolIdentifier,
375 in_signature: bool,
376 file_hash: Option<Word>,
377 ) {
378 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
379 match referencing_functionlike {
380 FunctionLikeIdentifier::Function(function_name) => {
381 self.add_symbol_reference_to_class_member(function_name, class_member, in_signature);
382 }
383 FunctionLikeIdentifier::Method(class_name, function_name) => self
384 .add_class_member_reference_to_class_member(
385 (class_name, function_name),
386 class_member,
387 in_signature,
388 ),
389 _ => {
390 if let Some(hash) = file_hash {
393 self.add_file_reference_to_class_member(hash, class_member, in_signature);
394 } else {
395 self.add_symbol_reference_to_class_member(empty_word(), class_member, in_signature);
396 }
397 }
398 }
399 } else if let Some(calling_class) = scope.get_class_like_name() {
400 self.add_symbol_reference_to_class_member(calling_class, class_member, in_signature);
402 } else {
403 if let Some(hash) = file_hash {
406 self.add_file_reference_to_class_member(hash, class_member, in_signature);
407 } else {
408 self.add_symbol_reference_to_class_member(empty_word(), class_member, in_signature);
409 }
410 }
411 }
412
413 #[inline]
414 pub fn add_reference_for_method_call(&mut self, scope: &ScopeContext<'_>, method: &MethodIdentifier) {
415 self.add_reference_to_class_member(
416 scope,
417 (ascii_lowercase_word(method.get_class_name().as_bytes()), method.get_method_name()),
418 false,
419 );
420 }
421
422 #[inline]
424 pub fn add_reference_for_property_read(&mut self, scope: &ScopeContext<'_>, class_name: Word, property_name: Word) {
425 let normalized_class_name = ascii_lowercase_word(class_name.as_bytes());
426 let class_member = (normalized_class_name, property_name);
427
428 self.add_reference_to_class_member(scope, class_member, false);
429
430 let referencing_key = self.get_referencing_key_from_scope(scope);
431 self.property_read_references.entry(referencing_key).or_default().insert(class_member);
432 }
433
434 #[inline]
437 pub fn add_reference_for_property_write(
438 &mut self,
439 scope: &ScopeContext<'_>,
440 class_name: Word,
441 property_name: Word,
442 ) {
443 let normalized_class_name = ascii_lowercase_word(class_name.as_bytes());
444 let class_member = (normalized_class_name, property_name);
445
446 self.add_reference_to_class_member(scope, class_member, false);
447
448 let referencing_key = self.get_referencing_key_from_scope(scope);
449 self.property_write_references.entry(referencing_key).or_default().insert(class_member);
450 }
451
452 #[inline]
454 fn get_referencing_key_from_scope(&self, scope: &ScopeContext<'_>) -> SymbolIdentifier {
455 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
456 match referencing_functionlike {
457 FunctionLikeIdentifier::Function(function_name) => (function_name, empty_word()),
458 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
459 _ => (empty_word(), empty_word()),
460 }
461 } else if let Some(calling_class) = scope.get_class_like_name() {
462 (ascii_lowercase_word(calling_class.as_bytes()), empty_word())
463 } else {
464 (empty_word(), empty_word())
465 }
466 }
467
468 #[inline]
471 pub fn add_reference_to_overridden_class_member(&mut self, scope: &ScopeContext, class_member: SymbolIdentifier) {
472 let referencing_key = if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
473 match referencing_functionlike {
474 FunctionLikeIdentifier::Function(function_name) => (empty_word(), function_name),
475 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
476 _ => {
477 return;
479 }
480 }
481 } else if let Some(calling_class) = scope.get_class_like_name() {
482 (ascii_lowercase_word(calling_class.as_bytes()), empty_word())
483 } else {
484 return; };
486
487 self.symbol_references_to_overridden_members.entry(referencing_key).or_default().insert(class_member);
488 }
489
490 #[inline]
493 pub fn add_reference_to_symbol(&mut self, scope: &ScopeContext, symbol: Word, in_signature: bool) {
494 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
495 match referencing_functionlike {
496 FunctionLikeIdentifier::Function(function_name) => {
497 self.add_symbol_reference_to_symbol(function_name, symbol, in_signature);
498 }
499 FunctionLikeIdentifier::Method(class_name, function_name) => {
500 self.add_class_member_reference_to_symbol((class_name, function_name), symbol, in_signature);
501 }
502 _ => {
503 }
505 }
506 } else if let Some(calling_class) = scope.get_class_like_name() {
507 self.add_symbol_reference_to_symbol(ascii_lowercase_word(calling_class.as_bytes()), symbol, in_signature);
508 }
509 }
510
511 #[inline]
513 pub fn add_reference_to_functionlike_return(
514 &mut self,
515 referencing_functionlike: FunctionLikeIdentifier,
516 referenced_functionlike: FunctionLikeIdentifier,
517 ) {
518 if referencing_functionlike == referenced_functionlike {
519 return;
520 }
521
522 self.functionlike_references_to_functionlike_returns
523 .entry(referencing_functionlike)
524 .or_default()
525 .insert(referenced_functionlike);
526 }
527
528 #[inline]
531 pub fn extend(&mut self, other: Self) {
532 for (k, v) in other.symbol_references_to_symbols {
533 self.symbol_references_to_symbols.entry(k).or_default().extend(v);
534 }
535 for (k, v) in other.symbol_references_to_symbols_in_signature {
536 self.symbol_references_to_symbols_in_signature.entry(k).or_default().extend(v);
537 }
538 for (k, v) in other.symbol_references_to_overridden_members {
539 self.symbol_references_to_overridden_members.entry(k).or_default().extend(v);
540 }
541 for (k, v) in other.functionlike_references_to_functionlike_returns {
542 self.functionlike_references_to_functionlike_returns.entry(k).or_default().extend(v);
543 }
544
545 for (k, v) in other.file_references_to_symbols {
546 self.file_references_to_symbols.entry(k).or_default().extend(v);
547 }
548
549 for (k, v) in other.file_references_to_symbols_in_signature {
550 self.file_references_to_symbols_in_signature.entry(k).or_default().extend(v);
551 }
552
553 for (k, v) in other.property_write_references {
554 self.property_write_references.entry(k).or_default().extend(v);
555 }
556
557 for (k, v) in other.property_read_references {
558 self.property_read_references.entry(k).or_default().extend(v);
559 }
560 }
561
562 #[inline]
569 #[must_use]
570 pub fn get_referenced_symbols_and_members(&self) -> HashSet<&SymbolIdentifier> {
571 let mut referenced_items = HashSet::default();
572 for refs in self.symbol_references_to_symbols.values() {
573 referenced_items.extend(refs.iter());
574 }
575 for refs in self.symbol_references_to_symbols_in_signature.values() {
576 referenced_items.extend(refs.iter());
577 }
578
579 referenced_items
580 }
581
582 #[inline]
589 #[must_use]
590 pub fn get_back_references(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
591 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
592
593 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
594 for referenced_item in referenced_items {
595 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
596 }
597 }
598 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
599 for referenced_item in referenced_items {
600 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
601 }
602 }
603 back_refs
604 }
605
606 #[inline]
617 #[must_use]
618 pub fn get_references_to_symbol(&self, target_symbol: SymbolIdentifier) -> HashSet<&SymbolIdentifier> {
619 let mut referencing_items = HashSet::default();
620 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
621 if referenced_items.contains(&target_symbol) {
622 referencing_items.insert(referencing_item);
623 }
624 }
625 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
626 if referenced_items.contains(&target_symbol) {
627 referencing_items.insert(referencing_item);
628 }
629 }
630 referencing_items
631 }
632
633 #[inline]
640 #[must_use]
641 pub fn get_referenced_symbols_and_members_with_counts(&self) -> HashMap<SymbolIdentifier, u32> {
642 let mut counts = HashMap::default();
643 for referenced_items in self.symbol_references_to_symbols.values() {
644 for referenced_item in referenced_items {
645 *counts.entry(*referenced_item).or_insert(0) += 1;
646 }
647 }
648 for referenced_items in self.symbol_references_to_symbols_in_signature.values() {
649 for referenced_item in referenced_items {
650 *counts.entry(*referenced_item).or_insert(0) += 1;
651 }
652 }
653 counts
654 }
655
656 #[inline]
663 #[must_use]
664 pub fn get_referenced_overridden_class_members(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
665 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
666
667 for (referencing_item, referenced_items) in &self.symbol_references_to_overridden_members {
668 for referenced_item in referenced_items {
669 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
670 }
671 }
672 back_refs
673 }
674
675 #[inline]
690 #[must_use]
691 pub fn get_invalid_symbols(&self, codebase_diff: &CodebaseDiff) -> Option<(HashSet<SymbolIdentifier>, WordSet)> {
692 let mut invalid_signatures = HashSet::default();
693 let mut partially_invalid_symbols = WordSet::default();
694
695 let mut sig_reverse_index: HashMap<SymbolIdentifier, Vec<SymbolIdentifier>> = HashMap::default();
696 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
697 let containing_symbol = (referencing_item.0, empty_word());
698 if codebase_diff.contains_changed_entry(&containing_symbol) {
699 invalid_signatures.insert(*referencing_item);
700 partially_invalid_symbols.insert(referencing_item.0);
701 }
702
703 for referenced in referenced_items {
704 sig_reverse_index.entry(*referenced).or_default().push(*referencing_item);
705 }
706 }
707
708 let mut symbols_to_process = codebase_diff.get_changed().iter().copied().collect::<Vec<_>>();
710 let mut processed_symbols = HashSet::default();
711 let mut expense_counter = 0;
712
713 const EXPENSE_LIMIT: usize = 5000;
714 while let Some(invalidated_item) = symbols_to_process.pop() {
715 if processed_symbols.contains(&invalidated_item) {
716 continue;
717 }
718
719 expense_counter += 1;
720 if expense_counter > EXPENSE_LIMIT {
721 return None;
722 }
723
724 invalid_signatures.insert(invalidated_item);
726 processed_symbols.insert(invalidated_item);
727 if !invalidated_item.1.is_empty() {
728 partially_invalid_symbols.insert(invalidated_item.0);
730 let containing_symbol = (invalidated_item.0, empty_word());
731 if !processed_symbols.contains(&containing_symbol) {
732 symbols_to_process.push(containing_symbol);
733 }
734 }
735
736 if let Some(referencing_items) = sig_reverse_index.get(&invalidated_item) {
738 for referencing_item in referencing_items {
739 if !processed_symbols.contains(referencing_item) {
740 symbols_to_process.push(*referencing_item);
741 }
742
743 invalid_signatures.insert(*referencing_item);
744 if !referencing_item.1.is_empty() {
745 partially_invalid_symbols.insert(referencing_item.0);
746 }
747 }
748 }
749 }
750
751 let mut invalid_bodies = HashSet::default();
754
755 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
756 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
757 invalid_bodies.insert(*referencing_item);
758 if !referencing_item.1.is_empty() {
759 partially_invalid_symbols.insert(referencing_item.0);
760 }
761 }
762 }
763
764 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
765 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
766 invalid_bodies.insert(*referencing_item);
767 if !referencing_item.1.is_empty() {
768 partially_invalid_symbols.insert(referencing_item.0);
769 }
770 }
771 }
772
773 let mut all_invalid_symbols = invalid_signatures;
774 all_invalid_symbols.extend(invalid_bodies);
775 Some((all_invalid_symbols, partially_invalid_symbols))
776 }
777
778 #[inline]
793 pub fn restore_references_for_safe_symbols(
794 &mut self,
795 previous: &SymbolReferences,
796 safe_symbols: &WordSet,
797 safe_symbol_members: &HashSet<SymbolIdentifier>,
798 ) {
799 let is_safe = |key: &SymbolIdentifier| -> bool {
800 if key.1.is_empty() { safe_symbols.contains(&key.0) } else { safe_symbol_members.contains(key) }
801 };
802
803 for (key, refs) in &previous.symbol_references_to_symbols {
805 if is_safe(key) && !self.symbol_references_to_symbols.contains_key(key) {
806 self.symbol_references_to_symbols.insert(*key, refs.clone());
807 }
808 }
809
810 for (key, refs) in &previous.symbol_references_to_overridden_members {
812 if is_safe(key) && !self.symbol_references_to_overridden_members.contains_key(key) {
813 self.symbol_references_to_overridden_members.insert(*key, refs.clone());
814 }
815 }
816
817 for (key, refs) in &previous.functionlike_references_to_functionlike_returns {
819 let sym_key = match key {
820 FunctionLikeIdentifier::Function(name) => (*name, mago_word::empty_word()),
821 FunctionLikeIdentifier::Method(class, method) => (*class, *method),
822 _ => continue,
823 };
824
825 if is_safe(&sym_key) && !self.functionlike_references_to_functionlike_returns.contains_key(key) {
826 self.functionlike_references_to_functionlike_returns.insert(*key, refs.clone());
827 }
828 }
829
830 for (key, refs) in &previous.property_write_references {
832 if is_safe(key) && !self.property_write_references.contains_key(key) {
833 self.property_write_references.insert(*key, refs.clone());
834 }
835 }
836
837 for (key, refs) in &previous.property_read_references {
839 if is_safe(key) && !self.property_read_references.contains_key(key) {
840 self.property_read_references.insert(*key, refs.clone());
841 }
842 }
843 }
844
845 #[inline]
856 pub fn remove_body_references_for_symbols(
857 &mut self,
858 symbols_and_members: &HashSet<SymbolIdentifier>,
859 file_names: &[Word],
860 ) {
861 for key in symbols_and_members {
863 self.symbol_references_to_symbols.remove(key);
864 self.symbol_references_to_overridden_members.remove(key);
865 self.property_write_references.remove(key);
866 self.property_read_references.remove(key);
867 }
868
869 self.functionlike_references_to_functionlike_returns.retain(|key, _| {
871 let sym_key = match key {
872 FunctionLikeIdentifier::Function(name) => (*name, mago_word::empty_word()),
873 FunctionLikeIdentifier::Method(class, method) => (*class, *method),
874 _ => return true,
875 };
876
877 !symbols_and_members.contains(&sym_key)
878 });
879
880 for name in file_names {
882 self.file_references_to_symbols.remove(name);
883 }
884 }
885
886 #[inline]
892 pub fn remove_references_from_invalid_symbols(&mut self, invalid_symbols_and_members: &HashSet<SymbolIdentifier>) {
893 self.symbol_references_to_symbols
895 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
896 self.symbol_references_to_symbols_in_signature
897 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
898 self.symbol_references_to_overridden_members
899 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
900 self.property_write_references
901 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
902 self.property_read_references
903 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
904 }
905
906 #[inline]
915 pub fn retain_safe_symbol_references(
916 &mut self,
917 safe_symbols: &WordSet,
918 safe_symbol_members: &HashSet<SymbolIdentifier>,
919 ) {
920 let is_safe = |key: &SymbolIdentifier| -> bool {
921 if key.1.is_empty() { safe_symbols.contains(&key.0) } else { safe_symbol_members.contains(key) }
922 };
923
924 self.symbol_references_to_symbols.retain(|k, _| is_safe(k));
925 self.symbol_references_to_symbols_in_signature.retain(|k, _| is_safe(k));
926 self.symbol_references_to_overridden_members.retain(|k, _| is_safe(k));
927 self.property_write_references.retain(|k, _| is_safe(k));
928 self.property_read_references.retain(|k, _| is_safe(k));
929
930 self.functionlike_references_to_functionlike_returns.retain(|key, _| {
931 let sym_key = match key {
932 FunctionLikeIdentifier::Function(name) => (*name, mago_word::empty_word()),
933 FunctionLikeIdentifier::Method(class, method) => (*class, *method),
934 _ => return true, };
936
937 is_safe(&sym_key)
938 });
939 }
940
941 pub fn remove_dirty_symbol_references(&mut self, dirty_symbols: &HashSet<SymbolIdentifier>) {
947 for key in dirty_symbols {
948 self.symbol_references_to_symbols.remove(key);
949 self.symbol_references_to_symbols_in_signature.remove(key);
950 self.symbol_references_to_overridden_members.remove(key);
951 self.property_write_references.remove(key);
952 self.property_read_references.remove(key);
953
954 let fl_key = if key.1.is_empty() {
955 FunctionLikeIdentifier::Function(key.0)
956 } else {
957 FunctionLikeIdentifier::Method(key.0, key.1)
958 };
959
960 self.functionlike_references_to_functionlike_returns.remove(&fl_key);
961 }
962 }
963
964 #[inline]
966 #[must_use]
967 pub fn get_symbol_references_to_symbols(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
968 &self.symbol_references_to_symbols
969 }
970
971 #[inline]
973 #[must_use]
974 pub fn get_symbol_references_to_symbols_in_signature(
975 &self,
976 ) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
977 &self.symbol_references_to_symbols_in_signature
978 }
979
980 #[inline]
982 #[must_use]
983 pub fn get_symbol_references_to_overridden_members(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
984 &self.symbol_references_to_overridden_members
985 }
986
987 #[inline]
989 #[must_use]
990 pub fn get_functionlike_references_to_functionlike_returns(
991 &self,
992 ) -> &HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>> {
993 &self.functionlike_references_to_functionlike_returns
994 }
995
996 #[inline]
998 #[must_use]
999 pub fn get_file_references_to_symbols(&self) -> &HashMap<Word, HashSet<SymbolIdentifier>> {
1000 &self.file_references_to_symbols
1001 }
1002
1003 #[inline]
1005 #[must_use]
1006 pub fn get_file_references_to_symbols_in_signature(&self) -> &HashMap<Word, HashSet<SymbolIdentifier>> {
1007 &self.file_references_to_symbols_in_signature
1008 }
1009}
1010
1011#[cfg(test)]
1012#[allow(clippy::unwrap_used, clippy::expect_used)]
1013mod tests {
1014 use super::*;
1015 use mago_word::empty_word;
1016 use mago_word::word;
1017
1018 fn make_refs_with_body(entries: Vec<(SymbolIdentifier, Vec<SymbolIdentifier>)>) -> SymbolReferences {
1019 let mut refs = SymbolReferences::new();
1020 for (key, values) in entries {
1021 let set: HashSet<SymbolIdentifier> = values.into_iter().collect();
1022 refs.symbol_references_to_symbols.insert(key, set);
1023 }
1024 refs
1025 }
1026
1027 #[test]
1028 fn test_restore_references_for_safe_symbols_restores_missing_body_refs() {
1029 let class_a = word("class_a");
1030 let class_b = word("class_b");
1031 let method_foo = word("foo");
1032 let method_bar = word("bar");
1033
1034 let previous = make_refs_with_body(vec![
1035 ((class_a, method_foo), vec![(class_b, empty_word())]),
1036 ((class_b, method_bar), vec![(class_a, empty_word())]),
1037 ]);
1038
1039 let mut current = make_refs_with_body(vec![((class_b, method_bar), vec![(class_a, empty_word())])]);
1040
1041 let safe_symbols = WordSet::default();
1042 let mut safe_members = HashSet::default();
1043 safe_members.insert((class_a, method_foo));
1044
1045 current.restore_references_for_safe_symbols(&previous, &safe_symbols, &safe_members);
1046
1047 assert!(current.symbol_references_to_symbols.contains_key(&(class_a, method_foo)));
1048 let restored = ¤t.symbol_references_to_symbols[&(class_a, method_foo)];
1049 assert!(restored.contains(&(class_b, empty_word())));
1050
1051 assert!(current.symbol_references_to_symbols.contains_key(&(class_b, method_bar)));
1052 }
1053
1054 #[test]
1055 fn test_restore_references_does_not_overwrite_existing() {
1056 let class_a = word("class_a");
1057 let class_b = word("class_b");
1058 let class_c = word("class_c");
1059 let method_foo = word("foo");
1060
1061 let previous = make_refs_with_body(vec![((class_a, method_foo), vec![(class_b, empty_word())])]);
1062
1063 let mut current = make_refs_with_body(vec![((class_a, method_foo), vec![(class_c, empty_word())])]);
1064
1065 let safe_symbols = WordSet::default();
1066 let mut safe_members = HashSet::default();
1067 safe_members.insert((class_a, method_foo));
1068
1069 current.restore_references_for_safe_symbols(&previous, &safe_symbols, &safe_members);
1070
1071 let refs = ¤t.symbol_references_to_symbols[&(class_a, method_foo)];
1072 assert!(refs.contains(&(class_c, empty_word())));
1073 assert!(!refs.contains(&(class_b, empty_word())));
1074 }
1075
1076 #[test]
1077 fn test_restore_references_for_safe_top_level_symbols() {
1078 let func_a = word("func_a");
1079 let class_b = word("class_b");
1080
1081 let previous = make_refs_with_body(vec![((func_a, empty_word()), vec![(class_b, empty_word())])]);
1082
1083 let mut current = SymbolReferences::new();
1084
1085 let mut safe_symbols = WordSet::default();
1086 safe_symbols.insert(func_a);
1087 let safe_members = HashSet::default();
1088
1089 current.restore_references_for_safe_symbols(&previous, &safe_symbols, &safe_members);
1090
1091 assert!(current.symbol_references_to_symbols.contains_key(&(func_a, empty_word())));
1092 let restored = ¤t.symbol_references_to_symbols[&(func_a, empty_word())];
1093 assert!(restored.contains(&(class_b, empty_word())));
1094 }
1095
1096 #[test]
1097 fn test_restore_skips_non_safe_symbols() {
1098 let func_a = word("func_a");
1099 let class_b = word("class_b");
1100 let previous = make_refs_with_body(vec![((func_a, empty_word()), vec![(class_b, empty_word())])]);
1101
1102 let mut current = SymbolReferences::new();
1103
1104 let safe_symbols = WordSet::default();
1105 let safe_members = HashSet::default();
1106
1107 current.restore_references_for_safe_symbols(&previous, &safe_symbols, &safe_members);
1108
1109 assert!(!current.symbol_references_to_symbols.contains_key(&(func_a, empty_word())));
1110 }
1111
1112 #[test]
1113 fn test_get_invalid_symbols_basic_cascade() {
1114 let class_a = word("class_a");
1115 let class_b = word("class_b");
1116 let method_foo = word("foo");
1117
1118 let mut refs = SymbolReferences::new();
1119 refs.symbol_references_to_symbols_in_signature.insert((class_b, method_foo), {
1120 let mut set = HashSet::default();
1121 set.insert((class_a, empty_word()));
1122 set
1123 });
1124
1125 let mut diff = crate::diff::CodebaseDiff::new();
1126 let mut changed = HashSet::default();
1127 changed.insert((class_a, empty_word()));
1128 diff = diff.with_changed(changed);
1129
1130 let result = refs.get_invalid_symbols(&diff);
1131 assert!(result.is_some());
1132 let (invalid, partially_invalid) = result.unwrap();
1133
1134 assert!(invalid.contains(&(class_a, empty_word())));
1135 assert!(invalid.contains(&(class_b, method_foo)));
1136 assert!(partially_invalid.contains(&class_b));
1137 }
1138}