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)
471 .is_none_or(|known| known.accepts(call_arity))
472 })
473 {
474 ctx.record(constructor.fq_name(), node);
475 } else {
476 ctx.record(unit.fq_name(), node);
477 }
478 return;
479 }
480 if let Some(owner) = scoped_call_owner(node, ctx) {
481 let member = scoped_call_member(node, ctx.source);
482 if !member.is_empty() {
483 ctx.record(format!("{owner}.{member}"), function_terminal_node(node));
484 return;
485 }
486 }
487 record_type_reference(node, ctx);
488 }
489 "call_expression" => record_call(node, ctx, bindings),
490 _ => {}
491 }
492}
493
494fn record_c_offsetof_field_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
495 let name = node_text(node, ctx.source);
496 let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
497 ctx.record_unproven(name, node);
498 return;
499 };
500 let owner = match resolve_type_node_lexically(
501 type_reference,
502 &ctx.analyzer,
503 ctx.visibility,
504 &ctx.ordinary_type_imports,
505 ctx.file,
506 ctx.source,
507 ) {
508 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
509 LexicalTypeResolution::Resolved { .. }
510 | LexicalTypeResolution::Ambiguous
511 | LexicalTypeResolution::Missing => {
512 ctx.record_unproven(name, member);
513 return;
514 }
515 };
516 let candidates = ctx
517 .visibility
518 .visible_members_for_owner_name(ctx.file, &owner, name)
519 .into_iter()
520 .filter(|candidate| candidate.is_field())
521 .collect::<Vec<_>>();
522 if candidates.len() == 1 {
523 ctx.record(candidates[0].fq_name(), member);
524 } else if candidates.len() > 1 {
525 ctx.record_unproven(name, member);
526 }
527}
528fn record_c_field_reference(
529 node: Node<'_>,
530 ctx: &mut CppScan<'_>,
531 bindings: &LocalInferenceEngine<CodeUnit>,
532) {
533 let Some(field) = node.child_by_field_name("field") else {
534 return;
535 };
536 let name = node_text(field, ctx.source);
537 if name.is_empty() {
538 return;
539 }
540 let Some(receiver) = cpp_field_expression_receiver(node) else {
541 ctx.record_unproven(name, field);
542 return;
543 };
544 let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) else {
545 ctx.record_unproven(name, field);
546 return;
547 };
548 let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
549 EnclosingMemberOwnerResolution::Owner(owner) => owner,
550 EnclosingMemberOwnerResolution::Ambiguous => {
551 ctx.record_unproven(name, field);
552 return;
553 }
554 EnclosingMemberOwnerResolution::Missing => return,
555 };
556 let fields = ctx
557 .visibility
558 .visible_members_for_owner_name(ctx.file, &owner, name)
559 .into_iter()
560 .filter(|candidate| {
561 candidate.is_field()
562 && (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
563 || ctx.visibility.declaration_visible_at_reference(
564 &ctx.analyzer,
565 ctx.file,
566 candidate,
567 field,
568 ))
569 })
570 .collect::<Vec<_>>();
571 match fields.as_slice() {
572 [candidate] => ctx.record(candidate.fq_name(), field),
573 [] => {}
574 _ => {
575 if fields
576 .iter()
577 .skip(1)
578 .all(|candidate| same_visible_symbol(candidate, fields[0]))
579 {
580 ctx.record(fields[0].fq_name(), field);
581 } else {
582 ctx.record_unproven(name, field);
583 }
584 }
585 }
586}
587
588fn record_object_macro_replacement_type_references(node: Node<'_>, ctx: &mut CppScan<'_>) {
589 for reference in object_macro_replacement_type_references(node, ctx.source) {
590 for component_count in 1..=reference.components.len() {
591 let resolution = resolve_type_components_lexically_at(
592 node,
593 &reference.components[..component_count],
594 reference.global,
595 &ctx.analyzer,
596 ctx.visibility,
597 &ctx.ordinary_type_imports,
598 ctx.file,
599 ctx.source,
600 );
601 let unit = match resolution {
602 LexicalTypeResolution::Resolved { unit, .. } => unit,
603 LexicalTypeResolution::Missing => {
604 let Some(unit) = unique_macro_replacement_type_candidate(
605 &ctx.analyzer,
606 ctx.visibility,
607 ctx.file,
608 &reference.components[..component_count],
609 ) else {
610 continue;
611 };
612 unit
613 }
614 LexicalTypeResolution::Ambiguous => continue,
615 };
616 let range = &reference.component_ranges[component_count - 1];
617 ctx.record_range(unit.fq_name(), node, range.start, range.end);
618 }
619 }
620}
621
622fn record_recovered_relational_template_member_call(
623 call: crate::graph::resolver::RecoveredRelationalTemplateMemberCall<'_>,
624 ctx: &mut CppScan<'_>,
625 bindings: &LocalInferenceEngine<CodeUnit>,
626) {
627 let name = node_text(call.member, ctx.source);
628 let Some(receiver_owner) = receiver_type_unit(call.receiver, ctx, bindings, 32) else {
629 ctx.record_unproven(name, call.member);
630 return;
631 };
632 let owner = match resolve_declaring_member_owner_cached(ctx, &receiver_owner, name) {
633 EnclosingMemberOwnerResolution::Owner(owner) => owner,
634 EnclosingMemberOwnerResolution::Ambiguous => {
635 ctx.record_unproven(name, call.member);
636 return;
637 }
638 EnclosingMemberOwnerResolution::Missing => return,
639 };
640 let VisibleMemberResolution::Callable(callables) = ctx
641 .visibility
642 .visible_member_for_owner_name(ctx.file, &owner, name)
643 else {
644 return;
645 };
646 let mut candidates = callables.into_iter().filter(|candidate| {
647 cpp_callable_arity(&ctx.analyzer, candidate).is_none_or(|known| known.accepts(call.arity))
648 && ctx
649 .visibility
650 .callable_is_template_declaration(&ctx.analyzer, candidate)
651 });
652 let Some(candidate) = candidates.next() else {
653 return;
654 };
655 if candidates.next().is_some() {
656 ctx.record_unproven(name, call.member);
657 return;
658 }
659 ctx.record(candidate.fq_name(), call.member);
660}
661
662fn record_recovered_macro_return_type_reference(return_type: Node<'_>, ctx: &mut CppScan<'_>) {
663 let name = node_text(return_type, ctx.source);
664 let Some(scope) = recovered_or_indexed_lexical_scope(return_type, ctx) else {
665 ctx.record_unproven(name, return_type);
666 return;
667 };
668 let components = [name.to_string()];
669 if let LexicalTypeResolution::Resolved { unit, .. } = ctx
670 .visibility
671 .resolve_type_components_lexically(&ctx.analyzer, ctx.file, &components, false, &scope)
672 {
673 ctx.record(unit.fq_name(), return_type);
674 return;
675 }
676
677 let mut aliases = ctx
678 .visibility
679 .visible_identifier_candidates(ctx.file, name)
680 .filter(|candidate| {
681 ctx.analyzer
682 .type_alias_provider()
683 .is_some_and(|provider| provider.is_type_alias(candidate))
684 && ctx.visibility.external_type_candidate_visible_in_context(
685 &ctx.analyzer,
686 ctx.file,
687 candidate,
688 return_type,
689 )
690 })
691 .filter_map(|candidate| {
692 let owner = ctx.analyzer.parent_of(candidate)?;
693 let owner_components = canonical_cpp_scope_components(&owner);
694 scope
695 .starts_with(&owner_components)
696 .then_some((candidate, owner_components.len()))
697 })
698 .collect::<Vec<_>>();
699 let deepest = aliases.iter().map(|(_, depth)| *depth).max();
700 aliases.retain(|(_, depth)| Some(*depth) == deepest);
701 if aliases.len() == 1 {
702 ctx.record(aliases[0].0.fq_name(), return_type);
703 } else {
704 ctx.record_unproven(name, return_type);
705 }
706}
707
708fn record_type_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
713 let ordinary_resolution = resolve_type_node_lexically(
714 node,
715 &ctx.analyzer,
716 ctx.visibility,
717 &ctx.ordinary_type_imports,
718 ctx.file,
719 ctx.source,
720 );
721 let resolution = match recovered_sentinel_type_resolution(node, ctx) {
722 Some(recovered @ LexicalTypeResolution::Resolved { .. }) => recovered,
723 Some(LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing) | None => {
724 ordinary_resolution
725 }
726 };
727 let resolution = resolve_inverted_type_node(node, ctx, resolution);
728 match resolution {
729 LexicalTypeResolution::Resolved { unit, .. } => {
730 let callee = unit.fq_name();
731 let hit = type_reference_hit_node(node);
732 ctx.record(callee.clone(), hit);
733 if initialized_type_declaration_with_cast(node) {
734 ctx.record(callee.clone(), function_terminal_node(hit));
735 }
736 if qualified_alias_reference_requires_terminal(
737 qualified_alias_reference_preserves_target(
738 node,
739 &unit,
740 &ctx.analyzer,
741 ctx.visibility,
742 ctx.file,
743 ctx.source,
744 ),
745 ) {
746 let terminal = function_terminal_node(node);
747 if terminal.start_byte() != node.start_byte()
748 || terminal.end_byte() != node.end_byte()
749 {
750 ctx.record(callee, terminal);
751 }
752 }
753 }
754 LexicalTypeResolution::Missing => {
755 if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
756 &ctx.analyzer,
757 ctx.file,
758 node,
759 ctx.source,
760 ) {
761 ctx.record(unit.fq_name(), node);
762 }
763 }
764 LexicalTypeResolution::Ambiguous => {}
765 }
766}
767
768fn record_nested_type_terminal_reference(node: Node<'_>, ctx: &mut CppScan<'_>) {
769 let Some(qualified) = node.parent().filter(|parent| {
770 matches!(
771 parent.kind(),
772 "qualified_identifier" | "scoped_type_identifier"
773 ) && parent.child_by_field_name("name") == Some(node)
774 }) else {
775 return;
776 };
777 let Some(owner) = qualified_owner_components(qualified, ctx.source) else {
778 return;
779 };
780 let mut complete = qualified;
781 while let Some(parent) = complete.parent().filter(|parent| {
782 matches!(
783 parent.kind(),
784 "qualified_identifier" | "scoped_type_identifier"
785 )
786 }) {
787 complete = parent;
788 }
789 if matches!(
790 resolve_type_node_lexically(
791 complete,
792 &ctx.analyzer,
793 ctx.visibility,
794 &ctx.ordinary_type_imports,
795 ctx.file,
796 ctx.source,
797 ),
798 LexicalTypeResolution::Resolved { .. }
799 ) {
800 return;
801 }
802
803 let Some(enclosing_owner) = enclosing_callable_owner(node, ctx) else {
804 return;
805 };
806 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
807 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
808 &ctx.analyzer,
809 ctx.file,
810 &owner.names,
811 owner.global,
812 &lexical_scope,
813 );
814 let owner_unit = match owner_resolution {
815 LexicalTypeResolution::Resolved { unit, .. } => unit,
816 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
817 let Some(unit) = ctx.visibility.inherited_injected_class_owner(
818 &ctx.analyzer,
819 ctx.file,
820 &enclosing_owner,
821 &owner.names[0],
822 ) else {
823 return;
824 };
825 unit
826 }
827 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return,
828 };
829 let name = node_text(node, ctx.source);
830 let mut candidates = Vec::new();
831 for candidate in ctx
832 .visibility
833 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
834 .into_iter()
835 .filter(|candidate| {
836 candidate.is_class()
837 || ctx
838 .analyzer
839 .type_alias_provider()
840 .is_some_and(|provider| provider.is_type_alias(candidate))
841 })
842 .filter(|candidate| {
843 ctx.visibility.external_type_candidate_visible_in_context(
844 &ctx.analyzer,
845 ctx.file,
846 candidate,
847 node,
848 )
849 })
850 {
851 if !candidates
852 .iter()
853 .any(|existing| same_logical_symbol(existing, candidate))
854 {
855 candidates.push(candidate.clone());
856 }
857 }
858 if let [candidate] = candidates.as_slice() {
859 ctx.record(candidate.fq_name(), complete);
860 }
861}
862
863fn recovered_sentinel_type_resolution(
864 node: Node<'_>,
865 ctx: &CppScan<'_>,
866) -> Option<LexicalTypeResolution> {
867 let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
868 let components = cpp_type_name_components(node, ctx.source)?;
869 let global = is_globally_qualified_cpp_name(node);
870 Some(ctx.visibility.resolve_type_components_lexically(
871 &ctx.analyzer,
872 ctx.file,
873 &components,
874 global,
875 &scope,
876 ))
877}
878
879fn recovered_or_indexed_lexical_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<Vec<String>> {
885 ctx.recovered_sentinel_scope(node).or_else(|| {
886 match enclosing_lexical_scope_components(
887 node,
888 &ctx.analyzer,
889 ctx.visibility,
890 ctx.file,
891 ctx.source,
892 ) {
893 LexicalScopeResolution::Resolved(scope) => Some(scope),
894 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
895 }
896 })
897}
898
899fn record_recovered_macro_decorated_type_reference(
906 scope_node: Node<'_>,
907 type_node: Node<'_>,
908 ctx: &mut CppScan<'_>,
909) {
910 let mut resolved = Vec::new();
911 for candidate in [scope_node, type_node] {
912 if resolved
913 .iter()
914 .any(|(_, existing): &(CodeUnit, Node<'_>)| same_node(*existing, candidate))
915 {
916 continue;
917 }
918 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_type_node_lexically(
919 candidate,
920 &ctx.analyzer,
921 ctx.visibility,
922 &ctx.ordinary_type_imports,
923 ctx.file,
924 ctx.source,
925 ) {
926 resolved.push((unit, candidate));
927 }
928 }
929 let [(unit, candidate)] = resolved.as_slice() else {
930 return;
931 };
932 ctx.record(unit.fq_name(), type_reference_hit_node(*candidate));
933}
934
935fn resolve_inverted_type_node(
943 node: Node<'_>,
944 ctx: &CppScan<'_>,
945 resolution: LexicalTypeResolution,
946) -> LexicalTypeResolution {
947 let Some(arguments) = cpp_template_reference_arguments(node, ctx.source) else {
948 return resolution;
949 };
950 let LexicalTypeResolution::Resolved {
951 unit,
952 components,
953 candidates,
954 } = resolution
955 else {
956 return resolution;
957 };
958
959 let mut specialized = Vec::new();
960 for candidate in candidates.iter().chain(std::iter::once(&unit)) {
961 if let Ok(resolved) =
962 ctx.visibility
963 .resolve_template_arguments(ctx.file, candidate.clone(), &arguments)
964 && !specialized
965 .iter()
966 .any(|existing: &CodeUnit| same_visible_symbol(existing, &resolved))
967 {
968 specialized.push(resolved);
969 }
970 }
971 match specialized.as_slice() {
972 [specialized] => LexicalTypeResolution::Resolved {
973 unit: specialized.clone(),
974 components,
975 candidates,
976 },
977 _ => LexicalTypeResolution::Resolved {
978 unit,
979 components,
980 candidates,
981 },
982 }
983}
984
985fn record_qualified_callable_value(
986 qualified: Node<'_>,
987 global: bool,
988 owner_components: &[Node<'_>],
989 member_node: Node<'_>,
990 ctx: &mut CppScan<'_>,
991) {
992 let member_name = node_text(member_node, ctx.source);
993 if member_name.is_empty() {
994 return;
995 }
996 let owner_components = owner_components
997 .iter()
998 .map(|component| node_text(*component, ctx.source))
999 .map(str::to_string)
1000 .collect::<Vec<_>>();
1001 let lexical_scope = if global {
1002 Vec::new()
1003 } else {
1004 match enclosing_lexical_scope_components(
1005 qualified,
1006 &ctx.analyzer,
1007 ctx.visibility,
1008 ctx.file,
1009 ctx.source,
1010 ) {
1011 LexicalScopeResolution::Resolved(scope) => scope,
1012 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
1013 ctx.record_unproven(member_name, member_node);
1014 return;
1015 }
1016 }
1017 };
1018 let owner = match ctx.visibility.resolve_callable_value_components_lexically(
1019 &ctx.analyzer,
1020 ctx.file,
1021 &owner_components,
1022 member_name,
1023 global,
1024 &lexical_scope,
1025 ) {
1026 LexicalCallableValueResolution::Type(owner) => owner,
1027 LexicalCallableValueResolution::FreeFunction(function) => {
1028 ctx.record(function.fq_name(), member_node);
1029 return;
1030 }
1031 LexicalCallableValueResolution::Ambiguous => {
1032 ctx.record_unproven(member_name, member_node);
1033 return;
1034 }
1035 LexicalCallableValueResolution::Missing => match resolve_type_components_lexically_at(
1036 qualified,
1037 &owner_components,
1038 global,
1039 &ctx.analyzer,
1040 ctx.visibility,
1041 &ctx.ordinary_type_imports,
1042 ctx.file,
1043 ctx.source,
1044 ) {
1045 LexicalTypeResolution::Resolved { unit, .. } => unit,
1046 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
1047 ctx.record_unproven(member_name, member_node);
1048 return;
1049 }
1050 },
1051 };
1052 match ctx
1053 .visibility
1054 .visible_member_for_owner_name(ctx.file, &owner, member_name)
1055 {
1056 VisibleMemberResolution::Callable(callables) => {
1057 if let Some(callable) = callables.first() {
1058 ctx.record(callable.fq_name(), member_node);
1059 }
1060 }
1061 VisibleMemberResolution::NonCallable => {}
1065 VisibleMemberResolution::AmbiguousKind | VisibleMemberResolution::Missing => {
1066 ctx.record_unproven(member_name, member_node);
1067 }
1068 }
1069}
1070
1071fn record_call(node: Node<'_>, ctx: &mut CppScan<'_>, bindings: &LocalInferenceEngine<CodeUnit>) {
1072 let Some(function) = node.child_by_field_name("function") else {
1073 return;
1074 };
1075 let Some(call_arity) = ctx
1076 .visibility
1077 .call_arity_evidence(ctx.file, node, ctx.source)
1078 .exact()
1079 else {
1080 let name_node = function
1081 .child_by_field_name("field")
1082 .or_else(|| function.child_by_field_name("name"))
1083 .unwrap_or(function);
1084 let name = node_text(name_node, ctx.source);
1085 if !name.is_empty() {
1086 ctx.record_unproven(name, name_node);
1087 }
1088 return;
1089 };
1090 match function.kind() {
1091 "field_expression" => {
1093 let Some(field) = function.child_by_field_name("field") else {
1094 return;
1095 };
1096 let name = node_text(field, ctx.source);
1097 if name.is_empty() {
1098 return;
1099 }
1100 let Some(receiver) = function
1101 .child_by_field_name("argument")
1102 .or_else(|| function.named_child(0))
1103 else {
1104 return;
1105 };
1106 if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
1107 ctx.record_unproven(name, field);
1113 return;
1114 }
1115 if let Some(receiver_owner) = receiver_type_unit(receiver, ctx, bindings, 32) {
1116 let ordinary = resolve_declaring_member_owner_cached(ctx, &receiver_owner, name);
1117 match resolve_declaring_callable_owner(
1118 &ctx.analyzer,
1119 ctx.visibility,
1120 ctx.file,
1121 ordinary,
1122 name,
1123 call_arity,
1124 ) {
1125 EnclosingMemberOwnerResolution::Owner(owner) => {
1126 match ctx
1127 .visibility
1128 .visible_member_for_owner_name(ctx.file, &owner, name)
1129 {
1130 VisibleMemberResolution::Callable(callables) => {
1131 if let Some(callable) = callables.iter().find(|callable| {
1132 cpp_callable_arity(&ctx.analyzer, callable)
1133 .is_none_or(|known| known.accepts(call_arity))
1134 }) {
1135 ctx.record(callable.fq_name(), field);
1136 }
1137 }
1138 VisibleMemberResolution::AmbiguousKind => {
1139 ctx.record_unproven(name, field);
1140 }
1141 VisibleMemberResolution::NonCallable
1142 | VisibleMemberResolution::Missing => {}
1143 }
1144 }
1145 EnclosingMemberOwnerResolution::Ambiguous => {
1146 ctx.record_unproven(name, field);
1147 }
1148 EnclosingMemberOwnerResolution::Missing => {}
1149 }
1150 } else {
1151 ctx.record_unproven(name, field);
1152 }
1153 }
1154 "identifier" | "template_function" => {
1158 let terminal = super::resolver::function_terminal_node(function);
1159 let name = node_text(terminal, ctx.source);
1160 if name.is_empty() {
1161 return;
1162 }
1163 if bindings.is_shadowed(name) {
1164 return;
1165 }
1166 if let Some(enclosing_owner) = enclosing_callable_owner(function, ctx) {
1167 let ordinary = resolve_declaring_member_owner_cached(ctx, &enclosing_owner, name);
1168 match resolve_declaring_callable_owner(
1169 &ctx.analyzer,
1170 ctx.visibility,
1171 ctx.file,
1172 ordinary,
1173 name,
1174 call_arity,
1175 ) {
1176 EnclosingMemberOwnerResolution::Owner(owner)
1177 if !same_visible_symbol(&owner, &enclosing_owner) =>
1178 {
1179 match ctx
1180 .visibility
1181 .visible_member_for_owner_name(ctx.file, &owner, name)
1182 {
1183 VisibleMemberResolution::Callable(callables) => {
1184 if let Some(callable) = callables.iter().find(|callable| {
1185 cpp_callable_arity(&ctx.analyzer, callable)
1186 .is_none_or(|known| known.accepts(call_arity))
1187 }) {
1188 ctx.record(callable.fq_name(), function);
1189 }
1190 }
1191 VisibleMemberResolution::AmbiguousKind => {
1192 ctx.record_unproven(name, function);
1193 }
1194 VisibleMemberResolution::NonCallable
1195 | VisibleMemberResolution::Missing => {}
1196 }
1197 return;
1198 }
1199 EnclosingMemberOwnerResolution::Owner(_) => {
1200 ctx.record_unproven(name, function);
1209 return;
1210 }
1211 EnclosingMemberOwnerResolution::Ambiguous => {
1212 ctx.record_unproven(name, function);
1213 return;
1214 }
1215 EnclosingMemberOwnerResolution::Missing => {}
1216 }
1217 }
1218 let resolution = resolve_bare_call_target(
1219 node,
1220 function,
1221 &ctx.analyzer,
1222 ctx.visibility,
1223 &ctx.ordinary_type_imports,
1224 ctx.file,
1225 ctx.source,
1226 );
1227 match resolution {
1228 BareCallTargetResolution::FreeFunctions(units) => {
1229 let mut recorded = HashSet::default();
1230 for unit in units {
1231 let fq_name = unit.fq_name();
1232 if recorded.insert(fq_name.clone()) {
1233 ctx.record(fq_name, terminal);
1234 }
1235 }
1236 }
1237 BareCallTargetResolution::Type(unit) => {
1238 if let VisibleMemberResolution::Callable(constructors) = ctx
1239 .visibility
1240 .visible_member_for_owner_name(ctx.file, &unit, unit.identifier())
1241 && let Some(constructor) = constructors.iter().find(|constructor| {
1242 cpp_callable_arity(&ctx.analyzer, constructor)
1243 .is_none_or(|known| known.accepts(call_arity))
1244 })
1245 {
1246 ctx.record(constructor.fq_name(), terminal);
1247 } else {
1248 ctx.record(unit.fq_name(), function);
1249 }
1250 }
1251 BareCallTargetResolution::UnprovenFreeFunctions(_)
1252 | BareCallTargetResolution::CallableShadow
1253 | BareCallTargetResolution::Ambiguous => {}
1254 BareCallTargetResolution::Missing => {}
1255 }
1256 }
1259 _ => {}
1260 }
1261}
1262
1263fn resolve_declaring_member_owner_cached(
1264 ctx: &mut CppScan<'_>,
1265 receiver_owner: &CodeUnit,
1266 name: &str,
1267) -> EnclosingMemberOwnerResolution {
1268 if let Some(cached) = ctx
1269 .declaring_member_cache
1270 .get(receiver_owner)
1271 .and_then(|by_name| by_name.get(name))
1272 .cloned()
1273 {
1274 return cached;
1275 }
1276 let resolution = resolve_declaring_member_owner(
1277 &ctx.analyzer,
1278 ctx.visibility,
1279 ctx.file,
1280 receiver_owner,
1281 name,
1282 );
1283 ctx.declaring_member_cache
1284 .entry(receiver_owner.clone())
1285 .or_default()
1286 .insert(name.to_string(), resolution.clone());
1287 resolution
1288}
1289
1290fn enclosing_callable_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1291 let mut current = node.parent();
1292 while let Some(parent) = current {
1293 if parent.kind() == "function_definition" {
1294 let declarator = parent.child_by_field_name("declarator")?;
1295 let function = declarator_name_node(declarator)?;
1296 if let Some(owners) = out_of_line_member_definition_owner(
1297 &ctx.analyzer,
1298 ctx.visibility,
1299 ctx.file,
1300 ctx.source,
1301 function,
1302 ) && let Some((_, owner)) = owners.innermost()
1303 {
1304 return Some(owner.clone());
1305 }
1306 break;
1307 }
1308 current = parent.parent();
1309 }
1310 ctx.enclosing_class(node.start_byte()).and_then(|fqn| {
1311 ctx.analyzer
1312 .definitions(fqn)
1313 .find(|candidate| candidate.is_class())
1314 })
1315}
1316
1317fn receiver_is_self_like(receiver: Node<'_>, reference_is_c: bool) -> bool {
1324 match receiver.kind() {
1325 "this" => !reference_is_c,
1326 "parenthesized_expression" | "pointer_expression" => receiver
1327 .child_by_field_name("argument")
1328 .or_else(|| receiver.named_child(0))
1329 .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
1330 _ => false,
1331 }
1332}
1333
1334fn scoped_free_function(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1336 if node.kind() != "qualified_identifier" {
1337 return None;
1338 }
1339 let parent = node.parent()?;
1340 if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1341 return None;
1342 }
1343 ctx.visibility.resolve_named(
1344 ctx.file,
1345 node_text(node, ctx.source),
1346 TargetKind::FreeFunction,
1347 )
1348}
1349
1350fn scoped_call_owner(node: Node<'_>, ctx: &CppScan<'_>) -> Option<String> {
1352 if node.kind() != "qualified_identifier" {
1353 return None;
1354 }
1355 let parent = node.parent()?;
1356 if parent.kind() != "call_expression" || parent.child_by_field_name("function") != Some(node) {
1357 return None;
1358 }
1359 let scope = node.child_by_field_name("scope")?;
1360 match resolve_type_node_lexically(
1361 scope,
1362 &ctx.analyzer,
1363 ctx.visibility,
1364 &ctx.ordinary_type_imports,
1365 ctx.file,
1366 ctx.source,
1367 ) {
1368 LexicalTypeResolution::Resolved { unit, .. } => Some(unit.fq_name()),
1369 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1370 }
1371}
1372
1373fn scoped_call_member(node: Node<'_>, source: &str) -> String {
1375 node.child_by_field_name("name")
1376 .map(|name| node_text(name, source).to_string())
1377 .unwrap_or_default()
1378}
1379
1380fn receiver_type_unit(
1381 receiver: Node<'_>,
1382 ctx: &CppScan<'_>,
1383 bindings: &LocalInferenceEngine<CodeUnit>,
1384 remaining_call_depth: usize,
1385) -> Option<CodeUnit> {
1386 match receiver.kind() {
1387 "identifier" => {
1388 let name = node_text(receiver, ctx.source);
1389 if let Some(binding) = ctx.visibility.macro_local_binding_at(
1395 ctx.file,
1396 ctx.input.root(),
1397 ctx.source,
1398 receiver.start_byte(),
1399 receiver.end_byte(),
1400 ) && binding.name == name
1401 {
1402 return binding
1403 .proven_unit
1404 .or_else(|| {
1405 binding.type_node.and_then(|type_node| {
1406 resolve_type_node_with_recovered_scope(type_node, ctx)
1407 })
1408 })
1409 .or_else(|| {
1410 binding.type_node.and_then(|type_node| {
1411 ctx.resolve_type_node_result(type_node).ok().flatten()
1412 })
1413 })
1414 .or_else(|| ctx.resolve_type(&normalize_type_text(&binding.type_name)));
1415 }
1416 first_precise(bindings, name).or_else(|| {
1420 if !bindings.is_shadowed(name)
1421 && let Some(unit) = global_receiver_type_unit(ctx, name)
1422 {
1423 return Some(unit);
1424 }
1425 (!bindings.is_shadowed(name))
1426 .then(|| resolve_type_node_with_recovered_scope(receiver, ctx))
1427 .flatten()
1428 .or_else(|| {
1429 (!bindings.is_shadowed(name))
1430 .then(|| ctx.resolve_type(name))
1431 .flatten()
1432 })
1433 })
1434 }
1435 "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
1436 first_precise(bindings, node_text(receiver, ctx.source))
1437 }
1438 "this" => ctx.enclosing_class(receiver.start_byte()).and_then(|fqn| {
1439 ctx.analyzer
1440 .definitions(fqn)
1441 .find(|candidate| candidate.is_class())
1442 }),
1443 "parenthesized_expression" | "pointer_expression" => receiver
1445 .child_by_field_name("argument")
1446 .or_else(|| receiver.named_child(0))
1447 .and_then(|inner| receiver_type_unit(inner, ctx, bindings, remaining_call_depth)),
1448 "call_expression" if remaining_call_depth > 0 => infer_cpp_initializer_binding(
1449 &ctx.analyzer,
1450 ctx.visibility,
1451 ctx.file,
1452 ctx.source,
1453 receiver,
1454 Some(&|inner, _source| {
1455 receiver_type_unit(inner, ctx, bindings, remaining_call_depth - 1)
1456 .into_iter()
1457 .collect()
1458 }),
1459 )
1460 .and_then(|binding| binding.unit),
1461 "field_expression" => {
1466 let field = receiver.child_by_field_name("field")?;
1467 let name = node_text(field, ctx.source);
1468 let inner = cpp_field_expression_receiver(receiver)?;
1469 let receiver_owner = receiver_type_unit(inner, ctx, bindings, remaining_call_depth)?;
1470 let owner = match resolve_declaring_member_owner(
1471 &ctx.analyzer,
1472 ctx.visibility,
1473 ctx.file,
1474 &receiver_owner,
1475 name,
1476 ) {
1477 EnclosingMemberOwnerResolution::Owner(owner) => owner,
1478 EnclosingMemberOwnerResolution::Ambiguous
1479 | EnclosingMemberOwnerResolution::Missing => return None,
1480 };
1481 let fields = ctx
1482 .visibility
1483 .visible_members_for_owner_name(ctx.file, &owner, name)
1484 .into_iter()
1485 .filter(|candidate| candidate.is_field())
1486 .collect::<Vec<_>>();
1487 let [field_unit] = fields.as_slice() else {
1488 return None;
1489 };
1490 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field_unit)
1491 .and_then(|binding| binding.unit)
1492 }
1493 "cast_expression" => cast_expression_type_node(receiver).and_then(|type_node| {
1494 resolve_type_node_with_recovered_scope(type_node, ctx).or_else(|| {
1495 match ctx.resolve_type_node_result(type_node) {
1496 Ok(Some(unit)) => Some(unit),
1497 Ok(None) => ctx.resolve_type(node_text(type_node, ctx.source)),
1498 Err(_) => None,
1499 }
1500 })
1501 }),
1502 _ => None,
1503 }
1504}
1505
1506fn global_receiver_type_unit(ctx: &CppScan<'_>, name: &str) -> Option<CodeUnit> {
1507 let fields = ctx
1508 .visibility
1509 .named_candidates(ctx.file, name, TargetKind::GlobalField)
1510 .into_iter()
1511 .filter(has_persisted_global_field_identity)
1512 .collect::<Vec<_>>();
1513 let first = fields.first()?;
1514 let mut linkage_cache = HashMap::default();
1515 if fields.iter().skip(1).any(|field| {
1516 !same_visible_global_field_symbol(&ctx.analyzer, &mut linkage_cache, first, field)
1517 }) {
1518 return None;
1519 }
1520 let mut units = fields
1521 .into_iter()
1522 .filter_map(|field| {
1523 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, &field)
1524 .and_then(|binding| binding.unit)
1525 })
1526 .collect::<Vec<_>>();
1527 units.dedup_by(|left, right| same_visible_symbol(left, right));
1528 match units.as_slice() {
1529 [unit] => Some(unit.clone()),
1530 _ => None,
1531 }
1532}
1533
1534fn seed_declaration(
1535 node: Node<'_>,
1536 ctx: &mut CppScan<'_>,
1537 bindings: &mut LocalInferenceEngine<CodeUnit>,
1538) {
1539 if recovered_macro_return_type_node(node, ctx.source).is_some()
1540 || crate::declarations::is_direct_recovered_exported_class_field_declaration(
1541 node, ctx.source,
1542 )
1543 {
1544 return;
1545 }
1546 match node.kind() {
1547 "parameter_declaration" | "optional_parameter_declaration" => {
1548 seed_typed_binding(node, ctx, bindings)
1549 }
1550 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx, bindings),
1551 "for_range_loop" => seed_range_binding(node, ctx, bindings),
1552 "expression_statement" => seed_function_macro_local_binding(node, ctx, bindings),
1553 "assignment_expression" => seed_function_macro_container_binding(node, ctx, bindings),
1554 _ => {}
1555 }
1556}
1557
1558fn seed_function_macro_local_binding(
1559 node: Node<'_>,
1560 ctx: &CppScan<'_>,
1561 bindings: &mut LocalInferenceEngine<CodeUnit>,
1562) {
1563 let Some(binding) = ctx
1564 .visibility
1565 .function_macro_local_binding(ctx.file, node, ctx.source)
1566 else {
1567 return;
1568 };
1569 let unit = binding
1570 .type_node
1571 .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1572 .or_else(|| {
1573 binding
1574 .type_node
1575 .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1576 })
1577 .or_else(|| ctx.resolve_type(&binding.type_name));
1578 match unit {
1579 Some(unit) => bindings.seed_symbol(binding.name, unit),
1580 None => bindings.declare_shadow(binding.name),
1581 }
1582}
1583
1584fn seed_function_macro_container_binding(
1585 node: Node<'_>,
1586 ctx: &CppScan<'_>,
1587 bindings: &mut LocalInferenceEngine<CodeUnit>,
1588) {
1589 let Some(binding) =
1590 ctx.visibility
1591 .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
1592 else {
1593 return;
1594 };
1595 let unit = binding.proven_unit.or_else(|| {
1596 binding
1597 .type_node
1598 .and_then(|type_node| resolve_type_node_with_recovered_scope(type_node, ctx))
1599 .or_else(|| {
1600 binding
1601 .type_node
1602 .and_then(|type_node| ctx.resolve_type_node_result(type_node).ok().flatten())
1603 })
1604 .or_else(|| ctx.resolve_type(&binding.type_name))
1605 });
1606 match unit {
1607 Some(unit) => bindings.seed_symbol(binding.name, unit),
1608 None => bindings.declare_shadow(binding.name),
1609 }
1610}
1611
1612fn seed_typed_binding(
1613 node: Node<'_>,
1614 ctx: &CppScan<'_>,
1615 bindings: &mut LocalInferenceEngine<CodeUnit>,
1616) {
1617 if !parameter_belongs_to_callable_scope(node) {
1618 return;
1619 }
1620 let Some(declarator) = node.child_by_field_name("declarator") else {
1621 return;
1622 };
1623 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1624 return;
1625 };
1626 let type_node = node
1627 .child_by_field_name("type")
1628 .or_else(|| first_type_child(node));
1629 seed_binding(&name, type_node, None, ctx, bindings);
1630}
1631
1632fn seed_range_binding(
1633 node: Node<'_>,
1634 ctx: &CppScan<'_>,
1635 bindings: &mut LocalInferenceEngine<CodeUnit>,
1636) {
1637 let Some(declarator) = node.child_by_field_name("declarator") else {
1638 return;
1639 };
1640 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1641 return;
1642 };
1643 let type_node = node
1644 .child_by_field_name("type")
1645 .or_else(|| first_type_child(node));
1646 seed_binding(&name, type_node, None, ctx, bindings);
1647}
1648
1649fn seed_variable_declaration(
1650 node: Node<'_>,
1651 ctx: &CppScan<'_>,
1652 bindings: &mut LocalInferenceEngine<CodeUnit>,
1653) {
1654 let type_node = node
1655 .child_by_field_name("type")
1656 .or_else(|| first_type_child(node));
1657 let type_text =
1658 type_node.map(|type_node| normalize_type_text(node_text(type_node, ctx.source)));
1659 let mut cursor = node.walk();
1660 for child in node.named_children(&mut cursor) {
1661 let declarator = if child.kind() == "init_declarator" {
1662 child.child_by_field_name("declarator")
1663 } else if is_declarator_node(child) {
1664 Some(child)
1665 } else {
1666 None
1667 };
1668 let Some(declarator) = declarator else {
1669 continue;
1670 };
1671 if declarator.kind() == "function_declarator"
1672 && !constructor_style_local_declaration(
1673 ctx.visibility,
1674 ctx.file,
1675 ctx.source,
1676 declarator,
1677 type_text.as_deref(),
1678 bindings,
1679 )
1680 {
1681 if node.kind() == "declaration"
1682 && has_function_scope_ancestor(node)
1683 && let Some(name) = extract_variable_name(declarator, ctx.source)
1684 {
1685 bindings.declare_shadow(name);
1686 }
1687 continue;
1688 }
1689 let Some(name) = extract_variable_name(declarator, ctx.source) else {
1690 continue;
1691 };
1692 let value = child.child_by_field_name("value");
1693 seed_binding(&name, type_node, value, ctx, bindings);
1694 }
1695}
1696
1697fn has_function_scope_ancestor(mut node: Node<'_>) -> bool {
1698 while let Some(parent) = node.parent() {
1699 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
1700 return true;
1701 }
1702 node = parent;
1703 }
1704 false
1705}
1706
1707fn seed_binding(
1708 name: &str,
1709 type_node: Option<Node<'_>>,
1710 value: Option<Node<'_>>,
1711 ctx: &CppScan<'_>,
1712 bindings: &mut LocalInferenceEngine<CodeUnit>,
1713) {
1714 if name.is_empty() {
1715 return;
1716 }
1717 let declared_type =
1721 type_node.filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto");
1722 let resolved = match declared_type {
1723 Some(node) => resolve_type_node_with_recovered_scope(node, ctx).or_else(|| {
1724 match ctx.resolve_type_node_result(node) {
1725 Ok(Some(unit)) => Some(unit),
1726 Ok(None) => ctx.resolve_type(node_text(node, ctx.source)),
1727 Err(_) => None,
1728 }
1729 }),
1730 None => value.and_then(|value| infer_type_from_value(value, ctx)),
1731 };
1732 match resolved {
1733 Some(unit) => bindings.seed_symbol(name.to_string(), unit),
1734 None => bindings.declare_shadow(name.to_string()),
1735 }
1736}
1737
1738fn resolve_type_node_with_recovered_scope(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1739 if let Some(owner) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
1740 return Some(owner);
1741 }
1742 let scope = recovered_or_indexed_lexical_scope(node, ctx)?;
1743 let components = cpp_type_name_components(node, ctx.source)?;
1744 let global = is_globally_qualified_cpp_name(node);
1745 match ctx.visibility.resolve_type_components_lexically(
1746 &ctx.analyzer,
1747 ctx.file,
1748 &components,
1749 global,
1750 &scope,
1751 ) {
1752 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
1753 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
1754 }
1755}
1756
1757fn infer_type_from_value(node: Node<'_>, ctx: &CppScan<'_>) -> Option<CodeUnit> {
1759 infer_cpp_initializer_type(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
1760}