1#![allow(clippy::similar_names)] use std::collections::HashMap;
42use std::path::Path;
43
44use super::super::edge::kind::{LifetimeConstraintKind, MacroExpansionKind, TypeOfContext};
45use super::super::resolution::canonicalize_graph_qualified_name;
46use super::staging::{
47 CIndirectStagingPayload, NodeMetadataFlag, NodeMetadataUpdate, PendingBinding,
48 PendingIndirectCallsite, StagingGraph,
49};
50use crate::graph::node::{Language, Span};
51use crate::graph::unified::edge::kind::{
52 ChannelBufferKind, ChannelPeerDirection, InferenceKind, TypeArg,
53};
54use crate::graph::unified::edge::{
55 EdgeKind, ExportKind, FfiConvention, HttpMethod, ResolvedVia, TableWriteOp, WrapKind,
56};
57use crate::graph::unified::file::FileId;
58use crate::graph::unified::node::{NodeId, NodeKind};
59use crate::graph::unified::storage::NodeEntry;
60use crate::graph::unified::storage::c_indirect::{BindingSiteKind, IndirectShape, LocalScopeIndex};
61use crate::graph::unified::string::StringId;
62
63pub(crate) const CALL_COMPATIBLE_KINDS: &[NodeKind] = &[
71 NodeKind::Function,
72 NodeKind::Method,
73 NodeKind::Macro,
74 NodeKind::Constant,
75 NodeKind::LambdaTarget,
76];
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum CalleeKindHint {
84 Function,
86 Method,
88 Macro,
90 Constant,
92 LambdaTarget,
94 Any,
96}
97
98impl CalleeKindHint {
99 fn to_node_kind(self) -> NodeKind {
101 match self {
102 Self::Function | Self::Any => NodeKind::Function,
103 Self::Method => NodeKind::Method,
104 Self::Macro => NodeKind::Macro,
105 Self::Constant => NodeKind::Constant,
106 Self::LambdaTarget => NodeKind::LambdaTarget,
107 }
108 }
109}
110
111#[derive(Debug)]
118pub struct GraphBuildHelper<'a> {
119 staging: &'a mut StagingGraph,
121 language: Language,
123 file_id: FileId,
125 file_path: String,
127 string_cache: HashMap<String, StringId>,
129 next_string_id: u32,
131 node_cache: HashMap<(String, NodeKind), NodeId>,
139}
140
141impl<'a> GraphBuildHelper<'a> {
142 pub fn new(staging: &'a mut StagingGraph, file: &Path, language: Language) -> Self {
147 Self {
148 staging,
149 language,
150 file_id: FileId::new(0), file_path: file.display().to_string(),
152 string_cache: HashMap::new(),
153 next_string_id: 0,
154 node_cache: HashMap::new(),
155 }
156 }
157
158 pub fn with_file_id(
160 staging: &'a mut StagingGraph,
161 file: &Path,
162 language: Language,
163 file_id: FileId,
164 ) -> Self {
165 Self {
166 staging,
167 language,
168 file_id,
169 file_path: file.display().to_string(),
170 string_cache: HashMap::new(),
171 next_string_id: 0,
172 node_cache: HashMap::new(),
173 }
174 }
175
176 #[must_use]
178 pub fn language(&self) -> Language {
179 self.language
180 }
181
182 #[must_use]
184 pub fn file_id(&self) -> FileId {
185 self.file_id
186 }
187
188 #[must_use]
194 pub fn lookup_node(&self, name: &str, kind: NodeKind) -> Option<NodeId> {
195 self.node_cache.get(&(name.to_string(), kind)).copied()
196 }
197
198 #[must_use]
200 pub fn file_path(&self) -> &str {
201 &self.file_path
202 }
203
204 #[must_use]
217 pub fn staging_mut(&mut self) -> &mut StagingGraph {
218 self.staging
219 }
220
221 pub fn attach_body_hashes(&mut self, content: &[u8]) {
229 self.staging.attach_body_hashes(content, None);
233 }
234
235 pub fn intern(&mut self, s: &str) -> StringId {
241 if let Some(&id) = self.string_cache.get(s) {
242 return id;
243 }
244
245 let id = StringId::new_local(self.next_string_id);
246 self.next_string_id += 1;
247 self.string_cache.insert(s.to_string(), id);
248 self.staging.intern_string(id, s.to_string());
250 id
251 }
252
253 #[must_use]
255 pub fn has_node(&self, qualified_name: &str) -> bool {
256 self.node_cache
257 .keys()
258 .any(|(name, _)| name == qualified_name)
259 }
260
261 #[must_use]
263 pub fn get_node(&self, qualified_name: &str) -> Option<NodeId> {
264 self.node_cache
265 .iter()
266 .find_map(|((name, _), id)| (name == qualified_name).then_some(*id))
267 }
268
269 #[must_use]
271 pub fn has_node_with_kind(&self, qualified_name: &str, kind: NodeKind) -> bool {
272 self.node_cache
273 .contains_key(&(qualified_name.to_string(), kind))
274 }
275
276 #[must_use]
278 pub fn get_node_with_kind(&self, qualified_name: &str, kind: NodeKind) -> Option<NodeId> {
279 self.node_cache
280 .get(&(qualified_name.to_string(), kind))
281 .copied()
282 }
283
284 pub fn add_function(
288 &mut self,
289 qualified_name: &str,
290 span: Option<Span>,
291 is_async: bool,
292 is_unsafe: bool,
293 ) -> NodeId {
294 self.add_function_inner(qualified_name, span, is_async, is_unsafe, false)
299 }
300
301 fn add_function_inner(
308 &mut self,
309 qualified_name: &str,
310 span: Option<Span>,
311 is_async: bool,
312 is_unsafe: bool,
313 is_definition: bool,
314 ) -> NodeId {
315 self.add_node_internal(
316 qualified_name,
317 span,
318 NodeKind::Function,
319 &[("async", is_async), ("unsafe", is_unsafe)],
320 None,
321 None,
322 is_definition,
323 )
324 }
325
326 pub fn add_function_with_visibility(
330 &mut self,
331 qualified_name: &str,
332 span: Option<Span>,
333 is_async: bool,
334 is_unsafe: bool,
335 visibility: Option<&str>,
336 ) -> NodeId {
337 self.add_node_internal(
338 qualified_name,
339 span,
340 NodeKind::Function,
341 &[("async", is_async), ("unsafe", is_unsafe)],
342 visibility,
343 None,
344 true,
345 )
346 }
347
348 pub fn add_function_with_signature(
353 &mut self,
354 qualified_name: &str,
355 span: Option<Span>,
356 is_async: bool,
357 is_unsafe: bool,
358 visibility: Option<&str>,
359 signature: Option<&str>,
360 ) -> NodeId {
361 self.add_node_internal(
362 qualified_name,
363 span,
364 NodeKind::Function,
365 &[("async", is_async), ("unsafe", is_unsafe)],
366 visibility,
367 signature,
368 true,
369 )
370 }
371
372 pub fn add_method(
374 &mut self,
375 qualified_name: &str,
376 span: Option<Span>,
377 is_async: bool,
378 is_static: bool,
379 ) -> NodeId {
380 self.add_method_inner(qualified_name, span, is_async, is_static, false)
384 }
385
386 fn add_method_inner(
391 &mut self,
392 qualified_name: &str,
393 span: Option<Span>,
394 is_async: bool,
395 is_static: bool,
396 is_definition: bool,
397 ) -> NodeId {
398 self.add_node_internal(
399 qualified_name,
400 span,
401 NodeKind::Method,
402 &[("async", is_async), ("static", is_static)],
403 None,
404 None,
405 is_definition,
406 )
407 }
408
409 pub fn add_method_with_visibility(
411 &mut self,
412 qualified_name: &str,
413 span: Option<Span>,
414 is_async: bool,
415 is_static: bool,
416 visibility: Option<&str>,
417 ) -> NodeId {
418 self.add_node_internal(
419 qualified_name,
420 span,
421 NodeKind::Method,
422 &[("async", is_async), ("static", is_static)],
423 visibility,
424 None,
425 true,
426 )
427 }
428
429 pub fn add_method_with_signature(
434 &mut self,
435 qualified_name: &str,
436 span: Option<Span>,
437 is_async: bool,
438 is_static: bool,
439 visibility: Option<&str>,
440 signature: Option<&str>,
441 ) -> NodeId {
442 self.add_node_internal(
443 qualified_name,
444 span,
445 NodeKind::Method,
446 &[("async", is_async), ("static", is_static)],
447 visibility,
448 signature,
449 true,
450 )
451 }
452
453 pub fn add_class(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
461 self.add_node_internal(
462 qualified_name,
463 span,
464 NodeKind::Class,
465 &[],
466 None,
467 None,
468 false,
469 )
470 }
471
472 pub fn add_class_with_visibility(
474 &mut self,
475 qualified_name: &str,
476 span: Option<Span>,
477 visibility: Option<&str>,
478 ) -> NodeId {
479 self.add_node_internal(
480 qualified_name,
481 span,
482 NodeKind::Class,
483 &[],
484 visibility,
485 None,
486 true,
487 )
488 }
489
490 pub fn add_struct(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
498 self.add_node_internal(
499 qualified_name,
500 span,
501 NodeKind::Struct,
502 &[],
503 None,
504 None,
505 false,
506 )
507 }
508
509 pub fn add_struct_with_visibility(
511 &mut self,
512 qualified_name: &str,
513 span: Option<Span>,
514 visibility: Option<&str>,
515 ) -> NodeId {
516 self.add_node_internal(
517 qualified_name,
518 span,
519 NodeKind::Struct,
520 &[],
521 visibility,
522 None,
523 true,
524 )
525 }
526
527 pub fn add_module(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
534 self.add_node_internal(
535 qualified_name,
536 span,
537 NodeKind::Module,
538 &[],
539 None,
540 None,
541 false,
542 )
543 }
544
545 pub fn add_resource(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
547 self.add_node_internal(
548 qualified_name,
549 span,
550 NodeKind::Resource,
551 &[],
552 None,
553 None,
554 true,
555 )
556 }
557
558 pub fn add_endpoint(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
566 self.add_node_internal(
567 qualified_name,
568 span,
569 NodeKind::Endpoint,
570 &[],
571 None,
572 None,
573 true,
574 )
575 }
576
577 pub fn add_import(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
579 self.add_node_internal(
580 qualified_name,
581 span,
582 NodeKind::Import,
583 &[],
584 None,
585 None,
586 false,
587 )
588 }
589
590 pub fn add_verbatim_import(&mut self, name: &str, span: Option<Span>) -> NodeId {
596 self.add_node_verbatim(name, span, NodeKind::Import, &[], None, None, false)
597 }
598
599 pub fn add_variable(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
606 self.add_node_internal(
607 qualified_name,
608 span,
609 NodeKind::Variable,
610 &[],
611 None,
612 None,
613 false,
614 )
615 }
616
617 pub fn add_verbatim_variable(&mut self, name: &str, span: Option<Span>) -> NodeId {
622 self.add_node_verbatim(name, span, NodeKind::Variable, &[], None, None, false)
623 }
624
625 pub fn add_constant(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
627 self.add_node_internal(
628 qualified_name,
629 span,
630 NodeKind::Constant,
631 &[],
632 None,
633 None,
634 true,
635 )
636 }
637
638 pub fn add_constant_with_visibility(
640 &mut self,
641 qualified_name: &str,
642 span: Option<Span>,
643 visibility: Option<&str>,
644 ) -> NodeId {
645 self.add_node_internal(
646 qualified_name,
647 span,
648 NodeKind::Constant,
649 &[],
650 visibility,
651 None,
652 true,
653 )
654 }
655
656 pub fn add_constant_with_static_and_visibility(
658 &mut self,
659 qualified_name: &str,
660 span: Option<Span>,
661 is_static: bool,
662 visibility: Option<&str>,
663 ) -> NodeId {
664 let attrs: &[(&str, bool)] = if is_static { &[("static", true)] } else { &[] };
665 self.add_node_internal(
666 qualified_name,
667 span,
668 NodeKind::Constant,
669 attrs,
670 visibility,
671 None,
672 true,
673 )
674 }
675
676 pub fn add_constant_with_name_static_and_visibility(
682 &mut self,
683 name: &str,
684 qualified_name: &str,
685 span: Option<Span>,
686 is_static: bool,
687 visibility: Option<&str>,
688 ) -> NodeId {
689 let attrs: &[(&str, bool)] = if is_static { &[("static", true)] } else { &[] };
690 self.add_node_internal_with_name(
691 name,
692 qualified_name,
693 span,
694 NodeKind::Constant,
695 attrs,
696 visibility,
697 None,
698 true,
699 )
700 }
701
702 pub fn add_property_with_static_and_visibility(
704 &mut self,
705 qualified_name: &str,
706 span: Option<Span>,
707 is_static: bool,
708 visibility: Option<&str>,
709 ) -> NodeId {
710 let attrs: &[(&str, bool)] = if is_static { &[("static", true)] } else { &[] };
711 self.add_node_internal(
712 qualified_name,
713 span,
714 NodeKind::Property,
715 attrs,
716 visibility,
717 None,
718 true,
719 )
720 }
721
722 pub fn add_property_with_name_static_and_visibility(
728 &mut self,
729 name: &str,
730 qualified_name: &str,
731 span: Option<Span>,
732 is_static: bool,
733 visibility: Option<&str>,
734 ) -> NodeId {
735 let attrs: &[(&str, bool)] = if is_static { &[("static", true)] } else { &[] };
736 self.add_node_internal_with_name(
737 name,
738 qualified_name,
739 span,
740 NodeKind::Property,
741 attrs,
742 visibility,
743 None,
744 true,
745 )
746 }
747
748 pub fn add_enum(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
750 self.add_node_internal(qualified_name, span, NodeKind::Enum, &[], None, None, true)
751 }
752
753 pub fn add_enum_with_visibility(
755 &mut self,
756 qualified_name: &str,
757 span: Option<Span>,
758 visibility: Option<&str>,
759 ) -> NodeId {
760 self.add_node_internal(
761 qualified_name,
762 span,
763 NodeKind::Enum,
764 &[],
765 visibility,
766 None,
767 true,
768 )
769 }
770
771 pub fn add_interface(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
779 self.add_node_internal(
780 qualified_name,
781 span,
782 NodeKind::Interface,
783 &[],
784 None,
785 None,
786 false,
787 )
788 }
789
790 pub fn add_interface_with_visibility(
792 &mut self,
793 qualified_name: &str,
794 span: Option<Span>,
795 visibility: Option<&str>,
796 ) -> NodeId {
797 self.add_node_internal(
798 qualified_name,
799 span,
800 NodeKind::Interface,
801 &[],
802 visibility,
803 None,
804 true,
805 )
806 }
807
808 pub fn add_type(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
817 self.add_node_internal(qualified_name, span, NodeKind::Type, &[], None, None, false)
818 }
819
820 pub fn add_type_with_visibility(
822 &mut self,
823 qualified_name: &str,
824 span: Option<Span>,
825 visibility: Option<&str>,
826 ) -> NodeId {
827 self.add_node_internal(
828 qualified_name,
829 span,
830 NodeKind::Type,
831 &[],
832 visibility,
833 None,
834 true,
835 )
836 }
837
838 pub fn add_lifetime(&mut self, qualified_name: &str, span: Option<Span>) -> NodeId {
840 self.add_node_internal(
841 qualified_name,
842 span,
843 NodeKind::Lifetime,
844 &[],
845 None,
846 None,
847 true,
848 )
849 }
850
851 pub fn add_lifetime_constraint_edge(
853 &mut self,
854 source: NodeId,
855 target: NodeId,
856 constraint_kind: LifetimeConstraintKind,
857 ) {
858 self.staging.add_edge(
859 source,
860 target,
861 EdgeKind::LifetimeConstraint { constraint_kind },
862 self.file_id,
863 );
864 }
865
866 pub fn add_trait_method_binding_edge(
871 &mut self,
872 caller: NodeId,
873 callee: NodeId,
874 trait_name: &str,
875 impl_type: &str,
876 is_ambiguous: bool,
877 ) {
878 let trait_name_id = self.intern(trait_name);
879 let impl_type_id = self.intern(impl_type);
880 self.staging.add_edge(
881 caller,
882 callee,
883 EdgeKind::TraitMethodBinding {
884 trait_name: trait_name_id,
885 impl_type: impl_type_id,
886 is_ambiguous,
887 },
888 self.file_id,
889 );
890 }
891
892 pub fn add_macro_expansion_edge(
918 &mut self,
919 invocation: NodeId,
920 expansion: NodeId,
921 expansion_kind: MacroExpansionKind,
922 is_verified: bool,
923 ) {
924 self.staging.add_edge(
925 invocation,
926 expansion,
927 EdgeKind::MacroExpansion {
928 expansion_kind,
929 is_verified,
930 },
931 self.file_id,
932 );
933 }
934
935 pub fn add_node(&mut self, qualified_name: &str, span: Option<Span>, kind: NodeKind) -> NodeId {
942 self.add_node_internal(qualified_name, span, kind, &[], None, None, false)
943 }
944
945 pub fn add_node_with_visibility(
950 &mut self,
951 qualified_name: &str,
952 span: Option<Span>,
953 kind: NodeKind,
954 visibility: Option<&str>,
955 ) -> NodeId {
956 self.add_node_internal(qualified_name, span, kind, &[], visibility, None, false)
957 }
958
959 pub fn mark_definition(&mut self, node_id: NodeId) {
976 let update = NodeMetadataUpdate::new().mark_if(NodeMetadataFlag::Definition, true);
977 self.staging.update_node_entry(node_id, &update);
978 }
979
980 fn add_node_internal(
990 &mut self,
991 qualified_name: &str,
992 span: Option<Span>,
993 kind: NodeKind,
994 attributes: &[(&str, bool)],
995 visibility: Option<&str>,
996 signature: Option<&str>,
997 is_definition: bool,
998 ) -> NodeId {
999 let canonical_qualified_name =
1000 canonicalize_graph_qualified_name(self.language, qualified_name);
1001 let semantic_name = semantic_name_for_node_input(qualified_name, &canonical_qualified_name);
1002 self.add_node_internal_with_canonical_name(
1003 &semantic_name,
1004 &canonical_qualified_name,
1005 span,
1006 kind,
1007 attributes,
1008 visibility,
1009 signature,
1010 is_definition,
1011 )
1012 }
1013
1014 #[allow(clippy::too_many_arguments)] fn add_node_internal_with_name(
1016 &mut self,
1017 semantic_name: &str,
1018 qualified_name: &str,
1019 span: Option<Span>,
1020 kind: NodeKind,
1021 attributes: &[(&str, bool)],
1022 visibility: Option<&str>,
1023 signature: Option<&str>,
1024 is_definition: bool,
1025 ) -> NodeId {
1026 let canonical_qualified_name =
1027 canonicalize_graph_qualified_name(self.language, qualified_name);
1028 self.add_node_internal_with_canonical_name(
1029 semantic_name,
1030 &canonical_qualified_name,
1031 span,
1032 kind,
1033 attributes,
1034 visibility,
1035 signature,
1036 is_definition,
1037 )
1038 }
1039
1040 #[allow(clippy::too_many_arguments)] fn add_node_internal_with_canonical_name(
1042 &mut self,
1043 semantic_name: &str,
1044 canonical_qualified_name: &str,
1045 span: Option<Span>,
1046 kind: NodeKind,
1047 attributes: &[(&str, bool)],
1048 visibility: Option<&str>,
1049 signature: Option<&str>,
1050 is_definition: bool,
1051 ) -> NodeId {
1052 let mut is_async = false;
1053 let mut is_static = false;
1054 let mut is_unsafe = false;
1055 for &(key, value) in attributes {
1056 match key {
1057 "async" => is_async |= value,
1058 "static" => is_static |= value,
1059 "unsafe" => is_unsafe |= value,
1060 _ => {}
1061 }
1062 }
1063
1064 if let Some(&id) = self
1066 .node_cache
1067 .get(&(canonical_qualified_name.to_string(), kind))
1068 {
1069 let visibility_id = visibility.map(|vis| self.intern(vis));
1070 let signature_id = signature.map(|sig| self.intern(sig));
1071 let update = NodeMetadataUpdate::new()
1072 .with_optional_span(span)
1073 .mark_if(NodeMetadataFlag::Async, is_async)
1074 .mark_if(NodeMetadataFlag::Static, is_static)
1075 .mark_if(NodeMetadataFlag::Unsafe, is_unsafe)
1076 .mark_if(NodeMetadataFlag::Definition, is_definition)
1077 .with_optional_visibility(visibility_id)
1078 .with_optional_signature(signature_id);
1079 self.staging.update_node_entry(id, &update);
1080 return id;
1081 }
1082
1083 let name_id = self.intern(semantic_name);
1084
1085 let mut entry = NodeEntry::new(kind, name_id, self.file_id);
1087 entry.is_definition = is_definition;
1088 if semantic_name != canonical_qualified_name {
1089 let qualified_name_id = self.intern(canonical_qualified_name);
1090 entry = entry.with_qualified_name(qualified_name_id);
1091 }
1092
1093 if let Some(s) = span {
1095 let start_line = u32::try_from(s.start.line.saturating_add(1)).unwrap_or(u32::MAX);
1096 let start_column = u32::try_from(s.start.column).unwrap_or(u32::MAX);
1097 let end_line = u32::try_from(s.end.line.saturating_add(1)).unwrap_or(u32::MAX);
1098 let end_column = u32::try_from(s.end.column).unwrap_or(u32::MAX);
1099 entry = entry.with_location(start_line, start_column, end_line, end_column);
1100 }
1101
1102 if is_async {
1104 entry = entry.with_async(true);
1105 }
1106 if is_static {
1107 entry = entry.with_static(true);
1108 }
1109 if is_unsafe {
1110 entry = entry.with_unsafe(true);
1111 }
1112
1113 if let Some(vis) = visibility {
1115 let vis_id = self.intern(vis);
1116 entry = entry.with_visibility(vis_id);
1117 }
1118
1119 if let Some(sig) = signature {
1121 let sig_id = self.intern(sig);
1122 entry = entry.with_signature(sig_id);
1123 }
1124
1125 let node_id = self.staging.add_node(entry);
1127
1128 self.node_cache
1130 .insert((canonical_qualified_name.to_string(), kind), node_id);
1131
1132 node_id
1133 }
1134
1135 fn add_node_verbatim(
1136 &mut self,
1137 name: &str,
1138 span: Option<Span>,
1139 kind: NodeKind,
1140 attributes: &[(&str, bool)],
1141 visibility: Option<&str>,
1142 signature: Option<&str>,
1143 is_definition: bool,
1144 ) -> NodeId {
1145 let mut is_async = false;
1146 let mut is_static = false;
1147 let mut is_unsafe = false;
1148 for &(key, value) in attributes {
1149 match key {
1150 "async" => is_async |= value,
1151 "static" => is_static |= value,
1152 "unsafe" => is_unsafe |= value,
1153 _ => {}
1154 }
1155 }
1156
1157 if let Some(&id) = self.node_cache.get(&(name.to_string(), kind)) {
1158 let visibility_id = visibility.map(|vis| self.intern(vis));
1159 let signature_id = signature.map(|sig| self.intern(sig));
1160 let update = NodeMetadataUpdate::new()
1161 .with_optional_span(span)
1162 .mark_if(NodeMetadataFlag::Async, is_async)
1163 .mark_if(NodeMetadataFlag::Static, is_static)
1164 .mark_if(NodeMetadataFlag::Unsafe, is_unsafe)
1165 .mark_if(NodeMetadataFlag::Definition, is_definition)
1166 .with_optional_visibility(visibility_id)
1167 .with_optional_signature(signature_id);
1168 self.staging.update_node_entry(id, &update);
1169 return id;
1170 }
1171
1172 let name_id = self.intern(name);
1173 let mut entry = NodeEntry::new(kind, name_id, self.file_id);
1174 entry.is_definition = is_definition;
1175
1176 if let Some(s) = span {
1177 let start_line = u32::try_from(s.start.line.saturating_add(1)).unwrap_or(u32::MAX);
1178 let start_column = u32::try_from(s.start.column).unwrap_or(u32::MAX);
1179 let end_line = u32::try_from(s.end.line.saturating_add(1)).unwrap_or(u32::MAX);
1180 let end_column = u32::try_from(s.end.column).unwrap_or(u32::MAX);
1181 entry = entry.with_location(start_line, start_column, end_line, end_column);
1182 }
1183
1184 if is_async {
1185 entry = entry.with_async(true);
1186 }
1187 if is_static {
1188 entry = entry.with_static(true);
1189 }
1190 if is_unsafe {
1191 entry = entry.with_unsafe(true);
1192 }
1193
1194 if let Some(vis) = visibility {
1195 let vis_id = self.intern(vis);
1196 entry = entry.with_visibility(vis_id);
1197 }
1198 if let Some(sig) = signature {
1199 let sig_id = self.intern(sig);
1200 entry = entry.with_signature(sig_id);
1201 }
1202
1203 let node_id = self.staging.add_node(entry);
1204 self.node_cache.insert((name.to_string(), kind), node_id);
1205 node_id
1206 }
1207
1208 pub fn add_call_edge(&mut self, caller: NodeId, callee: NodeId) {
1210 self.add_call_edge_with_span(caller, callee, Vec::new());
1211 }
1212
1213 pub fn add_call_edge_with_span(
1223 &mut self,
1224 caller: NodeId,
1225 callee: NodeId,
1226 spans: Vec<crate::graph::node::Span>,
1227 ) {
1228 self.staging.add_edge_with_spans(
1229 caller,
1230 callee,
1231 EdgeKind::Calls {
1232 argument_count: 255,
1233 is_async: false,
1234 resolved_via: ResolvedVia::Direct,
1235 },
1236 self.file_id,
1237 spans,
1238 );
1239 }
1240
1241 pub fn add_call_edge_full(
1272 &mut self,
1273 caller: NodeId,
1274 callee: NodeId,
1275 argument_count: u8,
1276 is_async: bool,
1277 ) {
1278 self.staging.add_edge(
1279 caller,
1280 callee,
1281 EdgeKind::Calls {
1282 argument_count,
1283 is_async,
1284 resolved_via: ResolvedVia::Direct,
1285 },
1286 self.file_id,
1287 );
1288 }
1289
1290 pub fn add_call_edge_full_with_span(
1295 &mut self,
1296 caller: NodeId,
1297 callee: NodeId,
1298 argument_count: u8,
1299 is_async: bool,
1300 spans: Vec<crate::graph::node::Span>,
1301 ) {
1302 self.staging.add_edge_with_spans(
1303 caller,
1304 callee,
1305 EdgeKind::Calls {
1306 argument_count,
1307 is_async,
1308 resolved_via: ResolvedVia::Direct,
1309 },
1310 self.file_id,
1311 spans,
1312 );
1313 }
1314
1315 pub fn add_channel(
1325 &mut self,
1326 qualified_name: &str,
1327 span: Option<Span>,
1328 _buffer_kind: ChannelBufferKind,
1329 _capacity: Option<u32>,
1330 ) -> NodeId {
1331 self.add_node(qualified_name, span, NodeKind::Channel)
1332 }
1333
1334 pub fn add_channel_peer_edge_with_span(
1337 &mut self,
1338 op_site: NodeId,
1339 channel: NodeId,
1340 direction: ChannelPeerDirection,
1341 buffer_kind: ChannelBufferKind,
1342 span: Span,
1343 ) {
1344 self.staging.add_edge_with_spans(
1345 op_site,
1346 channel,
1347 EdgeKind::ChannelPeer {
1348 direction,
1349 buffer_kind,
1350 },
1351 self.file_id,
1352 vec![span],
1353 );
1354 }
1355
1356 pub fn add_instantiates_edge_with_span(
1364 &mut self,
1365 call_site: NodeId,
1366 target: NodeId,
1367 type_args: smallvec::SmallVec<[TypeArg; 4]>,
1368 inference_kind: InferenceKind,
1369 span: Span,
1370 ) {
1371 self.staging.add_edge_with_spans(
1372 call_site,
1373 target,
1374 EdgeKind::Instantiates {
1375 type_args,
1376 inference_kind,
1377 },
1378 self.file_id,
1379 vec![span],
1380 );
1381 }
1382
1383 pub fn add_table_read_edge_with_span(
1385 &mut self,
1386 reader: NodeId,
1387 table: NodeId,
1388 table_name: &str,
1389 schema: Option<&str>,
1390 spans: Vec<crate::graph::node::Span>,
1391 ) {
1392 let table_name_id = self.intern(table_name);
1393 let schema_id = schema.map(|s| self.intern(s));
1394 self.staging.add_edge_with_spans(
1395 reader,
1396 table,
1397 EdgeKind::TableRead {
1398 table_name: table_name_id,
1399 schema: schema_id,
1400 },
1401 self.file_id,
1402 spans,
1403 );
1404 }
1405
1406 pub fn add_table_write_edge_with_span(
1408 &mut self,
1409 writer: NodeId,
1410 table: NodeId,
1411 table_name: &str,
1412 schema: Option<&str>,
1413 operation: TableWriteOp,
1414 spans: Vec<crate::graph::node::Span>,
1415 ) {
1416 let table_name_id = self.intern(table_name);
1417 let schema_id = schema.map(|s| self.intern(s));
1418 self.staging.add_edge_with_spans(
1419 writer,
1420 table,
1421 EdgeKind::TableWrite {
1422 table_name: table_name_id,
1423 schema: schema_id,
1424 operation,
1425 },
1426 self.file_id,
1427 spans,
1428 );
1429 }
1430
1431 pub fn add_triggered_by_edge_with_span(
1435 &mut self,
1436 trigger: NodeId,
1437 table: NodeId,
1438 trigger_name: &str,
1439 schema: Option<&str>,
1440 spans: Vec<crate::graph::node::Span>,
1441 ) {
1442 let trigger_name_id = self.intern(trigger_name);
1443 let schema_id = schema.map(|s| self.intern(s));
1444 self.staging.add_edge_with_spans(
1445 trigger,
1446 table,
1447 EdgeKind::TriggeredBy {
1448 trigger_name: trigger_name_id,
1449 schema: schema_id,
1450 },
1451 self.file_id,
1452 spans,
1453 );
1454 }
1455
1456 pub fn add_import_edge(&mut self, importer: NodeId, imported: NodeId) {
1462 self.staging.add_edge(
1463 importer,
1464 imported,
1465 EdgeKind::Imports {
1466 alias: None,
1467 is_wildcard: false,
1468 },
1469 self.file_id,
1470 );
1471 }
1472
1473 pub fn add_import_edge_full(
1505 &mut self,
1506 importer: NodeId,
1507 imported: NodeId,
1508 alias: Option<&str>,
1509 is_wildcard: bool,
1510 ) {
1511 let alias_id = alias.map(|s| self.intern(s));
1512 self.staging.add_edge(
1513 importer,
1514 imported,
1515 EdgeKind::Imports {
1516 alias: alias_id,
1517 is_wildcard,
1518 },
1519 self.file_id,
1520 );
1521 }
1522
1523 pub fn add_export_edge(&mut self, module: NodeId, exported: NodeId) {
1529 self.staging.add_edge(
1530 module,
1531 exported,
1532 EdgeKind::Exports {
1533 kind: ExportKind::Direct,
1534 alias: None,
1535 },
1536 self.file_id,
1537 );
1538 }
1539
1540 pub fn add_export_edge_full(
1582 &mut self,
1583 module: NodeId,
1584 exported: NodeId,
1585 kind: ExportKind,
1586 alias: Option<&str>,
1587 ) {
1588 let alias_id = alias.map(|s| self.intern(s));
1589 self.staging.add_edge(
1590 module,
1591 exported,
1592 EdgeKind::Exports {
1593 kind,
1594 alias: alias_id,
1595 },
1596 self.file_id,
1597 );
1598 }
1599
1600 pub fn add_reference_edge(&mut self, from: NodeId, to: NodeId) {
1602 self.staging
1603 .add_edge(from, to, EdgeKind::References, self.file_id);
1604 }
1605
1606 pub fn add_defines_edge(&mut self, parent: NodeId, child: NodeId) {
1608 self.staging
1609 .add_edge(parent, child, EdgeKind::Defines, self.file_id);
1610 }
1611
1612 pub fn add_typeof_edge(&mut self, source: NodeId, target: NodeId) {
1617 self.add_typeof_edge_with_context(source, target, None, None, None);
1618 }
1619
1620 pub fn add_typeof_edge_with_context(
1659 &mut self,
1660 source: NodeId,
1661 target: NodeId,
1662 context: Option<TypeOfContext>,
1663 index: Option<u16>,
1664 name: Option<&str>,
1665 ) {
1666 let name_id = name.map(|n| self.intern(n));
1667 self.staging.add_edge(
1668 source,
1669 target,
1670 EdgeKind::TypeOf {
1671 context,
1672 index,
1673 name: name_id,
1674 },
1675 self.file_id,
1676 );
1677 }
1678
1679 pub fn add_implements_edge(&mut self, implementor: NodeId, interface: NodeId) {
1681 self.staging
1682 .add_edge(implementor, interface, EdgeKind::Implements, self.file_id);
1683 }
1684
1685 pub fn add_inherits_edge(&mut self, child: NodeId, parent: NodeId) {
1687 self.staging
1688 .add_edge(child, parent, EdgeKind::Inherits, self.file_id);
1689 }
1690
1691 pub fn add_wraps_edge(
1707 &mut self,
1708 source: NodeId,
1709 target: NodeId,
1710 kind: WrapKind,
1711 chain_position: Option<u16>,
1712 span: Option<Span>,
1713 ) {
1714 let spans = span.map(|s| vec![s]).unwrap_or_default();
1715 self.staging.add_edge_with_spans(
1716 source,
1717 target,
1718 EdgeKind::Wraps {
1719 kind,
1720 chain_position,
1721 },
1722 self.file_id,
1723 spans,
1724 );
1725 }
1726
1727 pub fn add_contains_edge(&mut self, parent: NodeId, child: NodeId) {
1729 self.staging
1730 .add_edge(parent, child, EdgeKind::Contains, self.file_id);
1731 }
1732
1733 pub fn add_webassembly_edge(&mut self, caller: NodeId, wasm_target: NodeId) {
1740 self.staging
1741 .add_edge(caller, wasm_target, EdgeKind::WebAssemblyCall, self.file_id);
1742 }
1743
1744 pub fn add_ffi_edge(&mut self, caller: NodeId, ffi_target: NodeId, convention: FfiConvention) {
1752 self.staging.add_edge(
1753 caller,
1754 ffi_target,
1755 EdgeKind::FfiCall { convention },
1756 self.file_id,
1757 );
1758 }
1759
1760 pub fn add_http_request_edge(
1764 &mut self,
1765 caller: NodeId,
1766 target: NodeId,
1767 method: HttpMethod,
1768 url: Option<&str>,
1769 ) {
1770 let url_id = url.map(|value| self.intern(value));
1771 self.staging.add_edge(
1772 caller,
1773 target,
1774 EdgeKind::HttpRequest {
1775 method,
1776 url: url_id,
1777 },
1778 self.file_id,
1779 );
1780 }
1781
1782 fn reuse_across_call_compatible_kinds(
1789 &self,
1790 canonical: &str,
1791 exclude: NodeKind,
1792 ) -> Option<NodeId> {
1793 for &kind in CALL_COMPATIBLE_KINDS {
1794 if kind == exclude {
1795 continue;
1796 }
1797 if let Some(&id) = self.node_cache.get(&(canonical.to_string(), kind)) {
1798 return Some(id);
1799 }
1800 }
1801 None
1802 }
1803
1804 pub fn ensure_callee(
1815 &mut self,
1816 qualified_name: &str,
1817 call_site_span: Span,
1818 kind_hint: CalleeKindHint,
1819 ) -> NodeId {
1820 let canonical = canonicalize_graph_qualified_name(self.language, qualified_name);
1821 let target_kind = kind_hint.to_node_kind();
1822
1823 if let Some(&id) = self.node_cache.get(&(canonical.clone(), target_kind)) {
1825 return id;
1826 }
1827 if let Some(id) = self.reuse_across_call_compatible_kinds(&canonical, target_kind) {
1829 return id;
1830 }
1831 self.add_node_internal(
1834 qualified_name,
1835 Some(call_site_span),
1836 target_kind,
1837 &[],
1838 None,
1839 None,
1840 false,
1841 )
1842 }
1843
1844 pub fn ensure_function(
1856 &mut self,
1857 qualified_name: &str,
1858 span: Option<Span>,
1859 is_async: bool,
1860 is_unsafe: bool,
1861 ) -> NodeId {
1862 let canonical = canonicalize_graph_qualified_name(self.language, qualified_name);
1863 if let Some(id) = self.reuse_across_call_compatible_kinds(&canonical, NodeKind::Function) {
1864 return id;
1865 }
1866 self.add_function_inner(qualified_name, span, is_async, is_unsafe, false)
1868 }
1869
1870 pub fn ensure_method(
1877 &mut self,
1878 qualified_name: &str,
1879 span: Option<Span>,
1880 is_async: bool,
1881 is_static: bool,
1882 ) -> NodeId {
1883 let canonical = canonicalize_graph_qualified_name(self.language, qualified_name);
1884 if let Some(id) = self.reuse_across_call_compatible_kinds(&canonical, NodeKind::Method) {
1885 return id;
1886 }
1887 self.add_method_inner(qualified_name, span, is_async, is_static, false)
1889 }
1890
1891 #[must_use]
1893 pub fn stats(&self) -> HelperStats {
1894 let staging_stats = self.staging.stats();
1895 HelperStats {
1896 strings_interned: self.string_cache.len(),
1897 nodes_created: self.node_cache.len(),
1898 nodes_staged: staging_stats.nodes_staged,
1899 edges_staged: staging_stats.edges_staged,
1900 }
1901 }
1902
1903 pub fn mark_function_address_taken_by_name(&mut self, target_fn_name: &str) {
1930 let id = self.intern(target_fn_name);
1931 self.staging
1932 .c_indirect_mut()
1933 .pending_address_taken_names
1934 .push(id);
1935 }
1936
1937 pub fn set_local_scope_index(&mut self, index: LocalScopeIndex) {
1944 self.staging.c_indirect_mut().local_scope_index = Some(index);
1945 }
1946
1947 pub fn push_indirect_callsite(
1954 &mut self,
1955 caller_qualified_name: &str,
1956 use_span: (usize, usize),
1957 shape: IndirectShape,
1958 argument_count: u32,
1959 is_async: bool,
1960 ) {
1961 self.staging
1962 .c_indirect_mut()
1963 .pending_indirect_callsites
1964 .push(PendingIndirectCallsite {
1965 caller_qualified_name: caller_qualified_name.to_string(),
1966 use_span,
1967 shape,
1968 argument_count,
1969 is_async,
1970 });
1971 }
1972
1973 pub fn push_binding(
1981 &mut self,
1982 struct_tag: &str,
1983 field_name: &str,
1984 instance_name: &str,
1985 target_fn_name: &str,
1986 site_kind: BindingSiteKind,
1987 ) {
1988 self.staging
1989 .c_indirect_mut()
1990 .pending_bindings
1991 .push(PendingBinding {
1992 struct_tag: struct_tag.to_string(),
1993 field_name: field_name.to_string(),
1994 instance_name: instance_name.to_string(),
1995 target_fn_name: target_fn_name.to_string(),
1996 site_kind,
1997 });
1998 }
1999
2000 pub fn push_struct_field_fnptr_signature(
2007 &mut self,
2008 struct_tag: &str,
2009 field_name: &str,
2010 signature: &str,
2011 ) {
2012 self.staging
2013 .c_indirect_mut()
2014 .pending_struct_field_signatures
2015 .push((
2016 struct_tag.to_string(),
2017 field_name.to_string(),
2018 signature.to_string(),
2019 ));
2020 }
2021
2022 #[must_use]
2031 pub fn c_indirect(&self) -> Option<&CIndirectStagingPayload> {
2032 self.staging.c_indirect()
2033 }
2034}
2035
2036fn semantic_name_for_node_input(original: &str, canonical: &str) -> String {
2037 if original.contains('/') {
2038 return original.to_string();
2039 }
2040
2041 canonical
2042 .rsplit("::")
2043 .next()
2044 .map_or_else(|| original.to_string(), ToString::to_string)
2045}
2046
2047#[derive(Debug, Clone, Default)]
2049pub struct HelperStats {
2050 pub strings_interned: usize,
2052 pub nodes_created: usize,
2054 pub nodes_staged: usize,
2056 pub edges_staged: usize,
2058}
2059
2060#[cfg(test)]
2061mod tests {
2062 use super::*;
2063 use crate::graph::node::Position;
2064 use crate::graph::unified::build::staging::StagingOp;
2065 use std::path::PathBuf;
2066
2067 #[test]
2068 fn test_helper_add_function() {
2069 let mut staging = StagingGraph::new();
2070 let file = PathBuf::from("test.rs");
2071 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2072
2073 let node_id = helper.add_function("main", None, false, false);
2074 assert!(!node_id.is_invalid());
2075 assert_eq!(helper.stats().nodes_created, 1);
2076 }
2077
2078 #[test]
2079 fn test_helper_deduplication() {
2080 let mut staging = StagingGraph::new();
2081 let file = PathBuf::from("test.rs");
2082 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2083
2084 let id1 = helper.add_function("main", None, false, false);
2085 let id2 = helper.add_function("main", None, false, false);
2086
2087 assert_eq!(id1, id2, "Same function should return same NodeId");
2088 assert_eq!(
2089 helper.stats().nodes_created,
2090 1,
2091 "Should only create one node"
2092 );
2093 }
2094
2095 #[test]
2096 fn test_helper_string_interning() {
2097 let mut staging = StagingGraph::new();
2098 let file = PathBuf::from("test.rs");
2099 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2100
2101 let s1 = helper.intern("hello");
2102 let s2 = helper.intern("world");
2103 let s3 = helper.intern("hello"); assert_ne!(s1, s2, "Different strings should have different IDs");
2106 assert_eq!(s1, s3, "Same string should return same ID");
2107 assert_eq!(helper.stats().strings_interned, 2);
2108 }
2109
2110 #[test]
2111 fn test_helper_add_call_edge() {
2112 let mut staging = StagingGraph::new();
2113 let file = PathBuf::from("test.rs");
2114 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2115
2116 let main_id = helper.add_function("main", None, false, false);
2117 let helper_id = helper.add_function("helper", None, false, false);
2118
2119 helper.add_call_edge(main_id, helper_id);
2120
2121 assert_eq!(helper.stats().edges_staged, 1);
2122 let edge_kind = staging.operations().iter().find_map(|op| {
2123 if let StagingOp::AddEdge { kind, .. } = op {
2124 Some(kind)
2125 } else {
2126 None
2127 }
2128 });
2129 match edge_kind {
2130 Some(EdgeKind::Calls {
2131 argument_count,
2132 is_async,
2133 ..
2134 }) => {
2135 assert_eq!(*argument_count, 255);
2136 assert!(!*is_async);
2137 }
2138 _ => panic!("Expected Calls edge"),
2139 }
2140 }
2141
2142 #[test]
2143 fn test_helper_multiple_node_kinds() {
2144 let mut staging = StagingGraph::new();
2145 let file = PathBuf::from("test.py");
2146 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Python);
2147
2148 let _class_id = helper.add_class("MyClass", None);
2149 let _method_id = helper.add_method("MyClass.my_method", None, false, false);
2150 let _func_id = helper.add_function("standalone_func", None, true, false);
2151
2152 assert_eq!(helper.stats().nodes_created, 3);
2153 }
2154
2155 #[test]
2156 fn test_helper_canonicalizes_language_native_qualified_names() {
2157 let mut staging = StagingGraph::new();
2158 let file = PathBuf::from("test.py");
2159 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Python);
2160
2161 let _method_id = helper.add_method("pkg.module.run", None, false, false);
2162
2163 let add_node_op = staging
2164 .operations()
2165 .iter()
2166 .find(|op| matches!(op, StagingOp::AddNode { .. }))
2167 .expect("Expected AddNode operation");
2168
2169 if let StagingOp::AddNode { entry, .. } = add_node_op {
2170 assert_eq!(staging.resolve_local_string(entry.name), Some("run"));
2171 assert_eq!(
2172 staging.resolve_node_name(entry),
2173 Some("pkg::module::run"),
2174 "expected GraphBuildHelper to canonicalize Python dotted qualified names"
2175 );
2176 }
2177 }
2178
2179 #[test]
2180 fn test_helper_preserves_path_qualified_names() {
2181 let mut staging = StagingGraph::new();
2182 let file = PathBuf::from("test.js");
2183 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2184
2185 let _func_id = helper.add_function("frontend/api.js::fetchUsers", None, false, false);
2186
2187 let add_node_op = staging
2188 .operations()
2189 .iter()
2190 .find(|op| matches!(op, StagingOp::AddNode { .. }))
2191 .expect("Expected AddNode operation");
2192
2193 if let StagingOp::AddNode { entry, .. } = add_node_op {
2194 assert_eq!(
2195 staging.resolve_local_string(entry.name),
2196 Some("frontend/api.js::fetchUsers")
2197 );
2198 assert_eq!(
2199 staging.resolve_node_name(entry),
2200 Some("frontend/api.js::fetchUsers"),
2201 "expected path-qualified names to remain unchanged"
2202 );
2203 }
2204 }
2205
2206 #[test]
2207 fn test_helper_verbatim_import_preserves_resource_name() {
2208 let mut staging = StagingGraph::new();
2209 let file = PathBuf::from("index.html");
2210 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Html);
2211
2212 let _import_id = helper.add_verbatim_import("styles.css", None);
2213
2214 let add_node_op = staging
2215 .operations()
2216 .iter()
2217 .find(|op| matches!(op, StagingOp::AddNode { .. }))
2218 .expect("Expected AddNode operation");
2219
2220 if let StagingOp::AddNode { entry, .. } = add_node_op {
2221 assert_eq!(staging.resolve_local_string(entry.name), Some("styles.css"));
2222 assert_eq!(entry.qualified_name, None);
2223 assert_eq!(
2224 staging.resolve_node_name(entry),
2225 Some("styles.css"),
2226 "expected verbatim resource imports to preserve their literal identity"
2227 );
2228 }
2229 }
2230
2231 #[test]
2232 fn test_helper_verbatim_variable_preserves_resource_name() {
2233 let mut staging = StagingGraph::new();
2234 let file = PathBuf::from("index.html");
2235 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Html);
2236
2237 let _variable_id = helper.add_verbatim_variable("/assets/logo.icon.png", None);
2238
2239 let add_node_op = staging
2240 .operations()
2241 .iter()
2242 .find(|op| matches!(op, StagingOp::AddNode { .. }))
2243 .expect("Expected AddNode operation");
2244
2245 if let StagingOp::AddNode { entry, .. } = add_node_op {
2246 assert_eq!(
2247 staging.resolve_local_string(entry.name),
2248 Some("/assets/logo.icon.png")
2249 );
2250 assert_eq!(entry.qualified_name, None);
2251 assert_eq!(
2252 staging.resolve_node_name(entry),
2253 Some("/assets/logo.icon.png"),
2254 "expected verbatim resource variables to preserve their literal identity"
2255 );
2256 }
2257 }
2258
2259 #[test]
2260 fn test_helper_ensure_function() {
2261 let mut staging = StagingGraph::new();
2262 let file = PathBuf::from("test.rs");
2263 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2264
2265 let id1 = helper.ensure_function("foo", None, false, false);
2266 let id2 = helper.ensure_function("foo", None, true, false); assert_eq!(id1, id2, "ensure_function should be idempotent by name");
2269 }
2270
2271 #[test]
2272 fn test_helper_with_span() {
2273 let mut staging = StagingGraph::new();
2274 let file = PathBuf::from("test.rs");
2275 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2276
2277 let span = Span {
2278 start: Position {
2279 line: 10,
2280 column: 0,
2281 },
2282 end: Position {
2283 line: 15,
2284 column: 1,
2285 },
2286 };
2287
2288 let node_id = helper.add_function("main", Some(span), false, false);
2289 assert!(!node_id.is_invalid());
2290 }
2291
2292 #[test]
2293 fn test_helper_add_call_edge_full() {
2294 let mut staging = StagingGraph::new();
2295 let file = PathBuf::from("test.rs");
2296 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2297
2298 let caller_id = helper.add_function("caller", None, false, false);
2299 let callee_id = helper.add_function("callee", None, false, false);
2300
2301 helper.add_call_edge_full(caller_id, callee_id, 3, true);
2303
2304 assert_eq!(helper.stats().edges_staged, 1);
2305
2306 let edges = staging.operations();
2308 let call_edge = edges.iter().find(|op| {
2309 matches!(
2310 op,
2311 StagingOp::AddEdge {
2312 kind: EdgeKind::Calls { .. },
2313 ..
2314 }
2315 )
2316 });
2317
2318 assert!(call_edge.is_some());
2319 if let StagingOp::AddEdge {
2320 kind:
2321 EdgeKind::Calls {
2322 argument_count,
2323 is_async,
2324 ..
2325 },
2326 ..
2327 } = call_edge.unwrap()
2328 {
2329 assert_eq!(*argument_count, 3);
2330 assert!(*is_async);
2331 }
2332 }
2333
2334 #[test]
2335 fn test_helper_add_import_edge_full() {
2336 let mut staging = StagingGraph::new();
2337 let file = PathBuf::from("test.js");
2338 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2339
2340 let module_id = helper.add_module("app", None);
2341 let imported_id = helper.add_function("utils", None, false, false);
2342
2343 helper.add_import_edge_full(module_id, imported_id, Some("helpers"), false);
2345
2346 assert_eq!(helper.stats().edges_staged, 1);
2347
2348 let edges = staging.operations();
2350 let import_edge = edges.iter().find(|op| {
2351 matches!(
2352 op,
2353 StagingOp::AddEdge {
2354 kind: EdgeKind::Imports { .. },
2355 ..
2356 }
2357 )
2358 });
2359
2360 assert!(import_edge.is_some());
2361 if let StagingOp::AddEdge {
2362 kind: EdgeKind::Imports { alias, is_wildcard },
2363 ..
2364 } = import_edge.unwrap()
2365 {
2366 assert!(alias.is_some(), "Alias should be present");
2367 assert!(!*is_wildcard);
2368 }
2369 }
2370
2371 #[test]
2372 fn test_helper_add_import_edge_wildcard() {
2373 let mut staging = StagingGraph::new();
2374 let file = PathBuf::from("test.js");
2375 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2376
2377 let module_id = helper.add_module("app", None);
2378 let imported_id = helper.add_module("lodash", None);
2379
2380 helper.add_import_edge_full(module_id, imported_id, None, true);
2382
2383 let edges = staging.operations();
2384 let import_edge = edges.iter().find(|op| {
2385 matches!(
2386 op,
2387 StagingOp::AddEdge {
2388 kind: EdgeKind::Imports { .. },
2389 ..
2390 }
2391 )
2392 });
2393
2394 if let StagingOp::AddEdge {
2395 kind: EdgeKind::Imports { alias, is_wildcard },
2396 ..
2397 } = import_edge.unwrap()
2398 {
2399 assert!(alias.is_none());
2400 assert!(*is_wildcard);
2401 }
2402 }
2403
2404 #[test]
2405 fn test_helper_add_export_edge_full() {
2406 let mut staging = StagingGraph::new();
2407 let file = PathBuf::from("test.js");
2408 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2409
2410 let module_id = helper.add_module("app", None);
2411 let component_id = helper.add_class("MyComponent", None);
2412
2413 helper.add_export_edge_full(module_id, component_id, ExportKind::Default, None);
2415
2416 assert_eq!(helper.stats().edges_staged, 1);
2417
2418 let edges = staging.operations();
2419 let export_edge = edges.iter().find(|op| {
2420 matches!(
2421 op,
2422 StagingOp::AddEdge {
2423 kind: EdgeKind::Exports { .. },
2424 ..
2425 }
2426 )
2427 });
2428
2429 assert!(export_edge.is_some());
2430 if let StagingOp::AddEdge {
2431 kind: EdgeKind::Exports { kind, alias },
2432 ..
2433 } = export_edge.unwrap()
2434 {
2435 assert_eq!(*kind, ExportKind::Default);
2436 assert!(alias.is_none());
2437 }
2438 }
2439
2440 #[test]
2441 fn test_helper_add_export_edge_with_alias() {
2442 let mut staging = StagingGraph::new();
2443 let file = PathBuf::from("test.js");
2444 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2445
2446 let module_id = helper.add_module("app", None);
2447 let helper_fn_id = helper.add_function("internalHelper", None, false, false);
2448
2449 helper.add_export_edge_full(module_id, helper_fn_id, ExportKind::Direct, Some("helper"));
2451
2452 let edges = staging.operations();
2453 let export_edge = edges.iter().find(|op| {
2454 matches!(
2455 op,
2456 StagingOp::AddEdge {
2457 kind: EdgeKind::Exports { .. },
2458 ..
2459 }
2460 )
2461 });
2462
2463 if let StagingOp::AddEdge {
2464 kind: EdgeKind::Exports { kind, alias },
2465 ..
2466 } = export_edge.unwrap()
2467 {
2468 assert_eq!(*kind, ExportKind::Direct);
2469 assert!(alias.is_some(), "Alias should be present");
2470 }
2471 }
2472
2473 #[test]
2474 fn test_helper_add_export_edge_reexport() {
2475 let mut staging = StagingGraph::new();
2476 let file = PathBuf::from("index.js");
2477 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::JavaScript);
2478
2479 let module_id = helper.add_module("index", None);
2480 let utils_id = helper.add_module("utils", None);
2481
2482 helper.add_export_edge_full(module_id, utils_id, ExportKind::Namespace, Some("utils"));
2484
2485 let edges = staging.operations();
2486 let export_edge = edges.iter().find(|op| {
2487 matches!(
2488 op,
2489 StagingOp::AddEdge {
2490 kind: EdgeKind::Exports { .. },
2491 ..
2492 }
2493 )
2494 });
2495
2496 if let StagingOp::AddEdge {
2497 kind: EdgeKind::Exports { kind, alias },
2498 ..
2499 } = export_edge.unwrap()
2500 {
2501 assert_eq!(*kind, ExportKind::Namespace);
2502 assert!(alias.is_some());
2503 }
2504 }
2505
2506 #[test]
2507 fn test_helper_add_call_edge_full_with_span() {
2508 let mut staging = StagingGraph::new();
2509 let file = PathBuf::from("test.rs");
2510 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2511
2512 let caller_id = helper.add_function("caller", None, false, false);
2513 let callee_id = helper.add_function("callee", None, false, false);
2514
2515 let span = Span {
2516 start: Position { line: 5, column: 4 },
2517 end: Position {
2518 line: 5,
2519 column: 20,
2520 },
2521 };
2522
2523 helper.add_call_edge_full_with_span(caller_id, callee_id, 2, false, vec![span]);
2524
2525 let edges = staging.operations();
2526 let call_edge = edges.iter().find(|op| {
2527 matches!(
2528 op,
2529 StagingOp::AddEdge {
2530 kind: EdgeKind::Calls { .. },
2531 ..
2532 }
2533 )
2534 });
2535
2536 if let StagingOp::AddEdge {
2537 kind:
2538 EdgeKind::Calls {
2539 argument_count,
2540 is_async,
2541 ..
2542 },
2543 spans: edge_spans,
2544 ..
2545 } = call_edge.unwrap()
2546 {
2547 assert_eq!(*argument_count, 2);
2548 assert!(!*is_async);
2549 assert!(!edge_spans.is_empty());
2550 }
2551 }
2552
2553 #[test]
2554 fn test_helper_add_function_with_async_attribute() {
2555 let mut staging = StagingGraph::new();
2556 let file = PathBuf::from("test.kt");
2557 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Kotlin);
2558
2559 let _func_id = helper.add_function("fetchData", None, true, false);
2561
2562 let ops = staging.operations();
2564 let add_node_op = ops
2565 .iter()
2566 .find(|op| matches!(op, StagingOp::AddNode { .. }));
2567
2568 assert!(add_node_op.is_some(), "Expected AddNode operation");
2569 if let StagingOp::AddNode { entry, .. } = add_node_op.unwrap() {
2570 assert!(
2571 entry.is_async,
2572 "Expected is_async=true for suspend function, got is_async=false"
2573 );
2574 }
2575 }
2576
2577 #[test]
2578 fn test_helper_add_method_with_static_attribute() {
2579 let mut staging = StagingGraph::new();
2580 let file = PathBuf::from("test.java");
2581 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Java);
2582
2583 let _method_id = helper.add_method("MyClass.staticMethod", None, false, true);
2585
2586 let ops = staging.operations();
2588 let add_node_op = ops
2589 .iter()
2590 .find(|op| matches!(op, StagingOp::AddNode { .. }));
2591
2592 assert!(add_node_op.is_some(), "Expected AddNode operation");
2593 if let StagingOp::AddNode { entry, .. } = add_node_op.unwrap() {
2594 assert!(
2595 entry.is_static,
2596 "Expected is_static=true for static method, got is_static=false"
2597 );
2598 }
2599 }
2600
2601 #[test]
2602 fn test_helper_add_function_without_attributes() {
2603 let mut staging = StagingGraph::new();
2604 let file = PathBuf::from("test.rs");
2605 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2606
2607 let _func_id = helper.add_function("regular_function", None, false, false);
2609
2610 let ops = staging.operations();
2612 let add_node_op = ops
2613 .iter()
2614 .find(|op| matches!(op, StagingOp::AddNode { .. }));
2615
2616 assert!(add_node_op.is_some(), "Expected AddNode operation");
2617 if let StagingOp::AddNode { entry, .. } = add_node_op.unwrap() {
2618 assert!(
2619 !entry.is_async,
2620 "Expected is_async=false for regular function"
2621 );
2622 assert!(
2623 !entry.is_static,
2624 "Expected is_static=false for regular function"
2625 );
2626 }
2627 }
2628
2629 #[test]
2630 fn test_helper_add_method_with_both_attributes() {
2631 let mut staging = StagingGraph::new();
2632 let file = PathBuf::from("test.kt");
2633 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Kotlin);
2634
2635 let _method_id = helper.add_method("Service.asyncStaticMethod", None, true, true);
2637
2638 let ops = staging.operations();
2640 let add_node_op = ops
2641 .iter()
2642 .find(|op| matches!(op, StagingOp::AddNode { .. }));
2643
2644 assert!(add_node_op.is_some(), "Expected AddNode operation");
2645 if let StagingOp::AddNode { entry, .. } = add_node_op.unwrap() {
2646 assert!(entry.is_async, "Expected is_async=true for async method");
2647 assert!(entry.is_static, "Expected is_static=true for static method");
2648 }
2649 }
2650
2651 #[test]
2652 fn test_helper_add_function_with_unsafe_attribute() {
2653 let mut staging = StagingGraph::new();
2654 let file = PathBuf::from("test.rs");
2655 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
2656
2657 let _func_id = helper.add_function("unsafe_function", None, false, true);
2659
2660 let ops = staging.operations();
2662 let add_node_op = ops
2663 .iter()
2664 .find(|op| matches!(op, StagingOp::AddNode { .. }));
2665
2666 assert!(add_node_op.is_some(), "Expected AddNode operation");
2667 if let StagingOp::AddNode { entry, .. } = add_node_op.unwrap() {
2668 assert!(
2669 entry.is_unsafe,
2670 "Expected is_unsafe=true for unsafe function, got is_unsafe={}",
2671 entry.is_unsafe
2672 );
2673 }
2674 }
2675
2676 #[test]
2681 fn test_ensure_function_reuses_existing_method_node() {
2682 let mut staging = StagingGraph::new();
2683 let file = PathBuf::from("test.ts");
2684 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::TypeScript);
2685
2686 let span = Span::new(
2687 Position { line: 5, column: 4 },
2688 Position {
2689 line: 10,
2690 column: 5,
2691 },
2692 );
2693
2694 let method_id = helper.add_method("MyClass.doWork", Some(span), true, false);
2696
2697 let reused_id = helper.ensure_function("MyClass.doWork", None, true, false);
2699
2700 assert_eq!(
2701 method_id, reused_id,
2702 "ensure_function should reuse the existing Method node"
2703 );
2704 assert_eq!(
2705 helper.stats().nodes_created,
2706 1,
2707 "Only the Method node should exist"
2708 );
2709 }
2710
2711 #[test]
2712 fn test_ensure_method_reuses_existing_function_node() {
2713 let mut staging = StagingGraph::new();
2714 let file = PathBuf::from("test.ts");
2715 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::TypeScript);
2716
2717 let func_id = helper.add_function("standalone", None, false, false);
2718 let reused_id = helper.ensure_method("standalone", None, false, false);
2719
2720 assert_eq!(
2721 func_id, reused_id,
2722 "ensure_method should reuse the existing function node"
2723 );
2724 assert_eq!(helper.stats().nodes_created, 1);
2725 }
2726
2727 #[test]
2728 fn test_ensure_function_creates_new_when_no_method_exists() {
2729 let mut staging = StagingGraph::new();
2730 let file = PathBuf::from("test.ts");
2731 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::TypeScript);
2732
2733 let func_id = helper.ensure_function("topLevel", None, false, false);
2734 assert!(!func_id.is_invalid());
2735 assert_eq!(helper.stats().nodes_created, 1);
2736
2737 let func_id2 = helper.ensure_function("topLevel", None, false, false);
2738 assert_eq!(func_id, func_id2);
2739 assert_eq!(helper.stats().nodes_created, 1);
2740 }
2741
2742 #[test]
2743 fn test_no_method_function_duplicate_after_cross_kind_reuse() {
2744 let mut staging = StagingGraph::new();
2745 let file = PathBuf::from("browser-manager.ts");
2746 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::TypeScript);
2747
2748 let span_a = Span::new(
2749 Position { line: 3, column: 4 },
2750 Position { line: 8, column: 5 },
2751 );
2752 let span_b = Span::new(
2753 Position {
2754 line: 10,
2755 column: 4,
2756 },
2757 Position {
2758 line: 15,
2759 column: 5,
2760 },
2761 );
2762
2763 let _method_a = helper.add_method("BrowserManager.newTab", Some(span_a), true, false);
2765 let _method_b = helper.add_method("BrowserManager.restoreState", Some(span_b), true, false);
2766
2767 let _caller_a = helper.ensure_function("BrowserManager.newTab", None, true, false);
2769 let _caller_b = helper.ensure_function("BrowserManager.restoreState", None, true, false);
2770
2771 let ops = staging.operations();
2773 let mut method_names = std::collections::HashSet::new();
2774 let mut function_names = std::collections::HashSet::new();
2775
2776 for op in ops {
2777 if let StagingOp::AddNode { entry, .. } = op {
2778 if entry.kind == NodeKind::Method {
2779 method_names.insert(entry.name);
2780 } else if entry.kind == NodeKind::Function {
2781 function_names.insert(entry.name);
2782 }
2783 }
2784 }
2785
2786 let overlap: Vec<_> = method_names.intersection(&function_names).collect();
2787 assert!(
2788 overlap.is_empty(),
2789 "Found names that are both Method and Function: {overlap:?}"
2790 );
2791 }
2792
2793 #[test]
2798 fn test_ensure_function_reuses_existing_macro_node() {
2799 let mut staging = StagingGraph::new();
2800 let file = PathBuf::from("test.c");
2801 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::C);
2802
2803 let span = Span::new(
2804 Position { line: 1, column: 0 },
2805 Position {
2806 line: 1,
2807 column: 40,
2808 },
2809 );
2810
2811 let macro_id = helper.add_node("list_for_each_entry", Some(span), NodeKind::Macro);
2813
2814 let reused_id = helper.ensure_function("list_for_each_entry", None, false, false);
2816
2817 assert_eq!(
2818 macro_id, reused_id,
2819 "ensure_function should reuse the existing Macro node"
2820 );
2821 assert_eq!(helper.stats().nodes_created, 1);
2822 }
2823
2824 #[test]
2825 fn test_ensure_function_reuses_existing_constant_node() {
2826 let mut staging = StagingGraph::new();
2827 let file = PathBuf::from("test.c");
2828 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::C);
2829
2830 let span = Span::new(
2831 Position { line: 3, column: 0 },
2832 Position {
2833 line: 3,
2834 column: 30,
2835 },
2836 );
2837
2838 let const_id = helper.add_constant("handler_fn", Some(span));
2840
2841 let reused_id = helper.ensure_function("handler_fn", None, false, false);
2842
2843 assert_eq!(
2844 const_id, reused_id,
2845 "ensure_function should reuse the existing Constant node"
2846 );
2847 assert_eq!(helper.stats().nodes_created, 1);
2848 }
2849
2850 #[test]
2851 fn test_ensure_method_reuses_existing_lambda_target_node() {
2852 let mut staging = StagingGraph::new();
2853 let file = PathBuf::from("test.java");
2854 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Java);
2855
2856 let span = Span::new(
2857 Position { line: 7, column: 8 },
2858 Position {
2859 line: 10,
2860 column: 9,
2861 },
2862 );
2863
2864 let lambda_id = helper.add_node("Comparator.compare", Some(span), NodeKind::LambdaTarget);
2865
2866 let reused_id = helper.ensure_method("Comparator.compare", None, false, false);
2867
2868 assert_eq!(
2869 lambda_id, reused_id,
2870 "ensure_method should reuse the existing LambdaTarget node"
2871 );
2872 assert_eq!(helper.stats().nodes_created, 1);
2873 }
2874
2875 #[test]
2876 fn test_cross_kind_reuse_does_not_merge_incompatible_kinds() {
2877 let mut staging = StagingGraph::new();
2878 let file = PathBuf::from("test.css");
2879 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Css);
2880
2881 let style_id = helper.add_node_verbatim(
2883 ".container",
2884 None,
2885 NodeKind::StyleRule,
2886 &[],
2887 None,
2888 None,
2889 false,
2890 );
2891
2892 let func_id = helper.ensure_function(".container", None, false, false);
2894
2895 assert_ne!(
2896 style_id, func_id,
2897 "ensure_function must NOT merge into a StyleRule"
2898 );
2899 assert_eq!(helper.stats().nodes_created, 2);
2900 }
2901
2902 #[test]
2909 fn test_stub_first_ensure_function_then_add_method_reuses() {
2910 let mut staging = StagingGraph::new();
2911 let file = PathBuf::from("test.ts");
2912 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::TypeScript);
2913
2914 let stub_id = helper.ensure_function("Widget.render", None, false, false);
2916
2917 let span = Span::new(
2919 Position {
2920 line: 10,
2921 column: 4,
2922 },
2923 Position {
2924 line: 20,
2925 column: 5,
2926 },
2927 );
2928 let decl_id = helper.add_method("Widget.render", Some(span), false, false);
2929
2930 assert!(!stub_id.is_invalid());
2935 assert!(!decl_id.is_invalid());
2936 }
2939
2940 #[test]
2941 fn test_stub_first_ensure_method_then_add_function_reuses() {
2942 let mut staging = StagingGraph::new();
2943 let file = PathBuf::from("test.py");
2944 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Python);
2945
2946 let stub_id = helper.ensure_method("process_data", None, false, false);
2948
2949 let span = Span::new(
2951 Position { line: 5, column: 0 },
2952 Position {
2953 line: 15,
2954 column: 0,
2955 },
2956 );
2957 let decl_id = helper.add_function("process_data", Some(span), false, false);
2958
2959 assert!(!stub_id.is_invalid());
2960 assert!(!decl_id.is_invalid());
2961 }
2962
2963 #[test]
2964 fn test_ensure_callee_then_add_function_same_name_no_panic() {
2965 let mut staging = StagingGraph::new();
2966 let file = PathBuf::from("test.c");
2967 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::C);
2968
2969 let call_span = Span::new(
2970 Position {
2971 line: 50,
2972 column: 4,
2973 },
2974 Position {
2975 line: 50,
2976 column: 20,
2977 },
2978 );
2979 let callee_id = helper.ensure_callee("kfree", call_span, CalleeKindHint::Function);
2980
2981 let def_span = Span::new(
2982 Position { line: 1, column: 0 },
2983 Position {
2984 line: 10,
2985 column: 1,
2986 },
2987 );
2988 let def_id = helper.add_function("kfree", Some(def_span), false, false);
2989
2990 assert_eq!(
2993 callee_id, def_id,
2994 "add_function should reuse the node created by ensure_callee"
2995 );
2996 assert_eq!(helper.stats().nodes_created, 1);
2997 }
2998
2999 #[test]
3004 fn test_ensure_callee_function_hint_creates_with_span() {
3005 let mut staging = StagingGraph::new();
3006 let file = PathBuf::from("test.rs");
3007 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
3008
3009 let call_span = Span::new(
3010 Position {
3011 line: 20,
3012 column: 4,
3013 },
3014 Position {
3015 line: 20,
3016 column: 30,
3017 },
3018 );
3019
3020 let id = helper.ensure_callee("target_fn", call_span, CalleeKindHint::Function);
3021 assert!(!id.is_invalid());
3022
3023 let ops = staging.operations();
3025 let node_op = ops
3026 .iter()
3027 .find(|op| matches!(op, StagingOp::AddNode { .. }));
3028 if let Some(StagingOp::AddNode { entry, .. }) = node_op {
3029 assert!(
3030 entry.start_line > 0,
3031 "ensure_callee must produce nodes with line > 0"
3032 );
3033 }
3034 }
3035
3036 #[test]
3037 fn test_ensure_callee_macro_hint_reuses_existing_macro() {
3038 let mut staging = StagingGraph::new();
3039 let file = PathBuf::from("test.c");
3040 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::C);
3041
3042 let def_span = Span::new(
3043 Position { line: 5, column: 0 },
3044 Position {
3045 line: 5,
3046 column: 40,
3047 },
3048 );
3049 let call_span = Span::new(
3050 Position {
3051 line: 99,
3052 column: 4,
3053 },
3054 Position {
3055 line: 99,
3056 column: 30,
3057 },
3058 );
3059
3060 let macro_id = helper.add_node("IS_ERR", Some(def_span), NodeKind::Macro);
3061 let reused_id = helper.ensure_callee("IS_ERR", call_span, CalleeKindHint::Macro);
3062
3063 assert_eq!(
3064 macro_id, reused_id,
3065 "ensure_callee should reuse existing Macro node"
3066 );
3067 assert_eq!(helper.stats().nodes_created, 1);
3068 }
3069
3070 #[test]
3071 fn test_ensure_callee_idempotent_returns_first_spans_node() {
3072 let mut staging = StagingGraph::new();
3073 let file = PathBuf::from("test.rs");
3074 let mut helper = GraphBuildHelper::new(&mut staging, &file, Language::Rust);
3075
3076 let span1 = Span::new(
3077 Position {
3078 line: 10,
3079 column: 0,
3080 },
3081 Position {
3082 line: 10,
3083 column: 20,
3084 },
3085 );
3086 let span2 = Span::new(
3087 Position {
3088 line: 50,
3089 column: 0,
3090 },
3091 Position {
3092 line: 50,
3093 column: 20,
3094 },
3095 );
3096
3097 let id1 = helper.ensure_callee("func", span1, CalleeKindHint::Function);
3098 let id2 = helper.ensure_callee("func", span2, CalleeKindHint::Function);
3099
3100 assert_eq!(
3101 id1, id2,
3102 "Two ensure_callee calls for the same name return the same NodeId"
3103 );
3104 }
3105
3106 #[test]
3107 fn test_call_compatible_kinds_dry_no_body_changes_needed() {
3108 assert!(CALL_COMPATIBLE_KINDS.contains(&NodeKind::Function));
3114 assert!(CALL_COMPATIBLE_KINDS.contains(&NodeKind::Method));
3115 assert!(CALL_COMPATIBLE_KINDS.contains(&NodeKind::Macro));
3116 assert!(CALL_COMPATIBLE_KINDS.contains(&NodeKind::Constant));
3117 assert!(CALL_COMPATIBLE_KINDS.contains(&NodeKind::LambdaTarget));
3118 assert_eq!(CALL_COMPATIBLE_KINDS.len(), 5);
3119 }
3120}