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