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