1use ahash::HashMap;
2use ahash::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)]
35pub struct InvalidSymbols {
36 invalid_symbol_and_member_signatures: HashSet<SymbolIdentifier>,
39 invalid_symbol_and_member_bodies: HashSet<SymbolIdentifier>,
42 partially_invalid_symbols: AtomSet,
45}
46
47#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
53pub struct SymbolReferences {
54 symbol_references_to_symbols: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
58
59 symbol_references_to_symbols_in_signature: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
62
63 symbol_references_to_overridden_members: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
66
67 functionlike_references_to_functionlike_returns: HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>>,
70
71 file_references_to_symbols: HashMap<Atom, HashSet<SymbolIdentifier>>,
75
76 file_references_to_symbols_in_signature: HashMap<Atom, HashSet<SymbolIdentifier>>,
79
80 property_write_references: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
84
85 property_read_references: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>>,
89}
90
91impl SymbolReferences {
92 #[inline]
94 #[must_use]
95 pub fn new() -> Self {
96 Self {
97 symbol_references_to_symbols: HashMap::default(),
98 symbol_references_to_symbols_in_signature: HashMap::default(),
99 symbol_references_to_overridden_members: HashMap::default(),
100 functionlike_references_to_functionlike_returns: HashMap::default(),
101 file_references_to_symbols: HashMap::default(),
102 file_references_to_symbols_in_signature: HashMap::default(),
103 property_write_references: HashMap::default(),
104 property_read_references: HashMap::default(),
105 }
106 }
107
108 #[inline]
110 pub fn count_body_references(&self) -> usize {
111 self.symbol_references_to_symbols.values().map(std::collections::HashSet::len).sum()
112 }
113
114 #[inline]
116 pub fn count_signature_references(&self) -> usize {
117 self.symbol_references_to_symbols_in_signature.values().map(std::collections::HashSet::len).sum()
118 }
119
120 #[inline]
129 #[must_use]
130 pub fn count_referencing_symbols(&self, symbol: &SymbolIdentifier, in_signature: bool) -> usize {
131 let map = if in_signature {
132 &self.symbol_references_to_symbols_in_signature
133 } else {
134 &self.symbol_references_to_symbols
135 };
136
137 map.values().filter(|referenced_set| referenced_set.contains(symbol)).count()
138 }
139
140 #[inline]
150 #[must_use]
151 pub fn count_property_reads(&self, property: &SymbolIdentifier) -> usize {
152 self.property_read_references.values().filter(|read_set| read_set.contains(property)).count()
153 }
154
155 #[inline]
165 #[must_use]
166 pub fn count_property_writes(&self, property: &SymbolIdentifier) -> usize {
167 self.property_write_references.values().filter(|write_set| write_set.contains(property)).count()
168 }
169
170 #[inline]
180 pub fn add_symbol_reference_to_class_member(
181 &mut self,
182 referencing_symbol: Atom,
183 class_member: SymbolIdentifier,
184 in_signature: bool,
185 ) {
186 self.add_symbol_reference_to_symbol(referencing_symbol, class_member.0, false);
188
189 let key = (referencing_symbol, empty_atom());
191 if in_signature {
192 self.symbol_references_to_symbols_in_signature.entry(key).or_default().insert(class_member);
193 } else {
194 self.symbol_references_to_symbols.entry(key).or_default().insert(class_member);
195 }
196 }
197
198 #[inline]
207 pub fn add_symbol_reference_to_symbol(&mut self, referencing_symbol: Atom, symbol: Atom, in_signature: bool) {
208 if referencing_symbol == symbol {
209 return;
210 }
211
212 let referencing_key = (referencing_symbol, empty_atom());
214 let referenced_key = (symbol, empty_atom());
215
216 if in_signature {
217 self.symbol_references_to_symbols_in_signature.entry(referencing_key).or_default().insert(referenced_key);
218 } else {
219 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_key)
221 && sig_refs.contains(&referenced_key)
222 {
223 return;
224 }
225 self.symbol_references_to_symbols.entry(referencing_key).or_default().insert(referenced_key);
226 }
227 }
228
229 #[inline]
239 pub fn add_class_member_reference_to_class_member(
240 &mut self,
241 referencing_class_member: SymbolIdentifier,
242 class_member: SymbolIdentifier,
243 in_signature: bool,
244 ) {
245 if referencing_class_member == class_member {
246 return;
247 }
248
249 self.add_symbol_reference_to_symbol(referencing_class_member.0, class_member.0, false);
251 self.add_class_member_reference_to_symbol(referencing_class_member, class_member.0, false);
252
253 if in_signature {
255 self.symbol_references_to_symbols_in_signature
256 .entry(referencing_class_member)
257 .or_default()
258 .insert(class_member);
259 } else {
260 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(class_member);
263 }
264 }
265
266 #[inline]
276 pub fn add_class_member_reference_to_symbol(
277 &mut self,
278 referencing_class_member: SymbolIdentifier,
279 symbol: Atom,
280 in_signature: bool,
281 ) {
282 if referencing_class_member.0 == symbol {
283 return;
284 }
285
286 self.add_symbol_reference_to_symbol(referencing_class_member.0, symbol, false);
288
289 let referenced_key = (symbol, empty_atom());
291
292 if in_signature {
293 self.symbol_references_to_symbols_in_signature
294 .entry(referencing_class_member)
295 .or_default()
296 .insert(referenced_key);
297 } else {
298 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_class_member)
300 && sig_refs.contains(&referenced_key)
301 {
302 return;
303 }
304 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(referenced_key);
305 }
306 }
307
308 #[inline]
311 pub fn add_file_reference_to_class_member(
312 &mut self,
313 file_hash: Atom,
314 class_member: SymbolIdentifier,
315 in_signature: bool,
316 ) {
317 if in_signature {
318 self.file_references_to_symbols_in_signature.entry(file_hash).or_default().insert(class_member);
319 } else {
320 if let Some(sig_refs) = self.file_references_to_symbols_in_signature.get(&file_hash)
322 && sig_refs.contains(&class_member)
323 {
324 return;
325 }
326 self.file_references_to_symbols.entry(file_hash).or_default().insert(class_member);
327 }
328 }
329
330 #[inline]
333 pub fn add_reference_to_class_member(
334 &mut self,
335 scope: &ScopeContext<'_>,
336 class_member: SymbolIdentifier,
337 in_signature: bool,
338 ) {
339 self.add_reference_to_class_member_with_file(scope, class_member, in_signature, None);
340 }
341
342 #[inline]
352 pub fn add_reference_to_class_member_with_file(
353 &mut self,
354 scope: &ScopeContext<'_>,
355 class_member: SymbolIdentifier,
356 in_signature: bool,
357 file_hash: Option<Atom>,
358 ) {
359 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
360 match referencing_functionlike {
361 FunctionLikeIdentifier::Function(function_name) => {
362 self.add_symbol_reference_to_class_member(function_name, class_member, in_signature);
363 }
364 FunctionLikeIdentifier::Method(class_name, function_name) => self
365 .add_class_member_reference_to_class_member(
366 (class_name, function_name),
367 class_member,
368 in_signature,
369 ),
370 _ => {
371 if let Some(hash) = file_hash {
374 self.add_file_reference_to_class_member(hash, class_member, in_signature);
375 } else {
376 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature);
377 }
378 }
379 }
380 } else if let Some(calling_class) = scope.get_class_like_name() {
381 self.add_symbol_reference_to_class_member(calling_class, class_member, in_signature);
383 } else {
384 if let Some(hash) = file_hash {
387 self.add_file_reference_to_class_member(hash, class_member, in_signature);
388 } else {
389 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature);
390 }
391 }
392 }
393
394 #[inline]
395 pub fn add_reference_for_method_call(&mut self, scope: &ScopeContext<'_>, method: &MethodIdentifier) {
396 self.add_reference_to_class_member(
397 scope,
398 (ascii_lowercase_atom(method.get_class_name()), *method.get_method_name()),
399 false,
400 );
401 }
402
403 #[inline]
405 pub fn add_reference_for_property_read(&mut self, scope: &ScopeContext<'_>, class_name: Atom, property_name: Atom) {
406 let normalized_class_name = ascii_lowercase_atom(&class_name);
407 let class_member = (normalized_class_name, property_name);
408
409 self.add_reference_to_class_member(scope, class_member, false);
410
411 let referencing_key = self.get_referencing_key_from_scope(scope);
412 self.property_read_references.entry(referencing_key).or_default().insert(class_member);
413 }
414
415 #[inline]
418 pub fn add_reference_for_property_write(
419 &mut self,
420 scope: &ScopeContext<'_>,
421 class_name: Atom,
422 property_name: Atom,
423 ) {
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_write_references.entry(referencing_key).or_default().insert(class_member);
431 }
432
433 #[inline]
435 fn get_referencing_key_from_scope(&self, scope: &ScopeContext<'_>) -> SymbolIdentifier {
436 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
437 match referencing_functionlike {
438 FunctionLikeIdentifier::Function(function_name) => (function_name, empty_atom()),
439 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
440 _ => (empty_atom(), empty_atom()),
441 }
442 } else if let Some(calling_class) = scope.get_class_like_name() {
443 (ascii_lowercase_atom(&calling_class), empty_atom())
444 } else {
445 (empty_atom(), empty_atom())
446 }
447 }
448
449 #[inline]
452 pub fn add_reference_to_overridden_class_member(&mut self, scope: &ScopeContext, class_member: SymbolIdentifier) {
453 let referencing_key = if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
454 match referencing_functionlike {
455 FunctionLikeIdentifier::Function(function_name) => (empty_atom(), function_name),
456 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
457 _ => {
458 return;
460 }
461 }
462 } else if let Some(calling_class) = scope.get_class_like_name() {
463 (ascii_lowercase_atom(&calling_class), empty_atom())
464 } else {
465 return; };
467
468 self.symbol_references_to_overridden_members.entry(referencing_key).or_default().insert(class_member);
469 }
470
471 #[inline]
474 pub fn add_reference_to_symbol(&mut self, scope: &ScopeContext, symbol: Atom, in_signature: bool) {
475 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
476 match referencing_functionlike {
477 FunctionLikeIdentifier::Function(function_name) => {
478 self.add_symbol_reference_to_symbol(function_name, symbol, in_signature);
479 }
480 FunctionLikeIdentifier::Method(class_name, function_name) => {
481 self.add_class_member_reference_to_symbol((class_name, function_name), symbol, in_signature);
482 }
483 _ => {
484 }
486 }
487 } else if let Some(calling_class) = scope.get_class_like_name() {
488 self.add_symbol_reference_to_symbol(ascii_lowercase_atom(&calling_class), symbol, in_signature);
489 }
490 }
491
492 #[inline]
494 pub fn add_reference_to_functionlike_return(
495 &mut self,
496 referencing_functionlike: FunctionLikeIdentifier,
497 referenced_functionlike: FunctionLikeIdentifier,
498 ) {
499 if referencing_functionlike == referenced_functionlike {
500 return;
501 }
502
503 self.functionlike_references_to_functionlike_returns
504 .entry(referencing_functionlike)
505 .or_default()
506 .insert(referenced_functionlike);
507 }
508
509 #[inline]
512 pub fn extend(&mut self, other: Self) {
513 for (k, v) in other.symbol_references_to_symbols {
514 self.symbol_references_to_symbols.entry(k).or_default().extend(v);
515 }
516 for (k, v) in other.symbol_references_to_symbols_in_signature {
517 self.symbol_references_to_symbols_in_signature.entry(k).or_default().extend(v);
518 }
519 for (k, v) in other.symbol_references_to_overridden_members {
520 self.symbol_references_to_overridden_members.entry(k).or_default().extend(v);
521 }
522 for (k, v) in other.functionlike_references_to_functionlike_returns {
523 self.functionlike_references_to_functionlike_returns.entry(k).or_default().extend(v);
524 }
525
526 for (k, v) in other.file_references_to_symbols {
527 self.file_references_to_symbols.entry(k).or_default().extend(v);
528 }
529
530 for (k, v) in other.file_references_to_symbols_in_signature {
531 self.file_references_to_symbols_in_signature.entry(k).or_default().extend(v);
532 }
533
534 for (k, v) in other.property_write_references {
535 self.property_write_references.entry(k).or_default().extend(v);
536 }
537
538 for (k, v) in other.property_read_references {
539 self.property_read_references.entry(k).or_default().extend(v);
540 }
541 }
542
543 #[inline]
550 #[must_use]
551 pub fn get_referenced_symbols_and_members(&self) -> HashSet<&SymbolIdentifier> {
552 let mut referenced_items = HashSet::default();
553 for refs in self.symbol_references_to_symbols.values() {
554 referenced_items.extend(refs.iter());
555 }
556 for refs in self.symbol_references_to_symbols_in_signature.values() {
557 referenced_items.extend(refs.iter());
558 }
559
560 referenced_items
561 }
562
563 #[inline]
570 #[must_use]
571 pub fn get_back_references(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
572 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
573
574 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
575 for referenced_item in referenced_items {
576 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
577 }
578 }
579 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
580 for referenced_item in referenced_items {
581 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
582 }
583 }
584 back_refs
585 }
586
587 #[inline]
598 #[must_use]
599 pub fn get_references_to_symbol(&self, target_symbol: SymbolIdentifier) -> HashSet<&SymbolIdentifier> {
600 let mut referencing_items = HashSet::default();
601 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
602 if referenced_items.contains(&target_symbol) {
603 referencing_items.insert(referencing_item);
604 }
605 }
606 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
607 if referenced_items.contains(&target_symbol) {
608 referencing_items.insert(referencing_item);
609 }
610 }
611 referencing_items
612 }
613
614 #[inline]
621 #[must_use]
622 pub fn get_referenced_symbols_and_members_with_counts(&self) -> HashMap<SymbolIdentifier, u32> {
623 let mut counts = HashMap::default();
624 for referenced_items in self.symbol_references_to_symbols.values() {
625 for referenced_item in referenced_items {
626 *counts.entry(*referenced_item).or_insert(0) += 1;
627 }
628 }
629 for referenced_items in self.symbol_references_to_symbols_in_signature.values() {
630 for referenced_item in referenced_items {
631 *counts.entry(*referenced_item).or_insert(0) += 1;
632 }
633 }
634 counts
635 }
636
637 #[inline]
644 #[must_use]
645 pub fn get_referenced_overridden_class_members(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
646 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
647
648 for (referencing_item, referenced_items) in &self.symbol_references_to_overridden_members {
649 for referenced_item in referenced_items {
650 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
651 }
652 }
653 back_refs
654 }
655
656 #[inline]
671 #[must_use]
672 pub fn get_invalid_symbols(&self, codebase_diff: &CodebaseDiff) -> Option<(HashSet<SymbolIdentifier>, AtomSet)> {
673 let mut invalid_signatures = HashSet::default();
674 let mut partially_invalid_symbols = AtomSet::default();
675
676 for sig_ref_key in self.symbol_references_to_symbols_in_signature.keys() {
677 let containing_symbol = (sig_ref_key.0, empty_atom());
679
680 if codebase_diff.contains_changed_entry(&containing_symbol) {
681 invalid_signatures.insert(*sig_ref_key);
682 partially_invalid_symbols.insert(sig_ref_key.0);
683 }
684 }
685
686 let mut symbols_to_process = codebase_diff.get_changed().iter().copied().collect::<Vec<_>>();
688 let mut processed_symbols = HashSet::default();
689 let mut expense_counter = 0;
690
691 const EXPENSE_LIMIT: usize = 5000;
692 while let Some(invalidated_item) = symbols_to_process.pop() {
693 if processed_symbols.contains(&invalidated_item) {
694 continue;
695 }
696
697 expense_counter += 1;
698 if expense_counter > EXPENSE_LIMIT {
699 return None;
700 }
701
702 invalid_signatures.insert(invalidated_item);
704 processed_symbols.insert(invalidated_item);
705 if !invalidated_item.1.is_empty() {
706 partially_invalid_symbols.insert(invalidated_item.0);
708 }
709
710 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
712 if referenced_items.contains(&invalidated_item) {
713 if !processed_symbols.contains(referencing_item) {
715 symbols_to_process.push(*referencing_item);
716 }
717
718 invalid_signatures.insert(*referencing_item);
720 if !referencing_item.1.is_empty() {
721 partially_invalid_symbols.insert(referencing_item.0);
723 }
724 }
725 }
726
727 if expense_counter > EXPENSE_LIMIT {
729 return None;
730 }
731 }
732
733 let mut invalid_bodies = HashSet::default();
736
737 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
739 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
741 invalid_bodies.insert(*referencing_item);
742 if !referencing_item.1.is_empty() {
743 partially_invalid_symbols.insert(referencing_item.0);
745 }
746 }
747 }
748
749 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
753 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
754 invalid_bodies.insert(*referencing_item);
755 if !referencing_item.1.is_empty() {
756 partially_invalid_symbols.insert(referencing_item.0);
757 }
758 }
759 }
760
761 let mut all_invalid_symbols = invalid_signatures;
768 all_invalid_symbols.extend(invalid_bodies);
769 Some((all_invalid_symbols, partially_invalid_symbols))
770 }
771
772 #[inline]
778 pub fn remove_references_from_invalid_symbols(&mut self, invalid_symbols_and_members: &HashSet<SymbolIdentifier>) {
779 self.symbol_references_to_symbols
781 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
782 self.symbol_references_to_symbols_in_signature
783 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
784 self.symbol_references_to_overridden_members
785 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
786 self.property_write_references
787 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
788 self.property_read_references
789 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
790 }
791
792 #[inline]
794 #[must_use]
795 pub fn get_symbol_references_to_symbols(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
796 &self.symbol_references_to_symbols
797 }
798
799 #[inline]
801 #[must_use]
802 pub fn get_symbol_references_to_symbols_in_signature(
803 &self,
804 ) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
805 &self.symbol_references_to_symbols_in_signature
806 }
807
808 #[inline]
810 #[must_use]
811 pub fn get_symbol_references_to_overridden_members(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
812 &self.symbol_references_to_overridden_members
813 }
814
815 #[inline]
817 #[must_use]
818 pub fn get_functionlike_references_to_functionlike_returns(
819 &self,
820 ) -> &HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>> {
821 &self.functionlike_references_to_functionlike_returns
822 }
823
824 #[inline]
826 #[must_use]
827 pub fn get_file_references_to_symbols(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
828 &self.file_references_to_symbols
829 }
830
831 #[inline]
833 #[must_use]
834 pub fn get_file_references_to_symbols_in_signature(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
835 &self.file_references_to_symbols_in_signature
836 }
837}