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