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
81impl SymbolReferences {
82 #[inline]
84 #[must_use]
85 pub fn new() -> Self {
86 Self {
87 symbol_references_to_symbols: HashMap::default(),
88 symbol_references_to_symbols_in_signature: HashMap::default(),
89 symbol_references_to_overridden_members: HashMap::default(),
90 functionlike_references_to_functionlike_returns: HashMap::default(),
91 file_references_to_symbols: HashMap::default(),
92 file_references_to_symbols_in_signature: HashMap::default(),
93 }
94 }
95
96 #[inline]
98 pub fn count_body_references(&self) -> usize {
99 self.symbol_references_to_symbols.values().map(std::collections::HashSet::len).sum()
100 }
101
102 #[inline]
104 pub fn count_signature_references(&self) -> usize {
105 self.symbol_references_to_symbols_in_signature.values().map(std::collections::HashSet::len).sum()
106 }
107
108 #[inline]
117 #[must_use]
118 pub fn count_referencing_symbols(&self, symbol: &SymbolIdentifier, in_signature: bool) -> usize {
119 let map = if in_signature {
120 &self.symbol_references_to_symbols_in_signature
121 } else {
122 &self.symbol_references_to_symbols
123 };
124
125 map.values().filter(|referenced_set| referenced_set.contains(symbol)).count()
126 }
127
128 #[inline]
137 pub fn add_symbol_reference_to_class_member(
138 &mut self,
139 referencing_symbol: Atom,
140 class_member: SymbolIdentifier,
141 in_signature: bool,
142 ) {
143 self.add_symbol_reference_to_symbol(referencing_symbol, class_member.0, false);
145
146 let key = (referencing_symbol, empty_atom());
148 if in_signature {
149 self.symbol_references_to_symbols_in_signature.entry(key).or_default().insert(class_member);
150 } else {
151 self.symbol_references_to_symbols.entry(key).or_default().insert(class_member);
152 }
153 }
154
155 #[inline]
164 pub fn add_symbol_reference_to_symbol(&mut self, referencing_symbol: Atom, symbol: Atom, in_signature: bool) {
165 if referencing_symbol == symbol {
166 return;
167 }
168
169 let referencing_key = (referencing_symbol, empty_atom());
171 let referenced_key = (symbol, empty_atom());
172
173 if in_signature {
174 self.symbol_references_to_symbols_in_signature.entry(referencing_key).or_default().insert(referenced_key);
175 } else {
176 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_key)
178 && sig_refs.contains(&referenced_key)
179 {
180 return;
181 }
182 self.symbol_references_to_symbols.entry(referencing_key).or_default().insert(referenced_key);
183 }
184 }
185
186 #[inline]
196 pub fn add_class_member_reference_to_class_member(
197 &mut self,
198 referencing_class_member: SymbolIdentifier,
199 class_member: SymbolIdentifier,
200 in_signature: bool,
201 ) {
202 if referencing_class_member == class_member {
203 return;
204 }
205
206 self.add_symbol_reference_to_symbol(referencing_class_member.0, class_member.0, false);
208 self.add_class_member_reference_to_symbol(referencing_class_member, class_member.0, false);
209
210 if in_signature {
212 self.symbol_references_to_symbols_in_signature
213 .entry(referencing_class_member)
214 .or_default()
215 .insert(class_member);
216 } else {
217 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(class_member);
220 }
221 }
222
223 #[inline]
233 pub fn add_class_member_reference_to_symbol(
234 &mut self,
235 referencing_class_member: SymbolIdentifier,
236 symbol: Atom,
237 in_signature: bool,
238 ) {
239 if referencing_class_member.0 == symbol {
240 return;
241 }
242
243 self.add_symbol_reference_to_symbol(referencing_class_member.0, symbol, false);
245
246 let referenced_key = (symbol, empty_atom());
248
249 if in_signature {
250 self.symbol_references_to_symbols_in_signature
251 .entry(referencing_class_member)
252 .or_default()
253 .insert(referenced_key);
254 } else {
255 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_class_member)
257 && sig_refs.contains(&referenced_key)
258 {
259 return;
260 }
261 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(referenced_key);
262 }
263 }
264
265 #[inline]
268 pub fn add_file_reference_to_class_member(
269 &mut self,
270 file_hash: Atom,
271 class_member: SymbolIdentifier,
272 in_signature: bool,
273 ) {
274 if in_signature {
275 self.file_references_to_symbols_in_signature.entry(file_hash).or_default().insert(class_member);
276 } else {
277 if let Some(sig_refs) = self.file_references_to_symbols_in_signature.get(&file_hash)
279 && sig_refs.contains(&class_member)
280 {
281 return;
282 }
283 self.file_references_to_symbols.entry(file_hash).or_default().insert(class_member);
284 }
285 }
286
287 #[inline]
290 pub fn add_reference_to_class_member(
291 &mut self,
292 scope: &ScopeContext<'_>,
293 class_member: SymbolIdentifier,
294 in_signature: bool,
295 ) {
296 self.add_reference_to_class_member_with_file(scope, class_member, in_signature, None);
297 }
298
299 #[inline]
309 pub fn add_reference_to_class_member_with_file(
310 &mut self,
311 scope: &ScopeContext<'_>,
312 class_member: SymbolIdentifier,
313 in_signature: bool,
314 file_hash: Option<Atom>,
315 ) {
316 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
317 match referencing_functionlike {
318 FunctionLikeIdentifier::Function(function_name) => {
319 self.add_symbol_reference_to_class_member(function_name, class_member, in_signature);
320 }
321 FunctionLikeIdentifier::Method(class_name, function_name) => self
322 .add_class_member_reference_to_class_member(
323 (class_name, function_name),
324 class_member,
325 in_signature,
326 ),
327 _ => {
328 if let Some(hash) = file_hash {
331 self.add_file_reference_to_class_member(hash, class_member, in_signature);
332 } else {
333 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature);
334 }
335 }
336 }
337 } else if let Some(calling_class) = scope.get_class_like_name() {
338 self.add_symbol_reference_to_class_member(calling_class, class_member, in_signature);
340 } else {
341 if let Some(hash) = file_hash {
344 self.add_file_reference_to_class_member(hash, class_member, in_signature);
345 } else {
346 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature);
347 }
348 }
349 }
350
351 #[inline]
352 pub fn add_reference_for_method_call(&mut self, scope: &ScopeContext<'_>, method: &MethodIdentifier) {
353 self.add_reference_to_class_member(scope, (*method.get_class_name(), *method.get_method_name()), false);
354 }
355
356 #[inline]
357 pub fn add_reference_for_property_access(
358 &mut self,
359 scope: &ScopeContext<'_>,
360 class_name: Atom,
361 property_name: Atom,
362 ) {
363 self.add_reference_to_class_member(scope, (class_name, property_name), false);
364 }
365
366 #[inline]
369 pub fn add_reference_to_overridden_class_member(&mut self, scope: &ScopeContext, class_member: SymbolIdentifier) {
370 let referencing_key = if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
371 match referencing_functionlike {
372 FunctionLikeIdentifier::Function(function_name) => (empty_atom(), function_name),
373 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
374 _ => {
375 return;
377 }
378 }
379 } else if let Some(calling_class) = scope.get_class_like_name() {
380 (ascii_lowercase_atom(&calling_class), empty_atom())
381 } else {
382 return; };
384
385 self.symbol_references_to_overridden_members.entry(referencing_key).or_default().insert(class_member);
386 }
387
388 #[inline]
391 pub fn add_reference_to_symbol(&mut self, scope: &ScopeContext, symbol: Atom, in_signature: bool) {
392 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
393 match referencing_functionlike {
394 FunctionLikeIdentifier::Function(function_name) => {
395 self.add_symbol_reference_to_symbol(function_name, symbol, in_signature);
396 }
397 FunctionLikeIdentifier::Method(class_name, function_name) => {
398 self.add_class_member_reference_to_symbol((class_name, function_name), symbol, in_signature);
399 }
400 _ => {
401 }
403 }
404 } else if let Some(calling_class) = scope.get_class_like_name() {
405 self.add_symbol_reference_to_symbol(ascii_lowercase_atom(&calling_class), symbol, in_signature);
406 }
407 }
408
409 #[inline]
411 pub fn add_reference_to_functionlike_return(
412 &mut self,
413 referencing_functionlike: FunctionLikeIdentifier,
414 referenced_functionlike: FunctionLikeIdentifier,
415 ) {
416 if referencing_functionlike == referenced_functionlike {
417 return;
418 }
419
420 self.functionlike_references_to_functionlike_returns
421 .entry(referencing_functionlike)
422 .or_default()
423 .insert(referenced_functionlike);
424 }
425
426 #[inline]
429 pub fn extend(&mut self, other: Self) {
430 for (k, v) in other.symbol_references_to_symbols {
431 self.symbol_references_to_symbols.entry(k).or_default().extend(v);
432 }
433 for (k, v) in other.symbol_references_to_symbols_in_signature {
434 self.symbol_references_to_symbols_in_signature.entry(k).or_default().extend(v);
435 }
436 for (k, v) in other.symbol_references_to_overridden_members {
437 self.symbol_references_to_overridden_members.entry(k).or_default().extend(v);
438 }
439 for (k, v) in other.functionlike_references_to_functionlike_returns {
440 self.functionlike_references_to_functionlike_returns.entry(k).or_default().extend(v);
441 }
442
443 for (k, v) in other.file_references_to_symbols {
444 self.file_references_to_symbols.entry(k).or_default().extend(v);
445 }
446
447 for (k, v) in other.file_references_to_symbols_in_signature {
448 self.file_references_to_symbols_in_signature.entry(k).or_default().extend(v);
449 }
450 }
451
452 #[inline]
459 #[must_use]
460 pub fn get_referenced_symbols_and_members(&self) -> HashSet<&SymbolIdentifier> {
461 let mut referenced_items = HashSet::default();
462 for refs in self.symbol_references_to_symbols.values() {
463 referenced_items.extend(refs.iter());
464 }
465 for refs in self.symbol_references_to_symbols_in_signature.values() {
466 referenced_items.extend(refs.iter());
467 }
468
469 referenced_items
470 }
471
472 #[inline]
479 #[must_use]
480 pub fn get_back_references(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
481 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
482
483 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
484 for referenced_item in referenced_items {
485 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
486 }
487 }
488 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
489 for referenced_item in referenced_items {
490 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
491 }
492 }
493 back_refs
494 }
495
496 #[inline]
507 #[must_use]
508 pub fn get_references_to_symbol(&self, target_symbol: SymbolIdentifier) -> HashSet<&SymbolIdentifier> {
509 let mut referencing_items = HashSet::default();
510 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
511 if referenced_items.contains(&target_symbol) {
512 referencing_items.insert(referencing_item);
513 }
514 }
515 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
516 if referenced_items.contains(&target_symbol) {
517 referencing_items.insert(referencing_item);
518 }
519 }
520 referencing_items
521 }
522
523 #[inline]
530 #[must_use]
531 pub fn get_referenced_symbols_and_members_with_counts(&self) -> HashMap<SymbolIdentifier, u32> {
532 let mut counts = HashMap::default();
533 for referenced_items in self.symbol_references_to_symbols.values() {
534 for referenced_item in referenced_items {
535 *counts.entry(*referenced_item).or_insert(0) += 1;
536 }
537 }
538 for referenced_items in self.symbol_references_to_symbols_in_signature.values() {
539 for referenced_item in referenced_items {
540 *counts.entry(*referenced_item).or_insert(0) += 1;
541 }
542 }
543 counts
544 }
545
546 #[inline]
553 #[must_use]
554 pub fn get_referenced_overridden_class_members(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
555 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
556
557 for (referencing_item, referenced_items) in &self.symbol_references_to_overridden_members {
558 for referenced_item in referenced_items {
559 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
560 }
561 }
562 back_refs
563 }
564
565 #[inline]
580 #[must_use]
581 pub fn get_invalid_symbols(&self, codebase_diff: &CodebaseDiff) -> Option<(HashSet<SymbolIdentifier>, AtomSet)> {
582 let mut invalid_signatures = HashSet::default();
583 let mut partially_invalid_symbols = AtomSet::default();
584
585 for sig_ref_key in self.symbol_references_to_symbols_in_signature.keys() {
586 let containing_symbol = (sig_ref_key.0, empty_atom());
588
589 if codebase_diff.contains_changed_entry(&containing_symbol) {
590 invalid_signatures.insert(*sig_ref_key);
591 partially_invalid_symbols.insert(sig_ref_key.0);
592 }
593 }
594
595 let mut symbols_to_process = codebase_diff.get_changed().iter().copied().collect::<Vec<_>>();
597 let mut processed_symbols = HashSet::default();
598 let mut expense_counter = 0;
599
600 const EXPENSE_LIMIT: usize = 5000;
601 while let Some(invalidated_item) = symbols_to_process.pop() {
602 if processed_symbols.contains(&invalidated_item) {
603 continue;
604 }
605
606 expense_counter += 1;
607 if expense_counter > EXPENSE_LIMIT {
608 return None;
609 }
610
611 invalid_signatures.insert(invalidated_item);
613 processed_symbols.insert(invalidated_item);
614 if !invalidated_item.1.is_empty() {
615 partially_invalid_symbols.insert(invalidated_item.0);
617 }
618
619 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
621 if referenced_items.contains(&invalidated_item) {
622 if !processed_symbols.contains(referencing_item) {
624 symbols_to_process.push(*referencing_item);
625 }
626
627 invalid_signatures.insert(*referencing_item);
629 if !referencing_item.1.is_empty() {
630 partially_invalid_symbols.insert(referencing_item.0);
632 }
633 }
634 }
635
636 if expense_counter > EXPENSE_LIMIT {
638 return None;
639 }
640 }
641
642 let mut invalid_bodies = HashSet::default();
645
646 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
648 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
650 invalid_bodies.insert(*referencing_item);
651 if !referencing_item.1.is_empty() {
652 partially_invalid_symbols.insert(referencing_item.0);
654 }
655 }
656 }
657
658 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
662 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
663 invalid_bodies.insert(*referencing_item);
664 if !referencing_item.1.is_empty() {
665 partially_invalid_symbols.insert(referencing_item.0);
666 }
667 }
668 }
669
670 let mut all_invalid_symbols = invalid_signatures;
677 all_invalid_symbols.extend(invalid_bodies);
678 Some((all_invalid_symbols, partially_invalid_symbols))
679 }
680
681 #[inline]
687 pub fn remove_references_from_invalid_symbols(&mut self, invalid_symbols_and_members: &HashSet<SymbolIdentifier>) {
688 self.symbol_references_to_symbols
690 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
691 self.symbol_references_to_symbols_in_signature
692 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
693 self.symbol_references_to_overridden_members
694 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
695 }
696
697 #[inline]
699 #[must_use]
700 pub fn get_symbol_references_to_symbols(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
701 &self.symbol_references_to_symbols
702 }
703
704 #[inline]
706 #[must_use]
707 pub fn get_symbol_references_to_symbols_in_signature(
708 &self,
709 ) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
710 &self.symbol_references_to_symbols_in_signature
711 }
712
713 #[inline]
715 #[must_use]
716 pub fn get_symbol_references_to_overridden_members(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
717 &self.symbol_references_to_overridden_members
718 }
719
720 #[inline]
722 #[must_use]
723 pub fn get_functionlike_references_to_functionlike_returns(
724 &self,
725 ) -> &HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>> {
726 &self.functionlike_references_to_functionlike_returns
727 }
728
729 #[inline]
731 #[must_use]
732 pub fn get_file_references_to_symbols(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
733 &self.file_references_to_symbols
734 }
735
736 #[inline]
738 #[must_use]
739 pub fn get_file_references_to_symbols_in_signature(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
740 &self.file_references_to_symbols_in_signature
741 }
742}