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