1use crate::call_match::{
2 CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_forwarding_call_argument,
3 cpp_literal_arg_type, cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6 CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7 cpp_sentinel_recovered_scope_for_node, enclosing_cpp_namespace_nodes,
8 is_recovered_exported_class_base_type_node, node_text, normalize_cpp_whitespace,
9 recovered_macro_return_type_node,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
13use crate::graph::callable_definitions_share_identity_evidence_with_visibility as cpp_callable_definitions_share_identity_evidence_with_visibility;
14use crate::graph::hits::{
15 enclosing_context, is_member_field_own_declarator, push_declaration_reference_hit,
16 push_declared_reference_hit, push_definition_hit, push_hit, push_recovered_definition_hit,
17 push_recursive_reference_hit, push_reference_hit_range, push_self_receiver_hit, push_type_hit,
18 push_type_hit_range, push_unproven_definition_hit, push_unproven_hit,
19 push_unproven_reference_hit_range,
20};
21use crate::graph::namespace_alias::NamespaceQualifierBinding;
22use crate::graph::resolver::*;
23use crate::graph::syntax::{
24 function_macro_replacement_span, object_macro_replacement_type_references,
25 qualified_callable_value, receiver_is_self_like,
26};
27use crate::graph_support::CppSource;
28use brokk_bifrost_core::analyzer::fq_name::segment_interner;
29use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
30use brokk_bifrost_core::analyzer::query_token::QueryToken;
31use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_is_within;
32use brokk_bifrost_core::analyzer::tree_walk::{
33 ParentIndex, WalkControl, children_iter, first_named_child_of_kind, parent_if_field,
34 push_named_children_reversed, root_node, walk_named_tree_preorder,
35};
36use brokk_bifrost_core::analyzer::usages::common::same_node;
37use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
38use brokk_bifrost_core::analyzer::usages::local_inference::{
39 LocalInferenceConfig, LocalInferenceEngine, SymbolResolution,
40};
41use brokk_bifrost_core::analyzer::usages::model::{UsageHit, UsageHitSurface};
42use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
43use brokk_bifrost_core::hash::{HashMap, HashSet};
44#[cfg(any(test, feature = "test-support"))]
45use std::cell::Cell;
46use std::cell::RefCell;
47use std::collections::BTreeSet;
48use std::sync::Arc;
49use std::time::Instant;
50use tree_sitter::Node;
51
52#[cfg(any(test, feature = "test-support"))]
53thread_local! {
54 pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
55 static TYPE_REFERENCE_CANDIDATE_SCAN_COUNT: Cell<usize> = const { Cell::new(0) };
56}
57
58#[cfg(any(test, feature = "test-support"))]
59pub fn reset_type_reference_candidate_scan_count_for_test() {
60 TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(0));
61}
62
63#[cfg(any(test, feature = "test-support"))]
64pub fn type_reference_candidate_scan_count_for_test() -> usize {
65 TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(Cell::get)
66}
67
68pub struct ScanState<'a> {
69 pub max_usages: usize,
70 pub hits: &'a mut BTreeSet<UsageHit>,
71 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
72 pub raw_match_count: &'a mut usize,
73 pub limit_exceeded: &'a mut bool,
74}
75
76pub struct ScanCtx<'a> {
77 pub analyzer: CppGraphSource<'a>,
78 pub visibility: &'a VisibilityIndex<'a>,
79 pub file: &'a ProjectFile,
80 pub source: &'a str,
81 pub ancestry: ParentIndex<'a>,
87 ordinary_type_imports: OrdinaryTypeImportCell,
88 recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
89 class_ranges: Option<&'a ClassRangeIndex>,
90 pub line_starts: &'a [usize],
91 pub spec: &'a TargetSpec,
92 pub target_group: &'a HashSet<CodeUnit>,
93 pub has_proven_visible_type_target: bool,
94 uses_c_semantics: bool,
95 type_reference_component_names: HashSet<String>,
96 pub target_declaration_ranges: Vec<Range>,
97 target_macro_declaration_bytes: Vec<usize>,
98 pub bindings: LocalInferenceEngine<CppScanBinding>,
99 local_shadows: LocalInferenceEngine<()>,
100 using_enum_owners: ScopedUsingEnumOwners,
101 semantic_using_enum_owners: SemanticUsingEnumOwners,
102 needs_using_enum_member_resolution: bool,
103 pub hits: &'a mut BTreeSet<UsageHit>,
104 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
105 pub raw_match_count: &'a mut usize,
106 pub max_usages: usize,
107 pub external_hit_count: usize,
108 pub limit_exceeded: &'a mut bool,
109 pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
110 pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
111 lexical_scope_cache: LexicalScopeCache,
112 lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
113 member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
114 global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
115 receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
116}
117
118impl ScanCtx<'_> {
119 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
120 cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
121 }
122}
123
124#[derive(Clone, Default)]
125pub struct EnclosingContext {
126 pub enclosing: Option<CodeUnit>,
127 pub owner: Option<CodeUnit>,
128}
129
130pub fn prepare_file(
131 cpp: &dyn CppSource,
132 token: QueryToken<'_>,
133 file: &ProjectFile,
134) -> Option<Arc<PreparedSyntaxTree>> {
135 cpp.prepared_syntax(token, file)
136}
137
138#[allow(clippy::too_many_arguments)]
139pub fn scan_prepared_file(
140 analyzer: &CppGraphSource<'_>,
141 visibility: &VisibilityIndex<'_>,
142 file: &ProjectFile,
143 prepared: &PreparedSyntaxTree,
144 recovered_sentinel_classes: &[CppSentinelRecoveredClass],
145 class_ranges: Option<&ClassRangeIndex>,
146 spec: &TargetSpec,
147 target_group: &HashSet<CodeUnit>,
148 state: &mut ScanState<'_>,
149) {
150 if *state.limit_exceeded {
151 return;
152 }
153 let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
154 let has_proven_visible_type_target = spec.kind == TargetKind::Type
155 && (target_group.iter().any(|target| {
156 same_logical_symbol(target, &spec.target)
157 && visibility.is_physically_visible(file, target)
158 }) || {
159 let candidates = visibility
160 .visible_identifier_candidates(file, spec.target.identifier())
161 .collect::<Vec<_>>();
162 visibility.c_tag_declaration_family_matches_target(
163 analyzer,
164 file,
165 &candidates,
166 &spec.target,
167 )
168 });
169 if spec.kind == TargetKind::Type
170 && !has_proven_visible_type_target
171 && visibility
172 .visible_identifier_candidates(file, spec.target.identifier())
173 .any(|candidate| {
174 candidate != &spec.target
175 && !target_group.contains(candidate)
176 && same_logical_symbol(candidate, &spec.target)
177 && visibility.is_physically_visible(file, candidate)
178 && !visibility.c_tag_declaration_family_matches_target(
179 analyzer,
180 file,
181 std::slice::from_ref(&candidate),
182 &spec.target,
183 )
184 })
185 {
186 return;
187 }
188 let macro_target_declaration_ranges = if spec.kind == TargetKind::Macro {
189 analyzer.ranges(&spec.target)
190 } else {
191 Vec::new()
192 };
193 let target_declaration_ranges = if spec.kind == TargetKind::Type {
194 target_group
195 .iter()
196 .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
197 .flat_map(|target| analyzer.ranges(target))
198 .collect()
199 } else if spec.target.source() == file {
200 if spec.kind == TargetKind::Macro {
201 macro_target_declaration_ranges.clone()
202 } else {
203 analyzer.ranges(&spec.target)
204 }
205 } else {
206 Vec::new()
207 };
208 let target_macro_declaration_bytes = if spec.kind == TargetKind::Macro {
209 visibility.macro_declaration_bytes(&spec.target, ¯o_target_declaration_ranges)
210 } else {
211 Vec::new()
212 };
213 let type_reference_component_names = if spec.kind == TargetKind::Type {
214 visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
215 } else {
216 HashSet::default()
217 };
218 if spec.kind == TargetKind::Type
219 && !file_may_reference_type_target(
220 prepared.tree().root_node(),
221 prepared.source(),
222 analyzer,
223 visibility,
224 file,
225 &spec.target,
226 &type_reference_component_names,
227 )
228 {
229 return;
230 }
231 let ordinary_type_imports = initialized_ordinary_type_imports(
232 prepared.tree().root_node(),
233 analyzer,
234 visibility,
235 file,
236 prepared.source(),
237 );
238 let external_hit_count = state
239 .hits
240 .iter()
241 .filter(|hit| hit.kind.included_in(UsageHitSurface::ExternalUsages))
242 .count();
243 let mut ctx = ScanCtx {
244 analyzer: *analyzer,
245 visibility,
246 file,
247 source: prepared.source(),
248 ancestry: ParentIndex::new(prepared.tree().root_node()),
249 ordinary_type_imports,
250 recovered_sentinel_classes,
251 class_ranges,
252 line_starts: prepared.line_starts(),
253 spec,
254 target_group,
255 has_proven_visible_type_target,
256 uses_c_semantics: analyzer.reference_uses_c_semantics(file),
257 type_reference_component_names,
258 target_declaration_ranges,
259 target_macro_declaration_bytes,
260 bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
261 local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
262 using_enum_owners: ScopedUsingEnumOwners::new(),
263 semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
264 needs_using_enum_member_resolution,
265 hits: state.hits,
266 unproven_hits: state.unproven_hits,
267 raw_match_count: state.raw_match_count,
268 max_usages: state.max_usages,
269 external_hit_count,
270 limit_exceeded: state.limit_exceeded,
271 enclosing_cache: RefCell::new(HashMap::default()),
272 enclosing_owner_cache: RefCell::new(HashMap::default()),
273 lexical_scope_cache: LexicalScopeCache::new(visibility, file),
274 lexical_free_function_cache: RefCell::new(HashMap::default()),
275 member_owner_cache: RefCell::new(HashMap::default()),
276 global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
277 receiver_canonical_type_cache: RefCell::new(HashMap::default()),
278 };
279 if needs_using_enum_member_resolution {
280 collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
281 }
282 if spec.kind == TargetKind::Macro {
283 scan_macro_nodes(prepared.tree().root_node(), &mut ctx);
284 return;
285 }
286 scan_node(prepared.tree().root_node(), &mut ctx);
287}
288
289fn scan_macro_nodes(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
294 walk_named_tree_preorder(root, true, |node| {
295 if *ctx.limit_exceeded {
296 return WalkControl::Break;
297 }
298 maybe_record_macro_hit(node, ctx);
299 WalkControl::Continue
300 });
301}
302
303enum UsingEnumDeclarationScope {
304 Block,
305 Class(CodeUnit),
306 Namespace(Vec<String>),
307 UnsupportedClass,
308}
309
310fn resolved_using_enum_declaration(
311 node: Node<'_>,
312 ctx: &ScanCtx<'_>,
313) -> Option<(CodeUnit, UsingEnumDeclarationScope)> {
314 let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
315 node,
316 &ctx.analyzer,
317 ctx.visibility,
318 &ctx.ordinary_type_imports,
319 ctx.file,
320 ctx.source,
321 ) else {
322 return None;
323 };
324 Some((unit, using_enum_declaration_scope(node, ctx)))
325}
326
327fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
328 let mut current = ctx.ancestry.parent(node);
329 while let Some(parent) = current {
330 if matches!(
331 parent.kind(),
332 "compound_statement"
333 | "function_definition"
334 | "lambda_expression"
335 | "for_statement"
336 | "while_statement"
337 | "if_statement"
338 ) {
339 return UsingEnumDeclarationScope::Block;
340 }
341 if matches!(
342 parent.kind(),
343 "class_specifier" | "struct_specifier" | "union_specifier"
344 ) {
345 let resolution = enclosing_lexical_scope_components(
346 node,
347 &ctx.analyzer,
348 ctx.visibility,
349 ctx.file,
350 ctx.source,
351 );
352 if let LexicalScopeResolution::Resolved(components) = resolution
353 && let LexicalTypeResolution::Resolved { unit, .. } =
354 ctx.visibility.resolve_type_components_lexically(
355 &ctx.analyzer,
356 ctx.file,
357 &components,
358 true,
359 &[],
360 )
361 {
362 return UsingEnumDeclarationScope::Class(unit);
363 }
364 return UsingEnumDeclarationScope::UnsupportedClass;
365 }
366 current = ctx.ancestry.parent(parent);
367 }
368 UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
369}
370
371fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
372 let mut stack = vec![root];
373 while let Some(node) = stack.pop() {
374 if node.kind() == "using_declaration"
375 && let Some((unit, scope)) = resolved_using_enum_declaration(node, ctx)
376 {
377 match scope {
378 UsingEnumDeclarationScope::Block => {}
379 UsingEnumDeclarationScope::Class(class) => {
380 ctx.semantic_using_enum_owners.import_class(class, unit);
381 }
382 UsingEnumDeclarationScope::Namespace(namespace) => {
383 ctx.semantic_using_enum_owners.import_namespace(
384 namespace,
385 node.start_byte(),
386 unit,
387 );
388 }
389 UsingEnumDeclarationScope::UnsupportedClass => {}
390 }
391 }
392 push_named_children_reversed(node, &mut stack);
393 }
394}
395
396fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
397 struct Frame<'tree> {
398 node: Node<'tree>,
399 next_child: Option<Node<'tree>>,
400 previous_child: Option<Node<'tree>>,
401 entered: bool,
402 enters_scope: bool,
403 enters_using_enum_scope: bool,
404 fractured_function_scope: bool,
405 }
406 impl<'tree> Frame<'tree> {
407 fn new(node: Node<'tree>) -> Self {
408 Self {
409 node,
410 next_child: node.named_child(0),
411 previous_child: None,
412 entered: false,
413 enters_scope: false,
414 enters_using_enum_scope: false,
415 fractured_function_scope: false,
416 }
417 }
418 }
419 if *ctx.limit_exceeded {
420 return;
421 }
422 let mut stack = vec![Frame::new(node)];
423 while let Some(current) = stack.last_mut() {
424 let node = current.node;
425 let translation_unit = node.kind() == "translation_unit";
426 if !current.entered {
427 current.entered = true;
428 current.enters_scope = matches!(
429 node.kind(),
430 "compound_statement"
431 | "function_definition"
432 | "lambda_expression"
433 | "for_statement"
434 | "for_range_loop"
435 | "while_statement"
436 | "if_statement"
437 );
438 current.enters_using_enum_scope = ctx.needs_using_enum_member_resolution
439 && (current.enters_scope
440 || matches!(
441 node.kind(),
442 "namespace_definition"
443 | "class_specifier"
444 | "struct_specifier"
445 | "union_specifier"
446 ));
447 if current.enters_scope {
448 ctx.bindings.enter_scope();
449 ctx.local_shadows.enter_scope();
450 }
451 if current.enters_using_enum_scope {
452 ctx.using_enum_owners.enter_scope();
453 }
454 seed_declarations(node, ctx);
455 maybe_record_hit(node, ctx);
456 }
457 if let Some(child) = current.previous_child.take()
458 && !*ctx.limit_exceeded
459 && translation_unit
460 && macro_fractured_function(child)
461 && macro_fracture_boundary(child).is_some()
462 {
463 if current.fractured_function_scope {
464 ctx.bindings.exit_scope();
465 ctx.local_shadows.exit_scope();
466 }
467 ctx.bindings.enter_scope();
468 ctx.local_shadows.enter_scope();
469 seed_fractured_function_prefix(child, ctx);
470 current.fractured_function_scope = true;
471 }
472 if !*ctx.limit_exceeded
474 && let Some(child) = current.next_child.take()
475 {
476 current.next_child = child.next_named_sibling();
477 if translation_unit && current.fractured_function_scope && child.kind() == "ERROR" {
478 ctx.bindings.exit_scope();
479 ctx.local_shadows.exit_scope();
480 current.fractured_function_scope = false;
481 }
482 current.previous_child = Some(child);
483 stack.push(Frame::new(child));
484 continue;
485 }
486 if current.fractured_function_scope {
487 ctx.bindings.exit_scope();
488 ctx.local_shadows.exit_scope();
489 }
490 if current.enters_scope {
491 ctx.bindings.exit_scope();
492 ctx.local_shadows.exit_scope();
493 }
494 if current.enters_using_enum_scope {
495 ctx.using_enum_owners.exit_scope();
496 }
497 stack.pop();
498 }
499}
500
501fn macro_fractured_function(node: Node<'_>) -> bool {
502 if node.kind() != "function_definition" {
503 return false;
504 }
505 let Some(body) = node.child_by_field_name("body") else {
506 return false;
507 };
508 let mut stack = vec![body];
509 while let Some(current) = stack.pop() {
510 if matches!(current.kind(), "preproc_def" | "preproc_function_def") {
511 return true;
512 }
513 let mut cursor = current.walk();
514 stack.extend(current.named_children(&mut cursor));
515 }
516 false
517}
518
519pub fn macro_fracture_boundary(node: Node<'_>) -> Option<usize> {
521 let first_orphan = node.next_named_sibling()?;
522 if !matches!(
523 first_orphan.kind(),
524 "expression_statement"
525 | "compound_statement"
526 | "preproc_if"
527 | "preproc_ifdef"
528 | "if_statement"
529 | "for_statement"
530 | "while_statement"
531 | "do_statement"
532 | "return_statement"
533 ) {
534 return None;
535 }
536 let mut sibling = Some(first_orphan);
537 while let Some(current) = sibling {
538 if current.kind() == "ERROR" {
539 return Some(current.end_byte());
540 }
541 if matches!(
542 current.kind(),
543 "function_definition" | "declaration" | "type_definition"
544 ) {
545 return None;
546 }
547 sibling = current.next_named_sibling();
548 }
549 None
550}
551
552fn seed_fractured_function_prefix(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
553 let cutoff = node.end_byte().saturating_sub(1);
554 seed_fractured_active_path(node, cutoff, ctx);
555}
556
557fn seed_fractured_active_path(node: Node<'_>, cutoff: usize, ctx: &mut ScanCtx<'_>) {
558 let mut stack = vec![node];
559 while let Some(node) = stack.pop() {
560 if node.start_byte() >= cutoff {
561 continue;
562 }
563 let enters_scope = matches!(
564 node.kind(),
565 "compound_statement"
566 | "function_definition"
567 | "lambda_expression"
568 | "for_range_loop"
569 | "for_statement"
570 | "while_statement"
571 | "if_statement"
572 | "class_specifier"
573 | "struct_specifier"
574 | "union_specifier"
575 );
576 if enters_scope && !(node.start_byte() <= cutoff && cutoff < node.end_byte()) {
577 continue;
578 }
579 seed_declarations(node, ctx);
580 push_named_children_reversed(node, &mut stack);
581 }
582}
583
584fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
585 if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
586 || (ctx.spec.kind != TargetKind::Type
587 && indexed_recovered_class_field_declaration(node, ctx))
588 {
589 return;
590 }
591 match node.kind() {
592 "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
593 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
594 "for_range_loop" => seed_range_binding(node, ctx),
595 "expression_statement" | "macro_type_specifier" => {
596 seed_function_macro_local_binding(node, ctx)
597 }
598 "assignment_expression" => seed_function_macro_container_binding(node, ctx),
599 "using_declaration" => seed_using_enum(node, ctx),
600 _ => {}
601 }
602}
603
604fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
605 let Some(binding) = ctx
606 .visibility
607 .function_macro_local_binding(ctx.file, node, ctx.source)
608 else {
609 return;
610 };
611 if ctx.spec.kind == TargetKind::Type {
612 ctx.bindings.declare_shadow(binding.name);
613 return;
614 }
615 let normalized = normalize_cpp_type_name(&binding.type_name);
616 let unit = binding
617 .type_node
618 .and_then(|type_node| {
619 ctx.visibility
620 .resolve_type_node_result(ctx.file, type_node, ctx.source)
621 .ok()
622 .flatten()
623 })
624 .or_else(|| {
625 ctx.visibility
626 .canonical_type_for_reference(ctx.file, &normalized)
627 })
628 .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
629 ctx.bindings.seed_symbol(
630 binding.name,
631 CppScanBinding::from_type_name(
632 normalized,
633 unit,
634 binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
635 ),
636 );
637}
638
639fn seed_function_macro_container_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
640 let Some(binding) =
641 ctx.visibility
642 .function_macro_container_binding(&ctx.analyzer, ctx.file, node, ctx.source)
643 else {
644 return;
645 };
646 let normalized = normalize_cpp_type_name(&binding.type_name);
647 let unit = binding.proven_unit.clone().or_else(|| {
648 binding
649 .type_node
650 .and_then(|type_node| {
651 ctx.visibility
652 .resolve_type_node_result(ctx.file, type_node, ctx.source)
653 .ok()
654 .flatten()
655 })
656 .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized))
657 });
658 if let Some(unit) = unit {
659 ctx.bindings.seed_symbol(
660 binding.name,
661 CppScanBinding::from_type_name(normalized, Some(unit), binding.pointer_depth),
662 );
663 }
664}
665
666fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
667 if node.kind() != "declaration"
668 || !has_function_scope_ancestor(node)
669 || has_ancestor_kind(node, "lambda_expression")
670 || !(has_recovered_class_shape_ancestor(node)
671 || has_malformed_wrapper_function_definition_ancestor(node))
672 {
673 return false;
674 }
675 let context = enclosing_context(node, ctx);
676 context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
677 && context.owner.as_ref().is_some_and(CodeUnit::is_class)
678}
679
680fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
681 if !ctx.needs_using_enum_member_resolution {
682 return;
683 }
684 if let Some((unit, UsingEnumDeclarationScope::Block)) =
685 resolved_using_enum_declaration(node, ctx)
686 {
687 ctx.using_enum_owners.import(unit);
688 }
689}
690
691fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
692 if node.kind() == "field_declaration" {
697 return;
698 }
699 let type_node = node
700 .child_by_field_name("type")
701 .or_else(|| first_type_child(node));
702 let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
703 let mut cursor = node.walk();
704 for child in node.named_children(&mut cursor) {
705 let declarator = declaration_declarator(node, child);
706 let Some(declarator) = declarator else {
707 continue;
708 };
709 let Some(name) = extract_variable_name(declarator, ctx.source) else {
710 continue;
711 };
712 if declarator.kind() == "function_declarator"
713 && !constructor_style_local_declaration(
714 ctx.visibility,
715 ctx.file,
716 ctx.source,
717 declarator,
718 type_text.as_deref(),
719 &ctx.bindings,
720 )
721 {
722 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
723 ctx.local_shadows.declare_shadow(name);
724 }
725 continue;
726 }
727 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
728 ctx.local_shadows.declare_shadow(name.clone());
729 }
730 if ctx.spec.kind == TargetKind::Type {
731 ctx.bindings.declare_shadow(name);
732 continue;
733 }
734 let value = child.child_by_field_name("value");
735 seed_binding_from_type_or_value(&name, type_node, value, ctx);
736 }
737}
738
739fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
740 if !parameter_belongs_to_callable_scope(node) {
741 return;
742 }
743 let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
744 return;
745 };
746 if has_function_scope_ancestor(node) {
747 ctx.local_shadows.declare_shadow(name.clone());
748 }
749 if ctx.spec.kind == TargetKind::Type {
750 ctx.bindings.declare_shadow(name);
751 return;
752 }
753 seed_binding_from_type_or_value(&name, type_node, None, ctx);
754}
755
756fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
757 let Some((name, _, type_node)) = cpp_binding_parts(node, ctx.source) else {
758 return;
759 };
760 if has_function_scope_ancestor(node) {
761 ctx.local_shadows.declare_shadow(name.clone());
762 }
763 if ctx.spec.kind == TargetKind::Type {
764 ctx.bindings.declare_shadow(name);
765 return;
766 }
767 seed_binding_from_type_or_value(&name, type_node, None, ctx);
768}
769
770fn has_function_scope_ancestor(node: Node<'_>) -> bool {
771 let mut current = node.parent();
772 while let Some(parent) = current {
773 if parent.kind() == "namespace_definition" {
781 return false;
782 }
783 if parent.kind() == "function_definition" {
784 return !is_malformed_wrapper_function_definition(parent);
791 }
792 if parent.kind() == "lambda_expression" {
793 return true;
794 }
795 current = parent.parent();
796 }
797 false
798}
799
800fn seed_binding_from_type_or_value(
801 name: &str,
802 type_node: Option<Node<'_>>,
803 value: Option<Node<'_>>,
804 ctx: &mut ScanCtx<'_>,
805) {
806 if name.is_empty() {
807 return;
808 }
809 let resolved = type_node
810 .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
811 .map(|node| {
812 let text = node_text(node, ctx.source);
813 let name = normalize_cpp_type_name(text);
814 if let Some(unit) = anonymous_aggregate_owner(&ctx.analyzer, ctx.file, node) {
815 return CppScanBinding::from_type_name(
816 name,
817 Some(unit),
818 cpp_type_text_pointer_depth(text),
819 );
820 }
821 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
826 if cpp_template_reference_arguments(node, ctx.source).is_some() {
827 return None;
828 }
829 match enclosing_lexical_scope_components(
830 node,
831 &ctx.analyzer,
832 ctx.visibility,
833 ctx.file,
834 ctx.source,
835 ) {
836 LexicalScopeResolution::Resolved(scope) => Some(scope),
837 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
838 }
839 });
840 let unit = lexical_scope
841 .as_deref()
842 .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
843 .or_else(|| {
844 match ctx
845 .visibility
846 .resolve_type_node_result(ctx.file, node, ctx.source)
847 {
848 Ok(Some(unit)) => Some(unit),
849 Ok(None) => ctx
850 .visibility
851 .canonical_type_for_reference(ctx.file, &name)
852 .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
853 Err(_) => None,
854 }
855 });
856 CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
857 })
858 .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
859
860 if let Some(resolved) = resolved {
861 ctx.bindings.seed_symbol(name.to_string(), resolved);
862 } else if let Some(value) = value
863 && value.kind() == "identifier"
864 {
865 ctx.bindings
866 .alias_symbol(name.to_string(), node_text(value, ctx.source));
867 } else {
868 ctx.bindings.declare_shadow(name.to_string());
869 }
870}
871
872fn resolve_seed_type_node_lexically(
873 node: Node<'_>,
874 ctx: &ScanCtx<'_>,
875 scope: &[String],
876) -> Option<CodeUnit> {
877 let (components, global) = type_reference_components(node, ctx.source)?;
878 let resolution = match scan_owner_type(ctx) {
879 Some(target) => ctx.visibility.resolve_type_components_lexically_for_target(
880 &ctx.analyzer,
881 ctx.file,
882 &components,
883 global,
884 scope,
885 target,
886 ),
887 None => ctx.visibility.resolve_type_components_lexically(
888 &ctx.analyzer,
889 ctx.file,
890 &components,
891 global,
892 scope,
893 ),
894 };
895 match resolution {
896 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
897 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
898 }
899}
900
901fn scan_owner_type<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
914 ctx.spec
917 .owner
918 .as_ref()
919 .filter(|owner| owner.is_class() && ctx.visibility.is_physically_visible(ctx.file, owner))
920}
921
922const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
923
924fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
925 infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
926}
927
928fn infer_type_from_value_with_budget(
929 node: Node<'_>,
930 ctx: &ScanCtx<'_>,
931 remaining_call_depth: usize,
932) -> Option<CppScanBinding> {
933 match node.kind() {
934 "new_expression" | "call_expression" if remaining_call_depth == 0 => {
935 infer_cpp_initializer_binding(
936 &ctx.analyzer,
937 ctx.visibility,
938 ctx.file,
939 ctx.source,
940 node,
941 None,
942 )
943 }
944 "new_expression" | "call_expression" => infer_cpp_initializer_binding(
945 &ctx.analyzer,
946 ctx.visibility,
947 ctx.file,
948 ctx.source,
949 node,
950 Some(&|receiver, source| {
951 receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
952 }),
953 ),
954 "initializer_list" => None,
955 "identifier" => {
956 let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
957 resolved
958 .as_precise()?
959 .iter()
960 .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
961 .cloned()
962 }
963 _ => {
964 let text = node_text(node, ctx.source);
965 let name = normalize_cpp_type_name(text);
966 ctx.visibility
967 .resolve_type(ctx.file, &name)
968 .map(|unit| CppScanBinding::from_unit(unit, 0))
969 }
970 }
971}
972
973pub fn cpp_syntax_may_spell_member(root: Node<'_>, source: &str, identifier: &str) -> bool {
997 debug_assert!(
998 cpp_member_is_spelled_at_references(identifier),
999 "member admission is defined only for a plain identifier: {identifier:?}"
1000 );
1001 let mut spelled = false;
1002 walk_named_tree_preorder(root, true, |node| match node.kind() {
1003 "comment" => WalkControl::SkipChildren,
1004 "identifier"
1005 | "field_identifier"
1006 | "type_identifier"
1007 | "namespace_identifier"
1008 | "statement_identifier" => {
1009 if node_text(node, source).trim() == identifier {
1010 spelled = true;
1011 WalkControl::Break
1012 } else {
1013 WalkControl::SkipChildren
1014 }
1015 }
1016 "preproc_arg" | "ERROR" => {
1017 if node_text(node, source).contains(identifier) {
1018 spelled = true;
1019 WalkControl::Break
1020 } else {
1021 WalkControl::SkipChildren
1024 }
1025 }
1026 _ => WalkControl::Continue,
1027 });
1028 spelled
1029}
1030
1031pub fn cpp_member_is_spelled_at_references(identifier: &str) -> bool {
1037 let mut characters = identifier.chars();
1038 characters
1039 .next()
1040 .is_some_and(|first| first.is_ascii_alphabetic() || first == '_')
1041 && characters.all(|character| character.is_ascii_alphanumeric() || character == '_')
1042 && !identifier.starts_with("operator")
1043}
1044
1045fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1046 match ctx.spec.kind {
1047 TargetKind::Type => maybe_record_type_hit(node, ctx),
1048 TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
1049 TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
1050 TargetKind::Method => maybe_record_method_hit(node, ctx),
1051 TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
1052 TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
1053 TargetKind::Macro => maybe_record_macro_hit(node, ctx),
1054 }
1055}
1056
1057fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1058 if node_text(node, ctx.source) != ctx.spec.member_name {
1059 return;
1060 }
1061 if is_ordinary_macro_reference_node(node) {
1062 if ctx.visibility.macro_binding_matches_target_declaration_at(
1063 ctx.file,
1064 &ctx.spec.member_name,
1065 node.start_byte(),
1066 ctx.spec.target.source(),
1067 &ctx.target_macro_declaration_bytes,
1068 ) {
1069 *ctx.raw_match_count += 1;
1070 push_hit(node, ctx);
1071 return;
1072 }
1073 match ctx.visibility.resolve_ordinary_macro_reference(
1074 &ctx.analyzer,
1075 ctx.file,
1076 node,
1077 ctx.source,
1078 ) {
1079 OrdinaryMacroReferenceResolution::Resolved(unit)
1080 if ctx.target_group.contains(&unit) =>
1081 {
1082 *ctx.raw_match_count += 1;
1083 push_hit(node, ctx);
1084 }
1085 OrdinaryMacroReferenceResolution::Ambiguous
1086 if ctx
1087 .visibility
1088 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
1089 {
1090 *ctx.raw_match_count += 1;
1091 push_unproven_hit(node, ctx);
1092 }
1093 OrdinaryMacroReferenceResolution::Resolved(_)
1094 | OrdinaryMacroReferenceResolution::Ambiguous
1095 | OrdinaryMacroReferenceResolution::Missing => {}
1096 }
1097 return;
1098 }
1099 if !matches!(
1100 node.kind(),
1101 "identifier"
1102 | "field_identifier"
1103 | "type_identifier"
1104 | "namespace_identifier"
1105 | "preproc_arg"
1106 ) || ctx.ancestry.parent(node).is_some_and(|parent| {
1107 matches!(parent.kind(), "preproc_def" | "preproc_function_def")
1108 && parent
1109 .child_by_field_name("name")
1110 .is_some_and(|name| same_node(name, node))
1111 }) {
1112 return;
1113 }
1114 if ctx.visibility.macro_binding_matches_target_declaration_at(
1115 ctx.file,
1116 &ctx.spec.member_name,
1117 node.start_byte(),
1118 ctx.spec.target.source(),
1119 &ctx.target_macro_declaration_bytes,
1120 ) {
1121 *ctx.raw_match_count += 1;
1122 push_hit(node, ctx);
1123 } else if ctx.visibility.macro_name_may_be_bound_at(
1124 ctx.file,
1125 &ctx.spec.member_name,
1126 node.start_byte(),
1127 ) && ctx
1128 .visibility
1129 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
1130 {
1131 *ctx.raw_match_count += 1;
1132 push_unproven_hit(node, ctx);
1133 }
1134}
1135
1136fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1137 let kind = node.kind();
1138 if ctx.uses_c_semantics
1139 && recovered_c_new_expression_argument_at(node, node.start_byte(), node.end_byte(), true)
1140 .is_some()
1141 {
1142 return;
1143 }
1144 if kind == "preproc_arg" {
1145 maybe_record_object_macro_replacement_type_hits(node, ctx);
1146 return;
1147 }
1148 if let Some(type_node) = ctx
1149 .visibility
1150 .function_macro_type_argument(ctx.file, node, ctx.source)
1151 {
1152 if ctx
1153 .local_shadows
1154 .is_shadowed(node_text(type_node, ctx.source))
1155 {
1156 return;
1157 }
1158 if let LexicalTypeResolution::Resolved {
1159 unit, candidates, ..
1160 } = resolve_type_node_lexically_for_target(
1161 type_node,
1162 &ctx.analyzer,
1163 ctx.visibility,
1164 &ctx.ordinary_type_imports,
1165 ctx.file,
1166 ctx.source,
1167 &ctx.spec.target,
1168 Some(&ctx.lexical_scope_cache),
1169 ctx.recovered_sentinel_scope(type_node).as_deref(),
1170 ) && type_resolution_matches_target(type_node, &unit, &candidates, ctx)
1171 {
1172 *ctx.raw_match_count += 1;
1173 push_type_hit(type_node, ctx);
1174 }
1175 return;
1176 }
1177 let recovered_exported_class_base =
1178 matches!(
1179 kind,
1180 "qualified_identifier" | "scoped_type_identifier" | "template_type"
1181 ) && is_recovered_exported_class_base_type_node(node, ctx.source);
1182 if kind == "field_declaration"
1183 && let Some(return_type) = recovered_macro_return_type_node(node, ctx.source)
1184 {
1185 maybe_record_recovered_macro_return_type_hit(return_type, ctx);
1186 return;
1187 }
1188 if kind == "qualified_identifier"
1189 && let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source)
1190 {
1191 if ctx
1198 .analyzer
1199 .type_alias_provider()
1200 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1201 && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
1202 && let Some(terminal) = owner.nodes.last().copied()
1203 {
1204 if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
1205 return;
1206 }
1207 if ctx.visibility.external_type_candidate_visible_in_context(
1208 &ctx.analyzer,
1209 ctx.file,
1210 &ctx.spec.target,
1211 terminal,
1212 ) || ctx
1213 .visibility
1214 .dependent_member_pointer_alias_visible_in_context(
1215 &ctx.analyzer,
1216 ctx.file,
1217 &ctx.spec.target,
1218 &owner.names,
1219 terminal,
1220 )
1221 {
1222 *ctx.raw_match_count += 1;
1223 push_type_hit(terminal, ctx);
1224 } else if ctx
1225 .visibility
1226 .is_physically_visible(ctx.file, &ctx.spec.target)
1227 && ctx.visibility.declaration_guards_compatible_at_reference(
1228 &ctx.analyzer,
1229 ctx.file,
1230 &ctx.spec.target,
1231 terminal,
1232 )
1233 {
1234 *ctx.raw_match_count += 1;
1237 push_unproven_hit(terminal, ctx);
1238 }
1239 return;
1240 }
1241 if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
1242 *ctx.raw_match_count += 1;
1243 for scope in scopes {
1244 push_type_hit(scope, ctx);
1245 }
1246 return;
1247 }
1248 return;
1249 }
1250 if kind == "pointer_expression"
1251 && let Some(value) = qualified_callable_value(node)
1252 && let Some(owner_hit) = target_guided_qualified_value_owner_hit(value.qualified, ctx)
1253 {
1254 *ctx.raw_match_count += 1;
1255 match owner_hit {
1256 TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1257 TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => push_unproven_hit(scope, ctx),
1258 }
1259 return;
1260 }
1261 if kind == "call_expression" {
1262 maybe_record_direct_temporary_type_hit(node, ctx);
1263 return;
1264 }
1265 if !matches!(
1266 kind,
1267 "identifier"
1268 | "namespace_identifier"
1269 | "qualified_identifier"
1270 | "scoped_identifier"
1271 | "scoped_type_identifier"
1272 | "template_function"
1273 | "template_type"
1274 | "type_descriptor"
1275 | "type_identifier"
1276 | "using_declaration"
1277 ) {
1278 return;
1279 }
1280 let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
1281 let template_function_type = cpp_template_function_has_type_owner(node);
1282 let recovered_qualified_friend =
1283 is_recovered_qualified_friend_class_type_reference(node, ctx.source);
1284 if !recovered_type
1285 && !recovered_qualified_friend
1286 && !recovered_exported_class_base
1287 && matches!(
1288 kind,
1289 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1290 )
1291 && !type_reference_components_may_name_target(node, ctx)
1292 {
1293 return;
1294 }
1295 if ctx.ancestry.parent(node).is_some_and(|parent| {
1296 parent.kind() == "operator_cast"
1297 && parent
1298 .child_by_field_name("type")
1299 .is_some_and(|target| same_node(target, node))
1300 }) {
1301 return;
1302 }
1303 if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
1304 *ctx.raw_match_count += 1;
1305 push_type_hit(hit, ctx);
1306 return;
1307 }
1308 if matches!(kind, "identifier" | "template_function")
1309 && parent_if_field(node, "call_expression", "function").is_some()
1310 {
1311 return;
1312 }
1313 if kind == "using_declaration" {
1314 let (resolution, type_node) =
1315 if let Some(type_node) = using_enum_declaration_type_node(node) {
1316 (
1317 resolve_using_enum_declaration_owner(
1318 node,
1319 &ctx.analyzer,
1320 ctx.visibility,
1321 &ctx.ordinary_type_imports,
1322 ctx.file,
1323 ctx.source,
1324 ),
1325 type_node,
1326 )
1327 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
1328 (
1329 resolve_ordinary_using_declaration_owner(
1330 node,
1331 &ctx.analyzer,
1332 ctx.visibility,
1333 ctx.file,
1334 ctx.source,
1335 ),
1336 type_node,
1337 )
1338 } else {
1339 return;
1340 };
1341 if let LexicalTypeResolution::Resolved { unit, .. } = resolution
1342 && same_visible_symbol(&unit, &ctx.spec.target)
1343 {
1344 *ctx.raw_match_count += 1;
1345 push_type_hit(type_node, ctx);
1346 }
1347 return;
1348 }
1349 if ctx.uses_c_semantics && is_c_sizeof_expression_type_candidate(ctx.file, node) {
1350 if ctx.local_shadows.is_shadowed(node_text(node, ctx.source))
1351 || local_type_name_shadows(node, ctx)
1352 {
1353 return;
1354 }
1355 if let LexicalTypeResolution::Resolved {
1356 unit, candidates, ..
1357 } = resolve_type_node_lexically_for_target(
1358 node,
1359 &ctx.analyzer,
1360 ctx.visibility,
1361 &ctx.ordinary_type_imports,
1362 ctx.file,
1363 ctx.source,
1364 &ctx.spec.target,
1365 Some(&ctx.lexical_scope_cache),
1366 ctx.recovered_sentinel_scope(node).as_deref(),
1367 ) && type_resolution_matches_target(node, &unit, &candidates, ctx)
1368 {
1369 *ctx.raw_match_count += 1;
1370 push_type_hit(node, ctx);
1371 }
1372 return;
1373 }
1374 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
1375 let mut matching = Vec::new();
1383 for candidate in [node, type_node] {
1384 if matching
1385 .iter()
1386 .any(|existing| same_node(*existing, candidate))
1387 {
1388 continue;
1389 }
1390 if let LexicalTypeResolution::Resolved {
1391 unit, candidates, ..
1392 } = resolve_type_node_lexically_for_target(
1393 candidate,
1394 &ctx.analyzer,
1395 ctx.visibility,
1396 &ctx.ordinary_type_imports,
1397 ctx.file,
1398 ctx.source,
1399 &ctx.spec.target,
1400 Some(&ctx.lexical_scope_cache),
1401 ctx.recovered_sentinel_scope(candidate).as_deref(),
1402 ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
1403 {
1404 matching.push(candidate);
1405 }
1406 }
1407 match matching.as_slice() {
1408 [candidate] if !same_node(*candidate, node) => {
1409 maybe_record_type_hit(*candidate, ctx);
1410 return;
1411 }
1412 [candidate] if same_node(*candidate, node) => {}
1413 [] => {}
1414 _ => return,
1415 }
1416 }
1417 if !recovered_type
1418 && !template_function_type
1419 && !matches!(
1420 kind,
1421 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
1422 )
1423 {
1424 return;
1425 }
1426 if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
1427 components.len() == 1
1428 && !global
1429 && (local_type_name_shadows(node, ctx)
1430 || ctx
1431 .analyzer
1432 .type_alias_provider()
1433 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1434 && nearer_type_name_shadows_structured_reference(node, ctx))
1435 }) {
1436 return;
1437 }
1438 if !recovered_type
1439 && !recovered_qualified_friend
1440 && !recovered_exported_class_base
1441 && ctx.ancestry.parent(node).is_some_and(|parent| {
1442 parent.kind() == "alias_declaration"
1443 && parent
1444 .child_by_field_name("name")
1445 .is_some_and(|name| same_node(name, node))
1446 })
1447 {
1448 return;
1449 }
1450 #[cfg(any(test, feature = "test-support"))]
1451 TYPE_REFERENCE_CANDIDATE_SCAN_COUNT.with(|count| count.set(count.get() + 1));
1452 if !recovered_type
1453 && !recovered_qualified_friend
1454 && !recovered_exported_class_base
1455 && matches!(kind, "qualified_identifier" | "scoped_identifier")
1456 && is_declaration_name(node)
1457 && let Some(owners) = out_of_line_member_definition_owner(
1458 &ctx.analyzer,
1459 ctx.visibility,
1460 ctx.file,
1461 ctx.source,
1462 node,
1463 )
1464 {
1465 *ctx.raw_match_count += 1;
1466 let mut matched_owner = false;
1467 for (owner_node, owner) in owners.owners {
1468 if same_visible_symbol(&owner, &ctx.spec.target) {
1469 matched_owner = true;
1470 push_guarded_owner_hit(owner_node, &owner, node, ctx);
1471 }
1472 }
1473 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1474 for scope in scopes {
1475 push_hit(scope, ctx);
1476 }
1477 } else if !matched_owner
1478 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1479 {
1480 push_unproven_hit(scope, ctx);
1481 }
1482 return;
1483 }
1484 if !recovered_type
1485 && !recovered_qualified_friend
1486 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1487 && is_declaration_name(node)
1488 && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
1489 {
1490 *ctx.raw_match_count += 1;
1491 push_type_hit(owner, ctx);
1492 return;
1493 }
1494 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1500 && matches!(
1501 node.kind(),
1502 "qualified_identifier" | "scoped_type_identifier"
1503 )
1504 && node
1505 .child_by_field_name("name")
1506 .is_some_and(|name| name.kind() == "template_type")
1507 {
1508 return;
1509 }
1510 if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1511 *ctx.raw_match_count += 1;
1512 push_unproven_hit(scope, ctx);
1513 return;
1514 }
1515 if !recovered_type
1516 && !is_nested_type_node(node)
1517 && matches!(
1518 node.kind(),
1519 "qualified_identifier" | "scoped_type_identifier"
1520 )
1521 && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1522 {
1523 *ctx.raw_match_count += 1;
1524 for scope in scopes {
1525 push_type_hit(scope, ctx);
1526 }
1527 return;
1528 }
1529 if !recovered_type && is_nested_type_node(node) {
1530 if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1531 *ctx.raw_match_count += 1;
1532 push_type_hit(hit, ctx);
1533 return;
1534 }
1535 if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1536 *ctx.raw_match_count += 1;
1537 push_type_hit(hit, ctx);
1538 return;
1539 }
1540 let nested_template = if node.kind() == "template_type" {
1547 Some(node)
1548 } else {
1549 ctx.ancestry.parent(node).filter(|parent| {
1550 parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1551 })
1552 };
1553 let nested_alias_qualifier = nested_template.is_some_and(|template| {
1560 let enclosing_qualified_type_owns_range =
1561 ctx.ancestry.parent(template).is_some_and(|parent| {
1562 matches!(
1563 parent.kind(),
1564 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1565 ) && parent.child_by_field_name("name") == Some(template)
1566 });
1567 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1568 return false;
1569 };
1570 let Some(name) = template_reference_name_node(template) else {
1571 return false;
1572 };
1573 let alias_candidates = ctx
1574 .visibility
1575 .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1576 .filter(|candidate| alias_provider.is_type_alias(candidate))
1577 .cloned()
1578 .collect::<Vec<_>>();
1579 let direct_target_alias_visible = !alias_candidates
1580 .iter()
1581 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1582 || ctx.visibility.external_type_candidate_visible_in_context(
1583 &ctx.analyzer,
1584 ctx.file,
1585 &ctx.spec.target,
1586 template,
1587 );
1588 !enclosing_qualified_type_owns_range
1589 && direct_target_alias_visible
1590 && template_reference_candidates_select_target(
1591 template,
1592 &alias_candidates,
1593 &ctx.analyzer,
1594 ctx.visibility,
1595 ctx.file,
1596 ctx.source,
1597 &ctx.spec.target,
1598 )
1599 });
1600 if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1601 return;
1602 }
1603 if nested_alias_qualifier {
1604 let template = nested_template.expect("a nested alias qualifier has a template node");
1605 *ctx.raw_match_count += 1;
1606 let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1607 .unwrap_or_else(|| type_reference_hit_node(template));
1608 push_type_hit(hit, ctx);
1609 return;
1610 }
1611 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1617 && let Some(template) = nested_template
1618 && template_type_component_preserves_target(
1619 template,
1620 &ctx.visibility
1621 .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1622 .cloned()
1623 .collect::<Vec<_>>(),
1624 ctx,
1625 )
1626 {
1627 *ctx.raw_match_count += 1;
1628 let hit = template
1629 .child_by_field_name("name")
1630 .filter(|name| name.kind() == "type_identifier")
1631 .unwrap_or(template);
1632 push_type_hit(hit, ctx);
1633 return;
1634 }
1635 if let Some(template) = nested_template
1636 && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1637 {
1638 *ctx.raw_match_count += 1;
1639 let hit =
1640 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1641 if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1642 template
1643 .child_by_field_name("name")
1644 .filter(|name| name.kind() == "type_identifier")
1645 .unwrap_or(template)
1646 } else {
1647 type_reference_hit_node(template)
1648 }
1649 });
1650 push_type_hit(hit, ctx);
1651 }
1652 return;
1653 }
1654 if !recovered_type && let Some(call) = parent_if_field(node, "call_expression", "function") {
1655 let direct_target = resolve_qualified_call_target(
1656 call,
1657 node,
1658 &ctx.analyzer,
1659 ctx.visibility,
1660 &ctx.ordinary_type_imports,
1661 ctx.file,
1662 ctx.source,
1663 );
1664 if matches!(direct_target, BareCallTargetResolution::Type(_))
1665 && let LexicalTypeResolution::Resolved {
1666 unit, candidates, ..
1667 } = resolve_type_node_lexically_for_target(
1668 node,
1669 &ctx.analyzer,
1670 ctx.visibility,
1671 &ctx.ordinary_type_imports,
1672 ctx.file,
1673 ctx.source,
1674 &ctx.spec.target,
1675 Some(&ctx.lexical_scope_cache),
1676 ctx.recovered_sentinel_scope(node).as_deref(),
1677 )
1678 && type_resolution_matches_target(node, &unit, &candidates, ctx)
1679 {
1680 *ctx.raw_match_count += 1;
1681 push_resolved_type_hit(node, &candidates, ctx);
1682 } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1683 *ctx.raw_match_count += 1;
1684 for scope in scopes {
1685 push_type_hit(scope, ctx);
1686 }
1687 } else if let Some(owner_hit) = target_guided_qualified_value_owner_hit(node, ctx) {
1688 *ctx.raw_match_count += 1;
1689 match owner_hit {
1690 TargetGuidedQualifiedValueOwnerHit::Proven(scope) => push_type_hit(scope, ctx),
1691 TargetGuidedQualifiedValueOwnerHit::Unproven(scope) => {
1692 push_unproven_hit(scope, ctx)
1693 }
1694 }
1695 }
1696 return;
1697 }
1698 if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1699 *ctx.raw_match_count += 1;
1700 if proven {
1701 push_type_hit(hit, ctx);
1702 } else {
1703 push_unproven_hit(hit, ctx);
1704 }
1705 return;
1706 }
1707 if !recovered_type
1708 && !recovered_qualified_friend
1709 && !recovered_exported_class_base
1710 && is_declaration_name(node)
1711 {
1712 let mut matched_owner = false;
1713 if let Some(owners) = out_of_line_member_definition_owner(
1714 &ctx.analyzer,
1715 ctx.visibility,
1716 ctx.file,
1717 ctx.source,
1718 node,
1719 ) {
1720 for (owner_node, owner) in owners.owners {
1721 if same_visible_symbol(&owner, &ctx.spec.target) {
1722 matched_owner = true;
1723 *ctx.raw_match_count += 1;
1724 push_guarded_owner_hit(owner_node, &owner, node, ctx);
1725 }
1726 }
1727 }
1728 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1729 *ctx.raw_match_count += 1;
1730 for scope in scopes {
1731 push_hit(scope, ctx);
1732 }
1733 } else if !matched_owner
1734 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1735 {
1736 *ctx.raw_match_count += 1;
1737 push_unproven_hit(scope, ctx);
1738 }
1739 return;
1740 }
1741 let hit_node = node;
1742 let text = node_text(hit_node, ctx.source);
1743 let type_resolution = if hit_node.kind() == "template_type"
1744 && ctx.visibility.is_template_specialization(&ctx.spec.target)
1745 {
1746 resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1747 resolve_type_node_lexically_for_target(
1748 hit_node,
1749 &ctx.analyzer,
1750 ctx.visibility,
1751 &ctx.ordinary_type_imports,
1752 ctx.file,
1753 ctx.source,
1754 &ctx.spec.target,
1755 Some(&ctx.lexical_scope_cache),
1756 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1757 )
1758 })
1759 } else {
1760 resolve_type_node_lexically_for_target(
1761 hit_node,
1762 &ctx.analyzer,
1763 ctx.visibility,
1764 &ctx.ordinary_type_imports,
1765 ctx.file,
1766 ctx.source,
1767 &ctx.spec.target,
1768 Some(&ctx.lexical_scope_cache),
1769 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1770 )
1771 };
1772 match type_resolution {
1773 LexicalTypeResolution::Resolved {
1774 unit, candidates, ..
1775 } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1776 *ctx.raw_match_count += 1;
1777 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1778 for scope in scopes {
1779 push_type_hit(scope, ctx);
1780 }
1781 } else {
1782 push_resolved_type_hit(hit_node, &candidates, ctx);
1783 }
1784 return;
1785 }
1786 LexicalTypeResolution::Resolved {
1787 unit: _,
1788 candidates,
1789 ..
1790 } => {
1791 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1792 *ctx.raw_match_count += 1;
1793 for scope in scopes {
1794 push_type_hit(scope, ctx);
1795 }
1796 } else if let Some(hit) =
1797 target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1798 {
1799 *ctx.raw_match_count += 1;
1800 push_unproven_hit(hit, ctx);
1801 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1802 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1803 {
1804 *ctx.raw_match_count += 1;
1805 push_type_hit(leaf, ctx);
1806 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1807 *ctx.raw_match_count += 1;
1808 push_unproven_hit(leaf, ctx);
1809 }
1810 return;
1811 }
1812 LexicalTypeResolution::Ambiguous => {
1813 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1814 *ctx.raw_match_count += 1;
1815 for scope in scopes {
1816 push_type_hit(scope, ctx);
1817 }
1818 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1819 *ctx.raw_match_count += 1;
1820 push_unproven_hit(leaf, ctx);
1821 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1822 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1823 .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1824 {
1825 *ctx.raw_match_count += 1;
1826 push_type_hit(leaf, ctx);
1827 }
1828 return;
1829 }
1830 LexicalTypeResolution::Missing => {
1831 if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1832 &ctx.analyzer,
1833 ctx.file,
1834 hit_node,
1835 ctx.source,
1836 ) && same_visible_symbol(&unit, &ctx.spec.target)
1837 {
1838 *ctx.raw_match_count += 1;
1839 push_type_hit(hit_node, ctx);
1840 return;
1841 }
1842 if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1843 *ctx.raw_match_count += 1;
1844 push_unproven_hit(leaf, ctx);
1845 return;
1846 }
1847 if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1848 *ctx.raw_match_count += 1;
1849 push_unproven_hit(leaf, ctx);
1850 return;
1851 }
1852 if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1853 *ctx.raw_match_count += 1;
1854 push_type_hit(leaf, ctx);
1855 return;
1856 }
1857 let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1858 hit_node,
1859 &ctx.analyzer,
1860 ctx.visibility,
1861 ctx.file,
1862 ctx.source,
1863 &ctx.spec.target,
1864 );
1865 let raw_matches = matches!(
1866 raw_resolution,
1867 LexicalTypeResolution::Resolved {
1868 ref unit,
1869 ref candidates,
1870 ..
1871 } if type_resolution_identifies_unit_target(
1872 hit_node,
1873 unit,
1874 candidates,
1875 &ctx.spec.target,
1876 ctx,
1877 )
1878 );
1879 if raw_matches
1880 || type_node_has_exact_target_identity_without_visibility(
1881 hit_node,
1882 &ctx.analyzer,
1883 ctx.visibility,
1884 ctx.file,
1885 ctx.source,
1886 &ctx.spec.target,
1887 )
1888 {
1889 *ctx.raw_match_count += 1;
1890 push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1891 return;
1892 }
1893 }
1894 }
1895 if ctx
1900 .visibility
1901 .parser_alias_resolves_to_type(ctx.file, text, &ctx.spec.target)
1902 {
1903 *ctx.raw_match_count += 1;
1904 push_type_hit(type_reference_hit_node(hit_node), ctx);
1905 return;
1906 }
1907 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1908 *ctx.raw_match_count += 1;
1909 for scope in scopes {
1910 push_type_hit(scope, ctx);
1911 }
1912 return;
1913 }
1914 if !name_mentions(text, &ctx.spec.member_name) {
1915 return;
1916 }
1917 *ctx.raw_match_count += 1;
1918 if !ctx.visibility.external_type_candidate_visible_in_context(
1919 &ctx.analyzer,
1920 ctx.file,
1921 &ctx.spec.target,
1922 hit_node,
1923 ) {
1924 let unproven = static_qualifier_name_scope(node, ctx).unwrap_or(hit_node);
1925 if type_reference_resolves_away_from_target(unproven, ctx) {
1926 return;
1927 }
1928 push_unproven_hit(unproven, ctx);
1929 }
1930}
1931
1932fn maybe_record_object_macro_replacement_type_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
1933 for reference in object_macro_replacement_type_references(node, ctx.source) {
1934 for component_count in 1..=reference.components.len() {
1935 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
1936 node,
1937 &reference.components[..component_count],
1938 reference.global,
1939 &ctx.analyzer,
1940 ctx.visibility,
1941 &ctx.ordinary_type_imports,
1942 ctx.file,
1943 ctx.source,
1944 &ctx.spec.target,
1945 false,
1946 Some(&ctx.lexical_scope_cache),
1947 );
1948 let matches_target = match resolution {
1949 LexicalTypeResolution::Resolved {
1950 unit, candidates, ..
1951 } => {
1952 same_visible_symbol(&unit, &ctx.spec.target)
1953 || candidates
1954 .iter()
1955 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1956 }
1957 LexicalTypeResolution::Missing => unique_macro_replacement_type_candidate(
1958 &ctx.analyzer,
1959 ctx.visibility,
1960 ctx.file,
1961 &reference.components[..component_count],
1962 )
1963 .is_some_and(|candidate| same_visible_symbol(&candidate, &ctx.spec.target)),
1964 LexicalTypeResolution::Ambiguous => false,
1965 };
1966 if !matches_target {
1967 continue;
1968 }
1969 let range = &reference.component_ranges[component_count - 1];
1970 *ctx.raw_match_count += 1;
1971 push_type_hit_range(node, range.start, range.end, ctx);
1972 }
1973 }
1974}
1975
1976fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1977 node: Node<'tree>,
1978 ctx: &ScanCtx<'_>,
1979) -> Option<Node<'tree>> {
1980 let (components, global) = type_reference_components(node, ctx.source)?;
1981 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1982 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1983 node,
1984 &ctx.analyzer,
1985 ctx.visibility,
1986 ctx.file,
1987 ctx.source,
1988 false,
1989 false,
1990 Some(&ctx.lexical_scope_cache),
1991 ) {
1992 LexicalScopeResolution::Resolved(scope) => Some(scope),
1993 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1994 }
1995 })?;
1996 let OrdinaryTypeImportResolution::Resolved { target, .. } =
1997 compatible_foreign_type_import_resolution(
1998 node,
1999 &components,
2000 global,
2001 &ctx.analyzer,
2002 ctx.visibility,
2003 &ctx.ordinary_type_imports,
2004 ctx.file,
2005 ctx.source,
2006 &lexical_scope,
2007 Some(&ctx.spec.target),
2008 )
2009 else {
2010 return None;
2011 };
2012 same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
2013}
2014
2015fn target_guided_nested_type_terminal_hit<'tree>(
2022 node: Node<'tree>,
2023 ctx: &ScanCtx<'_>,
2024) -> Option<Node<'tree>> {
2025 let qualified = node.parent().filter(|parent| {
2028 matches!(
2029 parent.kind(),
2030 "qualified_identifier" | "scoped_type_identifier"
2031 ) && parent.child_by_field_name("name") == Some(node)
2032 })?;
2033 let mut complete = qualified;
2034 while let Some(parent) = complete.parent().filter(|parent| {
2035 matches!(
2036 parent.kind(),
2037 "qualified_identifier" | "scoped_type_identifier"
2038 )
2039 }) {
2040 complete = parent;
2041 }
2042 let owner = qualified_owner_components(complete, ctx.source)?;
2043
2044 if let LexicalTypeResolution::Resolved {
2045 unit, candidates, ..
2046 } = resolve_type_node_lexically_for_target(
2047 complete,
2048 &ctx.analyzer,
2049 ctx.visibility,
2050 &ctx.ordinary_type_imports,
2051 ctx.file,
2052 ctx.source,
2053 &ctx.spec.target,
2054 Some(&ctx.lexical_scope_cache),
2055 ctx.recovered_sentinel_scope(complete).as_deref(),
2056 ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
2057 && ctx
2058 .analyzer
2059 .parent_of(&ctx.spec.target)
2060 .is_some_and(|parent| parent.is_class())
2061 && !ctx
2062 .analyzer
2063 .type_alias_provider()
2064 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2065 && is_namespace_scope_type_reference(complete)
2066 && namespace_import_can_name_nested_target(complete, ctx)
2067 {
2068 return Some(node);
2069 }
2070
2071 let target_owner = ctx
2072 .analyzer
2073 .parent_of(&ctx.spec.target)
2074 .filter(CodeUnit::is_class);
2075 let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
2076 let target_components = canonical_cpp_scope_components(target_owner);
2077 if owner.global {
2078 target_components == owner.names
2079 } else {
2080 target_components.ends_with(&owner.names)
2081 }
2082 });
2083 let target_member_visible = |target_owner: &CodeUnit| {
2084 ctx.visibility
2085 .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
2086 .into_iter()
2087 .any(|candidate| {
2088 same_visible_symbol(candidate, &ctx.spec.target)
2089 && (ctx
2090 .visibility
2091 .external_type_candidate_guard_compatible_in_context(
2092 &ctx.analyzer,
2093 ctx.file,
2094 candidate,
2095 complete,
2096 )
2097 || (ctx
2098 .visibility
2099 .is_exhaustive_same_fqn_type_declaration_family(
2100 &ctx.analyzer,
2101 ctx.file,
2102 candidate,
2103 )
2104 && ctx.visibility.external_type_candidate_visible_in_context(
2105 &ctx.analyzer,
2106 ctx.file,
2107 candidate,
2108 complete,
2109 )))
2110 })
2111 };
2112
2113 if owner_spelling_can_name_target
2114 && (is_namespace_scope_type_reference(complete)
2115 || (is_compound_type_reference(complete)
2116 && namespace_import_can_name_nested_target(complete, ctx)))
2117 && let Some(target_owner) = target_owner.as_ref()
2118 && let LexicalTypeResolution::Resolved { unit, .. } =
2119 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
2120 complete,
2121 &owner.names,
2122 owner.global,
2123 &ctx.analyzer,
2124 ctx.visibility,
2125 &ctx.ordinary_type_imports,
2126 ctx.file,
2127 ctx.source,
2128 Some(&ctx.lexical_scope_cache),
2129 )
2130 && ctx
2131 .visibility
2132 .same_template_owner_identity(&unit, target_owner)
2133 && target_member_visible(target_owner)
2134 {
2135 return Some(node);
2136 }
2137
2138 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
2139 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
2140 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
2141 &ctx.analyzer,
2142 ctx.file,
2143 &owner.names,
2144 owner.global,
2145 &lexical_scope,
2146 );
2147 let owner_unit = match owner_resolution {
2148 LexicalTypeResolution::Resolved { unit, .. } => unit,
2149 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
2150 ctx.visibility.inherited_injected_class_owner(
2151 &ctx.analyzer,
2152 ctx.file,
2153 &enclosing_owner,
2154 owner.names.first()?,
2155 )?
2156 }
2157 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
2158 };
2159 let name = node_text(node, ctx.source);
2160 ctx.visibility
2161 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
2162 .into_iter()
2163 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
2164 .then_some(complete)
2165}
2166
2167fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
2168 let mut current = node.parent();
2173 while let Some(parent) = current {
2174 match parent.kind() {
2175 "call_expression" | "new_expression" => return true,
2176 "parameter_declaration"
2177 | "optional_parameter_declaration"
2178 | "field_declaration"
2179 | "function_definition"
2180 | "function_declarator"
2181 | "class_specifier"
2182 | "struct_specifier"
2183 | "union_specifier"
2184 | "compound_statement" => return false,
2185 "declaration" => {
2186 let mut cursor = parent.walk();
2187 if parent
2188 .named_children(&mut cursor)
2189 .any(|child| child.kind() == "function_declarator")
2190 {
2191 return false;
2192 }
2193 }
2194 _ => {}
2195 }
2196 current = parent.parent();
2197 }
2198 true
2199}
2200
2201fn is_compound_type_reference(node: Node<'_>) -> bool {
2202 let mut current = node.parent();
2203 while let Some(parent) = current {
2204 match parent.kind() {
2205 "compound_statement" => return true,
2206 "namespace_definition" | "translation_unit" => return false,
2207 _ => current = parent.parent(),
2208 }
2209 }
2210 false
2211}
2212
2213fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2214 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
2215 return false;
2216 };
2217 effective_using_bindings_for_name(
2218 ctx.visibility,
2219 &ctx.ordinary_type_imports,
2220 ctx.file,
2221 node,
2222 ctx.source,
2223 target_owner.identifier(),
2224 )
2225 .iter()
2226 .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2227}
2228
2229fn out_of_line_dependent_return_template_owner<'tree>(
2239 node: Node<'tree>,
2240 ctx: &ScanCtx<'_>,
2241) -> Option<Node<'tree>> {
2242 if node.kind() != "template_type" {
2243 return None;
2244 }
2245 let template_name = template_reference_name_node(node)?;
2246 let mut scope = node;
2247 while let Some(parent) = ctx.ancestry.parent(scope).filter(|parent| {
2248 matches!(
2249 parent.kind(),
2250 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2251 ) && parent.child_by_field_name("name") == Some(scope)
2252 }) {
2253 scope = parent;
2254 }
2255 let qualified = ctx.ancestry.parent(scope).filter(|parent| {
2256 matches!(
2257 parent.kind(),
2258 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2259 ) && parent.child_by_field_name("scope") == Some(scope)
2260 })?;
2261 let mut function = ctx.ancestry.parent(qualified);
2262 let function = loop {
2263 let candidate = function?;
2264 if candidate.kind() == "function_definition" {
2265 break candidate;
2266 }
2267 function = ctx.ancestry.parent(candidate);
2268 };
2269 let return_type = function.child_by_field_name("type")?;
2270 if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
2271 return None;
2272 }
2273 let declarator_name = function
2274 .child_by_field_name("declarator")
2275 .and_then(declarator_name_node)?;
2276 if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
2277 return None;
2278 }
2279 let resolved_owner_matches = out_of_line_member_definition_owner(
2280 &ctx.analyzer,
2281 ctx.visibility,
2282 ctx.file,
2283 ctx.source,
2284 declarator_name,
2285 )
2286 .is_some_and(|owners| {
2287 owners
2288 .owners
2289 .iter()
2290 .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
2291 });
2292 let indexed_owner_matches =
2293 indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
2294 let target_guided_owner_matches =
2295 target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
2296 (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
2297 .then_some(template_name)
2298}
2299
2300fn indexed_out_of_line_template_owner_hit<'tree>(
2303 node: Node<'tree>,
2304 ctx: &ScanCtx<'_>,
2305) -> Option<Node<'tree>> {
2306 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
2307 || !is_declaration_name(node)
2308 {
2309 return None;
2310 }
2311 let mut function = ctx.ancestry.parent(node);
2312 let function = loop {
2313 let candidate = function?;
2314 if candidate.kind() == "function_definition" {
2315 break candidate;
2316 }
2317 function = ctx.ancestry.parent(candidate);
2318 };
2319 if function
2320 .child_by_field_name("declarator")
2321 .and_then(declarator_name_node)
2322 != Some(node)
2323 {
2324 return None;
2325 }
2326 let target = physically_visible_type_target(ctx)?;
2327 let qualified = qualified_owner_components(node, ctx.source)?;
2328 if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
2329 return None;
2330 }
2331 if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
2332 != canonical_cpp_scope_components(target)
2333 {
2334 return None;
2335 }
2336 let owner = qualified.nodes.last().copied()?;
2337 let template = if owner.kind() == "template_type" {
2338 owner
2339 } else {
2340 owner
2341 .parent()
2342 .filter(|parent| parent.kind() == "template_type")?
2343 };
2344 template_reference_name_node(template)
2345}
2346
2347fn push_guarded_owner_hit(
2348 owner_node: Node<'_>,
2349 owner: &CodeUnit,
2350 reference: Node<'_>,
2351 ctx: &mut ScanCtx<'_>,
2352) {
2353 if ctx
2354 .visibility
2355 .external_type_candidate_guard_compatible_in_context(
2356 &ctx.analyzer,
2357 ctx.file,
2358 owner,
2359 reference,
2360 )
2361 {
2362 push_hit(owner_node, ctx);
2363 } else {
2364 push_unproven_hit(owner_node, ctx);
2365 }
2366}
2367
2368fn target_guided_alias_template_reference<'tree>(
2371 node: Node<'tree>,
2372 ctx: &ScanCtx<'_>,
2373) -> Option<(Node<'tree>, bool)> {
2374 let alias_provider = ctx.analyzer.type_alias_provider()?;
2375 if !matches!(
2376 node.kind(),
2377 "qualified_identifier" | "scoped_type_identifier"
2378 ) || !alias_provider.is_type_alias(&ctx.spec.target)
2379 {
2380 return None;
2381 }
2382 cpp_template_reference_arguments(node, ctx.source)?;
2383 let (components, global) = type_reference_components(node, ctx.source)?;
2384 if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
2385 return None;
2386 }
2387 let target = physically_visible_type_target(ctx)?;
2388 let target_components = canonical_cpp_scope_components(target);
2389 let parser_namespace = enclosing_namespace_components(node, ctx.source);
2390 let path_matches = if global {
2391 components == target_components
2392 || (!parser_namespace.is_empty()
2393 && target_components.starts_with(&parser_namespace)
2394 && target_components[parser_namespace.len()..] == components)
2395 } else {
2396 let lexical_scope = match enclosing_lexical_scope_components(
2397 node,
2398 &ctx.analyzer,
2399 ctx.visibility,
2400 ctx.file,
2401 ctx.source,
2402 ) {
2403 LexicalScopeResolution::Resolved(scope) => scope,
2404 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
2405 };
2406 lexical_component_tiers(&components, false, &lexical_scope)
2407 .any(|scope| scope == target_components)
2408 };
2409 let scoped_candidates = ctx
2410 .visibility
2411 .visible_identifier_candidates(ctx.file, target.identifier())
2412 .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
2413 .collect::<Vec<_>>();
2414 if !path_matches
2415 || scoped_candidates.is_empty()
2416 || scoped_candidates
2417 .iter()
2418 .any(|candidate| !same_visible_symbol(candidate, target))
2419 || !ctx.visibility.structured_alias_primary_preserves_target(
2420 &ctx.analyzer,
2421 ctx.file,
2422 target,
2423 target,
2424 )
2425 {
2426 return None;
2427 }
2428 let proven = ctx.visibility.external_type_candidate_visible_in_context(
2429 &ctx.analyzer,
2430 ctx.file,
2431 target,
2432 node,
2433 );
2434 Some((node, proven))
2435}
2436
2437fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
2443 let name = node_text(return_type, ctx.source);
2444 if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2445 return;
2446 }
2447 if physically_visible_type_target(ctx).is_some()
2448 && type_alias_owner_encloses_structured_reference(return_type, ctx)
2449 && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2450 && ctx.visibility.external_type_candidate_visible_in_context(
2451 &ctx.analyzer,
2452 ctx.file,
2453 &ctx.spec.target,
2454 return_type,
2455 )
2456 {
2457 *ctx.raw_match_count += 1;
2458 push_type_hit(return_type, ctx);
2459 return;
2460 }
2461 let Some(scope) = ctx
2462 .recovered_sentinel_scope(return_type)
2463 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2464 else {
2465 return;
2466 };
2467 let components = [name.to_string()];
2468 let resolution = ctx.visibility.resolve_type_components_lexically(
2469 &ctx.analyzer,
2470 ctx.file,
2471 &components,
2472 false,
2473 &scope,
2474 );
2475 if let LexicalTypeResolution::Resolved {
2476 unit, candidates, ..
2477 } = resolution
2478 && (same_visible_symbol(&unit, &ctx.spec.target)
2479 || candidates
2480 .iter()
2481 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2482 {
2483 *ctx.raw_match_count += 1;
2484 push_type_hit(return_type, ctx);
2485 }
2486}
2487
2488fn member_pointer_owner_components<'tree>(
2493 node: Node<'tree>,
2494 source: &str,
2495) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2496 if node.kind() != "qualified_identifier" {
2497 return None;
2498 }
2499 let declarator = node.child_by_field_name("name")?;
2500 if !matches!(
2501 declarator.kind(),
2502 "pointer_type_declarator" | "abstract_pointer_declarator"
2503 ) {
2504 return None;
2505 }
2506 let scope = node.child_by_field_name("scope")?;
2507 let mut saw_scope = false;
2514 let has_scope_separator = (0..node.child_count()).any(|index| {
2515 let Some(child) = node.child(index) else {
2516 return false;
2517 };
2518 if same_node(child, scope) {
2519 saw_scope = true;
2520 return false;
2521 }
2522 saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2523 });
2524 if !has_scope_separator {
2525 return None;
2526 }
2527 let mut nodes = cpp_name_component_nodes(scope)?;
2528 let mut outer = node;
2529 while let Some(parent) = outer.parent()
2530 && parent.kind() == "qualified_identifier"
2531 && parent.child_by_field_name("name") == Some(outer)
2532 {
2533 let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2534 prefix.append(&mut nodes);
2535 nodes = prefix;
2536 outer = parent;
2537 }
2538 let names = nodes
2539 .iter()
2540 .map(|component| node_text(*component, source).to_string())
2541 .collect();
2542 Some((
2543 QualifiedOwnerComponents {
2544 nodes,
2545 names,
2546 global: is_globally_qualified_cpp_name(outer),
2547 },
2548 outer,
2549 ))
2550}
2551
2552fn member_pointer_alias_owner_prefix_matches(
2553 node: Node<'_>,
2554 owner: &QualifiedOwnerComponents<'_>,
2555 ctx: &ScanCtx<'_>,
2556) -> bool {
2557 let Some((_, owner_prefix)) = owner.names.split_last() else {
2558 return false;
2559 };
2560 let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2561 return false;
2562 };
2563 let recovered_scope = ctx.recovered_sentinel_scope(node);
2564 let resolution = if let Some(recovered_scope) = recovered_scope {
2565 resolve_type_components_lexically_at_for_target_with_recovered_scope(
2566 node,
2567 owner_prefix,
2568 owner.global,
2569 &ctx.analyzer,
2570 ctx.visibility,
2571 &ctx.ordinary_type_imports,
2572 ctx.file,
2573 ctx.source,
2574 &parent,
2575 false,
2576 &recovered_scope,
2577 )
2578 } else {
2579 resolve_type_components_lexically_at_for_target_with_scope_cache(
2580 node,
2581 owner_prefix,
2582 owner.global,
2583 &ctx.analyzer,
2584 ctx.visibility,
2585 &ctx.ordinary_type_imports,
2586 ctx.file,
2587 ctx.source,
2588 &parent,
2589 false,
2590 Some(&ctx.lexical_scope_cache),
2591 )
2592 };
2593 let LexicalTypeResolution::Resolved {
2594 unit, candidates, ..
2595 } = resolution
2596 else {
2597 return false;
2598 };
2599 same_member_pointer_owner_identity(&unit, &parent)
2600 || candidates
2601 .iter()
2602 .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2603}
2604
2605fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2606 same_visible_symbol(left, right)
2607 || (left.kind() == right.kind()
2608 && left.fq_name() == right.fq_name()
2609 && left.source() == right.source())
2610}
2611
2612fn resolve_nested_template_type_for_target(
2619 node: Node<'_>,
2620 ctx: &ScanCtx<'_>,
2621) -> Option<LexicalTypeResolution> {
2622 let reference_node = node
2623 .parent()
2624 .filter(|parent| {
2625 parent.kind() == "qualified_identifier"
2626 && parent.child_by_field_name("name") == Some(node)
2627 })
2628 .unwrap_or(node);
2629 let target_resolution = resolve_type_node_lexically_for_target(
2630 reference_node,
2631 &ctx.analyzer,
2632 ctx.visibility,
2633 &ctx.ordinary_type_imports,
2634 ctx.file,
2635 ctx.source,
2636 &ctx.spec.target,
2637 Some(&ctx.lexical_scope_cache),
2638 ctx.recovered_sentinel_scope(reference_node).as_deref(),
2639 );
2640 if let LexicalTypeResolution::Resolved {
2641 unit, candidates, ..
2642 } = target_resolution
2643 && template_reference_candidates_select_target(
2644 reference_node,
2645 &candidates,
2646 &ctx.analyzer,
2647 ctx.visibility,
2648 ctx.file,
2649 ctx.source,
2650 &ctx.spec.target,
2651 )
2652 {
2653 return Some(LexicalTypeResolution::Resolved {
2654 unit,
2655 components: Vec::new(),
2656 candidates,
2657 });
2658 }
2659
2660 let normal_resolution = resolve_type_node_lexically(
2661 reference_node,
2662 &ctx.analyzer,
2663 ctx.visibility,
2664 &ctx.ordinary_type_imports,
2665 ctx.file,
2666 ctx.source,
2667 );
2668 let LexicalTypeResolution::Resolved {
2669 unit,
2670 components,
2671 candidates,
2672 } = normal_resolution
2673 else {
2674 return None;
2675 };
2676 let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2677 let specialized = ctx
2678 .visibility
2679 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2680 .ok()
2681 .unwrap_or(unit);
2682 (same_visible_symbol(&specialized, &ctx.spec.target)
2683 || template_type_component_preserves_target(reference_node, &candidates, ctx))
2684 .then_some(LexicalTypeResolution::Resolved {
2685 unit: specialized,
2686 components,
2687 candidates,
2688 })
2689}
2690
2691fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2692 let mut reference = node;
2693 while let Some(parent) = ctx.ancestry.parent(reference) {
2694 let owns_component = matches!(parent.kind(), "template_type" | "template_function")
2695 && parent.child_by_field_name("name") == Some(reference)
2696 || matches!(
2697 parent.kind(),
2698 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2699 ) && (parent.child_by_field_name("scope") == Some(reference)
2700 || parent.child_by_field_name("name") == Some(reference));
2701 if !owns_component {
2702 break;
2703 }
2704 reference = parent;
2705 }
2706 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2707 return false;
2708 };
2709 if !global
2710 && components.len() > 1
2711 && let Some(alias_type) = local_receiver_alias_type_node(reference, &components[0], ctx)
2712 {
2713 let Ok(Some(owner)) = resolve_receiver_type_node(alias_type, ctx) else {
2714 return false;
2715 };
2716 let mut redirected = canonical_cpp_scope_components(&owner);
2717 redirected.extend_from_slice(&components[1..]);
2718 return type_reference_component_list_may_name_target(
2719 &redirected,
2720 true,
2721 &ctx.analyzer,
2722 ctx.visibility,
2723 ctx.file,
2724 &ctx.spec.target,
2725 &ctx.type_reference_component_names,
2726 );
2727 }
2728 type_reference_component_list_may_name_target(
2729 &components,
2730 global,
2731 &ctx.analyzer,
2732 ctx.visibility,
2733 ctx.file,
2734 &ctx.spec.target,
2735 &ctx.type_reference_component_names,
2736 )
2737}
2738
2739fn type_reference_component_list_may_name_target(
2740 components: &[String],
2741 global: bool,
2742 analyzer: &CppGraphSource<'_>,
2743 visibility: &VisibilityIndex<'_>,
2744 file: &ProjectFile,
2745 target: &CodeUnit,
2746 reference_component_names: &HashSet<String>,
2747) -> bool {
2748 if components.iter().any(|component| {
2749 visibility.type_reference_component_directly_names_target(component, target)
2750 }) {
2751 return true;
2752 }
2753 if components.iter().any(|component| {
2754 visibility.parser_alias_name_may_resolve_to_target(file, component, target)
2755 }) {
2756 return true;
2757 }
2758 if components.len() > 1 {
2759 return visibility.qualified_alias_reference_may_reach_target(
2760 analyzer, file, components, global, target,
2761 );
2762 }
2763 components
2764 .iter()
2765 .any(|component| reference_component_names.contains(component))
2766}
2767
2768fn file_may_reference_type_target(
2774 root: Node<'_>,
2775 source: &str,
2776 analyzer: &CppGraphSource<'_>,
2777 visibility: &VisibilityIndex<'_>,
2778 file: &ProjectFile,
2779 target: &CodeUnit,
2780 reference_component_names: &HashSet<String>,
2781) -> bool {
2782 visit_file_type_reference_spellings(root, source, |components, global| {
2783 type_reference_component_list_may_name_target(
2784 components,
2785 global,
2786 analyzer,
2787 visibility,
2788 file,
2789 target,
2790 reference_component_names,
2791 )
2792 })
2793}
2794
2795fn visit_file_type_reference_spellings(
2796 root: Node<'_>,
2797 source: &str,
2798 mut visit: impl FnMut(&[String], bool) -> bool,
2799) -> bool {
2800 let mut stopped = false;
2801 walk_named_tree_preorder(root, true, |node| {
2802 if node.kind() == "comment" {
2803 return WalkControl::SkipChildren;
2804 }
2805 if node.kind() == "preproc_arg" {
2806 stopped = object_macro_replacement_type_references(node, source)
2807 .into_iter()
2808 .any(|reference| visit(&reference.components, reference.global));
2809 return if stopped {
2810 WalkControl::Break
2811 } else {
2812 WalkControl::SkipChildren
2813 };
2814 }
2815 if node.kind() == "field_declaration"
2816 && let Some(return_type) = recovered_macro_return_type_node(node, source)
2817 {
2818 let components = [node_text(return_type, source).to_string()];
2819 stopped = visit(&components, false);
2820 if stopped {
2821 return WalkControl::Break;
2822 }
2823 }
2824 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
2825 for candidate in [node, type_node] {
2826 let Some((components, global)) = type_reference_components(candidate, source)
2827 else {
2828 continue;
2829 };
2830 stopped = visit(&components, global);
2831 if stopped {
2832 return WalkControl::Break;
2833 }
2834 }
2835 }
2836 let Some((components, global)) = type_reference_components(node, source) else {
2837 return WalkControl::Continue;
2838 };
2839 if node.parent().is_some_and(|parent| {
2840 (matches!(parent.kind(), "template_type" | "template_function")
2841 && parent.child_by_field_name("name") == Some(node))
2842 || (matches!(
2843 parent.kind(),
2844 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2845 ) && (parent.child_by_field_name("scope") == Some(node)
2846 || parent.child_by_field_name("name") == Some(node)))
2847 }) {
2848 return WalkControl::Continue;
2849 }
2850 stopped = visit(&components, global);
2851 if stopped {
2852 WalkControl::Break
2853 } else {
2854 WalkControl::Continue
2855 }
2856 });
2857 stopped
2858}
2859
2860fn type_reference_resolves_away_from_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2873 let Some((components, _global)) = type_reference_components(node, ctx.source) else {
2874 return false;
2875 };
2876 let terminal = components
2877 .last()
2878 .expect("type reference components are non-empty");
2879 if terminal != ctx.spec.target.identifier() {
2880 return false;
2881 }
2882 if cpp_template_reference_arguments(node, ctx.source).is_some()
2887 && ctx.visibility.is_template_specialization(&ctx.spec.target)
2888 {
2889 return false;
2890 }
2891 if ctx
2898 .visibility
2899 .visible_identifier_candidates(ctx.file, terminal)
2900 .any(|candidate| {
2901 candidate.kind() == ctx.spec.target.kind()
2902 && candidate.fq_name() == ctx.spec.target.fq_name()
2903 })
2904 {
2905 return false;
2906 }
2907 !ctx.visibility
2908 .structured_type_reference_may_resolve_to_target(
2909 &ctx.analyzer,
2910 ctx.file,
2911 std::slice::from_ref(terminal),
2912 false,
2913 &[],
2914 &ctx.spec.target,
2915 )
2916}
2917
2918fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2919 let Some(scope) = node.child_by_field_name("scope") else {
2920 return false;
2921 };
2922 let mut pending = vec![scope];
2923 while let Some(candidate) = pending.pop() {
2924 if candidate.kind() == "template_type" {
2925 return true;
2926 }
2927 if matches!(
2928 candidate.kind(),
2929 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2930 ) {
2931 if let Some(scope) = candidate.child_by_field_name("scope") {
2932 pending.push(scope);
2933 }
2934 if let Some(name) = candidate.child_by_field_name("name") {
2935 pending.push(name);
2936 }
2937 }
2938 }
2939 false
2940}
2941
2942fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2943 ctx.target_group.iter().find(|target| {
2944 same_logical_symbol(target, &ctx.spec.target)
2945 && ctx.visibility.is_physically_visible(ctx.file, target)
2946 })
2947}
2948
2949fn push_resolved_type_hit(node: Node<'_>, candidates: &[CodeUnit], ctx: &mut ScanCtx<'_>) {
2953 if let Some(terminal) = resolved_alias_terminal_node(node, candidates, ctx) {
2954 push_type_hit_range(terminal, terminal.start_byte(), terminal.end_byte(), ctx);
2955 } else {
2956 push_type_hit(type_reference_hit_node(node), ctx);
2957 }
2958}
2959
2960fn resolved_alias_terminal_node<'tree>(
2961 node: Node<'tree>,
2962 candidates: &[CodeUnit],
2963 ctx: &ScanCtx<'_>,
2964) -> Option<Node<'tree>> {
2965 let [candidate] = candidates else {
2966 return None;
2967 };
2968 let alias_provider = ctx.analyzer.type_alias_provider()?;
2969 let candidate_is_alias = alias_provider.is_type_alias(candidate)
2970 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target);
2971 let target_is_alias = alias_provider.is_type_alias(&ctx.spec.target)
2972 && same_visible_symbol(candidate, &ctx.spec.target);
2973 if !(candidate_is_alias || target_is_alias) {
2974 return None;
2975 }
2976 if crate::graph::resolver::cpp_template_reference_arguments(node, ctx.source).is_some() {
2977 return None;
2978 }
2979 let reference = crate::graph::resolver::qualified_alias_reference_preserves_target(
2980 node,
2981 &ctx.spec.target,
2982 &ctx.analyzer,
2983 ctx.visibility,
2984 ctx.file,
2985 ctx.source,
2986 );
2987 if reference
2988 != Some(
2989 crate::graph::resolver::QualifiedAliasReferenceKind::ConstructorWithExpressionArgument,
2990 )
2991 {
2992 return None;
2993 }
2994 cpp_name_component_nodes(node)?.last().copied()
2995}
2996
2997fn target_guided_missing_direct_temporary_type<'tree>(
2998 function: Node<'tree>,
2999 ctx: &ScanCtx<'_>,
3000) -> Option<Node<'tree>> {
3001 let target = physically_visible_type_target(ctx)?;
3002 let component_nodes = cpp_name_component_nodes(function)?;
3003 let terminal = component_nodes.last().copied()?;
3004 if node_text(terminal, ctx.source) != target.identifier() {
3005 return None;
3006 }
3007 let components = component_nodes
3008 .iter()
3009 .map(|component| node_text(*component, ctx.source).to_string())
3010 .collect::<Vec<_>>();
3011 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
3012 indexed_scope_matches_target_name(
3013 &indexed_scope,
3014 &components,
3015 is_globally_qualified_cpp_name(function),
3016 target,
3017 )
3018 .then_some(terminal)
3019}
3020
3021fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
3022 let Some(function) = call.child_by_field_name("function") else {
3023 return;
3024 };
3025 if !matches!(
3026 function.kind(),
3027 "identifier"
3028 | "type_identifier"
3029 | "template_function"
3030 | "template_type"
3031 | "qualified_identifier"
3032 | "scoped_identifier"
3033 | "scoped_type_identifier"
3034 ) {
3035 return;
3036 }
3037 if !type_reference_components_may_name_target(function, ctx) {
3038 return;
3039 }
3040 if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
3041 *ctx.raw_match_count += 1;
3042 for scope in scopes {
3043 push_type_hit(scope, ctx);
3044 }
3045 return;
3046 }
3047 let terminal = function_terminal_node(function);
3048 let name = node_text(terminal, ctx.source);
3049 if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
3050 return;
3051 }
3052 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
3053 match resolve_declaring_member_owner(
3054 &ctx.analyzer,
3055 ctx.visibility,
3056 ctx.file,
3057 &enclosing_owner,
3058 name,
3059 ) {
3060 EnclosingMemberOwnerResolution::Owner(owner)
3061 if matches!(
3062 ctx.visibility
3063 .visible_member_for_owner_name(ctx.file, &owner, name,),
3064 VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
3065 ) =>
3066 {
3067 return;
3068 }
3069 EnclosingMemberOwnerResolution::Ambiguous => return,
3070 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
3071 }
3072 }
3073
3074 if ctx
3081 .analyzer
3082 .type_alias_provider()
3083 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3084 && name == ctx.spec.target.identifier()
3085 && physically_visible_type_target(ctx).is_some()
3086 && !local_type_name_shadows(function, ctx)
3087 && type_alias_owner_matches_structured_reference(function, ctx)
3088 && ctx.visibility.external_type_candidate_visible_in_context(
3089 &ctx.analyzer,
3090 ctx.file,
3091 &ctx.spec.target,
3092 function,
3093 )
3094 {
3095 *ctx.raw_match_count += 1;
3096 push_type_hit(terminal, ctx);
3097 return;
3098 }
3099
3100 let call_resolution = resolve_qualified_call_target(
3101 call,
3102 function,
3103 &ctx.analyzer,
3104 ctx.visibility,
3105 &ctx.ordinary_type_imports,
3106 ctx.file,
3107 ctx.source,
3108 );
3109 match call_resolution {
3110 BareCallTargetResolution::Type(unit) => {
3111 if same_visible_symbol(&unit, &ctx.spec.target) {
3112 *ctx.raw_match_count += 1;
3113 push_type_hit(function, ctx);
3114 return;
3115 }
3116 }
3117 BareCallTargetResolution::FreeFunctions(units)
3118 if units.iter().all(|unit| {
3119 unit.fq_name() == ctx.spec.target.fq_name()
3120 && ctx
3121 .visibility
3122 .callable_is_constructor_declaration(&ctx.analyzer, unit)
3123 }) => {}
3124 BareCallTargetResolution::Ambiguous => {
3125 push_unproven_hit(function, ctx);
3126 return;
3127 }
3128 BareCallTargetResolution::FreeFunctions(_)
3129 | BareCallTargetResolution::UnprovenFreeFunctions(_)
3130 | BareCallTargetResolution::CallableShadow => return,
3131 BareCallTargetResolution::Missing => {}
3136 }
3137 let target_resolution = resolve_type_node_lexically_for_target(
3138 function,
3139 &ctx.analyzer,
3140 ctx.visibility,
3141 &ctx.ordinary_type_imports,
3142 ctx.file,
3143 ctx.source,
3144 &ctx.spec.target,
3145 Some(&ctx.lexical_scope_cache),
3146 ctx.recovered_sentinel_scope(function).as_deref(),
3147 );
3148 match target_resolution {
3149 LexicalTypeResolution::Resolved {
3150 unit, candidates, ..
3151 } if same_visible_symbol(&unit, &ctx.spec.target)
3152 || candidates
3153 .iter()
3154 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
3155 {
3156 *ctx.raw_match_count += 1;
3157 push_type_hit(function, ctx);
3158 }
3159 LexicalTypeResolution::Missing => {
3160 if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
3161 .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
3162 {
3163 *ctx.raw_match_count += 1;
3164 push_type_hit(hit, ctx);
3165 }
3166 }
3167 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
3168 }
3169}
3170
3171pub enum BareCallTargetResolution {
3172 Type(CodeUnit),
3173 FreeFunctions(Vec<CodeUnit>),
3174 UnprovenFreeFunctions(Vec<CodeUnit>),
3175 CallableShadow,
3176 Ambiguous,
3177 Missing,
3178}
3179
3180pub enum BlockUsingCallTargetResolution {
3181 Target(BareCallTargetResolution),
3182 Unindexed(Vec<String>),
3183 Ambiguous,
3184}
3185
3186#[allow(clippy::too_many_arguments)]
3187fn resolve_qualified_call_target(
3188 call: Node<'_>,
3189 function: Node<'_>,
3190 analyzer: &CppGraphSource<'_>,
3191 visibility: &VisibilityIndex,
3192 ordinary_type_imports: &OrdinaryTypeImportCell,
3193 file: &ProjectFile,
3194 source: &str,
3195) -> BareCallTargetResolution {
3196 if matches!(function.kind(), "identifier" | "template_function") {
3197 return resolve_bare_call_target(
3198 call,
3199 function,
3200 analyzer,
3201 visibility,
3202 ordinary_type_imports,
3203 file,
3204 source,
3205 );
3206 }
3207 if !matches!(
3208 function.kind(),
3209 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
3210 ) {
3211 return BareCallTargetResolution::Missing;
3212 }
3213 let terminal = function_terminal_node(function);
3214 let name = node_text(terminal, source);
3215 let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
3216 return BareCallTargetResolution::Missing;
3217 };
3218 components.push(name.to_string());
3219 let qualified_name = components.join("::");
3220 let type_resolution = resolve_type_node_lexically(
3221 function,
3222 analyzer,
3223 visibility,
3224 ordinary_type_imports,
3225 file,
3226 source,
3227 );
3228 let same_name_resolves_to_type = matches!(
3229 &type_resolution,
3230 LexicalTypeResolution::Resolved {
3231 unit,
3232 candidates,
3233 ..
3234 } if cpp_name_for(unit) == qualified_name
3235 || candidates
3236 .iter()
3237 .any(|candidate| cpp_name_for(candidate) == qualified_name)
3238 );
3239 let has_explicit_template_arguments =
3240 cpp_template_reference_arguments(function, source).is_some();
3241 let candidates = visibility
3242 .visible_identifier_candidates(file, name)
3243 .filter(|candidate| {
3244 candidate.is_function()
3245 && type_owner_of(analyzer, candidate).is_none()
3246 && !(same_name_resolves_to_type
3247 && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3248 || has_explicit_template_arguments
3249 && visibility
3250 .callable_is_deduction_guide_declaration(analyzer, candidate)))
3251 && cpp_name_for(candidate) == qualified_name
3252 && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
3253 })
3254 .cloned()
3255 .collect::<Vec<_>>();
3256 if !candidates.is_empty() {
3257 return resolve_callable_candidates(
3258 candidates,
3259 visibility.call_arity_evidence(file, call, source).exact(),
3260 call.start_byte(),
3261 analyzer,
3262 visibility,
3263 file,
3264 );
3265 }
3266 match type_resolution {
3267 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3268 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3269 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3270 }
3271}
3272
3273fn ordinary_binding_lookup_targets(
3274 binding: &OrdinaryTypeImport,
3275 active_bindings: &[&OrdinaryTypeImport],
3276 name: &str,
3277) -> Vec<Vec<String>> {
3278 let Some(qualified) = binding.resolved_target_components.as_ref() else {
3279 return Vec::new();
3280 };
3281 let mut targets = Vec::new();
3282 match binding.target {
3283 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
3284 EffectiveUsingTarget::Namespace { .. } => {
3285 let mut stack = vec![qualified.clone()];
3286 let mut visited = HashSet::default();
3287 while let Some(namespace) = stack.pop() {
3288 if !visited.insert(namespace.clone()) {
3289 continue;
3290 }
3291 let mut target = namespace.clone();
3292 target.push(name.to_string());
3293 targets.push(target);
3294 stack.extend(active_bindings.iter().filter_map(|candidate| {
3295 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
3296 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
3297 .then(|| candidate.resolved_target_components.clone())
3298 .flatten()
3299 }));
3300 }
3301 }
3302 }
3303 targets
3304}
3305
3306fn binding_free_function_candidates(
3307 binding: &OrdinaryTypeImport,
3308 active_bindings: &[&OrdinaryTypeImport],
3309 analyzer: &CppGraphSource<'_>,
3310 visibility: &VisibilityIndex<'_>,
3311 file: &ProjectFile,
3312 name: &str,
3313 reference_byte: usize,
3314) -> Vec<CodeUnit> {
3315 ordinary_binding_lookup_targets(binding, active_bindings, name)
3316 .into_iter()
3317 .flat_map(|target| {
3318 let qualified_name = target.join("::");
3319 visibility
3320 .visible_identifier_candidates(file, name)
3321 .filter(move |candidate| {
3322 candidate.is_function()
3323 && type_owner_of(analyzer, candidate).is_none()
3324 && cpp_name_for(candidate) == qualified_name
3325 && visibility.declaration_visible_at(
3326 analyzer,
3327 file,
3328 candidate,
3329 reference_byte,
3330 )
3331 })
3332 .cloned()
3333 })
3334 .collect()
3335}
3336
3337fn dedupe_callable_candidates(
3347 candidates: &mut Vec<CodeUnit>,
3348 analyzer: &CppGraphSource<'_>,
3349 visibility: &VisibilityIndex<'_>,
3350) {
3351 let mut deduped = Vec::with_capacity(candidates.len());
3352 for candidate in candidates.drain(..) {
3353 if !deduped
3354 .iter()
3355 .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
3356 {
3357 deduped.push(candidate);
3358 }
3359 }
3360 *candidates = deduped;
3361}
3362
3363fn resolve_callable_candidates(
3364 candidates: Vec<CodeUnit>,
3365 call_arity: Option<usize>,
3366 reference_byte: usize,
3367 analyzer: &CppGraphSource<'_>,
3368 visibility: &VisibilityIndex<'_>,
3369 file: &ProjectFile,
3370) -> BareCallTargetResolution {
3371 let mut candidates = candidates;
3372 dedupe_callable_candidates(&mut candidates, analyzer, visibility);
3373 if candidates.is_empty() {
3374 return BareCallTargetResolution::Missing;
3375 }
3376 let Some(call_arity) = call_arity else {
3377 if candidates.len() == 1 {
3384 return BareCallTargetResolution::FreeFunctions(candidates);
3385 }
3386 return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
3387 };
3388 let applicable = candidates
3389 .into_iter()
3390 .filter(|candidate| {
3391 visibility
3392 .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
3393 .is_some_and(|arity| arity.accepts(call_arity))
3394 })
3395 .collect::<Vec<_>>();
3396 if applicable.is_empty() {
3397 BareCallTargetResolution::CallableShadow
3398 } else {
3399 BareCallTargetResolution::FreeFunctions(applicable)
3400 }
3401}
3402
3403fn deduplicate_type_import_candidates(
3404 candidates: Vec<(CodeUnit, Vec<String>)>,
3405) -> Vec<(CodeUnit, Vec<String>)> {
3406 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
3407 for candidate in candidates {
3408 if !logical
3409 .iter()
3410 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
3411 {
3412 logical.push(candidate);
3413 }
3414 }
3415 logical
3416}
3417
3418fn resolve_direct_type_candidates(
3419 candidates: Vec<(CodeUnit, Vec<String>)>,
3420 analyzer: &CppGraphSource<'_>,
3421 visibility: &VisibilityIndex<'_>,
3422 file: &ProjectFile,
3423) -> BareCallTargetResolution {
3424 let logical = deduplicate_type_import_candidates(candidates);
3425 let [(target, components)] = logical.as_slice() else {
3426 return if logical.is_empty() {
3427 BareCallTargetResolution::Missing
3428 } else {
3429 BareCallTargetResolution::Ambiguous
3430 };
3431 };
3432 match visibility
3433 .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
3434 {
3435 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3436 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3437 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3438 }
3439}
3440
3441#[allow(clippy::too_many_arguments)]
3446pub fn resolve_block_using_call_target(
3447 call: Node<'_>,
3448 function: Node<'_>,
3449 analyzer: &CppGraphSource<'_>,
3450 visibility: &VisibilityIndex<'_>,
3451 ordinary_type_imports: &OrdinaryTypeImportCell,
3452 file: &ProjectFile,
3453 source: &str,
3454) -> Option<BlockUsingCallTargetResolution> {
3455 if !matches!(function.kind(), "identifier" | "template_function") {
3456 return None;
3457 }
3458 let name = node_text(function_terminal_node(function), source);
3459 if name.is_empty() {
3460 return None;
3461 }
3462 let bindings = effective_using_bindings_for_name(
3463 visibility,
3464 ordinary_type_imports,
3465 file,
3466 function,
3467 source,
3468 name,
3469 );
3470 let block_bindings = bindings
3471 .iter()
3472 .filter(|binding| {
3473 binding.namespace_scope.is_none()
3474 && binding.block_scope
3475 && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
3476 })
3477 .collect::<Vec<_>>();
3478 if block_bindings.is_empty() {
3479 return None;
3480 }
3481 let lexical_scope =
3482 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3483 LexicalScopeResolution::Resolved(scope) => scope,
3484 LexicalScopeResolution::Ambiguous => {
3485 return Some(BlockUsingCallTargetResolution::Ambiguous);
3486 }
3487 LexicalScopeResolution::Missing => return None,
3488 };
3489 let reference_guards = preprocessor_guard_environment(function, source);
3490 let active = block_bindings
3491 .into_iter()
3492 .filter(|binding| {
3493 effective_using_binding_active(
3494 binding,
3495 function,
3496 &lexical_scope,
3497 reference_guards.as_ref(),
3498 visibility,
3499 file,
3500 )
3501 })
3502 .collect::<Vec<_>>();
3503 let depth = active.iter().map(|binding| binding.scope_depth).max()?;
3504 let at_tier = active
3505 .into_iter()
3506 .filter(|binding| binding.scope_depth == depth)
3507 .collect::<Vec<_>>();
3508 let callable_candidates = at_tier
3509 .iter()
3510 .flat_map(|binding| {
3511 binding_free_function_candidates(
3512 binding,
3513 &[],
3514 analyzer,
3515 visibility,
3516 file,
3517 name,
3518 call.start_byte(),
3519 )
3520 })
3521 .collect::<Vec<_>>();
3522 if !callable_candidates.is_empty() {
3523 return Some(BlockUsingCallTargetResolution::Target(
3524 resolve_callable_candidates(
3525 callable_candidates,
3526 visibility.call_arity_evidence(file, call, source).exact(),
3527 call.start_byte(),
3528 analyzer,
3529 visibility,
3530 file,
3531 ),
3532 ));
3533 }
3534 let type_candidates = at_tier
3535 .iter()
3536 .flat_map(|binding| {
3537 binding_type_candidates(
3538 binding,
3539 &[],
3540 analyzer,
3541 visibility,
3542 file,
3543 name,
3544 None,
3545 call.start_byte(),
3546 )
3547 })
3548 .collect::<Vec<_>>();
3549 if !type_candidates.is_empty() {
3550 return Some(BlockUsingCallTargetResolution::Target(
3551 resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
3552 ));
3553 }
3554
3555 let mut unindexed = Vec::new();
3556 for binding in at_tier {
3557 let Some(components) = binding.resolved_target_components.as_ref() else {
3558 continue;
3559 };
3560 if !unindexed.contains(components) {
3561 unindexed.push(components.clone());
3562 }
3563 }
3564 match unindexed.as_slice() {
3565 [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
3566 [] => None,
3567 _ => Some(BlockUsingCallTargetResolution::Ambiguous),
3568 }
3569}
3570
3571#[allow(clippy::too_many_arguments)]
3572pub fn resolve_bare_call_target(
3573 call: Node<'_>,
3574 function: Node<'_>,
3575 analyzer: &CppGraphSource<'_>,
3576 visibility: &VisibilityIndex<'_>,
3577 ordinary_type_imports: &OrdinaryTypeImportCell,
3578 file: &ProjectFile,
3579 source: &str,
3580) -> BareCallTargetResolution {
3581 if !matches!(function.kind(), "identifier" | "template_function") {
3582 return BareCallTargetResolution::Missing;
3583 }
3584 let terminal = function_terminal_node(function);
3585 let name = node_text(terminal, source);
3586 if name.is_empty() {
3587 return BareCallTargetResolution::Missing;
3588 }
3589 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
3590 let lexical_scope =
3591 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
3592 LexicalScopeResolution::Resolved(scope) => scope,
3593 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
3594 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
3595 };
3596 let type_resolution = resolve_type_node_lexically(
3597 function,
3598 analyzer,
3599 visibility,
3600 ordinary_type_imports,
3601 file,
3602 source,
3603 );
3604 let type_components = match &type_resolution {
3605 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3606 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3607 };
3608 let direct_type_resolution = visibility.resolve_type_components_lexically(
3609 analyzer,
3610 file,
3611 &[name.to_string()],
3612 false,
3613 &lexical_scope,
3614 );
3615 let direct_type_components = match &direct_type_resolution {
3616 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
3617 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
3618 };
3619 let has_explicit_template_arguments =
3620 cpp_template_reference_arguments(function, source).is_some();
3621 let bindings = effective_using_bindings_for_name(
3622 visibility,
3623 ordinary_type_imports,
3624 file,
3625 function,
3626 source,
3627 name,
3628 );
3629 let function_guards = if bindings.is_empty() {
3633 None
3634 } else {
3635 preprocessor_guard_environment(function, source)
3636 };
3637 let active_bindings = bindings
3638 .iter()
3639 .filter(|binding| {
3640 effective_using_binding_active(
3641 binding,
3642 function,
3643 &lexical_scope,
3644 function_guards.as_ref(),
3645 visibility,
3646 file,
3647 )
3648 })
3649 .collect::<Vec<_>>();
3650 let transitive_bindings = bindings
3651 .iter()
3652 .filter(|binding| {
3653 effective_using_binding_guards_active(
3654 binding,
3655 function.start_byte(),
3656 function_guards.as_ref(),
3657 visibility,
3658 file,
3659 ) && (binding.namespace_scope.is_some()
3660 || (binding.scope_start <= function.start_byte()
3661 && function.end_byte() <= binding.scope_end))
3662 })
3663 .collect::<Vec<_>>();
3664 let mut concrete_depths = active_bindings
3665 .iter()
3666 .filter(|binding| binding.namespace_scope.is_none())
3667 .map(|binding| binding.scope_depth)
3668 .collect::<Vec<_>>();
3669 concrete_depths.sort_unstable();
3670 concrete_depths.dedup();
3671 for depth in concrete_depths.into_iter().rev() {
3672 let at_tier = active_bindings
3673 .iter()
3674 .copied()
3675 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
3676 let direct = at_tier
3677 .clone()
3678 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3679 .flat_map(|binding| {
3680 binding_free_function_candidates(
3681 binding,
3682 &transitive_bindings,
3683 analyzer,
3684 visibility,
3685 file,
3686 name,
3687 call.start_byte(),
3688 )
3689 })
3690 .collect::<Vec<_>>();
3691 if !direct.is_empty() {
3692 return resolve_callable_candidates(
3693 direct,
3694 call_arity,
3695 call.start_byte(),
3696 analyzer,
3697 visibility,
3698 file,
3699 );
3700 }
3701 let direct_types = at_tier
3702 .clone()
3703 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3704 .flat_map(|binding| {
3705 binding_type_candidates(
3706 binding,
3707 &transitive_bindings,
3708 analyzer,
3709 visibility,
3710 file,
3711 name,
3712 None,
3713 call.start_byte(),
3714 )
3715 })
3716 .collect::<Vec<_>>();
3717 if !direct_types.is_empty() {
3718 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3723 }
3724 let directives = at_tier
3725 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3726 .flat_map(|binding| {
3727 binding_free_function_candidates(
3728 binding,
3729 &transitive_bindings,
3730 analyzer,
3731 visibility,
3732 file,
3733 name,
3734 call.start_byte(),
3735 )
3736 })
3737 .collect::<Vec<_>>();
3738 if !directives.is_empty() {
3739 return resolve_callable_candidates(
3740 directives,
3741 call_arity,
3742 call.start_byte(),
3743 analyzer,
3744 visibility,
3745 file,
3746 );
3747 }
3748 }
3749 for prefix_len in (0..=lexical_scope.len()).rev() {
3750 let mut qualified = lexical_scope[..prefix_len].to_vec();
3751 qualified.push(name.to_string());
3752 let same_name_resolves_to_type = direct_type_components
3753 .is_some_and(|components| components == qualified.as_slice())
3754 || type_components.is_some_and(|components| components == qualified.as_slice());
3755 let mut direct = visibility
3756 .visible_identifier_candidates(file, name)
3757 .filter(|candidate| {
3758 candidate.is_function()
3759 && type_owner_of(analyzer, candidate).is_none()
3760 && !(same_name_resolves_to_type
3761 && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3762 || has_explicit_template_arguments
3763 && visibility
3764 .callable_is_deduction_guide_declaration(analyzer, candidate)))
3765 && cpp_name_for(candidate) == qualified.join("::")
3766 && if analyzer.reference_uses_c_semantics(file) {
3767 visibility.declaration_visible_for_c_forward_call(
3768 analyzer,
3769 file,
3770 candidate,
3771 call.start_byte(),
3772 )
3773 } else {
3774 visibility.declaration_visible_at(
3775 analyzer,
3776 file,
3777 candidate,
3778 call.start_byte(),
3779 )
3780 }
3781 })
3782 .cloned()
3783 .collect::<Vec<_>>();
3784 let at_tier = active_bindings.iter().copied().filter(|binding| {
3785 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3786 });
3787 direct.extend(
3788 at_tier
3789 .clone()
3790 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3791 .flat_map(|binding| {
3792 binding_free_function_candidates(
3793 binding,
3794 &transitive_bindings,
3795 analyzer,
3796 visibility,
3797 file,
3798 name,
3799 call.start_byte(),
3800 )
3801 }),
3802 );
3803 if !direct.is_empty() {
3804 return resolve_callable_candidates(
3805 direct,
3806 call_arity,
3807 call.start_byte(),
3808 analyzer,
3809 visibility,
3810 file,
3811 );
3812 }
3813 let mut direct_types = at_tier
3814 .clone()
3815 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3816 .flat_map(|binding| {
3817 binding_type_candidates(
3818 binding,
3819 &transitive_bindings,
3820 analyzer,
3821 visibility,
3822 file,
3823 name,
3824 None,
3825 call.start_byte(),
3826 )
3827 })
3828 .collect::<Vec<_>>();
3829 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3830 && let LexicalTypeResolution::Resolved {
3831 unit, components, ..
3832 } = &direct_type_resolution
3833 {
3834 direct_types.push((unit.clone(), components.clone()));
3835 }
3836 if !direct_types.is_empty() {
3837 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3842 }
3843 let directives = at_tier
3844 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3845 .flat_map(|binding| {
3846 binding_free_function_candidates(
3847 binding,
3848 &transitive_bindings,
3849 analyzer,
3850 visibility,
3851 file,
3852 name,
3853 call.start_byte(),
3854 )
3855 })
3856 .collect::<Vec<_>>();
3857 if !directives.is_empty() {
3858 return resolve_callable_candidates(
3859 directives,
3860 call_arity,
3861 call.start_byte(),
3862 analyzer,
3863 visibility,
3864 file,
3865 );
3866 }
3867 if type_components.is_some_and(|components| components == qualified.as_slice()) {
3868 return match type_resolution {
3872 LexicalTypeResolution::Resolved { unit, .. } => {
3873 BareCallTargetResolution::Type(unit)
3874 }
3875 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3876 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3877 };
3878 }
3879 }
3880 match type_resolution {
3889 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3890 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3891 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3892 }
3893}
3894
3895fn static_qualifier_type_scopes<'tree>(
3896 node: Node<'tree>,
3897 ctx: &ScanCtx<'_>,
3898) -> Option<Vec<Node<'tree>>> {
3899 if !matches!(
3900 node.kind(),
3901 "qualified_identifier" | "scoped_type_identifier"
3902 ) {
3903 return None;
3904 }
3905 debug_assert!(!is_nested_type_node(node));
3908 let qualified = qualified_owner_components(node, ctx.source)?;
3909 static_qualifier_type_scopes_for_components(node, qualified, ctx)
3910}
3911
3912fn static_qualifier_type_scopes_for_components<'tree>(
3913 node: Node<'tree>,
3914 qualified: QualifiedOwnerComponents<'tree>,
3915 ctx: &ScanCtx<'_>,
3916) -> Option<Vec<Node<'tree>>> {
3917 if !qualified.global
3918 && let Some(name) = qualified.names.first()
3919 && let Some(alias_type) = local_receiver_alias_type_node(node, name, ctx)
3920 {
3921 return match resolve_receiver_type_node(alias_type, ctx) {
3922 Ok(Some(owner)) if same_visible_symbol(&owner, &ctx.spec.target) => {
3923 Some(vec![qualified.nodes[0]])
3924 }
3925 _ => None,
3926 };
3927 }
3928 let mut matches = Vec::new();
3929 let mut inherited_injected_name_is_shadowed = false;
3930 for component_count in 1..=qualified.names.len() {
3931 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3932 node,
3933 &qualified.names[..component_count],
3934 qualified.global,
3935 &ctx.analyzer,
3936 ctx.visibility,
3937 &ctx.ordinary_type_imports,
3938 ctx.file,
3939 ctx.source,
3940 &ctx.spec.target,
3941 false,
3942 Some(&ctx.lexical_scope_cache),
3943 );
3944 match resolution {
3945 LexicalTypeResolution::Resolved {
3946 unit, candidates, ..
3947 } if (!ctx
3948 .analyzer
3949 .type_alias_provider()
3950 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3951 || ctx.visibility.external_type_candidate_visible_in_context(
3952 &ctx.analyzer,
3953 ctx.file,
3954 &ctx.spec.target,
3955 node,
3956 ))
3957 && (same_visible_symbol(&unit, &ctx.spec.target)
3958 || candidates
3959 .iter()
3960 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3961 && target_alias_candidates_visible(&candidates, node, ctx) =>
3962 {
3963 let matched =
3964 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3965 if !template_type_component_preserves_target(matched, &candidates, ctx) {
3966 continue;
3967 }
3968 if !matches.iter().any(|existing: &Node<'_>| {
3969 existing.start_byte() == matched.start_byte()
3970 && existing.end_byte() == matched.end_byte()
3971 }) {
3972 matches.push(matched);
3973 }
3974 }
3975 LexicalTypeResolution::Ambiguous => {
3981 return (!inherited_injected_name_is_shadowed)
3982 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3983 .flatten()
3984 .map(|scope| vec![scope])
3985 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3986 }
3987 LexicalTypeResolution::Resolved { .. } => {
3988 if let Some(matched) =
3989 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3990 {
3991 matches.push(matched);
3992 }
3993 inherited_injected_name_is_shadowed |= component_count == 1;
3994 }
3995 LexicalTypeResolution::Missing => {
3996 if let Some(matched) =
3997 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3998 {
3999 matches.push(matched);
4000 }
4001 }
4002 }
4003 }
4004 if matches.is_empty() {
4005 (!inherited_injected_name_is_shadowed)
4006 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
4007 .flatten()
4008 .map(|scope| vec![scope])
4009 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
4010 } else {
4011 Some(matches)
4012 }
4013}
4014
4015enum TargetGuidedQualifiedValueOwnerHit<'tree> {
4018 Proven(Node<'tree>),
4019 Unproven(Node<'tree>),
4020}
4021
4022fn target_guided_qualified_value_owner_hit<'tree>(
4023 node: Node<'tree>,
4024 ctx: &ScanCtx<'_>,
4025) -> Option<TargetGuidedQualifiedValueOwnerHit<'tree>> {
4026 let target = physically_visible_type_target(ctx)?;
4027 if !target.is_class() {
4028 return None;
4029 }
4030 let qualified = qualified_owner_components(node, ctx.source)?;
4031 let lexical_scope = match enclosing_lexical_scope_components(
4032 node,
4033 &ctx.analyzer,
4034 ctx.visibility,
4035 ctx.file,
4036 ctx.source,
4037 ) {
4038 LexicalScopeResolution::Resolved(scope) => scope,
4039 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4040 enclosing_namespace_components(node, ctx.source)
4041 }
4042 };
4043 let LexicalTypeResolution::Resolved {
4044 unit, candidates, ..
4045 } = ctx.visibility.resolve_type_components_lexically_for_target(
4046 &ctx.analyzer,
4047 ctx.file,
4048 &qualified.names,
4049 qualified.global,
4050 &lexical_scope,
4051 target,
4052 )
4053 else {
4054 return None;
4055 };
4056 let scope = qualified.nodes.last().copied()?;
4057 let forward_uniquely_selects_target = match ctx
4061 .visibility
4062 .resolve_type_components_lexically_for_forward(
4063 &ctx.analyzer,
4064 ctx.file,
4065 &qualified.names,
4066 qualified.global,
4067 &lexical_scope,
4068 ) {
4069 LexicalTypeResolution::Resolved {
4070 unit, candidates, ..
4071 } if candidates.len() == 1 => {
4072 same_visible_symbol(&canonical_alias_target(&unit, ctx), target)
4073 && same_visible_symbol(&canonical_alias_target(&candidates[0], ctx), target)
4074 && ctx.visibility.external_type_candidate_visible_in_context(
4075 &ctx.analyzer,
4076 ctx.file,
4077 &candidates[0],
4078 scope,
4079 )
4080 }
4081 LexicalTypeResolution::Resolved { .. }
4082 | LexicalTypeResolution::Ambiguous
4083 | LexicalTypeResolution::Missing => false,
4084 };
4085 if forward_uniquely_selects_target
4086 || (same_visible_symbol(&unit, target)
4087 && !candidates.is_empty()
4088 && candidates
4089 .iter()
4090 .all(|candidate| same_visible_symbol(candidate, target)))
4091 {
4092 Some(TargetGuidedQualifiedValueOwnerHit::Proven(scope))
4093 } else if candidates
4094 .iter()
4095 .any(|candidate| same_visible_symbol(candidate, target))
4096 {
4097 Some(TargetGuidedQualifiedValueOwnerHit::Unproven(scope))
4098 } else {
4099 None
4100 }
4101}
4102
4103fn target_guided_nested_alias_type_scope<'tree>(
4109 node: Node<'tree>,
4110 qualified: &QualifiedOwnerComponents<'tree>,
4111 component_count: usize,
4112 ctx: &ScanCtx<'_>,
4113) -> Option<Node<'tree>> {
4114 if component_count < 2 {
4115 return None;
4116 }
4117 let (owner_components, member_name) =
4118 qualified.names[..component_count].split_at(component_count - 1);
4119 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
4120 node,
4121 owner_components,
4122 qualified.global,
4123 &ctx.analyzer,
4124 ctx.visibility,
4125 &ctx.ordinary_type_imports,
4126 ctx.file,
4127 ctx.source,
4128 ) else {
4129 return None;
4130 };
4131 let member_name = member_name.first()?;
4132 let alias_provider = ctx.analyzer.type_alias_provider()?;
4133 ctx.visibility
4134 .visible_members_for_owner_name(ctx.file, &owner, member_name)
4135 .into_iter()
4136 .filter(|member| alias_provider.is_type_alias(member))
4137 .find(|member| {
4138 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
4139 &ctx.analyzer,
4140 ctx.file,
4141 member,
4142 node,
4143 ) || ctx
4144 .visibility
4145 .external_type_candidate_guard_compatible_in_context(
4146 &ctx.analyzer,
4147 ctx.file,
4148 member,
4149 node,
4150 );
4151 if !member_visible {
4152 return false;
4153 }
4154 same_visible_symbol(member, &ctx.spec.target)
4155 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
4156 })
4157 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
4158}
4159
4160fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
4161 if ctx.visibility.structured_class_alias_resolves_to_target(
4162 &ctx.analyzer,
4163 ctx.file,
4164 candidate,
4165 &ctx.spec.target,
4166 ) {
4167 return ctx.spec.target.clone();
4168 }
4169 let structured = ctx
4170 .visibility
4171 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
4172 structured.unwrap_or_else(|| candidate.clone())
4173}
4174
4175fn class_alias_guard_compatible(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4179 ctx.visibility
4180 .external_type_candidate_guard_compatible_in_context(
4181 &ctx.analyzer,
4182 ctx.file,
4183 candidate,
4184 node,
4185 )
4186}
4187
4188fn visible_class_alias_candidate(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4189 ctx.visibility.is_physically_visible(ctx.file, candidate)
4190 && class_alias_guard_compatible(candidate, node, ctx)
4191}
4192
4193fn class_alias_path_preserves_target(
4194 candidate: &CodeUnit,
4195 target: &CodeUnit,
4196 ctx: &ScanCtx<'_>,
4197) -> bool {
4198 ctx.visibility.structured_class_alias_path_preserves_target(
4199 &ctx.analyzer,
4200 ctx.file,
4201 candidate,
4202 target,
4203 )
4204}
4205
4206fn target_guided_dependent_alias_qualifier_scope<'tree>(
4207 node: Node<'tree>,
4208 ctx: &ScanCtx<'_>,
4209) -> Option<Node<'tree>> {
4210 if !matches!(
4211 node.kind(),
4212 "qualified_identifier" | "scoped_type_identifier"
4213 ) {
4214 return None;
4215 }
4216 let target = physically_visible_type_target(ctx)?;
4217 let alias_provider = ctx.analyzer.type_alias_provider()?;
4218 if !target.is_class() || alias_provider.is_type_alias(target) {
4219 return None;
4220 }
4221 let nodes = cpp_name_component_nodes(node)?;
4222 let names = nodes
4223 .iter()
4224 .map(|component| node_text(*component, ctx.source).to_string())
4225 .collect::<Vec<_>>();
4226 let global = is_globally_qualified_cpp_name(node);
4227 let lexical_scope = match enclosing_lexical_scope_components(
4228 node,
4229 &ctx.analyzer,
4230 ctx.visibility,
4231 ctx.file,
4232 ctx.source,
4233 ) {
4234 LexicalScopeResolution::Resolved(scope) => scope,
4235 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4236 enclosing_namespace_components(node, ctx.source)
4237 }
4238 };
4239 for component_count in 1..=names.len() {
4240 let components = &names[..component_count];
4241 let name = components.last()?;
4242 let candidates = ctx
4243 .visibility
4244 .visible_identifier_candidates(ctx.file, name)
4245 .filter(|candidate| alias_provider.is_type_alias(candidate))
4246 .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4247 .filter(|candidate| {
4248 let candidate_components = canonical_cpp_scope_components(candidate);
4249 lexical_component_tiers(components, global, &lexical_scope)
4250 .any(|tier| tier == candidate_components)
4251 || (component_count == 1
4252 && member_alias_owner_matches_reference_for(
4253 candidate,
4254 nodes[component_count - 1],
4255 ctx,
4256 ))
4257 })
4258 .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx));
4259 let mut aliases: Vec<&CodeUnit> = Vec::new();
4260 for candidate in candidates {
4261 if !aliases
4262 .iter()
4263 .any(|existing| same_logical_symbol(existing, candidate))
4264 {
4265 aliases.push(candidate);
4266 }
4267 }
4268 if aliases.len() == 1 {
4269 return nodes.get(component_count - 1).copied();
4270 }
4271 if aliases.len() > 1 {
4272 return None;
4273 }
4274 }
4275 None
4276}
4277
4278fn target_guided_dependent_class_alias_leaf<'tree>(
4281 node: Node<'tree>,
4282 ctx: &ScanCtx<'_>,
4283) -> Option<Node<'tree>> {
4284 if node.kind() != "type_identifier"
4285 || is_declaration_name(node)
4286 || local_type_name_shadows(node, ctx)
4287 {
4288 return None;
4289 }
4290 let target = physically_visible_type_target(ctx)?;
4291 let alias_provider = ctx.analyzer.type_alias_provider()?;
4292 if !target.is_class() || alias_provider.is_type_alias(target) {
4293 return None;
4294 }
4295 let name = node_text(node, ctx.source);
4296 let aliases = ctx
4297 .visibility
4298 .visible_identifier_candidates(ctx.file, name)
4299 .filter(|candidate| alias_provider.is_type_alias(candidate))
4300 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4301 .filter(|candidate| visible_class_alias_candidate(candidate, node, ctx))
4302 .filter(|candidate| class_alias_path_preserves_target(candidate, target, ctx))
4303 .collect::<Vec<_>>();
4304 matches!(aliases.as_slice(), [_]).then_some(node)
4305}
4306
4307fn target_guided_unproven_alias_type_reference<'tree>(
4310 node: Node<'tree>,
4311 candidates: &[CodeUnit],
4312 ctx: &ScanCtx<'_>,
4313) -> Option<Node<'tree>> {
4314 let template_arguments = cpp_template_reference_arguments(node, ctx.source);
4315 let target = physically_visible_type_target(ctx)?;
4316 if !target.is_class() {
4317 return None;
4318 }
4319 let alias_provider = ctx.analyzer.type_alias_provider()?;
4320 let (components, _) = type_reference_components(node, ctx.source)?;
4321 let hit = template_arguments
4322 .as_ref()
4323 .and_then(|_| template_reference_name_node(node))
4324 .map(function_terminal_node)
4325 .unwrap_or_else(|| function_terminal_node(node));
4326 candidates
4327 .iter()
4328 .filter(|candidate| {
4329 alias_provider.is_type_alias(candidate)
4330 && ctx.visibility.is_physically_visible(ctx.file, candidate)
4331 && canonical_cpp_scope_components(candidate) == components
4332 })
4333 .find(|candidate| {
4334 template_arguments.as_ref().map_or_else(
4335 || {
4336 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4337 || ctx.visibility.structured_alias_primary_preserves_target(
4338 &ctx.analyzer,
4339 ctx.file,
4340 candidate,
4341 target,
4342 )
4343 },
4344 |arguments| {
4345 ctx.visibility.template_alias_arguments_preserve_target(
4346 &ctx.analyzer,
4347 ctx.file,
4348 candidate,
4349 arguments,
4350 target,
4351 )
4352 },
4353 )
4354 })
4355 .map(|_| hit)
4356}
4357
4358fn target_alias_candidates_visible(
4359 candidates: &[CodeUnit],
4360 reference: Node<'_>,
4361 ctx: &ScanCtx<'_>,
4362) -> bool {
4363 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4364 return true;
4365 };
4366 if candidates.iter().any(|candidate| {
4367 !alias_provider.is_type_alias(candidate)
4368 && ctx.visibility.same_template_member_identity(
4369 &ctx.analyzer,
4370 candidate,
4371 &ctx.spec.target,
4372 )
4373 }) {
4374 return true;
4375 }
4376 let target_aliases = candidates
4377 .iter()
4378 .filter(|candidate| {
4379 alias_provider.is_type_alias(candidate)
4380 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4381 })
4382 .collect::<Vec<_>>();
4383 target_aliases.is_empty()
4384 || target_aliases
4385 .iter()
4386 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
4387}
4388
4389fn type_candidate_visible_at_reference(
4390 candidate: &CodeUnit,
4391 reference: Node<'_>,
4392 ctx: &ScanCtx<'_>,
4393) -> bool {
4394 let class_owned_alias = ctx
4395 .analyzer
4396 .type_alias_provider()
4397 .is_some_and(|provider| provider.is_type_alias(candidate))
4398 && ctx
4399 .analyzer
4400 .parent_of(candidate)
4401 .is_some_and(|owner| owner.is_class());
4402 if class_owned_alias {
4403 let conditional_family = ctx
4404 .visibility
4405 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
4406 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
4407 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
4408 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
4409 let guard_match = ctx
4410 .visibility
4411 .external_type_candidate_guard_compatible_in_context(
4412 &ctx.analyzer,
4413 ctx.file,
4414 candidate,
4415 reference,
4416 );
4417 let general_match = conditional_family
4418 && ctx.visibility.external_type_candidate_visible_in_context(
4419 &ctx.analyzer,
4420 ctx.file,
4421 candidate,
4422 reference,
4423 );
4424 return owner_match && (guard_match || general_match);
4425 }
4426 ctx.visibility.external_type_candidate_visible_in_context(
4427 &ctx.analyzer,
4428 ctx.file,
4429 candidate,
4430 reference,
4431 )
4432}
4433
4434fn unqualified_reference_selects_inherited_alias(
4435 candidate: &CodeUnit,
4436 reference: Node<'_>,
4437 ctx: &ScanCtx<'_>,
4438) -> bool {
4439 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4440 return false;
4441 };
4442 if global || components.len() != 1 {
4443 return false;
4444 }
4445 matches!(
4446 resolve_type_node_lexically_for_target(
4447 reference,
4448 &ctx.analyzer,
4449 ctx.visibility,
4450 &ctx.ordinary_type_imports,
4451 ctx.file,
4452 ctx.source,
4453 candidate,
4454 Some(&ctx.lexical_scope_cache),
4455 ctx.recovered_sentinel_scope(reference).as_deref(),
4456 ),
4457 LexicalTypeResolution::Resolved {
4458 ref unit,
4459 ref candidates,
4460 ..
4461 } if ctx
4462 .visibility
4463 .same_template_member_identity(&ctx.analyzer, unit, candidate)
4464 || candidates.iter().any(|resolved| {
4465 ctx.visibility.same_template_member_identity(
4466 &ctx.analyzer,
4467 resolved,
4468 candidate,
4469 )
4470 })
4471 )
4472}
4473
4474fn qualified_reference_selects_type_candidate(
4475 candidate: &CodeUnit,
4476 reference: Node<'_>,
4477 ctx: &ScanCtx<'_>,
4478) -> bool {
4479 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
4480 return false;
4481 };
4482 if components.len() < 2 {
4483 return false;
4484 }
4485 let candidate_components = canonical_cpp_scope_components(candidate);
4486 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
4487 match enclosing_lexical_scope_components(
4488 reference,
4489 &ctx.analyzer,
4490 ctx.visibility,
4491 ctx.file,
4492 ctx.source,
4493 ) {
4494 LexicalScopeResolution::Resolved(scope) => scope,
4495 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4496 enclosing_namespace_components(reference, ctx.source)
4497 }
4498 }
4499 });
4500 lexical_component_tiers(&components, global, &lexical_scope)
4501 .any(|qualified| qualified == candidate_components)
4502}
4503
4504fn qualified_type_component_hit_node<'tree>(
4505 component: Node<'tree>,
4506 qualified: Node<'tree>,
4507) -> Node<'tree> {
4508 let mut current = component;
4509 while let Some(parent) = current.parent() {
4510 let is_type_name = matches!(
4511 parent.kind(),
4512 "template_type"
4513 | "qualified_identifier"
4514 | "scoped_identifier"
4515 | "scoped_type_identifier"
4516 ) && parent
4517 .child_by_field_name("name")
4518 .is_some_and(|name| same_node(name, current));
4519 if !is_type_name {
4520 break;
4521 }
4522 current = parent;
4523 if same_node(parent, qualified) {
4524 break;
4525 }
4526 }
4527 current
4528}
4529
4530fn template_type_component_preserves_target(
4531 node: Node<'_>,
4532 candidates: &[CodeUnit],
4533 ctx: &ScanCtx<'_>,
4534) -> bool {
4535 template_reference_candidates_select_target(
4536 node,
4537 candidates,
4538 &ctx.analyzer,
4539 ctx.visibility,
4540 ctx.file,
4541 ctx.source,
4542 &ctx.spec.target,
4543 )
4544}
4545
4546fn template_reference_candidates_select_target(
4547 node: Node<'_>,
4548 candidates: &[CodeUnit],
4549 analyzer: &CppGraphSource<'_>,
4550 visibility: &VisibilityIndex<'_>,
4551 file: &ProjectFile,
4552 source: &str,
4553 target: &CodeUnit,
4554) -> bool {
4555 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
4556 return !visibility.is_template_specialization(target);
4557 };
4558 let direct_template_name =
4559 template_reference_name_node(node).map(|name| node_text(name, source));
4560 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
4561 analyzer.type_alias_provider().is_some_and(|provider| {
4562 visibility
4563 .visible_identifier_candidates(file, name)
4564 .filter(|candidate| provider.is_type_alias(candidate))
4565 .any(|candidate| {
4566 visibility.template_alias_arguments_preserve_target(
4567 analyzer, file, candidate, &arguments, target,
4568 )
4569 })
4570 })
4571 });
4572 named_alias_selects_target
4573 || candidates.iter().any(|candidate| {
4574 (same_visible_symbol(candidate, target)
4575 && visibility.is_primary_template(target)
4576 && direct_template_name == Some(candidate.identifier()))
4577 || visibility.template_alias_arguments_preserve_target(
4578 analyzer, file, candidate, &arguments, target,
4579 )
4580 || visibility
4581 .resolve_template_arguments(file, candidate.clone(), &arguments)
4582 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
4583 })
4584}
4585
4586fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
4587 let template = if node.kind() == "template_type" {
4588 node
4589 } else {
4590 node.child_by_field_name("name")
4591 .filter(|name| name.kind() == "template_type")?
4592 };
4593 template.child_by_field_name("name")
4594}
4595
4596fn type_resolution_matches_target(
4597 node: Node<'_>,
4598 unit: &CodeUnit,
4599 candidates: &[CodeUnit],
4600 ctx: &ScanCtx<'_>,
4601) -> bool {
4602 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
4603}
4604
4605fn type_resolution_matches_unit_target(
4606 node: Node<'_>,
4607 unit: &CodeUnit,
4608 candidates: &[CodeUnit],
4609 target: &CodeUnit,
4610 ctx: &ScanCtx<'_>,
4611) -> bool {
4612 target_alias_candidates_visible(candidates, node, ctx)
4613 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
4614}
4615
4616fn type_resolution_identifies_unit_target(
4624 node: Node<'_>,
4625 unit: &CodeUnit,
4626 candidates: &[CodeUnit],
4627 target: &CodeUnit,
4628 ctx: &ScanCtx<'_>,
4629) -> bool {
4630 if !template_alias_owner_matches_reference(node, target, ctx) {
4631 return false;
4632 }
4633 if ctx.visibility.is_template_specialization(target)
4634 && cpp_template_reference_arguments(node, ctx.source).is_some()
4635 {
4636 let selected_unit =
4637 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
4638 ctx.visibility
4639 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
4640 .ok()
4641 });
4642 return selected_unit
4643 .as_ref()
4644 .is_some_and(|selected| same_visible_symbol(selected, target))
4645 || template_reference_candidates_select_target(
4646 node,
4647 candidates,
4648 &ctx.analyzer,
4649 ctx.visibility,
4650 ctx.file,
4651 ctx.source,
4652 target,
4653 );
4654 }
4655 unit == target
4656 || ctx
4657 .visibility
4658 .same_template_member_identity(&ctx.analyzer, unit, target)
4659 || ctx.visibility.c_tag_declaration_family_matches_target(
4660 &ctx.analyzer,
4661 ctx.file,
4662 &candidates.iter().collect::<Vec<_>>(),
4663 target,
4664 )
4665 || ctx.visibility.structured_class_alias_resolves_to_target(
4666 &ctx.analyzer,
4667 ctx.file,
4668 unit,
4669 target,
4670 )
4671 || candidates.iter().any(|candidate| {
4672 ctx.visibility
4673 .same_template_member_identity(&ctx.analyzer, candidate, target)
4674 || ctx.visibility.structured_class_alias_resolves_to_target(
4675 &ctx.analyzer,
4676 ctx.file,
4677 candidate,
4678 target,
4679 )
4680 })
4681}
4682
4683fn template_alias_owner_matches_reference(
4689 node: Node<'_>,
4690 target: &CodeUnit,
4691 ctx: &ScanCtx<'_>,
4692) -> bool {
4693 if !ctx
4694 .analyzer
4695 .type_alias_provider()
4696 .is_some_and(|provider| provider.is_type_alias(target))
4697 {
4698 return true;
4699 }
4700 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
4701 return true;
4702 };
4703 if !target_owner.is_class() {
4704 return true;
4705 }
4706 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4707 return true;
4708 };
4709 if !ctx.visibility.is_template_specialization(&target_owner)
4710 && !ctx.visibility.is_template_specialization(&reference_owner)
4711 {
4712 return true;
4713 }
4714 same_visible_symbol(&target_owner, &reference_owner)
4715}
4716
4717fn inherited_injected_class_qualifier_scope<'tree>(
4718 node: Node<'tree>,
4719 ctx: &ScanCtx<'_>,
4720) -> Option<Node<'tree>> {
4721 let qualified = qualified_owner_components(node, ctx.source)?;
4722 if qualified.global || qualified.names.is_empty() {
4723 return None;
4724 }
4725 let injected_name = &qualified.names[0];
4726 if !ctx.spec.target.is_class()
4727 || ctx.spec.target.identifier() != injected_name
4728 || physically_visible_type_target(ctx).is_none()
4729 {
4730 return None;
4731 }
4732 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
4733 let owner = ctx.visibility.inherited_injected_class_owner(
4734 &ctx.analyzer,
4735 ctx.file,
4736 &enclosing_owner,
4737 injected_name,
4738 )?;
4739 same_visible_symbol(&owner, &ctx.spec.target)
4740 .then(|| qualified.nodes.first().copied())
4741 .flatten()
4742}
4743
4744fn target_guided_qualifier_type_scopes<'tree>(
4747 node: Node<'tree>,
4748 ctx: &ScanCtx<'_>,
4749) -> Option<Vec<Node<'tree>>> {
4750 if !matches!(
4751 node.kind(),
4752 "qualified_identifier" | "scoped_type_identifier"
4753 ) {
4754 return None;
4755 }
4756 let target = physically_visible_type_target(ctx)?;
4757 let qualified = qualified_owner_components(node, ctx.source)?;
4758 if !qualified.global
4759 && qualified
4760 .names
4761 .first()
4762 .is_some_and(|name| local_receiver_alias_type_node(node, name, ctx).is_some())
4763 {
4764 return static_qualifier_type_scopes_for_components(node, qualified, ctx);
4765 }
4766 let lexical_scope = match enclosing_lexical_scope_components(
4775 node,
4776 &ctx.analyzer,
4777 ctx.visibility,
4778 ctx.file,
4779 ctx.source,
4780 ) {
4781 LexicalScopeResolution::Resolved(scope) => scope,
4782 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4783 enclosing_namespace_components(node, ctx.source)
4784 }
4785 };
4786 let indexed_owner_scope =
4787 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4788 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4789 let mut matches = Vec::new();
4790 for component_count in 1..=qualified.names.len() {
4791 let components = &qualified.names[..component_count];
4792 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4793 .collect::<Vec<_>>();
4794 let name = components.last()?;
4795 let mut candidates = Vec::new();
4796 let mut exact_candidates = Vec::new();
4797 for candidate in ctx
4798 .visibility
4799 .visible_identifier_candidates(ctx.file, name)
4800 .filter(|candidate| candidate.is_class())
4801 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4802 {
4803 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4804 brokk_bifrost_core::analyzer::Language::Cpp,
4805 &cpp_name_for(candidate),
4806 );
4807 if !candidate_components.ends_with(components)
4808 || candidates
4809 .iter()
4810 .any(|existing| same_logical_symbol(existing, candidate))
4811 {
4812 continue;
4813 }
4814 let exact_lexical_scope = lexical_tiers
4815 .iter()
4816 .any(|expected| expected == &candidate_components);
4817 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4818 let structured_owner_match = indexed_owner_scope
4819 .as_ref()
4820 .is_some_and(|owner| owner.starts_with(candidate_owner))
4821 || recovered_owner_scope
4822 .as_ref()
4823 .is_some_and(|owner| owner.starts_with(candidate_owner));
4824 let class_alias_owner_match = ctx
4825 .analyzer
4826 .type_alias_provider()
4827 .is_some_and(|provider| provider.is_type_alias(candidate))
4828 && member_alias_owner_matches_reference_for(candidate, node, ctx);
4829 let macro_namespace_owner_match = is_declaration_name(node)
4830 && macro_namespace_scope_matches(candidate_owner, node, ctx);
4831 if qualified.global && !exact_lexical_scope {
4832 continue;
4833 }
4834 if components.len() == 1
4835 && candidate_components != components
4836 && !exact_lexical_scope
4837 && !structured_owner_match
4838 && !class_alias_owner_match
4839 && !macro_namespace_owner_match
4840 {
4841 continue;
4842 }
4843 candidates.push(candidate.clone());
4844 if exact_lexical_scope {
4845 exact_candidates.push(candidate.clone());
4846 }
4847 }
4848 if !exact_candidates.is_empty() {
4849 candidates = exact_candidates;
4850 }
4851 let canonical_alias_target_matches = matches!(
4856 candidates.as_slice(),
4857 [candidate]
4858 if ctx
4859 .analyzer
4860 .type_alias_provider()
4861 .is_some_and(|provider| provider.is_type_alias(candidate))
4862 && type_candidate_visible_at_reference(candidate, node, ctx)
4863 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4864 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4865 brokk_bifrost_core::analyzer::Language::Cpp,
4866 &cpp_name_for(candidate),
4867 ) == components
4868 || member_alias_owner_matches_reference_for(candidate, node, ctx))
4869 );
4870 let direct_alias_target = ctx
4871 .analyzer
4872 .type_alias_provider()
4873 .is_some_and(|provider| provider.is_type_alias(target))
4874 && candidates
4875 .iter()
4876 .any(|candidate| same_symbol(candidate, target));
4877 let unique_target = matches!(
4878 candidates.as_slice(),
4879 [candidate] if same_visible_symbol(candidate, target)
4880 );
4881 if direct_alias_target || unique_target || canonical_alias_target_matches {
4882 let matched = if ctx
4883 .analyzer
4884 .type_alias_provider()
4885 .is_some_and(|provider| provider.is_type_alias(target))
4886 && ctx
4887 .analyzer
4888 .parent_of(target)
4889 .is_some_and(|owner| owner.is_class())
4890 && ctx
4891 .visibility
4892 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4893 && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4894 {
4895 qualified.nodes[component_count - 1]
4899 } else {
4900 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4901 };
4902 if template_type_component_preserves_target(matched, &candidates, ctx) {
4903 matches.push(matched);
4904 }
4905 }
4906 }
4907 (!matches.is_empty()).then_some(matches)
4908}
4909
4910fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4911 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4912 return false;
4913 };
4914 ctx.visibility
4915 .visible_identifier_candidates(ctx.file, target.identifier())
4916 .any(|candidate| {
4917 alias_provider.is_type_alias(candidate)
4918 && candidate.identifier() == target.identifier()
4919 && !same_visible_symbol(candidate, target)
4920 && ctx
4921 .analyzer
4922 .parent_of(candidate)
4923 .is_some_and(|owner| owner.is_class())
4924 })
4925}
4926
4927fn target_guided_unproven_out_of_line_owner<'tree>(
4930 node: Node<'tree>,
4931 ctx: &ScanCtx<'_>,
4932) -> Option<Node<'tree>> {
4933 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4934 || !is_declaration_name(node)
4935 {
4936 return None;
4937 }
4938 let target = physically_visible_type_target(ctx)?;
4939 if !target.is_class() {
4940 return None;
4941 }
4942 let qualified = qualified_owner_components(node, ctx.source)?;
4943 let target_components = canonical_cpp_scope_components(target);
4944 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4945 let parser_scope = enclosing_namespace_components(node, ctx.source);
4946 let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4947 if !parser_scope.is_empty() || target_namespace.is_empty() {
4948 Some(parser_scope)
4949 } else {
4950 indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4951 }
4952 })?;
4953 if has_malformed_wrapper_function_definition_ancestor(node)
4954 && target_namespace.starts_with(&scope)
4955 && target_namespace.len() > scope.len()
4956 {
4957 scope = target_namespace.to_vec();
4958 }
4959 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4960 .any(|components| components == target_components)
4961 {
4962 return None;
4963 }
4964 let owner_name = qualified.names.last()?;
4965 let candidates = ctx
4966 .visibility
4967 .visible_identifier_candidates(ctx.file, owner_name)
4968 .filter(|candidate| {
4969 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4970 })
4971 .collect::<Vec<_>>();
4972 if candidates.is_empty()
4973 || candidates
4974 .iter()
4975 .any(|candidate| !same_visible_symbol(candidate, target))
4976 {
4977 return None;
4978 }
4979 qualified.nodes.last().copied()
4980}
4981
4982fn macro_namespace_scope_matches(
4983 candidate_owner: &[String],
4984 node: Node<'_>,
4985 ctx: &ScanCtx<'_>,
4986) -> bool {
4987 let namespace = enclosing_namespace_components(node, ctx.source);
4988 if namespace.is_empty() || candidate_owner.is_empty() {
4989 return false;
4990 }
4991 let mut expanded_owner = Vec::new();
4992 for component in candidate_owner {
4993 if let Some(replacement) =
4994 ctx.visibility
4995 .object_macro_replacement_at(ctx.file, component, node.start_byte())
4996 {
4997 let replacement_components =
4998 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4999 brokk_bifrost_core::analyzer::Language::Cpp,
5000 &replacement,
5001 );
5002 if replacement_components.is_empty() {
5003 return false;
5004 }
5005 expanded_owner.extend(replacement_components);
5006 } else {
5007 expanded_owner.push(component.clone());
5008 }
5009 }
5010 expanded_owner == namespace
5011}
5012
5013fn target_guided_missing_type_leaf<'tree>(
5014 node: Node<'tree>,
5015 ctx: &ScanCtx<'_>,
5016) -> Option<Node<'tree>> {
5017 physically_visible_type_target(ctx)?;
5018 target_guided_missing_dependent_nested_type_leaf(node, ctx)
5019 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
5020 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
5021 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
5022 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
5023 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
5024}
5025
5026fn target_guided_missing_class_alias_target_type_leaf<'tree>(
5030 node: Node<'tree>,
5031 ctx: &ScanCtx<'_>,
5032) -> Option<Node<'tree>> {
5033 if node.kind() != "type_identifier"
5034 || is_declaration_name(node)
5035 || local_type_name_shadows(node, ctx)
5036 {
5037 return None;
5038 }
5039 let alias_provider = ctx.analyzer.type_alias_provider()?;
5040 let name = node_text(node, ctx.source);
5041 let aliases = ctx
5042 .visibility
5043 .visible_identifier_candidates(ctx.file, name)
5044 .filter(|candidate| alias_provider.is_type_alias(candidate))
5045 .filter(|candidate| {
5046 ctx.analyzer
5047 .parent_of(candidate)
5048 .is_some_and(|owner| owner.is_class())
5049 })
5050 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
5051 .filter(|candidate| class_alias_guard_compatible(candidate, node, ctx))
5052 .collect::<Vec<_>>();
5053 (!aliases.is_empty()
5054 && aliases.iter().all(|candidate| {
5055 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
5056 }))
5057 .then_some(node)
5058}
5059
5060fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
5064 node: Node<'tree>,
5065 ctx: &ScanCtx<'_>,
5066) -> Option<Node<'tree>> {
5067 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
5068 matches!(
5069 declaration.kind(),
5070 "parameter_declaration" | "optional_parameter_declaration"
5071 )
5072 });
5073 let placement_new_type = ctx.ancestry.parent(node).is_some_and(|parent| {
5074 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
5075 });
5076 if !parameter && !placement_new_type {
5077 return None;
5078 }
5079 if !ctx
5080 .analyzer
5081 .type_alias_provider()
5082 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5083 || !type_alias_owner_matches_structured_reference(node, ctx)
5084 {
5085 return None;
5086 }
5087 target_guided_missing_declaration_type_leaf(node, ctx)
5088}
5089
5090fn target_guided_missing_dependent_nested_type_leaf<'tree>(
5097 node: Node<'tree>,
5098 ctx: &ScanCtx<'_>,
5099) -> Option<Node<'tree>> {
5100 if !matches!(
5101 node.kind(),
5102 "qualified_identifier" | "scoped_type_identifier"
5103 ) || !qualified_type_scope_contains_template(node)
5104 {
5105 return None;
5106 }
5107 let name = node
5108 .child_by_field_name("name")
5109 .filter(|name| name.kind() == "type_identifier")?;
5110 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
5111 return None;
5112 }
5113 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
5114 if !owner_target.is_class() {
5115 return None;
5116 }
5117 let owner = node.child_by_field_name("scope")?;
5118 let owner_resolution = resolve_type_node_lexically_for_target(
5119 owner,
5120 &ctx.analyzer,
5121 ctx.visibility,
5122 &ctx.ordinary_type_imports,
5123 ctx.file,
5124 ctx.source,
5125 &owner_target,
5126 Some(&ctx.lexical_scope_cache),
5127 ctx.recovered_sentinel_scope(owner).as_deref(),
5128 );
5129 if matches!(
5130 owner_resolution,
5131 LexicalTypeResolution::Resolved {
5132 ref unit,
5133 ref candidates,
5134 ..
5135 } if type_resolution_matches_unit_target(
5136 owner,
5137 unit,
5138 candidates,
5139 &owner_target,
5140 ctx,
5141 )
5142 ) {
5143 return Some(name);
5144 }
5145
5146 let qualified = qualified_owner_components(node, ctx.source)?;
5147 let namespace = ctx
5148 .lexical_scope_cache
5149 .orphaned
5150 .enclosing_namespace_components(node, ctx.source);
5151 let indexed_scope = ctx
5152 .recovered_sentinel_scope(node)
5153 .or_else(|| (!namespace.is_empty()).then_some(namespace))
5154 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
5155 let owner_components = canonical_cpp_scope_components(&owner_target);
5156 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
5157 .any(|components| components == owner_components)
5158 {
5159 return None;
5160 }
5161 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
5162 .into_iter()
5163 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
5164 .collect::<Vec<_>>();
5165 (!scoped_candidates.is_empty()
5166 && scoped_candidates
5167 .iter()
5168 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
5169 .then_some(name)
5170}
5171
5172fn target_guided_missing_member_alias_type_leaf<'tree>(
5178 node: Node<'tree>,
5179 ctx: &ScanCtx<'_>,
5180) -> Option<Node<'tree>> {
5181 if !is_cpp_template_argument_type_leaf(node)
5182 || is_declaration_name(node)
5183 || ctx
5184 .target_declaration_ranges
5185 .iter()
5186 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
5187 || node_text(node, ctx.source) != ctx.spec.target.identifier()
5188 || local_type_name_shadows(node, ctx)
5189 || !ctx
5190 .analyzer
5191 .type_alias_provider()
5192 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5193 {
5194 return None;
5195 }
5196 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
5197 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
5198 if !owner_scope_matches
5199 && !indexed_scope.is_some_and(|scope| {
5200 indexed_scope_matches_target_name(
5201 &scope,
5202 &[ctx.spec.target.identifier().to_string()],
5203 false,
5204 &ctx.spec.target,
5205 )
5206 })
5207 {
5208 return None;
5209 }
5210 if !(ctx.visibility.external_type_candidate_visible_in_context(
5217 &ctx.analyzer,
5218 ctx.file,
5219 &ctx.spec.target,
5220 node,
5221 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
5222 {
5223 return None;
5224 }
5225 let target_visible = ctx
5226 .visibility
5227 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5228 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5229 target_visible.then_some(node)
5230}
5231
5232fn target_guided_missing_template_argument_type_leaf<'tree>(
5238 node: Node<'tree>,
5239 ctx: &ScanCtx<'_>,
5240) -> Option<Node<'tree>> {
5241 let target = &ctx.spec.target;
5242 let name = node_text(node, ctx.source);
5243 if !target.is_class()
5244 || !is_cpp_template_argument_type_leaf(node)
5245 || is_declaration_name(node)
5246 || name != target.identifier()
5247 || ctx.local_shadows.is_shadowed(name)
5248 || local_type_name_shadows(node, ctx)
5249 || !ctx.visibility.is_physically_visible(ctx.file, target)
5250 || ctx
5251 .analyzer
5252 .type_alias_provider()
5253 .is_some_and(|provider| provider.is_type_alias(target))
5254 {
5255 return None;
5256 }
5257
5258 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5259 let target_components = canonical_cpp_scope_components(target);
5260 if target_components.last().map(String::as_str) != Some(name)
5261 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5262 .any(|components| components == target_components)
5263 {
5264 return None;
5265 }
5266
5267 let candidates = visible_type_identifier_candidates(ctx, name);
5271 if candidates.is_empty()
5272 || candidates.iter().any(|candidate| {
5273 !candidate.is_class()
5274 || ctx
5275 .analyzer
5276 .type_alias_provider()
5277 .is_some_and(|provider| provider.is_type_alias(candidate))
5278 || (!same_visible_symbol(candidate, target)
5279 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
5280 .any(|components| components == canonical_cpp_scope_components(candidate)))
5281 })
5282 || !candidates
5283 .iter()
5284 .any(|candidate| same_visible_symbol(candidate, target))
5285 {
5286 return None;
5287 }
5288
5289 ctx.visibility
5292 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
5293 .then_some(node)
5294}
5295
5296fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
5299 let mut function = node;
5300 while function.kind() != "function_definition" {
5301 function = ctx.ancestry.parent(function)?;
5302 }
5303 let macro_name = function_definition_name_node(function)?;
5304 if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
5305 return None;
5306 }
5307
5308 let declaration = function.prev_named_sibling()?;
5314 if declaration.kind() != "declaration"
5315 || !declaration.has_error()
5316 || function.start_position().row > declaration.end_position().row + 1
5317 {
5318 return None;
5319 }
5320 let mut missing_semicolon = false;
5321 let mut real_semicolon = false;
5322 for child in children_iter(declaration) {
5323 if child.kind() == ";" {
5324 missing_semicolon |= child.is_missing();
5325 real_semicolon |= !child.is_missing();
5326 }
5327 }
5328 if !missing_semicolon || real_semicolon {
5329 return None;
5330 }
5331
5332 let initializer = declaration.child_by_field_name("declarator")?;
5333 if initializer.kind() != "init_declarator"
5334 || initializer
5335 .child_by_field_name("value")
5336 .is_none_or(|value| value.kind() != "argument_list")
5337 {
5338 return None;
5339 }
5340 let qualified_name = initializer
5341 .child_by_field_name("declarator")
5342 .and_then(declarator_name_node)?;
5343 let qualified = qualified_owner_components(qualified_name, ctx.source)?;
5344 let lexical_scope = enclosing_namespace_components(function, ctx.source);
5345 match ctx.visibility.resolve_type_components_lexically(
5346 &ctx.analyzer,
5347 ctx.file,
5348 &qualified.names,
5349 qualified.global,
5350 &lexical_scope,
5351 ) {
5352 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
5353 LexicalTypeResolution::Resolved { .. }
5354 | LexicalTypeResolution::Ambiguous
5355 | LexicalTypeResolution::Missing => None,
5356 }
5357}
5358
5359fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5364 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
5365}
5366
5367fn member_alias_owner_matches_reference_for(
5368 target: &CodeUnit,
5369 node: Node<'_>,
5370 ctx: &ScanCtx<'_>,
5371) -> bool {
5372 let Some(owner) = ctx.analyzer.parent_of(target) else {
5373 return false;
5374 };
5375 if !owner.is_class() {
5376 return false;
5377 }
5378 let reference_owner = ctx
5379 .class_ranges
5380 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
5381 .or_else(|| structured_enclosing_owner(node, ctx));
5382 if reference_owner.as_ref().is_some_and(|reference_owner| {
5383 ctx.visibility
5384 .same_template_owner_identity(&owner, reference_owner)
5385 }) {
5386 return true;
5387 }
5388 if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
5389 ctx.visibility
5390 .same_template_owner_identity(&owner, &reference_owner)
5391 }) {
5392 return true;
5393 }
5394 if reference_owner.is_some_and(|reference_owner| {
5395 matches!(
5396 resolve_declaring_member_owner(
5397 &ctx.analyzer,
5398 ctx.visibility,
5399 ctx.file,
5400 &reference_owner,
5401 target.identifier(),
5402 ),
5403 EnclosingMemberOwnerResolution::Owner(declaring_owner)
5404 if ctx
5405 .visibility
5406 .same_template_owner_identity(&owner, &declaring_owner)
5407 )
5408 }) {
5409 return true;
5410 }
5411 let range = Range {
5412 start_byte: node.start_byte(),
5413 end_byte: node.end_byte(),
5414 start_line: node.start_position().row + 1,
5415 end_line: node.end_position().row + 1,
5416 };
5417 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
5418 while let Some(candidate) = indexed_enclosing {
5419 if candidate.is_class()
5420 && ctx
5421 .visibility
5422 .same_template_owner_identity(&owner, &candidate)
5423 {
5424 return true;
5425 }
5426 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
5427 }
5428 if let Some(reference_body) = malformed_recovered_class_body(node) {
5429 let mut root = node;
5430 while let Some(parent) = ctx.ancestry.parent(root) {
5431 root = parent;
5432 }
5433 if ctx.analyzer.ranges(target).iter().any(|range| {
5434 root.descendant_for_byte_range(range.start_byte, range.end_byte)
5435 .and_then(malformed_recovered_class_body)
5436 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
5437 }) {
5438 return true;
5439 }
5440 }
5441 if structured_enclosing_owner(node, ctx)
5442 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
5443 {
5444 return true;
5445 }
5446 let owner_components = canonical_cpp_scope_components(&owner);
5447 if ctx
5448 .recovered_sentinel_scope(node)
5449 .is_some_and(|scope| scope == owner_components)
5450 {
5451 return true;
5452 }
5453 if matches!(
5454 cached_enclosing_lexical_scope_components_with_unresolved_owner(
5455 node,
5456 &ctx.analyzer,
5457 ctx.visibility,
5458 ctx.file,
5459 ctx.source,
5460 false,
5461 false,
5462 Some(&ctx.lexical_scope_cache),
5463 ),
5464 LexicalScopeResolution::Resolved(reference_scope)
5465 if reference_scope == owner_components
5466 ) {
5467 return true;
5468 }
5469 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5470 else {
5471 return false;
5472 };
5473 !owner_components.is_empty() && reference_scope == owner_components
5474}
5475
5476fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
5477 loop {
5478 if node.kind() == "compound_statement"
5479 && node
5480 .parent()
5481 .is_some_and(|parent| parent.kind() == "declaration_list")
5482 && node.prev_named_sibling().is_some_and(|header| {
5483 header.kind() == "ERROR"
5484 && header.end_byte() <= node.start_byte()
5485 && error_contains_class_header(header)
5486 })
5487 {
5488 return Some(node);
5489 }
5490 node = node.parent()?;
5491 }
5492}
5493
5494pub fn error_contains_class_header(node: Node<'_>) -> bool {
5496 let mut pending = vec![(node, 0usize)];
5497 while let Some((current, depth)) = pending.pop() {
5498 if matches!(current.kind(), "class" | "struct" | "union") {
5499 let mut sibling = current.next_sibling();
5500 let mut saw_name = false;
5501 while let Some(candidate) = sibling {
5502 match candidate.kind() {
5503 "comment" => {}
5504 "{" | "base_class_clause" | ":" => return saw_name,
5505 "identifier" | "type_identifier" if !saw_name => saw_name = true,
5506 _ if !candidate.is_named() => {}
5507 _ => break,
5508 }
5509 sibling = candidate.next_sibling();
5510 }
5511 }
5512 if depth >= 1 {
5513 continue;
5514 }
5515 let mut cursor = current.walk();
5516 pending.extend(
5517 current
5518 .children(&mut cursor)
5519 .filter(|child| child.kind() != "compound_statement")
5520 .map(|child| (child, depth + 1)),
5521 );
5522 }
5523 false
5524}
5525
5526fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5527 has_ancestor_kind(node, "compound_statement")
5528 && ctx
5529 .visibility
5530 .external_type_candidate_guard_compatible_in_context(
5531 &ctx.analyzer,
5532 ctx.file,
5533 &ctx.spec.target,
5534 node,
5535 )
5536}
5537
5538fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5539 ctx.analyzer
5540 .type_alias_provider()
5541 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5542 && member_alias_owner_matches_reference(node, ctx)
5543}
5544
5545fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5549 if !ctx
5550 .analyzer
5551 .type_alias_provider()
5552 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5553 {
5554 return false;
5555 }
5556 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5557 return false;
5558 };
5559 let mut reference_owner = structured_enclosing_owner(node, ctx);
5560 while let Some(owner) = reference_owner {
5561 if same_logical_symbol(&target_owner, &owner) {
5562 return true;
5563 }
5564 reference_owner = ctx.analyzer.parent_of(&owner);
5565 }
5566 false
5567}
5568
5569fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5574 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
5575 return false;
5576 };
5577 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5578 return false;
5579 };
5580 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5581 return false;
5582 };
5583 let candidates = ctx
5584 .visibility
5585 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5586 .filter(|candidate| {
5587 candidate.is_class()
5588 && alias_provider.is_type_alias(candidate)
5589 && !same_visible_symbol(candidate, &ctx.spec.target)
5590 })
5591 .cloned()
5592 .collect::<Vec<_>>();
5593
5594 let mut owner = Some(reference_owner);
5595 while let Some(owner_unit) = owner {
5596 if same_logical_symbol(&target_owner, &owner_unit) {
5597 return false;
5598 }
5599 if candidates.iter().any(|candidate| {
5600 ctx.analyzer
5601 .parent_of(candidate)
5602 .is_some_and(|candidate_owner| {
5603 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5604 })
5605 && ctx
5606 .visibility
5607 .external_type_candidate_guard_compatible_in_context(
5608 &ctx.analyzer,
5609 ctx.file,
5610 candidate,
5611 node,
5612 )
5613 }) {
5614 return true;
5615 }
5616 owner = ctx.analyzer.parent_of(&owner_unit);
5617 }
5618 false
5619}
5620
5621fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5622 if cpp_active_template_type_parameter(
5623 node,
5624 ctx.spec.target.identifier(),
5625 ctx.source,
5626 &ctx.ancestry,
5627 ) {
5628 return true;
5629 }
5630 let Some(callable) = nearest_callable_scope(node) else {
5631 return false;
5632 };
5633 let mut root_callable = callable;
5634 let mut ancestor = ctx.ancestry.parent(callable);
5635 while let Some(current) = ancestor {
5636 if matches!(current.kind(), "function_definition" | "lambda_expression") {
5637 root_callable = current;
5638 }
5639 ancestor = ctx.ancestry.parent(current);
5640 }
5641
5642 let mut stack = vec![root_callable];
5643 while let Some(current) = stack.pop() {
5644 if current.start_byte() >= node.start_byte() {
5645 continue;
5646 }
5647 if let Some(name) = local_type_name_declaration_node(current)
5648 && node_text(name, ctx.source) == ctx.spec.target.identifier()
5649 && nearest_callable_scope(current).is_some_and(|owner| {
5650 !is_malformed_wrapper_function_definition(owner)
5651 && owner.start_byte() <= callable.start_byte()
5652 && callable.end_byte() <= owner.end_byte()
5653 })
5654 && local_alias_scope_contains_node(current, node)
5655 {
5656 return true;
5657 }
5658 let mut cursor = current.walk();
5659 stack.extend(current.named_children(&mut cursor));
5660 }
5661 false
5662}
5663
5664fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5665 local_type_alias_name_node(node).or_else(|| match node.kind() {
5666 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5667 .child_by_field_name("name")
5668 .filter(|name| is_declaration_name(*name)),
5669 _ => None,
5670 })
5671}
5672
5673fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5674 loop {
5675 if matches!(node.kind(), "function_definition" | "lambda_expression") {
5676 return Some(node);
5677 }
5678 node = node.parent()?;
5679 }
5680}
5681
5682fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5683 match node.kind() {
5684 "alias_declaration" => node.child_by_field_name("name"),
5685 "type_definition" => node
5686 .child_by_field_name("declarator")
5687 .and_then(declarator_name_node),
5688 _ => None,
5689 }
5690}
5691
5692fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5693 let mut current = alias.parent();
5694 while let Some(parent) = current {
5695 if matches!(
5696 parent.kind(),
5697 "class_specifier" | "struct_specifier" | "union_specifier"
5698 ) {
5699 return false;
5700 }
5701 if parent.kind() == "compound_statement" {
5702 return parent.start_byte() <= node.start_byte()
5703 && node.end_byte() <= parent.end_byte();
5704 }
5705 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5706 let Some(body) = parent.child_by_field_name("body") else {
5707 return false;
5708 };
5709 return node_is_within(body, alias) && node_is_within(body, node);
5710 }
5711 current = parent.parent();
5712 }
5713 false
5714}
5715
5716fn target_guided_missing_declaration_type_leaf<'tree>(
5717 node: Node<'tree>,
5718 ctx: &ScanCtx<'_>,
5719) -> Option<Node<'tree>> {
5720 if is_declaration_name(node) {
5721 return None;
5722 }
5723 let component_nodes = cpp_name_component_nodes(node)?;
5724 let name_node = component_nodes.last().copied()?;
5725 let name = node_text(name_node, ctx.source);
5726 if name != ctx.spec.target.identifier() {
5727 return None;
5728 }
5729 let inside_target_declaration = ctx
5730 .target_declaration_ranges
5731 .iter()
5732 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5733 if !inside_target_declaration
5734 && !ctx.visibility.external_type_candidate_visible_in_context(
5735 &ctx.analyzer,
5736 ctx.file,
5737 &ctx.spec.target,
5738 node,
5739 )
5740 {
5741 return None;
5742 }
5743 let components = component_nodes
5744 .iter()
5745 .map(|component| node_text(*component, ctx.source).to_string())
5746 .collect::<Vec<_>>();
5747 let local_alias_shadow = local_type_name_shadows(node, ctx);
5748 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5749 let indexed_alias_owner = ctx
5750 .analyzer
5751 .type_alias_provider()
5752 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5753 && member_alias_owner_matches_reference(node, ctx);
5754 let target_alias_self_reference = inside_target_declaration
5755 && ctx
5756 .analyzer
5757 .type_alias_provider()
5758 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5759 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5760 &ctx.analyzer,
5761 ctx.file,
5762 &ctx.spec.target,
5763 node,
5764 ) || member_alias_complete_class_context(node, ctx);
5765 if !target_alias_self_reference
5766 && !local_alias_shadow
5767 && member_alias_visible
5768 && (structured_alias_owner || indexed_alias_owner)
5769 {
5770 return Some(node);
5771 }
5772 let declaration = nearest_declaration_type_context(node)?;
5773 let candidates = visible_type_identifier_candidates(ctx, name);
5774 let unique_visible_target = !candidates.is_empty()
5775 && candidates
5776 .iter()
5777 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5778 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5779 let exact_scope_match = indexed_scope_matches_target_name(
5780 &indexed_scope,
5781 &components,
5782 is_globally_qualified_cpp_name(node),
5783 &ctx.spec.target,
5784 );
5785 if matches!(declaration.kind(), "field_declaration" | "declaration") {
5786 let parser_lost_declaration_scope =
5787 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5788 && unique_visible_target;
5789 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5790 }
5791 let lost_namespace_parameter_context =
5792 matches!(
5793 declaration.kind(),
5794 "parameter_declaration" | "optional_parameter_declaration"
5795 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5796 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5797 return Some(node);
5798 }
5799 None
5800}
5801
5802fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5803 node: Node<'tree>,
5804 ctx: &ScanCtx<'_>,
5805) -> Option<Node<'tree>> {
5806 let mut stack = vec![node];
5807 while let Some(candidate) = stack.pop() {
5808 if candidate.kind() == "type_identifier"
5809 && !is_declaration_name(candidate)
5810 && matches!(
5811 ctx.ancestry.parent(candidate).map(|parent| parent.kind()),
5812 Some("template_type")
5813 )
5814 {
5815 let mut current = ctx.ancestry.parent(candidate);
5816 let mut saw_qualified = false;
5817 let mut saw_dependent = false;
5818 let mut saw_type_descriptor = false;
5819 let mut saw_alias_declaration = false;
5820 while let Some(ancestor) = current {
5821 match ancestor.kind() {
5822 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5823 "dependent_type" => saw_dependent = true,
5824 "type_descriptor" => saw_type_descriptor = true,
5825 "alias_declaration" => {
5826 saw_alias_declaration = true;
5827 break;
5828 }
5829 "template_type"
5830 | "template_argument_list"
5831 | "typename"
5832 | "template_declaration" => {}
5833 _ => {}
5834 }
5835 current = ctx.ancestry.parent(ancestor);
5836 }
5837 let name = node_text(candidate, ctx.source);
5838 let visible_candidates = visible_type_identifier_candidates(ctx, name);
5839 let canonical_alias_target = visible_candidates
5840 .iter()
5841 .filter_map(|alias| ctx.visibility.alias_target(alias))
5842 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5843 let alias_resolves =
5844 ctx.visibility
5845 .parser_alias_resolves_to_type(ctx.file, name, &ctx.spec.target)
5846 || canonical_alias_target;
5847 if saw_qualified
5848 && saw_dependent
5849 && saw_type_descriptor
5850 && saw_alias_declaration
5851 && alias_resolves
5852 && ctx.visibility.external_type_candidate_visible_in_context(
5853 &ctx.analyzer,
5854 ctx.file,
5855 &ctx.spec.target,
5856 candidate,
5857 )
5858 {
5859 return Some(candidate);
5860 }
5861 }
5862 push_named_children_reversed(candidate, &mut stack);
5863 }
5864 None
5865}
5866
5867fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5868 let mut current = Some(node);
5869 while let Some(ancestor) = current {
5870 if matches!(
5871 ancestor.kind(),
5872 "field_declaration"
5873 | "parameter_declaration"
5874 | "optional_parameter_declaration"
5875 | "declaration"
5876 | "type_descriptor"
5877 ) {
5878 let contains_type = crate::declarations::cpp_type_field_contains_node(ancestor, node);
5879 if contains_type
5880 && !(ancestor.kind() == "type_descriptor"
5881 && is_cpp_template_argument_type_leaf(node))
5882 {
5883 return Some(ancestor);
5884 }
5885 if ancestor.kind() == "type_descriptor"
5886 && ancestor.parent().is_some_and(|parent| {
5887 matches!(
5888 parent.kind(),
5889 "cast_expression"
5890 | "new_expression"
5891 | "sizeof_expression"
5892 | "alignof_expression"
5893 | "typeid_expression"
5894 )
5895 })
5896 {
5897 return Some(ancestor);
5898 }
5899 }
5900 if matches!(
5901 ancestor.kind(),
5902 "compound_statement"
5903 | "translation_unit"
5904 | "namespace_definition"
5905 | "alias_declaration"
5906 | "type_definition"
5907 | "base_class_clause"
5908 ) {
5909 return None;
5910 }
5911 current = ancestor.parent();
5912 }
5913 None
5914}
5915
5916fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5917 let mut candidates = Vec::new();
5918 for candidate in ctx
5919 .visibility
5920 .visible_identifier_candidates(ctx.file, name)
5921 .filter(|candidate| {
5922 candidate.is_class()
5923 || ctx
5924 .analyzer
5925 .type_alias_provider()
5926 .is_some_and(|provider| provider.is_type_alias(candidate))
5927 })
5928 {
5929 if !candidates
5930 .iter()
5931 .any(|existing| same_logical_symbol(existing, candidate))
5932 {
5933 candidates.push(candidate.clone());
5934 }
5935 }
5936 candidates
5937}
5938
5939fn target_guided_static_cast_alias_type_descriptor<'tree>(
5944 node: Node<'tree>,
5945 ctx: &ScanCtx<'_>,
5946) -> Option<Node<'tree>> {
5947 if node.kind() != "type_descriptor" {
5948 return None;
5949 }
5950 let argument_list = ctx.ancestry.parent(node).filter(|parent| {
5951 parent.kind() == "template_argument_list"
5952 && parent.named_child_count() == 1
5953 && parent.named_child(0) == Some(node)
5954 })?;
5955 let template = ctx.ancestry.parent(argument_list).filter(|parent| {
5956 parent.kind() == "template_function"
5957 && parent.child_by_field_name("arguments") == Some(argument_list)
5958 })?;
5959 let name = template.child_by_field_name("name")?;
5960 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5961 return None;
5962 }
5963 let target = &ctx.spec.target;
5964 if node_text(node, ctx.source) != target.identifier()
5965 || !ctx
5966 .analyzer
5967 .type_alias_provider()
5968 .is_some_and(|provider| provider.is_type_alias(target))
5969 || !ctx.visibility.is_physically_visible(ctx.file, target)
5970 || !ctx.visibility.external_type_candidate_visible_in_context(
5971 &ctx.analyzer,
5972 ctx.file,
5973 target,
5974 node,
5975 )
5976 {
5977 return None;
5978 }
5979
5980 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5981 let target_scope = canonical_cpp_scope_components(target);
5982 let name_components = [target.identifier().to_string()];
5983 if !lexical_component_tiers(&name_components, false, &indexed_scope)
5984 .any(|components| components == target_scope)
5985 {
5986 return None;
5987 }
5988
5989 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5990 if !candidates
5991 .iter()
5992 .any(|candidate| same_visible_symbol(candidate, target))
5993 {
5994 return None;
5995 }
5996 if candidates.iter().any(|candidate| {
5997 !same_visible_symbol(candidate, target)
5998 && lexical_component_tiers(&name_components, false, &indexed_scope)
5999 .any(|components| components == canonical_cpp_scope_components(candidate))
6000 }) {
6001 return None;
6002 }
6003 Some(node)
6004}
6005
6006fn indexed_scope_matches_target_name(
6007 indexed_scope: &[String],
6008 components: &[String],
6009 global: bool,
6010 target: &CodeUnit,
6011) -> bool {
6012 let target_name = cpp_name_for(target);
6013 lexical_component_tiers(components, global, indexed_scope)
6014 .any(|qualified| qualified.join("::") == target_name)
6015}
6016
6017fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
6018 if target.package_name().is_empty() || indexed_scope.len() <= 1 {
6023 return false;
6024 }
6025 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6026 brokk_bifrost_core::analyzer::Language::Cpp,
6027 &cpp_name_for(target),
6028 );
6029 target_scope.pop();
6030 (1..indexed_scope.len())
6031 .rev()
6032 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
6033}
6034
6035fn indexed_enclosing_lexical_scope(
6036 analyzer: &CppGraphSource<'_>,
6037 file: &ProjectFile,
6038 node: Node<'_>,
6039) -> Option<Vec<String>> {
6040 let range = Range {
6041 start_byte: node.start_byte(),
6042 end_byte: node.end_byte(),
6043 start_line: node.start_position().row,
6044 end_line: node.end_position().row,
6045 };
6046 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
6047 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6048 brokk_bifrost_core::analyzer::Language::Cpp,
6049 &cpp_name_for(&enclosing),
6050 );
6051 if !enclosing.is_class() && !enclosing.is_module() {
6052 components.pop();
6053 }
6054 Some(components)
6055}
6056
6057fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
6058 if node.kind() != "qualified_identifier" {
6059 return None;
6060 }
6061 let mut stack = vec![node];
6062 while let Some(current) = stack.pop() {
6063 if current.kind() != "qualified_identifier" {
6064 continue;
6065 }
6066 if let Some(scope) = current.child_by_field_name("scope") {
6067 let text = qualified_scope_text(scope, ctx.source);
6068 if name_mentions(&text, &ctx.spec.member_name) {
6069 return Some(scope);
6070 }
6071 }
6072 let mut cursor = current.walk();
6073 for child in current.named_children(&mut cursor) {
6074 if child.kind() == "qualified_identifier" {
6075 stack.push(child);
6076 }
6077 }
6078 }
6079 None
6080}
6081
6082fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
6083 let mut parts = vec![node_text(scope, source).to_string()];
6084 let mut current = scope.parent();
6085 while let Some(qualified) = current {
6086 let Some(parent) = qualified.parent() else {
6087 break;
6088 };
6089 if parent.kind() != "qualified_identifier"
6090 || parent.child_by_field_name("name") != Some(qualified)
6091 {
6092 break;
6093 }
6094 if let Some(outer_scope) = parent.child_by_field_name("scope") {
6095 parts.push(node_text(outer_scope, source).to_string());
6096 }
6097 current = Some(parent);
6098 }
6099 parts.reverse();
6100 parts.join("::")
6101}
6102
6103fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6104 if node.kind() == "using_declaration" {
6105 maybe_record_using_callable_hit(node, ctx);
6106 return;
6107 }
6108 if has_ancestor_kind(node, "using_declaration") {
6109 return;
6110 }
6111 if node.kind() == "function_definition" {
6112 return;
6113 }
6114 if !matches!(
6115 node.kind(),
6116 "call_expression"
6117 | "new_expression"
6118 | "compound_literal_expression"
6119 | "declaration"
6120 | "field_initializer"
6121 ) {
6122 return;
6123 }
6124 let Some(owner) = ctx.spec.owner.as_ref() else {
6125 return;
6126 };
6127 if node.kind() == "field_initializer" {
6128 if !field_initializer_constructs_target(node, ctx, owner)
6129 && !unqualified_base_initializer_constructs_target(node, ctx, owner)
6130 {
6131 return;
6132 }
6133 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6134 match ctx
6135 .visibility
6136 .call_arity_evidence(ctx.file, node, ctx.source)
6137 .accepts(expected)
6138 {
6139 Some(true) => {}
6140 Some(false) => return,
6141 None => {
6142 push_unproven_hit(node, ctx);
6143 return;
6144 }
6145 }
6146 }
6147 match constructor_overload_selection(node, ctx) {
6148 ConstructorOverloadSelection::Target => push_hit(node, ctx),
6149 ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6150 ConstructorOverloadSelection::OtherOverload => {}
6151 }
6152 return;
6153 }
6154 if node.kind() == "declaration" {
6155 if declaration_is_object_construction_candidate(node, ctx)
6156 && declaration_mentions_type(node, ctx, owner)
6157 && ctx
6158 .spec
6159 .callable_arity_at(node.start_byte())
6160 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
6161 {
6162 match constructor_overload_selection(node, ctx) {
6163 ConstructorOverloadSelection::Target => push_hit(node, ctx),
6164 ConstructorOverloadSelection::Ambiguous => push_unproven_hit(node, ctx),
6165 ConstructorOverloadSelection::OtherOverload => {}
6166 }
6167 }
6168 return;
6169 }
6170 let Some(type_node) = constructor_type_node(node) else {
6171 return;
6172 };
6173 let hit_node = function_terminal_node(type_node);
6174 let text = node_text(type_node, ctx.source);
6175 if !name_mentions(text, &ctx.spec.member_name) {
6176 return;
6177 }
6178 *ctx.raw_match_count += 1;
6179 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6180 match ctx
6181 .visibility
6182 .call_arity_evidence(ctx.file, node, ctx.source)
6183 .accepts(expected)
6184 {
6185 Some(true) => {}
6186 Some(false) => return,
6187 None => {
6188 push_unproven_hit(hit_node, ctx);
6189 return;
6190 }
6191 }
6192 }
6193 match constructor_overload_selection(node, ctx) {
6194 ConstructorOverloadSelection::Target => {}
6195 ConstructorOverloadSelection::Ambiguous => {
6196 push_unproven_hit(hit_node, ctx);
6197 return;
6198 }
6199 ConstructorOverloadSelection::OtherOverload => return,
6200 }
6201 let structured_resolution = resolve_type_node_lexically_for_target(
6202 type_node,
6203 &ctx.analyzer,
6204 ctx.visibility,
6205 &ctx.ordinary_type_imports,
6206 ctx.file,
6207 ctx.source,
6208 owner,
6209 Some(&ctx.lexical_scope_cache),
6210 ctx.recovered_sentinel_scope(type_node).as_deref(),
6211 );
6212 let structurally_resolves = matches!(
6213 &structured_resolution,
6214 LexicalTypeResolution::Resolved {
6215 unit, candidates, ..
6216 } if same_visible_symbol(unit, owner)
6217 || candidates
6218 .iter()
6219 .any(|candidate| same_visible_symbol(candidate, owner))
6220 );
6221 if structurally_resolves
6222 || matches!(structured_resolution, LexicalTypeResolution::Missing)
6223 && ctx
6224 .visibility
6225 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
6226 {
6227 push_hit(hit_node, ctx);
6228 } else {
6229 push_unproven_hit(hit_node, ctx);
6230 }
6231}
6232
6233enum ConstructorOverloadSelection {
6236 Target,
6240 Ambiguous,
6243 OtherOverload,
6245}
6246
6247fn constructor_overload_selection(
6260 node: Node<'_>,
6261 ctx: &ScanCtx<'_>,
6262) -> ConstructorOverloadSelection {
6263 let Some(owner) = ctx.spec.owner.as_ref() else {
6264 return ConstructorOverloadSelection::Target;
6265 };
6266 if ctx.spec.param_types.is_none() {
6267 return ConstructorOverloadSelection::Target;
6268 }
6269 let (arity, arg_types) = if node.kind() == "declaration" {
6274 match declaration_constructor_initializer(node) {
6275 DeclarationConstructorInitializer::Arguments(arguments) => (
6276 argument_children(arguments).count(),
6277 argument_list_types(arguments, ctx),
6278 ),
6279 DeclarationConstructorInitializer::Expression(value) => {
6280 (1, vec![expression_arg_type(value, ctx)])
6281 }
6282 DeclarationConstructorInitializer::Empty => (0, Vec::new()),
6283 }
6284 } else {
6285 let Some(arity) = ctx
6286 .visibility
6287 .call_arity_evidence(ctx.file, node, ctx.source)
6288 .exact()
6289 else {
6290 return ConstructorOverloadSelection::Target;
6291 };
6292 (arity, call_argument_types(node, ctx))
6293 };
6294 let mut candidates = ctx
6295 .visibility
6296 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6297 .into_iter()
6298 .filter(|unit| unit.is_function())
6299 .cloned()
6300 .collect::<Vec<_>>();
6301 candidates.retain(|unit| {
6302 cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6303 });
6304 if !candidates
6305 .iter()
6306 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6307 {
6308 return ConstructorOverloadSelection::Target;
6309 }
6310 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6311 candidates,
6312 &arg_types,
6313 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6314 &|candidate| {
6315 ctx.visibility
6316 .callable_is_template_declaration(&ctx.analyzer, candidate)
6317 },
6318 &|name| ctx.visibility.resolve_type(ctx.file, name),
6319 &|left, right| same_visible_symbol(left, right),
6320 );
6321 if !filtered
6322 .iter()
6323 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6324 {
6325 return ConstructorOverloadSelection::OtherOverload;
6326 }
6327 if filtered.iter().all(|candidate| {
6331 ctx.visibility
6332 .same_logical_callable(&ctx.analyzer, candidate, &ctx.spec.target)
6333 }) {
6334 ConstructorOverloadSelection::Target
6335 } else {
6336 ConstructorOverloadSelection::Ambiguous
6337 }
6338}
6339
6340fn unqualified_base_initializer_constructs_target(
6341 node: Node<'_>,
6342 ctx: &ScanCtx<'_>,
6343 target_owner: &CodeUnit,
6344) -> bool {
6345 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6346 return false;
6347 }
6348 let Some(name) = node
6349 .child_by_field_name("name")
6350 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6351 else {
6352 return false;
6353 };
6354 if node_text(name, ctx.source) != ctx.spec.member_name {
6355 return false;
6356 }
6357 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
6358 return false;
6359 };
6360 let inherited = ctx.visibility.inherited_injected_class_owner(
6361 &ctx.analyzer,
6362 ctx.file,
6363 &enclosing_owner,
6364 &ctx.spec.member_name,
6365 );
6366 inherited.is_some_and(|owner| {
6367 receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
6368 })
6369}
6370
6371fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6372 if node.kind() == "function_definition" {
6373 maybe_record_free_function_definition_hit(node, ctx);
6374 return;
6375 }
6376 if node.kind() == "function_declarator" {
6377 maybe_record_recovered_error_free_function_call(node, ctx);
6378 return;
6379 }
6380 if node.kind() == "identifier" {
6381 maybe_record_free_function_value_reference(node, ctx);
6382 return;
6383 }
6384 if node.kind() != "call_expression" {
6385 return;
6386 }
6387 let Some(function) = node
6388 .child_by_field_name("function")
6389 .or_else(|| node.named_child(0))
6390 else {
6391 return;
6392 };
6393 let text = node_text(function, ctx.source);
6394 if !name_matches_callable(text, &ctx.spec.member_name) {
6395 return;
6396 }
6397 *ctx.raw_match_count += 1;
6398 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6399 match ctx
6400 .visibility
6401 .call_arity_evidence(ctx.file, node, ctx.source)
6402 .accepts(expected)
6403 {
6404 Some(true) => {}
6405 Some(false) => return,
6406 None => {
6407 if !bare_name_binds_only_target(node, function, text, ctx) {
6414 push_unproven_hit(function_terminal_node(function), ctx);
6415 return;
6416 }
6417 }
6418 }
6419 }
6420 if matches!(function.kind(), "identifier" | "template_function") {
6421 let terminal = function_terminal_node(function);
6422 let name = node_text(terminal, ctx.source);
6423 if ctx.local_shadows.is_shadowed(name) {
6424 return;
6425 }
6426 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
6427 && !matches!(
6428 resolve_declaring_member_owner(
6429 &ctx.analyzer,
6430 ctx.visibility,
6431 ctx.file,
6432 &enclosing_owner,
6433 name,
6434 ),
6435 EnclosingMemberOwnerResolution::Missing
6436 )
6437 {
6438 return;
6439 }
6440 match resolve_bare_call_target(
6441 node,
6442 function,
6443 &ctx.analyzer,
6444 ctx.visibility,
6445 &ctx.ordinary_type_imports,
6446 ctx.file,
6447 ctx.source,
6448 ) {
6449 BareCallTargetResolution::FreeFunctions(units)
6450 if units
6451 .iter()
6452 .any(|unit| free_function_target_matches(unit, ctx)) =>
6453 {
6454 if free_function_call_may_target(node, text, ctx) {
6455 let recursive = enclosing_context(terminal, ctx)
6456 .enclosing
6457 .as_ref()
6458 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
6459 if recursive {
6460 push_recursive_reference_hit(terminal, ctx);
6461 } else {
6462 push_hit(terminal, ctx);
6463 }
6464 }
6465 }
6466 BareCallTargetResolution::UnprovenFreeFunctions(units)
6467 if units
6468 .iter()
6469 .any(|unit| free_function_target_matches(unit, ctx)) =>
6470 {
6471 push_unproven_hit(terminal, ctx);
6472 }
6473 BareCallTargetResolution::FreeFunctions(_)
6474 | BareCallTargetResolution::UnprovenFreeFunctions(_)
6475 | BareCallTargetResolution::Type(_)
6476 | BareCallTargetResolution::CallableShadow => {}
6477 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
6478 push_unproven_hit(terminal, ctx);
6479 }
6480 }
6481 return;
6482 }
6483 if !free_function_call_may_target(node, text, ctx) {
6484 return;
6485 }
6486 if ctx.visibility.contains_named_symbol(
6487 ctx.file,
6488 text,
6489 TargetKind::FreeFunction,
6490 &ctx.spec.target,
6491 ) {
6492 push_hit(function_terminal_node(function), ctx);
6493 } else if ctx.visibility.resolve_known_non_target(
6494 ctx.file,
6495 text,
6496 TargetKind::FreeFunction,
6497 &ctx.spec.target,
6498 ) {
6499 } else {
6502 push_unproven_hit(function_terminal_node(function), ctx);
6503 }
6504}
6505
6506fn free_function_target_matches(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6507 same_visible_symbol(unit, &ctx.spec.target)
6508 || ctx
6509 .visibility
6510 .same_logical_callable(&ctx.analyzer, unit, &ctx.spec.target)
6511 && cpp_callable_definitions_share_identity_evidence_with_visibility(
6512 &ctx.analyzer,
6513 ctx.visibility,
6514 unit,
6515 &ctx.spec.target,
6516 )
6517}
6518
6519fn maybe_record_recovered_error_free_function_call(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6530 let block_call_envelope = recovered_c_block_call_envelope(node).is_some();
6535 if !block_call_envelope
6536 && (ctx
6537 .ancestry
6538 .parent(node)
6539 .is_none_or(|parent| parent.kind() != "ERROR")
6540 || !has_ancestor_kind(node, "compound_statement"))
6541 {
6542 return;
6543 }
6544 let mut recovered_functions = Vec::new();
6545 if let Some(function) = node
6546 .child_by_field_name("declarator")
6547 .filter(|function| function.kind() == "identifier")
6548 {
6549 recovered_functions.push(function);
6550 }
6551 if let Some(parameters) = node.child_by_field_name("parameters") {
6552 let mut cursor = parameters.walk();
6553 for parameter in parameters
6554 .named_children(&mut cursor)
6555 .filter(|child| child.kind() == "parameter_declaration")
6556 {
6557 if parameter
6558 .child_by_field_name("declarator")
6559 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
6560 && let Some(function) = parameter
6561 .child_by_field_name("type")
6562 .filter(|function| function.kind() == "type_identifier")
6563 {
6564 recovered_functions.push(function);
6565 }
6566 }
6567 }
6568
6569 for function in recovered_functions {
6570 let name = node_text(function, ctx.source);
6571 if !name_matches_callable(name, &ctx.spec.member_name)
6572 || ctx.local_shadows.is_shadowed(name)
6573 {
6574 continue;
6575 }
6576 *ctx.raw_match_count += 1;
6577 if block_call_envelope
6578 && let Some(expected) = ctx.spec.callable_arity_at(node.start_byte())
6579 && ctx
6580 .visibility
6581 .call_arity_evidence(ctx.file, node, ctx.source)
6582 .accepts(expected)
6583 == Some(false)
6584 {
6585 continue;
6586 }
6587 if bare_name_at_binds_only_target(function.start_byte(), name, ctx) {
6588 push_hit(function, ctx);
6589 continue;
6590 }
6591 let mut candidates =
6592 ctx.visibility
6593 .named_candidates(ctx.file, name, TargetKind::FreeFunction);
6594 candidates.retain(|candidate| {
6595 ctx.visibility.declaration_visible_at(
6596 &ctx.analyzer,
6597 ctx.file,
6598 candidate,
6599 function.start_byte(),
6600 )
6601 });
6602 dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6603 if candidates
6604 .iter()
6605 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6606 {
6607 push_unproven_hit(function, ctx);
6608 }
6609 }
6610}
6611
6612fn bare_name_binds_only_target(
6621 call: Node<'_>,
6622 function: Node<'_>,
6623 text: &str,
6624 ctx: &ScanCtx<'_>,
6625) -> bool {
6626 if !matches!(function.kind(), "identifier" | "template_function") {
6627 return false;
6628 }
6629 bare_name_at_binds_only_target(call.start_byte(), text, ctx)
6630}
6631
6632fn bare_name_at_binds_only_target(reference_byte: usize, text: &str, ctx: &ScanCtx<'_>) -> bool {
6633 let mut candidates = ctx
6634 .visibility
6635 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6636 candidates.retain(|candidate| {
6637 ctx.visibility
6638 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, reference_byte)
6639 });
6640 dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
6641 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
6642}
6643
6644fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
6645 if ctx.spec.param_types.is_none() {
6646 return true;
6647 }
6648 let mut candidates = ctx
6649 .visibility
6650 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
6651 candidates.retain(|candidate| {
6652 ctx.visibility
6653 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
6654 });
6655 callable_candidates_may_match_target(call, candidates, ctx)
6656}
6657
6658fn callable_candidates_may_match_target(
6659 call: Node<'_>,
6660 mut candidates: Vec<CodeUnit>,
6661 ctx: &ScanCtx<'_>,
6662) -> bool {
6663 let Some(arity) = ctx
6664 .visibility
6665 .call_arity_evidence(ctx.file, call, ctx.source)
6666 .exact()
6667 else {
6668 return true;
6669 };
6670 candidates.retain(|unit| {
6671 cpp_callable_arity(&ctx.analyzer, unit).is_none_or(|known| known.accepts(arity))
6672 });
6673 if candidates.is_empty()
6674 || !candidates
6675 .iter()
6676 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6677 {
6678 return true;
6679 }
6680 let arg_types = call_argument_types(call, ctx);
6681 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6682 candidates,
6683 &arg_types,
6684 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6685 &|candidate| {
6686 ctx.visibility
6687 .callable_is_template_declaration(&ctx.analyzer, candidate)
6688 },
6689 &|name| ctx.visibility.resolve_type(ctx.file, name),
6690 &|left, right| same_visible_symbol(left, right),
6691 );
6692 filtered
6693 .iter()
6694 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6695}
6696
6697fn argument_list_types(arguments: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6703 argument_children(arguments)
6704 .map(|arg| expression_arg_type(arg, ctx))
6705 .collect()
6706}
6707
6708fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
6709 call_arguments_node(call)
6710 .map(|arguments| argument_list_types(arguments, ctx))
6711 .unwrap_or_default()
6712}
6713
6714fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6715 match node.kind() {
6716 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
6717 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
6718 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
6719 literal
6720 }),
6721 "identifier" => identifier_arg_type(node, node_text(node, ctx.source), ctx),
6722 "parameter_declaration" => {
6727 let declared = node.child_by_field_name("type")?;
6728 (node.child_by_field_name("declarator").is_none()
6729 && declared.kind() == "type_identifier")
6730 .then(|| identifier_arg_type(declared, node_text(declared, ctx.source), ctx))
6731 .flatten()
6732 }
6733 "field_expression" => {
6735 let receiver = node
6736 .child_by_field_name("argument")
6737 .or_else(|| node.named_child(0))?;
6738 let field = node.child_by_field_name("field")?;
6739 (receiver.kind() == "this")
6740 .then(|| enclosing_member_field_arg_type(node, node_text(field, ctx.source), ctx))
6741 .flatten()
6742 }
6743 "parenthesized_expression" => node
6744 .child_by_field_name("argument")
6745 .or_else(|| node.named_child(0))
6746 .and_then(|inner| expression_arg_type(inner, ctx)),
6747 "call_expression" => {
6750 let forwarded = cpp_forwarding_call_argument(node, ctx.source)?;
6751 expression_arg_type(forwarded, ctx)
6752 }
6753 "pointer_expression" => {
6754 let delta = match node.child_by_field_name("operator")?.kind() {
6755 "&" => 1,
6756 "*" => -1,
6757 _ => return None,
6758 };
6759 let inner = node
6760 .child_by_field_name("argument")
6761 .or_else(|| node.named_child(0))?;
6762 let mut arg_type = expression_arg_type(inner, ctx)?;
6763 arg_type.indirection += delta;
6764 Some(arg_type)
6765 }
6766 _ => None,
6767 }
6768}
6769
6770fn identifier_arg_type(node: Node<'_>, name: &str, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
6780 match ctx.bindings.resolve_symbol(name) {
6781 SymbolResolution::Precise(bindings) => {
6782 bindings.iter().find_map(CppScanBinding::as_arg_type)
6783 }
6784 SymbolResolution::Ambiguous => None,
6785 SymbolResolution::Unknown => (!ctx.local_shadows.is_shadowed(name))
6786 .then(|| enclosing_member_field_arg_type(node, name, ctx))
6787 .flatten(),
6788 }
6789}
6790
6791fn enclosing_member_field_arg_type(
6799 node: Node<'_>,
6800 name: &str,
6801 ctx: &ScanCtx<'_>,
6802) -> Option<CppArgType> {
6803 let owner = enclosing_context(node, ctx).owner?;
6804 let fields = ctx
6805 .visibility
6806 .visible_members_for_owner_name(ctx.file, &owner, name)
6807 .into_iter()
6808 .filter(|unit| unit.is_field())
6809 .collect::<Vec<_>>();
6810 let [field] = fields.as_slice() else {
6811 return None;
6812 };
6813 let (type_name, unit, indirection) =
6814 field_declared_type_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)?;
6815 Some(CppArgType {
6816 name: type_name,
6817 unit,
6818 indirection,
6819 pointee_const: false,
6820 })
6821}
6822
6823fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6828 let text = node_text(node, ctx.source);
6829 if !name_matches_callable(text, &ctx.spec.member_name) {
6830 return;
6831 }
6832 if let Some(kind) = recovered_callable_declaration_kind(node, ctx) {
6833 *ctx.raw_match_count += 1;
6834 match kind {
6835 RecoveredCallableDeclarationKind::Declaration => push_declared_reference_hit(node, ctx),
6836 RecoveredCallableDeclarationKind::Definition => {
6837 push_recovered_definition_hit(node, ctx)
6838 }
6839 }
6840 return;
6841 }
6842 if is_declaration_name(node) {
6843 maybe_record_free_function_declaration_reference(node, ctx);
6844 return;
6845 }
6846 if is_call_callee_node(node) {
6847 return;
6848 }
6849 *ctx.raw_match_count += 1;
6850 if ctx.visibility.contains_named_symbol(
6851 ctx.file,
6852 text,
6853 TargetKind::FreeFunction,
6854 &ctx.spec.target,
6855 ) {
6856 push_hit(node, ctx);
6857 } else if ctx.visibility.resolve_known_non_target(
6858 ctx.file,
6859 text,
6860 TargetKind::FreeFunction,
6861 &ctx.spec.target,
6862 ) {
6863 } else {
6865 push_unproven_hit(node, ctx);
6866 }
6867}
6868
6869#[derive(Clone, Copy)]
6870enum RecoveredCallableDeclarationKind {
6871 Declaration,
6872 Definition,
6873}
6874
6875fn recovered_callable_declaration_kind(
6884 node: Node<'_>,
6885 ctx: &ScanCtx<'_>,
6886) -> Option<RecoveredCallableDeclarationKind> {
6887 if !matches!(node.kind(), "identifier" | "field_identifier") {
6888 return None;
6889 }
6890 let target_identity_proven =
6891 ctx.target_declaration_ranges.iter().any(|range| {
6892 range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6893 }) || (ctx.analyzer.reference_uses_c_semantics(ctx.file)
6894 && ctx.spec.target.is_function()
6895 && ctx.spec.target.source() == ctx.file);
6896 let mut current = ctx.ancestry.parent(node);
6897 while let Some(parent) = current {
6898 if parent.is_error() {
6899 let error = parent;
6903 let Some(function) = ctx.ancestry.parent(error).filter(|function| {
6904 function.kind() == "function_declarator"
6905 && function
6906 .child_by_field_name("parameters")
6907 .is_some_and(|parameters| error.end_byte() <= parameters.start_byte())
6908 }) else {
6909 current = ctx.ancestry.parent(parent);
6910 continue;
6911 };
6912 let container = ctx.ancestry.parent(function)?;
6913 let kind = match container.kind() {
6914 "declaration" => RecoveredCallableDeclarationKind::Declaration,
6915 "function_definition" => RecoveredCallableDeclarationKind::Definition,
6916 _ => return None,
6917 };
6918 return target_identity_proven.then_some(kind);
6919 }
6920 if parent.kind() == "function_declarator" {
6921 let error = ctx
6922 .ancestry
6923 .parent(parent)
6924 .filter(|parent| parent.is_error())?;
6925 let declarator = parent.child_by_field_name("declarator")?;
6926 if declarator.start_byte() > node.start_byte()
6927 || node.end_byte() > declarator.end_byte()
6928 {
6929 return None;
6930 }
6931 let container = ctx.ancestry.parent(error)?;
6932 let kind = match container.kind() {
6933 "declaration" => RecoveredCallableDeclarationKind::Declaration,
6934 "function_definition" => RecoveredCallableDeclarationKind::Definition,
6935 _ => return None,
6936 };
6937 return target_identity_proven.then_some(kind);
6938 }
6939 if parent.is_error() {
6940 current = ctx.ancestry.parent(parent);
6941 continue;
6942 }
6943 if matches!(
6944 parent.kind(),
6945 "translation_unit" | "function_definition" | "compound_statement"
6946 ) {
6947 return None;
6948 }
6949 current = ctx.ancestry.parent(parent);
6950 }
6951 None
6952}
6953
6954fn maybe_record_free_function_declaration_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6955 if !ctx.spec.callable_has_definition_body {
6956 return;
6957 }
6958 let text = node_text(node, ctx.source);
6959 if !name_matches_callable(text, &ctx.spec.member_name) {
6960 return;
6961 }
6962 let mut declaration = ctx.ancestry.parent(node);
6963 while let Some(candidate) = declaration {
6964 if candidate.kind() == "function_definition" {
6965 return;
6966 }
6967 if candidate.kind() == "declaration" {
6968 break;
6969 }
6970 declaration = ctx.ancestry.parent(candidate);
6971 }
6972 if declaration.is_none() {
6973 return;
6974 }
6975 let linked_declaration = ctx
6976 .visibility
6977 .named_candidates(ctx.file, text, TargetKind::FreeFunction)
6978 .into_iter()
6979 .find(|candidate| {
6980 candidate.source() == ctx.file
6981 && candidate.is_function()
6982 && (!ctx.target_group.contains(candidate)
6983 || candidate.source() == ctx.spec.target.source())
6984 && ctx.analyzer.ranges(candidate).iter().any(|range| {
6985 range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte
6986 })
6987 && (candidate.source() == ctx.spec.target.source()
6988 && candidate.fq_name() == ctx.spec.target.fq_name()
6989 && candidate.signature() == ctx.spec.target.signature()
6990 || cpp_callable_definitions_share_identity_evidence_with_visibility(
6991 &ctx.analyzer,
6992 ctx.visibility,
6993 candidate,
6994 &ctx.spec.target,
6995 ))
6996 && callable_arity_matches_target(candidate, node, ctx)
6997 });
6998 if linked_declaration.is_none() {
6999 return;
7000 }
7001 *ctx.raw_match_count += 1;
7002 push_declaration_reference_hit(node, ctx);
7003}
7004
7005fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7006 let Some(function) = function_definition_name_node(node) else {
7007 return;
7008 };
7009 let text = node_text(function, ctx.source);
7010 if !name_matches_callable(text, &ctx.spec.member_name) {
7011 return;
7012 }
7013 *ctx.raw_match_count += 1;
7014 if !function_definition_signature_matches_target(node, ctx) {
7015 return;
7016 }
7017 match definition_name_target_resolution(function, TargetKind::FreeFunction, ctx) {
7018 DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7019 DefinitionNameTargetResolution::KnownNonTarget => {
7020 }
7022 DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7023 }
7024}
7025
7026fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7027 if node.kind() == "preproc_arg" {
7028 maybe_record_function_macro_replacement_method_hits(node, ctx);
7029 return;
7030 }
7031 if node.kind() == "using_declaration" {
7032 maybe_record_using_callable_hit(node, ctx);
7033 return;
7034 }
7035 if has_ancestor_kind(node, "using_declaration") {
7036 return;
7037 }
7038 if node.kind() == "function_definition" {
7039 maybe_record_method_definition_hit(node, ctx);
7040 return;
7041 }
7042 if is_declaration_name(node) {
7043 return;
7044 }
7045 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
7046 maybe_record_qualified_method_value_hit(node, member, ctx);
7047 return;
7048 }
7049 if let Some(value) = qualified_callable_value(node) {
7050 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
7051 return;
7052 }
7053 if let Some(call) = recovered_relational_template_member_call(node) {
7054 maybe_record_recovered_relational_template_method_hit(call, ctx);
7055 return;
7056 }
7057 if node.kind() != "call_expression" {
7058 return;
7059 }
7060 if let Some((receiver, operator)) = explicit_operator_call(node) {
7061 let text = node_text(operator, ctx.source);
7062 if !name_matches_callable(text, &ctx.spec.member_name) {
7063 return;
7064 }
7065 *ctx.raw_match_count += 1;
7066 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7067 match ctx
7068 .visibility
7069 .call_arity_evidence(ctx.file, node, ctx.source)
7070 .accepts(expected)
7071 {
7072 Some(true) => {}
7073 Some(false) => return,
7074 None => {
7075 push_unproven_hit(operator, ctx);
7076 return;
7077 }
7078 }
7079 }
7080 let arity = ctx
7081 .visibility
7082 .call_arity_evidence(ctx.file, node, ctx.source)
7083 .exact();
7084 record_explicit_receiver_method_hit(receiver, operator, arity, ctx);
7085 return;
7086 }
7087 let Some(function) = node
7088 .child_by_field_name("function")
7089 .or_else(|| node.named_child(0))
7090 else {
7091 return;
7092 };
7093 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7094 return;
7095 }
7096 if function.kind() == "identifier"
7097 && ctx
7098 .local_shadows
7099 .is_shadowed(node_text(function, ctx.source))
7100 {
7101 return;
7102 }
7103 *ctx.raw_match_count += 1;
7104 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
7105 match ctx
7106 .visibility
7107 .call_arity_evidence(ctx.file, node, ctx.source)
7108 .accepts(expected)
7109 {
7110 Some(true) => {}
7111 Some(false) => return,
7112 None => {
7113 push_unproven_hit(function_terminal_node(function), ctx);
7114 return;
7115 }
7116 }
7117 }
7118 if !method_call_may_target(node, ctx) {
7119 return;
7120 }
7121 if is_structurally_qualified(function) {
7122 match qualified_owner_resolution(function, ctx) {
7123 QualifiedOwnerResolution::Target => {
7124 push_hit(function_terminal_node(function), ctx);
7125 }
7126 QualifiedOwnerResolution::NonTarget => {}
7127 QualifiedOwnerResolution::Unresolved => {
7128 push_unproven_hit(function_terminal_node(function), ctx);
7129 }
7130 }
7131 return;
7132 }
7133 match call_function_target_resolution(function, ctx) {
7134 MethodReceiverTargetResolution::Target
7135 if call_function_has_direct_self_receiver(
7136 function,
7137 ctx.analyzer.reference_uses_c_semantics(ctx.file),
7138 ) =>
7139 {
7140 push_self_receiver_hit(function_terminal_node(function), ctx);
7141 }
7142 MethodReceiverTargetResolution::Target => {
7143 push_hit(function_terminal_node(function), ctx);
7144 }
7145 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7146 MethodReceiverTargetResolution::Missing
7152 if inherited_target_owner_context(function, ctx) =>
7153 {
7154 push_hit(function_terminal_node(function), ctx);
7155 }
7156 MethodReceiverTargetResolution::Missing
7157 if (matches!(function.kind(), "identifier" | "template_function")
7158 || call_function_has_direct_self_receiver(
7159 function,
7160 ctx.analyzer.reference_uses_c_semantics(ctx.file),
7161 ))
7162 && (same_owner_context(function, ctx)
7163 || out_of_line_target_owner_context(function, ctx)) =>
7164 {
7165 push_self_receiver_hit(function_terminal_node(function), ctx);
7166 }
7167 MethodReceiverTargetResolution::Missing
7168 if function.kind() == "identifier"
7169 && resolves_to_lexical_free_function(function, ctx) =>
7170 {
7171 }
7174 MethodReceiverTargetResolution::Missing
7175 if !receiver_has_known_non_target(function, ctx)
7176 && !known_non_target_owner_context(function, ctx) =>
7177 {
7178 push_unproven_hit(function_terminal_node(function), ctx);
7179 }
7180 MethodReceiverTargetResolution::Missing => {}
7181 }
7182}
7183
7184fn maybe_record_function_macro_replacement_method_hits(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7192 let Some(definition) = ctx.ancestry.parent(node).filter(|parent| {
7193 parent.kind() == "preproc_function_def"
7194 && parent
7195 .child_by_field_name("value")
7196 .is_some_and(|value| same_node(value, node))
7197 }) else {
7198 return;
7199 };
7200 let Some(body) = ctx
7201 .visibility
7202 .function_macro_replacement_body(ctx.file, definition, ctx.source)
7203 else {
7204 return;
7205 };
7206 let Some(statements) = body.statements() else {
7207 return;
7208 };
7209 let mut candidates = Vec::new();
7212 let mut stack = vec![statements];
7213 while let Some(current) = stack.pop() {
7214 push_named_children_reversed(current, &mut stack);
7215 if current.kind() != "call_expression" {
7216 continue;
7217 }
7218 let Some(function) = current
7219 .child_by_field_name("function")
7220 .filter(|function| function.kind() == "field_expression")
7221 else {
7222 continue;
7223 };
7224 let Some(member) = function.child_by_field_name("field") else {
7225 continue;
7226 };
7227 if !name_matches_callable(node_text(member, &body.source), &ctx.spec.member_name) {
7228 continue;
7229 }
7230 let Some(receiver) = function
7231 .child_by_field_name("argument")
7232 .or_else(|| function.child_by_field_name("object"))
7233 .or_else(|| function.named_child(0))
7234 else {
7235 continue;
7236 };
7237 candidates.push((current, member, receiver));
7238 }
7239 if candidates.is_empty() {
7240 return;
7241 }
7242 let Some(replacement_start) =
7243 function_macro_replacement_span(definition, ctx.source).map(|span| span.start)
7244 else {
7245 return;
7246 };
7247 let locals = macro_replacement_local_receivers(statements, &body, ctx);
7248 for (current, member, receiver) in candidates {
7249 if *ctx.limit_exceeded {
7250 return;
7251 }
7252 let range = body.file_range(member, replacement_start);
7253 debug_assert_eq!(
7254 ctx.source.get(range.clone()),
7255 Some(node_text(member, &body.source)),
7256 "macro replacement member range must spell the member name"
7257 );
7258 *ctx.raw_match_count += 1;
7259 let arity_evidence = ctx.visibility.call_arity_evidence_at(
7262 ctx.file,
7263 current,
7264 &body.source,
7265 definition.start_byte(),
7266 );
7267 if let Some(expected) = ctx.spec.callable_arity_at(range.start) {
7268 match arity_evidence.accepts(expected) {
7269 Some(true) => {}
7270 Some(false) => continue,
7271 None => {
7272 push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7273 continue;
7274 }
7275 }
7276 }
7277 let declaring_owner = match macro_replacement_receiver(receiver, &body, &locals, node, ctx)
7278 {
7279 MacroReplacementReceiver::Parameter => {
7280 if target_member_is_visible_candidate(ctx) {
7281 push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7282 }
7283 continue;
7284 }
7285 MacroReplacementReceiver::Unknown => continue,
7286 MacroReplacementReceiver::Units(units) => {
7287 declaring_owner_from_receiver_units(units, range.start, arity_evidence.exact(), ctx)
7288 }
7289 };
7290 match declaring_owner_target_resolution(declaring_owner, range.start, ctx) {
7291 MethodReceiverTargetResolution::Target => {
7292 push_reference_hit_range(node, range.start, range.end, ctx);
7293 }
7294 MethodReceiverTargetResolution::Missing => {
7295 push_unproven_reference_hit_range(node, range.start, range.end, ctx);
7296 }
7297 MethodReceiverTargetResolution::NonTarget
7298 | MethodReceiverTargetResolution::Ambiguous => {}
7299 }
7300 }
7301}
7302
7303enum MacroReplacementReceiver {
7304 Parameter,
7307 Units(Vec<CodeUnit>),
7309 Unknown,
7311}
7312
7313fn macro_replacement_receiver(
7314 receiver: Node<'_>,
7315 body: &ParsedReplacementBody,
7316 locals: &HashMap<String, Option<CodeUnit>>,
7317 anchor: Node<'_>,
7318 ctx: &ScanCtx<'_>,
7319) -> MacroReplacementReceiver {
7320 let mut current = receiver;
7321 while matches!(
7322 current.kind(),
7323 "parenthesized_expression" | "pointer_expression"
7324 ) {
7325 let Some(inner) = current
7326 .child_by_field_name("argument")
7327 .or_else(|| current.named_child(0))
7328 else {
7329 return MacroReplacementReceiver::Unknown;
7330 };
7331 current = inner;
7332 }
7333 if !matches!(current.kind(), "identifier" | "field_identifier") {
7334 return MacroReplacementReceiver::Unknown;
7335 }
7336 let name = node_text(current, &body.source);
7337 if body.parameters.iter().any(|parameter| parameter == name) {
7338 return MacroReplacementReceiver::Parameter;
7339 }
7340 if let Some(local) = locals.get(name) {
7341 return match local {
7342 Some(unit) => MacroReplacementReceiver::Units(vec![unit.clone()]),
7343 None => MacroReplacementReceiver::Unknown,
7344 };
7345 }
7346 let global_fields = ctx
7350 .visibility
7351 .visible_identifier_candidates(ctx.file, name)
7352 .filter(|unit| has_persisted_global_field_identity(unit) && unit.identifier() == name)
7353 .collect::<Vec<_>>();
7354 if !global_fields.is_empty() {
7355 return MacroReplacementReceiver::Units(receiver_units_from_declared_fields(
7356 global_fields,
7357 anchor,
7358 ctx,
7359 ));
7360 }
7361 MacroReplacementReceiver::Units(
7362 ctx.visibility
7363 .resolve_type(ctx.file, name)
7364 .into_iter()
7365 .collect(),
7366 )
7367}
7368
7369fn macro_replacement_local_receivers(
7376 statements: Node<'_>,
7377 body: &ParsedReplacementBody,
7378 ctx: &ScanCtx<'_>,
7379) -> HashMap<String, Option<CodeUnit>> {
7380 let mut locals = HashMap::default();
7381 let mut stack = vec![statements];
7382 while let Some(current) = stack.pop() {
7383 push_named_children_reversed(current, &mut stack);
7384 if current.kind() != "declaration" {
7385 continue;
7386 }
7387 let Some(type_node) = current
7388 .child_by_field_name("type")
7389 .or_else(|| first_type_child(current))
7390 else {
7391 continue;
7392 };
7393 let unit = macro_replacement_declared_unit(type_node, body, ctx);
7394 let mut cursor = current.walk();
7395 for child in current.named_children(&mut cursor) {
7396 let declarator = if child.kind() == "init_declarator" {
7397 child.child_by_field_name("declarator")
7398 } else {
7399 is_declarator_node(child).then_some(child)
7400 };
7401 let Some(name) =
7402 declarator.and_then(|declarator| extract_variable_name(declarator, &body.source))
7403 else {
7404 continue;
7405 };
7406 locals.insert(name, unit.clone());
7407 }
7408 }
7409 locals
7410}
7411
7412fn macro_replacement_declared_unit(
7413 type_node: Node<'_>,
7414 body: &ParsedReplacementBody,
7415 ctx: &ScanCtx<'_>,
7416) -> Option<CodeUnit> {
7417 let name = normalize_cpp_type_name(node_text(type_node, &body.source));
7418 let unit = match ctx
7419 .visibility
7420 .resolve_type_node_result(ctx.file, type_node, &body.source)
7421 {
7422 Ok(Some(unit)) => Some(unit),
7423 Ok(None) => ctx
7424 .visibility
7425 .canonical_type_for_reference(ctx.file, &name)
7426 .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
7427 Err(_) => None,
7428 }?;
7429 canonical_receiver_unit(&unit, ctx)
7430}
7431
7432fn target_member_is_visible_candidate(ctx: &ScanCtx<'_>) -> bool {
7438 ctx.visibility
7439 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7440 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
7441}
7442
7443fn maybe_record_recovered_relational_template_method_hit(
7444 call: RecoveredRelationalTemplateMemberCall<'_>,
7445 ctx: &mut ScanCtx<'_>,
7446) {
7447 if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
7448 return;
7449 }
7450 *ctx.raw_match_count += 1;
7451 if !ctx
7452 .visibility
7453 .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
7454 || ctx
7455 .spec
7456 .callable_arity_at(call.member.start_byte())
7457 .is_some_and(|arity| !arity.accepts(call.arity))
7458 {
7459 return;
7460 }
7461 record_explicit_receiver_method_hit(call.receiver, call.member, Some(call.arity), ctx);
7462}
7463
7464fn record_explicit_receiver_method_hit(
7465 receiver: Node<'_>,
7466 member: Node<'_>,
7467 arity: Option<usize>,
7468 ctx: &mut ScanCtx<'_>,
7469) {
7470 match explicit_receiver_target_resolution(receiver, arity, ctx) {
7471 MethodReceiverTargetResolution::Target
7472 if receiver_is_self_like(
7473 receiver,
7474 ctx.analyzer.reference_uses_c_semantics(ctx.file),
7475 ) =>
7476 {
7477 push_self_receiver_hit(member, ctx);
7478 }
7479 MethodReceiverTargetResolution::Target => push_hit(member, ctx),
7480 MethodReceiverTargetResolution::Missing => push_unproven_hit(member, ctx),
7481 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
7482 }
7483}
7484
7485fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
7486 if node.kind() != "qualified_identifier" {
7487 return None;
7488 }
7489 let parameter = node
7490 .parent()
7491 .filter(|parent| parent.kind() == "parameter_declaration")?;
7492 let parameter_declarator = parameter.child_by_field_name("declarator")?;
7493 if parameter.child_by_field_name("type") != Some(node)
7498 || parameter_declarator.kind() != "abstract_function_declarator"
7499 {
7500 return None;
7501 }
7502 let parameter_list = parameter
7503 .parent()
7504 .filter(|parent| parent.kind() == "parameter_list")?;
7505 if parameter_list.named_child_count() != 1 {
7506 return None;
7507 }
7508 let function_declarator = parameter_list
7509 .parent()
7510 .filter(|parent| parent.kind() == "function_declarator")?;
7511 if function_declarator
7512 .child_by_field_name("declarator")
7513 .is_none_or(|declarator| declarator.kind() != "identifier")
7514 || function_declarator
7515 .parent()
7516 .is_none_or(|parent| parent.kind() != "declaration")
7517 {
7518 return None;
7519 }
7520 node.child_by_field_name("name")
7521}
7522
7523fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7524 let Some(imported) = ordinary_using_declaration_type_node(node) else {
7525 return;
7526 };
7527 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
7528 return;
7529 }
7530 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7531 return;
7532 };
7533 *ctx.raw_match_count += 1;
7534 let owner_resolution = qualified_owner_components(imported, ctx.source)
7535 .map(|qualified| {
7536 let lexical_scope = match enclosing_lexical_scope_components(
7537 imported,
7538 &ctx.analyzer,
7539 ctx.visibility,
7540 ctx.file,
7541 ctx.source,
7542 ) {
7543 LexicalScopeResolution::Resolved(scope) => scope,
7544 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7545 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7546 };
7547 ctx.visibility.resolve_type_components_lexically(
7548 &ctx.analyzer,
7549 ctx.file,
7550 &qualified.names,
7551 qualified.global,
7552 &lexical_scope,
7553 )
7554 })
7555 .unwrap_or(LexicalTypeResolution::Missing);
7556 let matches_target_owner = matches!(
7557 owner_resolution,
7558 LexicalTypeResolution::Resolved {
7559 ref unit,
7560 ref candidates,
7561 ..
7562 } if same_visible_symbol(unit, target_owner)
7563 || candidates
7564 .iter()
7565 .any(|candidate| same_visible_symbol(candidate, target_owner))
7566 );
7567 if !matches_target_owner {
7568 match owner_resolution {
7569 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7570 push_unproven_hit(imported, ctx);
7571 }
7572 LexicalTypeResolution::Resolved { .. } => {}
7573 }
7574 return;
7575 }
7576 match visible_target_callable_member_resolution(target_owner, ctx) {
7577 VisibleTargetCallableMemberResolution::Target => push_hit(imported, ctx),
7578 VisibleTargetCallableMemberResolution::NonCallable => {}
7579 VisibleTargetCallableMemberResolution::Unresolved => push_unproven_hit(imported, ctx),
7580 }
7581}
7582
7583enum VisibleTargetCallableMemberResolution {
7584 Target,
7585 NonCallable,
7586 Unresolved,
7587}
7588
7589fn visible_target_callable_member_resolution(
7590 owner: &CodeUnit,
7591 ctx: &ScanCtx<'_>,
7592) -> VisibleTargetCallableMemberResolution {
7593 match ctx
7594 .visibility
7595 .visible_member_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7596 {
7597 VisibleMemberResolution::Callable(candidates)
7598 if candidates.iter().all(|candidate| {
7599 ctx.target_group.contains(candidate)
7600 || ctx
7601 .target_group
7602 .iter()
7603 .any(|target| same_visible_symbol(candidate, target))
7604 }) =>
7605 {
7606 VisibleTargetCallableMemberResolution::Target
7607 }
7608 VisibleMemberResolution::NonCallable => VisibleTargetCallableMemberResolution::NonCallable,
7609 VisibleMemberResolution::Callable(_)
7610 | VisibleMemberResolution::AmbiguousKind
7611 | VisibleMemberResolution::Missing => VisibleTargetCallableMemberResolution::Unresolved,
7612 }
7613}
7614
7615fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7616 let name = node_text(node, ctx.source);
7617 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
7618 let key = (namespace.clone(), name.to_string());
7619 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
7620 return resolved;
7621 }
7622 let resolved = ctx
7623 .visibility
7624 .visible_identifier_candidates(ctx.file, name)
7625 .any(|unit| {
7626 unit.is_function()
7627 && type_owner_of(&ctx.analyzer, unit).is_none()
7628 && unit.package_name() == namespace
7629 });
7630 ctx.lexical_free_function_cache
7631 .borrow_mut()
7632 .insert(key, resolved);
7633 resolved
7634}
7635
7636fn maybe_record_qualified_method_value_hit(
7637 qualified: Node<'_>,
7638 member: Node<'_>,
7639 ctx: &mut ScanCtx<'_>,
7640) {
7641 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
7642 return;
7643 }
7644 *ctx.raw_match_count += 1;
7645 let resolution =
7646 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
7647 match resolution {
7648 LexicalCallableValueResolution::Type(resolved_owner) => {
7649 let Some(owner) = ctx.spec.owner.as_ref() else {
7650 push_unproven_hit(member, ctx);
7651 return;
7652 };
7653 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
7654 if same_visible_symbol(&resolved_owner, owner) {
7655 push_unproven_hit(member, ctx);
7656 }
7657 return;
7658 }
7659 match visible_target_callable_member_resolution(owner, ctx) {
7662 VisibleTargetCallableMemberResolution::Target => push_hit(member, ctx),
7663 VisibleTargetCallableMemberResolution::NonCallable => {}
7664 VisibleTargetCallableMemberResolution::Unresolved => {
7665 push_unproven_hit(member, ctx);
7666 }
7667 }
7668 }
7669 LexicalCallableValueResolution::FreeFunction(_) => {}
7670 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
7671 push_unproven_hit(member, ctx);
7672 }
7673 }
7674}
7675
7676fn qualified_callable_value_resolution(
7677 qualified: Node<'_>,
7678 member_name: &str,
7679 ctx: &ScanCtx<'_>,
7680) -> LexicalCallableValueResolution {
7681 let Some((owner_components, global)) =
7682 qualified_callable_owner_components(qualified, ctx.source)
7683 else {
7684 return LexicalCallableValueResolution::Missing;
7685 };
7686 let lexical_scope = if global {
7687 Vec::new()
7688 } else {
7689 match enclosing_lexical_scope_components(
7690 qualified,
7691 &ctx.analyzer,
7692 ctx.visibility,
7693 ctx.file,
7694 ctx.source,
7695 ) {
7696 LexicalScopeResolution::Resolved(scope) => scope,
7697 LexicalScopeResolution::Ambiguous => {
7698 return LexicalCallableValueResolution::Ambiguous;
7699 }
7700 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
7701 }
7702 };
7703 if let Some(target_owner) = ctx.spec.owner.as_ref()
7704 && let LexicalTypeResolution::Resolved { unit, .. } =
7705 resolve_type_components_lexically_at_for_target_with_scope_cache(
7706 qualified,
7707 &owner_components,
7708 global,
7709 &ctx.analyzer,
7710 ctx.visibility,
7711 &ctx.ordinary_type_imports,
7712 ctx.file,
7713 ctx.source,
7714 target_owner,
7715 false,
7716 Some(&ctx.lexical_scope_cache),
7717 )
7718 {
7719 return LexicalCallableValueResolution::Type(unit);
7720 }
7721 ctx.visibility.resolve_callable_value_components_lexically(
7722 &ctx.analyzer,
7723 ctx.file,
7724 &owner_components,
7725 member_name,
7726 global,
7727 &lexical_scope,
7728 )
7729}
7730
7731fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7732 let Some(owner) = ctx.spec.owner.as_ref() else {
7733 return true;
7734 };
7735 if ctx.spec.param_types.is_none() {
7736 return true;
7737 }
7738 let candidates = ctx
7739 .visibility
7740 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
7741 .into_iter()
7742 .filter(|unit| unit.is_function())
7743 .cloned()
7744 .collect::<Vec<_>>();
7745 callable_candidates_may_match_target(call, candidates, ctx)
7746}
7747
7748fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7749 let Some(function) = function_definition_name_node(node) else {
7750 return;
7751 };
7752 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
7753 return;
7754 }
7755 *ctx.raw_match_count += 1;
7756 if !function_definition_signature_matches_target(node, ctx) {
7757 return;
7758 }
7759 if node_inside_target_declaration(function, ctx) {
7760 return;
7761 }
7762 if is_structurally_qualified(function) {
7763 match qualified_owner_resolution(function, ctx) {
7764 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
7765 QualifiedOwnerResolution::NonTarget => {}
7766 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7767 }
7768 return;
7769 }
7770 match definition_name_target_resolution(function, TargetKind::Method, ctx) {
7771 DefinitionNameTargetResolution::Target => push_definition_hit(function, ctx),
7772 DefinitionNameTargetResolution::KnownNonTarget => {
7773 }
7775 DefinitionNameTargetResolution::Unresolved
7776 if known_non_target_owner_context(function, ctx) => {}
7777 DefinitionNameTargetResolution::Unresolved => push_unproven_definition_hit(function, ctx),
7778 }
7779}
7780
7781pub(super) fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7782 let node_range = brokk_bifrost_core::analyzer::tree_walk::node_range(node);
7783 ctx.target_declaration_ranges
7784 .iter()
7785 .any(|range| range.contains(&node_range))
7786}
7787
7788fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
7789 let mut receiver = None;
7790 let mut cursor = node.walk();
7791 for child in node.named_children(&mut cursor) {
7792 if child.kind() == "argument_list" {
7793 continue;
7794 }
7795 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
7796 return receiver.map(|receiver| (receiver, operator));
7797 }
7798 if receiver.is_none() {
7799 receiver = Some(child);
7800 }
7801 }
7802 None
7803}
7804
7805pub fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
7807 if node.kind() != "function_definition" {
7808 return None;
7809 }
7810 node.child_by_field_name("declarator")
7811 .and_then(declarator_name_node)
7812}
7813
7814fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
7815 function_definition_name_node(node)
7816}
7817
7818fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7819 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7820 return true;
7821 };
7822 let Some(candidate) = canonical_callable_at_node(
7823 node,
7824 &ctx.analyzer,
7825 ctx.visibility,
7826 ctx.file,
7827 ctx.source,
7828 ctx.spec.kind,
7829 ) else {
7830 return false;
7831 };
7832 let Some(actual_arity) = cpp_callable_arity(&ctx.analyzer, &candidate) else {
7833 return false;
7834 };
7835 if !callable_arity_matches(expected, actual_arity) {
7836 return false;
7837 }
7838 ctx.spec.param_types.as_ref().is_none_or(|expected| {
7839 cpp_callable_parameter_types(&ctx.analyzer, &candidate)
7840 .is_some_and(|actual| actual == *expected)
7841 })
7842}
7843
7844fn callable_arity_matches_target(candidate: &CodeUnit, node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7845 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
7846 return true;
7847 };
7848 cpp_callable_arity(&ctx.analyzer, candidate)
7849 .is_some_and(|actual| callable_arity_matches(expected, actual))
7850}
7851
7852fn callable_arity_matches(
7853 expected: brokk_bifrost_core::analyzer::model::CallableArity,
7854 actual: brokk_bifrost_core::analyzer::model::CallableArity,
7855) -> bool {
7856 expected.accepts(actual.total())
7857}
7858
7859fn canonical_callable_at_node(
7860 node: Node<'_>,
7861 analyzer: &CppGraphSource<'_>,
7862 visibility: &VisibilityIndex<'_>,
7863 file: &ProjectFile,
7864 source: &str,
7865 kind: TargetKind,
7866) -> Option<CodeUnit> {
7867 let name = callable_name_node(node)?;
7868 let name_range = (name.start_byte(), name.end_byte());
7869 let name_text = node_text(name, source);
7870 for candidate in visibility.named_candidates(file, name_text, kind) {
7871 if candidate.source() != file || !candidate.is_function() {
7872 continue;
7873 }
7874 let Some(occurrences) = analyzer.declaration_source_occurrences(&candidate) else {
7875 continue;
7876 };
7877 for occurrence in occurrences {
7878 if occurrence
7879 .name_range
7880 .is_some_and(|range| (range.start_byte, range.end_byte) == name_range)
7881 {
7882 return Some(candidate);
7883 }
7884 }
7885 }
7886 None
7887}
7888
7889fn callable_name_node(node: Node<'_>) -> Option<Node<'_>> {
7890 let name = if node.kind() == "function_definition" {
7891 function_definition_name_node(node)?
7892 } else {
7893 node.child_by_field_name("declarator")
7894 .and_then(declarator_name_node)?
7895 };
7896 Some(function_terminal_node(name))
7897}
7898
7899fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
7900 name_matches_callable(node_text(function_terminal_node(node), source), expected)
7901}
7902
7903enum DefinitionNameTargetResolution {
7904 Target,
7905 KnownNonTarget,
7906 Unresolved,
7907}
7908
7909fn definition_name_target_resolution(
7910 function: Node<'_>,
7911 target_kind: TargetKind,
7912 ctx: &ScanCtx<'_>,
7913) -> DefinitionNameTargetResolution {
7914 let candidates = definition_name_candidates(function, ctx);
7915 if candidates
7916 .iter()
7917 .filter(|name| target_kind != TargetKind::Method || name.contains("::"))
7918 .any(|name| {
7919 ctx.visibility
7920 .contains_named_symbol(ctx.file, name, target_kind, &ctx.spec.target)
7921 })
7922 {
7923 DefinitionNameTargetResolution::Target
7924 } else if candidates.iter().any(|name| {
7925 ctx.visibility
7926 .resolve_known_non_target(ctx.file, name, target_kind, &ctx.spec.target)
7927 }) {
7928 DefinitionNameTargetResolution::KnownNonTarget
7929 } else {
7930 DefinitionNameTargetResolution::Unresolved
7931 }
7932}
7933
7934fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
7935 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
7936 if raw.is_empty() {
7937 return Vec::new();
7938 }
7939 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
7940 return vec![raw];
7941 };
7942 if !raw.contains("::") {
7943 return vec![format!("{namespace}::{raw}")];
7944 }
7945 if raw
7951 .split("::")
7952 .next()
7953 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
7954 {
7955 vec![format!("{namespace}::{raw}"), raw]
7956 } else {
7957 vec![raw]
7958 }
7959}
7960
7961fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7962 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(node, kind)
7963}
7964
7965fn scan_leaf_is_value_position(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7976 node.kind() != "type_identifier"
7977 || (is_type_shaped_template_argument_name(node)
7978 && ctx
7979 .visibility
7980 .resolve_type(ctx.file, node_text(node, ctx.source))
7981 .is_none())
7982}
7983
7984fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
7985 if is_c_offsetof_member_node(node) {
7986 return;
7987 }
7988 if matches!(node.kind(), "identifier" | "field_identifier")
7989 && designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
7990 .is_some()
7991 {
7992 return;
7993 }
7994 if !matches!(
7995 node.kind(),
7996 "identifier" | "field_identifier" | "qualified_identifier" | "type_identifier"
7997 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
7998 || !scan_leaf_is_value_position(node, ctx)
7999 || is_declaration_name(node)
8000 || is_member_field_own_declarator(node, ctx)
8001 || is_selected_field_expression_member_descendant(node)
8002 || is_nested_in_qualified_identifier(node)
8003 {
8004 return;
8005 }
8006 *ctx.raw_match_count += 1;
8007 if global_field_resolves_to_target(node, ctx) {
8008 push_hit(node, ctx);
8009 } else if global_field_is_known_non_target(node, ctx) {
8010 } else {
8011 push_unproven_hit(node, ctx);
8012 }
8013}
8014
8015fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
8021 let candidate = node;
8022 while let Some(parent) = node.parent() {
8023 if parent.kind() == "field_expression" {
8024 if let Some(field) = parent.child_by_field_name("field")
8025 && node_is_within(field, candidate)
8026 {
8027 let selected = match field.kind() {
8033 "dependent_name" => field.named_child(0).unwrap_or(field),
8034 _ => field,
8035 };
8036 let selected_name = match selected.kind() {
8037 "template_method" | "template_function" | "template_type" => {
8038 selected.child_by_field_name("name").unwrap_or(selected)
8039 }
8040 _ => selected,
8041 };
8042 if node_is_within(selected_name, candidate) {
8043 return true;
8044 }
8045 node = parent;
8049 continue;
8050 }
8051 let receiver = parent
8052 .child_by_field_name("argument")
8053 .or_else(|| parent.child_by_field_name("object"))
8054 .or_else(|| parent.named_child(0));
8055 if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
8056 return true;
8059 }
8060 }
8061 node = parent;
8062 }
8063 false
8064}
8065
8066fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8067 let text = node_text(node, ctx.source);
8068 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8069 return false;
8070 }
8071 if text.contains("::") {
8072 return ctx.visibility.contains_named_symbol(
8073 ctx.file,
8074 text,
8075 TargetKind::GlobalField,
8076 &ctx.spec.target,
8077 );
8078 }
8079 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
8080 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
8081 {
8082 return ctx.visibility.contains_named_symbol(
8083 ctx.file,
8084 text,
8085 TargetKind::GlobalField,
8086 &ctx.spec.target,
8087 );
8088 }
8089 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
8090 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
8091 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8092 brokk_bifrost_core::analyzer::Language::Cpp,
8093 &namespace,
8094 ) == indexed_scope
8095 })
8096 {
8097 return ctx.visibility.contains_named_symbol(
8098 ctx.file,
8099 text,
8100 TargetKind::GlobalField,
8101 &ctx.spec.target,
8102 );
8103 }
8104 bare_global_field_uniquely_resolves_to_target(text, ctx)
8105}
8106
8107fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
8108 let mut matched_target = false;
8109 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
8110 if !has_persisted_global_field_identity(unit)
8111 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
8112 {
8113 continue;
8114 }
8115 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
8116 continue;
8117 }
8118 if same_visible_global_field_symbol(
8119 &ctx.analyzer,
8120 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8121 unit,
8122 &ctx.spec.target,
8123 ) {
8124 matched_target = true;
8125 } else {
8126 return false;
8127 }
8128 }
8129 matched_target
8130}
8131
8132pub(crate) fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
8133 unit.is_field() && !unit.short_name().contains('.')
8138}
8139
8140fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8141 let text = node_text(node, ctx.source);
8142 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
8143 return true;
8144 }
8145 if text.contains("::") {
8146 return ctx.visibility.resolve_known_non_target(
8147 ctx.file,
8148 text,
8149 TargetKind::GlobalField,
8150 &ctx.spec.target,
8151 );
8152 }
8153 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
8154 return false;
8155 };
8156 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
8157 && ctx
8158 .visibility
8159 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
8160 .any(|unit| {
8161 has_persisted_global_field_identity(unit)
8162 && unit.identifier() == ctx.spec.member_name
8163 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
8164 && !same_visible_global_field_symbol(
8165 &ctx.analyzer,
8166 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8167 unit,
8168 &ctx.spec.target,
8169 )
8170 })
8171}
8172
8173fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8174 if is_c_offsetof_member_node(node) {
8175 maybe_record_c_offsetof_field_hit(node, ctx);
8176 return;
8177 }
8178 if node.kind() == "field_expression" {
8179 let Some(field) = node.child_by_field_name("field") else {
8180 return;
8181 };
8182 if node_text(field, ctx.source) != ctx.spec.member_name {
8183 return;
8184 }
8185 *ctx.raw_match_count += 1;
8186 if !ctx.visibility.declaration_guards_compatible_at_reference(
8187 &ctx.analyzer,
8188 ctx.file,
8189 &ctx.spec.target,
8190 field,
8191 ) {
8192 return;
8193 }
8194 let receiver = node
8195 .child_by_field_name("argument")
8196 .or_else(|| node.child_by_field_name("object"));
8197 let receiver_resolution =
8198 receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx));
8199 match receiver_resolution {
8200 Some(MethodReceiverTargetResolution::Target)
8201 if ctx.visibility.declaration_visible_at_reference(
8202 &ctx.analyzer,
8203 ctx.file,
8204 &ctx.spec.target,
8205 field,
8206 ) || (!ctx.analyzer.reference_uses_c_semantics(ctx.file)
8207 && ctx.spec.target.source() == ctx.file
8208 && ctx
8209 .visibility
8210 .external_type_candidate_guard_compatible_in_context(
8211 &ctx.analyzer,
8212 ctx.file,
8213 &ctx.spec.target,
8214 field,
8215 )) =>
8216 {
8217 push_hit(field, ctx)
8221 }
8222 Some(
8223 MethodReceiverTargetResolution::Target | MethodReceiverTargetResolution::Missing,
8224 )
8225 | None => push_unproven_hit(field, ctx),
8226 Some(
8227 MethodReceiverTargetResolution::NonTarget
8228 | MethodReceiverTargetResolution::Ambiguous,
8229 ) => {}
8230 }
8231 return;
8232 }
8233
8234 if matches!(node.kind(), "identifier" | "field_identifier")
8235 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8236 && let Some(designator_owner) =
8237 designated_initializer_owner(&ctx.analyzer, ctx.visibility, ctx.file, ctx.source, node)
8238 {
8239 *ctx.raw_match_count += 1;
8240 match designator_owner {
8241 DesignatedInitializerOwner::Resolved(owner)
8242 if ctx
8243 .spec
8244 .owner
8245 .as_ref()
8246 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
8247 {
8248 push_hit(node, ctx);
8249 }
8250 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
8251 DesignatedInitializerOwner::Resolved(_) => {}
8252 }
8253 return;
8254 }
8255
8256 let qualified_member_name_matches =
8257 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8258 && cpp_name_component_nodes(node)
8259 .and_then(|components| components.last().copied())
8260 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
8261 if !matches!(
8262 node.kind(),
8263 "identifier"
8264 | "field_identifier"
8265 | "qualified_identifier"
8266 | "scoped_identifier"
8267 | "type_identifier"
8268 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
8269 && !qualified_member_name_matches)
8270 || !scan_leaf_is_value_position(node, ctx)
8271 || is_declaration_name(node)
8272 || is_member_field_own_declarator(node, ctx)
8273 || is_selected_field_expression_member_descendant(node)
8274 || is_nested_in_qualified_identifier(node)
8275 {
8276 return;
8277 }
8278 *ctx.raw_match_count += 1;
8279 if is_structurally_qualified(node) {
8280 match qualified_owner_resolution(node, ctx) {
8281 QualifiedOwnerResolution::Target => push_hit(node, ctx),
8282 QualifiedOwnerResolution::NonTarget => {}
8283 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
8284 }
8285 return;
8286 }
8287 let text = node_text(node, ctx.source);
8288 if ctx.local_shadows.is_shadowed(text) {
8289 return;
8290 }
8291 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
8292 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
8293 let owner_context = structured_owner_context_resolution(node, ctx);
8294 if matches!(
8295 owner_context,
8296 StructuredOwnerContextResolution::SelfTarget
8297 | StructuredOwnerContextResolution::InheritedTarget
8298 ) || unscoped_enum_match
8299 {
8300 push_hit(node, ctx);
8301 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
8302 .then_some(ctx.spec.owner.as_ref())
8303 .flatten()
8304 {
8305 let resolution =
8306 match resolve_active_using_enum_member(node, ctx) {
8307 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
8308 ActiveUsingEnumMemberResolution::Class(resolution) => {
8309 if direct_class_member_shadows(node, ctx) {
8310 return;
8311 }
8312 resolution
8313 }
8314 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
8315 if let Some(owner) = structured_enclosing_owner(node, ctx) {
8316 if direct_class_member_shadows(node, ctx) {
8317 return;
8318 }
8319 let complete_same_file_leaf =
8320 owner.source() == ctx.file
8321 && ctx.analyzer.type_hierarchy_provider().is_some_and(
8322 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
8323 );
8324 if !complete_same_file_leaf {
8325 push_unproven_hit(node, ctx);
8326 return;
8327 }
8328 }
8329 match owner_context {
8330 StructuredOwnerContextResolution::SelfTarget
8331 | StructuredOwnerContextResolution::InheritedTarget
8332 | StructuredOwnerContextResolution::NonTarget => return,
8333 StructuredOwnerContextResolution::Ambiguous => {
8334 push_unproven_hit(node, ctx);
8335 return;
8336 }
8337 StructuredOwnerContextResolution::Missing => {}
8338 }
8339 if namespace_value_shadows(node, ctx) {
8340 return;
8341 }
8342 resolution
8343 }
8344 ActiveUsingEnumMemberResolution::Missing => {
8345 if direct_class_member_shadows(node, ctx)
8346 || (structured_enclosing_owner(node, ctx).is_none()
8347 && namespace_value_shadows(node, ctx))
8348 {
8349 return;
8350 }
8351 UsingEnumMemberResolution::Missing
8352 }
8353 };
8354 match resolution {
8355 UsingEnumMemberResolution::Resolved { owner, member }
8356 if same_visible_symbol(&owner, target_owner)
8357 && same_visible_symbol(&member, &ctx.spec.target) =>
8358 {
8359 push_hit(node, ctx);
8360 }
8361 UsingEnumMemberResolution::Resolved { .. } => {}
8362 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
8363 push_unproven_hit(node, ctx)
8364 }
8365 }
8366 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
8367 push_unproven_hit(node, ctx);
8368 }
8369}
8370
8371fn maybe_record_c_offsetof_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
8372 if node_text(node, ctx.source) != ctx.spec.member_name {
8373 return;
8374 }
8375 *ctx.raw_match_count += 1;
8376 let Some((type_reference, member)) = c_offsetof_member_parts(node) else {
8377 push_unproven_hit(node, ctx);
8378 return;
8379 };
8380 let owner = match resolve_type_node_lexically(
8381 type_reference,
8382 &ctx.analyzer,
8383 ctx.visibility,
8384 &ctx.ordinary_type_imports,
8385 ctx.file,
8386 ctx.source,
8387 ) {
8388 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => unit,
8389 LexicalTypeResolution::Resolved { .. }
8390 | LexicalTypeResolution::Ambiguous
8391 | LexicalTypeResolution::Missing => {
8392 push_unproven_hit(member, ctx);
8393 return;
8394 }
8395 };
8396 let candidates = ctx
8397 .visibility
8398 .visible_members_for_owner_name(ctx.file, &owner, ctx.spec.member_name.as_str())
8399 .into_iter()
8400 .filter(|candidate| candidate.is_field())
8401 .collect::<Vec<_>>();
8402 if candidates.len() == 1 && ctx.target_group.contains(candidates[0]) {
8403 push_hit(member, ctx);
8404 } else if candidates.len() > 1 {
8405 push_unproven_hit(member, ctx);
8406 }
8407}
8408
8409enum ActiveUsingEnumMemberResolution {
8410 Block(UsingEnumMemberResolution),
8411 Class(UsingEnumMemberResolution),
8412 Namespace(UsingEnumMemberResolution),
8413 Missing,
8414}
8415
8416fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8417 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
8418 ctx.visibility
8419 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
8420 .into_iter()
8421 .next()
8422 .is_some()
8423 })
8424}
8425
8426fn resolve_active_using_enum_member(
8427 node: Node<'_>,
8428 ctx: &ScanCtx<'_>,
8429) -> ActiveUsingEnumMemberResolution {
8430 let block =
8431 ctx.using_enum_owners
8432 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
8433 if !matches!(block, UsingEnumMemberResolution::Missing) {
8434 return ActiveUsingEnumMemberResolution::Block(block);
8435 }
8436 let class = structured_enclosing_owner(node, ctx);
8437 let namespace = enclosing_namespace_components(node, ctx.source);
8438 match ctx.semantic_using_enum_owners.resolve_member(
8439 ctx.visibility,
8440 ctx.file,
8441 class.as_ref(),
8442 &namespace,
8443 node.start_byte(),
8444 &ctx.spec.member_name,
8445 ) {
8446 SemanticUsingEnumMemberResolution::Class(resolution) => {
8447 ActiveUsingEnumMemberResolution::Class(resolution)
8448 }
8449 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
8450 ActiveUsingEnumMemberResolution::Namespace(resolution)
8451 }
8452 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
8453 }
8454}
8455
8456fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
8457 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
8458 !matches!(
8459 resolve_namespace_value(
8460 &ctx.analyzer,
8461 ctx.visibility,
8462 ctx.file,
8463 &namespace,
8464 &ctx.spec.member_name,
8465 node.start_byte(),
8466 ),
8467 NamespaceValueResolution::Missing
8468 )
8469}
8470
8471fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
8493 let mut current = node;
8494 while let Some(parent) = current.parent() {
8495 let on_name_path = ["scope", "name"].into_iter().any(|field| {
8496 parent
8497 .child_by_field_name(field)
8498 .is_some_and(|component| same_node(component, current))
8499 });
8500 if !on_name_path {
8501 return false;
8502 }
8503 if parent.kind() == "qualified_identifier" {
8504 return true;
8505 }
8506 current = parent;
8507 }
8508 false
8509}
8510
8511fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
8512 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
8513}
8514
8515pub enum QualifiedReceiverBase {
8522 Variable(Vec<CodeUnit>),
8524 Type(Option<CodeUnit>),
8526 Ambiguous,
8528}
8529
8530pub fn qualified_receiver_base(
8531 visibility: &VisibilityIndex<'_>,
8532 file: &ProjectFile,
8533 receiver: Node<'_>,
8534 source: &str,
8535) -> QualifiedReceiverBase {
8536 let reference = node_text(receiver, source);
8537 let fields = visibility
8538 .named_candidates(file, reference, TargetKind::GlobalField)
8539 .into_iter()
8540 .filter(has_persisted_global_field_identity)
8541 .collect::<Vec<_>>();
8542 let Some(first) = fields.first() else {
8543 return QualifiedReceiverBase::Type(visibility.resolve_type(file, reference));
8544 };
8545 if fields
8546 .iter()
8547 .skip(1)
8548 .any(|field| !same_visible_symbol(first, field))
8549 {
8550 return QualifiedReceiverBase::Ambiguous;
8551 }
8552 QualifiedReceiverBase::Variable(fields)
8553}
8554
8555fn receiver_type_units_with_budget(
8556 node: Node<'_>,
8557 source: &str,
8558 ctx: &ScanCtx<'_>,
8559 remaining_call_depth: usize,
8560) -> Vec<CodeUnit> {
8561 let mut current = node;
8562 let mut member_chain = Vec::new();
8563 let mut base_units = loop {
8564 match current.kind() {
8565 "field_expression" => {
8566 let Some(member) = current.child_by_field_name("field") else {
8567 return Vec::new();
8568 };
8569 let Some(receiver) = current
8570 .child_by_field_name("argument")
8571 .or_else(|| current.child_by_field_name("object"))
8572 .or_else(|| current.named_child(0))
8573 else {
8574 return Vec::new();
8575 };
8576 member_chain.push(node_text(member, source));
8577 current = receiver;
8578 }
8579 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
8580 let Some(inner) = current
8581 .child_by_field_name("argument")
8582 .or_else(|| current.named_child(0))
8583 else {
8584 return Vec::new();
8585 };
8586 current = inner;
8587 }
8588 "identifier" | "field_identifier" => {
8595 let name = node_text(current, source);
8596 let mut root = current;
8601 while let Some(parent) = ctx.ancestry.parent(root) {
8602 root = parent;
8603 }
8604 if let Some(binding) = ctx.visibility.macro_local_binding_at(
8605 ctx.file,
8606 root,
8607 source,
8608 current.start_byte(),
8609 current.end_byte(),
8610 ) && binding.name == name
8611 {
8612 let normalized = normalize_cpp_type_name(&binding.type_name);
8613 let unit = binding
8614 .proven_unit
8615 .clone()
8616 .or_else(|| {
8617 binding.type_node.and_then(|type_node| {
8618 resolve_receiver_type_node(type_node, ctx).ok().flatten()
8619 })
8620 })
8621 .or_else(|| receiver_type_name_unit(current, &normalized, ctx));
8622 break unit.into_iter().collect();
8626 }
8627 let local = ctx.bindings.resolve_symbol(name);
8628 if let Some(bindings) = local.as_precise() {
8629 break receiver_units_from_bindings(current, bindings, ctx);
8630 }
8631 if ctx.bindings.is_shadowed(name) {
8632 return Vec::new();
8633 }
8634 let owner = structured_enclosing_owner(current, ctx)
8635 .filter(CodeUnit::is_class)
8636 .or_else(|| {
8637 enclosing_context(current, ctx)
8638 .owner
8639 .filter(CodeUnit::is_class)
8640 });
8641 if let Some(owner) = owner {
8642 let declaring_owner = match resolve_declaring_member_owner(
8647 &ctx.analyzer,
8648 ctx.visibility,
8649 ctx.file,
8650 &owner,
8651 name,
8652 ) {
8653 EnclosingMemberOwnerResolution::Owner(owner) => Some(owner),
8654 EnclosingMemberOwnerResolution::Missing => None,
8655 EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8656 };
8657 if let Some(declaring_owner) = declaring_owner {
8658 let implicit_fields = ctx
8659 .visibility
8660 .visible_members_for_owner_name(ctx.file, &declaring_owner, name)
8661 .into_iter()
8662 .filter(|unit| unit.is_field())
8663 .collect::<Vec<_>>();
8664 if !implicit_fields.is_empty() {
8665 break receiver_units_from_declared_fields(
8666 implicit_fields,
8667 current,
8668 ctx,
8669 );
8670 }
8671 }
8672 }
8673 let global_fields = ctx
8674 .visibility
8675 .visible_identifier_candidates(ctx.file, name)
8676 .filter(|unit| {
8677 has_persisted_global_field_identity(unit) && unit.identifier() == name
8678 })
8679 .collect::<Vec<_>>();
8680 if global_fields.is_empty() {
8681 break ctx
8682 .visibility
8683 .resolve_type(ctx.file, name)
8684 .into_iter()
8685 .collect();
8686 }
8687 if let Some(first) = global_fields.first()
8688 && global_fields.iter().skip(1).any(|field| {
8689 !same_visible_global_field_symbol(
8690 &ctx.analyzer,
8691 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
8692 first,
8693 field,
8694 )
8695 })
8696 {
8697 return Vec::new();
8698 }
8699 break receiver_units_from_declared_fields(global_fields, current, ctx);
8700 }
8701 "call_expression" | "new_expression" => {
8702 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
8703 .and_then(|binding| binding.unit)
8704 .into_iter()
8705 .collect();
8706 }
8707 "cast_expression" => {
8711 let Some(descriptor) = current.child_by_field_name("type") else {
8712 return Vec::new();
8713 };
8714 let Ok(unit) = resolve_receiver_type_node(descriptor, ctx) else {
8715 return Vec::new();
8716 };
8717 break unit.into_iter().collect();
8718 }
8719 "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
8720 let name = node_text(current, source);
8721 let local = ctx.bindings.resolve_symbol(name);
8722 if let Some(bindings) = local.as_precise() {
8723 break receiver_units_from_bindings(current, bindings, ctx);
8724 }
8725 return Vec::new();
8726 }
8727 "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
8728 "qualified_identifier" | "scoped_identifier" => {
8729 match qualified_receiver_base(ctx.visibility, ctx.file, current, source) {
8730 QualifiedReceiverBase::Variable(fields) => {
8731 break receiver_units_from_declared_fields(
8732 fields.iter().collect(),
8733 current,
8734 ctx,
8735 );
8736 }
8737 QualifiedReceiverBase::Type(unit) => break unit.into_iter().collect(),
8738 QualifiedReceiverBase::Ambiguous => return Vec::new(),
8739 }
8740 }
8741 _ => {
8742 break ctx
8743 .visibility
8744 .resolve_type(ctx.file, node_text(current, source))
8745 .into_iter()
8746 .collect();
8747 }
8748 }
8749 };
8750
8751 base_units = canonical_receiver_units(base_units, ctx);
8752 if base_units.is_empty() {
8753 return Vec::new();
8754 }
8755
8756 while let Some(member_name) = member_chain.pop() {
8757 let mut next_units = Vec::new();
8758 for owner in &base_units {
8759 let declaring_owner = match resolve_declaring_member_owner(
8760 &ctx.analyzer,
8761 ctx.visibility,
8762 ctx.file,
8763 owner,
8764 member_name,
8765 ) {
8766 EnclosingMemberOwnerResolution::Owner(owner) => owner,
8767 EnclosingMemberOwnerResolution::Missing => continue,
8768 EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
8769 };
8770 let fields = ctx.visibility.visible_members_for_owner_name(
8771 ctx.file,
8772 &declaring_owner,
8773 member_name,
8774 );
8775 for field in fields.into_iter().filter(|unit| unit.is_field()) {
8776 let Some(unit) =
8777 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
8778 .and_then(|binding| binding.unit)
8779 .or_else(|| recovered_receiver_field_type(current, field, ctx))
8780 else {
8781 continue;
8782 };
8783 if !next_units
8784 .iter()
8785 .any(|existing| same_visible_symbol(existing, &unit))
8786 {
8787 next_units.push(unit);
8788 }
8789 }
8790 }
8791 if next_units.is_empty() {
8792 return Vec::new();
8793 }
8794 base_units = unanimous_receiver_units(next_units);
8795 if base_units.is_empty() {
8796 return Vec::new();
8797 }
8798 }
8799 base_units
8800}
8801
8802fn receiver_units_from_bindings(
8803 node: Node<'_>,
8804 bindings: &HashSet<CppScanBinding>,
8805 ctx: &ScanCtx<'_>,
8806) -> Vec<CodeUnit> {
8807 let mut units = Vec::new();
8808 for binding in bindings {
8809 let raw_unit = if let Some(unit) = &binding.unit {
8810 unit.clone()
8811 } else {
8812 let Some(type_name) = binding.type_name.as_deref() else {
8813 return Vec::new();
8814 };
8815 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
8816 return Vec::new();
8817 };
8818 unit
8819 };
8820 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
8821 if same_visible_symbol(&unit, &raw_unit)
8827 && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
8828 {
8829 units.push(recovered);
8830 continue;
8831 }
8832 units.push(unit);
8833 continue;
8834 }
8835 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
8836 units.push(unit);
8837 continue;
8838 }
8839 return Vec::new();
8840 }
8841 unanimous_receiver_units(units)
8842}
8843
8844fn recovered_receiver_alias_target(
8849 reference: Node<'_>,
8850 alias: &CodeUnit,
8851 ctx: &ScanCtx<'_>,
8852) -> Option<CodeUnit> {
8853 if !ctx
8854 .analyzer
8855 .type_alias_provider()
8856 .is_some_and(|provider| provider.is_type_alias(alias))
8857 {
8858 return None;
8859 }
8860 let target = ctx.spec.owner.as_ref()?.clone();
8861 if !target.is_class() || alias.source() != ctx.file {
8862 return None;
8863 }
8864 let range = ctx
8865 .analyzer
8866 .ranges(alias)
8867 .into_iter()
8868 .find(|range| range.start_byte < range.end_byte)?;
8869 let mut node =
8870 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
8871 while !matches!(node.kind(), "alias_declaration" | "type_definition") {
8872 node = ctx.ancestry.parent(node)?;
8873 }
8874 let type_descriptor = node.child_by_field_name("type")?;
8875 let type_node = receiver_type_node_base(type_descriptor);
8876 let resolution = resolve_type_node_lexically_for_target(
8877 type_node,
8878 &ctx.analyzer,
8879 ctx.visibility,
8880 &ctx.ordinary_type_imports,
8881 ctx.file,
8882 ctx.source,
8883 &target,
8884 Some(&ctx.lexical_scope_cache),
8885 ctx.recovered_sentinel_scope(type_node).as_deref(),
8886 );
8887 if let LexicalTypeResolution::Resolved {
8888 unit, candidates, ..
8889 } = resolution
8890 && (same_visible_symbol(&unit, &target)
8891 || candidates
8892 .iter()
8893 .any(|candidate| same_visible_symbol(candidate, &target)))
8894 {
8895 return Some(target);
8896 }
8897 let (components, global) = type_reference_components(type_node, ctx.source)?;
8898 if !global
8899 && components.len() == 2
8900 && cpp_active_template_type_parameter(type_node, &components[0], ctx.source, &ctx.ancestry)
8901 && dependent_nested_alias_concrete_target(
8902 &ctx.analyzer,
8903 ctx.visibility,
8904 ctx.file,
8905 alias,
8906 &components[1],
8907 type_node,
8908 )
8909 .is_some_and(|unit| same_visible_symbol(&unit, &target))
8910 {
8911 return Some(target);
8912 }
8913 let scope = ctx
8914 .recovered_sentinel_scope(type_node)
8915 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
8916 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
8917 let visible = ctx.visibility.external_type_candidate_visible_in_context(
8918 &ctx.analyzer,
8919 ctx.file,
8920 &target,
8921 type_node,
8922 );
8923 (path_matches && visible).then_some(target)
8924}
8925
8926pub fn dependent_nested_alias_concrete_target(
8933 analyzer: &CppGraphSource<'_>,
8934 visibility: &VisibilityIndex<'_>,
8935 file: &ProjectFile,
8936 alias: &CodeUnit,
8937 nested_name: &str,
8938 type_node: Node<'_>,
8939) -> Option<CodeUnit> {
8940 let alias_provider = analyzer.type_alias_provider()?;
8941 let concrete = visibility
8942 .visible_identifier_candidates(file, nested_name)
8943 .filter(|candidate| {
8944 alias_provider.is_type_alias(candidate)
8945 && !same_visible_symbol(candidate, alias)
8946 && type_owner_of(analyzer, candidate).is_some_and(|owner| owner.is_class())
8947 && visibility.is_physically_visible(file, candidate)
8948 && visibility.external_type_candidate_guard_compatible_in_context(
8949 analyzer, file, candidate, type_node,
8950 )
8951 })
8952 .filter_map(|candidate| {
8953 let canonical =
8954 visibility.canonical_visible_full_type_unit(analyzer, file, candidate)?;
8955 (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
8960 })
8961 .collect::<Vec<_>>();
8962 match unanimous_receiver_units(concrete).as_slice() {
8963 [unit] => Some(unit.clone()),
8964 _ => None,
8965 }
8966}
8967
8968fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8969 let normalized = normalize_cpp_type_name(type_name);
8970 if normalized.is_empty() {
8971 return None;
8972 }
8973
8974 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
8979 match resolve_receiver_type_node(alias_type, ctx) {
8980 Ok(Some(unit)) => return Some(unit),
8981 Err(_) => return None,
8982 Ok(None) => {}
8983 }
8984 }
8985
8986 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
8987 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
8988 LexicalTypeResolution::Ambiguous => return None,
8989 LexicalTypeResolution::Missing => {}
8990 }
8991 let candidates = ctx
8992 .visibility
8993 .type_name_candidates(ctx.file, &normalized)
8994 .into_iter()
8995 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
8996 .collect();
8997 unanimous_receiver_units(candidates).into_iter().next()
8998}
8999
9000fn resolve_receiver_type_node(
9008 type_node: Node<'_>,
9009 ctx: &ScanCtx<'_>,
9010) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
9011 let type_node = receiver_type_node_base(type_node);
9012 if let Some(unit) = ctx
9013 .visibility
9014 .resolve_type_node_result(ctx.file, type_node, ctx.source)?
9015 {
9016 return Ok(Some(unit));
9017 }
9018 Ok(resolve_receiver_type_node_lexically(type_node, ctx))
9019}
9020
9021fn resolve_receiver_type_node_lexically(
9022 type_node: Node<'_>,
9023 ctx: &ScanCtx<'_>,
9024) -> Option<CodeUnit> {
9025 let type_node = receiver_type_node_base(type_node);
9026 let components = cpp_type_name_components(type_node, ctx.source)?;
9027 let lexical_scope = match enclosing_lexical_scope_components(
9028 type_node,
9029 &ctx.analyzer,
9030 ctx.visibility,
9031 ctx.file,
9032 ctx.source,
9033 ) {
9034 LexicalScopeResolution::Resolved(scope) => scope,
9035 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
9036 };
9037 match ctx.visibility.resolve_type_components_lexically(
9038 &ctx.analyzer,
9039 ctx.file,
9040 &components,
9041 is_globally_qualified_cpp_name(type_node),
9042 &lexical_scope,
9043 ) {
9044 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
9045 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9046 }
9047}
9048
9049fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
9050 while matches!(node.kind(), "type_descriptor" | "dependent_type") {
9051 let Some(inner) = node.child_by_field_name("type").or_else(|| {
9052 if node.kind() == "dependent_type" {
9053 node.named_child(0)
9054 } else {
9055 None
9056 }
9057 }) else {
9058 break;
9059 };
9060 node = inner;
9061 }
9062 node
9063}
9064
9065fn resolve_receiver_type_name_lexically(
9066 node: Node<'_>,
9067 normalized: &str,
9068 ctx: &ScanCtx<'_>,
9069) -> LexicalTypeResolution {
9070 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9071 brokk_bifrost_core::analyzer::Language::Cpp,
9072 normalized,
9073 );
9074 if components.is_empty() {
9075 return LexicalTypeResolution::Missing;
9076 }
9077 let lexical_scope = match enclosing_lexical_scope_components(
9078 node,
9079 &ctx.analyzer,
9080 ctx.visibility,
9081 ctx.file,
9082 ctx.source,
9083 ) {
9084 LexicalScopeResolution::Resolved(scope) => scope,
9085 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9086 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9087 };
9088 ctx.visibility.resolve_type_components_lexically(
9089 &ctx.analyzer,
9090 ctx.file,
9091 &components,
9092 normalized.starts_with("::"),
9093 &lexical_scope,
9094 )
9095}
9096
9097fn local_receiver_alias_type_node<'tree>(
9098 node: Node<'tree>,
9099 name: &str,
9100 ctx: &ScanCtx<'_>,
9101) -> Option<Node<'tree>> {
9102 let callable = nearest_callable_scope(node)?;
9103 let mut root_callable = callable;
9104 let mut ancestor = callable.parent();
9107 while let Some(current) = ancestor {
9108 if matches!(current.kind(), "function_definition" | "lambda_expression") {
9109 root_callable = current;
9110 }
9111 ancestor = current.parent();
9112 }
9113
9114 let mut stack = vec![root_callable];
9115 let mut best = None;
9116 while let Some(current) = stack.pop() {
9117 if current.start_byte() >= node.start_byte() {
9118 continue;
9119 }
9120 if local_type_alias_name_node(current)
9121 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
9122 && current
9123 .parent()
9124 .is_none_or(|parent| parent.kind() != "template_declaration")
9125 && has_function_scope_ancestor(current)
9126 && local_alias_scope_contains_node(current, node)
9127 {
9128 let replace = best
9129 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
9130 if replace {
9131 best = current.child_by_field_name("type");
9132 }
9133 }
9134 let mut cursor = current.walk();
9135 stack.extend(current.named_children(&mut cursor));
9136 }
9137 best
9138}
9139
9140fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
9141 let mut canonical = Vec::with_capacity(units.len());
9142 for unit in units {
9143 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
9144 return Vec::new();
9145 };
9146 canonical.push(unit);
9147 }
9148 unanimous_receiver_units(canonical)
9149}
9150
9151fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9152 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
9153 return cached.clone();
9154 }
9155 let canonical = ctx
9156 .visibility
9157 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
9158 ctx.receiver_canonical_type_cache
9159 .borrow_mut()
9160 .insert(unit.clone(), canonical.clone());
9161 canonical
9162}
9163
9164fn receiver_units_from_declared_fields(
9165 fields: Vec<&CodeUnit>,
9166 reference: Node<'_>,
9167 ctx: &ScanCtx<'_>,
9168) -> Vec<CodeUnit> {
9169 let Some(first) = fields.first() else {
9170 return Vec::new();
9171 };
9172 if fields
9173 .iter()
9174 .skip(1)
9175 .any(|field| !same_visible_symbol(first, field))
9176 {
9177 return Vec::new();
9178 }
9179 unanimous_receiver_units(
9180 fields
9181 .into_iter()
9182 .filter_map(|field| {
9183 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
9184 .and_then(|binding| binding.unit)
9185 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
9186 })
9187 .collect(),
9188 )
9189}
9190
9191fn recovered_receiver_field_type(
9196 reference: Node<'_>,
9197 field: &CodeUnit,
9198 ctx: &ScanCtx<'_>,
9199) -> Option<CodeUnit> {
9200 let target = ctx.spec.owner.as_ref()?.clone();
9201 if !target.is_class() || field.source() != ctx.file {
9202 return None;
9203 }
9204 let range = ctx
9205 .analyzer
9206 .ranges(field)
9207 .into_iter()
9208 .find(|range| range.start_byte < range.end_byte)?;
9209 let mut declaration =
9210 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
9211 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
9212 declaration = ctx.ancestry.parent(declaration)?;
9213 }
9214 let type_node = first_type_child(declaration)?;
9215 let resolution = resolve_type_node_lexically_for_target(
9216 type_node,
9217 &ctx.analyzer,
9218 ctx.visibility,
9219 &ctx.ordinary_type_imports,
9220 ctx.file,
9221 ctx.source,
9222 &target,
9223 Some(&ctx.lexical_scope_cache),
9224 ctx.recovered_sentinel_scope(type_node).as_deref(),
9225 );
9226 if let LexicalTypeResolution::Resolved {
9227 unit, candidates, ..
9228 } = resolution
9229 && (same_visible_symbol(&unit, &target)
9230 || candidates
9231 .iter()
9232 .any(|candidate| same_visible_symbol(candidate, &target)))
9233 {
9234 return Some(target);
9235 }
9236 let type_node = receiver_type_node_base(type_node);
9237 let (components, global) = type_reference_components(type_node, ctx.source)?;
9238 let scope = ctx
9239 .recovered_sentinel_scope(type_node)
9240 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
9241 (indexed_scope_matches_target_name(&scope, &components, global, &target)
9242 && ctx.visibility.external_type_candidate_visible_in_context(
9243 &ctx.analyzer,
9244 ctx.file,
9245 &target,
9246 type_node,
9247 ))
9248 .then_some(target)
9249}
9250
9251fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
9252 let mut unique = Vec::new();
9253 for unit in units {
9254 if !unique
9255 .iter()
9256 .any(|existing| same_visible_symbol(existing, &unit))
9257 {
9258 unique.push(unit);
9259 if unique.len() > 1 {
9260 return Vec::new();
9261 }
9262 }
9263 }
9264 unique
9265}
9266
9267fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9268 let Some(owner) = ctx.spec.owner.as_ref() else {
9269 return false;
9270 };
9271 match node.kind() {
9272 "field_expression" => false,
9279 "call_expression" => node
9280 .child_by_field_name("function")
9281 .is_some_and(|function| receiver_matches_target(function, ctx)),
9282 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9283 .child_by_field_name("argument")
9284 .or_else(|| node.named_child(0))
9285 .is_some_and(|child| receiver_matches_target(child, ctx)),
9286 "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9287 .bindings
9288 .resolve_symbol(node_text(node, ctx.source))
9289 .as_precise()
9290 .is_some_and(|targets| {
9291 targets
9292 .iter()
9293 .filter_map(|target| target.unit.as_ref())
9294 .any(|target| {
9295 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
9296 })
9297 }),
9298 "this" => same_owner_context(node, ctx),
9299 _ => qualified_owner_matches(node, ctx),
9300 }
9301}
9302
9303fn declaring_owner_for_explicit_receiver(
9304 receiver: Node<'_>,
9305 call_arity: Option<usize>,
9306 ctx: &ScanCtx<'_>,
9307) -> EnclosingMemberOwnerResolution {
9308 if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
9309 return EnclosingMemberOwnerResolution::Missing;
9310 }
9311 declaring_owner_from_receiver_units(
9312 receiver_type_units(receiver, ctx.source, ctx),
9313 receiver.start_byte(),
9314 call_arity,
9315 ctx,
9316 )
9317}
9318
9319fn declaring_owner_from_receiver_units(
9326 receiver_units: Vec<CodeUnit>,
9327 reference_byte: usize,
9328 call_arity: Option<usize>,
9329 ctx: &ScanCtx<'_>,
9330) -> EnclosingMemberOwnerResolution {
9331 let mut declaring_owner = None;
9332 for receiver_owner in receiver_units {
9333 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
9334 receiver_owner_matches_target(&receiver_owner, target_owner, reference_byte, ctx)
9335 }) {
9336 if declaring_owner
9337 .as_ref()
9338 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
9339 {
9340 return EnclosingMemberOwnerResolution::Ambiguous;
9341 }
9342 declaring_owner = Some(receiver_owner);
9343 continue;
9344 }
9345 let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
9346 let owner_resolution = match call_arity {
9347 Some(arity) => resolve_declaring_callable_owner(
9348 &ctx.analyzer,
9349 ctx.visibility,
9350 ctx.file,
9351 ordinary,
9352 &ctx.spec.member_name,
9353 arity,
9354 ),
9355 None => ordinary,
9356 };
9357 match owner_resolution {
9358 EnclosingMemberOwnerResolution::Owner(owner) => {
9359 if declaring_owner
9360 .as_ref()
9361 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
9362 {
9363 return EnclosingMemberOwnerResolution::Ambiguous;
9364 }
9365 declaring_owner = Some(owner);
9366 }
9367 EnclosingMemberOwnerResolution::Ambiguous => {
9368 return EnclosingMemberOwnerResolution::Ambiguous;
9369 }
9370 EnclosingMemberOwnerResolution::Missing => {}
9371 }
9372 }
9373 declaring_owner
9374 .map(EnclosingMemberOwnerResolution::Owner)
9375 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
9376}
9377
9378fn declaring_owner_from_call_function(
9379 function: Node<'_>,
9380 call_arity: Option<usize>,
9381 ctx: &ScanCtx<'_>,
9382) -> Option<EnclosingMemberOwnerResolution> {
9383 match function.kind() {
9384 "field_expression" => function
9385 .child_by_field_name("argument")
9386 .or_else(|| function.child_by_field_name("object"))
9387 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
9388 .or(Some(EnclosingMemberOwnerResolution::Missing)),
9389 "call_expression" => function
9390 .child_by_field_name("function")
9391 .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
9392 _ => None,
9393 }
9394}
9395
9396enum MethodReceiverTargetResolution {
9397 Target,
9398 NonTarget,
9399 Ambiguous,
9400 Missing,
9401}
9402
9403fn method_receiver_target_resolution(
9404 node: Node<'_>,
9405 declaring_owner: EnclosingMemberOwnerResolution,
9406 ctx: &ScanCtx<'_>,
9407) -> MethodReceiverTargetResolution {
9408 match declaring_owner {
9409 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Ambiguous => {
9410 declaring_owner_target_resolution(declaring_owner, node.start_byte(), ctx)
9411 }
9412 EnclosingMemberOwnerResolution::Missing if ctx.spec.owner.is_none() => {
9413 MethodReceiverTargetResolution::Missing
9414 }
9415 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
9416 MethodReceiverTargetResolution::Target
9417 }
9418 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
9419 MethodReceiverTargetResolution::NonTarget
9420 }
9421 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
9422 }
9423}
9424
9425fn declaring_owner_target_resolution(
9432 declaring_owner: EnclosingMemberOwnerResolution,
9433 reference_byte: usize,
9434 ctx: &ScanCtx<'_>,
9435) -> MethodReceiverTargetResolution {
9436 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9437 return MethodReceiverTargetResolution::Missing;
9438 };
9439 match declaring_owner {
9440 EnclosingMemberOwnerResolution::Owner(owner)
9441 if receiver_owner_matches_target(&owner, target_owner, reference_byte, ctx) =>
9442 {
9443 MethodReceiverTargetResolution::Target
9444 }
9445 EnclosingMemberOwnerResolution::Owner(owner)
9446 if receiver_owner_is_known_non_target(&owner, target_owner, reference_byte, ctx) =>
9447 {
9448 MethodReceiverTargetResolution::NonTarget
9449 }
9450 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {
9451 MethodReceiverTargetResolution::Missing
9452 }
9453 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
9454 }
9455}
9456
9457fn explicit_receiver_target_resolution(
9458 receiver: Node<'_>,
9459 call_arity: Option<usize>,
9460 ctx: &ScanCtx<'_>,
9461) -> MethodReceiverTargetResolution {
9462 method_receiver_target_resolution(
9463 receiver,
9464 declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
9465 ctx,
9466 )
9467}
9468
9469fn call_function_target_resolution(
9470 function: Node<'_>,
9471 ctx: &ScanCtx<'_>,
9472) -> MethodReceiverTargetResolution {
9473 let call_arity = ctx.ancestry.parent(function).and_then(|call| {
9474 (call.kind() == "call_expression")
9475 .then(|| {
9476 ctx.visibility
9477 .call_arity_evidence(ctx.file, call, ctx.source)
9478 .exact()
9479 })
9480 .flatten()
9481 });
9482 let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
9483 else {
9484 return MethodReceiverTargetResolution::Missing;
9488 };
9489 method_receiver_target_resolution(function, declaring_owner, ctx)
9490}
9491
9492fn receiver_owner_matches_target(
9493 receiver_owner: &CodeUnit,
9494 target_owner: &CodeUnit,
9495 reference_byte: usize,
9496 ctx: &ScanCtx<'_>,
9497) -> bool {
9498 same_symbol(receiver_owner, target_owner)
9499 || same_logical_symbol(receiver_owner, target_owner)
9500 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
9501 || (ctx.spec.owner_is_forward_declaration
9502 && ctx
9503 .visibility
9504 .is_physically_visible(ctx.file, receiver_owner))
9505 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
9506 || target_group_contains_owner_peer(receiver_owner, ctx)
9507 || ctx.visibility.same_logical_class_definition(
9511 &ctx.analyzer,
9512 receiver_owner,
9513 target_owner,
9514 ))
9515}
9516
9517fn receiver_owner_is_known_non_target(
9518 receiver_owner: &CodeUnit,
9519 target_owner: &CodeUnit,
9520 reference_byte: usize,
9521 ctx: &ScanCtx<'_>,
9522) -> bool {
9523 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
9524 return false;
9525 }
9526 if !same_logical_symbol(receiver_owner, target_owner) {
9527 return true;
9528 }
9529 !ctx.target_group.iter().any(|target| {
9530 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
9531 })
9532}
9533
9534fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
9535 ctx.visibility
9536 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
9537 && ctx.target_group.iter().any(|target| {
9538 type_owner_of(&ctx.analyzer, target)
9539 .as_ref()
9540 .is_some_and(|target_owner| owner_peer_matches(target_owner, owner))
9541 })
9542}
9543
9544fn owner_peer_matches(candidate_owner: &CodeUnit, owner: &CodeUnit) -> bool {
9545 same_symbol(candidate_owner, owner)
9546 || (same_logical_symbol(candidate_owner, owner)
9547 && candidate_owner.source() == owner.source())
9548}
9549
9550fn visible_target_peer_matches_owner(
9551 owner: &CodeUnit,
9552 reference_byte: usize,
9553 ctx: &ScanCtx<'_>,
9554) -> bool {
9555 ctx.visibility
9556 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
9557 && ctx
9558 .visibility
9559 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
9560 .any(|candidate| {
9561 cpp_callable_definitions_share_identity_evidence(
9562 &ctx.analyzer,
9563 candidate,
9564 &ctx.spec.target,
9565 ) && ctx.visibility.declaration_visible_at(
9566 &ctx.analyzer,
9567 ctx.file,
9568 candidate,
9569 reference_byte,
9570 ) && type_owner_of(&ctx.analyzer, candidate)
9571 .as_ref()
9572 .is_some_and(|candidate_owner| owner_peer_matches(candidate_owner, owner))
9573 })
9574}
9575
9576fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
9577 match function.kind() {
9578 "field_expression" => function
9579 .child_by_field_name("argument")
9580 .or_else(|| function.child_by_field_name("object"))
9581 .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
9582 _ => receiver_is_self_like(function, reference_is_c),
9583 }
9584}
9585
9586fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9587 let Some(owner) = ctx.spec.owner.as_ref() else {
9588 return false;
9589 };
9590 match node.kind() {
9591 "field_expression" => node
9592 .child_by_field_name("argument")
9593 .or_else(|| node.child_by_field_name("object"))
9594 .is_some_and(|receiver| {
9595 if receiver_is_self_like(
9602 receiver,
9603 ctx.analyzer.reference_uses_c_semantics(ctx.file),
9604 ) {
9605 return false;
9606 }
9607 if receiver_is_known_function_declarator(receiver, ctx) {
9608 return true;
9609 }
9610 let units = receiver_type_units(receiver, ctx.source, ctx);
9611 !units.is_empty()
9612 && units.iter().all(|target| {
9613 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9614 })
9615 }),
9616 "call_expression" => node
9617 .child_by_field_name("function")
9618 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
9619 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => node
9620 .child_by_field_name("argument")
9621 .or_else(|| node.named_child(0))
9622 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
9623 "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
9624 .bindings
9625 .resolve_symbol(node_text(node, ctx.source))
9626 .as_precise()
9627 .is_some_and(|targets| {
9628 let units = targets
9629 .iter()
9630 .filter_map(|target| target.unit.as_ref())
9631 .collect::<Vec<_>>();
9632 !units.is_empty()
9633 && units.iter().all(|target| {
9634 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
9635 })
9636 }),
9637 "this" => known_non_target_owner_context(node, ctx),
9638 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
9639 qualified_owner_is_known_non_target(node, ctx)
9640 }
9641 _ => false,
9642 }
9643}
9644
9645fn receiver_is_known_function_declarator(receiver: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9646 if receiver.kind() != "identifier" {
9647 return false;
9648 }
9649 let receiver_name = node_text(receiver, ctx.source);
9650 let mut current = receiver;
9651 while let Some(parent) = ctx.ancestry.parent(current) {
9652 if parent.kind() == "compound_statement" {
9653 let mut cursor = parent.walk();
9654 for statement in parent.named_children(&mut cursor) {
9655 if statement.start_byte() >= receiver.start_byte() {
9656 break;
9657 }
9658 if statement.kind() != "declaration" || statement.end_byte() > receiver.start_byte()
9659 {
9660 continue;
9661 }
9662 let type_text = statement
9663 .child_by_field_name("type")
9664 .or_else(|| first_type_child(statement))
9665 .map(|type_node| node_text(type_node, ctx.source));
9666 let mut declaration_cursor = statement.walk();
9667 for child in statement.named_children(&mut declaration_cursor) {
9668 let declarator = if child.kind() == "init_declarator" {
9669 child.child_by_field_name("declarator")
9670 } else if is_declarator_node(child) {
9671 Some(child)
9672 } else {
9673 None
9674 };
9675 let Some(declarator) = declarator else {
9676 continue;
9677 };
9678 if declarator.kind() == "function_declarator"
9679 && extract_variable_name(declarator, ctx.source)
9680 .is_some_and(|name| name == receiver_name)
9681 && function_declarator_parameters_have_known_types(declarator, ctx)
9682 && !constructor_style_local_declaration(
9683 ctx.visibility,
9684 ctx.file,
9685 ctx.source,
9686 declarator,
9687 type_text,
9688 &ctx.bindings,
9689 )
9690 {
9691 return true;
9692 }
9693 }
9694 }
9695 return false;
9696 }
9697 current = parent;
9698 }
9699 false
9700}
9701
9702fn function_declarator_parameters_have_known_types(
9703 declarator: Node<'_>,
9704 ctx: &ScanCtx<'_>,
9705) -> bool {
9706 let Some(parameters) = declarator.child_by_field_name("parameters") else {
9707 return false;
9708 };
9709 let mut cursor = parameters.walk();
9710 parameters.named_children(&mut cursor).all(|parameter| {
9711 if !matches!(
9712 parameter.kind(),
9713 "parameter_declaration" | "optional_parameter_declaration"
9714 ) {
9715 return false;
9716 }
9717 let Some(type_node) = parameter
9718 .child_by_field_name("type")
9719 .or_else(|| first_type_child(parameter))
9720 else {
9721 return false;
9722 };
9723 type_node.kind() == "primitive_type"
9724 || ctx
9725 .visibility
9726 .resolve_type_node_result(ctx.file, type_node, ctx.source)
9727 .ok()
9728 .flatten()
9729 .is_some()
9730 })
9731}
9732
9733#[derive(Clone, Copy, PartialEq, Eq)]
9734enum QualifiedOwnerResolution {
9735 Target,
9736 NonTarget,
9737 Unresolved,
9738}
9739
9740#[derive(Clone)]
9741pub enum LexicalScopeResolution {
9742 Resolved(Vec<String>),
9743 Ambiguous,
9744 Missing,
9745}
9746
9747pub struct LexicalScopeCache {
9751 resolutions: RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>,
9752 orphaned: Arc<OrphanedNamespaceScopeIndex>,
9753}
9754
9755impl LexicalScopeCache {
9756 fn new(visibility: &VisibilityIndex<'_>, file: &ProjectFile) -> Self {
9757 Self {
9758 resolutions: RefCell::new(HashMap::default()),
9759 orphaned: orphaned_namespace_scopes(visibility, file),
9760 }
9761 }
9762}
9763
9764fn orphaned_namespace_scopes(
9767 visibility: &VisibilityIndex<'_>,
9768 file: &ProjectFile,
9769) -> Arc<OrphanedNamespaceScopeIndex> {
9770 visibility
9771 .cpp()
9772 .orphaned_namespace_scopes(visibility.token(), file)
9773}
9774
9775fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9776 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
9777}
9778
9779fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9780 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
9781}
9782
9783fn is_structurally_qualified(node: Node<'_>) -> bool {
9784 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9785 && qualified_name_has_concrete_scope_separators(node)
9786}
9787
9788fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
9789 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9790 return QualifiedOwnerResolution::Unresolved;
9791 };
9792 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
9793 return QualifiedOwnerResolution::Unresolved;
9794 };
9795 if !global
9801 && !matches!(
9802 enclosing_lexical_scope_components(
9803 node,
9804 &ctx.analyzer,
9805 ctx.visibility,
9806 ctx.file,
9807 ctx.source,
9808 ),
9809 LexicalScopeResolution::Resolved(_)
9810 )
9811 {
9812 return QualifiedOwnerResolution::Unresolved;
9813 }
9814 match resolve_type_components_lexically_at_for_target_with_scope_cache(
9815 node,
9816 &components,
9817 global,
9818 &ctx.analyzer,
9819 ctx.visibility,
9820 &ctx.ordinary_type_imports,
9821 ctx.file,
9822 ctx.source,
9823 target_owner,
9824 false,
9825 Some(&ctx.lexical_scope_cache),
9826 ) {
9827 LexicalTypeResolution::Resolved { unit: owner, .. } => {
9828 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
9829 return QualifiedOwnerResolution::Target;
9830 }
9831 match cached_declaring_member_owner(&owner, ctx) {
9832 EnclosingMemberOwnerResolution::Owner(declaring_owner)
9833 if receiver_owner_matches_target(
9834 &declaring_owner,
9835 target_owner,
9836 node.start_byte(),
9837 ctx,
9838 ) =>
9839 {
9840 QualifiedOwnerResolution::Target
9841 }
9842 EnclosingMemberOwnerResolution::Owner(declaring_owner)
9843 if receiver_owner_is_known_non_target(
9844 &declaring_owner,
9845 target_owner,
9846 node.start_byte(),
9847 ctx,
9848 ) =>
9849 {
9850 QualifiedOwnerResolution::NonTarget
9851 }
9852 EnclosingMemberOwnerResolution::Owner(_)
9853 | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
9854 EnclosingMemberOwnerResolution::Missing
9855 if same_visible_symbol(&owner, target_owner) =>
9856 {
9857 QualifiedOwnerResolution::Unresolved
9858 }
9859 EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
9860 }
9861 }
9862 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
9863 QualifiedOwnerResolution::Unresolved
9864 }
9865 }
9866}
9867
9868fn qualified_callable_owner_components(
9869 node: Node<'_>,
9870 source: &str,
9871) -> Option<(Vec<String>, bool)> {
9872 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
9873 || !qualified_name_has_concrete_scope_separators(node)
9874 {
9875 return None;
9876 }
9877 let owner = qualified_owner_components(node, source)?;
9878 Some((owner.names, owner.global))
9879}
9880
9881fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
9882 if !matches!(
9883 node.kind(),
9884 "identifier"
9885 | "type_identifier"
9886 | "namespace_identifier"
9887 | "qualified_identifier"
9888 | "scoped_identifier"
9889 | "scoped_type_identifier"
9890 | "template_type"
9891 | "template_function"
9892 ) {
9893 return None;
9894 }
9895 let components = cpp_type_name_components(node, source)?;
9896 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
9897}
9898
9899pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
9900 let mut namespaces = Vec::new();
9901 for parent in enclosing_cpp_namespace_nodes(node) {
9902 if let Some(name) = parent.child_by_field_name("name") {
9903 let mut components = Vec::new();
9904 if append_cpp_name_components(name, source, &mut components).is_some() {
9905 namespaces.push(components);
9906 }
9907 }
9908 }
9909 namespaces.into_iter().flatten().collect()
9910}
9911
9912pub fn enclosing_lexical_scope_components(
9913 node: Node<'_>,
9914 analyzer: &CppGraphSource<'_>,
9915 visibility: &VisibilityIndex<'_>,
9916 file: &ProjectFile,
9917 source: &str,
9918) -> LexicalScopeResolution {
9919 enclosing_lexical_scope_components_with_unresolved_owner(
9920 node,
9921 analyzer,
9922 visibility,
9923 file,
9924 source,
9925 false,
9926 false,
9927 &orphaned_namespace_scopes(visibility, file),
9928 )
9929}
9930
9931#[allow(clippy::too_many_arguments)]
9932fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
9933 node: Node<'_>,
9934 analyzer: &CppGraphSource<'_>,
9935 visibility: &VisibilityIndex<'_>,
9936 file: &ProjectFile,
9937 source: &str,
9938 allow_structured_unresolved_owner: bool,
9939 ignore_function_owner: bool,
9940 cache: Option<&LexicalScopeCache>,
9941) -> LexicalScopeResolution {
9942 let Some(cache) = cache else {
9943 return enclosing_lexical_scope_components_with_unresolved_owner(
9944 node,
9945 analyzer,
9946 visibility,
9947 file,
9948 source,
9949 allow_structured_unresolved_owner,
9950 ignore_function_owner,
9951 &orphaned_namespace_scopes(visibility, file),
9952 );
9953 };
9954 let orphaned = &cache.orphaned;
9955 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
9956 let (anchor_start, anchor_end) = match orphaned.region_at(node.start_byte()) {
9960 Some(region) => (anchor_start.max(region.start), anchor_end.min(region.end)),
9961 None => (anchor_start, anchor_end),
9962 };
9963 let key = (
9964 anchor_start,
9965 anchor_end,
9966 allow_structured_unresolved_owner,
9967 ignore_function_owner,
9968 );
9969 if let Some(cached) = cache.resolutions.borrow().get(&key).cloned() {
9970 return cached;
9971 }
9972 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
9973 node,
9974 analyzer,
9975 visibility,
9976 file,
9977 source,
9978 allow_structured_unresolved_owner,
9979 ignore_function_owner,
9980 orphaned,
9981 );
9982 cache.resolutions.borrow_mut().insert(key, resolved.clone());
9983 resolved
9984}
9985
9986fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
9987 let mut current = node;
9988 loop {
9989 if matches!(
9990 current.kind(),
9991 "function_definition"
9992 | "class_specifier"
9993 | "struct_specifier"
9994 | "union_specifier"
9995 | "namespace_definition"
9996 | "translation_unit"
9997 ) {
9998 return (current.start_byte(), current.end_byte());
9999 }
10000 let Some(parent) = current.parent() else {
10001 return (current.start_byte(), current.end_byte());
10002 };
10003 current = parent;
10004 }
10005}
10006
10007#[allow(clippy::too_many_arguments)]
10008fn enclosing_lexical_scope_components_with_unresolved_owner(
10009 node: Node<'_>,
10010 analyzer: &CppGraphSource<'_>,
10011 visibility: &VisibilityIndex<'_>,
10012 file: &ProjectFile,
10013 source: &str,
10014 allow_structured_unresolved_owner: bool,
10015 ignore_function_owner: bool,
10016 orphaned: &OrphanedNamespaceScopeIndex,
10017) -> LexicalScopeResolution {
10018 #[cfg(any(test, feature = "test-support"))]
10019 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
10020 let mut namespaces = Vec::new();
10026 let mut classes = Vec::new();
10027 let mut function_definition = None;
10028 let mut displaced_class_scope = false;
10029 let mut current = node.parent();
10030 while let Some(parent) = current {
10031 match parent.kind() {
10032 "namespace_definition" => {
10033 if let Some(name) = parent.child_by_field_name("name") {
10034 let mut components = Vec::new();
10035 if append_cpp_name_components(name, source, &mut components).is_some() {
10036 namespaces.push((
10037 parent.start_byte(),
10038 components,
10039 namespace_definition_has_real_braces(parent),
10040 ));
10041 }
10042 }
10043 }
10044 "class_specifier" | "struct_specifier" | "union_specifier" => {
10045 if let Some(name) = parent.child_by_field_name("name") {
10046 let mut components = Vec::new();
10047 if append_cpp_name_components(name, source, &mut components).is_some() {
10048 classes.push(components);
10049 }
10050 }
10051 }
10052 "function_definition" => {
10053 if function_definition.is_none() {
10054 function_definition = Some(parent);
10055 }
10056 displaced_class_scope = displaced_class_scope
10057 || parent.child_by_field_name("type").is_some_and(|type_node| {
10058 matches!(
10059 type_node.kind(),
10060 "class_specifier" | "struct_specifier" | "union_specifier"
10061 )
10062 })
10063 || is_malformed_wrapper_function_definition(parent);
10064 }
10065 _ => {}
10066 }
10067 current = parent.parent();
10068 }
10069 namespaces.reverse();
10070 let namespace = orphaned.restore_enclosing_namespaces(namespaces, node.start_byte());
10073 let mut scope = namespace.clone();
10074 let has_qualified_function_owner = function_definition
10079 .filter(|function| !is_malformed_wrapper_function_definition(*function))
10080 .and_then(function_definition_owner_lookup_node)
10081 .is_some_and(|owner| {
10082 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
10083 });
10084 let indexed_scope = displaced_class_scope
10085 .then(|| {
10086 indexed_structural_class_scope(visibility, file, node, source)
10087 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10088 })
10089 .flatten()
10090 .or_else(|| {
10091 (has_qualified_function_owner && function_definition.is_some())
10098 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
10099 .flatten()
10100 .filter(|indexed| {
10101 qualified_owner_scope_is_recoverable(
10102 indexed,
10103 &namespace,
10104 &classes,
10105 function_definition
10106 .and_then(function_definition_owner_lookup_node)
10107 .and_then(|owner| qualified_callable_owner_components(owner, source))
10108 .map(|(components, _)| components),
10109 )
10110 })
10111 })
10112 .or_else(|| {
10113 indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
10121 indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
10122 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
10123 })
10124 })
10125 })
10126 .or_else(|| {
10127 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
10131 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
10132 .flatten()
10133 .filter(|indexed| indexed.len() > namespace.len())
10134 });
10135 if let Some(indexed_scope) = indexed_scope.as_ref() {
10136 scope = indexed_scope.clone();
10141 classes.clear();
10142 }
10143
10144 if !ignore_function_owner
10145 && has_qualified_function_owner
10146 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
10147 {
10148 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
10149 return LexicalScopeResolution::Missing;
10150 };
10151 let imports = visibility.ordinary_type_import_cell(file);
10157 let owner_resolution = resolve_type_components_lexically_at_scoped(
10158 function,
10159 &owner,
10160 global,
10161 analyzer,
10162 visibility,
10163 &imports,
10164 file,
10165 source,
10166 None,
10167 false,
10168 false,
10169 false,
10170 namespace.clone(),
10171 );
10172 match owner_resolution {
10173 LexicalTypeResolution::Resolved {
10174 unit, components, ..
10175 } if is_indexed_class_owner(analyzer, &unit) => {
10176 scope = components;
10177 classes.clear();
10178 }
10179 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10180 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
10181 match visibility
10182 .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
10183 {
10184 LexicalTypeResolution::Resolved { components, .. } => scope = components,
10185 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
10186 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
10187 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
10188 qualified_owner_scope_is_recoverable(
10189 indexed,
10190 &namespace,
10191 &classes,
10192 Some(owner.clone()),
10193 )
10194 }) {
10195 scope = indexed.clone();
10196 } else {
10197 scope = if global || owner.starts_with(&namespace) {
10198 owner
10199 } else {
10200 let mut relative = namespace;
10201 relative.extend(owner);
10202 relative
10203 };
10204 }
10205 }
10206 LexicalTypeResolution::Missing => {
10207 match indexed_enclosing_owner_scope(analyzer, visibility, file, node)
10217 .or_else(|| {
10218 indexed_namespace_qualified_scope(
10219 analyzer, visibility, file, node, &owner,
10220 )
10221 }) {
10222 Some(indexed) => scope = indexed,
10223 None => return LexicalScopeResolution::Missing,
10224 }
10225 }
10226 }
10227 }
10228 }
10229 }
10230
10231 classes.reverse();
10232 scope.extend(classes.into_iter().flatten());
10233 LexicalScopeResolution::Resolved(scope)
10234}
10235
10236fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
10237 let mut current = node.parent();
10238 while let Some(parent) = current {
10239 if parent.kind() == "function_definition"
10240 && parent.child_by_field_name("type").is_some_and(|type_node| {
10241 matches!(
10242 type_node.kind(),
10243 "class_specifier" | "struct_specifier" | "union_specifier"
10244 )
10245 })
10246 {
10247 return true;
10248 }
10249 current = parent.parent();
10250 }
10251 false
10252}
10253
10254fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
10255 let mut current = node.parent();
10256 while let Some(parent) = current {
10257 if parent.kind() == "function_definition"
10258 && is_malformed_wrapper_function_definition(parent)
10259 {
10260 return true;
10261 }
10262 current = parent.parent();
10263 }
10264 false
10265}
10266
10267pub fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
10273 let mut cursor = node.walk();
10274 let recovery_evidence = node.has_error()
10275 || node.named_children(&mut cursor).any(|child| {
10276 matches!(
10277 child.kind(),
10278 "macro_modifier" | "macro_modifier_declarator" | "macro_type_specifier"
10279 )
10280 });
10281 recovery_evidence
10282 && node
10283 .child_by_field_name("declarator")
10284 .is_some_and(|declarator| {
10285 declarator.kind() != "function_declarator"
10286 && first_descendant_of_kind(declarator, "function_declarator").is_none()
10287 })
10288}
10289
10290fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
10296 node.child_by_field_name("scope")
10297 .and_then(|scope| recovered_macro_decorated_type_node(scope))
10298 .is_some()
10299}
10300
10301fn indexed_structural_class_scope(
10302 visibility: &VisibilityIndex<'_>,
10303 file: &ProjectFile,
10304 node: Node<'_>,
10305 source: &str,
10306) -> Option<Vec<String>> {
10307 let mut current = node.parent();
10308 while let Some(parent) = current {
10309 if matches!(
10310 parent.kind(),
10311 "class_specifier" | "struct_specifier" | "union_specifier"
10312 ) {
10313 return visibility.indexed_structural_class_scope(file, parent, source);
10314 }
10315 current = parent.parent();
10316 }
10317 None
10318}
10319
10320fn qualified_owner_scope_is_recoverable(
10331 indexed: &[String],
10332 namespace: &[String],
10333 classes: &[Vec<String>],
10334 qualified_owner: Option<Vec<String>>,
10335) -> bool {
10336 if let Some(owner) = qualified_owner {
10337 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
10338 return false;
10339 }
10340 if namespace.is_empty() {
10346 return true;
10347 }
10348 if indexed.len() <= namespace.len() {
10349 return false;
10350 }
10351 return super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed);
10352 }
10353 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
10354 if !class_components.is_empty() {
10355 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
10356 }
10357 if namespace.is_empty() || indexed.len() <= namespace.len() {
10358 return false;
10359 }
10360 super::cpp_namespace_path_is_ordered_subsequence(namespace, indexed)
10361}
10362
10363fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
10366 unit.is_class()
10367 && !analyzer
10368 .type_alias_provider()
10369 .is_some_and(|provider| provider.is_type_alias(unit))
10370}
10371
10372fn indexed_enclosing_owner_scope(
10386 analyzer: &CppGraphSource<'_>,
10387 visibility: &VisibilityIndex<'_>,
10388 file: &ProjectFile,
10389 node: Node<'_>,
10390) -> Option<Vec<String>> {
10391 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
10392}
10393
10394fn indexed_namespace_qualified_scope(
10411 analyzer: &CppGraphSource<'_>,
10412 visibility: &VisibilityIndex<'_>,
10413 file: &ProjectFile,
10414 node: Node<'_>,
10415 owner: &[String],
10416) -> Option<Vec<String>> {
10417 let name = owner.last().expect("a qualified owner has one component");
10418 if !visibility
10419 .visible_identifier_candidates(file, name)
10420 .any(|unit| unit.is_module())
10421 {
10422 return None;
10423 }
10424 let indexed = indexed_enclosing_lexical_scope(analyzer, file, node)?;
10425 indexed.ends_with(owner).then_some(indexed)
10426}
10427
10428fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10429 let start = enclosing_context(node, ctx).enclosing?;
10430 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
10431 ctx.analyzer.parent_of(unit)
10432 })
10433 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
10434}
10435
10436pub fn resolve_type_node_lexically(
10437 node: Node<'_>,
10438 analyzer: &CppGraphSource<'_>,
10439 visibility: &VisibilityIndex<'_>,
10440 ordinary_type_imports: &OrdinaryTypeImportCell,
10441 file: &ProjectFile,
10442 source: &str,
10443) -> LexicalTypeResolution {
10444 let Some((components, global)) = type_reference_components(node, source) else {
10445 return LexicalTypeResolution::Missing;
10446 };
10447 let resolution = resolve_type_components_lexically_at(
10448 node,
10449 &components,
10450 global,
10451 analyzer,
10452 visibility,
10453 ordinary_type_imports,
10454 file,
10455 source,
10456 );
10457 if !is_cpp_template_argument_type_leaf(node) {
10458 return resolution;
10459 }
10460
10461 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10468 return resolution;
10469 };
10470 let namespace_scope = enclosing_namespace_components(node, source);
10471 if indexed_scope.len() <= namespace_scope.len() {
10472 return resolution;
10473 }
10474 let indexed = resolve_type_components_in_authoritative_scope(
10475 node,
10476 &components,
10477 global,
10478 analyzer,
10479 visibility,
10480 ordinary_type_imports,
10481 file,
10482 source,
10483 None,
10484 false,
10485 false,
10486 false,
10487 indexed_scope,
10488 );
10489 match indexed {
10490 LexicalTypeResolution::Resolved { ref unit, .. }
10491 if !visibility
10492 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10493 {
10494 resolution
10495 }
10496 LexicalTypeResolution::Resolved { .. } => indexed,
10497 _ => resolution,
10498 }
10499}
10500
10501#[allow(clippy::too_many_arguments)]
10505pub fn resolve_type_node_lexically_for_target(
10506 node: Node<'_>,
10507 analyzer: &CppGraphSource<'_>,
10508 visibility: &VisibilityIndex<'_>,
10509 ordinary_type_imports: &OrdinaryTypeImportCell,
10510 file: &ProjectFile,
10511 source: &str,
10512 target: &CodeUnit,
10513 scope_cache: Option<&LexicalScopeCache>,
10514 recovered_scope: Option<&[String]>,
10515) -> LexicalTypeResolution {
10516 let Some((reference_components, global)) = type_reference_components(node, source) else {
10517 return LexicalTypeResolution::Missing;
10518 };
10519 let terminal = reference_components
10520 .last()
10521 .expect("type reference components are non-empty");
10522 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
10523 return LexicalTypeResolution::Missing;
10524 }
10525 let template_arguments = cpp_template_reference_arguments(node, source);
10526 let selects_concrete_specialization =
10527 template_arguments.is_some() && visibility.is_template_specialization(target);
10528 if !selects_concrete_specialization
10529 && !visibility.structured_type_reference_may_resolve_to_target(
10530 analyzer,
10531 file,
10532 std::slice::from_ref(terminal),
10533 false,
10534 &[],
10535 target,
10536 )
10537 {
10538 return LexicalTypeResolution::Missing;
10539 }
10540 if let Some(arguments) = template_arguments.as_ref() {
10541 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
10542 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10543 node,
10544 &reference_components,
10545 global,
10546 analyzer,
10547 visibility,
10548 ordinary_type_imports,
10549 file,
10550 source,
10551 recovered_scope,
10552 )
10553 } else {
10554 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10555 node,
10556 &reference_components,
10557 global,
10558 analyzer,
10559 visibility,
10560 ordinary_type_imports,
10561 file,
10562 source,
10563 scope_cache,
10564 )
10565 };
10566 return match alias_resolution {
10567 LexicalTypeResolution::Resolved {
10568 unit,
10569 components,
10570 candidates,
10571 } if visibility.template_alias_arguments_preserve_target(
10572 analyzer, file, &unit, arguments, target,
10573 ) =>
10574 {
10575 LexicalTypeResolution::Resolved {
10576 unit: target.clone(),
10577 components,
10578 candidates,
10579 }
10580 }
10581 LexicalTypeResolution::Resolved {
10582 unit,
10583 components,
10584 candidates,
10585 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
10586 Ok(resolved_unit) => {
10587 let target_guided = (!same_visible_symbol(&resolved_unit, target))
10588 .then(|| {
10589 target_guided_malformed_template_alias_resolution(
10590 node,
10591 analyzer,
10592 visibility,
10593 file,
10594 arguments,
10595 &reference_components,
10596 target,
10597 )
10598 })
10599 .flatten();
10600 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
10601 unit: resolved_unit,
10602 components,
10603 candidates,
10604 })
10605 }
10606 Err(_) => LexicalTypeResolution::Ambiguous,
10607 },
10608 LexicalTypeResolution::Missing => {
10609 let target_preserving = if let Some(recovered_scope) = recovered_scope {
10610 resolve_type_components_lexically_at_for_target_with_recovered_scope(
10611 node,
10612 &reference_components,
10613 global,
10614 analyzer,
10615 visibility,
10616 ordinary_type_imports,
10617 file,
10618 source,
10619 target,
10620 true,
10621 recovered_scope,
10622 )
10623 } else {
10624 resolve_type_components_lexically_at_for_target_with_scope_cache(
10625 node,
10626 &reference_components,
10627 global,
10628 analyzer,
10629 visibility,
10630 ordinary_type_imports,
10631 file,
10632 source,
10633 target,
10634 true,
10635 scope_cache,
10636 )
10637 };
10638 match target_preserving {
10639 LexicalTypeResolution::Resolved {
10640 unit: _,
10641 components,
10642 candidates,
10643 } if template_reference_candidates_select_target(
10644 node,
10645 &candidates,
10646 analyzer,
10647 visibility,
10648 file,
10649 source,
10650 target,
10651 ) =>
10652 {
10653 LexicalTypeResolution::Resolved {
10654 unit: target.clone(),
10655 components,
10656 candidates,
10657 }
10658 }
10659 _ => target_guided_malformed_template_alias_resolution(
10660 node,
10661 analyzer,
10662 visibility,
10663 file,
10664 arguments,
10665 &reference_components,
10666 target,
10667 )
10668 .unwrap_or(LexicalTypeResolution::Missing),
10669 }
10670 }
10671 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
10672 };
10673 }
10674 let resolution = if let Some(recovered_scope) = recovered_scope {
10675 resolve_type_components_lexically_at_for_target_with_recovered_scope(
10676 node,
10677 &reference_components,
10678 global,
10679 analyzer,
10680 visibility,
10681 ordinary_type_imports,
10682 file,
10683 source,
10684 target,
10685 true,
10686 recovered_scope,
10687 )
10688 } else {
10689 resolve_type_components_lexically_at_for_target_with_scope_cache(
10690 node,
10691 &reference_components,
10692 global,
10693 analyzer,
10694 visibility,
10695 ordinary_type_imports,
10696 file,
10697 source,
10698 target,
10699 true,
10700 scope_cache,
10701 )
10702 };
10703 let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
10704 target_guided_qualified_namespace_function_type_resolution(
10705 node,
10706 &reference_components,
10707 global,
10708 analyzer,
10709 visibility,
10710 ordinary_type_imports,
10711 file,
10712 source,
10713 target,
10714 )
10715 .unwrap_or(resolution)
10716 } else {
10717 resolution
10718 };
10719 if !is_cpp_template_argument_type_leaf(node) {
10720 return resolution;
10721 }
10722
10723 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
10731 return resolution;
10732 };
10733 let namespace_scope = enclosing_namespace_components(node, source);
10734 if indexed_scope.len() <= namespace_scope.len() {
10735 return resolution;
10736 }
10737 let indexed = visibility.resolve_type_components_lexically_for_target(
10738 analyzer,
10739 file,
10740 &reference_components,
10741 global,
10742 &indexed_scope,
10743 target,
10744 );
10745 match indexed {
10746 LexicalTypeResolution::Resolved { ref unit, .. }
10747 if visibility
10748 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10749 {
10750 indexed
10751 }
10752 _ => resolution,
10753 }
10754}
10755
10756#[allow(clippy::too_many_arguments)]
10766fn target_guided_qualified_namespace_function_type_resolution(
10767 node: Node<'_>,
10768 components: &[String],
10769 global: bool,
10770 analyzer: &CppGraphSource<'_>,
10771 visibility: &VisibilityIndex<'_>,
10772 ordinary_type_imports: &OrdinaryTypeImportCell,
10773 file: &ProjectFile,
10774 source: &str,
10775 target: &CodeUnit,
10776) -> Option<LexicalTypeResolution> {
10777 let function_definition = std::iter::successors(Some(node), |current| current.parent())
10778 .find(|current| current.kind() == "function_definition")?;
10779 let function = function_definition_name_node(function_definition)?;
10780 let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
10781 let target_namespace = target.package_name();
10782 if target_namespace.is_empty() {
10783 return None;
10784 }
10785 let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10786 brokk_bifrost_core::analyzer::Language::Cpp,
10787 target_namespace,
10788 );
10789 if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
10790 {
10791 return None;
10792 }
10793
10794 let function_name = node_text(function_terminal_node(function), source);
10795 let definition_unit = canonical_callable_at_node(
10796 function_definition,
10797 analyzer,
10798 visibility,
10799 file,
10800 source,
10801 TargetKind::FreeFunction,
10802 )?;
10803 let definition_arity = cpp_callable_arity(analyzer, &definition_unit)?.total();
10804 let declaration_proves_namespace = visibility
10805 .visible_identifier_candidates(file, function_name)
10806 .filter(|candidate| {
10807 candidate.is_function()
10808 && type_owner_of(analyzer, candidate).is_none()
10809 && candidate.package_name() == target_namespace
10810 })
10811 .any(|candidate| {
10812 cpp_callable_arity(analyzer, candidate)
10813 .is_none_or(|known| known.accepts(definition_arity))
10814 });
10815 if !declaration_proves_namespace {
10816 return None;
10817 }
10818
10819 let resolution = resolve_type_components_lexically_at_scoped(
10820 node,
10821 components,
10822 global,
10823 analyzer,
10824 visibility,
10825 ordinary_type_imports,
10826 file,
10827 source,
10828 Some(target),
10829 true,
10830 false,
10831 false,
10832 target_scope,
10833 );
10834 match &resolution {
10835 LexicalTypeResolution::Resolved {
10836 unit, candidates, ..
10837 } if (same_visible_symbol(unit, target)
10838 || candidates
10839 .iter()
10840 .any(|candidate| same_visible_symbol(candidate, target)))
10841 && visibility
10842 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10843 {
10844 Some(resolution)
10845 }
10846 LexicalTypeResolution::Resolved { .. }
10847 | LexicalTypeResolution::Ambiguous
10848 | LexicalTypeResolution::Missing => None,
10849 }
10850}
10851
10852#[allow(clippy::too_many_arguments)]
10853fn target_guided_malformed_template_alias_resolution(
10854 node: Node<'_>,
10855 analyzer: &CppGraphSource<'_>,
10856 visibility: &VisibilityIndex<'_>,
10857 file: &ProjectFile,
10858 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
10859 components: &[String],
10860 target: &CodeUnit,
10861) -> Option<LexicalTypeResolution> {
10862 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
10863 return None;
10864 }
10865
10866 let identifier = &components[0];
10867 let namespace =
10868 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
10869 let namespace_name = namespace.join("::");
10870 let candidates = visibility
10871 .visible_identifier_candidates(file, identifier)
10872 .filter(|candidate| {
10873 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
10874 && visibility.type_candidate_may_be_visible_before_reference(
10875 analyzer,
10876 file,
10877 candidate,
10878 node.start_byte(),
10879 )
10880 })
10881 .cloned()
10882 .collect::<Vec<_>>();
10883 let first = candidates.first()?;
10884 if !candidates
10885 .iter()
10886 .all(|candidate| same_logical_symbol(first, candidate))
10887 || !candidates.iter().all(|candidate| {
10888 visibility.template_alias_arguments_preserve_target(
10889 analyzer, file, candidate, arguments, target,
10890 )
10891 })
10892 {
10893 return None;
10894 }
10895
10896 let mut resolved_components = namespace;
10897 resolved_components.push(identifier.clone());
10898 Some(LexicalTypeResolution::Resolved {
10899 unit: target.clone(),
10900 components: resolved_components,
10901 candidates,
10902 })
10903}
10904
10905fn resolve_type_node_lexically_for_target_without_visibility(
10906 node: Node<'_>,
10907 analyzer: &CppGraphSource<'_>,
10908 visibility: &VisibilityIndex<'_>,
10909 file: &ProjectFile,
10910 source: &str,
10911 target: &CodeUnit,
10912) -> LexicalTypeResolution {
10913 let Some((components, global)) = type_reference_components(node, source) else {
10914 return LexicalTypeResolution::Missing;
10915 };
10916 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
10917 node,
10918 analyzer,
10919 visibility,
10920 file,
10921 source,
10922 true,
10923 recovered_macro_decorated_declarator_type(node)
10924 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10925 &orphaned_namespace_scopes(visibility, file),
10926 ) {
10927 LexicalScopeResolution::Resolved(scope) => scope,
10928 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10929 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10930 };
10931 visibility.resolve_type_components_lexically_for_target(
10932 analyzer,
10933 file,
10934 &components,
10935 global,
10936 &lexical_scope,
10937 target,
10938 )
10939}
10940
10941fn type_node_has_exact_target_identity_without_visibility(
10942 node: Node<'_>,
10943 analyzer: &CppGraphSource<'_>,
10944 visibility: &VisibilityIndex<'_>,
10945 file: &ProjectFile,
10946 source: &str,
10947 target: &CodeUnit,
10948) -> bool {
10949 let Some((components, global)) = type_reference_components(node, source) else {
10950 return false;
10951 };
10952 let LexicalScopeResolution::Resolved(lexical_scope) =
10953 enclosing_lexical_scope_components_with_unresolved_owner(
10954 node,
10955 analyzer,
10956 visibility,
10957 file,
10958 source,
10959 true,
10960 recovered_macro_decorated_declarator_type(node)
10961 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10962 &orphaned_namespace_scopes(visibility, file),
10963 )
10964 else {
10965 return false;
10966 };
10967 let target_name = cpp_name_for(target);
10968 lexical_component_tiers(&components, global, &lexical_scope)
10969 .any(|qualified| qualified.join("::") == target_name)
10970}
10971
10972pub fn resolve_using_enum_declaration_owner(
10973 node: Node<'_>,
10974 analyzer: &CppGraphSource<'_>,
10975 visibility: &VisibilityIndex<'_>,
10976 ordinary_type_imports: &OrdinaryTypeImportCell,
10977 file: &ProjectFile,
10978 source: &str,
10979) -> LexicalTypeResolution {
10980 let Some(type_node) = using_enum_declaration_type_node(node) else {
10981 return LexicalTypeResolution::Missing;
10982 };
10983 let mut components = Vec::new();
10984 if append_cpp_name_components(type_node, source, &mut components).is_none()
10985 || components.is_empty()
10986 {
10987 return LexicalTypeResolution::Missing;
10988 }
10989 resolve_type_components_lexically_at(
10990 type_node,
10991 &components,
10992 is_globally_qualified_cpp_name(type_node),
10993 analyzer,
10994 visibility,
10995 ordinary_type_imports,
10996 file,
10997 source,
10998 )
10999}
11000
11001pub fn resolve_ordinary_using_declaration_owner(
11002 node: Node<'_>,
11003 analyzer: &CppGraphSource<'_>,
11004 visibility: &VisibilityIndex<'_>,
11005 file: &ProjectFile,
11006 source: &str,
11007) -> LexicalTypeResolution {
11008 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
11009 return LexicalTypeResolution::Missing;
11010 };
11011 let mut components = Vec::new();
11012 if append_cpp_name_components(type_node, source, &mut components).is_none()
11013 || components.len() < 2
11014 {
11015 return LexicalTypeResolution::Missing;
11016 }
11017 let lexical_scope =
11018 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
11019 LexicalScopeResolution::Resolved(scope) => scope,
11020 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
11021 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
11022 };
11023 visibility.resolve_type_components_lexically(
11024 analyzer,
11025 file,
11026 &components,
11027 is_globally_qualified_cpp_name(type_node),
11028 &lexical_scope,
11029 )
11030}
11031
11032pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11033 (node.kind() == "using_declaration"
11034 && (0..node.child_count()).any(|index| {
11035 node.child(index)
11036 .is_some_and(|child| child.kind() == "enum")
11037 }))
11038 .then(|| node.named_child(0))
11039 .flatten()
11040}
11041
11042pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
11043 (node.kind() == "using_declaration"
11044 && using_enum_declaration_type_node(node).is_none()
11045 && using_namespace_directive_name_node(node).is_none())
11046 .then(|| node.named_child(0))
11047 .flatten()
11048}
11049
11050fn recovered_macro_using_declaration_type_node<'tree>(
11057 node: Node<'tree>,
11058 source: &str,
11059) -> Option<(Node<'tree>, bool)> {
11060 if node.kind() != "declaration" {
11061 return None;
11062 }
11063 let macro_type = node.child_by_field_name("type")?;
11064 if macro_type.kind() != "type_identifier"
11065 || !cpp_export_macro_token(node_text(macro_type, source))
11066 {
11067 return None;
11068 }
11069 let declarator = node.child_by_field_name("declarator")?;
11070 if declarator.kind() != "qualified_identifier" {
11071 return None;
11072 }
11073 let scope = declarator.child_by_field_name("scope")?;
11074 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
11075 return None;
11076 }
11077 let target = declarator.child_by_field_name("name")?;
11078 let mut components = Vec::new();
11079 append_cpp_name_components(target, source, &mut components)?;
11080 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
11081}
11082
11083fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
11084 let is_directive = node.kind() == "using_directive"
11085 || (node.kind() == "using_declaration"
11086 && (0..node.child_count()).any(|index| {
11087 node.child(index)
11088 .is_some_and(|child| child.kind() == "namespace")
11089 }));
11090 if !is_directive {
11091 return None;
11092 }
11093 node.child_by_field_name("name")
11094 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
11095}
11096
11097fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11098 let mut current = node.parent();
11099 while let Some(parent) = current {
11100 if matches!(
11101 parent.kind(),
11102 "compound_statement"
11103 | "function_definition"
11104 | "lambda_expression"
11105 | "for_statement"
11106 | "while_statement"
11107 | "if_statement"
11108 | "class_specifier"
11109 | "struct_specifier"
11110 | "union_specifier"
11111 ) {
11112 return None;
11113 }
11114 current = parent.parent();
11115 }
11116 Some(enclosing_namespace_components(node, source))
11117}
11118
11119fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
11120 let mut current = node.parent();
11121 while let Some(scope) = current {
11122 if matches!(
11123 scope.kind(),
11124 "compound_statement"
11125 | "declaration_list"
11126 | "field_declaration_list"
11127 | "translation_unit"
11128 ) {
11129 let mut depth = 0;
11130 let mut ancestor = scope.parent();
11131 while let Some(parent) = ancestor {
11132 depth += 1;
11133 ancestor = parent.parent();
11134 }
11135 return Some((
11136 scope.start_byte(),
11137 scope.end_byte(),
11138 depth,
11139 scope.kind() == "compound_statement",
11140 ));
11141 }
11142 current = scope.parent();
11143 }
11144 None
11145}
11146
11147pub fn build_source_using_index(
11154 cpp: &dyn CppSource,
11155 token: QueryToken<'_>,
11156 file: &ProjectFile,
11157) -> SourceUsingIndex {
11158 let Some(prepared) = cpp.prepared_syntax(token, file) else {
11159 return SourceUsingIndex::default();
11160 };
11161 collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
11162}
11163
11164fn collect_source_using_index(
11165 cpp: &dyn CppSource,
11166 source_file: &ProjectFile,
11167 root: Node<'_>,
11168 source: &str,
11169) -> SourceUsingIndex {
11170 #[cfg(not(any(test, feature = "test-support")))]
11171 let _ = cpp;
11172 let mut index = SourceUsingIndex::default();
11173 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
11174 let mut stack = vec![root];
11175 while let Some(node) = stack.pop() {
11176 let target = match node.kind() {
11177 "using_directive" | "using_declaration" => {
11178 if let Some(namespace_node) = using_namespace_directive_name_node(node) {
11179 let mut namespace_components = Vec::new();
11180 append_cpp_name_components(namespace_node, source, &mut namespace_components)
11181 .map(|_| EffectiveUsingTarget::Namespace {
11182 namespace_components,
11183 global: is_globally_qualified_cpp_name(namespace_node),
11184 })
11185 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
11186 let mut target_components = Vec::new();
11187 (append_cpp_name_components(type_node, source, &mut target_components)
11188 .is_some()
11189 && target_components.len() >= 2)
11190 .then(|| EffectiveUsingTarget::Ordinary {
11191 name: target_components
11192 .last()
11193 .expect("ordinary using has a terminal component")
11194 .clone(),
11195 target_components,
11196 global: is_globally_qualified_cpp_name(type_node),
11197 })
11198 } else {
11199 None
11200 }
11201 }
11202 "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
11203 |(type_node, global)| {
11204 let mut target_components = Vec::new();
11205 (append_cpp_name_components(type_node, source, &mut target_components)
11206 .is_some()
11207 && target_components.len() >= 2)
11208 .then(|| EffectiveUsingTarget::Ordinary {
11209 name: target_components
11210 .last()
11211 .expect("recovered ordinary using has a terminal component")
11212 .clone(),
11213 target_components,
11214 global,
11215 })
11216 },
11217 ),
11218 _ => None,
11219 };
11220 if let Some(target) = target {
11221 #[cfg(any(test, feature = "test-support"))]
11227 cpp.record_using_guard_context_inspection_for_test();
11228 let Some(required_guards) = binding_site_guards(node, source) else {
11229 let mut cursor = node.walk();
11230 stack.extend(node.children(&mut cursor));
11231 continue;
11232 };
11233 if let Some((scope_start, scope_end, scope_depth, block_scope)) =
11234 ordinary_using_scope(node)
11235 {
11236 let declaration_namespace = enclosing_namespace_components(node, source);
11237 let declaration_namespace = if declaration_namespace.is_empty() {
11238 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
11239 .unwrap_or(declaration_namespace)
11240 } else {
11241 declaration_namespace
11242 };
11243 let namespace_scope = using_named_scope(node, source);
11244 let lexical_depth = declaration_namespace.len();
11245 let binding = OrdinaryTypeImport {
11246 target,
11247 source: source_file.clone(),
11248 declaration_byte: node.end_byte(),
11249 scope_start,
11250 scope_end,
11251 scope_depth,
11252 block_scope,
11253 lexical_depth,
11254 declaration_namespace,
11255 namespace_scope,
11256 resolved_target_components: None,
11257 required_guards,
11258 };
11259 match &binding.target {
11260 EffectiveUsingTarget::Ordinary { name, .. } => index
11261 .ordinary_by_name
11262 .entry(name.clone())
11263 .or_default()
11264 .push(binding),
11265 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
11266 }
11267 }
11268 }
11269 let mut cursor = node.walk();
11270 stack.extend(node.children(&mut cursor));
11271 }
11272 index
11273}
11274
11275struct OrphanedNamespaceEnvelope {
11276 body_end: usize,
11277 components: Vec<String>,
11278 class_names: HashSet<String>,
11279}
11280
11281fn collect_orphaned_namespace_envelopes(
11288 root: Node<'_>,
11289 source: &str,
11290) -> Vec<OrphanedNamespaceEnvelope> {
11291 let mut envelopes = Vec::new();
11292 let mut stack = vec![root];
11293 while let Some(current) = stack.pop() {
11294 if current.kind() == "namespace_definition"
11295 && let Some(body) = current.child_by_field_name("body")
11296 && current.end_byte() == body.end_byte()
11297 && let Some(name) = current.child_by_field_name("name")
11298 {
11299 let mut components = enclosing_namespace_components(current, source);
11300 if append_cpp_name_components(name, source, &mut components).is_some()
11301 && !components.is_empty()
11302 {
11303 let mut class_names = HashSet::default();
11304 let mut body_stack = vec![body];
11305 while let Some(node) = body_stack.pop() {
11306 if let Some(name) = orphaned_class_definition_name(node, source) {
11307 class_names.insert(name);
11308 }
11309 let mut cursor = node.walk();
11310 if node.kind() == "ERROR" {
11311 body_stack.extend(node.children(&mut cursor));
11312 } else {
11313 body_stack.extend(node.named_children(&mut cursor));
11314 }
11315 }
11316 envelopes.push(OrphanedNamespaceEnvelope {
11317 body_end: body.end_byte(),
11318 components,
11319 class_names,
11320 });
11321 }
11322 }
11323 let mut cursor = current.walk();
11324 stack.extend(current.named_children(&mut cursor));
11325 }
11326 envelopes
11327}
11328
11329fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
11330 if matches!(
11331 node.kind(),
11332 "class_specifier" | "struct_specifier" | "union_specifier"
11333 ) {
11334 let body = node.child_by_field_name("body")?;
11335 let name = node.child_by_field_name("name")?;
11336 return (!name.is_missing() && !body.is_missing())
11337 .then(|| node_text(name, source).to_string());
11338 }
11339 if node.kind() != "ERROR" {
11340 return None;
11341 }
11342
11343 for index in 0..node.child_count() {
11349 let Some(keyword) = node.child(index) else {
11350 continue;
11351 };
11352 if !matches!(keyword.kind(), "class" | "struct" | "union") {
11353 continue;
11354 }
11355 let mut name = None;
11356 for next_index in (index + 1)..node.child_count() {
11357 let Some(next) = node.child(next_index) else {
11358 continue;
11359 };
11360 if next.kind() == ";" {
11361 break;
11362 }
11363 if next.kind() == "{" {
11364 return name
11365 .filter(|name_node: &Node<'_>| !name_node.is_missing())
11366 .map(|name_node| node_text(name_node, source).to_string());
11367 }
11368 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
11369 name = Some(next);
11370 }
11371 }
11372 }
11373 None
11374}
11375
11376fn recovered_orphaned_namespace_components(
11377 node: Node<'_>,
11378 source: &str,
11379 envelopes: &[OrphanedNamespaceEnvelope],
11380) -> Option<Vec<String>> {
11381 let owner_name = orphaned_using_owner_name(node, source)?;
11382 envelopes
11383 .iter()
11384 .filter(|envelope| {
11385 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
11386 })
11387 .max_by_key(|envelope| envelope.body_end)
11388 .map(|envelope| envelope.components.clone())
11389}
11390
11391fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
11392 let function = std::iter::successors(node.parent(), |current| current.parent())
11393 .find(|current| current.kind() == "function_definition")?;
11394 let owner = function_definition_owner_lookup_node(function)?;
11395 let scope = owner.child_by_field_name("scope")?;
11396 let mut components = Vec::new();
11397 append_cpp_name_components(scope, source, &mut components)?;
11398 if components.len() != 1 {
11405 return None;
11406 }
11407 components.pop()
11408}
11409
11410fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
11411 let started = Instant::now();
11412 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
11413 let source_files = visibility.all_visible_source_files();
11414 if report_stats {
11415 eprintln!(
11416 "BIFROST_CPP_USING_INDEX_STATS status=started source_files={}",
11417 source_files.len()
11418 );
11419 }
11420 let mut project = ProjectUsingIndex::default();
11421 let mut ordinary_bindings = 0usize;
11422 for source_file in &source_files {
11423 let source_index = visibility
11427 .cpp()
11428 .source_using_index(visibility.token(), source_file);
11429 for (name, bindings) in &source_index.ordinary_by_name {
11430 ordinary_bindings += bindings.len();
11431 project
11432 .ordinary_by_name
11433 .entry(name.clone())
11434 .or_default()
11435 .extend(bindings.iter().cloned());
11436 }
11437 project
11438 .directives
11439 .extend(source_index.directives.iter().cloned());
11440 }
11441 if report_stats {
11442 eprintln!(
11443 "BIFROST_CPP_USING_INDEX_STATS status=completed source_files={} ordinary_names={} ordinary_bindings={} directives={} elapsed_ms={}",
11444 source_files.len(),
11445 project.ordinary_by_name.len(),
11446 ordinary_bindings,
11447 project.directives.len(),
11448 started.elapsed().as_millis(),
11449 );
11450 }
11451 project
11452}
11453
11454fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
11455 visibility.project_using_index(|| build_project_using_index(visibility))
11456}
11457
11458pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
11464 let _ = project_using_index(visibility);
11465}
11466
11467fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
11468 let (components, global) = match &binding.target {
11469 EffectiveUsingTarget::Ordinary {
11470 target_components,
11471 global,
11472 ..
11473 } => (target_components, *global),
11474 EffectiveUsingTarget::Namespace {
11475 namespace_components,
11476 global,
11477 } => (namespace_components, *global),
11478 };
11479 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
11480}
11481
11482fn using_binding_target_components_for_name(
11483 binding: &OrdinaryTypeImport,
11484 project: &ProjectUsingIndex,
11485 visibility: &VisibilityIndex<'_>,
11486 file: &ProjectFile,
11487 name: &str,
11488) -> Option<Vec<String>> {
11489 let cpp_source = CppGraphSource::from_source(visibility.cpp(), visibility.token());
11492 let visible_candidates = visibility
11493 .visible_identifier_candidates(file, name)
11494 .filter(|candidate| {
11495 candidate.is_class()
11496 || declared_type_alias(&cpp_source, candidate)
11497 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
11498 })
11499 .collect::<Vec<_>>();
11500 if visible_candidates.is_empty() {
11501 return None;
11502 }
11503 match &binding.target {
11504 EffectiveUsingTarget::Ordinary {
11505 name: imported_name,
11506 ..
11507 } if imported_name == name => {
11508 effective_using_target_tiers(binding)
11509 .into_iter()
11510 .find(|qualified| {
11511 let qualified_name = qualified.join("::");
11512 visible_candidates
11513 .iter()
11514 .any(|candidate| cpp_name_for(candidate) == qualified_name)
11515 })
11516 }
11517 EffectiveUsingTarget::Namespace { .. } => {
11518 visibility.note_using_namespace_lookup_for_test();
11519 let target_tiers = effective_using_target_tiers(binding);
11520 let resolved = target_tiers
11521 .iter()
11522 .find(|namespace_components| {
11523 let namespace = namespace_components.join("::");
11524 visible_candidates.iter().any(|candidate| {
11525 visibility.note_using_name_candidate_inspection_for_test();
11526 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
11527 candidate_namespace == namespace
11528 || candidate_namespace.starts_with(&format!("{namespace}::"))
11529 })
11530 }) || project.directives.iter().any(|candidate| {
11531 candidate.namespace_scope.as_deref()
11532 == Some(namespace_components.as_slice())
11533 }) || project
11534 .ordinary_by_name
11535 .values()
11536 .flatten()
11537 .any(|candidate| {
11538 candidate.namespace_scope.as_deref()
11539 == Some(namespace_components.as_slice())
11540 })
11541 })
11542 .cloned();
11543 resolved.or_else(|| {
11544 (target_tiers.len() == 1)
11550 .then(|| target_tiers.into_iter().next())
11551 .flatten()
11552 })
11553 }
11554 EffectiveUsingTarget::Ordinary { .. } => None,
11555 }
11556}
11557
11558fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
11559 let start = activation.checked_sub(1)?;
11560 let mut node = root.descendant_for_byte_range(start, activation)?;
11561 while node.kind() != "preproc_include" {
11562 node = node.parent()?;
11563 }
11564 Some(node)
11565}
11566
11567fn project_using_bindings(
11568 binding: OrdinaryTypeImport,
11569 visibility: &VisibilityIndex<'_>,
11570 file: &ProjectFile,
11571 root: Node<'_>,
11572 source: &str,
11573) -> Vec<OrdinaryTypeImport> {
11574 if binding.source == *file {
11575 return vec![binding];
11576 }
11577 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
11578 return Vec::new();
11579 }
11580 visibility.note_using_donor_activation_for_test();
11581 let Some(prepared) = visibility.cpp().prepared_syntax(visibility.token(), file) else {
11582 return Vec::new();
11583 };
11584 let projections = visibility
11585 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
11586 .map_or_else(
11587 || {
11588 visibility.conditional_include_projections_for_source(
11589 file,
11590 prepared.as_ref(),
11591 &binding.source,
11592 )
11593 },
11594 |activation_byte| {
11595 Arc::from([ConditionalIncludeProjection {
11596 activation_byte,
11597 required_guards: HashSet::default(),
11598 partial_guards: HashSet::default(),
11599 }])
11600 },
11601 );
11602 projections
11603 .iter()
11604 .cloned()
11605 .filter_map(|projection| {
11606 let required_guards =
11607 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
11608 let mut projected = binding.clone();
11609 projected.required_guards = required_guards;
11610 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
11611 })
11612 .collect()
11613}
11614
11615fn project_using_binding_at_activation(
11616 mut binding: OrdinaryTypeImport,
11617 activation: usize,
11618 root: Node<'_>,
11619 source: &str,
11620) -> Option<OrdinaryTypeImport> {
11621 let include = include_node_for_activation(root, activation)?;
11622 let include_namespace = enclosing_namespace_components(include, source);
11623 let mut declaration_namespace = include_namespace.clone();
11624 declaration_namespace.extend(binding.declaration_namespace);
11625 binding.declaration_namespace = declaration_namespace;
11626 binding.declaration_byte = activation;
11627 if let Some(prefix) = using_named_scope(include, source) {
11628 let mut projected = prefix;
11629 projected.extend(binding.namespace_scope.take().unwrap_or_default());
11630 binding.scope_depth = projected.len();
11631 binding.block_scope = false;
11632 binding.lexical_depth = projected.len();
11633 binding.namespace_scope = Some(projected);
11634 binding.scope_start = 0;
11635 binding.scope_end = usize::MAX;
11636 Some(binding)
11637 } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
11638 binding.namespace_scope = None;
11639 binding.scope_start = start;
11640 binding.scope_end = end;
11641 binding.scope_depth = depth;
11642 binding.block_scope = block_scope;
11643 binding.lexical_depth = include_namespace.len();
11644 Some(binding)
11645 } else {
11646 None
11647 }
11648}
11649
11650pub fn effective_using_bindings_for_name(
11656 visibility: &VisibilityIndex<'_>,
11657 imports: &OrdinaryTypeImportCell,
11658 file: &ProjectFile,
11659 node: Node<'_>,
11660 source: &str,
11661 name: &str,
11662) -> Arc<[OrdinaryTypeImport]> {
11663 imports
11664 .projection_cell(name)
11665 .get_or_init(|| {
11666 let project = project_using_index(visibility);
11667 let name_bindings = project.ordinary_by_name.get(name);
11668 if name_bindings.is_none() && project.directives.is_empty() {
11669 return Arc::from(Vec::new());
11670 }
11671 let root = root_node(node);
11672 let mut projected = Vec::new();
11673 for binding in name_bindings
11674 .into_iter()
11675 .flatten()
11676 .chain(project.directives.iter())
11677 {
11678 if !visibility.source_is_visible(file, &binding.source) {
11679 continue;
11680 }
11681 let target_components = using_binding_target_components_for_name(
11682 binding, project, visibility, file, name,
11683 )
11684 .or_else(|| match &binding.target {
11685 EffectiveUsingTarget::Ordinary {
11686 name: imported_name,
11687 target_components,
11688 ..
11689 } if imported_name == name => Some(target_components.clone()),
11690 EffectiveUsingTarget::Ordinary { .. }
11691 | EffectiveUsingTarget::Namespace { .. } => None,
11692 });
11693 let Some(target_components) = target_components else {
11694 continue;
11695 };
11696 let mut binding = binding.clone();
11697 binding.resolved_target_components = Some(target_components);
11698 projected.extend(project_using_bindings(
11699 binding, visibility, file, root, source,
11700 ));
11701 }
11702 Arc::from(projected)
11703 })
11704 .clone()
11705}
11706
11707pub fn initialized_ordinary_type_imports(
11708 root: Node<'_>,
11709 analyzer: &CppGraphSource<'_>,
11710 visibility: &VisibilityIndex<'_>,
11711 file: &ProjectFile,
11712 source: &str,
11713) -> OrdinaryTypeImportCell {
11714 let cell = visibility.ordinary_type_import_cell(file);
11715 let _ = (root, analyzer, source);
11716 cell
11717}
11718
11719fn effective_using_binding_active(
11724 binding: &OrdinaryTypeImport,
11725 node: Node<'_>,
11726 lexical_scope: &[String],
11727 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11728 visibility: &VisibilityIndex<'_>,
11729 file: &ProjectFile,
11730) -> bool {
11731 effective_using_binding_guards_active(
11732 binding,
11733 node.start_byte(),
11734 reference_guards,
11735 visibility,
11736 file,
11737 ) && binding.namespace_scope.as_ref().map_or_else(
11738 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
11739 |namespace| lexical_scope.starts_with(namespace),
11740 )
11741}
11742
11743fn effective_using_binding_guards_active(
11744 binding: &OrdinaryTypeImport,
11745 reference_byte: usize,
11746 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11747 visibility: &VisibilityIndex<'_>,
11748 file: &ProjectFile,
11749) -> bool {
11750 binding.declaration_byte <= reference_byte
11751 && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
11752 && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11753}
11754
11755fn effective_using_binding_guards_compatible(
11756 binding: &OrdinaryTypeImport,
11757 reference_byte: usize,
11758 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11759 visibility: &VisibilityIndex<'_>,
11760 file: &ProjectFile,
11761) -> bool {
11762 binding.source != *file
11763 && !binding.required_guards.is_empty()
11764 && binding.declaration_byte <= reference_byte
11765 && reference_guards.is_some_and(|active| {
11766 !binding.required_guards.is_subset(active)
11767 && merge_preprocessor_guards(&binding.required_guards, active).is_some()
11768 })
11769 && effective_using_binding_guards_stable_between(binding, reference_byte, visibility, file)
11770}
11771
11772fn effective_using_binding_guards_stable_between(
11773 binding: &OrdinaryTypeImport,
11774 reference_byte: usize,
11775 visibility: &VisibilityIndex<'_>,
11776 file: &ProjectFile,
11777) -> bool {
11778 visibility.preprocessor_guards_stable_between(
11779 file,
11780 binding.declaration_byte,
11781 reference_byte,
11782 &binding.required_guards,
11783 )
11784}
11785
11786#[allow(clippy::too_many_arguments)]
11787fn binding_type_candidates(
11788 binding: &OrdinaryTypeImport,
11789 active_bindings: &[&OrdinaryTypeImport],
11790 analyzer: &CppGraphSource<'_>,
11791 visibility: &VisibilityIndex<'_>,
11792 file: &ProjectFile,
11793 name: &str,
11794 direct_target: Option<&CodeUnit>,
11795 reference_byte: usize,
11796) -> Vec<(CodeUnit, Vec<String>)> {
11797 ordinary_binding_lookup_targets(binding, active_bindings, name)
11798 .into_iter()
11799 .flat_map(|target| {
11800 let mut candidates = visibility
11801 .visible_identifier_candidates(file, name)
11802 .filter(|candidate| {
11803 (candidate.is_class() || declared_type_alias(analyzer, candidate))
11804 && type_candidate_matches_lookup_components(
11805 analyzer,
11806 visibility,
11807 file,
11808 candidate,
11809 reference_byte,
11810 &target,
11811 )
11812 })
11813 .cloned()
11814 .collect::<Vec<_>>();
11815 if candidates.is_empty()
11816 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11817 && let Some(target_unit) = direct_target
11818 {
11819 let expanded_target_name = macro_expanded_cpp_name_components(
11820 visibility,
11821 file,
11822 target_unit,
11823 reference_byte,
11824 );
11825 if (target_unit.is_class() || declared_type_alias(analyzer, target_unit))
11826 && expanded_target_name == target
11827 && visibility.external_type_candidate_visible_at(
11828 file,
11829 target_unit,
11830 reference_byte,
11831 )
11832 {
11833 candidates.push(target_unit.clone());
11834 }
11835 }
11836 if candidates.is_empty()
11837 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
11838 && let Some(target_unit) = direct_target
11839 {
11840 let visible_types = visibility
11841 .visible_identifier_candidates(file, name)
11842 .filter(|candidate| {
11843 candidate.is_class() || declared_type_alias(analyzer, candidate)
11844 })
11845 .collect::<Vec<_>>();
11846 let uniquely_names_target = !visible_types.is_empty()
11847 && visible_types
11848 .iter()
11849 .all(|candidate| same_visible_symbol(candidate, target_unit));
11850 if uniquely_names_target {
11851 candidates.extend(visible_types.into_iter().cloned());
11852 }
11853 }
11854 candidates
11855 .into_iter()
11856 .map(move |candidate| (candidate, target.clone()))
11857 })
11858 .collect()
11859}
11860
11861fn macro_expanded_cpp_name_components(
11862 visibility: &VisibilityIndex<'_>,
11863 file: &ProjectFile,
11864 unit: &CodeUnit,
11865 reference_byte: usize,
11866) -> Vec<String> {
11867 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11868 brokk_bifrost_core::analyzer::Language::Cpp,
11869 &cpp_name_for(unit),
11870 )
11871 .into_iter()
11872 .flat_map(|component| {
11873 macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
11874 })
11875 .collect()
11876}
11877
11878fn macro_expanded_cpp_name_component(
11879 visibility: &VisibilityIndex<'_>,
11880 file: &ProjectFile,
11881 component: String,
11882 reference_byte: usize,
11883) -> Vec<String> {
11884 let Some(replacement) =
11885 visibility.object_macro_replacement_at(file, &component, reference_byte)
11886 else {
11887 return vec![component];
11888 };
11889 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11890 brokk_bifrost_core::analyzer::Language::Cpp,
11891 &replacement,
11892 );
11893 if expanded.is_empty() {
11894 vec![component]
11895 } else {
11896 expanded
11897 }
11898}
11899
11900fn type_candidate_matches_lookup_components(
11908 analyzer: &CppGraphSource<'_>,
11909 visibility: &VisibilityIndex<'_>,
11910 file: &ProjectFile,
11911 candidate: &CodeUnit,
11912 reference_byte: usize,
11913 target: &[String],
11914) -> bool {
11915 let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
11916 if expanded == target {
11917 return true;
11918 }
11919 let Some(cpp) = analyzer.cpp else {
11920 return false;
11921 };
11922 let Some(prepared) = cpp.prepared_syntax(visibility.token(), candidate.source()) else {
11923 return false;
11924 };
11925 let root = prepared.tree().root_node();
11926 for range in analyzer.ranges(candidate) {
11927 let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
11928 else {
11929 continue;
11930 };
11931 let mut namespaces = Vec::<(Vec<String>, bool)>::new();
11932 loop {
11933 if current.kind() == "namespace_definition"
11934 && let Some(name) = current.child_by_field_name("name")
11935 {
11936 let mut components = Vec::new();
11937 if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
11938 && !components.is_empty()
11939 {
11940 let inline = (0..current.child_count())
11941 .filter_map(|index| current.child(index))
11942 .any(|child| !child.is_named() && child.kind() == "inline");
11943 namespaces.push((components, inline));
11944 }
11945 }
11946 let Some(parent) = current.parent() else {
11947 break;
11948 };
11949 current = parent;
11950 }
11951 namespaces.reverse();
11952 let mut namespace_components = Vec::new();
11953 let mut inline_indexes = HashSet::default();
11954 for (components, inline) in namespaces {
11955 for component in components {
11956 let expanded_component =
11957 macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
11958 if inline {
11959 inline_indexes.extend(
11960 namespace_components.len()
11961 ..namespace_components.len() + expanded_component.len(),
11962 );
11963 }
11964 namespace_components.extend(expanded_component);
11965 }
11966 }
11967 if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
11968 continue;
11969 }
11970 let mut reachable = vec![false; target.len() + 1];
11974 reachable[0] = true;
11975 for (index, component) in expanded.iter().enumerate() {
11976 let mut next = vec![false; target.len() + 1];
11977 for (target_index, reached) in reachable.iter().copied().enumerate() {
11978 if !reached {
11979 continue;
11980 }
11981 if inline_indexes.contains(&index) {
11982 next[target_index] = true;
11983 }
11984 if target
11985 .get(target_index)
11986 .is_some_and(|target_component| target_component == component)
11987 {
11988 next[target_index + 1] = true;
11989 }
11990 }
11991 reachable = next;
11992 }
11993 if reachable[target.len()] {
11994 return true;
11995 }
11996 }
11997 false
11998}
11999
12000#[allow(clippy::too_many_arguments)]
12001fn resolved_type_import(
12002 candidates: Vec<(CodeUnit, Vec<String>)>,
12003 lexical_depth: usize,
12004 is_direct: bool,
12005 analyzer: &CppGraphSource<'_>,
12006 visibility: &VisibilityIndex<'_>,
12007 file: &ProjectFile,
12008 direct_target: Option<&CodeUnit>,
12009) -> OrdinaryTypeImportResolution {
12010 let logical = deduplicate_type_import_candidates(candidates);
12011 let selected = match logical.as_slice() {
12012 [] => return OrdinaryTypeImportResolution::Missing,
12013 [only] => only,
12014 several => {
12019 let units = several
12020 .iter()
12021 .map(|(unit, _)| unit)
12022 .collect::<Vec<&CodeUnit>>();
12023 let Some(spelling) = direct_target.and_then(|target| {
12024 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
12025 }) else {
12026 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
12027 };
12028 several
12029 .iter()
12030 .find(|(unit, _)| same_symbol(unit, spelling))
12031 .expect("the selected spelling is one of the imported candidates")
12032 }
12033 };
12034 OrdinaryTypeImportResolution::Resolved {
12035 target: selected.0.clone(),
12036 target_components: selected.1.clone(),
12037 lexical_depth,
12038 is_direct,
12039 }
12040}
12041
12042#[allow(clippy::too_many_arguments)]
12043fn ordinary_type_import_resolution(
12044 node: Node<'_>,
12045 components: &[String],
12046 global: bool,
12047 analyzer: &CppGraphSource<'_>,
12048 visibility: &VisibilityIndex<'_>,
12049 imports: &OrdinaryTypeImportCell,
12050 file: &ProjectFile,
12051 source: &str,
12052 lexical_scope: &[String],
12053 direct_target: Option<&CodeUnit>,
12054) -> OrdinaryTypeImportResolution {
12055 if global || components.len() != 1 {
12056 return OrdinaryTypeImportResolution::Missing;
12057 }
12058 let name = &components[0];
12059 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12060 if bindings.is_empty() {
12064 return OrdinaryTypeImportResolution::Missing;
12065 }
12066 let reference_guards = preprocessor_guard_environment(node, source);
12067 let active = bindings
12068 .iter()
12069 .filter(|binding| {
12070 effective_using_binding_active(
12071 binding,
12072 node,
12073 lexical_scope,
12074 reference_guards.as_ref(),
12075 visibility,
12076 file,
12077 )
12078 })
12079 .collect::<Vec<_>>();
12080 let transitive = bindings
12081 .iter()
12082 .filter(|binding| {
12083 effective_using_binding_guards_active(
12084 binding,
12085 node.start_byte(),
12086 reference_guards.as_ref(),
12087 visibility,
12088 file,
12089 ) && (binding.namespace_scope.is_some()
12090 || (binding.scope_start <= node.start_byte()
12091 && node.end_byte() <= binding.scope_end))
12092 })
12093 .collect::<Vec<_>>();
12094 ordinary_type_import_resolution_for_bindings(
12095 node,
12096 name,
12097 analyzer,
12098 visibility,
12099 file,
12100 lexical_scope,
12101 direct_target,
12102 &active,
12103 &transitive,
12104 )
12105}
12106
12107#[allow(clippy::too_many_arguments)]
12108fn compatible_foreign_type_import_resolution(
12109 node: Node<'_>,
12110 components: &[String],
12111 global: bool,
12112 analyzer: &CppGraphSource<'_>,
12113 visibility: &VisibilityIndex<'_>,
12114 imports: &OrdinaryTypeImportCell,
12115 file: &ProjectFile,
12116 source: &str,
12117 lexical_scope: &[String],
12118 direct_target: Option<&CodeUnit>,
12119) -> OrdinaryTypeImportResolution {
12120 if global || components.len() != 1 {
12121 return OrdinaryTypeImportResolution::Missing;
12122 }
12123 let name = &components[0];
12124 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
12125 if bindings.is_empty() {
12126 return OrdinaryTypeImportResolution::Missing;
12127 }
12128 let reference_guards = preprocessor_guard_environment(node, source);
12129 let compatible = bindings
12130 .iter()
12131 .filter(|binding| {
12132 effective_using_binding_guards_compatible(
12133 binding,
12134 node.start_byte(),
12135 reference_guards.as_ref(),
12136 visibility,
12137 file,
12138 ) && binding.namespace_scope.as_ref().map_or_else(
12139 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
12140 |namespace| lexical_scope.starts_with(namespace),
12141 )
12142 })
12143 .collect::<Vec<_>>();
12144 let transitive = bindings
12145 .iter()
12146 .filter(|binding| {
12147 effective_using_binding_guards_compatible(
12148 binding,
12149 node.start_byte(),
12150 reference_guards.as_ref(),
12151 visibility,
12152 file,
12153 ) && (binding.namespace_scope.is_some()
12154 || (binding.scope_start <= node.start_byte()
12155 && node.end_byte() <= binding.scope_end))
12156 })
12157 .collect::<Vec<_>>();
12158 ordinary_type_import_resolution_for_bindings(
12159 node,
12160 name,
12161 analyzer,
12162 visibility,
12163 file,
12164 lexical_scope,
12165 direct_target,
12166 &compatible,
12167 &transitive,
12168 )
12169}
12170
12171#[allow(clippy::too_many_arguments)]
12172fn ordinary_type_import_resolution_for_bindings(
12173 node: Node<'_>,
12174 name: &str,
12175 analyzer: &CppGraphSource<'_>,
12176 visibility: &VisibilityIndex<'_>,
12177 file: &ProjectFile,
12178 lexical_scope: &[String],
12179 direct_target: Option<&CodeUnit>,
12180 active: &[&OrdinaryTypeImport],
12181 transitive: &[&OrdinaryTypeImport],
12182) -> OrdinaryTypeImportResolution {
12183 let mut concrete_depths = active
12184 .iter()
12185 .filter(|binding| binding.namespace_scope.is_none())
12186 .map(|binding| binding.scope_depth)
12187 .collect::<Vec<_>>();
12188 concrete_depths.sort_unstable();
12189 concrete_depths.dedup();
12190 for depth in concrete_depths.into_iter().rev() {
12191 let at_tier = active
12192 .iter()
12193 .copied()
12194 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
12195 let direct = at_tier
12196 .clone()
12197 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12198 .flat_map(|binding| {
12199 binding_type_candidates(
12200 binding,
12201 transitive,
12202 analyzer,
12203 visibility,
12204 file,
12205 name,
12206 direct_target,
12207 node.start_byte(),
12208 )
12209 })
12210 .collect::<Vec<_>>();
12211 if !direct.is_empty() {
12212 return resolved_type_import(
12213 direct,
12214 lexical_scope.len(),
12215 true,
12216 analyzer,
12217 visibility,
12218 file,
12219 direct_target,
12220 );
12221 }
12222 let directives = at_tier
12223 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12224 .flat_map(|binding| {
12225 binding_type_candidates(
12226 binding,
12227 transitive,
12228 analyzer,
12229 visibility,
12230 file,
12231 name,
12232 direct_target,
12233 node.start_byte(),
12234 )
12235 })
12236 .collect::<Vec<_>>();
12237 if !directives.is_empty() {
12238 return resolved_type_import(
12239 directives,
12240 lexical_scope.len(),
12241 false,
12242 analyzer,
12243 visibility,
12244 file,
12245 direct_target,
12246 );
12247 }
12248 }
12249 for prefix_len in (0..=lexical_scope.len()).rev() {
12250 let tier = &lexical_scope[..prefix_len];
12251 let at_tier = active
12252 .iter()
12253 .copied()
12254 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
12255 let direct = at_tier
12256 .clone()
12257 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
12258 .flat_map(|binding| {
12259 binding_type_candidates(
12260 binding,
12261 transitive,
12262 analyzer,
12263 visibility,
12264 file,
12265 name,
12266 direct_target,
12267 node.start_byte(),
12268 )
12269 })
12270 .collect::<Vec<_>>();
12271 if !direct.is_empty() {
12272 return resolved_type_import(
12273 direct,
12274 prefix_len,
12275 true,
12276 analyzer,
12277 visibility,
12278 file,
12279 direct_target,
12280 );
12281 }
12282 let directives = at_tier
12283 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
12284 .flat_map(|binding| {
12285 binding_type_candidates(
12286 binding,
12287 transitive,
12288 analyzer,
12289 visibility,
12290 file,
12291 name,
12292 direct_target,
12293 node.start_byte(),
12294 )
12295 })
12296 .collect::<Vec<_>>();
12297 if !directives.is_empty() {
12298 return resolved_type_import(
12299 directives,
12300 prefix_len,
12301 false,
12302 analyzer,
12303 visibility,
12304 file,
12305 direct_target,
12306 );
12307 }
12308 }
12309 OrdinaryTypeImportResolution::Missing
12310}
12311
12312#[allow(clippy::too_many_arguments)]
12313pub fn resolve_type_components_lexically_at(
12314 node: Node<'_>,
12315 components: &[String],
12316 global: bool,
12317 analyzer: &CppGraphSource<'_>,
12318 visibility: &VisibilityIndex<'_>,
12319 ordinary_type_imports: &OrdinaryTypeImportCell,
12320 file: &ProjectFile,
12321 source: &str,
12322) -> LexicalTypeResolution {
12323 resolve_type_components_lexically_at_inner(
12324 node,
12325 components,
12326 global,
12327 analyzer,
12328 visibility,
12329 ordinary_type_imports,
12330 file,
12331 source,
12332 None,
12333 false,
12334 false,
12335 false,
12336 None,
12337 )
12338}
12339
12340#[allow(clippy::too_many_arguments)]
12348pub fn resolve_type_components_lexically_at_preserving_alias(
12349 node: Node<'_>,
12350 components: &[String],
12351 global: bool,
12352 analyzer: &CppGraphSource<'_>,
12353 visibility: &VisibilityIndex<'_>,
12354 file: &ProjectFile,
12355 source: &str,
12356) -> LexicalTypeResolution {
12357 let ordinary_type_imports =
12358 initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
12359 resolve_type_components_lexically_at_inner(
12360 node,
12361 components,
12362 global,
12363 analyzer,
12364 visibility,
12365 &ordinary_type_imports,
12366 file,
12367 source,
12368 None,
12369 false,
12370 true,
12371 true,
12372 None,
12373 )
12374}
12375
12376#[allow(clippy::too_many_arguments)]
12377fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
12378 node: Node<'_>,
12379 components: &[String],
12380 global: bool,
12381 analyzer: &CppGraphSource<'_>,
12382 visibility: &VisibilityIndex<'_>,
12383 ordinary_type_imports: &OrdinaryTypeImportCell,
12384 file: &ProjectFile,
12385 source: &str,
12386 scope_cache: Option<&LexicalScopeCache>,
12387) -> LexicalTypeResolution {
12388 resolve_type_components_lexically_at_inner(
12389 node,
12390 components,
12391 global,
12392 analyzer,
12393 visibility,
12394 ordinary_type_imports,
12395 file,
12396 source,
12397 None,
12398 false,
12399 true,
12400 false,
12401 scope_cache,
12402 )
12403}
12404
12405#[allow(clippy::too_many_arguments)]
12406fn resolve_type_components_lexically_at_for_target_with_scope_cache(
12407 node: Node<'_>,
12408 components: &[String],
12409 global: bool,
12410 analyzer: &CppGraphSource<'_>,
12411 visibility: &VisibilityIndex<'_>,
12412 ordinary_type_imports: &OrdinaryTypeImportCell,
12413 file: &ProjectFile,
12414 source: &str,
12415 target: &CodeUnit,
12416 apply_structured_prefilter: bool,
12417 scope_cache: Option<&LexicalScopeCache>,
12418) -> LexicalTypeResolution {
12419 resolve_type_components_lexically_at_inner(
12420 node,
12421 components,
12422 global,
12423 analyzer,
12424 visibility,
12425 ordinary_type_imports,
12426 file,
12427 source,
12428 Some(target),
12429 apply_structured_prefilter,
12430 false,
12431 false,
12432 scope_cache,
12433 )
12434}
12435
12436#[allow(clippy::too_many_arguments)]
12437fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
12438 node: Node<'_>,
12439 components: &[String],
12440 global: bool,
12441 analyzer: &CppGraphSource<'_>,
12442 visibility: &VisibilityIndex<'_>,
12443 ordinary_type_imports: &OrdinaryTypeImportCell,
12444 file: &ProjectFile,
12445 source: &str,
12446 recovered_scope: &[String],
12447) -> LexicalTypeResolution {
12448 resolve_type_components_in_authoritative_scope(
12449 node,
12450 components,
12451 global,
12452 analyzer,
12453 visibility,
12454 ordinary_type_imports,
12455 file,
12456 source,
12457 None,
12458 false,
12459 true,
12460 false,
12461 recovered_scope.to_vec(),
12462 )
12463}
12464
12465#[allow(clippy::too_many_arguments)]
12466fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
12467 node: Node<'_>,
12468 components: &[String],
12469 global: bool,
12470 analyzer: &CppGraphSource<'_>,
12471 visibility: &VisibilityIndex<'_>,
12472 ordinary_type_imports: &OrdinaryTypeImportCell,
12473 file: &ProjectFile,
12474 source: &str,
12475 target: &CodeUnit,
12476 apply_structured_prefilter: bool,
12477 recovered_scope: &[String],
12478) -> LexicalTypeResolution {
12479 resolve_type_components_in_authoritative_scope(
12480 node,
12481 components,
12482 global,
12483 analyzer,
12484 visibility,
12485 ordinary_type_imports,
12486 file,
12487 source,
12488 Some(target),
12489 apply_structured_prefilter,
12490 false,
12491 false,
12492 recovered_scope.to_vec(),
12493 )
12494}
12495
12496#[allow(clippy::too_many_arguments)]
12497fn resolve_type_components_lexically_at_inner(
12498 node: Node<'_>,
12499 components: &[String],
12500 global: bool,
12501 analyzer: &CppGraphSource<'_>,
12502 visibility: &VisibilityIndex<'_>,
12503 ordinary_type_imports: &OrdinaryTypeImportCell,
12504 file: &ProjectFile,
12505 source: &str,
12506 direct_target: Option<&CodeUnit>,
12507 apply_structured_prefilter: bool,
12508 preserve_alias: bool,
12509 allow_compatible_foreign_import: bool,
12510 scope_cache: Option<&LexicalScopeCache>,
12511) -> LexicalTypeResolution {
12512 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12513 let lexical_scope_started = Instant::now();
12514 if report_stats {
12515 eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=started");
12516 }
12517 let lexical_scope = if global {
12518 Vec::new()
12519 } else {
12520 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
12521 node,
12522 analyzer,
12523 visibility,
12524 file,
12525 source,
12526 true,
12527 recovered_macro_decorated_declarator_type(node)
12528 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
12529 scope_cache,
12530 ) {
12531 LexicalScopeResolution::Resolved(scope) => scope,
12532 LexicalScopeResolution::Ambiguous => {
12533 if report_stats {
12534 eprintln!(
12535 "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=ambiguous elapsed_ms={}",
12536 lexical_scope_started.elapsed().as_millis(),
12537 );
12538 }
12539 return LexicalTypeResolution::Ambiguous;
12540 }
12541 LexicalScopeResolution::Missing => {
12542 if report_stats {
12543 eprintln!(
12544 "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed outcome=missing elapsed_ms={}",
12545 lexical_scope_started.elapsed().as_millis(),
12546 );
12547 }
12548 return LexicalTypeResolution::Missing;
12549 }
12550 }
12551 };
12552 if report_stats {
12553 eprintln!(
12554 "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_scope status=completed components={} elapsed_ms={}",
12555 lexical_scope.len(),
12556 lexical_scope_started.elapsed().as_millis(),
12557 );
12558 }
12559 resolve_type_components_lexically_at_scoped(
12560 node,
12561 components,
12562 global,
12563 analyzer,
12564 visibility,
12565 ordinary_type_imports,
12566 file,
12567 source,
12568 direct_target,
12569 apply_structured_prefilter,
12570 preserve_alias,
12571 allow_compatible_foreign_import,
12572 lexical_scope,
12573 )
12574}
12575
12576#[allow(clippy::too_many_arguments)]
12577fn resolve_type_components_lexically_at_scoped(
12578 node: Node<'_>,
12579 components: &[String],
12580 global: bool,
12581 analyzer: &CppGraphSource<'_>,
12582 visibility: &VisibilityIndex<'_>,
12583 ordinary_type_imports: &OrdinaryTypeImportCell,
12584 file: &ProjectFile,
12585 source: &str,
12586 direct_target: Option<&CodeUnit>,
12587 apply_structured_prefilter: bool,
12588 preserve_alias: bool,
12589 allow_compatible_foreign_import: bool,
12590 mut lexical_scope: Vec<String>,
12591) -> LexicalTypeResolution {
12592 if !global
12593 && components.len() == 1
12594 && let Some(target) = direct_target
12597 && lexical_scope
12598 .last()
12599 .is_none_or(|last| last != &components[0])
12600 && has_malformed_wrapper_function_definition_ancestor(node)
12605 && let Some(indexed_namespace) =
12606 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
12607 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
12608 {
12609 lexical_scope = indexed_namespace;
12610 }
12611 resolve_type_components_in_authoritative_scope(
12612 node,
12613 components,
12614 global,
12615 analyzer,
12616 visibility,
12617 ordinary_type_imports,
12618 file,
12619 source,
12620 direct_target,
12621 apply_structured_prefilter,
12622 preserve_alias,
12623 allow_compatible_foreign_import,
12624 lexical_scope,
12625 )
12626}
12627
12628#[allow(clippy::too_many_arguments)]
12640pub(super) fn resolve_type_components_in_authoritative_scope(
12641 node: Node<'_>,
12642 components: &[String],
12643 global: bool,
12644 analyzer: &CppGraphSource<'_>,
12645 visibility: &VisibilityIndex<'_>,
12646 ordinary_type_imports: &OrdinaryTypeImportCell,
12647 file: &ProjectFile,
12648 source: &str,
12649 direct_target: Option<&CodeUnit>,
12650 apply_structured_prefilter: bool,
12651 preserve_alias: bool,
12652 allow_compatible_foreign_import: bool,
12653 lexical_scope: Vec<String>,
12654) -> LexicalTypeResolution {
12655 let resolve = |components: &[String], global: bool, lexical_scope: Vec<String>| {
12656 resolve_unaliased_type_components_in_authoritative_scope(
12657 node,
12658 components,
12659 global,
12660 analyzer,
12661 visibility,
12662 ordinary_type_imports,
12663 file,
12664 source,
12665 direct_target,
12666 apply_structured_prefilter,
12667 preserve_alias,
12668 allow_compatible_foreign_import,
12669 lexical_scope,
12670 )
12671 };
12672 let binding =
12673 visibility.bind_namespace_qualifier(file, node, components, global, &lexical_scope);
12674 let Some(depth) = binding.depth() else {
12675 return resolve(components, global, lexical_scope);
12676 };
12677 if let LexicalTypeResolution::Resolved {
12678 components: resolved,
12679 ..
12680 } = visibility.resolve_type_components_lexically(
12681 analyzer,
12682 file,
12683 components,
12684 global,
12685 &lexical_scope,
12686 ) && resolved.len().saturating_sub(components.len()) > depth
12687 {
12688 return resolve(components, global, lexical_scope);
12689 }
12690 match binding {
12691 NamespaceQualifierBinding::Bound {
12692 components,
12693 global,
12694 lexical_scope,
12695 ..
12696 } => resolve(&components, global, lexical_scope),
12697 NamespaceQualifierBinding::Unresolved { .. } => LexicalTypeResolution::Ambiguous,
12698 NamespaceQualifierBinding::Unaliased => unreachable!("an unaliased binding has no depth"),
12699 }
12700}
12701
12702#[allow(clippy::too_many_arguments)]
12703fn resolve_unaliased_type_components_in_authoritative_scope(
12704 node: Node<'_>,
12705 components: &[String],
12706 global: bool,
12707 analyzer: &CppGraphSource<'_>,
12708 visibility: &VisibilityIndex<'_>,
12709 ordinary_type_imports: &OrdinaryTypeImportCell,
12710 file: &ProjectFile,
12711 source: &str,
12712 direct_target: Option<&CodeUnit>,
12713 apply_structured_prefilter: bool,
12714 preserve_alias: bool,
12715 allow_compatible_foreign_import: bool,
12716 lexical_scope: Vec<String>,
12717) -> LexicalTypeResolution {
12718 if apply_structured_prefilter
12719 && direct_target.is_some()
12720 && !preserve_alias
12721 && !global
12722 && components.len() == 1
12723 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
12724 {
12725 return LexicalTypeResolution::Missing;
12726 }
12727 if !global
12735 && components.len() == 1
12736 && (recovered_macro_decorated_type_node(node).is_some()
12737 || macro_modifier_decorated_type_node(node))
12738 && let Some(resolution) = recovered_same_file_type_alias_resolution(
12739 node,
12740 components,
12741 analyzer,
12742 visibility,
12743 file,
12744 direct_target,
12745 &lexical_scope,
12746 )
12747 {
12748 return resolution;
12749 }
12750 if apply_structured_prefilter
12751 && let Some(target) = direct_target
12752 && !preserve_alias
12753 && !visibility.structured_type_reference_may_resolve_to_target(
12754 analyzer,
12755 file,
12756 components,
12757 global,
12758 &lexical_scope,
12759 target,
12760 )
12761 {
12762 return LexicalTypeResolution::Missing;
12763 }
12764 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
12765 let lexical_normal_started = Instant::now();
12766 if report_stats {
12767 eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=started");
12768 }
12769 let normal = if preserve_alias {
12770 visibility.resolve_type_components_lexically_for_forward(
12771 analyzer,
12772 file,
12773 components,
12774 global,
12775 &lexical_scope,
12776 )
12777 } else {
12778 direct_target.map_or_else(
12779 || {
12780 visibility.resolve_type_components_lexically(
12781 analyzer,
12782 file,
12783 components,
12784 global,
12785 &lexical_scope,
12786 )
12787 },
12788 |target| {
12789 visibility.resolve_type_components_lexically_for_target(
12790 analyzer,
12791 file,
12792 components,
12793 global,
12794 &lexical_scope,
12795 target,
12796 )
12797 },
12798 )
12799 };
12800 if report_stats {
12801 eprintln!(
12802 "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=lexical_normal status=completed elapsed_ms={}",
12803 lexical_normal_started.elapsed().as_millis(),
12804 );
12805 }
12806 let normal = match normal {
12807 LexicalTypeResolution::Resolved { ref unit, .. }
12808 if !visibility
12809 .external_type_candidate_visible_in_context(analyzer, file, unit, node)
12810 && !direct_target.is_some_and(|target| {
12811 let candidate_refs = visibility
12812 .visible_identifier_candidates(file, target.identifier())
12813 .collect::<Vec<_>>();
12814 visibility.c_tag_declaration_family_matches_target(
12815 analyzer,
12816 file,
12817 &candidate_refs,
12818 target,
12819 )
12820 }) =>
12821 {
12822 LexicalTypeResolution::Missing
12823 }
12824 resolution => resolution,
12825 };
12826 let normal_depth = match &normal {
12827 LexicalTypeResolution::Resolved { components, .. } => {
12828 Some(components.len().saturating_sub(1))
12829 }
12830 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
12831 };
12832 let ordinary_import_started = Instant::now();
12838 if report_stats {
12839 eprintln!("BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=started");
12840 }
12841 let ordinary_import_resolution = ordinary_type_import_resolution(
12842 node,
12843 components,
12844 global,
12845 analyzer,
12846 visibility,
12847 ordinary_type_imports,
12848 file,
12849 source,
12850 &lexical_scope,
12851 direct_target,
12852 );
12853 if report_stats {
12854 eprintln!(
12855 "BIFROST_CPP_TYPE_LEXICAL_PHASE phase=ordinary_import status=completed elapsed_ms={}",
12856 ordinary_import_started.elapsed().as_millis(),
12857 );
12858 }
12859 let resolution = match ordinary_import_resolution {
12860 OrdinaryTypeImportResolution::Missing => normal,
12861 OrdinaryTypeImportResolution::Resolved {
12862 lexical_depth,
12863 is_direct,
12864 ..
12865 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
12866 || normal_depth.is_some_and(|depth| {
12867 depth > lexical_depth || (!is_direct && depth == lexical_depth)
12868 }) =>
12869 {
12870 normal
12871 }
12872 OrdinaryTypeImportResolution::Resolved {
12873 target,
12874 target_components,
12875 ..
12876 } => visibility.resolve_imported_type_candidate(
12877 analyzer,
12878 file,
12879 &target,
12880 &target_components,
12881 direct_target,
12882 preserve_alias,
12883 ),
12884 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
12885 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
12886 {
12887 normal
12888 }
12889 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12890 };
12891 if !allow_compatible_foreign_import || !matches!(resolution, LexicalTypeResolution::Missing) {
12892 return resolution;
12893 }
12894
12895 match compatible_foreign_type_import_resolution(
12902 node,
12903 components,
12904 global,
12905 analyzer,
12906 visibility,
12907 ordinary_type_imports,
12908 file,
12909 source,
12910 &lexical_scope,
12911 None,
12912 ) {
12913 OrdinaryTypeImportResolution::Missing => resolution,
12914 OrdinaryTypeImportResolution::Resolved {
12915 target,
12916 target_components,
12917 ..
12918 } => visibility.resolve_imported_type_candidate(
12919 analyzer,
12920 file,
12921 &target,
12922 &target_components,
12923 None,
12924 true,
12925 ),
12926 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
12927 }
12928}
12929
12930fn recovered_same_file_type_alias_resolution(
12931 node: Node<'_>,
12932 components: &[String],
12933 analyzer: &CppGraphSource<'_>,
12934 visibility: &VisibilityIndex<'_>,
12935 file: &ProjectFile,
12936 direct_target: Option<&CodeUnit>,
12937 lexical_scope: &[String],
12938) -> Option<LexicalTypeResolution> {
12939 debug_assert_eq!(components.len(), 1);
12940 debug_assert!(
12941 recovered_macro_decorated_type_node(node).is_some()
12942 || macro_modifier_decorated_type_node(node)
12943 );
12944 let alias_provider = analyzer.type_alias_provider()?;
12945 for qualified in lexical_component_tiers(components, false, lexical_scope) {
12946 let candidates = visibility
12947 .visible_identifier_candidates(file, &components[0])
12948 .filter(|candidate| {
12949 candidate.source() == file
12950 && canonical_cpp_scope_components(candidate) == qualified
12951 && visibility
12952 .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
12953 })
12954 .collect::<Vec<_>>();
12955 if candidates.is_empty() {
12956 continue;
12957 }
12958 if candidates
12959 .iter()
12960 .any(|candidate| !alias_provider.is_type_alias(candidate))
12961 {
12962 return None;
12963 }
12964 let unit = if let Some(target) = direct_target {
12965 visibility.unique_type_candidate_preserving_target(
12966 analyzer,
12967 file,
12968 &candidates,
12969 target,
12970 )?
12971 } else {
12972 let first = candidates[0];
12973 if candidates
12974 .iter()
12975 .any(|candidate| !same_visible_symbol(candidate, first))
12976 {
12977 return None;
12978 }
12979 first.clone()
12980 };
12981 return Some(LexicalTypeResolution::Resolved {
12982 unit,
12983 components: qualified,
12984 candidates: candidates.into_iter().cloned().collect(),
12985 });
12986 }
12987 None
12988}
12989
12990fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
12991 matches!(
12992 structured_owner_context_resolution(node, ctx),
12993 StructuredOwnerContextResolution::SelfTarget
12994 | StructuredOwnerContextResolution::InheritedTarget
12995 )
12996}
12997
12998fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13003 let Some(call) = ctx.ancestry.parent(node).filter(|parent| {
13004 parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
13005 }) else {
13006 return matches!(
13007 structured_owner_context_resolution(node, ctx),
13008 StructuredOwnerContextResolution::InheritedTarget
13009 );
13010 };
13011 let Some(target_owner) = ctx.spec.owner.as_ref() else {
13012 return false;
13013 };
13014 let chain = structured_enclosing_owner_chain(node, ctx);
13015 let Some(innermost) = chain.first() else {
13016 return false;
13017 };
13018 if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13019 return false;
13020 }
13021 let Some(arity) = ctx
13022 .visibility
13023 .call_arity_evidence(ctx.file, call, ctx.source)
13024 .exact()
13025 else {
13026 return false;
13027 };
13028 for enclosing_owner in &chain {
13029 if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13030 return true;
13031 }
13032 match resolve_declaring_callable_owner(
13033 &ctx.analyzer,
13034 ctx.visibility,
13035 ctx.file,
13036 cached_declaring_member_owner(enclosing_owner, ctx),
13037 &ctx.spec.member_name,
13038 arity,
13039 ) {
13040 EnclosingMemberOwnerResolution::Owner(owner) => {
13041 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13042 }
13043 EnclosingMemberOwnerResolution::Ambiguous => return false,
13044 EnclosingMemberOwnerResolution::Missing => {}
13047 }
13048 }
13049 false
13050}
13051
13052fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13053 matches!(
13054 structured_owner_context_resolution(node, ctx),
13055 StructuredOwnerContextResolution::NonTarget
13056 )
13057}
13058
13059fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13060 let Some(target_owner) = ctx.spec.owner.as_ref() else {
13061 return false;
13062 };
13063 let mut current = ctx.ancestry.parent(node);
13064 while let Some(parent) = current {
13065 if parent.kind() == "function_definition" {
13066 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
13067 return false;
13068 };
13069 if let Some(owners) = out_of_line_member_definition_owner(
13070 &ctx.analyzer,
13071 ctx.visibility,
13072 ctx.file,
13073 ctx.source,
13074 owner_lookup,
13075 ) && let Some((_, owner)) = owners.innermost()
13076 {
13077 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
13078 }
13079 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
13080 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
13081 }
13082 return false;
13083 }
13084 current = ctx.ancestry.parent(parent);
13085 }
13086 false
13087}
13088
13089#[derive(Clone, Copy)]
13090enum StructuredOwnerContextResolution {
13091 SelfTarget,
13094 InheritedTarget,
13100 NonTarget,
13101 Ambiguous,
13102 Missing,
13103}
13104
13105fn structured_owner_context_resolution(
13106 node: Node<'_>,
13107 ctx: &ScanCtx<'_>,
13108) -> StructuredOwnerContextResolution {
13109 let Some(target_owner) = ctx.spec.owner.as_ref() else {
13110 return StructuredOwnerContextResolution::Missing;
13111 };
13112 let chain = structured_enclosing_owner_chain(node, ctx);
13113 let Some(innermost) = chain.first() else {
13114 return StructuredOwnerContextResolution::Missing;
13115 };
13116 if receiver_owner_matches_target(innermost, target_owner, node.start_byte(), ctx) {
13117 return StructuredOwnerContextResolution::SelfTarget;
13118 }
13119 for enclosing_owner in &chain {
13124 if receiver_owner_matches_target(enclosing_owner, target_owner, node.start_byte(), ctx) {
13125 return StructuredOwnerContextResolution::InheritedTarget;
13126 }
13127 match cached_declaring_member_owner(enclosing_owner, ctx) {
13128 EnclosingMemberOwnerResolution::Owner(owner)
13129 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
13130 {
13131 return StructuredOwnerContextResolution::InheritedTarget;
13132 }
13133 EnclosingMemberOwnerResolution::Owner(_) => {
13134 return StructuredOwnerContextResolution::NonTarget;
13135 }
13136 EnclosingMemberOwnerResolution::Ambiguous => {
13137 return StructuredOwnerContextResolution::Ambiguous;
13138 }
13139 EnclosingMemberOwnerResolution::Missing => {}
13140 }
13141 }
13142 StructuredOwnerContextResolution::Missing
13143}
13144
13145fn cached_declaring_member_owner(
13146 receiver_owner: &CodeUnit,
13147 ctx: &ScanCtx<'_>,
13148) -> EnclosingMemberOwnerResolution {
13149 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
13150 return cached;
13151 }
13152 let resolved = resolve_declaring_member_owner(
13153 &ctx.analyzer,
13154 ctx.visibility,
13155 ctx.file,
13156 receiver_owner,
13157 &ctx.spec.member_name,
13158 );
13159 let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
13160 indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
13161 } else {
13162 resolved
13163 };
13164 ctx.member_owner_cache
13165 .borrow_mut()
13166 .insert(receiver_owner.clone(), resolved.clone());
13167 resolved
13168}
13169
13170fn indexed_declaring_owner_for_recovered_member(
13177 receiver_owner: &CodeUnit,
13178 ctx: &ScanCtx<'_>,
13179) -> EnclosingMemberOwnerResolution {
13180 let Some(spec_owner) = ctx.spec.owner.as_ref() else {
13181 return EnclosingMemberOwnerResolution::Missing;
13182 };
13183 if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
13184 return EnclosingMemberOwnerResolution::Missing;
13185 }
13186 let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
13187 return EnclosingMemberOwnerResolution::Missing;
13188 };
13189 let Some(receiver_owner) =
13190 ctx.visibility
13191 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
13192 else {
13193 return EnclosingMemberOwnerResolution::Ambiguous;
13194 };
13195 let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
13196 ctx.visibility
13197 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
13198 }) else {
13199 return EnclosingMemberOwnerResolution::Missing;
13200 };
13201
13202 let owner_declares_member = |owner: &CodeUnit| {
13203 if same_visible_symbol(owner, &target_owner) {
13204 return true;
13205 }
13206 let mut member_fq = owner.fq().clone();
13207 member_fq.push(
13208 ctx.spec
13209 .target
13210 .fq()
13211 .last()
13212 .expect("a method target has a terminal member segment"),
13213 );
13214 ctx.analyzer
13215 .definitions(&member_fq.display(segment_interner()))
13216 .any(|child| child.is_function())
13217 };
13218 if owner_declares_member(&receiver_owner) {
13219 return EnclosingMemberOwnerResolution::Owner(receiver_owner);
13220 }
13221
13222 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
13223 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
13224 let mut declaring_owner = None;
13225 while let Some(raw_owner) = stack.pop() {
13226 let Some(owner) =
13227 ctx.visibility
13228 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
13229 else {
13230 return EnclosingMemberOwnerResolution::Ambiguous;
13231 };
13232 let propagated = propagated_counts.entry(owner.clone()).or_default();
13233 if *propagated == 2 {
13234 continue;
13235 }
13236 *propagated += 1;
13237 if owner_declares_member(&owner) {
13238 if declaring_owner.is_some() {
13239 return EnclosingMemberOwnerResolution::Ambiguous;
13240 }
13241 declaring_owner = Some(owner);
13242 continue;
13243 }
13244 stack.extend(hierarchy.get_direct_ancestors(&owner));
13245 }
13246 declaring_owner
13247 .map(EnclosingMemberOwnerResolution::Owner)
13248 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
13249}
13250
13251fn structured_enclosing_owner_chain(node: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
13254 let Some(innermost) = structured_enclosing_owner(node, ctx) else {
13255 return Vec::new();
13256 };
13257 brokk_bifrost_core::analyzer::usages::common::enclosing_class_chain(innermost, |unit| {
13260 ctx.analyzer.parent_of(unit)
13261 })
13262 .collect()
13263}
13264
13265fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13266 if (has_recovered_class_shape_ancestor(node)
13271 || has_malformed_wrapper_function_definition_ancestor(node))
13272 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
13273 {
13274 return Some(owner);
13275 }
13276 let mut current = ctx.ancestry.parent(node);
13277 while let Some(parent) = current {
13278 if parent.kind() == "function_definition" {
13279 let owner_lookup = function_definition_owner_lookup_node(parent);
13280 if let Some(owner_lookup) = owner_lookup
13281 && let Some(owners) = out_of_line_member_definition_owner(
13282 &ctx.analyzer,
13283 ctx.visibility,
13284 ctx.file,
13285 ctx.source,
13286 owner_lookup,
13287 )
13288 && let Some((_, owner)) = owners.innermost()
13289 {
13290 return Some(owner.clone());
13291 }
13292 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
13293 return Some(owner);
13294 }
13295 if let Some(owner) = enclosing_context(parent, ctx)
13296 .owner
13297 .filter(|owner| owner.is_class())
13298 {
13299 return Some(owner);
13300 }
13301 if let Some(owner_lookup) = owner_lookup
13302 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
13303 {
13304 return Some(owner);
13305 }
13306 break;
13307 }
13308 current = ctx.ancestry.parent(parent);
13309 }
13310 enclosing_context(node, ctx)
13311 .owner
13312 .filter(|owner| owner.is_class())
13313}
13314
13315fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
13316 let target_owner = ctx.spec.owner.as_ref()?;
13317 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
13318 let owner_name = owner_components.last()?;
13319 let mut candidates = Vec::new();
13320 for candidate in ctx
13321 .visibility
13322 .visible_identifier_candidates(ctx.file, owner_name)
13323 .filter(|candidate| candidate.is_class())
13324 {
13325 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
13326 brokk_bifrost_core::analyzer::Language::Cpp,
13327 &cpp_name_for(candidate),
13328 );
13329 if !components.ends_with(&owner_components)
13330 || candidates
13331 .iter()
13332 .any(|existing| same_logical_symbol(existing, candidate))
13333 {
13334 continue;
13335 }
13336 candidates.push(candidate.clone());
13337 }
13338 let [candidate] = candidates.as_slice() else {
13339 return None;
13340 };
13341 (same_logical_symbol(candidate, target_owner)
13342 && target_group_contains_owner_peer(candidate, ctx))
13343 .then(|| candidate.clone())
13344}