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