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