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