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