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