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