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