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 has_persisted_global_field_identity, initialized_ordinary_type_imports,
34 ordinary_using_declaration_type_node, resolve_bare_call_target,
35 resolve_ordinary_using_declaration_owner, resolve_type_components_lexically_at,
36 resolve_type_node_lexically, resolve_using_enum_declaration_owner,
37 using_enum_declaration_type_node,
38};
39use crate::graph::resolver::field_declared_binding;
40use crate::graph::resolver::{
41 CppTemplateResolutionError, DesignatedInitializerOwner, EnclosingMemberOwnerResolution,
42 LexicalCallableValueResolution, LexicalTypeResolution, OrdinaryMacroReferenceResolution,
43 OrdinaryTypeImportCell, TargetKind, VisibilityIndex, VisibleMemberResolution,
44 anonymous_aggregate_owner, c_offsetof_member_parts, canonical_cpp_scope_components,
45 cast_expression_type_node, constructor_style_local_declaration, cpp_binding_parts,
46 cpp_callable_arity, cpp_field_expression_receiver, cpp_template_reference_arguments,
47 cpp_type_name_components, declaration_declarator, declarator_name_node,
48 designated_initializer_owner, extract_variable_name, first_type_child, function_terminal_node,
49 has_ancestor_kind, infer_cpp_initializer_binding, infer_cpp_initializer_type,
50 initialized_type_declaration_with_cast, is_c_offsetof_member_node,
51 is_c_sizeof_expression_type_candidate, is_cpp_template_argument_type_leaf, is_declaration_name,
52 is_globally_qualified_cpp_name, is_nested_type_node,
53 is_recovered_qualified_friend_class_type_reference, normalize_type_text,
54 out_of_line_member_definition_owner, parameter_belongs_to_callable_scope,
55 qualified_alias_reference_preserves_target, qualified_alias_reference_requires_terminal,
56 qualified_owner_components, recovered_c_new_expression_argument_at,
57 recovered_macro_decorated_type_node, recovered_relational_template_member_call,
58 resolve_declaring_callable_owner, resolve_declaring_member_owner, same_logical_symbol,
59 same_visible_global_field_symbol, same_visible_symbol, type_reference_hit_node,
60 unique_macro_replacement_type_candidate,
61};
62use crate::graph::syntax::receiver_is_self_like;
63use crate::graph::syntax::{object_macro_replacement_type_references, qualified_callable_value};
64use brokk_bifrost_core::analyzer::tree_walk::{TreeWalkAction, walk_tree_iterative};
65use brokk_bifrost_core::analyzer::usages::common::same_node;
66use brokk_bifrost_core::analyzer::usages::inverted_edges::{
67 ClassRangeIndex, FileEdgeScanInput, PerFileEdges, classify_reference_node, first_precise,
68};
69use brokk_bifrost_core::analyzer::usages::local_inference::{
70 LocalInferenceConfig, LocalInferenceEngine,
71};
72use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile};
73use brokk_bifrost_core::hash::{HashMap, HashSet};
74use tree_sitter::Node;
75
76pub fn scan_file(
84 analyzer: &CppGraphSource<'_>,
85 visibility: &VisibilityIndex<'_>,
86 file: &ProjectFile,
87 input: &FileEdgeScanInput<'_>,
88) -> PerFileEdges {
89 let ordinary_type_imports =
90 initialized_ordinary_type_imports(input.root(), analyzer, visibility, file, input.source);
91 let recovered_sentinel_classes = cpp_sentinel_recovered_classes(input.root(), input.source);
92 let mut ctx = CppScan {
93 analyzer: *analyzer,
94 visibility,
95 file,
96 source: input.source,
97 ordinary_type_imports,
98 recovered_sentinel_classes,
99 class_ranges: ClassRangeIndex::build(analyzer.index, file),
100 declaring_member_cache: HashMap::default(),
101 input,
102 edges: PerFileEdges::default(),
103 };
104 let mut bindings = LocalInferenceEngine::new(LocalInferenceConfig::default());
105 walk(input.root(), &mut ctx, &mut bindings);
106 ctx.edges
107}
108
109struct CppScan<'a> {
110 analyzer: CppGraphSource<'a>,
111 visibility: &'a VisibilityIndex<'a>,
112 file: &'a ProjectFile,
113 source: &'a str,
114 ordinary_type_imports: OrdinaryTypeImportCell,
115 recovered_sentinel_classes: Vec<CppSentinelRecoveredClass>,
116 class_ranges: ClassRangeIndex,
117 declaring_member_cache: HashMap<CodeUnit, HashMap<String, EnclosingMemberOwnerResolution>>,
118 input: &'a FileEdgeScanInput<'a>,
119 edges: PerFileEdges,
120}
121
122impl CppScan<'_> {
123 fn resolve_type(&self, text: &str) -> Option<CodeUnit> {
125 self.visibility.resolve_type(self.file, text)
126 }
127
128 fn resolve_type_node_result(
129 &self,
130 node: Node<'_>,
131 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
132 self.visibility
133 .resolve_type_node_result(self.file, node, self.source)
134 }
135
136 fn enclosing_class(&self, byte: usize) -> Option<&str> {
138 self.class_ranges.enclosing(byte)
139 }
140
141 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
148 cpp_sentinel_recovered_scope_for_node(node, self.source, &self.recovered_sentinel_classes)
149 }
150
151 fn record(&mut self, callee: String, node: Node<'_>) {
152 self.edges.record_reference(self.input, callee, node);
153 }
154
155 fn record_range(&mut self, callee: String, anchor: Node<'_>, start: usize, end: usize) {
156 self.edges.record_kind(
157 self.input,
158 callee,
159 classify_reference_node(anchor),
160 start,
161 end,
162 );
163 }
164
165 fn record_unproven(&mut self, name: &str, node: Node<'_>) {
166 self.edges
167 .record_unproven_name_at_node(self.input, name, node);
168 }
169}
170
171const SCOPE_NODES: &[&str] = &[
172 "compound_statement",
173 "field_declaration_list",
174 "function_definition",
175 "for_range_loop",
176 "lambda_expression",
177 "for_statement",
178 "while_statement",
179 "if_statement",
180];
181
182fn walk(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &mut LocalInferenceEngine<CodeUnit>) {
183 let mut state = (ctx, bindings);
184 walk_tree_iterative(
185 node,
186 &mut state,
187 |node, (ctx, bindings)| {
188 if walk_enter(node, ctx, bindings) {
189 TreeWalkAction::DescendWithExit
190 } else {
191 TreeWalkAction::Descend
192 }
193 },
194 |(_, bindings)| bindings.exit_scope(),
195 );
196}
197
198fn walk_enter(
199 node: Node<'_>,
200 ctx: &mut CppScan<'_>,
201 bindings: &mut LocalInferenceEngine<CodeUnit>,
202) -> bool {
203 let enters_scope = SCOPE_NODES.contains(&node.kind());
204 if enters_scope {
205 bindings.enter_scope();
206 }
207 seed_declaration(node, ctx, bindings);
208 record_reference(node, ctx, bindings);
209 enters_scope
210}
211
212fn record_reference(
213 node: Node<'_>,
214 ctx: &mut CppScan<'_>,
215 bindings: &LocalInferenceEngine<CodeUnit>,
216) {
217 if recovered_c_new_expression_argument_at(
218 node,
219 node.start_byte(),
220 node.end_byte(),
221 ctx.analyzer.reference_uses_c_semantics(ctx.file),
222 )
223 .is_some()
224 {
225 return;
226 }
227 if node.kind() == "preproc_arg" {
228 record_object_macro_replacement_type_references(node, ctx);
229 }
230 if let Some(type_node) = ctx
231 .visibility
232 .function_macro_type_argument(ctx.file, node, ctx.source)
233 {
234 let name = node_text(type_node, ctx.source);
235 if bindings.is_shadowed(name) {
236 return;
237 }
238 match resolve_type_node_lexically(
239 type_node,
240 &ctx.analyzer,
241 ctx.visibility,
242 &ctx.ordinary_type_imports,
243 ctx.file,
244 ctx.source,
245 ) {
246 LexicalTypeResolution::Resolved { unit, .. } => {
247 ctx.record(unit.fq_name(), type_node);
248 }
249 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
250 ctx.record_unproven(name, type_node);
251 }
252 }
253 return;
254 }
255 if node
256 .parent()
257 .is_some_and(|parent| parent.kind() == "destructor_name")
258 {
259 return;
260 }
261 match ctx
262 .visibility
263 .resolve_ordinary_macro_reference(&ctx.analyzer, ctx.file, node, ctx.source)
264 {
265 OrdinaryMacroReferenceResolution::Resolved(macro_unit) => {
266 ctx.record(macro_unit.fq_name(), node);
267 return;
268 }
269 OrdinaryMacroReferenceResolution::Ambiguous => {
270 ctx.record_unproven(node_text(node, ctx.source), node);
271 return;
272 }
273 OrdinaryMacroReferenceResolution::Missing => {}
274 }
275 if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
276 record_recovered_macro_return_type_reference(return_type, ctx);
277 return;
278 }
279 if let Some(call) = recovered_relational_template_member_call(node) {
280 record_recovered_relational_template_member_call(call, ctx, bindings);
281 return;
282 }
283 if node.kind() == "using_declaration" {
284 let (resolution, type_node) =
285 if let Some(type_node) = using_enum_declaration_type_node(node) {
286 (
287 resolve_using_enum_declaration_owner(
288 node,
289 &ctx.analyzer,
290 ctx.visibility,
291 &ctx.ordinary_type_imports,
292 ctx.file,
293 ctx.source,
294 ),
295 type_node,
296 )
297 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
298 (
299 resolve_ordinary_using_declaration_owner(
300 node,
301 &ctx.analyzer,
302 ctx.visibility,
303 ctx.file,
304 ctx.source,
305 ),
306 type_node,
307 )
308 } else {
309 return;
310 };
311 match resolution {
312 LexicalTypeResolution::Resolved { unit, .. } => ctx.record(unit.fq_name(), type_node),
313 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
314 ctx.record_unproven(node_text(type_node, ctx.source), type_node);
315 }
316 }
317 return;
318 }
319 if has_ancestor_kind(node, "using_declaration") {
320 return;
321 }
322 if is_c_offsetof_member_node(node) {
323 record_c_offsetof_field_reference(node, ctx);
324 return;
325 }
326 if ctx.analyzer.reference_uses_c_semantics(ctx.file)
327 && node.kind() == "field_expression"
328 && !node.parent().is_some_and(|parent| {
329 parent.kind() == "call_expression"
330 && parent.child_by_field_name("function") == Some(node)
331 })
332 {
333 record_c_field_reference(node, ctx, bindings);
334 return;
335 }
336 if is_c_sizeof_expression_type_candidate(ctx.file, node) {
337 let name = node_text(node, ctx.source);
338 if bindings.is_shadowed(name) {
339 return;
340 }
341 match ctx.resolve_type_node_result(node) {
342 Ok(Some(unit)) => ctx.record(unit.fq_name(), node),
343 Ok(None) | Err(_) => ctx.record_unproven(name, node),
344 }
345 return;
346 }
347 if matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
348 && is_declaration_name(node)
349 && !is_recovered_qualified_friend_class_type_reference(node, ctx.source)
350 && let Some(owners) = out_of_line_member_definition_owner(
351 &ctx.analyzer,
352 ctx.visibility,
353 ctx.file,
354 ctx.source,
355 node,
356 )
357 {
358 for (owner_node, owner) in owners.owners {
359 ctx.record(owner.fq_name(), owner_node);
360 }
361 return;
362 }
363 if let Some(value) = qualified_callable_value(node) {
364 record_qualified_callable_value(
365 value.qualified,
366 value.global,
367 &value.owner_components,
368 value.member,
369 ctx,
370 );
371 return;
372 }
373 if matches!(node.kind(), "identifier" | "field_identifier")
374 && let Some(designator_owner) =
375 designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
376 {
377 let name = node_text(node, ctx.source);
378 match designator_owner {
379 DesignatedInitializerOwner::Resolved(owner) => {
380 if let Some(field) = ctx
381 .visibility
382 .visible_members_for_owner_name(ctx.file, &owner, name)
383 .into_iter()
384 .find(|unit| unit.is_field())
385 {
386 ctx.record(field.fq_name(), node);
387 }
388 }
389 DesignatedInitializerOwner::Unresolved => ctx.record_unproven(name, node),
390 }
391 return;
392 }
393 if ctx.analyzer.reference_uses_c_semantics(ctx.file)
398 && node.kind() == "field_expression"
399 && !node.parent().is_some_and(|parent| {
400 parent.kind() == "call_expression"
401 && parent.child_by_field_name("function") == Some(node)
402 })
403 {
404 record_c_field_reference(node, ctx, bindings);
405 return;
406 }
407 match node.kind() {
408 "namespace_identifier"
409 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) =>
410 {
411 record_recovered_macro_decorated_type_reference(node, type_node, ctx);
412 }
413 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type" => {
417 if is_declaration_name(node)
418 && !is_recovered_qualified_friend_class_type_reference(node, ctx.source)
419 {
420 if let Some(owners) = out_of_line_member_definition_owner(
421 &ctx.analyzer,
422 ctx.visibility,
423 ctx.file,
424 ctx.source,
425 node,
426 ) {
427 for (owner_node, owner) in owners.owners {
428 ctx.record(owner.fq_name(), owner_node);
429 }
430 }
431 return;
432 }
433 if is_nested_type_node(node) && !is_cpp_template_argument_type_leaf(node) {
434 record_nested_type_terminal_reference(node, ctx);
435 return;
436 }
437 if let Some(function) = scoped_free_function(node, ctx) {
440 ctx.record(function.fq_name(), function_terminal_node(node));
441 return;
442 }
443 if let Some(call) = node.parent().filter(|parent| {
444 parent.kind() == "call_expression"
445 && parent.child_by_field_name("function") == Some(node)
446 }) && let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
447 node,
448 &ctx.analyzer,
449 ctx.visibility,
450 &ctx.ordinary_type_imports,
451 ctx.file,
452 ctx.source,
453 ) {
454 let Some(call_arity) = ctx
455 .visibility
456 .call_arity_evidence(ctx.file, call, ctx.source)
457 .exact()
458 else {
459 ctx.record_unproven(node_text(node, ctx.source), function_terminal_node(node));
460 return;
461 };
462 if let VisibleMemberResolution::Callable(constructors) = ctx
463 .visibility
464 .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
465 && let Some(constructor) = constructors.iter().find(|constructor| {
466 cpp_callable_arity(&ctx.analyzer, constructor)
467 .is_none_or(|known| known.accepts(call_arity))
468 })
469 {
470 ctx.record(constructor.fq_name(), node);
471 } else {
472 ctx.record(unit.fq_name(), node);
473 }
474 return;
475 }
476 if let Some(owner) = scoped_call_owner(node, ctx) {
477 let member = scoped_call_member(node, ctx.source);
478 if !member.is_empty() {
479 ctx.record(format!("{owner}.{member}"), function_terminal_node(node));
480 return;
481 }
482 }
483 record_type_reference(node, ctx);
484 }
485 "call_expression" => record_call(node, ctx, bindings),
486 _ => {}
487 }
488}
489
490fn record_c_offsetof_field_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
491 let name = node_text(node, ctx.source);
492 let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
493 ctx.record_unproven(name, node);
494 return;
495 };
496 let owner = match resolve_type_node_lexically(
497 type_reference,
498 &ctx.analyzer,
499 ctx.visibility,
500 &ctx.ordinary_type_imports,
501 ctx.file,
502 ctx.source,
503 ) {
504 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
505 LexicalTypeResolution::Resolved { .. }
506 | LexicalTypeResolution::Ambiguous
507 | LexicalTypeResolution::Missing => {
508 ctx.record_unproven(name, member);
509 return;
510 }
511 };
512 let candidates = ctx
513 .visibility
514 .visible_members_for_owner_name(ctx.file, &owner, name)
515 .into_iter()
516 .filter(|candidate| candidate.is_field())
517 .collect::<Vec<_>>();
518 if candidates.len() == 1 {
519 ctx.record(candidates[0].fq_name(), member);
520 } else if candidates.len() > 1 {
521 ctx.record_unproven(name, member);
522 }
523}
524fn record_c_field_reference(
525 node: Node<'_>,
526 ctx: &mut CppScan<'_>,
527 bindings: &LocalInferenceEngine<CodeUnit>,
528) {
529 let Some(field) = node.child_by_field_name("field") else {
530 return;
531 };
532 let name = node_text(field, ctx.source);
533 if name.is_empty() {
534 return;
535 }
536 let Some(receiver) = cpp_field_expression_receiver(node) else {
537 ctx.record_unproven(name, field);
538 return;
539 };
540 let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) else {
541 ctx.record_unproven(name, field);
542 return;
543 };
544 let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
545 EnclosingMemberOwnerResolution::Owner(owner) => owner,
546 EnclosingMemberOwnerResolution::Ambiguous => {
547 ctx.record_unproven(name, field);
548 return;
549 }
550 EnclosingMemberOwnerResolution::Missing => return,
551 };
552 let fields = ctx
553 .visibility
554 .visible_members_for_owner_name(ctx.file, &owner, name)
555 .into_iter()
556 .filter(|candidate| {
557 candidate.is_field()
558 && (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
559 || ctx.visibility.declaration_visible_at_reference(
560 &ctx.analyzer,
561 ctx.file,
562 candidate,
563 field,
564 ))
565 })
566 .collect::<Vec<_>>();
567 match fields.as_slice() {
568 [candidate] => ctx.record(candidate.fq_name(), field),
569 [] => {}
570 _ => {
571 if fields
572 .iter()
573 .skip(1)
574 .all(|candidate| same_visible_symbol(candidate, fields[0]))
575 {
576 ctx.record(fields[0].fq_name(), field);
577 } else {
578 ctx.record_unproven(name, field);
579 }
580 }
581 }
582}
583
584fn record_object_macro_replacement_type_references(node: Node<'_>, ctx: &mut CppScan<'_>) {
585 for reference in object_macro_replacement_type_references(node, ctx.source) {
586 for component_count in 1..=reference.components.len() {
587 let resolution = resolve_type_components_lexically_at(
588 node,
589 &reference.components[..component_count],
590 reference.global,
591 &ctx.analyzer,
592 ctx.visibility,
593 &ctx.ordinary_type_imports,
594 ctx.file,
595 ctx.source,
596 );
597 let unit = match resolution {
598 LexicalTypeResolution::Resolved { unit, .. } => unit,
599 LexicalTypeResolution::Missing => {
600 let Some(unit) = unique_macro_replacement_type_candidate(
601 &ctx.analyzer,
602 ctx.visibility,
603 ctx.file,
604 &reference.components[..component_count],
605 ) else {
606 continue;
607 };
608 unit
609 }
610 LexicalTypeResolution::Ambiguous => continue,
611 };
612 let range = &reference.component_ranges[component_count - 1];
613 ctx.record_range(unit.fq_name(), node, range.start, range.end);
614 }
615 }
616}
617
618fn record_recovered_relational_template_member_call(
619 call: crate::graph::resolver::RecoveredRelationalTemplateMemberCall<'_>,
620 ctx: &mut CppScan<'_>,
621 bindings: &LocalInferenceEngine<CodeUnit>,
622) {
623 let name = node_text(call.member, ctx.source);
624 let Some(receiver_owner) = receiver_type_unit(call.receiver, ctx, bindings, 32) else {
625 ctx.record_unproven(name, call.member);
626 return;
627 };
628 let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
629 EnclosingMemberOwnerResolution::Owner(owner) => owner,
630 EnclosingMemberOwnerResolution::Ambiguous => {
631 ctx.record_unproven(name, call.member);
632 return;
633 }
634 EnclosingMemberOwnerResolution::Missing => return,
635 };
636 let VisibleMemberResolution::Callable(callables) = ctx
637 .visibility
638 .visible_member_for_owner_name(ctx.file, &owner, name)
639 else {
640 return;
641 };
642 let mut candidates = callables.into_iter().filter(|candidate| {
643 cpp_callable_arity(&ctx.analyzer, candidate).is_none_or(|known| known.accepts(call.arity))
644 && ctx
645 .visibility
646 .callable_is_template_declaration(&ctx.analyzer, candidate)
647 });
648 let Some(candidate) = candidates.next() else {
649 return;
650 };
651 if candidates.next().is_some() {
652 ctx.record_unproven(name, call.member);
653 return;
654 }
655 ctx.record(candidate.fq_name(), call.member);
656}
657
658fn record_recovered_macro_return_type_reference(return_type: Node<'_>, ctx: &mut CppScan<'_>) {
659 let name = node_text(return_type, ctx.source);
660 let Some(scope) = recovered_or_indexed_lexical_scope(return_type, ctx) else {
661 ctx.record_unproven(name, return_type);
662 return;
663 };
664 let components = [name.to_string()];
665 if let LexicalTypeResolution::Resolved { unit, .. } = ctx
666 .visibility
667 .resolve_type_components_lexically(&ctx.analyzer, ctx.file, &components, false, &scope)
668 {
669 ctx.record(unit.fq_name(), return_type);
670 return;
671 }
672
673 let mut aliases = ctx
674 .visibility
675 .visible_identifier_candidates(ctx.file, name)
676 .filter(|candidate| {
677 ctx.analyzer
678 .type_alias_provider()
679 .is_some_and(|provider| provider.is_type_alias(candidate))
680 && ctx.visibility.external_type_candidate_visible_in_context(
681 &ctx.analyzer,
682 ctx.file,
683 candidate,
684 return_type,
685 )
686 })
687 .filter_map(|candidate| {
688 let owner = ctx.analyzer.parent_of(candidate)?;
689 let owner_components = canonical_cpp_scope_components(&owner);
690 scope
691 .starts_with(&owner_components)
692 .then_some((candidate, owner_components.len()))
693 })
694 .collect::<Vec<_>>();
695 let deepest = aliases.iter().map(|(_, depth)| *depth).max();
696 aliases.retain(|(_, depth)| Some(*depth) == deepest);
697 if aliases.len() == 1 {
698 ctx.record(aliases[0].0.fq_name(), return_type);
699 } else {
700 ctx.record_unproven(name, return_type);
701 }
702}
703
704fn record_type_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
709 let ordinary_resolution = resolve_type_node_lexically(
710 node,
711 &ctx.analyzer,
712 ctx.visibility,
713 &ctx.ordinary_type_imports,
714 ctx.file,
715 ctx.source,
716 );
717 let resolution = match recovered_sentinel_type_resolution(node, ctx) {
718 Some(recovered @ LexicalTypeResolution::Resolved { .. }) => recovered,
719 Some(LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing) | None => {
720 ordinary_resolution
721 }
722 };
723 let resolution = resolve_inverted_type_node(node, ctx, resolution);
724 match resolution {
725 LexicalTypeResolution::Resolved { unit, .. } => {
726 let callee = unit.fq_name();
727 let hit = type_reference_hit_node(node);
728 ctx.record(callee.clone(), hit);
729 if initialized_type_declaration_with_cast(node) {
730 ctx.record(callee.clone(), function_terminal_node(hit));
731 }
732 if qualified_alias_reference_requires_terminal(
733 qualified_alias_reference_preserves_target(
734 node,
735 &unit,
736 &ctx.analyzer,
737 ctx.visibility,
738 ctx.file,
739 ctx.source,
740 ),
741 ) {
742 let terminal = function_terminal_node(node);
743 if terminal.start_byte() != node.start_byte()
744 || terminal.end_byte() != node.end_byte()
745 {
746 ctx.record(callee, terminal);
747 }
748 }
749 }
750 LexicalTypeResolution::Missing => {
751 if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
752 &ctx.analyzer,
753 ctx.file,
754 node,
755 ctx.source,
756 ) {
757 ctx.record(unit.fq_name(), node);
758 }
759 }
760 LexicalTypeResolution::Ambiguous => {}
761 }
762}
763
764fn record_nested_type_terminal_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
765 let Some(qualified) = node.parent().filter(|parent| {
766 matches!(
767 parent.kind(),
768 "qualified_identifier" | "scoped_type_identifier"
769 ) && parent.child_by_field_name("name") == Some(node)
770 }) else {
771 return;
772 };
773 let Some(owner) = qualified_owner_components(qualified, ctx.source) else {
774 return;
775 };
776 let mut complete = qualified;
777 while let Some(parent) = complete.parent().filter(|parent| {
778 matches!(
779 parent.kind(),
780 "qualified_identifier" | "scoped_type_identifier"
781 )
782 }) {
783 complete = parent;
784 }
785 if matches!(
786 resolve_type_node_lexically(
787 complete,
788 &ctx.analyzer,
789 ctx.visibility,
790 &ctx.ordinary_type_imports,
791 ctx.file,
792 ctx.source,
793 ),
794 LexicalTypeResolution::Resolved { .. }
795 ) {
796 return;
797 }
798
799 let Some(enclosing_owner) = enclosing_callable_owner(node, ctx) else {
800 return;
801 };
802 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
803 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
804 &ctx.analyzer,
805 ctx.file,
806 &owner.names,
807 owner.global,
808 &lexical_scope,
809 );
810 let owner_unit = match owner_resolution {
811 LexicalTypeResolution::Resolved { unit, .. } => unit,
812 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
813 let Some(unit) = ctx.visibility.inherited_injected_class_owner(
814 &ctx.analyzer,
815 ctx.file,
816 &enclosing_owner,
817 &owner.names[0],
818 ) else {
819 return;
820 };
821 unit
822 }
823 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return,
824 };
825 let name = node_text(node, ctx.source);
826 let mut candidates = Vec::new();
827 for candidate in ctx
828 .visibility
829 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
830 .into_iter()
831 .filter(|candidate| {
832 candidate.is_class()
833 || ctx
834 .analyzer
835 .type_alias_provider()
836 .is_some_and(|provider| provider.is_type_alias(candidate))
837 })
838 .filter(|candidate| {
839 ctx.visibility.external_type_candidate_visible_in_context(
840 &ctx.analyzer,
841 ctx.file,
842 candidate,
843 node,
844 )
845 })
846 {
847 if !candidates
848 .iter()
849 .any(|existing| same_logical_symbol(existing, candidate))
850 {
851 candidates.push(candidate.clone());
852 }
853 }
854 if let [candidate] = candidates.as_slice() {
855 ctx.record(candidate.fq_name(), complete);
856 }
857}
858
859fn recovered_sentinel_type_resolution(
860 node: Node<'_>,
861 ctx: &CppScan<'_>,
862) -> Option<LexicalTypeResolution> {
863 let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
864 let components = cpp_type_name_components(node, ctx.source)?;
865 let global = is_globally_qualified_cpp_name(node);
866 Some(ctx.visibility.resolve_type_components_lexically(
867 &ctx.analyzer,
868 ctx.file,
869 &components,
870 global,
871 &scope,
872 ))
873}
874
875fn recovered_or_indexed_lexical_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<Vec<String>> {
881 ctx.recovered_sentinel_scope(node).or_else(|| {
882 match enclosing_lexical_scope_components(
883 node,
884 &ctx.analyzer,
885 ctx.visibility,
886 ctx.file,
887 ctx.source,
888 ) {
889 LexicalScopeResolution::Resolved(scope) => Some(scope),
890 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
891 }
892 })
893}
894
895fn record_recovered_macro_decorated_type_reference(
902 scope_node: Node<'_>,
903 type_node: Node<'_>,
904 ctx: &mut CppScan<'_>,
905) {
906 let mut resolved = Vec::new();
907 for candidate in [scope_node, type_node] {
908 if resolved
909 .iter()
910 .any(|(_, existing): &(CodeUnit, Node<'_>)| same_node(*existing, candidate))
911 {
912 continue;
913 }
914 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
915 candidate,
916 &ctx.analyzer,
917 ctx.visibility,
918 &ctx.ordinary_type_imports,
919 ctx.file,
920 ctx.source,
921 ) {
922 resolved.push((unit, candidate));
923 }
924 }
925 let [(unit, candidate)] = resolved.as_slice() else {
926 return;
927 };
928 ctx.record(unit.fq_name(), type_reference_hit_node(*candidate));
929}
930
931fn resolve_inverted_type_node(
939 node: Node<'_>,
940 ctx: &CppScan<'_>,
941 resolution: LexicalTypeResolution,
942) -> LexicalTypeResolution {
943 let Some(arguments) = cpp_template_reference_arguments(node, ctx.source) else {
944 return resolution;
945 };
946 let LexicalTypeResolution::Resolved {
947 unit,
948 components,
949 candidates,
950 } = resolution
951 else {
952 return resolution;
953 };
954
955 let mut specialized = Vec::new();
956 for candidate in candidates.iter().chain(std::iter::once(&unit)) {
957 if let Ok(resolved) =
958 ctx.visibility
959 .resolve_template_arguments(ctx.file, candidate.clone(), &arguments)
960 && !specialized
961 .iter()
962 .any(|existing: &CodeUnit| same_visible_symbol(existing, &resolved))
963 {
964 specialized.push(resolved);
965 }
966 }
967 match specialized.as_slice() {
968 [specialized] => LexicalTypeResolution::Resolved {
969 unit: specialized.clone(),
970 components,
971 candidates,
972 },
973 _ => LexicalTypeResolution::Resolved {
974 unit,
975 components,
976 candidates,
977 },
978 }
979}
980
981fn record_qualified_callable_value(
982 qualified: Node<'_>,
983 global: bool,
984 owner_components: &[Node<'_>],
985 member_node: Node<'_>,
986 ctx: &mut CppScan<'_>,
987) {
988 let member_name = node_text(member_node, ctx.source);
989 if member_name.is_empty() {
990 return;
991 }
992 let owner_components = owner_components
993 .iter()
994 .map(|component| node_text(*component, ctx.source))
995 .map(str::to_string)
996 .collect::<Vec<_>>();
997 let lexical_scope = if global {
998 Vec::new()
999 } else {
1000 match enclosing_lexical_scope_components(
1001 qualified,
1002 &ctx.analyzer,
1003 ctx.visibility,
1004 ctx.file,
1005 ctx.source,
1006 ) {
1007 LexicalScopeResolution::Resolved(scope) => scope,
1008 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
1009 ctx.record_unproven(member_name, member_node);
1010 return;
1011 }
1012 }
1013 };
1014 let owner = match ctx.visibility.resolve_callable_value_components_lexically(
1015 &ctx.analyzer,
1016 ctx.file,
1017 &owner_components,
1018 member_name,
1019 global,
1020 &lexical_scope,
1021 ) {
1022 LexicalCallableValueResolution::Type(owner) => owner,
1023 LexicalCallableValueResolution::FreeFunction(function) => {
1024 ctx.record(function.fq_name(), member_node);
1025 return;
1026 }
1027 LexicalCallableValueResolution::Ambiguous => {
1028 ctx.record_unproven(member_name, member_node);
1029 return;
1030 }
1031 LexicalCallableValueResolution::Missing => match resolve_type_components_lexically_at(
1032 qualified,
1033 &owner_components,
1034 global,
1035 &ctx.analyzer,
1036 ctx.visibility,
1037 &ctx.ordinary_type_imports,
1038 ctx.file,
1039 ctx.source,
1040 ) {
1041 LexicalTypeResolution::Resolved { unit, .. } => unit,
1042 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
1043 ctx.record_unproven(member_name, member_node);
1044 return;
1045 }
1046 },
1047 };
1048 match ctx
1049 .visibility
1050 .visible_member_for_owner_name(ctx.file, &owner, member_name)
1051 {
1052 VisibleMemberResolution::Callable(callables) => {
1053 if let Some(callable) = callables.first() {
1054 ctx.record(callable.fq_name(), member_node);
1055 }
1056 }
1057 VisibleMemberResolution::NonCallable => {}
1061 VisibleMemberResolution::AmbiguousKind | VisibleMemberResolution::Missing => {
1062 ctx.record_unproven(member_name, member_node);
1063 }
1064 }
1065}
1066
1067fn record_call(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &LocalInferenceEngine<CodeUnit>) {
1068 let Some(function) = node.child_by_field_name("function") else {
1069 return;
1070 };
1071 let Some(call_arity) = ctx
1072 .visibility
1073 .call_arity_evidence(ctx.file, node, ctx.source)
1074 .exact()
1075 else {
1076 let name_node = function
1077 .child_by_field_name("field")
1078 .or_else(|| function.child_by_field_name("name"))
1079 .unwrap_or(function);
1080 let name = node_text(name_node, ctx.source);
1081 if !name.is_empty() {
1082 ctx.record_unproven(name, name_node);
1083 }
1084 return;
1085 };
1086 match function.kind() {
1087 "field_expression" => {
1089 let Some(field) = function.child_by_field_name("field") else {
1090 return;
1091 };
1092 let name = node_text(field, ctx.source);
1093 if name.is_empty() {
1094 return;
1095 }
1096 let Some(receiver) = function
1097 .child_by_field_name("argument")
1098 .or_else(|| function.named_child(0))
1099 else {
1100 return;
1101 };
1102 if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
1103 ctx.record_unproven(name, field);
1109 return;
1110 }
1111 if let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) {
1112 let ordinary = resolve_declaring_member_owner_cached(ctx, &receiver_owner, name);
1113 match resolve_declaring_callable_owner(
1114 &ctx.analyzer,
1115 ctx.visibility,
1116 ctx.file,
1117 ordinary,
1118 name,
1119 call_arity,
1120 ) {
1121 EnclosingMemberOwnerResolution::Owner(owner) => {
1122 match ctx
1123 .visibility
1124 .visible_member_for_owner_name(ctx.file, &owner, name)
1125 {
1126 VisibleMemberResolution::Callable(callables) => {
1127 if let Some(callable) = callables.iter().find(|callable| {
1128 cpp_callable_arity(&ctx.analyzer, callable)
1129 .is_none_or(|known| known.accepts(call_arity))
1130 }) {
1131 ctx.record(callable.fq_name(), field);
1132 }
1133 }
1134 VisibleMemberResolution::AmbiguousKind => {
1135 ctx.record_unproven(name, field);
1136 }
1137 VisibleMemberResolution::NonCallable
1138 | VisibleMemberResolution::Missing => {}
1139 }
1140 }
1141 EnclosingMemberOwnerResolution::Ambiguous => {
1142 ctx.record_unproven(name, field);
1143 }
1144 EnclosingMemberOwnerResolution::Missing => {}
1145 }
1146 } else {
1147 ctx.record_unproven(name, field);
1148 }
1149 }
1150 "identifier" | "template_function" => {
1154 let terminal = super::resolver::function_terminal_node(function);
1155 let name = node_text(terminal, ctx.source);
1156 if name.is_empty() {
1157 return;
1158 }
1159 if bindings.is_shadowed(name) {
1160 return;
1161 }
1162 if let Some(enclosing_owner) = enclosing_callable_owner(function, ctx) {
1163 let ordinary = resolve_declaring_member_owner_cached(ctx, &enclosing_owner, name);
1164 match resolve_declaring_callable_owner(
1165 &ctx.analyzer,
1166 ctx.visibility,
1167 ctx.file,
1168 ordinary,
1169 name,
1170 call_arity,
1171 ) {
1172 EnclosingMemberOwnerResolution::Owner(owner)
1173 if !same_visible_symbol(&owner, &enclosing_owner) =>
1174 {
1175 match ctx
1176 .visibility
1177 .visible_member_for_owner_name(ctx.file, &owner, name)
1178 {
1179 VisibleMemberResolution::Callable(callables) => {
1180 if let Some(callable) = callables.iter().find(|callable| {
1181 cpp_callable_arity(&ctx.analyzer, callable)
1182 .is_none_or(|known| known.accepts(call_arity))
1183 }) {
1184 ctx.record(callable.fq_name(), function);
1185 }
1186 }
1187 VisibleMemberResolution::AmbiguousKind => {
1188 ctx.record_unproven(name, function);
1189 }
1190 VisibleMemberResolution::NonCallable
1191 | VisibleMemberResolution::Missing => {}
1192 }
1193 return;
1194 }
1195 EnclosingMemberOwnerResolution::Owner(_) => {
1196 ctx.record_unproven(name, function);
1205 return;
1206 }
1207 EnclosingMemberOwnerResolution::Ambiguous => {
1208 ctx.record_unproven(name, function);
1209 return;
1210 }
1211 EnclosingMemberOwnerResolution::Missing => {}
1212 }
1213 }
1214 let resolution = resolve_bare_call_target(
1215 node,
1216 function,
1217 &ctx.analyzer,
1218 ctx.visibility,
1219 &ctx.ordinary_type_imports,
1220 ctx.file,
1221 ctx.source,
1222 );
1223 match resolution {
1224 BareCallTargetResolution::FreeFunctions(units) => {
1225 let mut recorded = HashSet::default();
1226 for unit in units {
1227 let fq_name = unit.fq_name();
1228 if recorded.insert(fq_name.clone()) {
1229 ctx.record(fq_name, terminal);
1230 }
1231 }
1232 }
1233 BareCallTargetResolution::Type(unit) => {
1234 if let VisibleMemberResolution::Callable(constructors) = ctx
1235 .visibility
1236 .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
1237 && let Some(constructor) = constructors.iter().find(|constructor| {
1238 cpp_callable_arity(&ctx.analyzer, constructor)
1239 .is_none_or(|known| known.accepts(call_arity))
1240 })
1241 {
1242 ctx.record(constructor.fq_name(), terminal);
1243 } else {
1244 ctx.record(unit.fq_name(), function);
1245 }
1246 }
1247 BareCallTargetResolution::UnprovenFreeFunctions(_)
1248 | BareCallTargetResolution::CallableShadow
1249 | BareCallTargetResolution::Ambiguous => {}
1250 BareCallTargetResolution::Missing => {}
1251 }
1252 }
1255 _ => {}
1256 }
1257}
1258
1259fn resolve_declaring_member_owner_cached(
1260 ctx: &mut CppScan<'_>,
1261 receiver_owner: &CodeUnit,
1262 name: &str,
1263) -> EnclosingMemberOwnerResolution {
1264 if let Some(cached) = ctx
1265 .declaring_member_cache
1266 .get(receiver_owner)
1267 .and_then(|by_name| by_name.get(name))
1268 .cloned()
1269 {
1270 return cached;
1271 }
1272 let resolution = resolve_declaring_member_owner(
1273 &ctx.analyzer,
1274 ctx.visibility,
1275 ctx.file,
1276 receiver_owner,
1277 name,
1278 );
1279 ctx.declaring_member_cache
1280 .entry(receiver_owner.clone())
1281 .or_default()
1282 .insert(name.to_string(), resolution.clone());
1283 resolution
1284}
1285
1286fn enclosing_callable_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1287 let mut current = node.parent();
1288 while let Some(parent) = current {
1289 if parent.kind() == "function_definition" {
1290 let declarator = parent.child_by_field_name("declarator")?;
1291 let function = declarator_name_node(declarator)?;
1292 if let Some(owners) = out_of_line_member_definition_owner(
1293 &ctx.analyzer,
1294 ctx.visibility,
1295 ctx.file,
1296 ctx.source,
1297 function,
1298 ) && let Some((_, owner)) = owners.innermost()
1299 {
1300 return Some(owner.clone());
1301 }
1302 break;
1303 }
1304 current = parent.parent();
1305 }
1306 ctx.enclosing_class(node.start_byte()).and_then(|fqn| {
1307 ctx.analyzer
1308 .definitions(fqn)
1309 .find(|candidate| candidate.is_class())
1310 })
1311}
1312
1313fn scoped_free_function(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1315 let node = scoped_call_function(node)?;
1316 ctx.visibility.resolve_named(
1317 ctx.file,
1318 node_text(node, ctx.source),
1319 TargetKind::FreeFunction,
1320 )
1321}
1322
1323fn scoped_call_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<String> {
1325 let node = scoped_call_function(node)?;
1326 let scope = node.child_by_field_name("scope")?;
1327 match resolve_type_node_lexically(
1328 scope,
1329 &ctx.analyzer,
1330 ctx.visibility,
1331 &ctx.ordinary_type_imports,
1332 ctx.file,
1333 ctx.source,
1334 ) {
1335 LexicalTypeResolution::Resolved { unit, .. } => Some(unit.fq_name()),
1336 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1337 }
1338}
1339
1340fn scoped_call_function(node: Node<'_>) -> Option<Node<'_>> {
1341 if node.kind() != "qualified_identifier" {
1342 return None;
1343 }
1344 let parent = node.parent()?;
1345 if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1346 return None;
1347 }
1348 Some(node)
1349}
1350
1351fn scoped_call_member(node: Node<'_>, source: &str) -> String {
1353 node.child_by_field_name("name")
1354 .map(|name| node_text(name, source).to_string())
1355 .unwrap_or_default()
1356}
1357
1358fn receiver_type_unit(
1359 receiver: Node<'_>,
1360 ctx: &CppScan<'_>,
1361 bindings: &LocalInferenceEngine<CodeUnit>,
1362 remaining_call_depth: usize,
1363) -> Option<CodeUnit> {
1364 match receiver.kind() {
1365 "identifier" => {
1366 let name = node_text(receiver, ctx.source);
1367 if let Some(binding) = ctx.visibility.macro_local_binding_at(
1373 ctx.file,
1374 ctx.input.root(),
1375 ctx.source,
1376 receiver.start_byte(),
1377 receiver.end_byte(),
1378 ) && binding.name == name
1379 {
1380 return binding
1381 .proven_unit
1382 .or_else(|| {
1383 binding.type_node.and_then(|type_node| {
1384 resolve_type_node_with_recovered_scope(type_node, ctx)
1385 })
1386 })
1387 .or_else(|| {
1388 binding.type_node.and_then(|type_node| {
1389 ctx.resolve_type_node_result(type_node).ok().flatten()
1390 })
1391 })
1392 .or_else(|| ctx.resolve_type(&normalize_type_text(&binding.type_name)));
1393 }
1394 first_precise(bindings, name).or_else(|| {
1398 if !bindings.is_shadowed(name)
1399 && let Some(unit) = global_receiver_type_unit(ctx, name)
1400 {
1401 return Some(unit);
1402 }
1403 (!bindings.is_shadowed(name))
1404 .then(|| resolve_type_node_with_recovered_scope(receiver, ctx))
1405 .flatten()
1406 .or_else(|| {
1407 (!bindings.is_shadowed(name))
1408 .then(|| ctx.resolve_type(name))
1409 .flatten()
1410 })
1411 })
1412 }
1413 "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
1414 first_precise(bindings, node_text(receiver, ctx.source))
1415 }
1416 "this" => ctx.enclosing_class(receiver.start_byte()).and_then(|fqn| {
1417 ctx.analyzer
1418 .definitions(fqn)
1419 .find(|candidate| candidate.is_class())
1420 }),
1421 "parenthesized_expression" | "pointer_expression" => receiver
1423 .child_by_field_name("argument")
1424 .or_else(|| receiver.named_child(0))
1425 .and_then(|inner| receiver_type_unit(inner, ctx, bindings, remaining_call_depth)),
1426 "call_expression" if remaining_call_depth > 0 => infer_cpp_initializer_binding(
1427 &ctx.analyzer,
1428 ctx.visibility,
1429 ctx.file,
1430 ctx.source,
1431 receiver,
1432 Some(&|inner, _source| {
1433 receiver_type_unit(inner, ctx, bindings, remaining_call_depth - 1)
1434 .into_iter()
1435 .collect()
1436 }),
1437 )
1438 .and_then(|binding| binding.unit),
1439 "field_expression" => {
1444 let field = receiver.child_by_field_name("field")?;
1445 let name = node_text(field, ctx.source);
1446 let inner = cpp_field_expression_receiver(receiver)?;
1447 let receiver_owner = receiver_type_unit(inner, ctx, bindings, remaining_call_depth)?;
1448 let owner = match resolve_declaring_member_owner(
1449 &ctx.analyzer,
1450 ctx.visibility,
1451 ctx.file,
1452 &receiver_owner,
1453 name,
1454 ) {
1455 EnclosingMemberOwnerResolution::Owner(owner) => owner,
1456 EnclosingMemberOwnerResolution::Ambiguous
1457 | EnclosingMemberOwnerResolution::Missing => return None,
1458 };
1459 let fields = ctx
1460 .visibility
1461 .visible_members_for_owner_name(ctx.file, &owner, name)
1462 .into_iter()
1463 .filter(|candidate| candidate.is_field())
1464 .collect::<Vec<_>>();
1465 let [field_unit] = fields.as_slice() else {
1466 return None;
1467 };
1468 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field_unit)
1469 .and_then(|binding| binding.unit)
1470 }
1471 "cast_expression" => cast_expression_type_node(receiver).and_then(|type_node| {
1472 resolve_type_node_with_recovered_scope(type_node, ctx).or_else(|| {
1473 match ctx.resolve_type_node_result(type_node) {
1474 Ok(Some(unit)) => Some(unit),
1475 Ok(None) => ctx.resolve_type(node_text(type_node, ctx.source)),
1476 Err(_) => None,
1477 }
1478 })
1479 }),
1480 _ => None,
1481 }
1482}
1483
1484fn global_receiver_type_unit(ctx: &CppScan<'_>, name: &str) -> Option<CodeUnit> {
1485 let fields = ctx
1486 .visibility
1487 .named_candidates(ctx.file, name, TargetKind::GlobalField)
1488 .into_iter()
1489 .filter(has_persisted_global_field_identity)
1490 .collect::<Vec<_>>();
1491 let first = fields.first()?;
1492 let mut linkage_cache = HashMap::default();
1493 if fields.iter().skip(1).any(|field| {
1494 !same_visible_global_field_symbol(&ctx.analyzer, &mut linkage_cache, first, field)
1495 }) {
1496 return None;
1497 }
1498 let mut units = fields
1499 .into_iter()
1500 .filter_map(|field| {
1501 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, &field)
1502 .and_then(|binding| binding.unit)
1503 })
1504 .collect::<Vec<_>>();
1505 units.dedup_by(|left, right| same_visible_symbol(left, right));
1506 match units.as_slice() {
1507 [unit] => Some(unit.clone()),
1508 _ => None,
1509 }
1510}
1511
1512fn seed_declaration(
1513 node: Node<'_>,
1514 ctx: &mut CppScan<'_>,
1515 bindings: &mut LocalInferenceEngine<CodeUnit>,
1516) {
1517 if recovered_macro_return_type_node(node, ctx.source).is_some()
1518 || crate::declarations::is_direct_recovered_exported_class_field_declaration(
1519 node, ctx.source,
1520 )
1521 {
1522 return;
1523 }
1524 match node.kind() {
1525 "parameter_declaration" | "optional_parameter_declaration" => {
1526 seed_typed_binding(node, ctx, bindings)
1527 }
1528 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx, bindings),
1529 "for_range_loop" => seed_range_binding(node, ctx, bindings),
1530 "expression_statement" => seed_function_macro_local_binding(node, ctx, bindings),
1531 "assignment_expression" => seed_function_macro_container_binding(node, ctx, bindings),
1532 _ => {}
1533 }
1534}
1535
1536fn seed_function_macro_local_binding(
1537 node: Node<'_>,
1538 ctx: &CppScan<'_>,
1539 bindings: &mut LocalInferenceEngine<CodeUnit>,
1540) {
1541 let Some(binding) = ctx
1542 .visibility
1543 .function_macro_local_binding(ctx.file, node, ctx.source)
1544 else {
1545 return;
1546 };
1547 let unit = binding
1548 .type_node
1549 .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1550 .or_else(|| {
1551 binding
1552 .type_node
1553 .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1554 })
1555 .or_else(|| ctx.resolve_type(&binding.type_name));
1556 bindings.seed_or_shadow(binding.name, unit);
1557}
1558
1559fn seed_function_macro_container_binding(
1560 node: Node<'_>,
1561 ctx: &CppScan<'_>,
1562 bindings: &mut LocalInferenceEngine<CodeUnit>,
1563) {
1564 let Some(binding) =
1565 ctx.visibility
1566 .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
1567 else {
1568 return;
1569 };
1570 let unit = binding.proven_unit.or_else(|| {
1571 binding
1572 .type_node
1573 .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1574 .or_else(|| {
1575 binding
1576 .type_node
1577 .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1578 })
1579 .or_else(|| ctx.resolve_type(&binding.type_name))
1580 });
1581 bindings.seed_or_shadow(binding.name, unit);
1582}
1583
1584fn seed_typed_binding(
1585 node: Node<'_>,
1586 ctx: &CppScan<'_>,
1587 bindings: &mut LocalInferenceEngine<CodeUnit>,
1588) {
1589 if !parameter_belongs_to_callable_scope(node) {
1590 return;
1591 }
1592 let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
1593 return;
1594 };
1595 seed_binding(&name, type_node, None, ctx, bindings);
1596}
1597
1598fn seed_range_binding(
1599 node: Node<'_>,
1600 ctx: &CppScan<'_>,
1601 bindings: &mut LocalInferenceEngine<CodeUnit>,
1602) {
1603 let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
1604 return;
1605 };
1606 seed_binding(&name, type_node, None, ctx, bindings);
1607}
1608
1609fn seed_variable_declaration(
1610 node: Node<'_>,
1611 ctx: &CppScan<'_>,
1612 bindings: &mut LocalInferenceEngine<CodeUnit>,
1613) {
1614 let type_node = node
1615 .child_by_field_name("type")
1616 .or_else(|| first_type_child(node));
1617 let type_text =
1618 type_node.map(|type_node| normalize_type_text(node_text(type_node, ctx.source)));
1619 let mut cursor = node.walk();
1620 for child in node.named_children(&mut cursor) {
1621 let declarator = declaration_declarator(node, child);
1622 let Some(declarator) = declarator else {
1623 continue;
1624 };
1625 if declarator.kind() == "function_declarator"
1626 && !constructor_style_local_declaration(
1627 ctx.visibility,
1628 ctx.file,
1629 ctx.source,
1630 declarator,
1631 type_text.as_deref(),
1632 bindings,
1633 )
1634 {
1635 if node.kind() == "declaration"
1636 && has_function_scope_ancestor(node)
1637 && let Some(name) = extract_variable_name(declarator, ctx.source)
1638 {
1639 bindings.declare_shadow(name);
1640 }
1641 continue;
1642 }
1643 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1644 continue;
1645 };
1646 let value = child.child_by_field_name("value");
1647 seed_binding(&name, type_node, value, ctx, bindings);
1648 }
1649}
1650
1651use crate::graph::syntax::has_function_scope_ancestor;
1652
1653fn seed_binding(
1654 name: &str,
1655 type_node: Option<Node<'_>>,
1656 value: Option<Node<'_>>,
1657 ctx: &CppScan<'_>,
1658 bindings: &mut LocalInferenceEngine<CodeUnit>,
1659) {
1660 if name.is_empty() {
1661 return;
1662 }
1663 let declared_type =
1667 type_node.filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto");
1668 let resolved = match declared_type {
1669 Some(node) => resolve_type_node_with_recovered_scope(node, ctx).or_else(|| {
1670 match ctx.resolve_type_node_result(node) {
1671 Ok(Some(unit)) => Some(unit),
1672 Ok(None) => ctx.resolve_type(node_text(node, ctx.source)),
1673 Err(_) => None,
1674 }
1675 }),
1676 None => value.and_then(|value| infer_type_from_value(value, ctx)),
1677 };
1678 bindings.seed_or_shadow(name.to_string(), resolved);
1679}
1680
1681fn resolve_type_node_with_recovered_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1682 if let Some(owner) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
1683 return Some(owner);
1684 }
1685 match recovered_sentinel_type_resolution(node, ctx)? {
1686 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
1687 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1688 }
1689}
1690
1691fn infer_type_from_value(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1693 infer_cpp_initializer_type(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
1694}