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