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 pub fn new() -> Self {
85 Self {
86 symbol_references_to_symbols: HashMap::default(),
87 symbol_references_to_symbols_in_signature: HashMap::default(),
88 symbol_references_to_overridden_members: HashMap::default(),
89 functionlike_references_to_functionlike_returns: HashMap::default(),
90 file_references_to_symbols: HashMap::default(),
91 file_references_to_symbols_in_signature: HashMap::default(),
92 }
93 }
94
95 #[inline]
97 pub fn count_body_references(&self) -> usize {
98 self.symbol_references_to_symbols.values().map(|v| v.len()).sum()
99 }
100
101 #[inline]
103 pub fn count_signature_references(&self) -> usize {
104 self.symbol_references_to_symbols_in_signature.values().map(|v| v.len()).sum()
105 }
106
107 #[inline]
116 pub fn count_referencing_symbols(&self, symbol: &SymbolIdentifier, in_signature: bool) -> usize {
117 let map = if in_signature {
118 &self.symbol_references_to_symbols_in_signature
119 } else {
120 &self.symbol_references_to_symbols
121 };
122
123 map.values().filter(|referenced_set| referenced_set.contains(symbol)).count()
124 }
125
126 #[inline]
135 pub fn add_symbol_reference_to_class_member(
136 &mut self,
137 referencing_symbol: Atom,
138 class_member: SymbolIdentifier,
139 in_signature: bool,
140 ) {
141 self.add_symbol_reference_to_symbol(referencing_symbol, class_member.0, false);
143
144 let key = (referencing_symbol, empty_atom());
146 if in_signature {
147 self.symbol_references_to_symbols_in_signature.entry(key).or_default().insert(class_member);
148 } else {
149 self.symbol_references_to_symbols.entry(key).or_default().insert(class_member);
150 }
151 }
152
153 #[inline]
162 pub fn add_symbol_reference_to_symbol(&mut self, referencing_symbol: Atom, symbol: Atom, in_signature: bool) {
163 if referencing_symbol == symbol {
164 return;
165 }
166
167 let referencing_key = (referencing_symbol, empty_atom());
169 let referenced_key = (symbol, empty_atom());
170
171 if in_signature {
172 self.symbol_references_to_symbols_in_signature.entry(referencing_key).or_default().insert(referenced_key);
173 } else {
174 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_key)
176 && sig_refs.contains(&referenced_key)
177 {
178 return;
179 }
180 self.symbol_references_to_symbols.entry(referencing_key).or_default().insert(referenced_key);
181 }
182 }
183
184 #[inline]
194 pub fn add_class_member_reference_to_class_member(
195 &mut self,
196 referencing_class_member: SymbolIdentifier,
197 class_member: SymbolIdentifier,
198 in_signature: bool,
199 ) {
200 if referencing_class_member == class_member {
201 return;
202 }
203
204 self.add_symbol_reference_to_symbol(referencing_class_member.0, class_member.0, false);
206 self.add_class_member_reference_to_symbol(referencing_class_member, class_member.0, false);
207
208 if in_signature {
210 self.symbol_references_to_symbols_in_signature
211 .entry(referencing_class_member)
212 .or_default()
213 .insert(class_member);
214 } else {
215 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(class_member);
218 }
219 }
220
221 #[inline]
231 pub fn add_class_member_reference_to_symbol(
232 &mut self,
233 referencing_class_member: SymbolIdentifier,
234 symbol: Atom,
235 in_signature: bool,
236 ) {
237 if referencing_class_member.0 == symbol {
238 return;
239 }
240
241 self.add_symbol_reference_to_symbol(referencing_class_member.0, symbol, false);
243
244 let referenced_key = (symbol, empty_atom());
246
247 if in_signature {
248 self.symbol_references_to_symbols_in_signature
249 .entry(referencing_class_member)
250 .or_default()
251 .insert(referenced_key);
252 } else {
253 if let Some(sig_refs) = self.symbol_references_to_symbols_in_signature.get(&referencing_class_member)
255 && sig_refs.contains(&referenced_key)
256 {
257 return;
258 }
259 self.symbol_references_to_symbols.entry(referencing_class_member).or_default().insert(referenced_key);
260 }
261 }
262
263 #[inline]
266 pub fn add_file_reference_to_class_member(
267 &mut self,
268 file_hash: Atom,
269 class_member: SymbolIdentifier,
270 in_signature: bool,
271 ) {
272 if in_signature {
273 self.file_references_to_symbols_in_signature.entry(file_hash).or_default().insert(class_member);
274 } else {
275 if let Some(sig_refs) = self.file_references_to_symbols_in_signature.get(&file_hash)
277 && sig_refs.contains(&class_member)
278 {
279 return;
280 }
281 self.file_references_to_symbols.entry(file_hash).or_default().insert(class_member);
282 }
283 }
284
285 #[inline]
288 pub fn add_reference_to_class_member(
289 &mut self,
290 scope: &ScopeContext<'_>,
291 class_member: SymbolIdentifier,
292 in_signature: bool,
293 ) {
294 self.add_reference_to_class_member_with_file(scope, class_member, in_signature, None)
295 }
296
297 #[inline]
307 pub fn add_reference_to_class_member_with_file(
308 &mut self,
309 scope: &ScopeContext<'_>,
310 class_member: SymbolIdentifier,
311 in_signature: bool,
312 file_hash: Option<Atom>,
313 ) {
314 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
315 match referencing_functionlike {
316 FunctionLikeIdentifier::Function(function_name) => {
317 self.add_symbol_reference_to_class_member(function_name, class_member, in_signature)
318 }
319 FunctionLikeIdentifier::Method(class_name, function_name) => self
320 .add_class_member_reference_to_class_member(
321 (class_name, function_name),
322 class_member,
323 in_signature,
324 ),
325 _ => {
326 if let Some(hash) = file_hash {
329 self.add_file_reference_to_class_member(hash, class_member, in_signature)
330 } else {
331 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature)
332 }
333 }
334 }
335 } else if let Some(calling_class) = scope.get_class_like_name() {
336 self.add_symbol_reference_to_class_member(calling_class, class_member, in_signature)
338 } else {
339 if let Some(hash) = file_hash {
342 self.add_file_reference_to_class_member(hash, class_member, in_signature)
343 } else {
344 self.add_symbol_reference_to_class_member(empty_atom(), class_member, in_signature)
345 }
346 }
347 }
348
349 #[inline]
350 pub fn add_reference_for_method_call(&mut self, scope: &ScopeContext<'_>, method: &MethodIdentifier) {
351 self.add_reference_to_class_member(scope, (*method.get_class_name(), *method.get_method_name()), false);
352 }
353
354 #[inline]
355 pub fn add_reference_for_property_access(
356 &mut self,
357 scope: &ScopeContext<'_>,
358 class_name: Atom,
359 property_name: Atom,
360 ) {
361 self.add_reference_to_class_member(scope, (class_name, property_name), false);
362 }
363
364 #[inline]
367 pub fn add_reference_to_overridden_class_member(&mut self, scope: &ScopeContext, class_member: SymbolIdentifier) {
368 let referencing_key = if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
369 match referencing_functionlike {
370 FunctionLikeIdentifier::Function(function_name) => (empty_atom(), function_name),
371 FunctionLikeIdentifier::Method(class_name, function_name) => (class_name, function_name),
372 _ => {
373 return;
375 }
376 }
377 } else if let Some(calling_class) = scope.get_class_like_name() {
378 (ascii_lowercase_atom(&calling_class), empty_atom())
379 } else {
380 return; };
382
383 self.symbol_references_to_overridden_members.entry(referencing_key).or_default().insert(class_member);
384 }
385
386 #[inline]
389 pub fn add_reference_to_symbol(&mut self, scope: &ScopeContext, symbol: Atom, in_signature: bool) {
390 if let Some(referencing_functionlike) = scope.get_function_like_identifier() {
391 match referencing_functionlike {
392 FunctionLikeIdentifier::Function(function_name) => {
393 self.add_symbol_reference_to_symbol(function_name, symbol, in_signature)
394 }
395 FunctionLikeIdentifier::Method(class_name, function_name) => {
396 self.add_class_member_reference_to_symbol((class_name, function_name), symbol, in_signature)
397 }
398 _ => {
399 }
401 }
402 } else if let Some(calling_class) = scope.get_class_like_name() {
403 self.add_symbol_reference_to_symbol(ascii_lowercase_atom(&calling_class), symbol, in_signature)
404 }
405 }
406
407 #[inline]
409 pub fn add_reference_to_functionlike_return(
410 &mut self,
411 referencing_functionlike: FunctionLikeIdentifier,
412 referenced_functionlike: FunctionLikeIdentifier,
413 ) {
414 if referencing_functionlike == referenced_functionlike {
415 return;
416 }
417
418 self.functionlike_references_to_functionlike_returns
419 .entry(referencing_functionlike)
420 .or_default()
421 .insert(referenced_functionlike);
422 }
423
424 #[inline]
427 pub fn extend(&mut self, other: Self) {
428 for (k, v) in other.symbol_references_to_symbols {
429 self.symbol_references_to_symbols.entry(k).or_default().extend(v);
430 }
431 for (k, v) in other.symbol_references_to_symbols_in_signature {
432 self.symbol_references_to_symbols_in_signature.entry(k).or_default().extend(v);
433 }
434 for (k, v) in other.symbol_references_to_overridden_members {
435 self.symbol_references_to_overridden_members.entry(k).or_default().extend(v);
436 }
437 for (k, v) in other.functionlike_references_to_functionlike_returns {
438 self.functionlike_references_to_functionlike_returns.entry(k).or_default().extend(v);
439 }
440
441 for (k, v) in other.file_references_to_symbols {
442 self.file_references_to_symbols.entry(k).or_default().extend(v);
443 }
444
445 for (k, v) in other.file_references_to_symbols_in_signature {
446 self.file_references_to_symbols_in_signature.entry(k).or_default().extend(v);
447 }
448 }
449
450 #[inline]
457 pub fn get_referenced_symbols_and_members(&self) -> HashSet<&SymbolIdentifier> {
458 let mut referenced_items = HashSet::default();
459 for refs in self.symbol_references_to_symbols.values() {
460 referenced_items.extend(refs.iter());
461 }
462 for refs in self.symbol_references_to_symbols_in_signature.values() {
463 referenced_items.extend(refs.iter());
464 }
465
466 referenced_items
467 }
468
469 #[inline]
476 pub fn get_back_references(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
477 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
478
479 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
480 for referenced_item in referenced_items {
481 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
482 }
483 }
484 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
485 for referenced_item in referenced_items {
486 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
487 }
488 }
489 back_refs
490 }
491
492 #[inline]
503 pub fn get_references_to_symbol(&self, target_symbol: SymbolIdentifier) -> HashSet<&SymbolIdentifier> {
504 let mut referencing_items = HashSet::default();
505 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
506 if referenced_items.contains(&target_symbol) {
507 referencing_items.insert(referencing_item);
508 }
509 }
510 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
511 if referenced_items.contains(&target_symbol) {
512 referencing_items.insert(referencing_item);
513 }
514 }
515 referencing_items
516 }
517
518 #[inline]
525 pub fn get_referenced_symbols_and_members_with_counts(&self) -> HashMap<SymbolIdentifier, u32> {
526 let mut counts = HashMap::default();
527 for referenced_items in self.symbol_references_to_symbols.values() {
528 for referenced_item in referenced_items {
529 *counts.entry(*referenced_item).or_insert(0) += 1;
530 }
531 }
532 for referenced_items in self.symbol_references_to_symbols_in_signature.values() {
533 for referenced_item in referenced_items {
534 *counts.entry(*referenced_item).or_insert(0) += 1;
535 }
536 }
537 counts
538 }
539
540 #[inline]
547 pub fn get_referenced_overridden_class_members(&self) -> HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
548 let mut back_refs: HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> = HashMap::default();
549
550 for (referencing_item, referenced_items) in &self.symbol_references_to_overridden_members {
551 for referenced_item in referenced_items {
552 back_refs.entry(*referenced_item).or_default().insert(*referencing_item);
553 }
554 }
555 back_refs
556 }
557
558 #[inline]
573 pub fn get_invalid_symbols(&self, codebase_diff: &CodebaseDiff) -> Option<(HashSet<SymbolIdentifier>, AtomSet)> {
574 let mut invalid_signatures = HashSet::default();
575 let mut partially_invalid_symbols = AtomSet::default();
576
577 for sig_ref_key in self.symbol_references_to_symbols_in_signature.keys() {
578 let containing_symbol = (sig_ref_key.0, empty_atom());
580
581 if codebase_diff.contains_changed_entry(&containing_symbol) {
582 invalid_signatures.insert(*sig_ref_key);
583 partially_invalid_symbols.insert(sig_ref_key.0);
584 }
585 }
586
587 let mut symbols_to_process = codebase_diff.get_changed().iter().copied().collect::<Vec<_>>();
589 let mut processed_symbols = HashSet::default();
590 let mut expense_counter = 0;
591
592 const EXPENSE_LIMIT: usize = 5000;
593 while let Some(invalidated_item) = symbols_to_process.pop() {
594 if processed_symbols.contains(&invalidated_item) {
595 continue;
596 }
597
598 expense_counter += 1;
599 if expense_counter > EXPENSE_LIMIT {
600 return None;
601 }
602
603 invalid_signatures.insert(invalidated_item);
605 processed_symbols.insert(invalidated_item);
606 if !invalidated_item.1.is_empty() {
607 partially_invalid_symbols.insert(invalidated_item.0);
609 }
610
611 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
613 if referenced_items.contains(&invalidated_item) {
614 if !processed_symbols.contains(referencing_item) {
616 symbols_to_process.push(*referencing_item);
617 }
618
619 invalid_signatures.insert(*referencing_item);
621 if !referencing_item.1.is_empty() {
622 partially_invalid_symbols.insert(referencing_item.0);
624 }
625 }
626 }
627
628 if expense_counter > EXPENSE_LIMIT {
630 return None;
631 }
632 }
633
634 let mut invalid_bodies = HashSet::default();
637
638 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols {
640 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
642 invalid_bodies.insert(*referencing_item);
643 if !referencing_item.1.is_empty() {
644 partially_invalid_symbols.insert(referencing_item.0);
646 }
647 }
648 }
649
650 for (referencing_item, referenced_items) in &self.symbol_references_to_symbols_in_signature {
654 if referenced_items.iter().any(|r| invalid_signatures.contains(r)) {
655 invalid_bodies.insert(*referencing_item);
656 if !referencing_item.1.is_empty() {
657 partially_invalid_symbols.insert(referencing_item.0);
658 }
659 }
660 }
661
662 let mut all_invalid_symbols = invalid_signatures;
669 all_invalid_symbols.extend(invalid_bodies);
670 Some((all_invalid_symbols, partially_invalid_symbols))
671 }
672
673 #[inline]
679 pub fn remove_references_from_invalid_symbols(&mut self, invalid_symbols_and_members: &HashSet<SymbolIdentifier>) {
680 self.symbol_references_to_symbols
682 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
683 self.symbol_references_to_symbols_in_signature
684 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
685 self.symbol_references_to_overridden_members
686 .retain(|referencing_item, _| !invalid_symbols_and_members.contains(referencing_item));
687 }
688
689 #[inline]
691 pub fn get_symbol_references_to_symbols(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
692 &self.symbol_references_to_symbols
693 }
694
695 #[inline]
697 pub fn get_symbol_references_to_symbols_in_signature(
698 &self,
699 ) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
700 &self.symbol_references_to_symbols_in_signature
701 }
702
703 #[inline]
705 pub fn get_symbol_references_to_overridden_members(&self) -> &HashMap<SymbolIdentifier, HashSet<SymbolIdentifier>> {
706 &self.symbol_references_to_overridden_members
707 }
708
709 #[inline]
711 pub fn get_functionlike_references_to_functionlike_returns(
712 &self,
713 ) -> &HashMap<FunctionLikeIdentifier, HashSet<FunctionLikeIdentifier>> {
714 &self.functionlike_references_to_functionlike_returns
715 }
716
717 #[inline]
719 pub fn get_file_references_to_symbols(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
720 &self.file_references_to_symbols
721 }
722
723 #[inline]
725 pub fn get_file_references_to_symbols_in_signature(&self) -> &HashMap<Atom, HashSet<SymbolIdentifier>> {
726 &self.file_references_to_symbols_in_signature
727 }
728}