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