1use crate::declarations::{
27 CppSentinelRecoveredClass, cpp_sentinel_recovered_classes,
28 cpp_sentinel_recovered_scope_for_node, node_text, recovered_macro_return_type_node,
29};
30use crate::graph::CppGraphSource;
31use crate::graph::extractor::{
32 BareCallTargetResolution, LexicalScopeResolution, enclosing_lexical_scope_components,
33 initialized_ordinary_type_imports, ordinary_using_declaration_type_node,
34 resolve_bare_call_target, resolve_ordinary_using_declaration_owner,
35 resolve_type_components_lexically_at, resolve_type_node_lexically,
36 resolve_using_enum_declaration_owner, using_enum_declaration_type_node,
37};
38use crate::graph::resolver::{
39 CppTemplateResolutionError, DesignatedInitializerOwner, EnclosingMemberOwnerResolution,
40 LexicalCallableValueResolution, LexicalTypeResolution, OrdinaryTypeImportCell, TargetKind,
41 VisibilityIndex, VisibleMemberResolution, canonical_cpp_scope_components,
42 constructor_style_local_declaration, cpp_callable_arity, cpp_template_reference_arguments,
43 cpp_type_name_components, declarator_name_node, designated_initializer_owner,
44 extract_variable_name, first_type_child, function_terminal_node, has_ancestor_kind,
45 infer_cpp_initializer_binding, infer_cpp_initializer_type, is_declaration_name,
46 is_declarator_node, is_globally_qualified_cpp_name, is_nested_type_node, normalize_type_text,
47 out_of_line_destructor_type_reference, out_of_line_member_definition_owner,
48 parameter_belongs_to_callable_scope, qualified_owner_components,
49 recovered_macro_decorated_type_node, resolve_declaring_member_owner, same_logical_symbol,
50 same_visible_symbol, type_reference_hit_node,
51};
52use crate::graph::syntax::qualified_callable_value;
53use brokk_bifrost_core::analyzer::tree_walk::{TreeWalkAction, walk_tree_iterative};
54use brokk_bifrost_core::analyzer::usages::common::same_node;
55use brokk_bifrost_core::analyzer::usages::inverted_edges::{
56 ClassRangeIndex, FileEdgeScanInput, PerFileEdges, classify_reference_node, first_precise,
57};
58use brokk_bifrost_core::analyzer::usages::local_inference::{
59 LocalInferenceConfig, LocalInferenceEngine,
60};
61use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile};
62use brokk_bifrost_core::hash::{HashMap, HashSet};
63use tree_sitter::Node;
64
65pub fn scan_file(
73 analyzer: &CppGraphSource<'_>,
74 visibility: &VisibilityIndex<'_>,
75 file: &ProjectFile,
76 input: &FileEdgeScanInput<'_>,
77) -> PerFileEdges {
78 let ordinary_type_imports =
79 initialized_ordinary_type_imports(input.root(), analyzer, visibility, file, input.source);
80 let recovered_sentinel_classes = cpp_sentinel_recovered_classes(input.root(), input.source);
81 let mut ctx = CppScan {
82 analyzer: *analyzer,
83 visibility,
84 file,
85 source: input.source,
86 ordinary_type_imports,
87 recovered_sentinel_classes,
88 class_ranges: ClassRangeIndex::build(analyzer.index, file),
89 declaring_member_cache: HashMap::default(),
90 input,
91 edges: PerFileEdges::default(),
92 };
93 let mut bindings = LocalInferenceEngine::new(LocalInferenceConfig::default());
94 walk(input.root(), &mut ctx, &mut bindings);
95 ctx.edges
96}
97
98struct CppScan<'a> {
99 analyzer: CppGraphSource<'a>,
100 visibility: &'a VisibilityIndex<'a>,
101 file: &'a ProjectFile,
102 source: &'a str,
103 ordinary_type_imports: OrdinaryTypeImportCell,
104 recovered_sentinel_classes: Vec<CppSentinelRecoveredClass>,
105 class_ranges: ClassRangeIndex,
106 declaring_member_cache: HashMap<CodeUnit, HashMap<String, EnclosingMemberOwnerResolution>>,
107 input: &'a FileEdgeScanInput<'a>,
108 edges: PerFileEdges,
109}
110
111impl CppScan<'_> {
112 fn resolve_type(&self, text: &str) -> Option<CodeUnit> {
114 self.visibility.resolve_type(self.file, text)
115 }
116
117 fn resolve_type_node_result(
118 &self,
119 node: Node<'_>,
120 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
121 self.visibility
122 .resolve_type_node_result(self.file, node, self.source)
123 }
124
125 fn enclosing_class(&self, byte: usize) -> Option<&str> {
127 self.class_ranges.enclosing(byte)
128 }
129
130 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
137 cpp_sentinel_recovered_scope_for_node(node, self.source, &self.recovered_sentinel_classes)
138 }
139
140 fn record(&mut self, callee: String, node: Node<'_>) {
141 self.edges.record_kind(
142 self.input,
143 callee,
144 classify_reference_node(node),
145 node.start_byte(),
146 node.end_byte(),
147 );
148 }
149
150 fn record_unproven(&mut self, name: &str, node: Node<'_>) {
151 self.edges
152 .record_unproven_name(self.input, name, node.start_byte(), node.end_byte());
153 }
154}
155
156const SCOPE_NODES: &[&str] = &[
157 "compound_statement",
158 "field_declaration_list",
159 "function_definition",
160 "for_range_loop",
161 "lambda_expression",
162 "for_statement",
163 "while_statement",
164 "if_statement",
165];
166
167fn walk(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &mut LocalInferenceEngine<CodeUnit>) {
168 let mut state = (ctx, bindings);
169 walk_tree_iterative(
170 node,
171 &mut state,
172 |node, (ctx, bindings)| {
173 if walk_enter(node, ctx, bindings) {
174 TreeWalkAction::DescendWithExit
175 } else {
176 TreeWalkAction::Descend
177 }
178 },
179 |(_, bindings)| bindings.exit_scope(),
180 );
181}
182
183fn walk_enter(
184 node: Node<'_>,
185 ctx: &mut CppScan<'_>,
186 bindings: &mut LocalInferenceEngine<CodeUnit>,
187) -> bool {
188 let enters_scope = SCOPE_NODES.contains(&node.kind());
189 if enters_scope {
190 bindings.enter_scope();
191 }
192 seed_declaration(node, ctx, bindings);
193 record_reference(node, ctx, bindings);
194 enters_scope
195}
196
197fn record_reference(
198 node: Node<'_>,
199 ctx: &mut CppScan<'_>,
200 bindings: &LocalInferenceEngine<CodeUnit>,
201) {
202 if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
203 record_recovered_macro_return_type_reference(return_type, ctx);
204 return;
205 }
206 if node.kind() == "using_declaration" {
207 let (resolution, type_node) =
208 if let Some(type_node) = using_enum_declaration_type_node(node) {
209 (
210 resolve_using_enum_declaration_owner(
211 node,
212 &ctx.analyzer,
213 ctx.visibility,
214 &ctx.ordinary_type_imports,
215 ctx.file,
216 ctx.source,
217 ),
218 type_node,
219 )
220 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
221 (
222 resolve_ordinary_using_declaration_owner(
223 node,
224 &ctx.analyzer,
225 ctx.visibility,
226 ctx.file,
227 ctx.source,
228 ),
229 type_node,
230 )
231 } else {
232 return;
233 };
234 match resolution {
235 LexicalTypeResolution::Resolved { unit, .. } => ctx.record(unit.fq_name(), type_node),
236 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
237 ctx.record_unproven(node_text(type_node, ctx.source), type_node);
238 }
239 }
240 return;
241 }
242 if has_ancestor_kind(node, "using_declaration") {
243 return;
244 }
245 if matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
246 && is_declaration_name(node)
247 && let Some(owners) = out_of_line_member_definition_owner(
248 &ctx.analyzer,
249 ctx.visibility,
250 ctx.file,
251 ctx.source,
252 node,
253 )
254 {
255 let terminal_destructor = out_of_line_destructor_type_reference(node);
256 let innermost = owners.innermost().map(|(_, owner)| owner.clone());
257 for (owner_node, owner) in owners.owners {
258 ctx.record(owner.fq_name(), owner_node);
259 }
260 if let (Some(terminal), Some(owner)) = (terminal_destructor, innermost) {
261 ctx.record(owner.fq_name(), terminal);
262 }
263 return;
264 }
265 if let Some(value) = qualified_callable_value(node) {
266 record_qualified_callable_value(
267 value.qualified,
268 value.global,
269 &value.owner_components,
270 value.member,
271 ctx,
272 );
273 return;
274 }
275 if matches!(node.kind(), "identifier" | "field_identifier")
276 && let Some(designator_owner) =
277 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
278 {
279 let name = node_text(node, ctx.source);
280 match designator_owner {
281 DesignatedInitializerOwner::Resolved(owner) => {
282 if let Some(field) = ctx
283 .visibility
284 .visible_members_for_owner_name(ctx.file, &owner, name)
285 .into_iter()
286 .find(|unit| unit.is_field())
287 {
288 ctx.record(field.fq_name(), node);
289 }
290 }
291 DesignatedInitializerOwner::Unresolved => ctx.record_unproven(name, node),
292 }
293 return;
294 }
295 match node.kind() {
296 "namespace_identifier"
297 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) =>
298 {
299 record_recovered_macro_decorated_type_reference(node, type_node, ctx, bindings);
300 }
301 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type" => {
305 if is_declaration_name(node) {
306 if let Some(owners) = out_of_line_member_definition_owner(
307 &ctx.analyzer,
308 ctx.visibility,
309 ctx.file,
310 ctx.source,
311 node,
312 ) {
313 let terminal_destructor = out_of_line_destructor_type_reference(node);
314 let innermost = owners.innermost().map(|(_, owner)| owner.clone());
315 for (owner_node, owner) in owners.owners {
316 ctx.record(owner.fq_name(), owner_node);
317 }
318 if let (Some(terminal), Some(owner)) = (terminal_destructor, innermost) {
319 ctx.record(owner.fq_name(), terminal);
320 }
321 }
322 return;
323 }
324 if is_nested_type_node(node) && !is_template_argument_type_leaf(node) {
325 record_nested_type_terminal_reference(node, ctx);
326 return;
327 }
328 if let Some(function) = scoped_free_function(node, ctx) {
331 ctx.record(function.fq_name(), function_terminal_node(node));
332 return;
333 }
334 if let Some(call) = node.parent().filter(|parent| {
335 parent.kind() == "call_expression"
336 && parent.child_by_field_name("function") == Some(node)
337 }) && let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
338 node,
339 &ctx.analyzer,
340 ctx.visibility,
341 &ctx.ordinary_type_imports,
342 ctx.file,
343 ctx.source,
344 ) {
345 let Some(call_arity) = ctx
346 .visibility
347 .call_arity_evidence(ctx.file, call, ctx.source)
348 .exact()
349 else {
350 ctx.record_unproven(node_text(node, ctx.source), function_terminal_node(node));
351 return;
352 };
353 if let VisibleMemberResolution::Callable(constructors) = ctx
354 .visibility
355 .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
356 && let Some(constructor) = constructors.iter().find(|constructor| {
357 cpp_callable_arity(&ctx.analyzer, constructor).accepts(call_arity)
358 })
359 {
360 ctx.record(constructor.fq_name(), function_terminal_node(node));
361 } else {
362 ctx.record(unit.fq_name(), function_terminal_node(node));
363 }
364 return;
365 }
366 if let Some(owner) = scoped_call_owner(node, ctx) {
367 let member = scoped_call_member(node, ctx.source);
368 if !member.is_empty() {
369 ctx.record(format!("{owner}.{member}"), function_terminal_node(node));
370 return;
371 }
372 }
373 record_type_reference(node, ctx, bindings);
374 }
375 "call_expression" => record_call(node, ctx, bindings),
376 _ => {}
377 }
378}
379
380fn record_recovered_macro_return_type_reference(return_type: Node<'_>, ctx: &mut CppScan<'_>) {
381 let name = node_text(return_type, ctx.source);
382 let Some(scope) = recovered_or_indexed_lexical_scope(return_type, ctx) else {
383 ctx.record_unproven(name, return_type);
384 return;
385 };
386 let components = [name.to_string()];
387 if let LexicalTypeResolution::Resolved { unit, .. } = ctx
388 .visibility
389 .resolve_type_components_lexically(&ctx.analyzer, ctx.file, &components, false, &scope)
390 {
391 ctx.record(unit.fq_name(), return_type);
392 return;
393 }
394
395 let mut aliases = ctx
396 .visibility
397 .visible_identifier_candidates(ctx.file, name)
398 .filter(|candidate| {
399 ctx.analyzer
400 .type_alias_provider()
401 .is_some_and(|provider| provider.is_type_alias(candidate))
402 && ctx.visibility.external_type_candidate_visible_in_context(
403 &ctx.analyzer,
404 ctx.file,
405 candidate,
406 return_type,
407 )
408 })
409 .filter_map(|candidate| {
410 let owner = ctx.analyzer.parent_of(candidate)?;
411 let owner_components = canonical_cpp_scope_components(&owner);
412 scope
413 .starts_with(&owner_components)
414 .then_some((candidate, owner_components.len()))
415 })
416 .collect::<Vec<_>>();
417 let deepest = aliases.iter().map(|(_, depth)| *depth).max();
418 aliases.retain(|(_, depth)| Some(*depth) == deepest);
419 if aliases.len() == 1 {
420 ctx.record(aliases[0].0.fq_name(), return_type);
421 } else {
422 ctx.record_unproven(name, return_type);
423 }
424}
425
426fn is_template_argument_type_leaf(node: Node<'_>) -> bool {
431 let Some(type_descriptor) = node.parent() else {
432 return false;
433 };
434 if type_descriptor.kind() != "type_descriptor"
435 || type_descriptor.child_by_field_name("type") != Some(node)
436 {
437 return false;
438 }
439 let Some(arguments) = type_descriptor.parent() else {
440 return false;
441 };
442 if arguments.kind() != "template_argument_list" {
443 return false;
444 }
445 arguments.parent().is_some_and(|parent| {
446 matches!(parent.kind(), "template_type" | "template_function")
447 && parent.child_by_field_name("arguments") == Some(arguments)
448 })
449}
450
451fn record_type_reference(
452 node: Node<'_>,
453 ctx: &mut CppScan<'_>,
454 bindings: &LocalInferenceEngine<CodeUnit>,
455) {
456 let ordinary_resolution = resolve_type_node_lexically(
457 node,
458 &ctx.analyzer,
459 ctx.visibility,
460 &ctx.ordinary_type_imports,
461 ctx.file,
462 ctx.source,
463 );
464 let resolution = match recovered_sentinel_type_resolution(node, ctx) {
465 Some(recovered @ LexicalTypeResolution::Resolved { .. }) => recovered,
466 Some(LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing) | None => {
467 ordinary_resolution
468 }
469 };
470 let resolution = resolve_inverted_type_node(node, ctx, resolution);
471 match resolution {
472 LexicalTypeResolution::Resolved { unit, .. } => ctx.record(
473 unit.fq_name(),
474 type_reference_hit_node(node, ctx.file, ctx.source, bindings),
475 ),
476 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {}
477 }
478}
479
480fn record_nested_type_terminal_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
481 let Some(qualified) = node.parent().filter(|parent| {
482 matches!(
483 parent.kind(),
484 "qualified_identifier" | "scoped_type_identifier"
485 ) && parent.child_by_field_name("name") == Some(node)
486 }) else {
487 return;
488 };
489 let Some(owner) = qualified_owner_components(qualified, ctx.source) else {
490 return;
491 };
492 let mut complete = qualified;
493 while let Some(parent) = complete.parent().filter(|parent| {
494 matches!(
495 parent.kind(),
496 "qualified_identifier" | "scoped_type_identifier"
497 )
498 }) {
499 complete = parent;
500 }
501 if matches!(
502 resolve_type_node_lexically(
503 complete,
504 &ctx.analyzer,
505 ctx.visibility,
506 &ctx.ordinary_type_imports,
507 ctx.file,
508 ctx.source,
509 ),
510 LexicalTypeResolution::Resolved { .. }
511 ) {
512 return;
513 }
514
515 let Some(enclosing_owner) = enclosing_callable_owner(node, ctx) else {
516 return;
517 };
518 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
519 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
520 &ctx.analyzer,
521 ctx.file,
522 &owner.names,
523 owner.global,
524 &lexical_scope,
525 );
526 let owner_unit = match owner_resolution {
527 LexicalTypeResolution::Resolved { unit, .. } => unit,
528 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
529 let Some(unit) = ctx.visibility.inherited_injected_class_owner(
530 &ctx.analyzer,
531 ctx.file,
532 &enclosing_owner,
533 &owner.names[0],
534 ) else {
535 return;
536 };
537 unit
538 }
539 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return,
540 };
541 let name = node_text(node, ctx.source);
542 let mut candidates = Vec::new();
543 for candidate in ctx
544 .visibility
545 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
546 .into_iter()
547 .filter(|candidate| {
548 candidate.is_class()
549 || ctx
550 .analyzer
551 .type_alias_provider()
552 .is_some_and(|provider| provider.is_type_alias(candidate))
553 })
554 .filter(|candidate| {
555 ctx.visibility.external_type_candidate_visible_in_context(
556 &ctx.analyzer,
557 ctx.file,
558 candidate,
559 node,
560 )
561 })
562 {
563 if !candidates
564 .iter()
565 .any(|existing| same_logical_symbol(existing, candidate))
566 {
567 candidates.push(candidate.clone());
568 }
569 }
570 if let [candidate] = candidates.as_slice() {
571 ctx.record(candidate.fq_name(), complete);
572 }
573}
574
575fn recovered_sentinel_type_resolution(
576 node: Node<'_>,
577 ctx: &CppScan<'_>,
578) -> Option<LexicalTypeResolution> {
579 let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
580 let components = cpp_type_name_components(node, ctx.source)?;
581 let global = is_globally_qualified_cpp_name(node);
582 Some(ctx.visibility.resolve_type_components_lexically(
583 &ctx.analyzer,
584 ctx.file,
585 &components,
586 global,
587 &scope,
588 ))
589}
590
591fn recovered_or_indexed_lexical_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<Vec<String>> {
597 ctx.recovered_sentinel_scope(node).or_else(|| {
598 match enclosing_lexical_scope_components(
599 node,
600 &ctx.analyzer,
601 ctx.visibility,
602 ctx.file,
603 ctx.source,
604 ) {
605 LexicalScopeResolution::Resolved(scope) => Some(scope),
606 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
607 }
608 })
609}
610
611fn record_recovered_macro_decorated_type_reference(
618 scope_node: Node<'_>,
619 type_node: Node<'_>,
620 ctx: &mut CppScan<'_>,
621 bindings: &LocalInferenceEngine<CodeUnit>,
622) {
623 let mut resolved = Vec::new();
624 for candidate in [scope_node, type_node] {
625 if resolved
626 .iter()
627 .any(|(_, existing): &(CodeUnit, Node<'_>)| same_node(*existing, candidate))
628 {
629 continue;
630 }
631 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
632 candidate,
633 &ctx.analyzer,
634 ctx.visibility,
635 &ctx.ordinary_type_imports,
636 ctx.file,
637 ctx.source,
638 ) {
639 resolved.push((unit, candidate));
640 }
641 }
642 let [(unit, candidate)] = resolved.as_slice() else {
643 return;
644 };
645 ctx.record(
646 unit.fq_name(),
647 type_reference_hit_node(*candidate, ctx.file, ctx.source, bindings),
648 );
649}
650
651fn resolve_inverted_type_node(
659 node: Node<'_>,
660 ctx: &CppScan<'_>,
661 resolution: LexicalTypeResolution,
662) -> LexicalTypeResolution {
663 let Some(arguments) = cpp_template_reference_arguments(node, ctx.source) else {
664 return resolution;
665 };
666 let LexicalTypeResolution::Resolved {
667 unit,
668 components,
669 candidates,
670 } = resolution
671 else {
672 return resolution;
673 };
674
675 let mut specialized = Vec::new();
676 for candidate in candidates.iter().chain(std::iter::once(&unit)) {
677 if let Ok(resolved) =
678 ctx.visibility
679 .resolve_template_arguments(ctx.file, candidate.clone(), &arguments)
680 && !specialized
681 .iter()
682 .any(|existing: &CodeUnit| same_visible_symbol(existing, &resolved))
683 {
684 specialized.push(resolved);
685 }
686 }
687 match specialized.as_slice() {
688 [specialized] => LexicalTypeResolution::Resolved {
689 unit: specialized.clone(),
690 components,
691 candidates,
692 },
693 _ => LexicalTypeResolution::Resolved {
694 unit,
695 components,
696 candidates,
697 },
698 }
699}
700
701fn record_qualified_callable_value(
702 qualified: Node<'_>,
703 global: bool,
704 owner_components: &[Node<'_>],
705 member_node: Node<'_>,
706 ctx: &mut CppScan<'_>,
707) {
708 let member_name = node_text(member_node, ctx.source);
709 if member_name.is_empty() {
710 return;
711 }
712 let owner_components = owner_components
713 .iter()
714 .map(|component| node_text(*component, ctx.source))
715 .map(str::to_string)
716 .collect::<Vec<_>>();
717 let lexical_scope = if global {
718 Vec::new()
719 } else {
720 match enclosing_lexical_scope_components(
721 qualified,
722 &ctx.analyzer,
723 ctx.visibility,
724 ctx.file,
725 ctx.source,
726 ) {
727 LexicalScopeResolution::Resolved(scope) => scope,
728 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
729 ctx.record_unproven(member_name, member_node);
730 return;
731 }
732 }
733 };
734 let owner = match ctx.visibility.resolve_callable_value_components_lexically(
735 &ctx.analyzer,
736 ctx.file,
737 &owner_components,
738 member_name,
739 global,
740 &lexical_scope,
741 ) {
742 LexicalCallableValueResolution::Type(owner) => owner,
743 LexicalCallableValueResolution::FreeFunction(function) => {
744 ctx.record(function.fq_name(), member_node);
745 return;
746 }
747 LexicalCallableValueResolution::Ambiguous => {
748 ctx.record_unproven(member_name, member_node);
749 return;
750 }
751 LexicalCallableValueResolution::Missing => match resolve_type_components_lexically_at(
752 qualified,
753 &owner_components,
754 global,
755 &ctx.analyzer,
756 ctx.visibility,
757 &ctx.ordinary_type_imports,
758 ctx.file,
759 ctx.source,
760 ) {
761 LexicalTypeResolution::Resolved { unit, .. } => unit,
762 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
763 ctx.record_unproven(member_name, member_node);
764 return;
765 }
766 },
767 };
768 match ctx
769 .visibility
770 .visible_member_for_owner_name(ctx.file, &owner, member_name)
771 {
772 VisibleMemberResolution::Callable(callables) => {
773 if let Some(callable) = callables.first() {
774 ctx.record(callable.fq_name(), member_node);
775 }
776 }
777 VisibleMemberResolution::NonCallable => {}
781 VisibleMemberResolution::AmbiguousKind | VisibleMemberResolution::Missing => {
782 ctx.record_unproven(member_name, member_node);
783 }
784 }
785}
786
787fn record_call(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &LocalInferenceEngine<CodeUnit>) {
788 let Some(function) = node.child_by_field_name("function") else {
789 return;
790 };
791 let Some(call_arity) = ctx
792 .visibility
793 .call_arity_evidence(ctx.file, node, ctx.source)
794 .exact()
795 else {
796 let name_node = function
797 .child_by_field_name("field")
798 .or_else(|| function.child_by_field_name("name"))
799 .unwrap_or(function);
800 let name = node_text(name_node, ctx.source);
801 if !name.is_empty() {
802 ctx.record_unproven(name, name_node);
803 }
804 return;
805 };
806 match function.kind() {
807 "field_expression" => {
809 let Some(field) = function.child_by_field_name("field") else {
810 return;
811 };
812 let name = node_text(field, ctx.source);
813 if name.is_empty() {
814 return;
815 }
816 let Some(receiver) = function
817 .child_by_field_name("argument")
818 .or_else(|| function.named_child(0))
819 else {
820 return;
821 };
822 if receiver_is_self_like(receiver) {
823 ctx.record_unproven(name, field);
829 return;
830 }
831 if let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) {
832 match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
833 EnclosingMemberOwnerResolution::Owner(owner) => {
834 match ctx
835 .visibility
836 .visible_member_for_owner_name(ctx.file, &owner, name)
837 {
838 VisibleMemberResolution::Callable(callables) => {
839 if let Some(callable) = callables.iter().find(|callable| {
840 cpp_callable_arity(&ctx.analyzer, callable).accepts(call_arity)
841 }) {
842 ctx.record(callable.fq_name(), field);
843 }
844 }
845 VisibleMemberResolution::AmbiguousKind => {
846 ctx.record_unproven(name, field);
847 }
848 VisibleMemberResolution::NonCallable
849 | VisibleMemberResolution::Missing => {}
850 }
851 }
852 EnclosingMemberOwnerResolution::Ambiguous => {
853 ctx.record_unproven(name, field);
854 }
855 EnclosingMemberOwnerResolution::Missing => {}
856 }
857 } else {
858 ctx.record_unproven(name, field);
859 }
860 }
861 "identifier" | "template_function" => {
865 let terminal = super::resolver::function_terminal_node(function);
866 let name = node_text(terminal, ctx.source);
867 if name.is_empty() {
868 return;
869 }
870 if bindings.is_shadowed(name) {
871 return;
872 }
873 if let Some(enclosing_owner) = enclosing_callable_owner(function, ctx) {
874 match resolve_declaring_member_owner_cached(ctx, &enclosing_owner, name) {
875 EnclosingMemberOwnerResolution::Owner(owner)
876 if !same_visible_symbol(&owner, &enclosing_owner) =>
877 {
878 match ctx
879 .visibility
880 .visible_member_for_owner_name(ctx.file, &owner, name)
881 {
882 VisibleMemberResolution::Callable(callables) => {
883 if let Some(callable) = callables.first() {
884 ctx.record(callable.fq_name(), function);
885 }
886 }
887 VisibleMemberResolution::AmbiguousKind => {
888 ctx.record_unproven(name, function);
889 }
890 VisibleMemberResolution::NonCallable
891 | VisibleMemberResolution::Missing => {}
892 }
893 return;
894 }
895 EnclosingMemberOwnerResolution::Owner(_) => {
896 ctx.record_unproven(name, function);
905 return;
906 }
907 EnclosingMemberOwnerResolution::Ambiguous => {
908 ctx.record_unproven(name, function);
909 return;
910 }
911 EnclosingMemberOwnerResolution::Missing => {}
912 }
913 }
914 let resolution = resolve_bare_call_target(
915 node,
916 function,
917 &ctx.analyzer,
918 ctx.visibility,
919 &ctx.ordinary_type_imports,
920 ctx.file,
921 ctx.source,
922 );
923 match resolution {
924 BareCallTargetResolution::FreeFunctions(units) => {
925 let mut recorded = HashSet::default();
926 for unit in units {
927 let fq_name = unit.fq_name();
928 if recorded.insert(fq_name.clone()) {
929 ctx.record(fq_name, terminal);
930 }
931 }
932 }
933 BareCallTargetResolution::Type(unit) => {
934 if let VisibleMemberResolution::Callable(constructors) = ctx
935 .visibility
936 .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
937 && let Some(constructor) = constructors.iter().find(|constructor| {
938 cpp_callable_arity(&ctx.analyzer, constructor).accepts(call_arity)
939 })
940 {
941 ctx.record(constructor.fq_name(), terminal);
942 } else {
943 ctx.record(unit.fq_name(), function);
944 }
945 }
946 BareCallTargetResolution::UnprovenFreeFunctions(_)
947 | BareCallTargetResolution::CallableShadow
948 | BareCallTargetResolution::Ambiguous => {}
949 BareCallTargetResolution::Missing => {}
950 }
951 }
954 _ => {}
955 }
956}
957
958fn resolve_declaring_member_owner_cached(
959 ctx: &mut CppScan<'_>,
960 receiver_owner: &CodeUnit,
961 name: &str,
962) -> EnclosingMemberOwnerResolution {
963 if let Some(cached) = ctx
964 .declaring_member_cache
965 .get(receiver_owner)
966 .and_then(|by_name| by_name.get(name))
967 .cloned()
968 {
969 return cached;
970 }
971 let resolution = resolve_declaring_member_owner(
972 &ctx.analyzer,
973 ctx.visibility,
974 ctx.file,
975 receiver_owner,
976 name,
977 );
978 ctx.declaring_member_cache
979 .entry(receiver_owner.clone())
980 .or_default()
981 .insert(name.to_string(), resolution.clone());
982 resolution
983}
984
985fn enclosing_callable_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
986 let mut current = node.parent();
987 while let Some(parent) = current {
988 if parent.kind() == "function_definition" {
989 let declarator = parent.child_by_field_name("declarator")?;
990 let function = declarator_name_node(declarator)?;
991 if let Some(owners) = out_of_line_member_definition_owner(
992 &ctx.analyzer,
993 ctx.visibility,
994 ctx.file,
995 ctx.source,
996 function,
997 ) && let Some((_, owner)) = owners.innermost()
998 {
999 return Some(owner.clone());
1000 }
1001 break;
1002 }
1003 current = parent.parent();
1004 }
1005 ctx.enclosing_class(node.start_byte()).and_then(|fqn| {
1006 ctx.analyzer
1007 .definitions(fqn)
1008 .find(|candidate| candidate.is_class())
1009 })
1010}
1011
1012fn receiver_is_self_like(receiver: Node<'_>) -> bool {
1013 match receiver.kind() {
1014 "this" => true,
1015 "parenthesized_expression" | "pointer_expression" => receiver
1016 .child_by_field_name("argument")
1017 .or_else(|| receiver.named_child(0))
1018 .is_some_and(receiver_is_self_like),
1019 _ => false,
1020 }
1021}
1022
1023fn scoped_free_function(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1025 if node.kind() != "qualified_identifier" {
1026 return None;
1027 }
1028 let parent = node.parent()?;
1029 if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1030 return None;
1031 }
1032 ctx.visibility.resolve_named(
1033 ctx.file,
1034 node_text(node, ctx.source),
1035 TargetKind::FreeFunction,
1036 )
1037}
1038
1039fn scoped_call_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<String> {
1041 if node.kind() != "qualified_identifier" {
1042 return None;
1043 }
1044 let parent = node.parent()?;
1045 if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1046 return None;
1047 }
1048 let scope = node.child_by_field_name("scope")?;
1049 match resolve_type_node_lexically(
1050 scope,
1051 &ctx.analyzer,
1052 ctx.visibility,
1053 &ctx.ordinary_type_imports,
1054 ctx.file,
1055 ctx.source,
1056 ) {
1057 LexicalTypeResolution::Resolved { unit, .. } => Some(unit.fq_name()),
1058 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1059 }
1060}
1061
1062fn scoped_call_member(node: Node<'_>, source: &str) -> String {
1064 node.child_by_field_name("name")
1065 .map(|name| node_text(name, source).to_string())
1066 .unwrap_or_default()
1067}
1068
1069fn receiver_type_unit(
1070 receiver: Node<'_>,
1071 ctx: &CppScan<'_>,
1072 bindings: &LocalInferenceEngine<CodeUnit>,
1073 remaining_call_depth: usize,
1074) -> Option<CodeUnit> {
1075 match receiver.kind() {
1076 "identifier" => {
1077 let name = node_text(receiver, ctx.source);
1078 first_precise(bindings, name).or_else(|| {
1082 (!bindings.is_shadowed(name))
1083 .then(|| resolve_type_node_with_recovered_scope(receiver, ctx))
1084 .flatten()
1085 .or_else(|| {
1086 (!bindings.is_shadowed(name))
1087 .then(|| ctx.resolve_type(name))
1088 .flatten()
1089 })
1090 })
1091 }
1092 "this" => ctx.enclosing_class(receiver.start_byte()).and_then(|fqn| {
1093 ctx.analyzer
1094 .definitions(fqn)
1095 .find(|candidate| candidate.is_class())
1096 }),
1097 "parenthesized_expression" | "pointer_expression" => receiver
1099 .child_by_field_name("argument")
1100 .or_else(|| receiver.named_child(0))
1101 .and_then(|inner| receiver_type_unit(inner, ctx, bindings, remaining_call_depth)),
1102 "call_expression" if remaining_call_depth > 0 => infer_cpp_initializer_binding(
1103 &ctx.analyzer,
1104 ctx.visibility,
1105 ctx.file,
1106 ctx.source,
1107 receiver,
1108 Some(&|inner, _source| {
1109 receiver_type_unit(inner, ctx, bindings, remaining_call_depth - 1)
1110 .into_iter()
1111 .collect()
1112 }),
1113 )
1114 .and_then(|binding| binding.unit),
1115 _ => None,
1116 }
1117}
1118
1119fn seed_declaration(
1120 node: Node<'_>,
1121 ctx: &mut CppScan<'_>,
1122 bindings: &mut LocalInferenceEngine<CodeUnit>,
1123) {
1124 if recovered_macro_return_type_node(node, ctx.source).is_some()
1125 || crate::declarations::is_direct_recovered_exported_class_field_declaration(
1126 node, ctx.source,
1127 )
1128 {
1129 return;
1130 }
1131 match node.kind() {
1132 "parameter_declaration" | "optional_parameter_declaration" => {
1133 seed_typed_binding(node, ctx, bindings)
1134 }
1135 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx, bindings),
1136 "for_range_loop" => seed_range_binding(node, ctx, bindings),
1137 _ => {}
1138 }
1139}
1140
1141fn seed_typed_binding(
1142 node: Node<'_>,
1143 ctx: &CppScan<'_>,
1144 bindings: &mut LocalInferenceEngine<CodeUnit>,
1145) {
1146 if !parameter_belongs_to_callable_scope(node) {
1147 return;
1148 }
1149 let Some(declarator) = node.child_by_field_name("declarator") else {
1150 return;
1151 };
1152 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1153 return;
1154 };
1155 let type_node = node
1156 .child_by_field_name("type")
1157 .or_else(|| first_type_child(node));
1158 seed_binding(&name, type_node, None, ctx, bindings);
1159}
1160
1161fn seed_range_binding(
1162 node: Node<'_>,
1163 ctx: &CppScan<'_>,
1164 bindings: &mut LocalInferenceEngine<CodeUnit>,
1165) {
1166 let Some(declarator) = node.child_by_field_name("declarator") else {
1167 return;
1168 };
1169 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1170 return;
1171 };
1172 let type_node = node
1173 .child_by_field_name("type")
1174 .or_else(|| first_type_child(node));
1175 seed_binding(&name, type_node, None, ctx, bindings);
1176}
1177
1178fn seed_variable_declaration(
1179 node: Node<'_>,
1180 ctx: &CppScan<'_>,
1181 bindings: &mut LocalInferenceEngine<CodeUnit>,
1182) {
1183 let type_node = node
1184 .child_by_field_name("type")
1185 .or_else(|| first_type_child(node));
1186 let type_text =
1187 type_node.map(|type_node| normalize_type_text(node_text(type_node, ctx.source)));
1188 let mut cursor = node.walk();
1189 for child in node.named_children(&mut cursor) {
1190 let declarator = if child.kind() == "init_declarator" {
1191 child.child_by_field_name("declarator")
1192 } else if is_declarator_node(child) {
1193 Some(child)
1194 } else {
1195 None
1196 };
1197 let Some(declarator) = declarator else {
1198 continue;
1199 };
1200 if declarator.kind() == "function_declarator"
1201 && !constructor_style_local_declaration(
1202 ctx.visibility,
1203 ctx.file,
1204 ctx.source,
1205 declarator,
1206 type_text.as_deref(),
1207 bindings,
1208 )
1209 {
1210 if node.kind() == "declaration"
1211 && has_function_scope_ancestor(node)
1212 && let Some(name) = extract_variable_name(declarator, ctx.source)
1213 {
1214 bindings.declare_shadow(name);
1215 }
1216 continue;
1217 }
1218 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1219 continue;
1220 };
1221 let value = child.child_by_field_name("value");
1222 seed_binding(&name, type_node, value, ctx, bindings);
1223 }
1224}
1225
1226fn has_function_scope_ancestor(mut node: Node<'_>) -> bool {
1227 while let Some(parent) = node.parent() {
1228 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1229 return true;
1230 }
1231 node = parent;
1232 }
1233 false
1234}
1235
1236fn seed_binding(
1237 name: &str,
1238 type_node: Option<Node<'_>>,
1239 value: Option<Node<'_>>,
1240 ctx: &CppScan<'_>,
1241 bindings: &mut LocalInferenceEngine<CodeUnit>,
1242) {
1243 if name.is_empty() {
1244 return;
1245 }
1246 let declared_type =
1250 type_node.filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto");
1251 let resolved = match declared_type {
1252 Some(node) => resolve_type_node_with_recovered_scope(node, ctx).or_else(|| {
1253 match ctx.resolve_type_node_result(node) {
1254 Ok(Some(unit)) => Some(unit),
1255 Ok(None) => ctx.resolve_type(node_text(node, ctx.source)),
1256 Err(_) => None,
1257 }
1258 }),
1259 None => value.and_then(|value| infer_type_from_value(value, ctx)),
1260 };
1261 match resolved {
1262 Some(unit) => bindings.seed_symbol(name.to_string(), unit),
1263 None => bindings.declare_shadow(name.to_string()),
1264 }
1265}
1266
1267fn resolve_type_node_with_recovered_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1268 let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
1269 let components = cpp_type_name_components(node, ctx.source)?;
1270 let global = is_globally_qualified_cpp_name(node);
1271 match ctx.visibility.resolve_type_components_lexically(
1272 &ctx.analyzer,
1273 ctx.file,
1274 &components,
1275 global,
1276 &scope,
1277 ) {
1278 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
1279 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1280 }
1281}
1282
1283fn infer_type_from_value(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1285 infer_cpp_initializer_type(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
1286}