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