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