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