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