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