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