1use crate::call_match::{
2 CppArgType, cpp_filter_candidates_by_args, cpp_literal_arg_type, cpp_signature_param_types,
3 cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6 CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7 cpp_sentinel_recovered_scope_for_node, node_text, recovered_macro_return_type_node,
8};
9use crate::graph::CppGraphSource;
10use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
11use crate::graph::hits::{
12 enclosing_context, is_member_field_own_declarator, push_definition_hit, push_hit,
13 push_recursive_reference_hit, push_self_receiver_hit, push_type_hit,
14 push_unproven_definition_hit, push_unproven_hit,
15};
16use crate::graph::resolver::*;
17use crate::graph::syntax::qualified_callable_value;
18use crate::graph_support::CppSource;
19use brokk_bifrost_core::analyzer::fq_name::segment_interner;
20use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
21use brokk_bifrost_core::analyzer::usages::common::same_node;
22use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
23use brokk_bifrost_core::analyzer::usages::local_inference::{
24 LocalInferenceConfig, LocalInferenceEngine,
25};
26use brokk_bifrost_core::analyzer::usages::model::UsageHit;
27use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
28use brokk_bifrost_core::hash::{HashMap, HashSet};
29#[cfg(any(test, feature = "test-support"))]
30use std::cell::Cell;
31use std::cell::RefCell;
32use std::collections::BTreeSet;
33use std::sync::Arc;
34use tree_sitter::Node;
35
36#[cfg(any(test, feature = "test-support"))]
37thread_local! {
38 pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
39}
40
41pub struct ScanState<'a> {
42 pub max_usages: usize,
43 pub hits: &'a mut BTreeSet<UsageHit>,
44 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
45 pub raw_match_count: &'a mut usize,
46 pub limit_exceeded: &'a mut bool,
47}
48
49pub struct ScanCtx<'a> {
50 pub analyzer: CppGraphSource<'a>,
51 pub visibility: &'a VisibilityIndex<'a>,
52 pub file: &'a ProjectFile,
53 pub source: &'a str,
54 ordinary_type_imports: OrdinaryTypeImportCell,
55 recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
56 class_ranges: Option<&'a ClassRangeIndex>,
57 pub line_starts: &'a [usize],
58 pub spec: &'a TargetSpec,
59 pub target_group: &'a HashSet<CodeUnit>,
60 pub has_physically_visible_type_target: bool,
61 type_reference_component_names: HashSet<String>,
62 pub target_declaration_ranges: Vec<Range>,
63 orphaned_namespaces: Vec<OrphanedNamespaceEnvelope>,
64 pub bindings: LocalInferenceEngine<CppScanBinding>,
65 local_shadows: LocalInferenceEngine<()>,
66 using_enum_owners: ScopedUsingEnumOwners,
67 semantic_using_enum_owners: SemanticUsingEnumOwners,
68 needs_using_enum_member_resolution: bool,
69 pub hits: &'a mut BTreeSet<UsageHit>,
70 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
71 pub raw_match_count: &'a mut usize,
72 pub max_usages: usize,
73 pub limit_exceeded: &'a mut bool,
74 pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
75 pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
76 lexical_scope_cache: LexicalScopeCache,
77 lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
78 member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
79 global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
80 receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
81}
82
83impl ScanCtx<'_> {
84 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
85 cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
86 }
87}
88
89#[derive(Clone, Default)]
90pub struct EnclosingContext {
91 pub enclosing: Option<CodeUnit>,
92 pub owner: Option<CodeUnit>,
93}
94
95pub fn prepare_file(cpp: &dyn CppSource, file: &ProjectFile) -> Option<Arc<PreparedSyntaxTree>> {
96 cpp.prepared_syntax(file)
97}
98
99#[allow(clippy::too_many_arguments)]
100pub fn scan_prepared_file(
101 analyzer: &CppGraphSource<'_>,
102 visibility: &VisibilityIndex<'_>,
103 file: &ProjectFile,
104 prepared: &PreparedSyntaxTree,
105 recovered_sentinel_classes: &[CppSentinelRecoveredClass],
106 class_ranges: Option<&ClassRangeIndex>,
107 spec: &TargetSpec,
108 target_group: &HashSet<CodeUnit>,
109 state: &mut ScanState<'_>,
110) {
111 if *state.limit_exceeded {
112 return;
113 }
114 let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
115 let has_physically_visible_type_target = spec.kind == TargetKind::Type
116 && target_group.iter().any(|target| {
117 same_logical_symbol(target, &spec.target)
118 && visibility.is_physically_visible(file, target)
119 });
120 if spec.kind == TargetKind::Type
121 && !has_physically_visible_type_target
122 && visibility
123 .visible_identifier_candidates(file, spec.target.identifier())
124 .any(|candidate| {
125 candidate != &spec.target
126 && !target_group.contains(candidate)
127 && same_logical_symbol(candidate, &spec.target)
128 && visibility.is_physically_visible(file, candidate)
129 })
130 {
131 return;
132 }
133 let target_declaration_ranges = if spec.kind == TargetKind::Type {
134 target_group
135 .iter()
136 .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
137 .flat_map(|target| analyzer.ranges(target))
138 .collect()
139 } else if spec.target.source() == file {
140 analyzer.ranges(&spec.target)
141 } else {
142 Vec::new()
143 };
144 let ordinary_type_imports = initialized_ordinary_type_imports(
145 prepared.tree().root_node(),
146 analyzer,
147 visibility,
148 file,
149 prepared.source(),
150 );
151 let type_reference_component_names = if spec.kind == TargetKind::Type {
152 visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
153 } else {
154 HashSet::default()
155 };
156 let orphaned_namespaces = if spec.kind == TargetKind::Type
157 && prepared.tree().root_node().has_error()
158 {
159 collect_orphaned_namespace_type_envelopes(prepared.tree().root_node(), prepared.source())
160 } else {
161 Vec::new()
162 };
163 let mut ctx = ScanCtx {
164 analyzer: *analyzer,
165 visibility,
166 file,
167 source: prepared.source(),
168 ordinary_type_imports,
169 recovered_sentinel_classes,
170 class_ranges,
171 line_starts: prepared.line_starts(),
172 spec,
173 target_group,
174 has_physically_visible_type_target,
175 type_reference_component_names,
176 target_declaration_ranges,
177 orphaned_namespaces,
178 bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
179 local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
180 using_enum_owners: ScopedUsingEnumOwners::new(),
181 semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
182 needs_using_enum_member_resolution,
183 hits: state.hits,
184 unproven_hits: state.unproven_hits,
185 raw_match_count: state.raw_match_count,
186 max_usages: state.max_usages,
187 limit_exceeded: state.limit_exceeded,
188 enclosing_cache: RefCell::new(HashMap::default()),
189 enclosing_owner_cache: RefCell::new(HashMap::default()),
190 lexical_scope_cache: RefCell::new(HashMap::default()),
191 lexical_free_function_cache: RefCell::new(HashMap::default()),
192 member_owner_cache: RefCell::new(HashMap::default()),
193 global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
194 receiver_canonical_type_cache: RefCell::new(HashMap::default()),
195 };
196 if needs_using_enum_member_resolution {
197 collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
198 }
199 scan_node(prepared.tree().root_node(), &mut ctx);
200}
201
202enum UsingEnumDeclarationScope {
203 Block,
204 Class(CodeUnit),
205 Namespace(Vec<String>),
206 UnsupportedClass,
207}
208
209fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
210 let mut current = node.parent();
211 while let Some(parent) = current {
212 if matches!(
213 parent.kind(),
214 "compound_statement"
215 | "function_definition"
216 | "lambda_expression"
217 | "for_statement"
218 | "while_statement"
219 | "if_statement"
220 ) {
221 return UsingEnumDeclarationScope::Block;
222 }
223 if matches!(
224 parent.kind(),
225 "class_specifier" | "struct_specifier" | "union_specifier"
226 ) {
227 let resolution = enclosing_lexical_scope_components(
228 node,
229 &ctx.analyzer,
230 ctx.visibility,
231 ctx.file,
232 ctx.source,
233 );
234 if let LexicalScopeResolution::Resolved(components) = resolution
235 && let LexicalTypeResolution::Resolved { unit, .. } =
236 ctx.visibility.resolve_type_components_lexically(
237 &ctx.analyzer,
238 ctx.file,
239 &components,
240 true,
241 &[],
242 )
243 {
244 return UsingEnumDeclarationScope::Class(unit);
245 }
246 return UsingEnumDeclarationScope::UnsupportedClass;
247 }
248 current = parent.parent();
249 }
250 UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
251}
252
253fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
254 let mut stack = vec![root];
255 while let Some(node) = stack.pop() {
256 if node.kind() == "using_declaration"
257 && let LexicalTypeResolution::Resolved { unit, .. } =
258 resolve_using_enum_declaration_owner(
259 node,
260 &ctx.analyzer,
261 ctx.visibility,
262 &ctx.ordinary_type_imports,
263 ctx.file,
264 ctx.source,
265 )
266 {
267 match using_enum_declaration_scope(node, ctx) {
268 UsingEnumDeclarationScope::Block => {}
269 UsingEnumDeclarationScope::Class(class) => {
270 ctx.semantic_using_enum_owners.import_class(class, unit);
271 }
272 UsingEnumDeclarationScope::Namespace(namespace) => {
273 ctx.semantic_using_enum_owners.import_namespace(
274 namespace,
275 node.start_byte(),
276 unit,
277 );
278 }
279 UsingEnumDeclarationScope::UnsupportedClass => {}
280 }
281 }
282 for index in (0..node.named_child_count()).rev() {
283 if let Some(child) = node.named_child(index) {
284 stack.push(child);
285 }
286 }
287 }
288}
289
290fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
291 if *ctx.limit_exceeded {
292 return;
293 }
294 let enters_scope = matches!(
295 node.kind(),
296 "compound_statement"
297 | "function_definition"
298 | "lambda_expression"
299 | "for_statement"
300 | "for_range_loop"
301 | "while_statement"
302 | "if_statement"
303 );
304 let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
305 && (enters_scope
306 || matches!(
307 node.kind(),
308 "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
309 ));
310 if enters_scope {
311 ctx.bindings.enter_scope();
312 ctx.local_shadows.enter_scope();
313 }
314 if enters_using_enum_scope {
315 ctx.using_enum_owners.enter_scope();
316 }
317
318 seed_declarations(node, ctx);
319 maybe_record_hit(node, ctx);
320
321 let mut cursor = node.walk();
322 for child in node.named_children(&mut cursor) {
323 scan_node(child, ctx);
324 if *ctx.limit_exceeded {
325 break;
326 }
327 }
328
329 if enters_scope {
330 ctx.bindings.exit_scope();
331 ctx.local_shadows.exit_scope();
332 }
333 if enters_using_enum_scope {
334 ctx.using_enum_owners.exit_scope();
335 }
336}
337
338fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
339 if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
340 || (ctx.spec.kind != TargetKind::Type
341 && indexed_recovered_class_field_declaration(node, ctx))
342 {
343 return;
344 }
345 match node.kind() {
346 "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
347 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
348 "for_range_loop" => seed_range_binding(node, ctx),
349 "expression_statement" => seed_function_macro_local_binding(node, ctx),
350 "using_declaration" => seed_using_enum(node, ctx),
351 _ => {}
352 }
353}
354
355fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
356 let Some(binding) = ctx
357 .visibility
358 .function_macro_local_binding(ctx.file, node, ctx.source)
359 else {
360 return;
361 };
362 if ctx.spec.kind == TargetKind::Type {
363 ctx.bindings.declare_shadow(binding.name);
364 return;
365 }
366 let normalized = normalize_cpp_type_name(&binding.type_name);
367 let unit = binding
368 .type_node
369 .and_then(|type_node| {
370 ctx.visibility
371 .resolve_type_node_result(ctx.file, type_node, ctx.source)
372 .ok()
373 .flatten()
374 })
375 .or_else(|| {
376 ctx.visibility
377 .canonical_type_for_reference(ctx.file, &normalized)
378 })
379 .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
380 ctx.bindings.seed_symbol(
381 binding.name,
382 CppScanBinding::from_type_name(
383 normalized,
384 unit,
385 binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
386 ),
387 );
388}
389
390fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
391 if node.kind() != "declaration"
392 || !has_function_scope_ancestor(node)
393 || has_ancestor_kind(node, "lambda_expression")
394 || !(has_recovered_class_shape_ancestor(node)
395 || has_malformed_wrapper_function_definition_ancestor(node))
396 {
397 return false;
398 }
399 let context = enclosing_context(node, ctx);
400 context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
401 && context.owner.as_ref().is_some_and(CodeUnit::is_class)
402}
403
404fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
405 if !ctx.needs_using_enum_member_resolution {
406 return;
407 }
408 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
409 node,
410 &ctx.analyzer,
411 ctx.visibility,
412 &ctx.ordinary_type_imports,
413 ctx.file,
414 ctx.source,
415 ) && matches!(
416 using_enum_declaration_scope(node, ctx),
417 UsingEnumDeclarationScope::Block
418 ) {
419 ctx.using_enum_owners.import(unit);
420 }
421}
422
423fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
424 if node.kind() == "field_declaration" {
429 return;
430 }
431 let type_node = node
432 .child_by_field_name("type")
433 .or_else(|| first_type_child(node));
434 let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
435 let mut cursor = node.walk();
436 for child in node.named_children(&mut cursor) {
437 let declarator = if child.kind() == "init_declarator" {
438 child.child_by_field_name("declarator")
439 } else if is_declarator_node(child) {
440 Some(child)
441 } else {
442 None
443 };
444 let Some(declarator) = declarator else {
445 continue;
446 };
447 let Some(name) = extract_variable_name(declarator, ctx.source) else {
448 continue;
449 };
450 if declarator.kind() == "function_declarator"
451 && !constructor_style_local_declaration(
452 ctx.visibility,
453 ctx.file,
454 ctx.source,
455 declarator,
456 type_text.as_deref(),
457 &ctx.bindings,
458 )
459 {
460 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
461 ctx.local_shadows.declare_shadow(name);
462 }
463 continue;
464 }
465 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
466 ctx.local_shadows.declare_shadow(name.clone());
467 }
468 if ctx.spec.kind == TargetKind::Type {
469 ctx.bindings.declare_shadow(name);
470 continue;
471 }
472 let value = child.child_by_field_name("value");
473 seed_binding_from_type_or_value(&name, type_node, value, ctx);
474 }
475}
476
477fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
478 if !parameter_belongs_to_callable_scope(node) {
479 return;
480 }
481 let Some(declarator) = node.child_by_field_name("declarator") else {
482 return;
483 };
484 let Some(name) = extract_variable_name(declarator, ctx.source) else {
485 return;
486 };
487 if has_function_scope_ancestor(node) {
488 ctx.local_shadows.declare_shadow(name.clone());
489 }
490 if ctx.spec.kind == TargetKind::Type {
491 ctx.bindings.declare_shadow(name);
492 return;
493 }
494 let type_node = node
495 .child_by_field_name("type")
496 .or_else(|| first_type_child(node));
497 seed_binding_from_type_or_value(&name, type_node, None, ctx);
498}
499
500fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
501 let Some(declarator) = node.child_by_field_name("declarator") else {
502 return;
503 };
504 let Some(name) = extract_variable_name(declarator, ctx.source) else {
505 return;
506 };
507 if has_function_scope_ancestor(node) {
508 ctx.local_shadows.declare_shadow(name.clone());
509 }
510 if ctx.spec.kind == TargetKind::Type {
511 ctx.bindings.declare_shadow(name);
512 return;
513 }
514 let type_node = node
515 .child_by_field_name("type")
516 .or_else(|| first_type_child(node));
517 seed_binding_from_type_or_value(&name, type_node, None, ctx);
518}
519
520fn has_function_scope_ancestor(node: Node<'_>) -> bool {
521 let mut current = node.parent();
522 while let Some(parent) = current {
523 if parent.kind() == "namespace_definition" {
531 return false;
532 }
533 if parent.kind() == "function_definition" {
534 return !is_malformed_wrapper_function_definition(parent);
541 }
542 if parent.kind() == "lambda_expression" {
543 return true;
544 }
545 current = parent.parent();
546 }
547 false
548}
549
550fn seed_binding_from_type_or_value(
551 name: &str,
552 type_node: Option<Node<'_>>,
553 value: Option<Node<'_>>,
554 ctx: &mut ScanCtx<'_>,
555) {
556 if name.is_empty() {
557 return;
558 }
559 let resolved = type_node
560 .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
561 .map(|node| {
562 let text = node_text(node, ctx.source);
563 let name = normalize_cpp_type_name(text);
564 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
569 if cpp_template_reference_arguments(node, ctx.source).is_some() {
570 return None;
571 }
572 match enclosing_lexical_scope_components(
573 node,
574 &ctx.analyzer,
575 ctx.visibility,
576 ctx.file,
577 ctx.source,
578 ) {
579 LexicalScopeResolution::Resolved(scope) => Some(scope),
580 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
581 }
582 });
583 let unit = lexical_scope
584 .as_deref()
585 .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
586 .or_else(|| {
587 match ctx
588 .visibility
589 .resolve_type_node_result(ctx.file, node, ctx.source)
590 {
591 Ok(Some(unit)) => Some(unit),
592 Ok(None) => ctx
593 .visibility
594 .canonical_type_for_reference(ctx.file, &name)
595 .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
596 Err(_) => None,
597 }
598 });
599 CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
600 })
601 .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
602
603 if let Some(resolved) = resolved {
604 ctx.bindings.seed_symbol(name.to_string(), resolved);
605 } else if let Some(value) = value
606 && value.kind() == "identifier"
607 {
608 ctx.bindings
609 .alias_symbol(name.to_string(), node_text(value, ctx.source));
610 } else {
611 ctx.bindings.declare_shadow(name.to_string());
612 }
613}
614
615fn resolve_seed_type_node_lexically(
616 node: Node<'_>,
617 ctx: &ScanCtx<'_>,
618 scope: &[String],
619) -> Option<CodeUnit> {
620 let (components, global) = type_reference_components(node, ctx.source)?;
621 match ctx.visibility.resolve_type_components_lexically(
622 &ctx.analyzer,
623 ctx.file,
624 &components,
625 global,
626 scope,
627 ) {
628 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
629 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
630 }
631}
632
633const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
634
635fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
636 infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
637}
638
639fn infer_type_from_value_with_budget(
640 node: Node<'_>,
641 ctx: &ScanCtx<'_>,
642 remaining_call_depth: usize,
643) -> Option<CppScanBinding> {
644 match node.kind() {
645 "new_expression" | "call_expression" if remaining_call_depth == 0 => {
646 infer_cpp_initializer_binding(
647 &ctx.analyzer,
648 ctx.visibility,
649 ctx.file,
650 ctx.source,
651 node,
652 None,
653 )
654 }
655 "new_expression" | "call_expression" => infer_cpp_initializer_binding(
656 &ctx.analyzer,
657 ctx.visibility,
658 ctx.file,
659 ctx.source,
660 node,
661 Some(&|receiver, source| {
662 receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
663 }),
664 ),
665 "initializer_list" => None,
666 "identifier" => {
667 let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
668 resolved
669 .as_precise()?
670 .iter()
671 .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
672 .cloned()
673 }
674 _ => {
675 let text = node_text(node, ctx.source);
676 let name = normalize_cpp_type_name(text);
677 ctx.visibility
678 .resolve_type(ctx.file, &name)
679 .map(|unit| CppScanBinding::from_unit(unit, 0))
680 }
681 }
682}
683
684fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
685 match ctx.spec.kind {
686 TargetKind::Type => maybe_record_type_hit(node, ctx),
687 TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
688 TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
689 TargetKind::Method => maybe_record_method_hit(node, ctx),
690 TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
691 TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
692 TargetKind::Macro => maybe_record_macro_hit(node, ctx),
693 }
694}
695
696fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
697 if node_text(node, ctx.source) != ctx.spec.member_name {
698 return;
699 }
700 if is_ordinary_macro_reference_node(node) {
701 match ctx.visibility.resolve_ordinary_macro_reference(
702 &ctx.analyzer,
703 ctx.file,
704 node,
705 ctx.source,
706 ) {
707 OrdinaryMacroReferenceResolution::Resolved(unit)
708 if ctx.target_group.contains(&unit) =>
709 {
710 *ctx.raw_match_count += 1;
711 push_hit(node, ctx);
712 }
713 OrdinaryMacroReferenceResolution::Ambiguous
714 if ctx
715 .visibility
716 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
717 {
718 *ctx.raw_match_count += 1;
719 push_unproven_hit(node, ctx);
720 }
721 OrdinaryMacroReferenceResolution::Resolved(_)
722 | OrdinaryMacroReferenceResolution::Ambiguous
723 | OrdinaryMacroReferenceResolution::Missing => {}
724 }
725 return;
726 }
727 if !matches!(
728 node.kind(),
729 "identifier"
730 | "field_identifier"
731 | "type_identifier"
732 | "namespace_identifier"
733 | "preproc_arg"
734 ) || node.parent().is_some_and(|parent| {
735 matches!(parent.kind(), "preproc_def" | "preproc_function_def")
736 && parent
737 .child_by_field_name("name")
738 .is_some_and(|name| same_node(name, node))
739 }) {
740 return;
741 }
742 if ctx.visibility.macro_binding_matches_target_at(
743 &ctx.analyzer,
744 ctx.file,
745 &ctx.spec.member_name,
746 node.start_byte(),
747 &ctx.spec.target,
748 ) {
749 *ctx.raw_match_count += 1;
750 push_hit(node, ctx);
751 } else if ctx.visibility.macro_name_may_be_bound_at(
752 ctx.file,
753 &ctx.spec.member_name,
754 node.start_byte(),
755 ) && ctx
756 .visibility
757 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
758 {
759 *ctx.raw_match_count += 1;
760 push_unproven_hit(node, ctx);
761 }
762}
763
764fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
765 if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
766 maybe_record_recovered_macro_return_type_hit(return_type, ctx);
767 return;
768 }
769 if let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source) {
770 if ctx
777 .analyzer
778 .type_alias_provider()
779 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
780 && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
781 && let Some(terminal) = owner.nodes.last().copied()
782 {
783 if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
784 return;
785 }
786 if ctx.visibility.external_type_candidate_visible_in_context(
787 &ctx.analyzer,
788 ctx.file,
789 &ctx.spec.target,
790 terminal,
791 ) || ctx
792 .visibility
793 .dependent_member_pointer_alias_visible_in_context(
794 &ctx.analyzer,
795 ctx.file,
796 &ctx.spec.target,
797 &owner.names,
798 terminal,
799 )
800 {
801 *ctx.raw_match_count += 1;
802 push_type_hit(terminal, ctx);
803 }
804 return;
805 }
806 if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
807 *ctx.raw_match_count += 1;
808 for scope in scopes {
809 push_type_hit(scope, ctx);
810 }
811 return;
812 }
813 return;
814 }
815 if node.kind() == "call_expression" {
816 maybe_record_direct_temporary_type_hit(node, ctx);
817 return;
818 }
819 if node.parent().is_some_and(|parent| {
820 parent.kind() == "operator_cast"
821 && parent
822 .child_by_field_name("type")
823 .is_some_and(|target| same_node(target, node))
824 }) {
825 return;
826 }
827 if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
828 *ctx.raw_match_count += 1;
829 push_type_hit(hit, ctx);
830 return;
831 }
832 if matches!(node.kind(), "identifier" | "template_function")
833 && call_for_function_node(node).is_some()
834 {
835 return;
836 }
837 if node.kind() == "using_declaration" {
838 let (resolution, type_node) =
839 if let Some(type_node) = using_enum_declaration_type_node(node) {
840 (
841 resolve_using_enum_declaration_owner(
842 node,
843 &ctx.analyzer,
844 ctx.visibility,
845 &ctx.ordinary_type_imports,
846 ctx.file,
847 ctx.source,
848 ),
849 type_node,
850 )
851 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
852 (
853 resolve_ordinary_using_declaration_owner(
854 node,
855 &ctx.analyzer,
856 ctx.visibility,
857 ctx.file,
858 ctx.source,
859 ),
860 type_node,
861 )
862 } else {
863 return;
864 };
865 if let LexicalTypeResolution::Resolved { unit, .. } = resolution
866 && same_visible_symbol(&unit, &ctx.spec.target)
867 {
868 *ctx.raw_match_count += 1;
869 push_type_hit(type_node, ctx);
870 }
871 return;
872 }
873 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
874 let mut matching = Vec::new();
882 for candidate in [node, type_node] {
883 if matching
884 .iter()
885 .any(|existing| same_node(*existing, candidate))
886 {
887 continue;
888 }
889 if let LexicalTypeResolution::Resolved {
890 unit, candidates, ..
891 } = resolve_type_node_lexically_for_target(
892 candidate,
893 &ctx.analyzer,
894 ctx.visibility,
895 &ctx.ordinary_type_imports,
896 ctx.file,
897 ctx.source,
898 &ctx.spec.target,
899 Some(&ctx.lexical_scope_cache),
900 ctx.recovered_sentinel_scope(candidate).as_deref(),
901 ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
902 {
903 matching.push(candidate);
904 }
905 }
906 match matching.as_slice() {
907 [candidate] if !same_node(*candidate, node) => {
908 maybe_record_type_hit(*candidate, ctx);
909 return;
910 }
911 [candidate] if same_node(*candidate, node) => {}
912 [] => {}
913 _ => return,
914 }
915 }
916 let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
917 if !recovered_type
918 && !matches!(
919 node.kind(),
920 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
921 )
922 {
923 return;
924 }
925 if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
926 components.len() == 1 && !global && local_type_name_shadows(node, ctx)
927 }) {
928 return;
929 }
930 if !recovered_type
931 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
932 && is_declaration_name(node)
933 && let Some(owners) = out_of_line_member_definition_owner(
934 &ctx.analyzer,
935 ctx.visibility,
936 ctx.file,
937 ctx.source,
938 node,
939 )
940 {
941 let terminal_destructor = out_of_line_destructor_type_reference(node);
942 let innermost = owners.innermost().map(|(_, owner)| owner.clone());
943 *ctx.raw_match_count += 1;
944 let mut matched_owner = false;
945 for (owner_node, owner) in owners.owners {
946 if same_visible_symbol(&owner, &ctx.spec.target) {
947 matched_owner = true;
948 push_guarded_owner_hit(owner_node, &owner, node, ctx);
949 }
950 }
951 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
952 for scope in scopes {
953 push_hit(scope, ctx);
954 }
955 } else if !matched_owner
956 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
957 {
958 push_unproven_hit(scope, ctx);
959 }
960 if let Some(terminal_destructor) = terminal_destructor
961 && innermost
962 .as_ref()
963 .is_some_and(|owner| same_visible_symbol(owner, &ctx.spec.target))
964 {
965 push_hit(terminal_destructor, ctx);
966 }
967 return;
968 }
969 if !recovered_type
970 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
971 && is_declaration_name(node)
972 && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
973 {
974 *ctx.raw_match_count += 1;
975 push_type_hit(owner, ctx);
976 if let Some(destructor) = out_of_line_destructor_type_reference(node) {
977 push_type_hit(destructor, ctx);
978 }
979 return;
980 }
981 if ctx.visibility.is_template_specialization(&ctx.spec.target)
987 && node.kind() == "qualified_identifier"
988 && node
989 .child_by_field_name("name")
990 .is_some_and(|name| name.kind() == "template_type")
991 {
992 return;
993 }
994 if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
995 *ctx.raw_match_count += 1;
996 push_unproven_hit(scope, ctx);
997 return;
998 }
999 if !recovered_type && is_nested_type_node(node) {
1000 if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1001 *ctx.raw_match_count += 1;
1002 push_type_hit(hit, ctx);
1003 return;
1004 }
1005 if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1006 *ctx.raw_match_count += 1;
1007 push_type_hit(hit, ctx);
1008 return;
1009 }
1010 let nested_template = if node.kind() == "template_type" {
1017 Some(node)
1018 } else {
1019 node.parent().filter(|parent| {
1020 parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1021 })
1022 };
1023 let nested_alias_qualifier = nested_template.is_some_and(|template| {
1030 let enclosing_qualified_type_owns_range = template.parent().is_some_and(|parent| {
1031 matches!(
1032 parent.kind(),
1033 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1034 ) && parent.child_by_field_name("name") == Some(template)
1035 });
1036 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1037 return false;
1038 };
1039 let Some(name) = template_reference_name_node(template) else {
1040 return false;
1041 };
1042 let alias_candidates = ctx
1043 .visibility
1044 .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1045 .filter(|candidate| alias_provider.is_type_alias(candidate))
1046 .cloned()
1047 .collect::<Vec<_>>();
1048 let direct_target_alias_visible = !alias_candidates
1049 .iter()
1050 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1051 || ctx.visibility.external_type_candidate_visible_in_context(
1052 &ctx.analyzer,
1053 ctx.file,
1054 &ctx.spec.target,
1055 template,
1056 );
1057 !enclosing_qualified_type_owns_range
1058 && direct_target_alias_visible
1059 && template_reference_candidates_select_target(
1060 template,
1061 &alias_candidates,
1062 &ctx.analyzer,
1063 ctx.visibility,
1064 ctx.file,
1065 ctx.source,
1066 &ctx.spec.target,
1067 )
1068 });
1069 if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1070 return;
1071 }
1072 if nested_alias_qualifier {
1073 let template = nested_template.expect("a nested alias qualifier has a template node");
1074 *ctx.raw_match_count += 1;
1075 let hit =
1076 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1077 type_reference_hit_node(template, ctx.file, ctx.source, &ctx.bindings)
1078 });
1079 push_type_hit(hit, ctx);
1080 return;
1081 }
1082 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1089 && let Some(template) = nested_template
1090 && template_type_component_preserves_target(
1091 template,
1092 &ctx.visibility
1093 .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1094 .cloned()
1095 .collect::<Vec<_>>(),
1096 ctx,
1097 )
1098 {
1099 *ctx.raw_match_count += 1;
1100 let hit = template
1101 .child_by_field_name("name")
1102 .filter(|name| name.kind() == "type_identifier")
1103 .unwrap_or(template);
1104 push_type_hit(hit, ctx);
1105 return;
1106 }
1107 if let Some(template) = nested_template
1108 && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1109 {
1110 *ctx.raw_match_count += 1;
1111 let hit =
1112 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1113 if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1114 template
1115 .child_by_field_name("name")
1116 .filter(|name| name.kind() == "type_identifier")
1117 .unwrap_or(template)
1118 } else {
1119 type_reference_hit_node(template, ctx.file, ctx.source, &ctx.bindings)
1120 }
1121 });
1122 push_type_hit(hit, ctx);
1123 }
1124 return;
1125 }
1126 if !recovered_type && !type_reference_components_may_name_target(node, ctx) {
1127 return;
1128 }
1129 if !recovered_type && let Some(call) = call_for_function_node(node) {
1130 let direct_target = resolve_qualified_call_target(
1131 call,
1132 node,
1133 &ctx.analyzer,
1134 ctx.visibility,
1135 &ctx.ordinary_type_imports,
1136 ctx.file,
1137 ctx.source,
1138 );
1139 if matches!(direct_target, BareCallTargetResolution::Type(_))
1140 && let LexicalTypeResolution::Resolved {
1141 unit, candidates, ..
1142 } = resolve_type_node_lexically_for_target(
1143 node,
1144 &ctx.analyzer,
1145 ctx.visibility,
1146 &ctx.ordinary_type_imports,
1147 ctx.file,
1148 ctx.source,
1149 &ctx.spec.target,
1150 Some(&ctx.lexical_scope_cache),
1151 ctx.recovered_sentinel_scope(node).as_deref(),
1152 )
1153 && type_resolution_matches_target(node, &unit, &candidates, ctx)
1154 {
1155 *ctx.raw_match_count += 1;
1156 push_type_hit(
1157 type_reference_hit_node(node, ctx.file, ctx.source, &ctx.bindings),
1158 ctx,
1159 );
1160 } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1161 *ctx.raw_match_count += 1;
1162 for scope in scopes {
1163 push_type_hit(scope, ctx);
1164 }
1165 } else if let Some(scope) = target_guided_unproven_qualified_call_owner_scope(node, ctx) {
1166 *ctx.raw_match_count += 1;
1167 push_unproven_hit(scope, ctx);
1168 }
1169 return;
1170 }
1171 if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1172 *ctx.raw_match_count += 1;
1173 if proven {
1174 push_type_hit(hit, ctx);
1175 } else {
1176 push_unproven_hit(hit, ctx);
1177 }
1178 return;
1179 }
1180 if !recovered_type && is_declaration_name(node) {
1181 let mut matched_owner = false;
1182 if let Some(owners) = out_of_line_member_definition_owner(
1183 &ctx.analyzer,
1184 ctx.visibility,
1185 ctx.file,
1186 ctx.source,
1187 node,
1188 ) {
1189 for (owner_node, owner) in owners.owners {
1190 if same_visible_symbol(&owner, &ctx.spec.target) {
1191 matched_owner = true;
1192 *ctx.raw_match_count += 1;
1193 push_guarded_owner_hit(owner_node, &owner, node, ctx);
1194 }
1195 }
1196 }
1197 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1198 *ctx.raw_match_count += 1;
1199 for scope in scopes {
1200 push_hit(scope, ctx);
1201 }
1202 } else if !matched_owner
1203 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1204 {
1205 *ctx.raw_match_count += 1;
1206 push_unproven_hit(scope, ctx);
1207 }
1208 return;
1209 }
1210 let hit_node = node;
1211 let text = node_text(hit_node, ctx.source);
1212 let type_resolution = if hit_node.kind() == "template_type"
1213 && ctx.visibility.is_template_specialization(&ctx.spec.target)
1214 {
1215 resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1216 resolve_type_node_lexically_for_target(
1217 hit_node,
1218 &ctx.analyzer,
1219 ctx.visibility,
1220 &ctx.ordinary_type_imports,
1221 ctx.file,
1222 ctx.source,
1223 &ctx.spec.target,
1224 Some(&ctx.lexical_scope_cache),
1225 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1226 )
1227 })
1228 } else {
1229 resolve_type_node_lexically_for_target(
1230 hit_node,
1231 &ctx.analyzer,
1232 ctx.visibility,
1233 &ctx.ordinary_type_imports,
1234 ctx.file,
1235 ctx.source,
1236 &ctx.spec.target,
1237 Some(&ctx.lexical_scope_cache),
1238 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1239 )
1240 };
1241 let type_resolution = if matches!(type_resolution, LexicalTypeResolution::Missing) {
1242 orphaned_namespace_alias_type_resolution(hit_node, ctx).unwrap_or(type_resolution)
1243 } else {
1244 type_resolution
1245 };
1246 match type_resolution {
1247 LexicalTypeResolution::Resolved {
1248 unit, candidates, ..
1249 } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1250 *ctx.raw_match_count += 1;
1251 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1252 for scope in scopes {
1253 push_type_hit(scope, ctx);
1254 }
1255 } else {
1256 let hit_node = if recovered_type && hit_node.kind() == "template_type" {
1257 hit_node
1258 .child_by_field_name("name")
1259 .filter(|name| name.kind() == "type_identifier")
1260 .unwrap_or(hit_node)
1261 } else if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1262 hit_node
1263 .child_by_field_name("name")
1264 .filter(|name| name.kind() == "type_identifier")
1265 .unwrap_or(hit_node)
1266 } else if ctx
1267 .analyzer
1268 .type_alias_provider()
1269 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1270 && ctx
1271 .analyzer
1272 .parent_of(&ctx.spec.target)
1273 .is_some_and(|owner| owner.is_class())
1274 && ctx
1275 .visibility
1276 .is_exhaustive_same_fqn_type_declaration_family(
1277 &ctx.analyzer,
1278 ctx.file,
1279 &ctx.spec.target,
1280 )
1281 && matches!(
1282 hit_node.kind(),
1283 "qualified_identifier" | "scoped_type_identifier"
1284 )
1285 {
1286 hit_node.child_by_field_name("name").unwrap_or(hit_node)
1287 } else if cpp_template_reference_arguments(hit_node, ctx.source).is_some()
1288 && ctx.analyzer.type_alias_provider().is_some_and(|provider| {
1289 candidates
1290 .iter()
1291 .any(|candidate| provider.is_type_alias(candidate))
1292 })
1293 {
1294 template_reference_name_node(hit_node)
1295 .map(function_terminal_node)
1296 .unwrap_or(hit_node)
1297 } else if qualified_type_scope_contains_template(hit_node) {
1298 function_terminal_node(hit_node)
1299 } else {
1300 hit_node
1301 };
1302 let hit_node = target_guided_missing_orphaned_namespace_type_leaf(hit_node, ctx)
1303 .unwrap_or(hit_node);
1304 push_type_hit(
1305 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1306 ctx,
1307 );
1308 }
1309 return;
1310 }
1311 LexicalTypeResolution::Resolved {
1312 unit: _,
1313 candidates,
1314 ..
1315 } => {
1316 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1317 *ctx.raw_match_count += 1;
1318 for scope in scopes {
1319 push_type_hit(scope, ctx);
1320 }
1321 } else if let Some(hit) =
1322 target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1323 {
1324 *ctx.raw_match_count += 1;
1325 push_unproven_hit(hit, ctx);
1326 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1327 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1328 {
1329 *ctx.raw_match_count += 1;
1330 push_type_hit(leaf, ctx);
1331 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1332 *ctx.raw_match_count += 1;
1333 push_unproven_hit(leaf, ctx);
1334 }
1335 return;
1336 }
1337 LexicalTypeResolution::Ambiguous => {
1338 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1339 *ctx.raw_match_count += 1;
1340 for scope in scopes {
1341 push_type_hit(scope, ctx);
1342 }
1343 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1344 *ctx.raw_match_count += 1;
1345 push_unproven_hit(leaf, ctx);
1346 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1347 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1348 .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1349 {
1350 *ctx.raw_match_count += 1;
1351 push_type_hit(leaf, ctx);
1352 }
1353 return;
1354 }
1355 LexicalTypeResolution::Missing => {
1356 if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1357 *ctx.raw_match_count += 1;
1358 push_unproven_hit(leaf, ctx);
1359 return;
1360 }
1361 if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1362 *ctx.raw_match_count += 1;
1363 push_unproven_hit(leaf, ctx);
1364 return;
1365 }
1366 if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1367 *ctx.raw_match_count += 1;
1368 push_type_hit(leaf, ctx);
1369 return;
1370 }
1371 let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1372 hit_node,
1373 &ctx.analyzer,
1374 ctx.visibility,
1375 ctx.file,
1376 ctx.source,
1377 &ctx.spec.target,
1378 );
1379 let raw_matches = matches!(
1380 raw_resolution,
1381 LexicalTypeResolution::Resolved {
1382 ref unit,
1383 ref candidates,
1384 ..
1385 } if type_resolution_identifies_unit_target(
1386 hit_node,
1387 unit,
1388 candidates,
1389 &ctx.spec.target,
1390 ctx,
1391 )
1392 );
1393 if raw_matches
1394 || type_node_has_exact_target_identity_without_visibility(
1395 hit_node,
1396 &ctx.analyzer,
1397 ctx.visibility,
1398 ctx.file,
1399 ctx.source,
1400 &ctx.spec.target,
1401 )
1402 {
1403 *ctx.raw_match_count += 1;
1404 push_unproven_hit(
1405 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1406 ctx,
1407 );
1408 return;
1409 }
1410 }
1411 }
1412 if ctx
1413 .visibility
1414 .parser_alias_resolves_to_type(&ctx.analyzer, ctx.file, text, &ctx.spec.target)
1415 {
1416 *ctx.raw_match_count += 1;
1417 push_type_hit(
1418 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1419 ctx,
1420 );
1421 return;
1422 }
1423 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1424 *ctx.raw_match_count += 1;
1425 for scope in scopes {
1426 push_type_hit(scope, ctx);
1427 }
1428 return;
1429 }
1430 if !name_mentions(text, &ctx.spec.member_name) {
1431 return;
1432 }
1433 *ctx.raw_match_count += 1;
1434 if !ctx.visibility.external_type_candidate_visible_in_context(
1435 &ctx.analyzer,
1436 ctx.file,
1437 &ctx.spec.target,
1438 hit_node,
1439 ) {
1440 if let Some(scope) = static_qualifier_name_scope(node, ctx) {
1441 push_unproven_hit(scope, ctx);
1442 } else {
1443 push_unproven_hit(hit_node, ctx);
1444 }
1445 }
1446}
1447
1448fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1449 node: Node<'tree>,
1450 ctx: &ScanCtx<'_>,
1451) -> Option<Node<'tree>> {
1452 let (components, global) = type_reference_components(node, ctx.source)?;
1453 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1454 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1455 node,
1456 &ctx.analyzer,
1457 ctx.visibility,
1458 ctx.file,
1459 ctx.source,
1460 false,
1461 false,
1462 Some(&ctx.lexical_scope_cache),
1463 ) {
1464 LexicalScopeResolution::Resolved(scope) => Some(scope),
1465 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1466 }
1467 })?;
1468 let OrdinaryTypeImportResolution::Resolved { target, .. } =
1469 compatible_foreign_type_import_resolution(
1470 node,
1471 &components,
1472 global,
1473 &ctx.analyzer,
1474 ctx.visibility,
1475 &ctx.ordinary_type_imports,
1476 ctx.file,
1477 ctx.source,
1478 &lexical_scope,
1479 &ctx.spec.target,
1480 )
1481 else {
1482 return None;
1483 };
1484 same_visible_symbol(&target, &ctx.spec.target)
1485 .then(|| type_reference_hit_node(node, ctx.file, ctx.source, &ctx.bindings))
1486}
1487
1488fn target_guided_nested_type_terminal_hit<'tree>(
1494 node: Node<'tree>,
1495 ctx: &ScanCtx<'_>,
1496) -> Option<Node<'tree>> {
1497 let qualified = node.parent().filter(|parent| {
1498 matches!(
1499 parent.kind(),
1500 "qualified_identifier" | "scoped_type_identifier"
1501 ) && parent.child_by_field_name("name") == Some(node)
1502 })?;
1503 let owner = qualified_owner_components(qualified, ctx.source)?;
1504 let mut complete = qualified;
1505 while let Some(parent) = complete.parent().filter(|parent| {
1506 matches!(
1507 parent.kind(),
1508 "qualified_identifier" | "scoped_type_identifier"
1509 )
1510 }) {
1511 complete = parent;
1512 }
1513
1514 if let LexicalTypeResolution::Resolved {
1515 unit, candidates, ..
1516 } = resolve_type_node_lexically_for_target(
1517 complete,
1518 &ctx.analyzer,
1519 ctx.visibility,
1520 &ctx.ordinary_type_imports,
1521 ctx.file,
1522 ctx.source,
1523 &ctx.spec.target,
1524 Some(&ctx.lexical_scope_cache),
1525 ctx.recovered_sentinel_scope(complete).as_deref(),
1526 ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
1527 {
1528 return None;
1529 }
1530
1531 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
1532 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
1533 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
1534 &ctx.analyzer,
1535 ctx.file,
1536 &owner.names,
1537 owner.global,
1538 &lexical_scope,
1539 );
1540 let owner_unit = match owner_resolution {
1541 LexicalTypeResolution::Resolved { unit, .. } => unit,
1542 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
1543 ctx.visibility.inherited_injected_class_owner(
1544 &ctx.analyzer,
1545 ctx.file,
1546 &enclosing_owner,
1547 owner.names.first()?,
1548 )?
1549 }
1550 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
1551 };
1552 let name = node_text(node, ctx.source);
1553 ctx.visibility
1554 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
1555 .into_iter()
1556 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1557 .then_some(complete)
1558}
1559
1560fn out_of_line_dependent_return_template_owner<'tree>(
1570 node: Node<'tree>,
1571 ctx: &ScanCtx<'_>,
1572) -> Option<Node<'tree>> {
1573 if node.kind() != "template_type" {
1574 return None;
1575 }
1576 let template_name = template_reference_name_node(node)?;
1577 let mut scope = node;
1578 while let Some(parent) = scope.parent().filter(|parent| {
1579 matches!(
1580 parent.kind(),
1581 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1582 ) && parent.child_by_field_name("name") == Some(scope)
1583 }) {
1584 scope = parent;
1585 }
1586 let qualified = scope.parent().filter(|parent| {
1587 matches!(
1588 parent.kind(),
1589 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1590 ) && parent.child_by_field_name("scope") == Some(scope)
1591 })?;
1592 let mut function = qualified.parent();
1593 let function = loop {
1594 let candidate = function?;
1595 if candidate.kind() == "function_definition" {
1596 break candidate;
1597 }
1598 function = candidate.parent();
1599 };
1600 let return_type = function.child_by_field_name("type")?;
1601 if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
1602 return None;
1603 }
1604 let declarator_name = function
1605 .child_by_field_name("declarator")
1606 .and_then(declarator_name_node)?;
1607 if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
1608 return None;
1609 }
1610 let resolved_owner_matches = out_of_line_member_definition_owner(
1611 &ctx.analyzer,
1612 ctx.visibility,
1613 ctx.file,
1614 ctx.source,
1615 declarator_name,
1616 )
1617 .is_some_and(|owners| {
1618 owners
1619 .owners
1620 .iter()
1621 .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
1622 });
1623 let indexed_owner_matches =
1624 indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
1625 let target_guided_owner_matches =
1626 target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
1627 (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
1628 .then_some(template_name)
1629}
1630
1631fn indexed_out_of_line_template_owner_hit<'tree>(
1634 node: Node<'tree>,
1635 ctx: &ScanCtx<'_>,
1636) -> Option<Node<'tree>> {
1637 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1638 || !is_declaration_name(node)
1639 {
1640 return None;
1641 }
1642 let mut function = node.parent();
1643 let function = loop {
1644 let candidate = function?;
1645 if candidate.kind() == "function_definition" {
1646 break candidate;
1647 }
1648 function = candidate.parent();
1649 };
1650 if function
1651 .child_by_field_name("declarator")
1652 .and_then(declarator_name_node)
1653 != Some(node)
1654 {
1655 return None;
1656 }
1657 let target = physically_visible_type_target(ctx)?;
1658 if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
1659 != canonical_cpp_scope_components(target)
1660 {
1661 return None;
1662 }
1663 let qualified = qualified_owner_components(node, ctx.source)?;
1664 if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
1665 return None;
1666 }
1667 let owner = qualified.nodes.last().copied()?;
1668 let template = if owner.kind() == "template_type" {
1669 owner
1670 } else {
1671 owner
1672 .parent()
1673 .filter(|parent| parent.kind() == "template_type")?
1674 };
1675 template_reference_name_node(template)
1676}
1677
1678fn push_guarded_owner_hit(
1679 owner_node: Node<'_>,
1680 owner: &CodeUnit,
1681 reference: Node<'_>,
1682 ctx: &mut ScanCtx<'_>,
1683) {
1684 if ctx
1685 .visibility
1686 .external_type_candidate_guard_compatible_in_context(
1687 &ctx.analyzer,
1688 ctx.file,
1689 owner,
1690 reference,
1691 )
1692 {
1693 push_hit(owner_node, ctx);
1694 } else {
1695 push_unproven_hit(owner_node, ctx);
1696 }
1697}
1698
1699fn target_guided_alias_template_reference<'tree>(
1702 node: Node<'tree>,
1703 ctx: &ScanCtx<'_>,
1704) -> Option<(Node<'tree>, bool)> {
1705 let alias_provider = ctx.analyzer.type_alias_provider()?;
1706 if !matches!(
1707 node.kind(),
1708 "qualified_identifier" | "scoped_type_identifier"
1709 ) || !alias_provider.is_type_alias(&ctx.spec.target)
1710 {
1711 return None;
1712 }
1713 cpp_template_reference_arguments(node, ctx.source)?;
1714 let (components, global) = type_reference_components(node, ctx.source)?;
1715 if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
1716 return None;
1717 }
1718 let target = physically_visible_type_target(ctx)?;
1719 let target_components = canonical_cpp_scope_components(target);
1720 let parser_namespace = enclosing_namespace_components(node, ctx.source);
1721 let path_matches = if global {
1722 components == target_components
1723 || (!parser_namespace.is_empty()
1724 && target_components.starts_with(&parser_namespace)
1725 && target_components[parser_namespace.len()..] == components)
1726 } else {
1727 let lexical_scope = match enclosing_lexical_scope_components(
1728 node,
1729 &ctx.analyzer,
1730 ctx.visibility,
1731 ctx.file,
1732 ctx.source,
1733 ) {
1734 LexicalScopeResolution::Resolved(scope) => scope,
1735 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
1736 };
1737 lexical_component_tiers(&components, false, &lexical_scope)
1738 .any(|scope| scope == target_components)
1739 };
1740 let scoped_candidates = ctx
1741 .visibility
1742 .visible_identifier_candidates(ctx.file, target.identifier())
1743 .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
1744 .collect::<Vec<_>>();
1745 if !path_matches
1746 || scoped_candidates.is_empty()
1747 || scoped_candidates
1748 .iter()
1749 .any(|candidate| !same_visible_symbol(candidate, target))
1750 || !ctx.visibility.structured_alias_primary_preserves_target(
1751 &ctx.analyzer,
1752 ctx.file,
1753 target,
1754 target,
1755 )
1756 {
1757 return None;
1758 }
1759 let proven = ctx.visibility.external_type_candidate_visible_in_context(
1760 &ctx.analyzer,
1761 ctx.file,
1762 target,
1763 node,
1764 );
1765 Some((node, proven))
1766}
1767
1768fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
1774 let name = node_text(return_type, ctx.source);
1775 if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
1776 return;
1777 }
1778 if physically_visible_type_target(ctx).is_some()
1779 && type_alias_owner_encloses_structured_reference(return_type, ctx)
1780 && !nearer_type_name_shadows_structured_reference(return_type, ctx)
1781 && ctx.visibility.external_type_candidate_visible_in_context(
1782 &ctx.analyzer,
1783 ctx.file,
1784 &ctx.spec.target,
1785 return_type,
1786 )
1787 {
1788 *ctx.raw_match_count += 1;
1789 push_type_hit(return_type, ctx);
1790 return;
1791 }
1792 let Some(scope) = ctx
1793 .recovered_sentinel_scope(return_type)
1794 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
1795 else {
1796 return;
1797 };
1798 let components = [name.to_string()];
1799 let resolution = ctx.visibility.resolve_type_components_lexically(
1800 &ctx.analyzer,
1801 ctx.file,
1802 &components,
1803 false,
1804 &scope,
1805 );
1806 if let LexicalTypeResolution::Resolved {
1807 unit, candidates, ..
1808 } = resolution
1809 && (same_visible_symbol(&unit, &ctx.spec.target)
1810 || candidates
1811 .iter()
1812 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
1813 {
1814 *ctx.raw_match_count += 1;
1815 push_type_hit(return_type, ctx);
1816 }
1817}
1818
1819fn member_pointer_owner_components<'tree>(
1824 node: Node<'tree>,
1825 source: &str,
1826) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
1827 if node.kind() != "qualified_identifier" {
1828 return None;
1829 }
1830 let declarator = node.child_by_field_name("name")?;
1831 if !matches!(
1832 declarator.kind(),
1833 "pointer_type_declarator" | "abstract_pointer_declarator"
1834 ) {
1835 return None;
1836 }
1837 let scope = node.child_by_field_name("scope")?;
1838 let mut saw_scope = false;
1845 let has_scope_separator = (0..node.child_count()).any(|index| {
1846 let Some(child) = node.child(index) else {
1847 return false;
1848 };
1849 if same_node(child, scope) {
1850 saw_scope = true;
1851 return false;
1852 }
1853 saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
1854 });
1855 if !has_scope_separator {
1856 return None;
1857 }
1858 let mut nodes = cpp_name_component_nodes(scope)?;
1859 let mut outer = node;
1860 while let Some(parent) = outer.parent()
1861 && parent.kind() == "qualified_identifier"
1862 && parent.child_by_field_name("name") == Some(outer)
1863 {
1864 let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
1865 prefix.append(&mut nodes);
1866 nodes = prefix;
1867 outer = parent;
1868 }
1869 let names = nodes
1870 .iter()
1871 .map(|component| node_text(*component, source).to_string())
1872 .collect();
1873 Some((
1874 QualifiedOwnerComponents {
1875 nodes,
1876 names,
1877 global: is_globally_qualified_cpp_name(outer),
1878 },
1879 outer,
1880 ))
1881}
1882
1883fn member_pointer_alias_owner_prefix_matches(
1884 node: Node<'_>,
1885 owner: &QualifiedOwnerComponents<'_>,
1886 ctx: &ScanCtx<'_>,
1887) -> bool {
1888 let Some((_, owner_prefix)) = owner.names.split_last() else {
1889 return false;
1890 };
1891 let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
1892 return false;
1893 };
1894 let recovered_scope = ctx.recovered_sentinel_scope(node);
1895 let resolution = if let Some(recovered_scope) = recovered_scope {
1896 resolve_type_components_lexically_at_for_target_with_recovered_scope(
1897 node,
1898 owner_prefix,
1899 owner.global,
1900 &ctx.analyzer,
1901 ctx.visibility,
1902 &ctx.ordinary_type_imports,
1903 ctx.file,
1904 ctx.source,
1905 &parent,
1906 false,
1907 &recovered_scope,
1908 )
1909 } else {
1910 resolve_type_components_lexically_at_for_target_with_scope_cache(
1911 node,
1912 owner_prefix,
1913 owner.global,
1914 &ctx.analyzer,
1915 ctx.visibility,
1916 &ctx.ordinary_type_imports,
1917 ctx.file,
1918 ctx.source,
1919 &parent,
1920 false,
1921 Some(&ctx.lexical_scope_cache),
1922 )
1923 };
1924 let LexicalTypeResolution::Resolved {
1925 unit, candidates, ..
1926 } = resolution
1927 else {
1928 return false;
1929 };
1930 same_member_pointer_owner_identity(&unit, &parent)
1931 || candidates
1932 .iter()
1933 .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
1934}
1935
1936fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
1937 same_visible_symbol(left, right)
1938 || (left.kind() == right.kind()
1939 && left.fq_name() == right.fq_name()
1940 && left.source() == right.source())
1941}
1942
1943fn resolve_nested_template_type_for_target(
1950 node: Node<'_>,
1951 ctx: &ScanCtx<'_>,
1952) -> Option<LexicalTypeResolution> {
1953 let reference_node = node
1954 .parent()
1955 .filter(|parent| {
1956 parent.kind() == "qualified_identifier"
1957 && parent.child_by_field_name("name") == Some(node)
1958 })
1959 .unwrap_or(node);
1960 let target_resolution = resolve_type_node_lexically_for_target(
1961 reference_node,
1962 &ctx.analyzer,
1963 ctx.visibility,
1964 &ctx.ordinary_type_imports,
1965 ctx.file,
1966 ctx.source,
1967 &ctx.spec.target,
1968 Some(&ctx.lexical_scope_cache),
1969 ctx.recovered_sentinel_scope(reference_node).as_deref(),
1970 );
1971 if let LexicalTypeResolution::Resolved {
1972 unit, candidates, ..
1973 } = target_resolution
1974 && template_reference_candidates_select_target(
1975 reference_node,
1976 &candidates,
1977 &ctx.analyzer,
1978 ctx.visibility,
1979 ctx.file,
1980 ctx.source,
1981 &ctx.spec.target,
1982 )
1983 {
1984 return Some(LexicalTypeResolution::Resolved {
1985 unit,
1986 components: Vec::new(),
1987 candidates,
1988 });
1989 }
1990
1991 let normal_resolution = resolve_type_node_lexically(
1992 reference_node,
1993 &ctx.analyzer,
1994 ctx.visibility,
1995 &ctx.ordinary_type_imports,
1996 ctx.file,
1997 ctx.source,
1998 );
1999 let LexicalTypeResolution::Resolved {
2000 unit,
2001 components,
2002 candidates,
2003 } = normal_resolution
2004 else {
2005 return None;
2006 };
2007 let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2008 let specialized = ctx
2009 .visibility
2010 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2011 .ok()
2012 .unwrap_or(unit);
2013 (same_visible_symbol(&specialized, &ctx.spec.target)
2014 || template_type_component_preserves_target(reference_node, &candidates, ctx))
2015 .then_some(LexicalTypeResolution::Resolved {
2016 unit: specialized,
2017 components,
2018 candidates,
2019 })
2020}
2021
2022fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2023 let Some((components, _)) = type_reference_components(node, ctx.source) else {
2024 return false;
2025 };
2026 components.iter().any(|component| {
2027 ctx.type_reference_component_names.contains(component)
2028 || (component == ctx.spec.target.identifier()
2029 && matches!(
2030 node.kind(),
2031 "qualified_identifier" | "scoped_type_identifier"
2032 )
2033 && cpp_template_reference_arguments(node, ctx.source).is_some()
2034 && ctx
2035 .analyzer
2036 .type_alias_provider()
2037 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2038 && physically_visible_type_target(ctx).is_some())
2039 })
2040}
2041
2042fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2043 let Some(scope) = node.child_by_field_name("scope") else {
2044 return false;
2045 };
2046 let mut pending = vec![scope];
2047 while let Some(candidate) = pending.pop() {
2048 if candidate.kind() == "template_type" {
2049 return true;
2050 }
2051 if matches!(
2052 candidate.kind(),
2053 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2054 ) {
2055 if let Some(scope) = candidate.child_by_field_name("scope") {
2056 pending.push(scope);
2057 }
2058 if let Some(name) = candidate.child_by_field_name("name") {
2059 pending.push(name);
2060 }
2061 }
2062 }
2063 false
2064}
2065
2066fn call_for_function_node(node: Node<'_>) -> Option<Node<'_>> {
2067 let parent = node.parent()?;
2068 (parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node))
2069 .then_some(parent)
2070}
2071
2072fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2073 ctx.target_group.iter().find(|target| {
2074 same_logical_symbol(target, &ctx.spec.target)
2075 && ctx.visibility.is_physically_visible(ctx.file, target)
2076 })
2077}
2078
2079fn target_guided_missing_direct_temporary_type<'tree>(
2080 function: Node<'tree>,
2081 ctx: &ScanCtx<'_>,
2082) -> Option<Node<'tree>> {
2083 let target = physically_visible_type_target(ctx)?;
2084 let component_nodes = cpp_name_component_nodes(function)?;
2085 let terminal = component_nodes.last().copied()?;
2086 if node_text(terminal, ctx.source) != target.identifier() {
2087 return None;
2088 }
2089 let components = component_nodes
2090 .iter()
2091 .map(|component| node_text(*component, ctx.source).to_string())
2092 .collect::<Vec<_>>();
2093 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
2094 indexed_scope_matches_target_name(
2095 &indexed_scope,
2096 &components,
2097 is_globally_qualified_cpp_name(function),
2098 target,
2099 )
2100 .then_some(terminal)
2101}
2102
2103fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
2104 let Some(function) = call.child_by_field_name("function") else {
2105 return;
2106 };
2107 if !matches!(
2108 function.kind(),
2109 "identifier"
2110 | "type_identifier"
2111 | "template_function"
2112 | "template_type"
2113 | "qualified_identifier"
2114 | "scoped_identifier"
2115 | "scoped_type_identifier"
2116 ) {
2117 return;
2118 }
2119 if !type_reference_components_may_name_target(function, ctx) {
2120 return;
2121 }
2122 if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
2123 *ctx.raw_match_count += 1;
2124 for scope in scopes {
2125 push_type_hit(scope, ctx);
2126 }
2127 return;
2128 }
2129 let terminal = function_terminal_node(function);
2130 let name = node_text(terminal, ctx.source);
2131 if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
2132 return;
2133 }
2134 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
2135 match resolve_declaring_member_owner(
2136 &ctx.analyzer,
2137 ctx.visibility,
2138 ctx.file,
2139 &enclosing_owner,
2140 name,
2141 ) {
2142 EnclosingMemberOwnerResolution::Owner(owner)
2143 if matches!(
2144 ctx.visibility
2145 .visible_member_for_owner_name(ctx.file, &owner, name,),
2146 VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
2147 ) =>
2148 {
2149 return;
2150 }
2151 EnclosingMemberOwnerResolution::Ambiguous => return,
2152 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
2153 }
2154 }
2155
2156 if ctx
2163 .analyzer
2164 .type_alias_provider()
2165 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2166 && name == ctx.spec.target.identifier()
2167 && physically_visible_type_target(ctx).is_some()
2168 && !local_type_name_shadows(function, ctx)
2169 && type_alias_owner_matches_structured_reference(function, ctx)
2170 && ctx.visibility.external_type_candidate_visible_in_context(
2171 &ctx.analyzer,
2172 ctx.file,
2173 &ctx.spec.target,
2174 function,
2175 )
2176 {
2177 *ctx.raw_match_count += 1;
2178 push_type_hit(terminal, ctx);
2179 return;
2180 }
2181
2182 let call_resolution = resolve_qualified_call_target(
2183 call,
2184 function,
2185 &ctx.analyzer,
2186 ctx.visibility,
2187 &ctx.ordinary_type_imports,
2188 ctx.file,
2189 ctx.source,
2190 );
2191 match call_resolution {
2192 BareCallTargetResolution::Type(unit) => {
2193 if same_visible_symbol(&unit, &ctx.spec.target) {
2194 *ctx.raw_match_count += 1;
2195 let hit_node = if matches!(
2196 function.kind(),
2197 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2198 ) {
2199 function_terminal_node(function)
2200 } else {
2201 function
2202 };
2203 push_type_hit(hit_node, ctx);
2204 return;
2205 }
2206 }
2207 BareCallTargetResolution::FreeFunctions(units)
2208 if units.iter().all(|unit| {
2209 unit.fq_name() == ctx.spec.target.fq_name()
2210 && ctx
2211 .visibility
2212 .callable_is_constructor_declaration(&ctx.analyzer, unit)
2213 }) => {}
2214 BareCallTargetResolution::Ambiguous => {
2215 push_unproven_hit(function, ctx);
2216 return;
2217 }
2218 BareCallTargetResolution::FreeFunctions(_)
2219 | BareCallTargetResolution::UnprovenFreeFunctions(_)
2220 | BareCallTargetResolution::CallableShadow => return,
2221 BareCallTargetResolution::Missing => {}
2226 }
2227 let target_resolution = resolve_type_node_lexically_for_target(
2228 function,
2229 &ctx.analyzer,
2230 ctx.visibility,
2231 &ctx.ordinary_type_imports,
2232 ctx.file,
2233 ctx.source,
2234 &ctx.spec.target,
2235 Some(&ctx.lexical_scope_cache),
2236 ctx.recovered_sentinel_scope(function).as_deref(),
2237 );
2238 match target_resolution {
2239 LexicalTypeResolution::Resolved {
2240 unit, candidates, ..
2241 } if same_visible_symbol(&unit, &ctx.spec.target)
2242 || candidates
2243 .iter()
2244 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
2245 {
2246 *ctx.raw_match_count += 1;
2247 let hit_node = if matches!(
2248 function.kind(),
2249 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2250 ) {
2251 function_terminal_node(function)
2252 } else {
2253 function
2254 };
2255 push_type_hit(hit_node, ctx);
2256 }
2257 LexicalTypeResolution::Missing => {
2258 if let Some(hit) = target_guided_missing_direct_temporary_type(function, ctx) {
2259 *ctx.raw_match_count += 1;
2260 push_type_hit(hit, ctx);
2261 }
2262 }
2263 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
2264 }
2265}
2266
2267pub enum BareCallTargetResolution {
2268 Type(CodeUnit),
2269 FreeFunctions(Vec<CodeUnit>),
2270 UnprovenFreeFunctions(Vec<CodeUnit>),
2271 CallableShadow,
2272 Ambiguous,
2273 Missing,
2274}
2275
2276pub enum BlockUsingCallTargetResolution {
2277 Target(BareCallTargetResolution),
2278 Unindexed(Vec<String>),
2279 Ambiguous,
2280}
2281
2282#[allow(clippy::too_many_arguments)]
2283fn resolve_qualified_call_target(
2284 call: Node<'_>,
2285 function: Node<'_>,
2286 analyzer: &CppGraphSource<'_>,
2287 visibility: &VisibilityIndex,
2288 ordinary_type_imports: &OrdinaryTypeImportCell,
2289 file: &ProjectFile,
2290 source: &str,
2291) -> BareCallTargetResolution {
2292 if matches!(function.kind(), "identifier" | "template_function") {
2293 return resolve_bare_call_target(
2294 call,
2295 function,
2296 analyzer,
2297 visibility,
2298 ordinary_type_imports,
2299 file,
2300 source,
2301 );
2302 }
2303 if !matches!(
2304 function.kind(),
2305 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2306 ) {
2307 return BareCallTargetResolution::Missing;
2308 }
2309 let terminal = function_terminal_node(function);
2310 let name = node_text(terminal, source);
2311 let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
2312 return BareCallTargetResolution::Missing;
2313 };
2314 components.push(name.to_string());
2315 let qualified_name = components.join("::");
2316 let type_resolution = resolve_type_node_lexically(
2317 function,
2318 analyzer,
2319 visibility,
2320 ordinary_type_imports,
2321 file,
2322 source,
2323 );
2324 let same_name_resolves_to_type = matches!(
2325 &type_resolution,
2326 LexicalTypeResolution::Resolved {
2327 unit,
2328 candidates,
2329 ..
2330 } if cpp_name_for(unit) == qualified_name
2331 || candidates
2332 .iter()
2333 .any(|candidate| cpp_name_for(candidate) == qualified_name)
2334 );
2335 let candidates = visibility
2336 .visible_identifier_candidates(file, name)
2337 .filter(|candidate| {
2338 candidate.is_function()
2339 && type_owner_of(analyzer, candidate).is_none()
2340 && !(same_name_resolves_to_type
2341 && visibility.callable_is_constructor_declaration(analyzer, candidate))
2342 && cpp_name_for(candidate) == qualified_name
2343 && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
2344 })
2345 .cloned()
2346 .collect::<Vec<_>>();
2347 if !candidates.is_empty() {
2348 return resolve_callable_candidates(
2349 candidates,
2350 visibility.call_arity_evidence(file, call, source).exact(),
2351 call.start_byte(),
2352 analyzer,
2353 visibility,
2354 file,
2355 );
2356 }
2357 match type_resolution {
2358 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2359 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2360 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2361 }
2362}
2363
2364fn binding_free_function_candidates(
2365 binding: &OrdinaryTypeImport,
2366 active_bindings: &[&OrdinaryTypeImport],
2367 analyzer: &CppGraphSource<'_>,
2368 visibility: &VisibilityIndex<'_>,
2369 file: &ProjectFile,
2370 name: &str,
2371 reference_byte: usize,
2372) -> Vec<CodeUnit> {
2373 let Some(qualified) = binding.resolved_target_components.as_ref() else {
2374 return Vec::new();
2375 };
2376 let mut targets = Vec::new();
2377 match binding.target {
2378 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
2379 EffectiveUsingTarget::Namespace { .. } => {
2380 let mut stack = vec![qualified.clone()];
2381 let mut visited = HashSet::default();
2382 while let Some(namespace) = stack.pop() {
2383 if !visited.insert(namespace.clone()) {
2384 continue;
2385 }
2386 let mut target = namespace.clone();
2387 target.push(name.to_string());
2388 targets.push(target);
2389 stack.extend(active_bindings.iter().filter_map(|candidate| {
2390 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
2391 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
2392 .then(|| candidate.resolved_target_components.clone())
2393 .flatten()
2394 }));
2395 }
2396 }
2397 }
2398 targets
2399 .into_iter()
2400 .flat_map(|target| {
2401 let qualified_name = target.join("::");
2402 visibility
2403 .visible_identifier_candidates(file, name)
2404 .filter(move |candidate| {
2405 candidate.is_function()
2406 && type_owner_of(analyzer, candidate).is_none()
2407 && cpp_name_for(candidate) == qualified_name
2408 && visibility.declaration_visible_at(
2409 analyzer,
2410 file,
2411 candidate,
2412 reference_byte,
2413 )
2414 })
2415 .cloned()
2416 })
2417 .collect()
2418}
2419
2420fn dedupe_callable_candidates(candidates: &mut Vec<CodeUnit>) {
2421 let mut deduped = Vec::with_capacity(candidates.len());
2422 for candidate in candidates.drain(..) {
2423 if !deduped
2424 .iter()
2425 .any(|existing| same_logical_symbol(existing, &candidate))
2426 {
2427 deduped.push(candidate);
2428 }
2429 }
2430 *candidates = deduped;
2431}
2432
2433fn resolve_callable_candidates(
2434 candidates: Vec<CodeUnit>,
2435 call_arity: Option<usize>,
2436 reference_byte: usize,
2437 analyzer: &CppGraphSource<'_>,
2438 visibility: &VisibilityIndex<'_>,
2439 file: &ProjectFile,
2440) -> BareCallTargetResolution {
2441 let mut candidates = candidates;
2442 dedupe_callable_candidates(&mut candidates);
2443 if candidates.is_empty() {
2444 return BareCallTargetResolution::Missing;
2445 }
2446 let Some(call_arity) = call_arity else {
2447 if candidates.len() == 1 {
2454 return BareCallTargetResolution::FreeFunctions(candidates);
2455 }
2456 return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
2457 };
2458 let applicable = candidates
2459 .into_iter()
2460 .filter(|candidate| {
2461 visibility
2462 .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
2463 .is_some_and(|arity| arity.accepts(call_arity))
2464 })
2465 .collect::<Vec<_>>();
2466 if applicable.is_empty() {
2467 BareCallTargetResolution::CallableShadow
2468 } else {
2469 BareCallTargetResolution::FreeFunctions(applicable)
2470 }
2471}
2472
2473fn resolve_direct_type_candidates(
2474 candidates: Vec<(CodeUnit, Vec<String>)>,
2475 analyzer: &CppGraphSource<'_>,
2476 visibility: &VisibilityIndex<'_>,
2477 file: &ProjectFile,
2478) -> BareCallTargetResolution {
2479 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
2480 for candidate in candidates {
2481 if !logical
2482 .iter()
2483 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
2484 {
2485 logical.push(candidate);
2486 }
2487 }
2488 let [(target, components)] = logical.as_slice() else {
2489 return if logical.is_empty() {
2490 BareCallTargetResolution::Missing
2491 } else {
2492 BareCallTargetResolution::Ambiguous
2493 };
2494 };
2495 match visibility
2496 .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
2497 {
2498 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2499 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2500 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2501 }
2502}
2503
2504#[allow(clippy::too_many_arguments)]
2509pub fn resolve_block_using_call_target(
2510 call: Node<'_>,
2511 function: Node<'_>,
2512 analyzer: &CppGraphSource<'_>,
2513 visibility: &VisibilityIndex<'_>,
2514 ordinary_type_imports: &OrdinaryTypeImportCell,
2515 file: &ProjectFile,
2516 source: &str,
2517) -> Option<BlockUsingCallTargetResolution> {
2518 if !matches!(function.kind(), "identifier" | "template_function") {
2519 return None;
2520 }
2521 let name = node_text(function_terminal_node(function), source);
2522 if name.is_empty() {
2523 return None;
2524 }
2525 let bindings = effective_using_bindings_for_name(
2526 visibility,
2527 ordinary_type_imports,
2528 file,
2529 function,
2530 source,
2531 name,
2532 );
2533 let block_bindings = bindings
2534 .iter()
2535 .filter(|binding| {
2536 binding.namespace_scope.is_none()
2537 && binding.block_scope
2538 && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
2539 })
2540 .collect::<Vec<_>>();
2541 if block_bindings.is_empty() {
2542 return None;
2543 }
2544 let lexical_scope =
2545 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2546 LexicalScopeResolution::Resolved(scope) => scope,
2547 LexicalScopeResolution::Ambiguous => {
2548 return Some(BlockUsingCallTargetResolution::Ambiguous);
2549 }
2550 LexicalScopeResolution::Missing => return None,
2551 };
2552 let reference_guards = preprocessor_guard_environment(function, source);
2553 let active = block_bindings
2554 .into_iter()
2555 .filter(|binding| {
2556 effective_using_binding_active(
2557 binding,
2558 function,
2559 &lexical_scope,
2560 reference_guards.as_ref(),
2561 visibility,
2562 file,
2563 )
2564 })
2565 .collect::<Vec<_>>();
2566 let depth = active.iter().map(|binding| binding.scope_depth).max()?;
2567 let at_tier = active
2568 .into_iter()
2569 .filter(|binding| binding.scope_depth == depth)
2570 .collect::<Vec<_>>();
2571 let callable_candidates = at_tier
2572 .iter()
2573 .flat_map(|binding| {
2574 binding_free_function_candidates(
2575 binding,
2576 &[],
2577 analyzer,
2578 visibility,
2579 file,
2580 name,
2581 call.start_byte(),
2582 )
2583 })
2584 .collect::<Vec<_>>();
2585 if !callable_candidates.is_empty() {
2586 return Some(BlockUsingCallTargetResolution::Target(
2587 resolve_callable_candidates(
2588 callable_candidates,
2589 visibility.call_arity_evidence(file, call, source).exact(),
2590 call.start_byte(),
2591 analyzer,
2592 visibility,
2593 file,
2594 ),
2595 ));
2596 }
2597 let type_candidates = at_tier
2598 .iter()
2599 .flat_map(|binding| {
2600 binding_type_candidates(
2601 binding,
2602 &[],
2603 visibility,
2604 file,
2605 name,
2606 None,
2607 call.start_byte(),
2608 )
2609 })
2610 .collect::<Vec<_>>();
2611 if !type_candidates.is_empty() {
2612 return Some(BlockUsingCallTargetResolution::Target(
2613 resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
2614 ));
2615 }
2616
2617 let mut unindexed = Vec::new();
2618 for binding in at_tier {
2619 let Some(components) = binding.resolved_target_components.as_ref() else {
2620 continue;
2621 };
2622 if !unindexed.contains(components) {
2623 unindexed.push(components.clone());
2624 }
2625 }
2626 match unindexed.as_slice() {
2627 [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
2628 [] => None,
2629 _ => Some(BlockUsingCallTargetResolution::Ambiguous),
2630 }
2631}
2632
2633#[allow(clippy::too_many_arguments)]
2634pub fn resolve_bare_call_target(
2635 call: Node<'_>,
2636 function: Node<'_>,
2637 analyzer: &CppGraphSource<'_>,
2638 visibility: &VisibilityIndex<'_>,
2639 ordinary_type_imports: &OrdinaryTypeImportCell,
2640 file: &ProjectFile,
2641 source: &str,
2642) -> BareCallTargetResolution {
2643 if !matches!(function.kind(), "identifier" | "template_function") {
2644 return BareCallTargetResolution::Missing;
2645 }
2646 let terminal = function_terminal_node(function);
2647 let name = node_text(terminal, source);
2648 if name.is_empty() {
2649 return BareCallTargetResolution::Missing;
2650 }
2651 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
2652 let lexical_scope =
2653 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2654 LexicalScopeResolution::Resolved(scope) => scope,
2655 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
2656 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
2657 };
2658 let type_resolution = resolve_type_node_lexically(
2659 function,
2660 analyzer,
2661 visibility,
2662 ordinary_type_imports,
2663 file,
2664 source,
2665 );
2666 let type_components = match &type_resolution {
2667 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2668 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2669 };
2670 let direct_type_resolution = visibility.resolve_type_components_lexically(
2671 analyzer,
2672 file,
2673 &[name.to_string()],
2674 false,
2675 &lexical_scope,
2676 );
2677 let direct_type_components = match &direct_type_resolution {
2678 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2679 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2680 };
2681 let bindings = effective_using_bindings_for_name(
2682 visibility,
2683 ordinary_type_imports,
2684 file,
2685 function,
2686 source,
2687 name,
2688 );
2689 let function_guards = if bindings.is_empty() {
2693 None
2694 } else {
2695 preprocessor_guard_environment(function, source)
2696 };
2697 let active_bindings = bindings
2698 .iter()
2699 .filter(|binding| {
2700 effective_using_binding_active(
2701 binding,
2702 function,
2703 &lexical_scope,
2704 function_guards.as_ref(),
2705 visibility,
2706 file,
2707 )
2708 })
2709 .collect::<Vec<_>>();
2710 let transitive_bindings = bindings
2711 .iter()
2712 .filter(|binding| {
2713 effective_using_binding_guards_active(
2714 binding,
2715 function.start_byte(),
2716 function_guards.as_ref(),
2717 visibility,
2718 file,
2719 ) && (binding.namespace_scope.is_some()
2720 || (binding.scope_start <= function.start_byte()
2721 && function.end_byte() <= binding.scope_end))
2722 })
2723 .collect::<Vec<_>>();
2724 let mut concrete_depths = active_bindings
2725 .iter()
2726 .filter(|binding| binding.namespace_scope.is_none())
2727 .map(|binding| binding.scope_depth)
2728 .collect::<Vec<_>>();
2729 concrete_depths.sort_unstable();
2730 concrete_depths.dedup();
2731 for depth in concrete_depths.into_iter().rev() {
2732 let at_tier = active_bindings
2733 .iter()
2734 .copied()
2735 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
2736 let direct = at_tier
2737 .clone()
2738 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2739 .flat_map(|binding| {
2740 binding_free_function_candidates(
2741 binding,
2742 &transitive_bindings,
2743 analyzer,
2744 visibility,
2745 file,
2746 name,
2747 call.start_byte(),
2748 )
2749 })
2750 .collect::<Vec<_>>();
2751 if !direct.is_empty() {
2752 return resolve_callable_candidates(
2753 direct,
2754 call_arity,
2755 call.start_byte(),
2756 analyzer,
2757 visibility,
2758 file,
2759 );
2760 }
2761 let direct_types = at_tier
2762 .clone()
2763 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2764 .flat_map(|binding| {
2765 binding_type_candidates(
2766 binding,
2767 &transitive_bindings,
2768 visibility,
2769 file,
2770 name,
2771 None,
2772 call.start_byte(),
2773 )
2774 })
2775 .collect::<Vec<_>>();
2776 if !direct_types.is_empty() {
2777 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2782 }
2783 let directives = at_tier
2784 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2785 .flat_map(|binding| {
2786 binding_free_function_candidates(
2787 binding,
2788 &transitive_bindings,
2789 analyzer,
2790 visibility,
2791 file,
2792 name,
2793 call.start_byte(),
2794 )
2795 })
2796 .collect::<Vec<_>>();
2797 if !directives.is_empty() {
2798 return resolve_callable_candidates(
2799 directives,
2800 call_arity,
2801 call.start_byte(),
2802 analyzer,
2803 visibility,
2804 file,
2805 );
2806 }
2807 }
2808 for prefix_len in (0..=lexical_scope.len()).rev() {
2809 let mut qualified = lexical_scope[..prefix_len].to_vec();
2810 qualified.push(name.to_string());
2811 let same_name_resolves_to_type = direct_type_components
2812 .is_some_and(|components| components == qualified.as_slice())
2813 || type_components.is_some_and(|components| components == qualified.as_slice());
2814 let mut direct = visibility
2815 .visible_identifier_candidates(file, name)
2816 .filter(|candidate| {
2817 candidate.is_function()
2818 && type_owner_of(analyzer, candidate).is_none()
2819 && !(same_name_resolves_to_type
2820 && visibility.callable_is_constructor_declaration(analyzer, candidate))
2821 && cpp_name_for(candidate) == qualified.join("::")
2822 && visibility.declaration_visible_at(
2823 analyzer,
2824 file,
2825 candidate,
2826 call.start_byte(),
2827 )
2828 })
2829 .cloned()
2830 .collect::<Vec<_>>();
2831 let at_tier = active_bindings.iter().copied().filter(|binding| {
2832 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
2833 });
2834 direct.extend(
2835 at_tier
2836 .clone()
2837 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2838 .flat_map(|binding| {
2839 binding_free_function_candidates(
2840 binding,
2841 &transitive_bindings,
2842 analyzer,
2843 visibility,
2844 file,
2845 name,
2846 call.start_byte(),
2847 )
2848 }),
2849 );
2850 if !direct.is_empty() {
2851 return resolve_callable_candidates(
2852 direct,
2853 call_arity,
2854 call.start_byte(),
2855 analyzer,
2856 visibility,
2857 file,
2858 );
2859 }
2860 let mut direct_types = at_tier
2861 .clone()
2862 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2863 .flat_map(|binding| {
2864 binding_type_candidates(
2865 binding,
2866 &transitive_bindings,
2867 visibility,
2868 file,
2869 name,
2870 None,
2871 call.start_byte(),
2872 )
2873 })
2874 .collect::<Vec<_>>();
2875 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
2876 && let LexicalTypeResolution::Resolved {
2877 unit, components, ..
2878 } = &direct_type_resolution
2879 {
2880 direct_types.push((unit.clone(), components.clone()));
2881 }
2882 if !direct_types.is_empty() {
2883 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2888 }
2889 let directives = at_tier
2890 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2891 .flat_map(|binding| {
2892 binding_free_function_candidates(
2893 binding,
2894 &transitive_bindings,
2895 analyzer,
2896 visibility,
2897 file,
2898 name,
2899 call.start_byte(),
2900 )
2901 })
2902 .collect::<Vec<_>>();
2903 if !directives.is_empty() {
2904 return resolve_callable_candidates(
2905 directives,
2906 call_arity,
2907 call.start_byte(),
2908 analyzer,
2909 visibility,
2910 file,
2911 );
2912 }
2913 if type_components.is_some_and(|components| components == qualified.as_slice()) {
2914 return match type_resolution {
2918 LexicalTypeResolution::Resolved { unit, .. } => {
2919 BareCallTargetResolution::Type(unit)
2920 }
2921 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2922 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2923 };
2924 }
2925 }
2926 match type_resolution {
2935 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2936 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2937 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2938 }
2939}
2940
2941fn static_qualifier_type_scopes<'tree>(
2942 node: Node<'tree>,
2943 ctx: &ScanCtx<'_>,
2944) -> Option<Vec<Node<'tree>>> {
2945 if !matches!(
2946 node.kind(),
2947 "qualified_identifier" | "scoped_type_identifier"
2948 ) {
2949 return None;
2950 }
2951 debug_assert!(!is_nested_type_node(node));
2954 let qualified = qualified_owner_components(node, ctx.source)?;
2955 static_qualifier_type_scopes_for_components(node, qualified, ctx)
2956}
2957
2958fn static_qualifier_type_scopes_for_components<'tree>(
2959 node: Node<'tree>,
2960 qualified: QualifiedOwnerComponents<'tree>,
2961 ctx: &ScanCtx<'_>,
2962) -> Option<Vec<Node<'tree>>> {
2963 if !qualified.global
2964 && qualified.names.first().is_some_and(|name| {
2965 name == ctx.spec.target.identifier()
2966 && qualified
2967 .nodes
2968 .first()
2969 .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
2970 })
2971 {
2972 return None;
2973 }
2974 let mut matches = Vec::new();
2975 let mut inherited_injected_name_is_shadowed = false;
2976 for component_count in 1..=qualified.names.len() {
2977 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
2978 node,
2979 &qualified.names[..component_count],
2980 qualified.global,
2981 &ctx.analyzer,
2982 ctx.visibility,
2983 &ctx.ordinary_type_imports,
2984 ctx.file,
2985 ctx.source,
2986 &ctx.spec.target,
2987 false,
2988 Some(&ctx.lexical_scope_cache),
2989 );
2990 match resolution {
2991 LexicalTypeResolution::Resolved {
2992 unit, candidates, ..
2993 } if (!ctx
2994 .analyzer
2995 .type_alias_provider()
2996 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2997 || ctx.visibility.external_type_candidate_visible_in_context(
2998 &ctx.analyzer,
2999 ctx.file,
3000 &ctx.spec.target,
3001 node,
3002 ))
3003 && (same_visible_symbol(&unit, &ctx.spec.target)
3004 || candidates
3005 .iter()
3006 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3007 && target_alias_candidates_visible(&candidates, node, ctx) =>
3008 {
3009 let matched =
3010 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3011 if !template_type_component_preserves_target(matched, &candidates, ctx) {
3012 continue;
3013 }
3014 if !matches.iter().any(|existing: &Node<'_>| {
3015 existing.start_byte() == matched.start_byte()
3016 && existing.end_byte() == matched.end_byte()
3017 }) {
3018 matches.push(matched);
3019 }
3020 }
3021 LexicalTypeResolution::Ambiguous => {
3027 return (!inherited_injected_name_is_shadowed)
3028 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3029 .flatten()
3030 .map(|scope| vec![scope])
3031 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3032 }
3033 LexicalTypeResolution::Resolved { .. } => {
3034 if let Some(matched) =
3035 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3036 {
3037 matches.push(matched);
3038 }
3039 inherited_injected_name_is_shadowed |= component_count == 1;
3040 }
3041 LexicalTypeResolution::Missing => {
3042 if let Some(matched) =
3043 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3044 {
3045 matches.push(matched);
3046 }
3047 }
3048 }
3049 }
3050 if matches.is_empty() {
3051 (!inherited_injected_name_is_shadowed)
3052 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3053 .flatten()
3054 .map(|scope| vec![scope])
3055 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3056 } else {
3057 Some(matches)
3058 }
3059}
3060
3061fn target_guided_unproven_qualified_call_owner_scope<'tree>(
3064 node: Node<'tree>,
3065 ctx: &ScanCtx<'_>,
3066) -> Option<Node<'tree>> {
3067 let target = physically_visible_type_target(ctx)?;
3068 if !target.is_class() {
3069 return None;
3070 }
3071 let qualified = qualified_owner_components(node, ctx.source)?;
3072 let lexical_scope = match enclosing_lexical_scope_components(
3073 node,
3074 &ctx.analyzer,
3075 ctx.visibility,
3076 ctx.file,
3077 ctx.source,
3078 ) {
3079 LexicalScopeResolution::Resolved(scope) => scope,
3080 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3081 enclosing_namespace_components(node, ctx.source)
3082 }
3083 };
3084 let LexicalTypeResolution::Resolved {
3085 unit, candidates, ..
3086 } = ctx.visibility.resolve_type_components_lexically_for_target(
3087 &ctx.analyzer,
3088 ctx.file,
3089 &qualified.names,
3090 qualified.global,
3091 &lexical_scope,
3092 target,
3093 )
3094 else {
3095 return None;
3096 };
3097 (same_visible_symbol(&unit, target)
3098 || candidates
3099 .iter()
3100 .any(|candidate| same_visible_symbol(candidate, target)))
3101 .then(|| qualified.nodes.last().copied())
3102 .flatten()
3103}
3104
3105fn target_guided_nested_alias_type_scope<'tree>(
3111 node: Node<'tree>,
3112 qualified: &QualifiedOwnerComponents<'tree>,
3113 component_count: usize,
3114 ctx: &ScanCtx<'_>,
3115) -> Option<Node<'tree>> {
3116 if component_count < 2 {
3117 return None;
3118 }
3119 let (owner_components, member_name) =
3120 qualified.names[..component_count].split_at(component_count - 1);
3121 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
3122 node,
3123 owner_components,
3124 qualified.global,
3125 &ctx.analyzer,
3126 ctx.visibility,
3127 &ctx.ordinary_type_imports,
3128 ctx.file,
3129 ctx.source,
3130 ) else {
3131 return None;
3132 };
3133 let member_name = member_name.first()?;
3134 let alias_provider = ctx.analyzer.type_alias_provider()?;
3135 ctx.visibility
3136 .visible_members_for_owner_name(ctx.file, &owner, member_name)
3137 .into_iter()
3138 .filter(|member| alias_provider.is_type_alias(member))
3139 .find(|member| {
3140 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
3141 &ctx.analyzer,
3142 ctx.file,
3143 member,
3144 node,
3145 ) || ctx
3146 .visibility
3147 .external_type_candidate_guard_compatible_in_context(
3148 &ctx.analyzer,
3149 ctx.file,
3150 member,
3151 node,
3152 );
3153 if !member_visible {
3154 return false;
3155 }
3156 same_visible_symbol(member, &ctx.spec.target)
3157 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
3158 })
3159 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
3160}
3161
3162fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
3163 if ctx.visibility.structured_class_alias_resolves_to_target(
3164 &ctx.analyzer,
3165 ctx.file,
3166 candidate,
3167 &ctx.spec.target,
3168 ) {
3169 return ctx.spec.target.clone();
3170 }
3171 let structured = ctx
3172 .visibility
3173 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
3174 if let Some(canonical) = structured
3175 .as_ref()
3176 .filter(|canonical| !same_visible_symbol(canonical, candidate))
3177 {
3178 return canonical.clone();
3179 }
3180 structured.unwrap_or_else(|| candidate.clone())
3181}
3182
3183fn target_guided_dependent_alias_qualifier_scope<'tree>(
3187 node: Node<'tree>,
3188 ctx: &ScanCtx<'_>,
3189) -> Option<Node<'tree>> {
3190 if !matches!(
3191 node.kind(),
3192 "qualified_identifier" | "scoped_type_identifier"
3193 ) {
3194 return None;
3195 }
3196 let target = physically_visible_type_target(ctx)?;
3197 let alias_provider = ctx.analyzer.type_alias_provider()?;
3198 if !target.is_class() || alias_provider.is_type_alias(target) {
3199 return None;
3200 }
3201 let nodes = cpp_name_component_nodes(node)?;
3202 let names = nodes
3203 .iter()
3204 .map(|component| node_text(*component, ctx.source).to_string())
3205 .collect::<Vec<_>>();
3206 let global = is_globally_qualified_cpp_name(node);
3207 let lexical_scope = match enclosing_lexical_scope_components(
3208 node,
3209 &ctx.analyzer,
3210 ctx.visibility,
3211 ctx.file,
3212 ctx.source,
3213 ) {
3214 LexicalScopeResolution::Resolved(scope) => scope,
3215 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3216 enclosing_namespace_components(node, ctx.source)
3217 }
3218 };
3219 for component_count in 1..=names.len() {
3220 let components = &names[..component_count];
3221 let name = components.last()?;
3222 let candidates = ctx
3223 .visibility
3224 .visible_identifier_candidates(ctx.file, name)
3225 .filter(|candidate| alias_provider.is_type_alias(candidate))
3226 .filter(|candidate| {
3227 ctx.visibility.is_physically_visible(ctx.file, candidate)
3228 && ctx
3229 .visibility
3230 .external_type_candidate_guard_compatible_in_context(
3231 &ctx.analyzer,
3232 ctx.file,
3233 candidate,
3234 node,
3235 )
3236 })
3237 .filter(|candidate| {
3238 let candidate_components = canonical_cpp_scope_components(candidate);
3239 lexical_component_tiers(components, global, &lexical_scope)
3240 .any(|tier| tier == candidate_components)
3241 || (component_count == 1
3242 && member_alias_owner_matches_reference_for(
3243 candidate,
3244 nodes[component_count - 1],
3245 ctx,
3246 ))
3247 })
3248 .filter(|candidate| {
3249 ctx.visibility.structured_class_alias_path_preserves_target(
3250 &ctx.analyzer,
3251 ctx.file,
3252 candidate,
3253 target,
3254 )
3255 });
3256 let mut aliases: Vec<&CodeUnit> = Vec::new();
3257 for candidate in candidates {
3258 if !aliases
3259 .iter()
3260 .any(|existing| same_logical_symbol(existing, candidate))
3261 {
3262 aliases.push(candidate);
3263 }
3264 }
3265 if aliases.len() == 1 {
3266 return nodes.get(component_count - 1).copied();
3267 }
3268 if aliases.len() > 1 {
3269 return None;
3270 }
3271 }
3272 None
3273}
3274
3275fn target_guided_dependent_class_alias_leaf<'tree>(
3278 node: Node<'tree>,
3279 ctx: &ScanCtx<'_>,
3280) -> Option<Node<'tree>> {
3281 if node.kind() != "type_identifier"
3282 || is_declaration_name(node)
3283 || local_type_name_shadows(node, ctx)
3284 {
3285 return None;
3286 }
3287 let target = physically_visible_type_target(ctx)?;
3288 let alias_provider = ctx.analyzer.type_alias_provider()?;
3289 if !target.is_class() || alias_provider.is_type_alias(target) {
3290 return None;
3291 }
3292 let name = node_text(node, ctx.source);
3293 let aliases = ctx
3294 .visibility
3295 .visible_identifier_candidates(ctx.file, name)
3296 .filter(|candidate| alias_provider.is_type_alias(candidate))
3297 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3298 .filter(|candidate| {
3299 ctx.visibility.is_physically_visible(ctx.file, candidate)
3300 && ctx
3301 .visibility
3302 .external_type_candidate_guard_compatible_in_context(
3303 &ctx.analyzer,
3304 ctx.file,
3305 candidate,
3306 node,
3307 )
3308 })
3309 .filter(|candidate| {
3310 ctx.visibility.structured_class_alias_path_preserves_target(
3311 &ctx.analyzer,
3312 ctx.file,
3313 candidate,
3314 target,
3315 )
3316 })
3317 .collect::<Vec<_>>();
3318 matches!(aliases.as_slice(), [_]).then_some(node)
3319}
3320
3321fn target_guided_unproven_alias_type_reference<'tree>(
3324 node: Node<'tree>,
3325 candidates: &[CodeUnit],
3326 ctx: &ScanCtx<'_>,
3327) -> Option<Node<'tree>> {
3328 let template_arguments = cpp_template_reference_arguments(node, ctx.source);
3329 let target = physically_visible_type_target(ctx)?;
3330 if !target.is_class() {
3331 return None;
3332 }
3333 let alias_provider = ctx.analyzer.type_alias_provider()?;
3334 let (components, _) = type_reference_components(node, ctx.source)?;
3335 let hit = template_arguments
3336 .as_ref()
3337 .and_then(|_| template_reference_name_node(node))
3338 .map(function_terminal_node)
3339 .unwrap_or_else(|| function_terminal_node(node));
3340 candidates
3341 .iter()
3342 .filter(|candidate| {
3343 alias_provider.is_type_alias(candidate)
3344 && ctx.visibility.is_physically_visible(ctx.file, candidate)
3345 && canonical_cpp_scope_components(candidate) == components
3346 })
3347 .find(|candidate| {
3348 template_arguments.as_ref().map_or_else(
3349 || {
3350 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3351 || ctx.visibility.structured_alias_primary_preserves_target(
3352 &ctx.analyzer,
3353 ctx.file,
3354 candidate,
3355 target,
3356 )
3357 },
3358 |arguments| {
3359 ctx.visibility.template_alias_arguments_preserve_target(
3360 &ctx.analyzer,
3361 ctx.file,
3362 candidate,
3363 arguments,
3364 target,
3365 )
3366 },
3367 )
3368 })
3369 .map(|_| hit)
3370}
3371
3372fn target_alias_candidates_visible(
3373 candidates: &[CodeUnit],
3374 reference: Node<'_>,
3375 ctx: &ScanCtx<'_>,
3376) -> bool {
3377 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
3378 return true;
3379 };
3380 if candidates.iter().any(|candidate| {
3381 !alias_provider.is_type_alias(candidate)
3382 && ctx.visibility.same_template_member_identity(
3383 &ctx.analyzer,
3384 candidate,
3385 &ctx.spec.target,
3386 )
3387 }) {
3388 return true;
3389 }
3390 let target_aliases = candidates
3391 .iter()
3392 .filter(|candidate| {
3393 alias_provider.is_type_alias(candidate)
3394 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3395 })
3396 .collect::<Vec<_>>();
3397 target_aliases.is_empty()
3398 || target_aliases
3399 .iter()
3400 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
3401}
3402
3403fn type_candidate_visible_at_reference(
3404 candidate: &CodeUnit,
3405 reference: Node<'_>,
3406 ctx: &ScanCtx<'_>,
3407) -> bool {
3408 let class_owned_alias = ctx
3409 .analyzer
3410 .type_alias_provider()
3411 .is_some_and(|provider| provider.is_type_alias(candidate))
3412 && ctx
3413 .analyzer
3414 .parent_of(candidate)
3415 .is_some_and(|owner| owner.is_class());
3416 if class_owned_alias {
3417 let conditional_family = ctx
3418 .visibility
3419 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
3420 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
3421 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
3422 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
3423 let guard_match = ctx
3424 .visibility
3425 .external_type_candidate_guard_compatible_in_context(
3426 &ctx.analyzer,
3427 ctx.file,
3428 candidate,
3429 reference,
3430 );
3431 let general_match = conditional_family
3432 && ctx.visibility.external_type_candidate_visible_in_context(
3433 &ctx.analyzer,
3434 ctx.file,
3435 candidate,
3436 reference,
3437 );
3438 return owner_match && (guard_match || general_match);
3439 }
3440 ctx.visibility.external_type_candidate_visible_in_context(
3441 &ctx.analyzer,
3442 ctx.file,
3443 candidate,
3444 reference,
3445 )
3446}
3447
3448fn unqualified_reference_selects_inherited_alias(
3449 candidate: &CodeUnit,
3450 reference: Node<'_>,
3451 ctx: &ScanCtx<'_>,
3452) -> bool {
3453 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3454 return false;
3455 };
3456 if global || components.len() != 1 {
3457 return false;
3458 }
3459 matches!(
3460 resolve_type_node_lexically_for_target(
3461 reference,
3462 &ctx.analyzer,
3463 ctx.visibility,
3464 &ctx.ordinary_type_imports,
3465 ctx.file,
3466 ctx.source,
3467 candidate,
3468 Some(&ctx.lexical_scope_cache),
3469 ctx.recovered_sentinel_scope(reference).as_deref(),
3470 ),
3471 LexicalTypeResolution::Resolved {
3472 ref unit,
3473 ref candidates,
3474 ..
3475 } if ctx
3476 .visibility
3477 .same_template_member_identity(&ctx.analyzer, unit, candidate)
3478 || candidates.iter().any(|resolved| {
3479 ctx.visibility.same_template_member_identity(
3480 &ctx.analyzer,
3481 resolved,
3482 candidate,
3483 )
3484 })
3485 )
3486}
3487
3488fn qualified_reference_selects_type_candidate(
3489 candidate: &CodeUnit,
3490 reference: Node<'_>,
3491 ctx: &ScanCtx<'_>,
3492) -> bool {
3493 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3494 return false;
3495 };
3496 if components.len() < 2 {
3497 return false;
3498 }
3499 let candidate_components = canonical_cpp_scope_components(candidate);
3500 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3501 match enclosing_lexical_scope_components(
3502 reference,
3503 &ctx.analyzer,
3504 ctx.visibility,
3505 ctx.file,
3506 ctx.source,
3507 ) {
3508 LexicalScopeResolution::Resolved(scope) => scope,
3509 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3510 enclosing_namespace_components(reference, ctx.source)
3511 }
3512 }
3513 });
3514 lexical_component_tiers(&components, global, &lexical_scope)
3515 .any(|qualified| qualified == candidate_components)
3516}
3517
3518fn qualified_type_component_hit_node<'tree>(
3519 component: Node<'tree>,
3520 qualified: Node<'tree>,
3521) -> Node<'tree> {
3522 let mut current = component;
3523 while let Some(parent) = current.parent() {
3524 let is_type_name = matches!(
3525 parent.kind(),
3526 "template_type"
3527 | "qualified_identifier"
3528 | "scoped_identifier"
3529 | "scoped_type_identifier"
3530 ) && parent
3531 .child_by_field_name("name")
3532 .is_some_and(|name| same_node(name, current));
3533 if !is_type_name {
3534 break;
3535 }
3536 current = parent;
3537 if same_node(parent, qualified) {
3538 break;
3539 }
3540 }
3541 current
3542}
3543
3544fn template_type_component_preserves_target(
3545 node: Node<'_>,
3546 candidates: &[CodeUnit],
3547 ctx: &ScanCtx<'_>,
3548) -> bool {
3549 template_reference_candidates_select_target(
3550 node,
3551 candidates,
3552 &ctx.analyzer,
3553 ctx.visibility,
3554 ctx.file,
3555 ctx.source,
3556 &ctx.spec.target,
3557 )
3558}
3559
3560fn template_reference_candidates_select_target(
3561 node: Node<'_>,
3562 candidates: &[CodeUnit],
3563 analyzer: &CppGraphSource<'_>,
3564 visibility: &VisibilityIndex<'_>,
3565 file: &ProjectFile,
3566 source: &str,
3567 target: &CodeUnit,
3568) -> bool {
3569 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3570 return !visibility.is_template_specialization(target);
3571 };
3572 let direct_template_name =
3573 template_reference_name_node(node).map(|name| node_text(name, source));
3574 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
3575 analyzer.type_alias_provider().is_some_and(|provider| {
3576 visibility
3577 .visible_identifier_candidates(file, name)
3578 .filter(|candidate| provider.is_type_alias(candidate))
3579 .any(|candidate| {
3580 visibility.template_alias_arguments_preserve_target(
3581 analyzer, file, candidate, &arguments, target,
3582 )
3583 })
3584 })
3585 });
3586 named_alias_selects_target
3587 || candidates.iter().any(|candidate| {
3588 (same_visible_symbol(candidate, target)
3589 && visibility.is_primary_template(target)
3590 && direct_template_name == Some(candidate.identifier()))
3591 || visibility.template_alias_arguments_preserve_target(
3592 analyzer, file, candidate, &arguments, target,
3593 )
3594 || visibility
3595 .resolve_template_arguments(file, candidate.clone(), &arguments)
3596 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
3597 })
3598}
3599
3600fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
3601 let template = if node.kind() == "template_type" {
3602 node
3603 } else {
3604 node.child_by_field_name("name")
3605 .filter(|name| name.kind() == "template_type")?
3606 };
3607 template.child_by_field_name("name")
3608}
3609
3610fn type_resolution_matches_target(
3611 node: Node<'_>,
3612 unit: &CodeUnit,
3613 candidates: &[CodeUnit],
3614 ctx: &ScanCtx<'_>,
3615) -> bool {
3616 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
3617}
3618
3619fn type_resolution_matches_unit_target(
3620 node: Node<'_>,
3621 unit: &CodeUnit,
3622 candidates: &[CodeUnit],
3623 target: &CodeUnit,
3624 ctx: &ScanCtx<'_>,
3625) -> bool {
3626 target_alias_candidates_visible(candidates, node, ctx)
3627 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
3628}
3629
3630fn type_resolution_identifies_unit_target(
3638 node: Node<'_>,
3639 unit: &CodeUnit,
3640 candidates: &[CodeUnit],
3641 target: &CodeUnit,
3642 ctx: &ScanCtx<'_>,
3643) -> bool {
3644 if !template_alias_owner_matches_reference(node, target, ctx) {
3645 return false;
3646 }
3647 if ctx.visibility.is_template_specialization(target)
3648 && cpp_template_reference_arguments(node, ctx.source).is_some()
3649 {
3650 let selected_unit =
3651 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
3652 ctx.visibility
3653 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
3654 .ok()
3655 });
3656 return selected_unit
3657 .as_ref()
3658 .is_some_and(|selected| same_visible_symbol(selected, target))
3659 || template_reference_candidates_select_target(
3660 node,
3661 candidates,
3662 &ctx.analyzer,
3663 ctx.visibility,
3664 ctx.file,
3665 ctx.source,
3666 target,
3667 );
3668 }
3669 ctx.visibility
3670 .same_template_member_identity(&ctx.analyzer, unit, target)
3671 || ctx.visibility.structured_class_alias_resolves_to_target(
3672 &ctx.analyzer,
3673 ctx.file,
3674 unit,
3675 target,
3676 )
3677 || candidates.iter().any(|candidate| {
3678 ctx.visibility
3679 .same_template_member_identity(&ctx.analyzer, candidate, target)
3680 || ctx.visibility.structured_class_alias_resolves_to_target(
3681 &ctx.analyzer,
3682 ctx.file,
3683 candidate,
3684 target,
3685 )
3686 })
3687}
3688
3689fn template_alias_owner_matches_reference(
3695 node: Node<'_>,
3696 target: &CodeUnit,
3697 ctx: &ScanCtx<'_>,
3698) -> bool {
3699 if !ctx
3700 .analyzer
3701 .type_alias_provider()
3702 .is_some_and(|provider| provider.is_type_alias(target))
3703 {
3704 return true;
3705 }
3706 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
3707 return true;
3708 };
3709 if !target_owner.is_class() {
3710 return true;
3711 }
3712 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
3713 return true;
3714 };
3715 if !ctx.visibility.is_template_specialization(&target_owner)
3716 && !ctx.visibility.is_template_specialization(&reference_owner)
3717 {
3718 return true;
3719 }
3720 same_visible_symbol(&target_owner, &reference_owner)
3721}
3722
3723fn inherited_injected_class_qualifier_scope<'tree>(
3724 node: Node<'tree>,
3725 ctx: &ScanCtx<'_>,
3726) -> Option<Node<'tree>> {
3727 let qualified = qualified_owner_components(node, ctx.source)?;
3728 if qualified.global || qualified.names.is_empty() {
3729 return None;
3730 }
3731 let injected_name = &qualified.names[0];
3732 if !ctx.spec.target.is_class()
3733 || ctx.spec.target.identifier() != injected_name
3734 || physically_visible_type_target(ctx).is_none()
3735 {
3736 return None;
3737 }
3738 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
3739 let owner = ctx.visibility.inherited_injected_class_owner(
3740 &ctx.analyzer,
3741 ctx.file,
3742 &enclosing_owner,
3743 injected_name,
3744 )?;
3745 same_visible_symbol(&owner, &ctx.spec.target)
3746 .then(|| qualified.nodes.first().copied())
3747 .flatten()
3748}
3749
3750fn target_guided_qualifier_type_scopes<'tree>(
3753 node: Node<'tree>,
3754 ctx: &ScanCtx<'_>,
3755) -> Option<Vec<Node<'tree>>> {
3756 if !matches!(
3757 node.kind(),
3758 "qualified_identifier" | "scoped_type_identifier"
3759 ) {
3760 return None;
3761 }
3762 let target = physically_visible_type_target(ctx)?;
3763 let qualified = qualified_owner_components(node, ctx.source)?;
3764 let lexical_scope = match enclosing_lexical_scope_components(
3773 node,
3774 &ctx.analyzer,
3775 ctx.visibility,
3776 ctx.file,
3777 ctx.source,
3778 ) {
3779 LexicalScopeResolution::Resolved(scope) => scope,
3780 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3781 enclosing_namespace_components(node, ctx.source)
3782 }
3783 };
3784 let indexed_owner_scope =
3785 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
3786 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
3787 let mut matches = Vec::new();
3788 for component_count in 1..=qualified.names.len() {
3789 let components = &qualified.names[..component_count];
3790 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
3791 .collect::<Vec<_>>();
3792 let name = components.last()?;
3793 let mut candidates = Vec::new();
3794 let mut exact_candidates = Vec::new();
3795 for candidate in ctx
3796 .visibility
3797 .visible_identifier_candidates(ctx.file, name)
3798 .filter(|candidate| candidate.is_class())
3799 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
3800 {
3801 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3802 brokk_bifrost_core::analyzer::Language::Cpp,
3803 &cpp_name_for(candidate),
3804 );
3805 if !candidate_components.ends_with(components)
3806 || candidates
3807 .iter()
3808 .any(|existing| same_logical_symbol(existing, candidate))
3809 {
3810 continue;
3811 }
3812 let exact_lexical_scope = lexical_tiers
3813 .iter()
3814 .any(|expected| expected == &candidate_components);
3815 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
3816 let structured_owner_match = indexed_owner_scope
3817 .as_ref()
3818 .is_some_and(|owner| owner.starts_with(candidate_owner))
3819 || recovered_owner_scope
3820 .as_ref()
3821 .is_some_and(|owner| owner.starts_with(candidate_owner));
3822 let class_alias_owner_match = ctx
3823 .analyzer
3824 .type_alias_provider()
3825 .is_some_and(|provider| provider.is_type_alias(candidate))
3826 && member_alias_owner_matches_reference_for(candidate, node, ctx);
3827 let macro_namespace_owner_match = is_declaration_name(node)
3828 && macro_namespace_scope_matches(candidate_owner, node, ctx);
3829 if components.len() == 1
3830 && candidate_components != components
3831 && !exact_lexical_scope
3832 && !structured_owner_match
3833 && !class_alias_owner_match
3834 && !macro_namespace_owner_match
3835 {
3836 continue;
3837 }
3838 candidates.push(candidate.clone());
3839 if exact_lexical_scope {
3840 exact_candidates.push(candidate.clone());
3841 }
3842 }
3843 if !exact_candidates.is_empty() {
3844 candidates = exact_candidates;
3845 }
3846 let canonical_alias_target_matches = matches!(
3851 candidates.as_slice(),
3852 [candidate]
3853 if ctx
3854 .analyzer
3855 .type_alias_provider()
3856 .is_some_and(|provider| provider.is_type_alias(candidate))
3857 && type_candidate_visible_at_reference(candidate, node, ctx)
3858 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3859 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3860 brokk_bifrost_core::analyzer::Language::Cpp,
3861 &cpp_name_for(candidate),
3862 ) == components
3863 || member_alias_owner_matches_reference_for(candidate, node, ctx))
3864 );
3865 let direct_alias_target = ctx
3866 .analyzer
3867 .type_alias_provider()
3868 .is_some_and(|provider| provider.is_type_alias(target))
3869 && candidates
3870 .iter()
3871 .any(|candidate| same_symbol(candidate, target));
3872 let unique_target = matches!(
3873 candidates.as_slice(),
3874 [candidate] if same_visible_symbol(candidate, target)
3875 );
3876 if direct_alias_target || unique_target || canonical_alias_target_matches {
3877 let matched = if ctx
3878 .analyzer
3879 .type_alias_provider()
3880 .is_some_and(|provider| provider.is_type_alias(target))
3881 && ctx
3882 .analyzer
3883 .parent_of(target)
3884 .is_some_and(|owner| owner.is_class())
3885 && ctx
3886 .visibility
3887 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
3888 {
3889 qualified.nodes[component_count - 1]
3893 } else {
3894 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
3895 };
3896 if template_type_component_preserves_target(matched, &candidates, ctx) {
3897 matches.push(matched);
3898 }
3899 }
3900 }
3901 (!matches.is_empty()).then_some(matches)
3902}
3903
3904fn target_guided_unproven_out_of_line_owner<'tree>(
3907 node: Node<'tree>,
3908 ctx: &ScanCtx<'_>,
3909) -> Option<Node<'tree>> {
3910 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
3911 || !is_declaration_name(node)
3912 {
3913 return None;
3914 }
3915 let target = physically_visible_type_target(ctx)?;
3916 if !target.is_class() {
3917 return None;
3918 }
3919 let qualified = qualified_owner_components(node, ctx.source)?;
3920 let target_components = canonical_cpp_scope_components(target);
3921 let mut scope = ctx
3922 .recovered_sentinel_scope(node)
3923 .or_else(|| {
3924 let parser_scope = enclosing_namespace_components(node, ctx.source);
3925 (!parser_scope.is_empty()).then_some(parser_scope)
3926 })
3927 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
3928 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
3929 if has_malformed_wrapper_function_definition_ancestor(node)
3930 && target_namespace.starts_with(&scope)
3931 && target_namespace.len() > scope.len()
3932 {
3933 scope = target_namespace.to_vec();
3934 }
3935 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
3936 .any(|components| components == target_components)
3937 {
3938 return None;
3939 }
3940 let owner_name = qualified.names.last()?;
3941 let candidates = ctx
3942 .visibility
3943 .visible_identifier_candidates(ctx.file, owner_name)
3944 .filter(|candidate| {
3945 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
3946 })
3947 .collect::<Vec<_>>();
3948 if candidates.is_empty()
3949 || candidates
3950 .iter()
3951 .any(|candidate| !same_visible_symbol(candidate, target))
3952 {
3953 return None;
3954 }
3955 qualified.nodes.last().copied()
3956}
3957
3958fn macro_namespace_scope_matches(
3959 candidate_owner: &[String],
3960 node: Node<'_>,
3961 ctx: &ScanCtx<'_>,
3962) -> bool {
3963 let namespace = enclosing_namespace_components(node, ctx.source);
3964 if namespace.is_empty() || candidate_owner.is_empty() {
3965 return false;
3966 }
3967 let mut expanded_owner = Vec::new();
3968 for component in candidate_owner {
3969 if let Some(replacement) =
3970 ctx.visibility
3971 .object_macro_replacement_at(ctx.file, component, node.start_byte())
3972 {
3973 let replacement_components =
3974 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3975 brokk_bifrost_core::analyzer::Language::Cpp,
3976 &replacement,
3977 );
3978 if replacement_components.is_empty() {
3979 return false;
3980 }
3981 expanded_owner.extend(replacement_components);
3982 } else {
3983 expanded_owner.push(component.clone());
3984 }
3985 }
3986 expanded_owner == namespace
3987}
3988
3989fn target_guided_missing_type_leaf<'tree>(
3990 node: Node<'tree>,
3991 ctx: &ScanCtx<'_>,
3992) -> Option<Node<'tree>> {
3993 physically_visible_type_target(ctx)?;
3994 target_guided_missing_dependent_nested_type_leaf(node, ctx)
3995 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
3996 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
3997 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
3998 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
3999 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4000 .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
4001}
4002
4003fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4007 node: Node<'tree>,
4008 ctx: &ScanCtx<'_>,
4009) -> Option<Node<'tree>> {
4010 if node.kind() != "type_identifier"
4011 || is_declaration_name(node)
4012 || local_type_name_shadows(node, ctx)
4013 {
4014 return None;
4015 }
4016 let alias_provider = ctx.analyzer.type_alias_provider()?;
4017 let name = node_text(node, ctx.source);
4018 let aliases = ctx
4019 .visibility
4020 .visible_identifier_candidates(ctx.file, name)
4021 .filter(|candidate| alias_provider.is_type_alias(candidate))
4022 .filter(|candidate| {
4023 ctx.analyzer
4024 .parent_of(candidate)
4025 .is_some_and(|owner| owner.is_class())
4026 })
4027 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4028 .filter(|candidate| {
4029 ctx.visibility
4030 .external_type_candidate_guard_compatible_in_context(
4031 &ctx.analyzer,
4032 ctx.file,
4033 candidate,
4034 node,
4035 )
4036 })
4037 .collect::<Vec<_>>();
4038 (!aliases.is_empty()
4039 && aliases.iter().all(|candidate| {
4040 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4041 }))
4042 .then_some(node)
4043}
4044
4045fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
4049 node: Node<'tree>,
4050 ctx: &ScanCtx<'_>,
4051) -> Option<Node<'tree>> {
4052 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
4053 matches!(
4054 declaration.kind(),
4055 "parameter_declaration" | "optional_parameter_declaration"
4056 )
4057 });
4058 let placement_new_type = node.parent().is_some_and(|parent| {
4059 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
4060 });
4061 if !parameter && !placement_new_type {
4062 return None;
4063 }
4064 if !ctx
4065 .analyzer
4066 .type_alias_provider()
4067 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4068 || !type_alias_owner_matches_structured_reference(node, ctx)
4069 {
4070 return None;
4071 }
4072 target_guided_missing_declaration_type_leaf(node, ctx)
4073}
4074
4075fn target_guided_missing_dependent_nested_type_leaf<'tree>(
4082 node: Node<'tree>,
4083 ctx: &ScanCtx<'_>,
4084) -> Option<Node<'tree>> {
4085 if !matches!(
4086 node.kind(),
4087 "qualified_identifier" | "scoped_type_identifier"
4088 ) || !qualified_type_scope_contains_template(node)
4089 {
4090 return None;
4091 }
4092 let name = node
4093 .child_by_field_name("name")
4094 .filter(|name| name.kind() == "type_identifier")?;
4095 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
4096 return None;
4097 }
4098 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
4099 if !owner_target.is_class() {
4100 return None;
4101 }
4102 let owner = node.child_by_field_name("scope")?;
4103 let owner_resolution = resolve_type_node_lexically_for_target(
4104 owner,
4105 &ctx.analyzer,
4106 ctx.visibility,
4107 &ctx.ordinary_type_imports,
4108 ctx.file,
4109 ctx.source,
4110 &owner_target,
4111 Some(&ctx.lexical_scope_cache),
4112 ctx.recovered_sentinel_scope(owner).as_deref(),
4113 );
4114 if matches!(
4115 owner_resolution,
4116 LexicalTypeResolution::Resolved {
4117 ref unit,
4118 ref candidates,
4119 ..
4120 } if type_resolution_matches_unit_target(
4121 owner,
4122 unit,
4123 candidates,
4124 &owner_target,
4125 ctx,
4126 )
4127 ) {
4128 return Some(name);
4129 }
4130
4131 let qualified = qualified_owner_components(node, ctx.source)?;
4132 let parser_namespace = enclosing_namespace_components(node, ctx.source);
4133 let orphaned_namespace = ctx
4134 .orphaned_namespaces
4135 .iter()
4136 .filter(|envelope| envelope.error_marked && envelope.body_end < node.start_byte())
4137 .max_by_key(|envelope| envelope.body_end)
4138 .map(|envelope| envelope.components.clone());
4139 let indexed_scope = ctx
4140 .recovered_sentinel_scope(node)
4141 .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
4142 .or(orphaned_namespace)
4143 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
4144 let owner_components = canonical_cpp_scope_components(&owner_target);
4145 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
4146 .any(|components| components == owner_components)
4147 {
4148 return None;
4149 }
4150 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
4151 .into_iter()
4152 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
4153 .collect::<Vec<_>>();
4154 (!scoped_candidates.is_empty()
4155 && scoped_candidates
4156 .iter()
4157 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
4158 .then_some(name)
4159}
4160
4161fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
4176 node: Node<'tree>,
4177 ctx: &ScanCtx<'_>,
4178) -> Option<Node<'tree>> {
4179 let target = &ctx.spec.target;
4180 let leaf = template_reference_name_node(node).unwrap_or(node);
4181 let name = node_text(leaf, ctx.source);
4182 let (components, global) = type_reference_components(node, ctx.source)?;
4183 if global || components.len() != 1 || components[0] != name {
4184 return None;
4185 }
4186 if !target.is_class()
4187 || ctx
4188 .analyzer
4189 .type_alias_provider()
4190 .is_some_and(|provider| provider.is_type_alias(target))
4191 || is_declaration_name(leaf)
4192 || name != target.identifier()
4193 || ctx.local_shadows.is_shadowed(name)
4194 || local_type_name_shadows(leaf, ctx)
4195 || !ctx.visibility.is_physically_visible(ctx.file, target)
4196 || !ctx.visibility.external_type_candidate_visible_in_context(
4197 &ctx.analyzer,
4198 ctx.file,
4199 target,
4200 leaf,
4201 )
4202 {
4203 return None;
4204 }
4205
4206 let target_components = canonical_cpp_scope_components(target);
4207 let target_package_len = target_components.len().checked_sub(1)?;
4208 let target_package = &target_components[..target_package_len];
4209 let surviving_namespace = enclosing_namespace_components(leaf, ctx.source);
4210 if !target_package.starts_with(&surviving_namespace) {
4211 return None;
4212 }
4213 let envelope = ctx
4214 .orphaned_namespaces
4215 .iter()
4216 .filter(|envelope| envelope.error_marked && envelope.body_end < leaf.start_byte())
4217 .max_by_key(|envelope| envelope.body_end)?;
4218 if envelope.components.as_slice() != target_package {
4219 return None;
4220 }
4221
4222 if target.source() == ctx.file
4223 && !ctx.analyzer.ranges(target).iter().any(|range| {
4224 range.start_byte < envelope.body_end && range.end_byte <= envelope.body_end
4225 })
4226 {
4227 return None;
4228 }
4229
4230 let candidates = visible_type_identifier_candidates(ctx, name);
4231 if candidates.is_empty()
4232 || candidates
4233 .iter()
4234 .any(|candidate| !same_visible_symbol(candidate, target))
4235 {
4236 return None;
4237 }
4238 Some(leaf)
4239}
4240
4241fn orphaned_namespace_alias_type_resolution(
4246 node: Node<'_>,
4247 ctx: &ScanCtx<'_>,
4248) -> Option<LexicalTypeResolution> {
4249 let (reference_components, global) = type_reference_components(node, ctx.source)?;
4250 if global
4251 || reference_components.len() != 1
4252 || is_declaration_name(node)
4253 || local_type_name_shadows(node, ctx)
4254 {
4255 return None;
4256 }
4257
4258 let surviving_namespace = enclosing_namespace_components(node, ctx.source);
4259 let envelope = ctx
4260 .orphaned_namespaces
4261 .iter()
4262 .filter(|envelope| {
4263 envelope.error_marked
4264 && envelope.body_end < node.start_byte()
4265 && envelope.components.starts_with(&surviving_namespace)
4266 })
4267 .max_by_key(|envelope| envelope.body_end)?;
4268 let resolution = resolve_type_node_lexically_for_target(
4269 node,
4270 &ctx.analyzer,
4271 ctx.visibility,
4272 &ctx.ordinary_type_imports,
4273 ctx.file,
4274 ctx.source,
4275 &ctx.spec.target,
4276 Some(&ctx.lexical_scope_cache),
4277 Some(&envelope.components),
4278 );
4279 let LexicalTypeResolution::Resolved {
4280 ref unit,
4281 ref components,
4282 ref candidates,
4283 } = resolution
4284 else {
4285 return None;
4286 };
4287 let alias_provider = ctx.analyzer.type_alias_provider()?;
4288 if components.len() != envelope.components.len() + 1
4289 || !components.starts_with(&envelope.components)
4290 || candidates.is_empty()
4291 || candidates.iter().any(|candidate| {
4292 !alias_provider.is_type_alias(candidate)
4293 || canonical_cpp_scope_components(candidate) != *components
4294 })
4295 || !type_resolution_matches_target(node, unit, candidates, ctx)
4296 {
4297 return None;
4298 }
4299 Some(resolution)
4300}
4301
4302fn target_guided_missing_member_alias_type_leaf<'tree>(
4308 node: Node<'tree>,
4309 ctx: &ScanCtx<'_>,
4310) -> Option<Node<'tree>> {
4311 if !is_template_argument_type_leaf(node)
4312 || is_declaration_name(node)
4313 || ctx
4314 .target_declaration_ranges
4315 .iter()
4316 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
4317 || node_text(node, ctx.source) != ctx.spec.target.identifier()
4318 || local_type_name_shadows(node, ctx)
4319 || !ctx
4320 .analyzer
4321 .type_alias_provider()
4322 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4323 {
4324 return None;
4325 }
4326 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
4327 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
4328 if !owner_scope_matches
4329 && !indexed_scope.is_some_and(|scope| {
4330 indexed_scope_matches_target_name(
4331 &scope,
4332 &[ctx.spec.target.identifier().to_string()],
4333 false,
4334 &ctx.spec.target,
4335 )
4336 })
4337 {
4338 return None;
4339 }
4340 if !(ctx.visibility.external_type_candidate_visible_in_context(
4347 &ctx.analyzer,
4348 ctx.file,
4349 &ctx.spec.target,
4350 node,
4351 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
4352 {
4353 return None;
4354 }
4355 let target_visible = ctx
4356 .visibility
4357 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4358 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4359 target_visible.then_some(node)
4360}
4361
4362fn target_guided_missing_template_argument_type_leaf<'tree>(
4368 node: Node<'tree>,
4369 ctx: &ScanCtx<'_>,
4370) -> Option<Node<'tree>> {
4371 let target = &ctx.spec.target;
4372 let name = node_text(node, ctx.source);
4373 if !target.is_class()
4374 || !is_template_argument_type_leaf(node)
4375 || is_declaration_name(node)
4376 || name != target.identifier()
4377 || ctx.local_shadows.is_shadowed(name)
4378 || local_type_name_shadows(node, ctx)
4379 || !ctx.visibility.is_physically_visible(ctx.file, target)
4380 || ctx
4381 .analyzer
4382 .type_alias_provider()
4383 .is_some_and(|provider| provider.is_type_alias(target))
4384 {
4385 return None;
4386 }
4387
4388 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4389 let target_components = canonical_cpp_scope_components(target);
4390 if target_components.last().map(String::as_str) != Some(name)
4391 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4392 .any(|components| components == target_components)
4393 {
4394 return None;
4395 }
4396
4397 let candidates = visible_type_identifier_candidates(ctx, name);
4401 if candidates.is_empty()
4402 || candidates.iter().any(|candidate| {
4403 !candidate.is_class()
4404 || ctx
4405 .analyzer
4406 .type_alias_provider()
4407 .is_some_and(|provider| provider.is_type_alias(candidate))
4408 || (!same_visible_symbol(candidate, target)
4409 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4410 .any(|components| components == canonical_cpp_scope_components(candidate)))
4411 })
4412 || !candidates
4413 .iter()
4414 .any(|candidate| same_visible_symbol(candidate, target))
4415 {
4416 return None;
4417 }
4418
4419 ctx.visibility
4422 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
4423 .then_some(node)
4424}
4425
4426fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4431 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
4432}
4433
4434fn member_alias_owner_matches_reference_for(
4435 target: &CodeUnit,
4436 node: Node<'_>,
4437 ctx: &ScanCtx<'_>,
4438) -> bool {
4439 let Some(owner) = ctx.analyzer.parent_of(target) else {
4440 return false;
4441 };
4442 if !owner.is_class() {
4443 return false;
4444 }
4445 let reference_owner = ctx
4446 .class_ranges
4447 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
4448 .or_else(|| structured_enclosing_owner(node, ctx));
4449 if reference_owner.as_ref().is_some_and(|reference_owner| {
4450 ctx.visibility
4451 .same_template_owner_identity(&owner, reference_owner)
4452 }) {
4453 return true;
4454 }
4455 if reference_owner.is_some_and(|reference_owner| {
4456 matches!(
4457 resolve_declaring_member_owner(
4458 &ctx.analyzer,
4459 ctx.visibility,
4460 ctx.file,
4461 &reference_owner,
4462 target.identifier(),
4463 ),
4464 EnclosingMemberOwnerResolution::Owner(declaring_owner)
4465 if ctx
4466 .visibility
4467 .same_template_owner_identity(&owner, &declaring_owner)
4468 )
4469 }) {
4470 return true;
4471 }
4472 let range = Range {
4473 start_byte: node.start_byte(),
4474 end_byte: node.end_byte(),
4475 start_line: node.start_position().row + 1,
4476 end_line: node.end_position().row + 1,
4477 };
4478 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
4479 while let Some(candidate) = indexed_enclosing {
4480 if candidate.is_class()
4481 && ctx
4482 .visibility
4483 .same_template_owner_identity(&owner, &candidate)
4484 {
4485 return true;
4486 }
4487 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
4488 }
4489 if let Some(reference_body) = malformed_recovered_class_body(node) {
4490 let mut root = node;
4491 while let Some(parent) = root.parent() {
4492 root = parent;
4493 }
4494 if ctx.analyzer.ranges(target).iter().any(|range| {
4495 root.descendant_for_byte_range(range.start_byte, range.end_byte)
4496 .and_then(malformed_recovered_class_body)
4497 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
4498 }) {
4499 return true;
4500 }
4501 }
4502 if structured_enclosing_owner(node, ctx)
4503 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
4504 {
4505 return true;
4506 }
4507 let owner_components = canonical_cpp_scope_components(&owner);
4508 if ctx
4509 .recovered_sentinel_scope(node)
4510 .is_some_and(|scope| scope == owner_components)
4511 {
4512 return true;
4513 }
4514 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4515 else {
4516 return false;
4517 };
4518 !owner_components.is_empty() && reference_scope == owner_components
4519}
4520
4521fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
4522 loop {
4523 if node.kind() == "compound_statement"
4524 && node
4525 .parent()
4526 .is_some_and(|parent| parent.kind() == "declaration_list")
4527 && node.prev_named_sibling().is_some_and(|header| {
4528 header.kind() == "ERROR"
4529 && header.end_byte() <= node.start_byte()
4530 && error_contains_class_header(header)
4531 })
4532 {
4533 return Some(node);
4534 }
4535 node = node.parent()?;
4536 }
4537}
4538
4539fn error_contains_class_header(node: Node<'_>) -> bool {
4540 let mut pending = vec![(node, 0usize)];
4541 while let Some((current, depth)) = pending.pop() {
4542 if matches!(current.kind(), "class" | "struct" | "union") {
4543 let mut sibling = current.next_sibling();
4544 let mut saw_name = false;
4545 while let Some(candidate) = sibling {
4546 match candidate.kind() {
4547 "comment" => {}
4548 "{" | "base_class_clause" | ":" => return saw_name,
4549 "identifier" | "type_identifier" if !saw_name => saw_name = true,
4550 _ if !candidate.is_named() => {}
4551 _ => break,
4552 }
4553 sibling = candidate.next_sibling();
4554 }
4555 }
4556 if depth >= 1 {
4557 continue;
4558 }
4559 let mut cursor = current.walk();
4560 pending.extend(
4561 current
4562 .children(&mut cursor)
4563 .filter(|child| child.kind() != "compound_statement")
4564 .map(|child| (child, depth + 1)),
4565 );
4566 }
4567 false
4568}
4569
4570fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4571 has_ancestor_kind(node, "compound_statement")
4572 && ctx
4573 .visibility
4574 .external_type_candidate_guard_compatible_in_context(
4575 &ctx.analyzer,
4576 ctx.file,
4577 &ctx.spec.target,
4578 node,
4579 )
4580}
4581
4582fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4583 ctx.analyzer
4584 .type_alias_provider()
4585 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4586 && member_alias_owner_matches_reference(node, ctx)
4587}
4588
4589fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4593 if !ctx
4594 .analyzer
4595 .type_alias_provider()
4596 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4597 {
4598 return false;
4599 }
4600 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4601 return false;
4602 };
4603 let mut reference_owner = structured_enclosing_owner(node, ctx);
4604 while let Some(owner) = reference_owner {
4605 if same_logical_symbol(&target_owner, &owner) {
4606 return true;
4607 }
4608 reference_owner = ctx.analyzer.parent_of(&owner);
4609 }
4610 false
4611}
4612
4613fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4618 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4619 return false;
4620 };
4621 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4622 return false;
4623 };
4624 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4625 return false;
4626 };
4627 let candidates = ctx
4628 .visibility
4629 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4630 .filter(|candidate| {
4631 candidate.is_class()
4632 && alias_provider.is_type_alias(candidate)
4633 && !same_visible_symbol(candidate, &ctx.spec.target)
4634 })
4635 .cloned()
4636 .collect::<Vec<_>>();
4637
4638 let mut owner = Some(reference_owner);
4639 while let Some(owner_unit) = owner {
4640 if same_logical_symbol(&target_owner, &owner_unit) {
4641 return false;
4642 }
4643 if candidates.iter().any(|candidate| {
4644 ctx.analyzer
4645 .parent_of(candidate)
4646 .is_some_and(|candidate_owner| {
4647 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
4648 })
4649 && ctx
4650 .visibility
4651 .external_type_candidate_guard_compatible_in_context(
4652 &ctx.analyzer,
4653 ctx.file,
4654 candidate,
4655 node,
4656 )
4657 }) {
4658 return true;
4659 }
4660 owner = ctx.analyzer.parent_of(&owner_unit);
4661 }
4662 false
4663}
4664
4665fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4666 if cpp_active_template_type_parameter(node, ctx.spec.target.identifier(), ctx.source) {
4667 return true;
4668 }
4669 let Some(callable) = nearest_callable_scope(node) else {
4670 return false;
4671 };
4672 let mut root_callable = callable;
4673 let mut ancestor = callable.parent();
4674 while let Some(current) = ancestor {
4675 if matches!(current.kind(), "function_definition" | "lambda_expression") {
4676 root_callable = current;
4677 }
4678 ancestor = current.parent();
4679 }
4680
4681 let mut stack = vec![root_callable];
4682 while let Some(current) = stack.pop() {
4683 if current.start_byte() >= node.start_byte() {
4684 continue;
4685 }
4686 if let Some(name) = local_type_name_declaration_node(current)
4687 && node_text(name, ctx.source) == ctx.spec.target.identifier()
4688 && nearest_callable_scope(current).is_some_and(|owner| {
4689 !is_malformed_wrapper_function_definition(owner)
4690 && owner.start_byte() <= callable.start_byte()
4691 && callable.end_byte() <= owner.end_byte()
4692 })
4693 && local_alias_scope_contains_node(current, node)
4694 {
4695 return true;
4696 }
4697 let mut cursor = current.walk();
4698 stack.extend(current.named_children(&mut cursor));
4699 }
4700 false
4701}
4702
4703fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
4704 local_type_alias_name_node(node).or_else(|| match node.kind() {
4705 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
4706 .child_by_field_name("name")
4707 .filter(|name| is_declaration_name(*name)),
4708 _ => None,
4709 })
4710}
4711
4712fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
4713 loop {
4714 if matches!(node.kind(), "function_definition" | "lambda_expression") {
4715 return Some(node);
4716 }
4717 node = node.parent()?;
4718 }
4719}
4720
4721fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
4722 match node.kind() {
4723 "alias_declaration" => node.child_by_field_name("name"),
4724 "type_definition" => node
4725 .child_by_field_name("declarator")
4726 .and_then(declarator_name_node),
4727 _ => None,
4728 }
4729}
4730
4731fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
4732 let mut current = alias.parent();
4733 while let Some(parent) = current {
4734 if matches!(
4735 parent.kind(),
4736 "class_specifier" | "struct_specifier" | "union_specifier"
4737 ) {
4738 return false;
4739 }
4740 if parent.kind() == "compound_statement" {
4741 return parent.start_byte() <= node.start_byte()
4742 && node.end_byte() <= parent.end_byte();
4743 }
4744 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
4745 let Some(body) = parent.child_by_field_name("body") else {
4746 return false;
4747 };
4748 return node_is_within(body, alias) && node_is_within(body, node);
4749 }
4750 current = parent.parent();
4751 }
4752 false
4753}
4754
4755fn target_guided_missing_declaration_type_leaf<'tree>(
4756 node: Node<'tree>,
4757 ctx: &ScanCtx<'_>,
4758) -> Option<Node<'tree>> {
4759 if is_declaration_name(node) {
4760 return None;
4761 }
4762 let component_nodes = cpp_name_component_nodes(node)?;
4763 let name_node = component_nodes.last().copied()?;
4764 let name = node_text(name_node, ctx.source);
4765 if name != ctx.spec.target.identifier() {
4766 return None;
4767 }
4768 let inside_target_declaration = ctx
4769 .target_declaration_ranges
4770 .iter()
4771 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
4772 if !inside_target_declaration
4773 && !ctx.visibility.external_type_candidate_visible_in_context(
4774 &ctx.analyzer,
4775 ctx.file,
4776 &ctx.spec.target,
4777 node,
4778 )
4779 {
4780 return None;
4781 }
4782 let components = component_nodes
4783 .iter()
4784 .map(|component| node_text(*component, ctx.source).to_string())
4785 .collect::<Vec<_>>();
4786 let local_alias_shadow = local_type_name_shadows(node, ctx);
4787 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
4788 let indexed_alias_owner = ctx
4789 .analyzer
4790 .type_alias_provider()
4791 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4792 && member_alias_owner_matches_reference(node, ctx);
4793 let target_alias_self_reference = inside_target_declaration
4794 && ctx
4795 .analyzer
4796 .type_alias_provider()
4797 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
4798 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
4799 &ctx.analyzer,
4800 ctx.file,
4801 &ctx.spec.target,
4802 node,
4803 ) || member_alias_complete_class_context(node, ctx);
4804 if !target_alias_self_reference
4805 && !local_alias_shadow
4806 && member_alias_visible
4807 && (structured_alias_owner || indexed_alias_owner)
4808 {
4809 return Some(node);
4810 }
4811 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4812 let declaration = nearest_declaration_type_context(node)?;
4813 let exact_scope_match = indexed_scope_matches_target_name(
4814 &indexed_scope,
4815 &components,
4816 is_globally_qualified_cpp_name(node),
4817 &ctx.spec.target,
4818 );
4819 let candidates = visible_type_identifier_candidates(ctx, name);
4820 let unique_visible_target = !candidates.is_empty()
4821 && candidates
4822 .iter()
4823 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4824 if matches!(declaration.kind(), "field_declaration" | "declaration") {
4825 let parser_lost_declaration_scope =
4826 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
4827 && unique_visible_target;
4828 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
4829 }
4830 let lost_namespace_parameter_context =
4831 matches!(
4832 declaration.kind(),
4833 "parameter_declaration" | "optional_parameter_declaration"
4834 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
4835 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
4836 return Some(node);
4837 }
4838 None
4839}
4840
4841fn target_guided_missing_alias_rhs_type_leaf<'tree>(
4842 node: Node<'tree>,
4843 ctx: &ScanCtx<'_>,
4844) -> Option<Node<'tree>> {
4845 let mut stack = vec![node];
4846 while let Some(candidate) = stack.pop() {
4847 if candidate.kind() == "type_identifier"
4848 && !is_declaration_name(candidate)
4849 && matches!(
4850 candidate.parent().map(|parent| parent.kind()),
4851 Some("template_type")
4852 )
4853 {
4854 let mut current = candidate.parent();
4855 let mut saw_qualified = false;
4856 let mut saw_dependent = false;
4857 let mut saw_type_descriptor = false;
4858 let mut saw_alias_declaration = false;
4859 while let Some(ancestor) = current {
4860 match ancestor.kind() {
4861 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
4862 "dependent_type" => saw_dependent = true,
4863 "type_descriptor" => saw_type_descriptor = true,
4864 "alias_declaration" => {
4865 saw_alias_declaration = true;
4866 break;
4867 }
4868 "template_type"
4869 | "template_argument_list"
4870 | "typename"
4871 | "template_declaration" => {}
4872 _ => {}
4873 }
4874 current = ancestor.parent();
4875 }
4876 let name = node_text(candidate, ctx.source);
4877 let visible_candidates = visible_type_identifier_candidates(ctx, name);
4878 let canonical_alias_target = visible_candidates
4879 .iter()
4880 .filter_map(|alias| ctx.visibility.alias_target(alias))
4881 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
4882 let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
4883 &ctx.analyzer,
4884 ctx.file,
4885 name,
4886 &ctx.spec.target,
4887 ) || canonical_alias_target;
4888 if saw_qualified
4889 && saw_dependent
4890 && saw_type_descriptor
4891 && saw_alias_declaration
4892 && alias_resolves
4893 && ctx.visibility.external_type_candidate_visible_in_context(
4894 &ctx.analyzer,
4895 ctx.file,
4896 &ctx.spec.target,
4897 candidate,
4898 )
4899 {
4900 return Some(candidate);
4901 }
4902 }
4903 for index in (0..candidate.named_child_count()).rev() {
4904 if let Some(child) = candidate.named_child(index) {
4905 stack.push(child);
4906 }
4907 }
4908 }
4909 None
4910}
4911
4912fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
4913 let mut current = Some(node);
4914 while let Some(ancestor) = current {
4915 if matches!(
4916 ancestor.kind(),
4917 "field_declaration"
4918 | "parameter_declaration"
4919 | "optional_parameter_declaration"
4920 | "declaration"
4921 | "type_descriptor"
4922 ) {
4923 let contains_type = ancestor
4924 .child_by_field_name("type")
4925 .is_some_and(|type_node| {
4926 type_node.start_byte() <= node.start_byte()
4927 && node.end_byte() <= type_node.end_byte()
4928 });
4929 if contains_type
4930 && !(ancestor.kind() == "type_descriptor" && is_template_argument_type_leaf(node))
4931 {
4932 return Some(ancestor);
4933 }
4934 if ancestor.kind() == "type_descriptor"
4935 && ancestor.parent().is_some_and(|parent| {
4936 matches!(
4937 parent.kind(),
4938 "cast_expression"
4939 | "new_expression"
4940 | "sizeof_expression"
4941 | "alignof_expression"
4942 | "typeid_expression"
4943 )
4944 })
4945 {
4946 return Some(ancestor);
4947 }
4948 }
4949 if matches!(
4950 ancestor.kind(),
4951 "compound_statement"
4952 | "translation_unit"
4953 | "namespace_definition"
4954 | "alias_declaration"
4955 | "type_definition"
4956 | "base_class_clause"
4957 ) {
4958 return None;
4959 }
4960 current = ancestor.parent();
4961 }
4962 None
4963}
4964
4965fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
4966 let mut candidates = Vec::new();
4967 for candidate in ctx
4968 .visibility
4969 .visible_identifier_candidates(ctx.file, name)
4970 .filter(|candidate| {
4971 candidate.is_class()
4972 || ctx
4973 .analyzer
4974 .type_alias_provider()
4975 .is_some_and(|provider| provider.is_type_alias(candidate))
4976 })
4977 {
4978 if !candidates
4979 .iter()
4980 .any(|existing| same_logical_symbol(existing, candidate))
4981 {
4982 candidates.push(candidate.clone());
4983 }
4984 }
4985 candidates
4986}
4987
4988fn target_guided_static_cast_alias_type_descriptor<'tree>(
4993 node: Node<'tree>,
4994 ctx: &ScanCtx<'_>,
4995) -> Option<Node<'tree>> {
4996 if node.kind() != "type_descriptor" {
4997 return None;
4998 }
4999 let argument_list = node.parent().filter(|parent| {
5000 parent.kind() == "template_argument_list"
5001 && parent.named_child_count() == 1
5002 && parent.named_child(0) == Some(node)
5003 })?;
5004 let template = argument_list.parent().filter(|parent| {
5005 parent.kind() == "template_function"
5006 && parent.child_by_field_name("arguments") == Some(argument_list)
5007 })?;
5008 let name = template.child_by_field_name("name")?;
5009 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5010 return None;
5011 }
5012 let target = &ctx.spec.target;
5013 if node_text(node, ctx.source) != target.identifier()
5014 || !ctx
5015 .analyzer
5016 .type_alias_provider()
5017 .is_some_and(|provider| provider.is_type_alias(target))
5018 || !ctx.visibility.is_physically_visible(ctx.file, target)
5019 || !ctx.visibility.external_type_candidate_visible_in_context(
5020 &ctx.analyzer,
5021 ctx.file,
5022 target,
5023 node,
5024 )
5025 {
5026 return None;
5027 }
5028
5029 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5030 let target_scope = canonical_cpp_scope_components(target);
5031 let name_components = [target.identifier().to_string()];
5032 if !lexical_component_tiers(&name_components, false, &indexed_scope)
5033 .any(|components| components == target_scope)
5034 {
5035 return None;
5036 }
5037
5038 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5039 if !candidates
5040 .iter()
5041 .any(|candidate| same_visible_symbol(candidate, target))
5042 {
5043 return None;
5044 }
5045 if candidates.iter().any(|candidate| {
5046 !same_visible_symbol(candidate, target)
5047 && lexical_component_tiers(&name_components, false, &indexed_scope)
5048 .any(|components| components == canonical_cpp_scope_components(candidate))
5049 }) {
5050 return None;
5051 }
5052 Some(node)
5053}
5054
5055fn indexed_scope_matches_target_name(
5056 indexed_scope: &[String],
5057 components: &[String],
5058 global: bool,
5059 target: &CodeUnit,
5060) -> bool {
5061 let target_name = cpp_name_for(target);
5062 lexical_component_tiers(components, global, indexed_scope)
5063 .any(|qualified| qualified.join("::") == target_name)
5064}
5065
5066fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5067 if target.package_name().is_empty() {
5068 return false;
5069 }
5070 if indexed_scope.len() <= 1 {
5071 return true;
5072 }
5073 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5074 brokk_bifrost_core::analyzer::Language::Cpp,
5075 &cpp_name_for(target),
5076 );
5077 target_scope.pop();
5078 (1..indexed_scope.len())
5079 .rev()
5080 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5081}
5082
5083fn indexed_enclosing_lexical_scope(
5084 analyzer: &CppGraphSource<'_>,
5085 file: &ProjectFile,
5086 node: Node<'_>,
5087) -> Option<Vec<String>> {
5088 let range = Range {
5089 start_byte: node.start_byte(),
5090 end_byte: node.end_byte(),
5091 start_line: node.start_position().row,
5092 end_line: node.end_position().row,
5093 };
5094 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
5095 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5096 brokk_bifrost_core::analyzer::Language::Cpp,
5097 &cpp_name_for(&enclosing),
5098 );
5099 if !enclosing.is_class() && !enclosing.is_module() {
5100 components.pop();
5101 }
5102 Some(components)
5103}
5104
5105fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
5106 if node.kind() != "qualified_identifier" {
5107 return None;
5108 }
5109 let mut stack = vec![node];
5110 while let Some(current) = stack.pop() {
5111 if current.kind() != "qualified_identifier" {
5112 continue;
5113 }
5114 if let Some(scope) = current.child_by_field_name("scope") {
5115 let text = qualified_scope_text(scope, ctx.source);
5116 if name_mentions(&text, &ctx.spec.member_name) {
5117 return Some(scope);
5118 }
5119 }
5120 let mut cursor = current.walk();
5121 for child in current.named_children(&mut cursor) {
5122 if child.kind() == "qualified_identifier" {
5123 stack.push(child);
5124 }
5125 }
5126 }
5127 None
5128}
5129
5130fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
5131 let mut parts = vec![node_text(scope, source).to_string()];
5132 let mut current = scope.parent();
5133 while let Some(qualified) = current {
5134 let Some(parent) = qualified.parent() else {
5135 break;
5136 };
5137 if parent.kind() != "qualified_identifier"
5138 || parent.child_by_field_name("name") != Some(qualified)
5139 {
5140 break;
5141 }
5142 if let Some(outer_scope) = parent.child_by_field_name("scope") {
5143 parts.push(node_text(outer_scope, source).to_string());
5144 }
5145 current = Some(parent);
5146 }
5147 parts.reverse();
5148 parts.join("::")
5149}
5150
5151fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5152 if node.kind() == "using_declaration" {
5153 maybe_record_using_callable_hit(node, ctx);
5154 return;
5155 }
5156 if has_ancestor_kind(node, "using_declaration") {
5157 return;
5158 }
5159 if node.kind() == "function_definition" {
5160 return;
5161 }
5162 if !matches!(
5163 node.kind(),
5164 "call_expression"
5165 | "new_expression"
5166 | "compound_literal_expression"
5167 | "declaration"
5168 | "field_initializer"
5169 ) {
5170 return;
5171 }
5172 let Some(owner) = ctx.spec.owner.as_ref() else {
5173 return;
5174 };
5175 if node.kind() == "field_initializer" {
5176 if !field_initializer_constructs_target(node, ctx, owner) {
5177 return;
5178 }
5179 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5180 match ctx
5181 .visibility
5182 .call_arity_evidence(ctx.file, node, ctx.source)
5183 .accepts(expected)
5184 {
5185 Some(true) => {}
5186 Some(false) => return,
5187 None => {
5188 push_unproven_hit(node, ctx);
5189 return;
5190 }
5191 }
5192 }
5193 push_hit(node, ctx);
5194 return;
5195 }
5196 if node.kind() == "declaration" {
5197 if declaration_is_object_construction_candidate(node, ctx)
5198 && declaration_mentions_type(node, ctx, owner)
5199 && ctx
5200 .spec
5201 .callable_arity_at(node.start_byte())
5202 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
5203 {
5204 push_hit(node, ctx);
5205 }
5206 return;
5207 }
5208 let Some(type_node) = constructor_type_node(node) else {
5209 return;
5210 };
5211 let hit_node = function_terminal_node(type_node);
5212 let text = node_text(type_node, ctx.source);
5213 if !name_mentions(text, &ctx.spec.member_name) {
5214 return;
5215 }
5216 *ctx.raw_match_count += 1;
5217 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5218 match ctx
5219 .visibility
5220 .call_arity_evidence(ctx.file, node, ctx.source)
5221 .accepts(expected)
5222 {
5223 Some(true) => {}
5224 Some(false) => return,
5225 None => {
5226 push_unproven_hit(hit_node, ctx);
5227 return;
5228 }
5229 }
5230 }
5231 let structured_resolution = resolve_type_node_lexically_for_target(
5232 type_node,
5233 &ctx.analyzer,
5234 ctx.visibility,
5235 &ctx.ordinary_type_imports,
5236 ctx.file,
5237 ctx.source,
5238 owner,
5239 Some(&ctx.lexical_scope_cache),
5240 ctx.recovered_sentinel_scope(type_node).as_deref(),
5241 );
5242 let structurally_resolves = matches!(
5243 &structured_resolution,
5244 LexicalTypeResolution::Resolved {
5245 unit, candidates, ..
5246 } if same_visible_symbol(unit, owner)
5247 || candidates
5248 .iter()
5249 .any(|candidate| same_visible_symbol(candidate, owner))
5250 );
5251 if structurally_resolves
5252 || matches!(structured_resolution, LexicalTypeResolution::Missing)
5253 && ctx
5254 .visibility
5255 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
5256 {
5257 push_hit(hit_node, ctx);
5258 } else {
5259 push_unproven_hit(hit_node, ctx);
5260 }
5261}
5262
5263fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5264 if node.kind() == "function_definition" {
5265 maybe_record_free_function_definition_hit(node, ctx);
5266 return;
5267 }
5268 if node.kind() == "identifier" {
5269 maybe_record_free_function_value_reference(node, ctx);
5270 return;
5271 }
5272 if node.kind() != "call_expression" {
5273 return;
5274 }
5275 let Some(function) = node
5276 .child_by_field_name("function")
5277 .or_else(|| node.named_child(0))
5278 else {
5279 return;
5280 };
5281 let text = node_text(function, ctx.source);
5282 if !name_matches_callable(text, &ctx.spec.member_name) {
5283 return;
5284 }
5285 *ctx.raw_match_count += 1;
5286 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5287 match ctx
5288 .visibility
5289 .call_arity_evidence(ctx.file, node, ctx.source)
5290 .accepts(expected)
5291 {
5292 Some(true) => {}
5293 Some(false) => return,
5294 None => {
5295 if !bare_name_binds_only_target(node, function, text, ctx) {
5302 push_unproven_hit(function_terminal_node(function), ctx);
5303 return;
5304 }
5305 }
5306 }
5307 }
5308 if matches!(function.kind(), "identifier" | "template_function") {
5309 let terminal = function_terminal_node(function);
5310 let name = node_text(terminal, ctx.source);
5311 if ctx.local_shadows.is_shadowed(name) {
5312 return;
5313 }
5314 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
5315 && !matches!(
5316 resolve_declaring_member_owner(
5317 &ctx.analyzer,
5318 ctx.visibility,
5319 ctx.file,
5320 &enclosing_owner,
5321 name,
5322 ),
5323 EnclosingMemberOwnerResolution::Missing
5324 )
5325 {
5326 return;
5327 }
5328 match resolve_bare_call_target(
5329 node,
5330 function,
5331 &ctx.analyzer,
5332 ctx.visibility,
5333 &ctx.ordinary_type_imports,
5334 ctx.file,
5335 ctx.source,
5336 ) {
5337 BareCallTargetResolution::FreeFunctions(units)
5338 if units
5339 .iter()
5340 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5341 {
5342 if free_function_call_may_target(node, text, ctx) {
5343 let recursive = enclosing_context(terminal, ctx)
5344 .enclosing
5345 .as_ref()
5346 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
5347 if recursive {
5348 push_recursive_reference_hit(terminal, ctx);
5349 } else {
5350 push_hit(terminal, ctx);
5351 }
5352 }
5353 }
5354 BareCallTargetResolution::UnprovenFreeFunctions(units)
5355 if units
5356 .iter()
5357 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5358 {
5359 push_unproven_hit(terminal, ctx);
5360 }
5361 BareCallTargetResolution::FreeFunctions(_)
5362 | BareCallTargetResolution::UnprovenFreeFunctions(_)
5363 | BareCallTargetResolution::Type(_)
5364 | BareCallTargetResolution::CallableShadow => {}
5365 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
5366 push_unproven_hit(terminal, ctx);
5367 }
5368 }
5369 return;
5370 }
5371 if !free_function_call_may_target(node, text, ctx) {
5372 return;
5373 }
5374 if ctx.visibility.contains_named_symbol(
5375 ctx.file,
5376 text,
5377 TargetKind::FreeFunction,
5378 &ctx.spec.target,
5379 ) {
5380 push_hit(function_terminal_node(function), ctx);
5381 } else if ctx.visibility.resolve_known_non_target(
5382 ctx.file,
5383 text,
5384 TargetKind::FreeFunction,
5385 &ctx.spec.target,
5386 ) {
5387 } else {
5390 push_unproven_hit(function_terminal_node(function), ctx);
5391 }
5392}
5393
5394fn bare_name_binds_only_target(
5403 call: Node<'_>,
5404 function: Node<'_>,
5405 text: &str,
5406 ctx: &ScanCtx<'_>,
5407) -> bool {
5408 if !matches!(function.kind(), "identifier" | "template_function") {
5409 return false;
5410 }
5411 let mut candidates = ctx
5412 .visibility
5413 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5414 candidates.retain(|candidate| {
5415 ctx.visibility
5416 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5417 });
5418 dedupe_callable_candidates(&mut candidates);
5419 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
5420}
5421
5422fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
5423 if ctx.spec.param_types.is_none() {
5424 return true;
5425 }
5426 let mut candidates = ctx
5427 .visibility
5428 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5429 candidates.retain(|candidate| {
5430 ctx.visibility
5431 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5432 });
5433 let Some(arity) = ctx
5434 .visibility
5435 .call_arity_evidence(ctx.file, call, ctx.source)
5436 .exact()
5437 else {
5438 return true;
5439 };
5440 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5441 if candidates.is_empty()
5442 || !candidates
5443 .iter()
5444 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5445 {
5446 return true;
5447 }
5448 let arg_types = call_argument_types(call, ctx);
5449 let filtered = cpp_filter_candidates_by_args(
5450 candidates,
5451 &arg_types,
5452 &|name| ctx.visibility.resolve_type(ctx.file, name),
5453 &|left, right| same_visible_symbol(left, right),
5454 );
5455 filtered
5456 .iter()
5457 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5458}
5459
5460fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
5461 let Some(args) = call
5462 .child_by_field_name("arguments")
5463 .or_else(|| call.child_by_field_name("parameters"))
5464 .or_else(|| call.child_by_field_name("value"))
5465 else {
5466 return Vec::new();
5467 };
5468 argument_children(args)
5469 .map(|arg| expression_arg_type(arg, ctx))
5470 .collect()
5471}
5472
5473fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
5474 match node.kind() {
5475 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
5476 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
5477 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
5478 literal
5479 }),
5480 "identifier" => ctx
5481 .bindings
5482 .resolve_symbol(node_text(node, ctx.source))
5483 .as_precise()
5484 .and_then(|bindings| bindings.iter().find_map(CppScanBinding::as_arg_type)),
5485 "parenthesized_expression" => node
5486 .child_by_field_name("argument")
5487 .or_else(|| node.named_child(0))
5488 .and_then(|inner| expression_arg_type(inner, ctx)),
5489 "pointer_expression" => {
5490 let delta = match node.child_by_field_name("operator")?.kind() {
5491 "&" => 1,
5492 "*" => -1,
5493 _ => return None,
5494 };
5495 let inner = node
5496 .child_by_field_name("argument")
5497 .or_else(|| node.named_child(0))?;
5498 let mut arg_type = expression_arg_type(inner, ctx)?;
5499 arg_type.indirection += delta;
5500 Some(arg_type)
5501 }
5502 _ => None,
5503 }
5504}
5505
5506fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5511 let text = node_text(node, ctx.source);
5512 if !name_matches_callable(text, &ctx.spec.member_name) {
5513 return;
5514 }
5515 if is_declaration_name(node) || is_call_callee_node(node) {
5516 return;
5517 }
5518 *ctx.raw_match_count += 1;
5519 if ctx.visibility.contains_named_symbol(
5520 ctx.file,
5521 text,
5522 TargetKind::FreeFunction,
5523 &ctx.spec.target,
5524 ) {
5525 push_hit(node, ctx);
5526 } else if ctx.visibility.resolve_known_non_target(
5527 ctx.file,
5528 text,
5529 TargetKind::FreeFunction,
5530 &ctx.spec.target,
5531 ) {
5532 } else {
5534 push_unproven_hit(node, ctx);
5535 }
5536}
5537
5538fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5539 let Some(function) = function_definition_name_node(node) else {
5540 return;
5541 };
5542 let text = node_text(function, ctx.source);
5543 if !name_matches_callable(text, &ctx.spec.member_name) {
5544 return;
5545 }
5546 *ctx.raw_match_count += 1;
5547 if !function_definition_signature_matches_target(node, ctx) {
5548 return;
5549 }
5550 if definition_name_candidates(function, ctx)
5551 .iter()
5552 .any(|name| {
5553 ctx.visibility.contains_named_symbol(
5554 ctx.file,
5555 name,
5556 TargetKind::FreeFunction,
5557 &ctx.spec.target,
5558 )
5559 })
5560 {
5561 push_definition_hit(function, ctx);
5562 } else if definition_name_candidates(function, ctx)
5563 .iter()
5564 .any(|name| {
5565 ctx.visibility.resolve_known_non_target(
5566 ctx.file,
5567 name,
5568 TargetKind::FreeFunction,
5569 &ctx.spec.target,
5570 )
5571 })
5572 {
5573 } else {
5575 push_unproven_definition_hit(function, ctx);
5576 }
5577}
5578
5579fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5580 if node.kind() == "using_declaration" {
5581 maybe_record_using_callable_hit(node, ctx);
5582 return;
5583 }
5584 if has_ancestor_kind(node, "using_declaration") {
5585 return;
5586 }
5587 if node.kind() == "function_definition" {
5588 maybe_record_method_definition_hit(node, ctx);
5589 return;
5590 }
5591 if node_inside_function_definition_declarator(node) {
5592 return;
5593 }
5594 if is_declaration_name(node) {
5595 return;
5596 }
5597 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
5598 maybe_record_qualified_method_value_hit(node, member, ctx);
5599 return;
5600 }
5601 if let Some(value) = qualified_callable_value(node) {
5602 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
5603 return;
5604 }
5605 if node.kind() != "call_expression" {
5606 return;
5607 }
5608 if let Some((receiver, operator)) = explicit_operator_call(node) {
5609 let text = node_text(operator, ctx.source);
5610 if !name_matches_callable(text, &ctx.spec.member_name) {
5611 return;
5612 }
5613 *ctx.raw_match_count += 1;
5614 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5615 match ctx
5616 .visibility
5617 .call_arity_evidence(ctx.file, node, ctx.source)
5618 .accepts(expected)
5619 {
5620 Some(true) => {}
5621 Some(false) => return,
5622 None => {
5623 push_unproven_hit(operator, ctx);
5624 return;
5625 }
5626 }
5627 }
5628 match explicit_receiver_target_resolution(receiver, ctx) {
5629 MethodReceiverTargetResolution::Target if receiver_is_self_like(receiver, ctx.file) => {
5630 push_self_receiver_hit(operator, ctx);
5631 }
5632 MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
5633 MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
5634 MethodReceiverTargetResolution::NonTarget
5635 | MethodReceiverTargetResolution::Ambiguous => {}
5636 }
5637 return;
5638 }
5639 let Some(function) = node
5640 .child_by_field_name("function")
5641 .or_else(|| node.named_child(0))
5642 else {
5643 return;
5644 };
5645 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
5646 return;
5647 }
5648 if function.kind() == "identifier"
5649 && ctx
5650 .local_shadows
5651 .is_shadowed(node_text(function, ctx.source))
5652 {
5653 return;
5654 }
5655 *ctx.raw_match_count += 1;
5656 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5657 match ctx
5658 .visibility
5659 .call_arity_evidence(ctx.file, node, ctx.source)
5660 .accepts(expected)
5661 {
5662 Some(true) => {}
5663 Some(false) => return,
5664 None => {
5665 push_unproven_hit(function_terminal_node(function), ctx);
5666 return;
5667 }
5668 }
5669 }
5670 if !method_call_may_target(node, ctx) {
5671 return;
5672 }
5673 if is_structurally_qualified(function) {
5674 match qualified_owner_resolution(function, ctx) {
5675 QualifiedOwnerResolution::Target => {
5676 push_hit(function_terminal_node(function), ctx);
5677 }
5678 QualifiedOwnerResolution::NonTarget => {}
5679 QualifiedOwnerResolution::Unresolved => {
5680 push_unproven_hit(function_terminal_node(function), ctx);
5681 }
5682 }
5683 return;
5684 }
5685 match call_function_target_resolution(function, ctx) {
5686 MethodReceiverTargetResolution::Target
5687 if call_function_has_direct_self_receiver(function, ctx.file) =>
5688 {
5689 push_self_receiver_hit(function_terminal_node(function), ctx);
5690 }
5691 MethodReceiverTargetResolution::Target => {
5692 push_hit(function_terminal_node(function), ctx);
5693 }
5694 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
5695 MethodReceiverTargetResolution::Missing
5701 if inherited_target_owner_context(function, ctx) =>
5702 {
5703 push_hit(function_terminal_node(function), ctx);
5704 }
5705 MethodReceiverTargetResolution::Missing
5706 if same_owner_context(function, ctx)
5707 || out_of_line_target_owner_context(function, ctx) =>
5708 {
5709 push_self_receiver_hit(function_terminal_node(function), ctx);
5710 }
5711 MethodReceiverTargetResolution::Missing
5712 if function.kind() == "identifier"
5713 && resolves_to_lexical_free_function(function, ctx) =>
5714 {
5715 }
5718 MethodReceiverTargetResolution::Missing
5719 if !receiver_has_known_non_target(function, ctx)
5720 && !known_non_target_owner_context(function, ctx) =>
5721 {
5722 push_unproven_hit(function_terminal_node(function), ctx);
5723 }
5724 MethodReceiverTargetResolution::Missing => {}
5725 }
5726}
5727
5728fn node_inside_function_definition_declarator(node: Node<'_>) -> bool {
5729 let mut current = node.parent();
5730 while let Some(parent) = current {
5731 if parent.kind() == "function_definition" {
5732 return parent
5733 .child_by_field_name("declarator")
5734 .is_some_and(|declarator| {
5735 node.start_byte() >= declarator.start_byte()
5736 && node.end_byte() <= declarator.end_byte()
5737 });
5738 }
5739 current = parent.parent();
5740 }
5741 false
5742}
5743
5744fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
5745 if node.kind() != "qualified_identifier" {
5746 return None;
5747 }
5748 let parameter = node
5749 .parent()
5750 .filter(|parent| parent.kind() == "parameter_declaration")?;
5751 let parameter_declarator = parameter.child_by_field_name("declarator")?;
5752 if parameter.child_by_field_name("type") != Some(node)
5757 || parameter_declarator.kind() != "abstract_function_declarator"
5758 {
5759 return None;
5760 }
5761 let parameter_list = parameter
5762 .parent()
5763 .filter(|parent| parent.kind() == "parameter_list")?;
5764 if parameter_list.named_child_count() != 1 {
5765 return None;
5766 }
5767 let function_declarator = parameter_list
5768 .parent()
5769 .filter(|parent| parent.kind() == "function_declarator")?;
5770 if function_declarator
5771 .child_by_field_name("declarator")
5772 .is_none_or(|declarator| declarator.kind() != "identifier")
5773 || function_declarator
5774 .parent()
5775 .is_none_or(|parent| parent.kind() != "declaration")
5776 {
5777 return None;
5778 }
5779 node.child_by_field_name("name")
5780}
5781
5782fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5783 let Some(imported) = ordinary_using_declaration_type_node(node) else {
5784 return;
5785 };
5786 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
5787 return;
5788 }
5789 let Some(target_owner) = ctx.spec.owner.as_ref() else {
5790 return;
5791 };
5792 *ctx.raw_match_count += 1;
5793 let owner_resolution = qualified_owner_components(imported, ctx.source)
5794 .map(|qualified| {
5795 let lexical_scope = match enclosing_lexical_scope_components(
5796 imported,
5797 &ctx.analyzer,
5798 ctx.visibility,
5799 ctx.file,
5800 ctx.source,
5801 ) {
5802 LexicalScopeResolution::Resolved(scope) => scope,
5803 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
5804 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
5805 };
5806 ctx.visibility.resolve_type_components_lexically(
5807 &ctx.analyzer,
5808 ctx.file,
5809 &qualified.names,
5810 qualified.global,
5811 &lexical_scope,
5812 )
5813 })
5814 .unwrap_or(LexicalTypeResolution::Missing);
5815 let matches_target_owner = matches!(
5816 owner_resolution,
5817 LexicalTypeResolution::Resolved {
5818 ref unit,
5819 ref candidates,
5820 ..
5821 } if same_visible_symbol(unit, target_owner)
5822 || candidates
5823 .iter()
5824 .any(|candidate| same_visible_symbol(candidate, target_owner))
5825 );
5826 if !matches_target_owner {
5827 match owner_resolution {
5828 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5829 push_unproven_hit(imported, ctx);
5830 }
5831 LexicalTypeResolution::Resolved { .. } => {}
5832 }
5833 return;
5834 }
5835 match ctx.visibility.visible_member_for_owner_name(
5836 ctx.file,
5837 target_owner,
5838 &ctx.spec.member_name,
5839 ) {
5840 VisibleMemberResolution::Callable(candidates)
5841 if candidates.iter().all(|candidate| {
5842 ctx.target_group.contains(candidate)
5843 || ctx
5844 .target_group
5845 .iter()
5846 .any(|target| same_visible_symbol(candidate, target))
5847 }) =>
5848 {
5849 push_hit(imported, ctx);
5850 }
5851 VisibleMemberResolution::NonCallable => {}
5852 VisibleMemberResolution::Callable(_)
5853 | VisibleMemberResolution::AmbiguousKind
5854 | VisibleMemberResolution::Missing => {
5855 push_unproven_hit(imported, ctx);
5856 }
5857 }
5858}
5859
5860fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5861 let name = node_text(node, ctx.source);
5862 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
5863 let key = (namespace.clone(), name.to_string());
5864 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
5865 return resolved;
5866 }
5867 let resolved = ctx
5868 .visibility
5869 .visible_identifier_candidates(ctx.file, name)
5870 .any(|unit| {
5871 unit.is_function()
5872 && type_owner_of(&ctx.analyzer, unit).is_none()
5873 && unit.package_name() == namespace
5874 });
5875 ctx.lexical_free_function_cache
5876 .borrow_mut()
5877 .insert(key, resolved);
5878 resolved
5879}
5880
5881fn maybe_record_qualified_method_value_hit(
5882 qualified: Node<'_>,
5883 member: Node<'_>,
5884 ctx: &mut ScanCtx<'_>,
5885) {
5886 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
5887 return;
5888 }
5889 *ctx.raw_match_count += 1;
5890 let resolution =
5891 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
5892 match resolution {
5893 LexicalCallableValueResolution::Type(resolved_owner) => {
5894 let Some(owner) = ctx.spec.owner.as_ref() else {
5895 push_unproven_hit(member, ctx);
5896 return;
5897 };
5898 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
5899 if same_visible_symbol(&resolved_owner, owner) {
5900 push_unproven_hit(member, ctx);
5901 }
5902 return;
5903 }
5904 match ctx.visibility.visible_member_for_owner_name(
5905 ctx.file,
5906 owner,
5907 &ctx.spec.member_name,
5908 ) {
5909 VisibleMemberResolution::Callable(candidates)
5910 if candidates.iter().all(|candidate| {
5911 ctx.target_group.contains(candidate)
5912 || ctx
5913 .target_group
5914 .iter()
5915 .any(|target| same_visible_symbol(candidate, target))
5916 }) =>
5917 {
5918 push_hit(member, ctx);
5921 }
5922 VisibleMemberResolution::NonCallable => {}
5923 VisibleMemberResolution::Callable(_)
5924 | VisibleMemberResolution::AmbiguousKind
5925 | VisibleMemberResolution::Missing => {
5926 push_unproven_hit(member, ctx);
5927 }
5928 }
5929 }
5930 LexicalCallableValueResolution::FreeFunction(_) => {}
5931 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
5932 push_unproven_hit(member, ctx);
5933 }
5934 }
5935}
5936
5937fn qualified_callable_value_resolution(
5938 qualified: Node<'_>,
5939 member_name: &str,
5940 ctx: &ScanCtx<'_>,
5941) -> LexicalCallableValueResolution {
5942 let Some((owner_components, global)) =
5943 qualified_callable_owner_components(qualified, ctx.source)
5944 else {
5945 return LexicalCallableValueResolution::Missing;
5946 };
5947 let lexical_scope = if global {
5948 Vec::new()
5949 } else {
5950 match enclosing_lexical_scope_components(
5951 qualified,
5952 &ctx.analyzer,
5953 ctx.visibility,
5954 ctx.file,
5955 ctx.source,
5956 ) {
5957 LexicalScopeResolution::Resolved(scope) => scope,
5958 LexicalScopeResolution::Ambiguous => {
5959 return LexicalCallableValueResolution::Ambiguous;
5960 }
5961 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
5962 }
5963 };
5964 if let Some(target_owner) = ctx.spec.owner.as_ref()
5965 && let LexicalTypeResolution::Resolved { unit, .. } =
5966 resolve_type_components_lexically_at_for_target_with_scope_cache(
5967 qualified,
5968 &owner_components,
5969 global,
5970 &ctx.analyzer,
5971 ctx.visibility,
5972 &ctx.ordinary_type_imports,
5973 ctx.file,
5974 ctx.source,
5975 target_owner,
5976 false,
5977 Some(&ctx.lexical_scope_cache),
5978 )
5979 {
5980 return LexicalCallableValueResolution::Type(unit);
5981 }
5982 ctx.visibility.resolve_callable_value_components_lexically(
5983 &ctx.analyzer,
5984 ctx.file,
5985 &owner_components,
5986 member_name,
5987 global,
5988 &lexical_scope,
5989 )
5990}
5991
5992fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5993 let Some(owner) = ctx.spec.owner.as_ref() else {
5994 return true;
5995 };
5996 if ctx.spec.param_types.is_none() {
5997 return true;
5998 }
5999 let mut candidates = ctx
6000 .visibility
6001 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6002 .into_iter()
6003 .filter(|unit| unit.is_function())
6004 .cloned()
6005 .collect::<Vec<_>>();
6006 let Some(arity) = ctx
6007 .visibility
6008 .call_arity_evidence(ctx.file, call, ctx.source)
6009 .exact()
6010 else {
6011 return true;
6012 };
6013 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
6014 if candidates.is_empty()
6015 || !candidates
6016 .iter()
6017 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6018 {
6019 return true;
6020 }
6021 let arg_types = call_argument_types(call, ctx);
6022 let filtered = cpp_filter_candidates_by_args(
6023 candidates,
6024 &arg_types,
6025 &|name| ctx.visibility.resolve_type(ctx.file, name),
6026 &|left, right| same_visible_symbol(left, right),
6027 );
6028 filtered
6029 .iter()
6030 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6031}
6032
6033fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6034 let Some(function) = function_definition_name_node(node) else {
6035 return;
6036 };
6037 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6038 return;
6039 }
6040 *ctx.raw_match_count += 1;
6041 if !function_definition_signature_matches_target(node, ctx) {
6042 return;
6043 }
6044 if node_inside_target_declaration(function, ctx) {
6045 return;
6046 }
6047 if is_structurally_qualified(function) {
6048 match qualified_owner_resolution(function, ctx) {
6049 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
6050 QualifiedOwnerResolution::NonTarget => {}
6051 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
6052 }
6053 return;
6054 }
6055 if definition_name_candidates(function, ctx)
6056 .iter()
6057 .any(|name| {
6058 name.contains("::")
6059 && ctx.visibility.contains_named_symbol(
6060 ctx.file,
6061 name,
6062 TargetKind::Method,
6063 &ctx.spec.target,
6064 )
6065 })
6066 {
6067 push_definition_hit(function, ctx);
6068 } else if definition_name_candidates(function, ctx)
6069 .iter()
6070 .any(|name| {
6071 ctx.visibility.resolve_known_non_target(
6072 ctx.file,
6073 name,
6074 TargetKind::Method,
6075 &ctx.spec.target,
6076 )
6077 })
6078 || known_non_target_owner_context(function, ctx)
6079 {
6080 } else {
6082 push_unproven_definition_hit(function, ctx);
6083 }
6084}
6085
6086fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6087 ctx.target_declaration_ranges
6088 .iter()
6089 .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
6090}
6091
6092fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
6093 let mut receiver = None;
6094 let mut cursor = node.walk();
6095 for child in node.named_children(&mut cursor) {
6096 if child.kind() == "argument_list" {
6097 continue;
6098 }
6099 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
6100 return receiver.map(|receiver| (receiver, operator));
6101 }
6102 if receiver.is_none() {
6103 receiver = Some(child);
6104 }
6105 }
6106 None
6107}
6108
6109fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
6110 if node.kind() != "function_definition" {
6111 return None;
6112 }
6113 node.child_by_field_name("declarator")
6114 .and_then(declarator_name_node)
6115}
6116
6117fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
6118 function_definition_name_node(node)
6119}
6120
6121fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6122 let definition = node_text(node, ctx.source);
6123 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
6124 return true;
6125 };
6126 if !expected.accepts(signature_arity(Some(definition))) {
6127 return false;
6128 }
6129 let Some(target_signature) = ctx.spec.target.signature() else {
6130 return true;
6131 };
6132 cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
6133}
6134
6135fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
6136 name_matches_callable(node_text(function_terminal_node(node), source), expected)
6137}
6138
6139fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
6140 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
6141 if raw.is_empty() {
6142 return Vec::new();
6143 }
6144 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
6145 return vec![raw];
6146 };
6147 if !raw.contains("::") {
6148 return vec![format!("{namespace}::{raw}")];
6149 }
6150 if raw
6156 .split("::")
6157 .next()
6158 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
6159 {
6160 vec![format!("{namespace}::{raw}"), raw]
6161 } else {
6162 vec![raw]
6163 }
6164}
6165
6166fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6167 if node.kind() == kind {
6168 return Some(node);
6169 }
6170 let mut cursor = node.walk();
6171 for child in node.named_children(&mut cursor) {
6172 if let Some(found) = first_descendant_of_kind(child, kind) {
6173 return Some(found);
6174 }
6175 }
6176 None
6177}
6178
6179fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6180 if matches!(node.kind(), "identifier" | "field_identifier")
6181 && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
6182 {
6183 return;
6184 }
6185 if !matches!(
6186 node.kind(),
6187 "identifier" | "field_identifier" | "qualified_identifier"
6188 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6189 || is_declaration_name(node)
6190 || is_member_field_own_declarator(node, ctx)
6191 || is_selected_field_expression_member_descendant(node)
6192 || is_nested_in_qualified_identifier(node)
6193 {
6194 return;
6195 }
6196 *ctx.raw_match_count += 1;
6197 if global_field_resolves_to_target(node, ctx) {
6198 push_hit(node, ctx);
6199 } else if global_field_is_known_non_target(node, ctx) {
6200 } else {
6201 push_unproven_hit(node, ctx);
6202 }
6203}
6204
6205fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
6211 let candidate = node;
6212 while let Some(parent) = node.parent() {
6213 if parent.kind() == "field_expression" {
6214 if let Some(field) = parent.child_by_field_name("field")
6215 && node_is_within(field, candidate)
6216 {
6217 let selected_name = match field.kind() {
6218 "template_method" => field.child_by_field_name("name").unwrap_or(field),
6219 _ => field,
6220 };
6221 if node_is_within(selected_name, candidate) {
6222 return true;
6223 }
6224 node = parent;
6228 continue;
6229 }
6230 let receiver = parent
6231 .child_by_field_name("argument")
6232 .or_else(|| parent.child_by_field_name("object"))
6233 .or_else(|| parent.named_child(0));
6234 if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
6235 return true;
6238 }
6239 }
6240 node = parent;
6241 }
6242 false
6243}
6244
6245fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
6246 parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
6247}
6248
6249fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6250 let text = node_text(node, ctx.source);
6251 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6252 return false;
6253 }
6254 if text.contains("::") {
6255 return ctx.visibility.contains_named_symbol(
6256 ctx.file,
6257 text,
6258 TargetKind::GlobalField,
6259 &ctx.spec.target,
6260 );
6261 }
6262 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
6263 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
6264 {
6265 return ctx.visibility.contains_named_symbol(
6266 ctx.file,
6267 text,
6268 TargetKind::GlobalField,
6269 &ctx.spec.target,
6270 );
6271 }
6272 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
6273 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
6274 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6275 brokk_bifrost_core::analyzer::Language::Cpp,
6276 &namespace,
6277 ) == indexed_scope
6278 })
6279 {
6280 return ctx.visibility.contains_named_symbol(
6281 ctx.file,
6282 text,
6283 TargetKind::GlobalField,
6284 &ctx.spec.target,
6285 );
6286 }
6287 bare_global_field_uniquely_resolves_to_target(text, ctx)
6288}
6289
6290fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
6291 let mut matched_target = false;
6292 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
6293 if !has_persisted_global_field_identity(unit)
6294 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
6295 {
6296 continue;
6297 }
6298 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
6299 continue;
6300 }
6301 if same_visible_global_field_symbol(
6302 &ctx.analyzer,
6303 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6304 unit,
6305 &ctx.spec.target,
6306 ) {
6307 matched_target = true;
6308 } else {
6309 return false;
6310 }
6311 }
6312 matched_target
6313}
6314
6315fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
6316 unit.is_field() && !unit.short_name().contains('.')
6321}
6322
6323fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6324 let text = node_text(node, ctx.source);
6325 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6326 return true;
6327 }
6328 if text.contains("::") {
6329 return ctx.visibility.resolve_known_non_target(
6330 ctx.file,
6331 text,
6332 TargetKind::GlobalField,
6333 &ctx.spec.target,
6334 );
6335 }
6336 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
6337 return false;
6338 };
6339 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
6340 && ctx
6341 .visibility
6342 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6343 .any(|unit| {
6344 has_persisted_global_field_identity(unit)
6345 && unit.identifier() == ctx.spec.member_name
6346 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
6347 && !same_visible_global_field_symbol(
6348 &ctx.analyzer,
6349 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6350 unit,
6351 &ctx.spec.target,
6352 )
6353 })
6354}
6355
6356fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6357 if node.kind() == "field_expression" {
6358 let Some(field) = node.child_by_field_name("field") else {
6359 return;
6360 };
6361 if node_text(field, ctx.source) != ctx.spec.member_name {
6362 return;
6363 }
6364 *ctx.raw_match_count += 1;
6365 let receiver = node
6366 .child_by_field_name("argument")
6367 .or_else(|| node.child_by_field_name("object"));
6368 match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, ctx)) {
6369 Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
6370 Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
6371 Some(
6372 MethodReceiverTargetResolution::NonTarget
6373 | MethodReceiverTargetResolution::Ambiguous,
6374 ) => {}
6375 }
6376 return;
6377 }
6378
6379 if matches!(node.kind(), "identifier" | "field_identifier")
6380 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6381 && let Some(designator_owner) =
6382 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
6383 {
6384 *ctx.raw_match_count += 1;
6385 match designator_owner {
6386 DesignatedInitializerOwner::Resolved(owner)
6387 if ctx
6388 .spec
6389 .owner
6390 .as_ref()
6391 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
6392 {
6393 push_hit(node, ctx);
6394 }
6395 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
6396 DesignatedInitializerOwner::Resolved(_) => {}
6397 }
6398 return;
6399 }
6400
6401 let qualified_member_name_matches =
6402 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
6403 && cpp_name_component_nodes(node)
6404 .and_then(|components| components.last().copied())
6405 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
6406 if !matches!(
6407 node.kind(),
6408 "identifier" | "field_identifier" | "qualified_identifier" | "scoped_identifier"
6409 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6410 && !qualified_member_name_matches)
6411 || is_declaration_name(node)
6412 || is_member_field_own_declarator(node, ctx)
6413 || is_selected_field_expression_member_descendant(node)
6414 || is_nested_in_qualified_identifier(node)
6415 {
6416 return;
6417 }
6418 *ctx.raw_match_count += 1;
6419 if is_structurally_qualified(node) {
6420 match qualified_owner_resolution(node, ctx) {
6421 QualifiedOwnerResolution::Target => push_hit(node, ctx),
6422 QualifiedOwnerResolution::NonTarget => {}
6423 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
6424 }
6425 return;
6426 }
6427 let text = node_text(node, ctx.source);
6428 if ctx.local_shadows.is_shadowed(text) {
6429 return;
6430 }
6431 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
6432 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
6433 let owner_context = structured_owner_context_resolution(node, ctx);
6434 if matches!(
6435 owner_context,
6436 StructuredOwnerContextResolution::SelfTarget
6437 | StructuredOwnerContextResolution::InheritedTarget
6438 ) || unscoped_enum_match
6439 {
6440 push_hit(node, ctx);
6441 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
6442 .then_some(ctx.spec.owner.as_ref())
6443 .flatten()
6444 {
6445 let resolution =
6446 match resolve_active_using_enum_member(node, ctx) {
6447 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
6448 ActiveUsingEnumMemberResolution::Class(resolution) => {
6449 if direct_class_member_shadows(node, ctx) {
6450 return;
6451 }
6452 resolution
6453 }
6454 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
6455 if let Some(owner) = structured_enclosing_owner(node, ctx) {
6456 if direct_class_member_shadows(node, ctx) {
6457 return;
6458 }
6459 let complete_same_file_leaf =
6460 owner.source() == ctx.file
6461 && ctx.analyzer.type_hierarchy_provider().is_some_and(
6462 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
6463 );
6464 if !complete_same_file_leaf {
6465 push_unproven_hit(node, ctx);
6466 return;
6467 }
6468 }
6469 match owner_context {
6470 StructuredOwnerContextResolution::SelfTarget
6471 | StructuredOwnerContextResolution::InheritedTarget
6472 | StructuredOwnerContextResolution::NonTarget => return,
6473 StructuredOwnerContextResolution::Ambiguous => {
6474 push_unproven_hit(node, ctx);
6475 return;
6476 }
6477 StructuredOwnerContextResolution::Missing => {}
6478 }
6479 if namespace_value_shadows(node, ctx) {
6480 return;
6481 }
6482 resolution
6483 }
6484 ActiveUsingEnumMemberResolution::Missing => {
6485 if direct_class_member_shadows(node, ctx)
6486 || (structured_enclosing_owner(node, ctx).is_none()
6487 && namespace_value_shadows(node, ctx))
6488 {
6489 return;
6490 }
6491 UsingEnumMemberResolution::Missing
6492 }
6493 };
6494 match resolution {
6495 UsingEnumMemberResolution::Resolved { owner, member }
6496 if same_visible_symbol(&owner, target_owner)
6497 && same_visible_symbol(&member, &ctx.spec.target) =>
6498 {
6499 push_hit(node, ctx);
6500 }
6501 UsingEnumMemberResolution::Resolved { .. } => {}
6502 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
6503 push_unproven_hit(node, ctx)
6504 }
6505 }
6506 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
6507 push_unproven_hit(node, ctx);
6508 }
6509}
6510
6511enum ActiveUsingEnumMemberResolution {
6512 Block(UsingEnumMemberResolution),
6513 Class(UsingEnumMemberResolution),
6514 Namespace(UsingEnumMemberResolution),
6515 Missing,
6516}
6517
6518fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6519 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
6520 ctx.visibility
6521 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
6522 .into_iter()
6523 .next()
6524 .is_some()
6525 })
6526}
6527
6528fn resolve_active_using_enum_member(
6529 node: Node<'_>,
6530 ctx: &ScanCtx<'_>,
6531) -> ActiveUsingEnumMemberResolution {
6532 let block =
6533 ctx.using_enum_owners
6534 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
6535 if !matches!(block, UsingEnumMemberResolution::Missing) {
6536 return ActiveUsingEnumMemberResolution::Block(block);
6537 }
6538 let class = structured_enclosing_owner(node, ctx);
6539 let namespace = enclosing_namespace_components(node, ctx.source);
6540 match ctx.semantic_using_enum_owners.resolve_member(
6541 ctx.visibility,
6542 ctx.file,
6543 class.as_ref(),
6544 &namespace,
6545 node.start_byte(),
6546 &ctx.spec.member_name,
6547 ) {
6548 SemanticUsingEnumMemberResolution::Class(resolution) => {
6549 ActiveUsingEnumMemberResolution::Class(resolution)
6550 }
6551 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
6552 ActiveUsingEnumMemberResolution::Namespace(resolution)
6553 }
6554 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
6555 }
6556}
6557
6558fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6559 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
6560 !matches!(
6561 resolve_namespace_value(
6562 &ctx.analyzer,
6563 ctx.visibility,
6564 ctx.file,
6565 &namespace,
6566 &ctx.spec.member_name,
6567 node.start_byte(),
6568 ),
6569 NamespaceValueResolution::Missing
6570 )
6571}
6572
6573fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
6574 if node.kind() == "qualified_identifier" {
6575 return false;
6576 }
6577 let mut current = node.parent();
6578 while let Some(parent) = current {
6579 if parent.kind() == "ERROR" {
6587 return false;
6588 }
6589 if parent.kind() == "template_argument_list" {
6594 return false;
6595 }
6596 if parent.kind() == "qualified_identifier" {
6597 return true;
6598 }
6599 current = parent.parent();
6600 }
6601 false
6602}
6603
6604fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6605 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
6606}
6607
6608fn receiver_type_units_with_budget(
6609 node: Node<'_>,
6610 source: &str,
6611 ctx: &ScanCtx<'_>,
6612 remaining_call_depth: usize,
6613) -> Vec<CodeUnit> {
6614 let mut current = node;
6615 let mut member_chain = Vec::new();
6616 let mut base_units = loop {
6617 match current.kind() {
6618 "field_expression" => {
6619 let Some(member) = current.child_by_field_name("field") else {
6620 return Vec::new();
6621 };
6622 let Some(receiver) = current
6623 .child_by_field_name("argument")
6624 .or_else(|| current.child_by_field_name("object"))
6625 .or_else(|| current.named_child(0))
6626 else {
6627 return Vec::new();
6628 };
6629 member_chain.push(node_text(member, source));
6630 current = receiver;
6631 }
6632 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
6633 let Some(inner) = current
6634 .child_by_field_name("argument")
6635 .or_else(|| current.named_child(0))
6636 else {
6637 return Vec::new();
6638 };
6639 current = inner;
6640 }
6641 "identifier" | "field_identifier" => {
6648 let name = node_text(current, source);
6649 let local = ctx.bindings.resolve_symbol(name);
6650 if let Some(bindings) = local.as_precise() {
6651 break receiver_units_from_bindings(current, bindings, ctx);
6652 }
6653 if ctx.bindings.is_shadowed(name) {
6654 return Vec::new();
6655 }
6656 let owner = structured_enclosing_owner(current, ctx)
6657 .filter(CodeUnit::is_class)
6658 .or_else(|| {
6659 enclosing_context(current, ctx)
6660 .owner
6661 .filter(CodeUnit::is_class)
6662 });
6663 if let Some(owner) = owner {
6664 let implicit_fields = ctx
6665 .visibility
6666 .visible_members_for_owner_name(ctx.file, &owner, name)
6667 .into_iter()
6668 .filter(|unit| unit.is_field())
6669 .collect::<Vec<_>>();
6670 if !implicit_fields.is_empty() {
6671 break receiver_units_from_declared_fields(implicit_fields, current, ctx);
6672 }
6673 }
6674 let global_fields = ctx
6675 .visibility
6676 .visible_identifier_candidates(ctx.file, name)
6677 .filter(|unit| {
6678 has_persisted_global_field_identity(unit) && unit.identifier() == name
6679 })
6680 .collect::<Vec<_>>();
6681 if global_fields.is_empty() {
6682 break ctx
6683 .visibility
6684 .resolve_type(ctx.file, name)
6685 .into_iter()
6686 .collect();
6687 }
6688 if let Some(first) = global_fields.first()
6689 && global_fields
6690 .iter()
6691 .skip(1)
6692 .any(|field| !same_visible_symbol(first, field))
6693 {
6694 return Vec::new();
6695 }
6696 break receiver_units_from_declared_fields(global_fields, current, ctx);
6697 }
6698 "call_expression" | "new_expression" => {
6699 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
6700 .and_then(|binding| binding.unit)
6701 .into_iter()
6702 .collect();
6703 }
6704 "this" if is_c_source_file(ctx.file) => {
6705 let name = node_text(current, source);
6706 let local = ctx.bindings.resolve_symbol(name);
6707 if let Some(bindings) = local.as_precise() {
6708 break receiver_units_from_bindings(current, bindings, ctx);
6709 }
6710 return Vec::new();
6711 }
6712 "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
6713 "qualified_identifier" | "scoped_identifier" => {
6714 let reference = node_text(current, source);
6715 let fields = ctx
6716 .visibility
6717 .named_candidates(ctx.file, reference, TargetKind::GlobalField)
6718 .into_iter()
6719 .filter(has_persisted_global_field_identity)
6720 .collect::<Vec<_>>();
6721 if fields.is_empty() {
6722 break ctx
6723 .visibility
6724 .resolve_type(ctx.file, reference)
6725 .into_iter()
6726 .collect();
6727 }
6728 break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
6729 }
6730 _ => {
6731 break ctx
6732 .visibility
6733 .resolve_type(ctx.file, node_text(current, source))
6734 .into_iter()
6735 .collect();
6736 }
6737 }
6738 };
6739
6740 base_units = canonical_receiver_units(base_units, ctx);
6741 if base_units.is_empty() {
6742 return Vec::new();
6743 }
6744
6745 while let Some(member_name) = member_chain.pop() {
6746 let mut next_units = Vec::new();
6747 for owner in &base_units {
6748 let declaring_owner = match resolve_declaring_member_owner(
6749 &ctx.analyzer,
6750 ctx.visibility,
6751 ctx.file,
6752 owner,
6753 member_name,
6754 ) {
6755 EnclosingMemberOwnerResolution::Owner(owner) => owner,
6756 EnclosingMemberOwnerResolution::Missing => continue,
6757 EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
6758 };
6759 let fields = ctx.visibility.visible_members_for_owner_name(
6760 ctx.file,
6761 &declaring_owner,
6762 member_name,
6763 );
6764 for field in fields.into_iter().filter(|unit| unit.is_field()) {
6765 let Some(unit) =
6766 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
6767 .and_then(|binding| binding.unit)
6768 .or_else(|| recovered_receiver_field_type(current, field, ctx))
6769 else {
6770 continue;
6771 };
6772 if !next_units
6773 .iter()
6774 .any(|existing| same_visible_symbol(existing, &unit))
6775 {
6776 next_units.push(unit);
6777 }
6778 }
6779 }
6780 if next_units.is_empty() {
6781 return Vec::new();
6782 }
6783 base_units = unanimous_receiver_units(next_units);
6784 if base_units.is_empty() {
6785 return Vec::new();
6786 }
6787 }
6788 base_units
6789}
6790
6791fn receiver_units_from_bindings(
6792 node: Node<'_>,
6793 bindings: &HashSet<CppScanBinding>,
6794 ctx: &ScanCtx<'_>,
6795) -> Vec<CodeUnit> {
6796 let mut units = Vec::new();
6797 for binding in bindings {
6798 let raw_unit = if let Some(unit) = &binding.unit {
6799 unit.clone()
6800 } else {
6801 let Some(type_name) = binding.type_name.as_deref() else {
6802 return Vec::new();
6803 };
6804 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
6805 return Vec::new();
6806 };
6807 unit
6808 };
6809 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
6810 units.push(unit);
6811 continue;
6812 }
6813 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
6814 units.push(unit);
6815 continue;
6816 }
6817 return Vec::new();
6818 }
6819 unanimous_receiver_units(units)
6820}
6821
6822fn recovered_receiver_alias_target(
6827 reference: Node<'_>,
6828 alias: &CodeUnit,
6829 ctx: &ScanCtx<'_>,
6830) -> Option<CodeUnit> {
6831 if !ctx
6832 .analyzer
6833 .type_alias_provider()
6834 .is_some_and(|provider| provider.is_type_alias(alias))
6835 {
6836 return None;
6837 }
6838 let target = ctx.spec.owner.as_ref()?.clone();
6839 if !target.is_class() || alias.source() != ctx.file {
6840 return None;
6841 }
6842 let range = ctx
6843 .analyzer
6844 .ranges(alias)
6845 .into_iter()
6846 .find(|range| range.start_byte < range.end_byte)?;
6847 let mut node =
6848 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
6849 while node.kind() != "alias_declaration" {
6850 node = node.parent()?;
6851 }
6852 let type_descriptor = node.child_by_field_name("type")?;
6853 let type_node = first_type_child(type_descriptor).unwrap_or(type_descriptor);
6854 let resolution = resolve_type_node_lexically_for_target(
6855 type_node,
6856 &ctx.analyzer,
6857 ctx.visibility,
6858 &ctx.ordinary_type_imports,
6859 ctx.file,
6860 ctx.source,
6861 &target,
6862 Some(&ctx.lexical_scope_cache),
6863 ctx.recovered_sentinel_scope(type_node).as_deref(),
6864 );
6865 if let LexicalTypeResolution::Resolved {
6866 unit, candidates, ..
6867 } = resolution
6868 && (same_visible_symbol(&unit, &target)
6869 || candidates
6870 .iter()
6871 .any(|candidate| same_visible_symbol(candidate, &target)))
6872 {
6873 return Some(target);
6874 }
6875 let (components, global) = type_reference_components(type_node, ctx.source)?;
6876 let scope = ctx
6877 .recovered_sentinel_scope(type_node)
6878 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
6879 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
6880 let visible = ctx.visibility.external_type_candidate_visible_in_context(
6881 &ctx.analyzer,
6882 ctx.file,
6883 &target,
6884 type_node,
6885 );
6886 (path_matches && visible).then_some(target)
6887}
6888
6889fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
6890 let normalized = normalize_cpp_type_name(type_name);
6891 if normalized.is_empty() {
6892 return None;
6893 }
6894
6895 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
6900 let alias_type = receiver_type_node_base(alias_type);
6901 match ctx
6902 .visibility
6903 .resolve_type_node_result(ctx.file, alias_type, ctx.source)
6904 {
6905 Ok(Some(unit)) => return Some(unit),
6906 Err(_) => return None,
6907 Ok(None) => {}
6908 }
6909 if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
6910 return Some(unit);
6911 }
6912 }
6913
6914 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
6915 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
6916 LexicalTypeResolution::Ambiguous => return None,
6917 LexicalTypeResolution::Missing => {}
6918 }
6919 let candidates = ctx
6920 .visibility
6921 .type_name_candidates(ctx.file, &normalized)
6922 .into_iter()
6923 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
6924 .collect();
6925 unanimous_receiver_units(candidates).into_iter().next()
6926}
6927
6928fn resolve_receiver_type_node_lexically(
6929 type_node: Node<'_>,
6930 ctx: &ScanCtx<'_>,
6931) -> Option<CodeUnit> {
6932 let type_node = receiver_type_node_base(type_node);
6933 let components = cpp_type_name_components(type_node, ctx.source)?;
6934 let lexical_scope = match enclosing_lexical_scope_components(
6935 type_node,
6936 &ctx.analyzer,
6937 ctx.visibility,
6938 ctx.file,
6939 ctx.source,
6940 ) {
6941 LexicalScopeResolution::Resolved(scope) => scope,
6942 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
6943 };
6944 match ctx.visibility.resolve_type_components_lexically(
6945 &ctx.analyzer,
6946 ctx.file,
6947 &components,
6948 is_globally_qualified_cpp_name(type_node),
6949 &lexical_scope,
6950 ) {
6951 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6952 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6953 }
6954}
6955
6956fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
6957 while node.kind() == "type_descriptor" {
6958 let Some(inner) = node.child_by_field_name("type") else {
6959 break;
6960 };
6961 node = inner;
6962 }
6963 node
6964}
6965
6966fn resolve_receiver_type_name_lexically(
6967 node: Node<'_>,
6968 normalized: &str,
6969 ctx: &ScanCtx<'_>,
6970) -> LexicalTypeResolution {
6971 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6972 brokk_bifrost_core::analyzer::Language::Cpp,
6973 normalized,
6974 );
6975 if components.is_empty() {
6976 return LexicalTypeResolution::Missing;
6977 }
6978 let lexical_scope = match enclosing_lexical_scope_components(
6979 node,
6980 &ctx.analyzer,
6981 ctx.visibility,
6982 ctx.file,
6983 ctx.source,
6984 ) {
6985 LexicalScopeResolution::Resolved(scope) => scope,
6986 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
6987 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
6988 };
6989 ctx.visibility.resolve_type_components_lexically(
6990 &ctx.analyzer,
6991 ctx.file,
6992 &components,
6993 normalized.starts_with("::"),
6994 &lexical_scope,
6995 )
6996}
6997
6998fn local_receiver_alias_type_node<'tree>(
6999 node: Node<'tree>,
7000 name: &str,
7001 ctx: &ScanCtx<'_>,
7002) -> Option<Node<'tree>> {
7003 let callable = nearest_callable_scope(node)?;
7004 let mut root_callable = callable;
7005 let mut ancestor = callable.parent();
7006 while let Some(current) = ancestor {
7007 if matches!(current.kind(), "function_definition" | "lambda_expression") {
7008 root_callable = current;
7009 }
7010 ancestor = current.parent();
7011 }
7012
7013 let mut stack = vec![root_callable];
7014 let mut best = None;
7015 while let Some(current) = stack.pop() {
7016 if current.start_byte() >= node.start_byte() {
7017 continue;
7018 }
7019 if local_type_alias_name_node(current)
7020 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
7021 && local_alias_scope_contains_node(current, node)
7022 {
7023 let replace = best
7024 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
7025 if replace {
7026 best = current.child_by_field_name("type");
7027 }
7028 }
7029 let mut cursor = current.walk();
7030 stack.extend(current.named_children(&mut cursor));
7031 }
7032 best
7033}
7034
7035fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
7036 let mut canonical = Vec::with_capacity(units.len());
7037 for unit in units {
7038 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
7039 return Vec::new();
7040 };
7041 canonical.push(unit);
7042 }
7043 unanimous_receiver_units(canonical)
7044}
7045
7046fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7047 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
7048 return cached.clone();
7049 }
7050 let canonical = ctx
7051 .visibility
7052 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
7053 ctx.receiver_canonical_type_cache
7054 .borrow_mut()
7055 .insert(unit.clone(), canonical.clone());
7056 canonical
7057}
7058
7059fn receiver_units_from_declared_fields(
7060 fields: Vec<&CodeUnit>,
7061 reference: Node<'_>,
7062 ctx: &ScanCtx<'_>,
7063) -> Vec<CodeUnit> {
7064 let Some(first) = fields.first() else {
7065 return Vec::new();
7066 };
7067 if fields
7068 .iter()
7069 .skip(1)
7070 .any(|field| !same_visible_symbol(first, field))
7071 {
7072 return Vec::new();
7073 }
7074 unanimous_receiver_units(
7075 fields
7076 .into_iter()
7077 .filter_map(|field| {
7078 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
7079 .and_then(|binding| binding.unit)
7080 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
7081 })
7082 .collect(),
7083 )
7084}
7085
7086fn recovered_receiver_field_type(
7091 reference: Node<'_>,
7092 field: &CodeUnit,
7093 ctx: &ScanCtx<'_>,
7094) -> Option<CodeUnit> {
7095 let target = ctx.spec.owner.as_ref()?.clone();
7096 if !target.is_class() || field.source() != ctx.file {
7097 return None;
7098 }
7099 let range = ctx
7100 .analyzer
7101 .ranges(field)
7102 .into_iter()
7103 .find(|range| range.start_byte < range.end_byte)?;
7104 let mut declaration =
7105 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
7106 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
7107 declaration = declaration.parent()?;
7108 }
7109 let type_node = first_type_child(declaration)?;
7110 let resolution = resolve_type_node_lexically_for_target(
7111 type_node,
7112 &ctx.analyzer,
7113 ctx.visibility,
7114 &ctx.ordinary_type_imports,
7115 ctx.file,
7116 ctx.source,
7117 &target,
7118 Some(&ctx.lexical_scope_cache),
7119 ctx.recovered_sentinel_scope(type_node).as_deref(),
7120 );
7121 if let LexicalTypeResolution::Resolved {
7122 unit, candidates, ..
7123 } = resolution
7124 && (same_visible_symbol(&unit, &target)
7125 || candidates
7126 .iter()
7127 .any(|candidate| same_visible_symbol(candidate, &target)))
7128 {
7129 return Some(target);
7130 }
7131 let type_node = receiver_type_node_base(type_node);
7132 let (components, global) = type_reference_components(type_node, ctx.source)?;
7133 let scope = ctx
7134 .recovered_sentinel_scope(type_node)
7135 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
7136 (indexed_scope_matches_target_name(&scope, &components, global, &target)
7137 && ctx.visibility.external_type_candidate_visible_in_context(
7138 &ctx.analyzer,
7139 ctx.file,
7140 &target,
7141 type_node,
7142 ))
7143 .then_some(target)
7144}
7145
7146fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
7147 let mut unique = Vec::new();
7148 for unit in units {
7149 if !unique
7150 .iter()
7151 .any(|existing| same_visible_symbol(existing, &unit))
7152 {
7153 unique.push(unit);
7154 if unique.len() > 1 {
7155 return Vec::new();
7156 }
7157 }
7158 }
7159 unique
7160}
7161
7162fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7163 let Some(owner) = ctx.spec.owner.as_ref() else {
7164 return false;
7165 };
7166 match node.kind() {
7167 "field_expression" => node
7168 .child_by_field_name("argument")
7169 .or_else(|| node.child_by_field_name("object"))
7170 .is_some_and(|receiver| {
7171 receiver_is_self_like(receiver, ctx.file) && same_owner_context(receiver, ctx)
7172 || receiver_type_units(receiver, ctx.source, ctx)
7173 .iter()
7174 .any(|target| {
7175 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7176 })
7177 }),
7178 "call_expression" => node
7179 .child_by_field_name("function")
7180 .is_some_and(|function| receiver_matches_target(function, ctx)),
7181 "pointer_expression" | "parenthesized_expression" => node
7182 .child_by_field_name("argument")
7183 .or_else(|| node.named_child(0))
7184 .is_some_and(|child| receiver_matches_target(child, ctx)),
7185 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
7186 .bindings
7187 .resolve_symbol(node_text(node, ctx.source))
7188 .as_precise()
7189 .is_some_and(|targets| {
7190 targets
7191 .iter()
7192 .filter_map(|target| target.unit.as_ref())
7193 .any(|target| {
7194 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7195 })
7196 }),
7197 "this" => same_owner_context(node, ctx),
7198 _ => qualified_owner_matches(node, ctx),
7199 }
7200}
7201
7202fn declaring_owner_for_explicit_receiver(
7203 receiver: Node<'_>,
7204 ctx: &ScanCtx<'_>,
7205) -> EnclosingMemberOwnerResolution {
7206 if receiver_is_self_like(receiver, ctx.file) {
7207 return EnclosingMemberOwnerResolution::Missing;
7208 }
7209 let receiver_units = receiver_type_units(receiver, ctx.source, ctx);
7210 let mut declaring_owner = None;
7211 for receiver_owner in receiver_units {
7212 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
7213 receiver_owner_matches_target(&receiver_owner, target_owner, receiver.start_byte(), ctx)
7214 }) {
7215 if declaring_owner
7216 .as_ref()
7217 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
7218 {
7219 return EnclosingMemberOwnerResolution::Ambiguous;
7220 }
7221 declaring_owner = Some(receiver_owner);
7222 continue;
7223 }
7224 match cached_declaring_member_owner(&receiver_owner, ctx) {
7225 EnclosingMemberOwnerResolution::Owner(owner) => {
7226 if declaring_owner
7227 .as_ref()
7228 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
7229 {
7230 return EnclosingMemberOwnerResolution::Ambiguous;
7231 }
7232 declaring_owner = Some(owner);
7233 }
7234 EnclosingMemberOwnerResolution::Ambiguous => {
7235 return EnclosingMemberOwnerResolution::Ambiguous;
7236 }
7237 EnclosingMemberOwnerResolution::Missing => {}
7238 }
7239 }
7240 declaring_owner
7241 .map(EnclosingMemberOwnerResolution::Owner)
7242 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
7243}
7244
7245fn declaring_owner_from_call_function(
7246 function: Node<'_>,
7247 ctx: &ScanCtx<'_>,
7248) -> Option<EnclosingMemberOwnerResolution> {
7249 match function.kind() {
7250 "field_expression" => function
7251 .child_by_field_name("argument")
7252 .or_else(|| function.child_by_field_name("object"))
7253 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, ctx))
7254 .or(Some(EnclosingMemberOwnerResolution::Missing)),
7255 "call_expression" => function
7256 .child_by_field_name("function")
7257 .and_then(|inner| declaring_owner_from_call_function(inner, ctx)),
7258 _ => None,
7259 }
7260}
7261
7262enum MethodReceiverTargetResolution {
7263 Target,
7264 NonTarget,
7265 Ambiguous,
7266 Missing,
7267}
7268
7269fn method_receiver_target_resolution(
7270 node: Node<'_>,
7271 declaring_owner: EnclosingMemberOwnerResolution,
7272 ctx: &ScanCtx<'_>,
7273) -> MethodReceiverTargetResolution {
7274 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7275 return MethodReceiverTargetResolution::Missing;
7276 };
7277 match declaring_owner {
7278 EnclosingMemberOwnerResolution::Owner(owner)
7279 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
7280 {
7281 MethodReceiverTargetResolution::Target
7282 }
7283 EnclosingMemberOwnerResolution::Owner(owner)
7284 if receiver_owner_is_known_non_target(&owner, target_owner, node.start_byte(), ctx) =>
7285 {
7286 MethodReceiverTargetResolution::NonTarget
7287 }
7288 EnclosingMemberOwnerResolution::Owner(_) => MethodReceiverTargetResolution::Missing,
7289 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
7290 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
7291 MethodReceiverTargetResolution::Target
7292 }
7293 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
7294 MethodReceiverTargetResolution::NonTarget
7295 }
7296 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
7297 }
7298}
7299
7300fn explicit_receiver_target_resolution(
7301 receiver: Node<'_>,
7302 ctx: &ScanCtx<'_>,
7303) -> MethodReceiverTargetResolution {
7304 method_receiver_target_resolution(
7305 receiver,
7306 declaring_owner_for_explicit_receiver(receiver, ctx),
7307 ctx,
7308 )
7309}
7310
7311fn call_function_target_resolution(
7312 function: Node<'_>,
7313 ctx: &ScanCtx<'_>,
7314) -> MethodReceiverTargetResolution {
7315 let Some(declaring_owner) = declaring_owner_from_call_function(function, ctx) else {
7316 return MethodReceiverTargetResolution::Missing;
7320 };
7321 method_receiver_target_resolution(function, declaring_owner, ctx)
7322}
7323
7324fn receiver_owner_matches_target(
7325 receiver_owner: &CodeUnit,
7326 target_owner: &CodeUnit,
7327 reference_byte: usize,
7328 ctx: &ScanCtx<'_>,
7329) -> bool {
7330 same_symbol(receiver_owner, target_owner)
7331 || same_logical_symbol(receiver_owner, target_owner)
7332 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
7333 || (ctx.spec.owner_is_forward_declaration
7334 && ctx
7335 .visibility
7336 .is_physically_visible(ctx.file, receiver_owner))
7337 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
7338 || target_group_contains_owner_peer(receiver_owner, ctx))
7339}
7340
7341fn receiver_owner_is_known_non_target(
7342 receiver_owner: &CodeUnit,
7343 target_owner: &CodeUnit,
7344 reference_byte: usize,
7345 ctx: &ScanCtx<'_>,
7346) -> bool {
7347 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
7348 return false;
7349 }
7350 if !same_logical_symbol(receiver_owner, target_owner) {
7351 return true;
7352 }
7353 !ctx.target_group.iter().any(|target| {
7354 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
7355 })
7356}
7357
7358fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
7359 ctx.visibility
7360 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
7361 && ctx.target_group.iter().any(|target| {
7362 type_owner_of(&ctx.analyzer, target)
7363 .as_ref()
7364 .is_some_and(|target_owner| {
7365 same_symbol(target_owner, owner)
7366 || (same_logical_symbol(target_owner, owner)
7367 && target_owner.source() == owner.source())
7368 })
7369 })
7370}
7371
7372fn visible_target_peer_matches_owner(
7373 owner: &CodeUnit,
7374 reference_byte: usize,
7375 ctx: &ScanCtx<'_>,
7376) -> bool {
7377 ctx.visibility
7378 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
7379 && ctx
7380 .visibility
7381 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7382 .any(|candidate| {
7383 cpp_callable_definitions_share_identity_evidence(
7384 &ctx.analyzer,
7385 candidate,
7386 &ctx.spec.target,
7387 ) && ctx.visibility.declaration_visible_at(
7388 &ctx.analyzer,
7389 ctx.file,
7390 candidate,
7391 reference_byte,
7392 ) && type_owner_of(&ctx.analyzer, candidate)
7393 .as_ref()
7394 .is_some_and(|candidate_owner| {
7395 same_symbol(candidate_owner, owner)
7396 || (same_logical_symbol(candidate_owner, owner)
7397 && candidate_owner.source() == owner.source())
7398 })
7399 })
7400}
7401
7402fn receiver_is_self_like(node: Node<'_>, file: &ProjectFile) -> bool {
7403 match node.kind() {
7404 "this" => !is_c_source_file(file),
7405 "pointer_expression" | "parenthesized_expression" => node
7406 .child_by_field_name("argument")
7407 .or_else(|| node.named_child(0))
7408 .is_some_and(|inner| receiver_is_self_like(inner, file)),
7409 _ => false,
7410 }
7411}
7412
7413fn call_function_has_direct_self_receiver(function: Node<'_>, file: &ProjectFile) -> bool {
7414 match function.kind() {
7415 "field_expression" => function
7416 .child_by_field_name("argument")
7417 .or_else(|| function.child_by_field_name("object"))
7418 .is_some_and(|receiver| receiver_is_self_like(receiver, file)),
7419 _ => receiver_is_self_like(function, file),
7420 }
7421}
7422
7423fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7424 let Some(owner) = ctx.spec.owner.as_ref() else {
7425 return false;
7426 };
7427 match node.kind() {
7428 "field_expression" => node
7429 .child_by_field_name("argument")
7430 .or_else(|| node.child_by_field_name("object"))
7431 .is_some_and(|receiver| {
7432 let units = receiver_type_units(receiver, ctx.source, ctx);
7433 !units.is_empty()
7434 && units.iter().all(|target| {
7435 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7436 })
7437 }),
7438 "call_expression" => node
7439 .child_by_field_name("function")
7440 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
7441 "pointer_expression" | "parenthesized_expression" => node
7442 .child_by_field_name("argument")
7443 .or_else(|| node.named_child(0))
7444 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
7445 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
7446 .bindings
7447 .resolve_symbol(node_text(node, ctx.source))
7448 .as_precise()
7449 .is_some_and(|targets| {
7450 let units = targets
7451 .iter()
7452 .filter_map(|target| target.unit.as_ref())
7453 .collect::<Vec<_>>();
7454 !units.is_empty()
7455 && units.iter().all(|target| {
7456 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7457 })
7458 }),
7459 "this" => known_non_target_owner_context(node, ctx),
7460 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
7461 qualified_owner_is_known_non_target(node, ctx)
7462 }
7463 _ => false,
7464 }
7465}
7466
7467#[derive(Clone, Copy, PartialEq, Eq)]
7468enum QualifiedOwnerResolution {
7469 Target,
7470 NonTarget,
7471 Unresolved,
7472}
7473
7474#[derive(Clone)]
7475pub enum LexicalScopeResolution {
7476 Resolved(Vec<String>),
7477 Ambiguous,
7478 Missing,
7479}
7480
7481type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
7482
7483fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7484 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
7485}
7486
7487fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7488 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
7489}
7490
7491fn is_structurally_qualified(node: Node<'_>) -> bool {
7492 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7493 && qualified_name_has_concrete_scope_separators(node)
7494}
7495
7496fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
7497 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7498 return QualifiedOwnerResolution::Unresolved;
7499 };
7500 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
7501 return QualifiedOwnerResolution::Unresolved;
7502 };
7503 if !global
7504 && !matches!(
7505 enclosing_lexical_scope_components(
7506 node,
7507 &ctx.analyzer,
7508 ctx.visibility,
7509 ctx.file,
7510 ctx.source,
7511 ),
7512 LexicalScopeResolution::Resolved(_)
7513 )
7514 {
7515 return QualifiedOwnerResolution::Unresolved;
7516 }
7517 match resolve_type_components_lexically_at_for_target_with_scope_cache(
7518 node,
7519 &components,
7520 global,
7521 &ctx.analyzer,
7522 ctx.visibility,
7523 &ctx.ordinary_type_imports,
7524 ctx.file,
7525 ctx.source,
7526 target_owner,
7527 false,
7528 Some(&ctx.lexical_scope_cache),
7529 ) {
7530 LexicalTypeResolution::Resolved { unit: owner, .. } => {
7531 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
7532 return QualifiedOwnerResolution::Target;
7533 }
7534 match cached_declaring_member_owner(&owner, ctx) {
7535 EnclosingMemberOwnerResolution::Owner(declaring_owner)
7536 if receiver_owner_matches_target(
7537 &declaring_owner,
7538 target_owner,
7539 node.start_byte(),
7540 ctx,
7541 ) =>
7542 {
7543 QualifiedOwnerResolution::Target
7544 }
7545 EnclosingMemberOwnerResolution::Owner(declaring_owner)
7546 if receiver_owner_is_known_non_target(
7547 &declaring_owner,
7548 target_owner,
7549 node.start_byte(),
7550 ctx,
7551 ) =>
7552 {
7553 QualifiedOwnerResolution::NonTarget
7554 }
7555 EnclosingMemberOwnerResolution::Owner(_)
7556 | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
7557 EnclosingMemberOwnerResolution::Missing
7558 if same_visible_symbol(&owner, target_owner) =>
7559 {
7560 QualifiedOwnerResolution::Unresolved
7561 }
7562 EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
7563 }
7564 }
7565 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7566 QualifiedOwnerResolution::Unresolved
7567 }
7568 }
7569}
7570
7571fn qualified_callable_owner_components(
7572 node: Node<'_>,
7573 source: &str,
7574) -> Option<(Vec<String>, bool)> {
7575 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7576 || !qualified_name_has_concrete_scope_separators(node)
7577 {
7578 return None;
7579 }
7580 let global = is_globally_qualified_cpp_name(node);
7581 let mut components = Vec::new();
7582 append_cpp_name_components(node, source, &mut components)?;
7583 components.pop()?;
7584 (!components.is_empty()).then_some((components, global))
7585}
7586
7587fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
7588 if !matches!(
7589 node.kind(),
7590 "identifier"
7591 | "type_identifier"
7592 | "namespace_identifier"
7593 | "qualified_identifier"
7594 | "scoped_identifier"
7595 | "scoped_type_identifier"
7596 | "template_type"
7597 | "template_function"
7598 ) {
7599 return None;
7600 }
7601 let mut components = Vec::new();
7602 append_cpp_name_components(node, source, &mut components)?;
7603 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
7604}
7605
7606pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
7607 let mut namespaces = Vec::new();
7608 let mut current = node.parent();
7609 while let Some(parent) = current {
7610 if parent.kind() == "namespace_definition"
7611 && let Some(name) = parent.child_by_field_name("name")
7612 {
7613 let mut components = Vec::new();
7614 if append_cpp_name_components(name, source, &mut components).is_some() {
7615 namespaces.push(components);
7616 }
7617 }
7618 current = parent.parent();
7619 }
7620 namespaces.reverse();
7621 namespaces.into_iter().flatten().collect()
7622}
7623
7624pub fn enclosing_lexical_scope_components(
7625 node: Node<'_>,
7626 analyzer: &CppGraphSource<'_>,
7627 visibility: &VisibilityIndex<'_>,
7628 file: &ProjectFile,
7629 source: &str,
7630) -> LexicalScopeResolution {
7631 enclosing_lexical_scope_components_with_unresolved_owner(
7632 node, analyzer, visibility, file, source, false, false,
7633 )
7634}
7635
7636#[allow(clippy::too_many_arguments)]
7637fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
7638 node: Node<'_>,
7639 analyzer: &CppGraphSource<'_>,
7640 visibility: &VisibilityIndex<'_>,
7641 file: &ProjectFile,
7642 source: &str,
7643 allow_structured_unresolved_owner: bool,
7644 ignore_function_owner: bool,
7645 cache: Option<&LexicalScopeCache>,
7646) -> LexicalScopeResolution {
7647 let Some(cache) = cache else {
7648 return enclosing_lexical_scope_components_with_unresolved_owner(
7649 node,
7650 analyzer,
7651 visibility,
7652 file,
7653 source,
7654 allow_structured_unresolved_owner,
7655 ignore_function_owner,
7656 );
7657 };
7658 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
7659 let key = (
7660 anchor_start,
7661 anchor_end,
7662 allow_structured_unresolved_owner,
7663 ignore_function_owner,
7664 );
7665 if let Some(cached) = cache.borrow().get(&key).cloned() {
7666 return cached;
7667 }
7668 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
7669 node,
7670 analyzer,
7671 visibility,
7672 file,
7673 source,
7674 allow_structured_unresolved_owner,
7675 ignore_function_owner,
7676 );
7677 cache.borrow_mut().insert(key, resolved.clone());
7678 resolved
7679}
7680
7681fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
7682 let mut current = node;
7683 loop {
7684 if matches!(
7685 current.kind(),
7686 "function_definition"
7687 | "class_specifier"
7688 | "struct_specifier"
7689 | "union_specifier"
7690 | "namespace_definition"
7691 | "translation_unit"
7692 ) {
7693 return (current.start_byte(), current.end_byte());
7694 }
7695 let Some(parent) = current.parent() else {
7696 return (current.start_byte(), current.end_byte());
7697 };
7698 current = parent;
7699 }
7700}
7701
7702fn enclosing_lexical_scope_components_with_unresolved_owner(
7703 node: Node<'_>,
7704 analyzer: &CppGraphSource<'_>,
7705 visibility: &VisibilityIndex<'_>,
7706 file: &ProjectFile,
7707 source: &str,
7708 allow_structured_unresolved_owner: bool,
7709 ignore_function_owner: bool,
7710) -> LexicalScopeResolution {
7711 #[cfg(any(test, feature = "test-support"))]
7712 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
7713 let mut namespaces = Vec::new();
7719 let mut classes = Vec::new();
7720 let mut function_definition = None;
7721 let mut displaced_class_scope = false;
7722 let mut current = node.parent();
7723 while let Some(parent) = current {
7724 match parent.kind() {
7725 "namespace_definition" => {
7726 if let Some(name) = parent.child_by_field_name("name") {
7727 let mut components = Vec::new();
7728 if append_cpp_name_components(name, source, &mut components).is_some() {
7729 namespaces.push(components);
7730 }
7731 }
7732 }
7733 "class_specifier" | "struct_specifier" | "union_specifier" => {
7734 if let Some(name) = parent.child_by_field_name("name") {
7735 let mut components = Vec::new();
7736 if append_cpp_name_components(name, source, &mut components).is_some() {
7737 classes.push(components);
7738 }
7739 }
7740 }
7741 "function_definition" => {
7742 if function_definition.is_none() {
7743 function_definition = Some(parent);
7744 }
7745 displaced_class_scope = displaced_class_scope
7746 || parent.child_by_field_name("type").is_some_and(|type_node| {
7747 matches!(
7748 type_node.kind(),
7749 "class_specifier" | "struct_specifier" | "union_specifier"
7750 )
7751 })
7752 || is_malformed_wrapper_function_definition(parent);
7753 }
7754 _ => {}
7755 }
7756 current = parent.parent();
7757 }
7758 namespaces.reverse();
7759 let namespace: Vec<String> = namespaces.into_iter().flatten().collect();
7760 let mut scope = namespace.clone();
7761 let has_qualified_function_owner = function_definition
7762 .and_then(function_definition_owner_lookup_node)
7763 .is_some_and(|owner| {
7764 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
7765 });
7766 let indexed_scope = displaced_class_scope
7767 .then(|| {
7768 indexed_structural_class_scope(visibility, file, node, source)
7769 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7770 })
7771 .flatten()
7772 .or_else(|| {
7773 (has_qualified_function_owner && function_definition.is_some())
7780 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7781 .flatten()
7782 .filter(|indexed| {
7783 qualified_owner_scope_is_recoverable(
7784 indexed,
7785 &namespace,
7786 &classes,
7787 function_definition
7788 .and_then(function_definition_owner_lookup_node)
7789 .and_then(|owner| qualified_callable_owner_components(owner, source))
7790 .map(|(components, _)| components),
7791 )
7792 })
7793 })
7794 .or_else(|| {
7795 indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
7803 indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
7804 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
7805 })
7806 })
7807 })
7808 .or_else(|| {
7809 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
7813 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
7814 .flatten()
7815 .filter(|indexed| indexed.len() > namespace.len())
7816 });
7817 if let Some(indexed_scope) = indexed_scope.as_ref() {
7818 scope = indexed_scope.clone();
7823 classes.clear();
7824 }
7825
7826 if !ignore_function_owner
7827 && has_qualified_function_owner
7828 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
7829 {
7830 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
7831 return LexicalScopeResolution::Missing;
7832 };
7833 let imports = visibility.ordinary_type_import_cell(file);
7839 let owner_resolution = resolve_type_components_lexically_at_scoped(
7840 function,
7841 &owner,
7842 global,
7843 analyzer,
7844 visibility,
7845 &imports,
7846 file,
7847 source,
7848 None,
7849 false,
7850 false,
7851 namespace.clone(),
7852 );
7853 match owner_resolution {
7854 LexicalTypeResolution::Resolved {
7855 unit, components, ..
7856 } if is_indexed_class_owner(analyzer, &unit) => {
7857 scope = components;
7858 classes.clear();
7859 }
7860 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
7861 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
7862 match visibility
7863 .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
7864 {
7865 LexicalTypeResolution::Resolved { components, .. } => scope = components,
7866 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
7867 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
7868 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
7869 qualified_owner_scope_is_recoverable(
7870 indexed,
7871 &namespace,
7872 &classes,
7873 Some(owner.clone()),
7874 )
7875 }) {
7876 scope = indexed.clone();
7877 } else {
7878 scope = if global || owner.starts_with(&namespace) {
7879 owner
7880 } else {
7881 let mut relative = namespace;
7882 relative.extend(owner);
7883 relative
7884 };
7885 }
7886 }
7887 LexicalTypeResolution::Missing => {
7888 match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
7898 Some(indexed) => scope = indexed,
7899 None => return LexicalScopeResolution::Missing,
7900 }
7901 }
7902 }
7903 }
7904 }
7905 }
7906
7907 classes.reverse();
7908 scope.extend(classes.into_iter().flatten());
7909 LexicalScopeResolution::Resolved(scope)
7910}
7911
7912fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
7913 let mut current = node.parent();
7914 while let Some(parent) = current {
7915 if parent.kind() == "function_definition"
7916 && parent.child_by_field_name("type").is_some_and(|type_node| {
7917 matches!(
7918 type_node.kind(),
7919 "class_specifier" | "struct_specifier" | "union_specifier"
7920 )
7921 })
7922 {
7923 return true;
7924 }
7925 current = parent.parent();
7926 }
7927 false
7928}
7929
7930fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
7931 let mut current = node.parent();
7932 while let Some(parent) = current {
7933 if parent.kind() == "function_definition"
7934 && is_malformed_wrapper_function_definition(parent)
7935 {
7936 return true;
7937 }
7938 current = parent.parent();
7939 }
7940 false
7941}
7942
7943fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
7944 node.has_error()
7945 && node
7946 .child_by_field_name("declarator")
7947 .is_some_and(|declarator| {
7948 declarator.kind() != "function_declarator"
7949 && first_descendant_of_kind(declarator, "function_declarator").is_none()
7950 })
7951}
7952
7953fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
7959 node.child_by_field_name("scope")
7960 .and_then(|scope| recovered_macro_decorated_type_node(scope))
7961 .is_some()
7962}
7963
7964fn indexed_structural_class_scope(
7965 visibility: &VisibilityIndex<'_>,
7966 file: &ProjectFile,
7967 node: Node<'_>,
7968 source: &str,
7969) -> Option<Vec<String>> {
7970 let mut current = node.parent();
7971 while let Some(parent) = current {
7972 if matches!(
7973 parent.kind(),
7974 "class_specifier" | "struct_specifier" | "union_specifier"
7975 ) {
7976 return visibility.indexed_structural_class_scope(file, parent, source);
7977 }
7978 current = parent.parent();
7979 }
7980 None
7981}
7982
7983fn qualified_owner_scope_is_recoverable(
7994 indexed: &[String],
7995 namespace: &[String],
7996 classes: &[Vec<String>],
7997 qualified_owner: Option<Vec<String>>,
7998) -> bool {
7999 if let Some(owner) = qualified_owner {
8000 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
8001 return false;
8002 }
8003 if namespace.is_empty() {
8009 return true;
8010 }
8011 if indexed.len() <= namespace.len() {
8012 return false;
8013 }
8014 let mut prefix = indexed.iter();
8015 return namespace
8016 .iter()
8017 .all(|component| prefix.any(|candidate| candidate == component));
8018 }
8019 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
8020 if !class_components.is_empty() {
8021 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
8022 }
8023 if namespace.is_empty() || indexed.len() <= namespace.len() {
8024 return false;
8025 }
8026 let mut prefix = indexed.iter();
8027 namespace
8028 .iter()
8029 .all(|component| prefix.any(|candidate| candidate == component))
8030}
8031
8032fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
8035 unit.is_class()
8036 && !analyzer
8037 .type_alias_provider()
8038 .is_some_and(|provider| provider.is_type_alias(unit))
8039}
8040
8041fn indexed_enclosing_owner_scope(
8055 analyzer: &CppGraphSource<'_>,
8056 visibility: &VisibilityIndex<'_>,
8057 file: &ProjectFile,
8058 node: Node<'_>,
8059) -> Option<Vec<String>> {
8060 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
8061}
8062
8063fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8064 let start = enclosing_context(node, ctx).enclosing?;
8065 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
8066 ctx.analyzer.parent_of(unit)
8067 })
8068 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
8069}
8070
8071pub fn resolve_type_node_lexically(
8072 node: Node<'_>,
8073 analyzer: &CppGraphSource<'_>,
8074 visibility: &VisibilityIndex<'_>,
8075 ordinary_type_imports: &OrdinaryTypeImportCell,
8076 file: &ProjectFile,
8077 source: &str,
8078) -> LexicalTypeResolution {
8079 let Some((components, global)) = type_reference_components(node, source) else {
8080 return LexicalTypeResolution::Missing;
8081 };
8082 let resolution = resolve_type_components_lexically_at(
8083 node,
8084 &components,
8085 global,
8086 analyzer,
8087 visibility,
8088 ordinary_type_imports,
8089 file,
8090 source,
8091 );
8092 if !is_template_argument_type_leaf(node) {
8093 return resolution;
8094 }
8095
8096 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8103 return resolution;
8104 };
8105 let namespace_scope = enclosing_namespace_components(node, source);
8106 if indexed_scope.len() <= namespace_scope.len() {
8107 return resolution;
8108 }
8109 let indexed = visibility.resolve_type_components_lexically(
8110 analyzer,
8111 file,
8112 &components,
8113 global,
8114 &indexed_scope,
8115 );
8116 match indexed {
8117 LexicalTypeResolution::Resolved { ref unit, .. }
8118 if !visibility
8119 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8120 {
8121 resolution
8122 }
8123 LexicalTypeResolution::Resolved { .. } => indexed,
8124 _ => resolution,
8125 }
8126}
8127
8128fn is_template_argument_type_leaf(node: Node<'_>) -> bool {
8129 let Some(type_descriptor) = node.parent() else {
8130 return false;
8131 };
8132 if type_descriptor.kind() != "type_descriptor"
8133 || type_descriptor.child_by_field_name("type") != Some(node)
8134 {
8135 return false;
8136 }
8137 let Some(arguments) = type_descriptor.parent() else {
8138 return false;
8139 };
8140 if arguments.kind() != "template_argument_list" {
8141 return false;
8142 }
8143 arguments.parent().is_some_and(|parent| {
8144 matches!(parent.kind(), "template_type" | "template_function")
8145 && parent.child_by_field_name("arguments") == Some(arguments)
8146 })
8147}
8148
8149#[allow(clippy::too_many_arguments)]
8150pub fn resolve_type_node_lexically_for_target(
8151 node: Node<'_>,
8152 analyzer: &CppGraphSource<'_>,
8153 visibility: &VisibilityIndex<'_>,
8154 ordinary_type_imports: &OrdinaryTypeImportCell,
8155 file: &ProjectFile,
8156 source: &str,
8157 target: &CodeUnit,
8158 scope_cache: Option<&LexicalScopeCache>,
8159 recovered_scope: Option<&[String]>,
8160) -> LexicalTypeResolution {
8161 let Some((reference_components, global)) = type_reference_components(node, source) else {
8162 return LexicalTypeResolution::Missing;
8163 };
8164 let terminal = reference_components
8165 .last()
8166 .expect("type reference components are non-empty");
8167 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
8168 return LexicalTypeResolution::Missing;
8169 }
8170 let template_arguments = cpp_template_reference_arguments(node, source);
8171 let selects_concrete_specialization =
8172 template_arguments.is_some() && visibility.is_template_specialization(target);
8173 if !selects_concrete_specialization
8174 && !visibility.structured_type_reference_may_resolve_to_target(
8175 analyzer,
8176 file,
8177 std::slice::from_ref(terminal),
8178 false,
8179 &[],
8180 target,
8181 )
8182 {
8183 return LexicalTypeResolution::Missing;
8184 }
8185 if let Some(arguments) = template_arguments.as_ref() {
8186 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
8187 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
8188 node,
8189 &reference_components,
8190 global,
8191 analyzer,
8192 visibility,
8193 ordinary_type_imports,
8194 file,
8195 source,
8196 recovered_scope,
8197 )
8198 } else {
8199 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
8200 node,
8201 &reference_components,
8202 global,
8203 analyzer,
8204 visibility,
8205 ordinary_type_imports,
8206 file,
8207 source,
8208 scope_cache,
8209 )
8210 };
8211 return match alias_resolution {
8212 LexicalTypeResolution::Resolved {
8213 unit,
8214 components,
8215 candidates,
8216 } if visibility.template_alias_arguments_preserve_target(
8217 analyzer, file, &unit, arguments, target,
8218 ) =>
8219 {
8220 LexicalTypeResolution::Resolved {
8221 unit: target.clone(),
8222 components,
8223 candidates,
8224 }
8225 }
8226 LexicalTypeResolution::Resolved {
8227 unit,
8228 components,
8229 candidates,
8230 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
8231 Ok(resolved_unit) => {
8232 let target_guided = (!same_visible_symbol(&resolved_unit, target))
8233 .then(|| {
8234 target_guided_malformed_template_alias_resolution(
8235 node,
8236 analyzer,
8237 visibility,
8238 file,
8239 arguments,
8240 &reference_components,
8241 target,
8242 )
8243 })
8244 .flatten();
8245 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
8246 unit: resolved_unit,
8247 components,
8248 candidates,
8249 })
8250 }
8251 Err(_) => LexicalTypeResolution::Ambiguous,
8252 },
8253 LexicalTypeResolution::Missing => {
8254 let target_preserving = if let Some(recovered_scope) = recovered_scope {
8255 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8256 node,
8257 &reference_components,
8258 global,
8259 analyzer,
8260 visibility,
8261 ordinary_type_imports,
8262 file,
8263 source,
8264 target,
8265 true,
8266 recovered_scope,
8267 )
8268 } else {
8269 resolve_type_components_lexically_at_for_target_with_scope_cache(
8270 node,
8271 &reference_components,
8272 global,
8273 analyzer,
8274 visibility,
8275 ordinary_type_imports,
8276 file,
8277 source,
8278 target,
8279 true,
8280 scope_cache,
8281 )
8282 };
8283 match target_preserving {
8284 LexicalTypeResolution::Resolved {
8285 unit: _,
8286 components,
8287 candidates,
8288 } if template_reference_candidates_select_target(
8289 node,
8290 &candidates,
8291 analyzer,
8292 visibility,
8293 file,
8294 source,
8295 target,
8296 ) =>
8297 {
8298 LexicalTypeResolution::Resolved {
8299 unit: target.clone(),
8300 components,
8301 candidates,
8302 }
8303 }
8304 _ => target_guided_malformed_template_alias_resolution(
8305 node,
8306 analyzer,
8307 visibility,
8308 file,
8309 arguments,
8310 &reference_components,
8311 target,
8312 )
8313 .unwrap_or(LexicalTypeResolution::Missing),
8314 }
8315 }
8316 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
8317 };
8318 }
8319 let resolution = if let Some(recovered_scope) = recovered_scope {
8320 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8321 node,
8322 &reference_components,
8323 global,
8324 analyzer,
8325 visibility,
8326 ordinary_type_imports,
8327 file,
8328 source,
8329 target,
8330 true,
8331 recovered_scope,
8332 )
8333 } else {
8334 resolve_type_components_lexically_at_for_target_with_scope_cache(
8335 node,
8336 &reference_components,
8337 global,
8338 analyzer,
8339 visibility,
8340 ordinary_type_imports,
8341 file,
8342 source,
8343 target,
8344 true,
8345 scope_cache,
8346 )
8347 };
8348 if !is_template_argument_type_leaf(node) {
8349 return resolution;
8350 }
8351
8352 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8360 return resolution;
8361 };
8362 let namespace_scope = enclosing_namespace_components(node, source);
8363 if indexed_scope.len() <= namespace_scope.len() {
8364 return resolution;
8365 }
8366 let indexed = visibility.resolve_type_components_lexically_for_target(
8367 analyzer,
8368 file,
8369 &reference_components,
8370 global,
8371 &indexed_scope,
8372 target,
8373 );
8374 match indexed {
8375 LexicalTypeResolution::Resolved {
8376 ref unit,
8377 ref candidates,
8378 ..
8379 } if (same_visible_symbol(unit, target)
8380 || candidates
8381 .iter()
8382 .any(|candidate| same_visible_symbol(candidate, target)))
8383 && visibility
8384 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8385 {
8386 indexed
8387 }
8388 _ => resolution,
8389 }
8390}
8391
8392#[allow(clippy::too_many_arguments)]
8393fn target_guided_malformed_template_alias_resolution(
8394 node: Node<'_>,
8395 analyzer: &CppGraphSource<'_>,
8396 visibility: &VisibilityIndex<'_>,
8397 file: &ProjectFile,
8398 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
8399 components: &[String],
8400 target: &CodeUnit,
8401) -> Option<LexicalTypeResolution> {
8402 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
8403 return None;
8404 }
8405
8406 let identifier = &components[0];
8407 let namespace =
8408 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
8409 let namespace_name = namespace.join("::");
8410 let candidates = visibility
8411 .visible_identifier_candidates(file, identifier)
8412 .filter(|candidate| {
8413 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
8414 && visibility.type_candidate_may_be_visible_before_reference(
8415 analyzer,
8416 file,
8417 candidate,
8418 node.start_byte(),
8419 )
8420 })
8421 .cloned()
8422 .collect::<Vec<_>>();
8423 let first = candidates.first()?;
8424 if !candidates
8425 .iter()
8426 .all(|candidate| same_logical_symbol(first, candidate))
8427 || !candidates.iter().all(|candidate| {
8428 visibility.template_alias_arguments_preserve_target(
8429 analyzer, file, candidate, arguments, target,
8430 )
8431 })
8432 {
8433 return None;
8434 }
8435
8436 let mut resolved_components = namespace;
8437 resolved_components.push(identifier.clone());
8438 Some(LexicalTypeResolution::Resolved {
8439 unit: target.clone(),
8440 components: resolved_components,
8441 candidates,
8442 })
8443}
8444
8445fn resolve_type_node_lexically_for_target_without_visibility(
8446 node: Node<'_>,
8447 analyzer: &CppGraphSource<'_>,
8448 visibility: &VisibilityIndex<'_>,
8449 file: &ProjectFile,
8450 source: &str,
8451 target: &CodeUnit,
8452) -> LexicalTypeResolution {
8453 let Some((components, global)) = type_reference_components(node, source) else {
8454 return LexicalTypeResolution::Missing;
8455 };
8456 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
8457 node,
8458 analyzer,
8459 visibility,
8460 file,
8461 source,
8462 true,
8463 recovered_macro_decorated_declarator_type(node)
8464 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
8465 ) {
8466 LexicalScopeResolution::Resolved(scope) => scope,
8467 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
8468 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
8469 };
8470 visibility.resolve_type_components_lexically_for_target(
8471 analyzer,
8472 file,
8473 &components,
8474 global,
8475 &lexical_scope,
8476 target,
8477 )
8478}
8479
8480fn type_node_has_exact_target_identity_without_visibility(
8481 node: Node<'_>,
8482 analyzer: &CppGraphSource<'_>,
8483 visibility: &VisibilityIndex<'_>,
8484 file: &ProjectFile,
8485 source: &str,
8486 target: &CodeUnit,
8487) -> bool {
8488 let Some((components, global)) = type_reference_components(node, source) else {
8489 return false;
8490 };
8491 let LexicalScopeResolution::Resolved(lexical_scope) =
8492 enclosing_lexical_scope_components_with_unresolved_owner(
8493 node,
8494 analyzer,
8495 visibility,
8496 file,
8497 source,
8498 true,
8499 recovered_macro_decorated_declarator_type(node)
8500 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
8501 )
8502 else {
8503 return false;
8504 };
8505 let target_name = cpp_name_for(target);
8506 lexical_component_tiers(&components, global, &lexical_scope)
8507 .any(|qualified| qualified.join("::") == target_name)
8508}
8509
8510pub fn resolve_using_enum_declaration_owner(
8511 node: Node<'_>,
8512 analyzer: &CppGraphSource<'_>,
8513 visibility: &VisibilityIndex<'_>,
8514 ordinary_type_imports: &OrdinaryTypeImportCell,
8515 file: &ProjectFile,
8516 source: &str,
8517) -> LexicalTypeResolution {
8518 let Some(type_node) = using_enum_declaration_type_node(node) else {
8519 return LexicalTypeResolution::Missing;
8520 };
8521 let mut components = Vec::new();
8522 if append_cpp_name_components(type_node, source, &mut components).is_none()
8523 || components.is_empty()
8524 {
8525 return LexicalTypeResolution::Missing;
8526 }
8527 resolve_type_components_lexically_at(
8528 type_node,
8529 &components,
8530 is_globally_qualified_cpp_name(type_node),
8531 analyzer,
8532 visibility,
8533 ordinary_type_imports,
8534 file,
8535 source,
8536 )
8537}
8538
8539pub fn resolve_ordinary_using_declaration_owner(
8540 node: Node<'_>,
8541 analyzer: &CppGraphSource<'_>,
8542 visibility: &VisibilityIndex<'_>,
8543 file: &ProjectFile,
8544 source: &str,
8545) -> LexicalTypeResolution {
8546 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
8547 return LexicalTypeResolution::Missing;
8548 };
8549 let mut components = Vec::new();
8550 if append_cpp_name_components(type_node, source, &mut components).is_none()
8551 || components.len() < 2
8552 {
8553 return LexicalTypeResolution::Missing;
8554 }
8555 let lexical_scope =
8556 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
8557 LexicalScopeResolution::Resolved(scope) => scope,
8558 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
8559 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
8560 };
8561 visibility.resolve_type_components_lexically(
8562 analyzer,
8563 file,
8564 &components,
8565 is_globally_qualified_cpp_name(type_node),
8566 &lexical_scope,
8567 )
8568}
8569
8570pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
8571 (node.kind() == "using_declaration"
8572 && (0..node.child_count()).any(|index| {
8573 node.child(index)
8574 .is_some_and(|child| child.kind() == "enum")
8575 }))
8576 .then(|| node.named_child(0))
8577 .flatten()
8578}
8579
8580pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
8581 (node.kind() == "using_declaration"
8582 && using_enum_declaration_type_node(node).is_none()
8583 && using_namespace_directive_name_node(node).is_none())
8584 .then(|| node.named_child(0))
8585 .flatten()
8586}
8587
8588fn recovered_macro_using_declaration_type_node<'tree>(
8595 node: Node<'tree>,
8596 source: &str,
8597) -> Option<(Node<'tree>, bool)> {
8598 if node.kind() != "declaration" {
8599 return None;
8600 }
8601 let macro_type = node.child_by_field_name("type")?;
8602 if macro_type.kind() != "type_identifier"
8603 || !cpp_export_macro_token(node_text(macro_type, source))
8604 {
8605 return None;
8606 }
8607 let declarator = node.child_by_field_name("declarator")?;
8608 if declarator.kind() != "qualified_identifier" {
8609 return None;
8610 }
8611 let scope = declarator.child_by_field_name("scope")?;
8612 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
8613 return None;
8614 }
8615 let target = declarator.child_by_field_name("name")?;
8616 let mut components = Vec::new();
8617 append_cpp_name_components(target, source, &mut components)?;
8618 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
8619}
8620
8621fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
8622 let is_directive = node.kind() == "using_directive"
8623 || (node.kind() == "using_declaration"
8624 && (0..node.child_count()).any(|index| {
8625 node.child(index)
8626 .is_some_and(|child| child.kind() == "namespace")
8627 }));
8628 if !is_directive {
8629 return None;
8630 }
8631 node.child_by_field_name("name")
8632 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
8633}
8634
8635fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
8636 let mut current = node.parent();
8637 while let Some(parent) = current {
8638 if matches!(
8639 parent.kind(),
8640 "compound_statement"
8641 | "function_definition"
8642 | "lambda_expression"
8643 | "for_statement"
8644 | "while_statement"
8645 | "if_statement"
8646 | "class_specifier"
8647 | "struct_specifier"
8648 | "union_specifier"
8649 ) {
8650 return None;
8651 }
8652 current = parent.parent();
8653 }
8654 Some(enclosing_namespace_components(node, source))
8655}
8656
8657fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
8658 let mut current = node.parent();
8659 while let Some(scope) = current {
8660 if matches!(
8661 scope.kind(),
8662 "compound_statement"
8663 | "declaration_list"
8664 | "field_declaration_list"
8665 | "translation_unit"
8666 ) {
8667 let mut depth = 0;
8668 let mut ancestor = scope.parent();
8669 while let Some(parent) = ancestor {
8670 depth += 1;
8671 ancestor = parent.parent();
8672 }
8673 return Some((
8674 scope.start_byte(),
8675 scope.end_byte(),
8676 depth,
8677 scope.kind() == "compound_statement",
8678 ));
8679 }
8680 current = scope.parent();
8681 }
8682 None
8683}
8684
8685pub fn build_source_using_index(cpp: &dyn CppSource, file: &ProjectFile) -> SourceUsingIndex {
8692 let Some(prepared) = cpp.prepared_syntax(file) else {
8693 return SourceUsingIndex::default();
8694 };
8695 collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
8696}
8697
8698fn collect_source_using_index(
8699 cpp: &dyn CppSource,
8700 source_file: &ProjectFile,
8701 root: Node<'_>,
8702 source: &str,
8703) -> SourceUsingIndex {
8704 #[cfg(not(any(test, feature = "test-support")))]
8705 let _ = cpp;
8706 let mut index = SourceUsingIndex::default();
8707 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
8708 let mut stack = vec![root];
8709 while let Some(node) = stack.pop() {
8710 let target = match node.kind() {
8711 "using_directive" | "using_declaration" => {
8712 if let Some(namespace_node) = using_namespace_directive_name_node(node) {
8713 let mut namespace_components = Vec::new();
8714 append_cpp_name_components(namespace_node, source, &mut namespace_components)
8715 .map(|_| EffectiveUsingTarget::Namespace {
8716 namespace_components,
8717 global: is_globally_qualified_cpp_name(namespace_node),
8718 })
8719 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
8720 let mut target_components = Vec::new();
8721 (append_cpp_name_components(type_node, source, &mut target_components)
8722 .is_some()
8723 && target_components.len() >= 2)
8724 .then(|| EffectiveUsingTarget::Ordinary {
8725 name: target_components
8726 .last()
8727 .expect("ordinary using has a terminal component")
8728 .clone(),
8729 target_components,
8730 global: is_globally_qualified_cpp_name(type_node),
8731 })
8732 } else {
8733 None
8734 }
8735 }
8736 "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
8737 |(type_node, global)| {
8738 let mut target_components = Vec::new();
8739 (append_cpp_name_components(type_node, source, &mut target_components)
8740 .is_some()
8741 && target_components.len() >= 2)
8742 .then(|| EffectiveUsingTarget::Ordinary {
8743 name: target_components
8744 .last()
8745 .expect("recovered ordinary using has a terminal component")
8746 .clone(),
8747 target_components,
8748 global,
8749 })
8750 },
8751 ),
8752 _ => None,
8753 };
8754 if let Some(target) = target {
8755 #[cfg(any(test, feature = "test-support"))]
8761 cpp.record_using_guard_context_inspection_for_test();
8762 let required_guards = if callable_preprocessor_context_is_visible(node, source) {
8763 Some(HashSet::default())
8764 } else {
8765 preprocessor_guard_environment(node, source)
8766 };
8767 let Some(required_guards) = required_guards else {
8768 let mut cursor = node.walk();
8769 stack.extend(node.children(&mut cursor));
8770 continue;
8771 };
8772 if let Some((scope_start, scope_end, scope_depth, block_scope)) =
8773 ordinary_using_scope(node)
8774 {
8775 let declaration_namespace = enclosing_namespace_components(node, source);
8776 let declaration_namespace = if declaration_namespace.is_empty() {
8777 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
8778 .unwrap_or(declaration_namespace)
8779 } else {
8780 declaration_namespace
8781 };
8782 let namespace_scope = using_named_scope(node, source);
8783 let lexical_depth = declaration_namespace.len();
8784 let binding = OrdinaryTypeImport {
8785 target,
8786 source: source_file.clone(),
8787 declaration_byte: node.end_byte(),
8788 scope_start,
8789 scope_end,
8790 scope_depth,
8791 block_scope,
8792 lexical_depth,
8793 declaration_namespace,
8794 namespace_scope,
8795 resolved_target_components: None,
8796 required_guards,
8797 };
8798 match &binding.target {
8799 EffectiveUsingTarget::Ordinary { name, .. } => index
8800 .ordinary_by_name
8801 .entry(name.clone())
8802 .or_default()
8803 .push(binding),
8804 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
8805 }
8806 }
8807 }
8808 let mut cursor = node.walk();
8809 stack.extend(node.children(&mut cursor));
8810 }
8811 index
8812}
8813
8814struct OrphanedNamespaceEnvelope {
8815 body_end: usize,
8816 components: Vec<String>,
8817 class_names: HashSet<String>,
8818 error_marked: bool,
8819}
8820
8821fn collect_orphaned_namespace_envelopes(
8828 root: Node<'_>,
8829 source: &str,
8830) -> Vec<OrphanedNamespaceEnvelope> {
8831 let mut envelopes = Vec::new();
8832 let mut stack = vec![root];
8833 while let Some(current) = stack.pop() {
8834 if current.kind() == "namespace_definition"
8835 && let Some(body) = current.child_by_field_name("body")
8836 && current.end_byte() == body.end_byte()
8837 && let Some(name) = current.child_by_field_name("name")
8838 {
8839 let mut components = enclosing_namespace_components(current, source);
8840 if append_cpp_name_components(name, source, &mut components).is_some()
8841 && !components.is_empty()
8842 {
8843 let mut class_names = HashSet::default();
8844 let mut body_stack = vec![body];
8845 while let Some(node) = body_stack.pop() {
8846 if let Some(name) = orphaned_class_definition_name(node, source) {
8847 class_names.insert(name);
8848 }
8849 let mut cursor = node.walk();
8850 if node.kind() == "ERROR" {
8851 body_stack.extend(node.children(&mut cursor));
8852 } else {
8853 body_stack.extend(node.named_children(&mut cursor));
8854 }
8855 }
8856 envelopes.push(OrphanedNamespaceEnvelope {
8857 body_end: body.end_byte(),
8858 components,
8859 class_names,
8860 error_marked: current.has_error(),
8861 });
8862 }
8863 }
8864 let mut cursor = current.walk();
8865 stack.extend(current.named_children(&mut cursor));
8866 }
8867 envelopes
8868}
8869
8870fn collect_orphaned_namespace_type_envelopes(
8877 root: Node<'_>,
8878 source: &str,
8879) -> Vec<OrphanedNamespaceEnvelope> {
8880 let mut envelopes = Vec::new();
8881 let mut stack = vec![root];
8882 while let Some(current) = stack.pop() {
8883 if current.kind() == "namespace_definition"
8884 && current.has_error()
8885 && let Some(body) = current.child_by_field_name("body")
8886 && current.end_byte() == body.end_byte()
8887 && let Some(name) = current.child_by_field_name("name")
8888 {
8889 let mut components = enclosing_namespace_components(current, source);
8890 if append_cpp_name_components(name, source, &mut components).is_some() {
8891 envelopes.push(OrphanedNamespaceEnvelope {
8892 body_end: body.end_byte(),
8893 components,
8894 class_names: HashSet::default(),
8895 error_marked: true,
8896 });
8897 }
8898 }
8899 if !current.has_error() {
8900 continue;
8901 }
8902 let mut cursor = current.walk();
8903 stack.extend(
8904 current
8905 .named_children(&mut cursor)
8906 .filter(|child| child.has_error()),
8907 );
8908 }
8909 envelopes
8910}
8911
8912fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
8913 if matches!(
8914 node.kind(),
8915 "class_specifier" | "struct_specifier" | "union_specifier"
8916 ) {
8917 let body = node.child_by_field_name("body")?;
8918 let name = node.child_by_field_name("name")?;
8919 return (!name.is_missing() && !body.is_missing())
8920 .then(|| node_text(name, source).to_string());
8921 }
8922 if node.kind() != "ERROR" {
8923 return None;
8924 }
8925
8926 for index in 0..node.child_count() {
8932 let Some(keyword) = node.child(index) else {
8933 continue;
8934 };
8935 if !matches!(keyword.kind(), "class" | "struct" | "union") {
8936 continue;
8937 }
8938 let mut name = None;
8939 for next_index in (index + 1)..node.child_count() {
8940 let Some(next) = node.child(next_index) else {
8941 continue;
8942 };
8943 if next.kind() == ";" {
8944 break;
8945 }
8946 if next.kind() == "{" {
8947 return name
8948 .filter(|name_node: &Node<'_>| !name_node.is_missing())
8949 .map(|name_node| node_text(name_node, source).to_string());
8950 }
8951 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
8952 name = Some(next);
8953 }
8954 }
8955 }
8956 None
8957}
8958
8959fn recovered_orphaned_namespace_components(
8960 node: Node<'_>,
8961 source: &str,
8962 envelopes: &[OrphanedNamespaceEnvelope],
8963) -> Option<Vec<String>> {
8964 let owner_name = orphaned_using_owner_name(node, source)?;
8965 envelopes
8966 .iter()
8967 .filter(|envelope| {
8968 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
8969 })
8970 .max_by_key(|envelope| envelope.body_end)
8971 .map(|envelope| envelope.components.clone())
8972}
8973
8974fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
8975 let function = std::iter::successors(node.parent(), |current| current.parent())
8976 .find(|current| current.kind() == "function_definition")?;
8977 let owner = function_definition_owner_lookup_node(function)?;
8978 let scope = owner.child_by_field_name("scope")?;
8979 let mut components = Vec::new();
8980 append_cpp_name_components(scope, source, &mut components)?;
8981 if components.len() != 1 {
8988 return None;
8989 }
8990 components.pop()
8991}
8992
8993fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
8994 let mut project = ProjectUsingIndex::default();
8995 for source_file in visibility.all_visible_source_files() {
8996 let source_index = visibility.cpp().source_using_index(&source_file);
9000 for (name, bindings) in &source_index.ordinary_by_name {
9001 project
9002 .ordinary_by_name
9003 .entry(name.clone())
9004 .or_default()
9005 .extend(bindings.iter().cloned());
9006 }
9007 project
9008 .directives
9009 .extend(source_index.directives.iter().cloned());
9010 }
9011 project
9012}
9013
9014fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
9015 visibility.project_using_index(|| build_project_using_index(visibility))
9016}
9017
9018pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
9024 let _ = project_using_index(visibility);
9025}
9026
9027fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
9028 let (components, global) = match &binding.target {
9029 EffectiveUsingTarget::Ordinary {
9030 target_components,
9031 global,
9032 ..
9033 } => (target_components, *global),
9034 EffectiveUsingTarget::Namespace {
9035 namespace_components,
9036 global,
9037 } => (namespace_components, *global),
9038 };
9039 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
9040}
9041
9042fn using_binding_target_components_for_name(
9043 binding: &OrdinaryTypeImport,
9044 project: &ProjectUsingIndex,
9045 visibility: &VisibilityIndex<'_>,
9046 file: &ProjectFile,
9047 name: &str,
9048) -> Option<Vec<String>> {
9049 let cpp_source = CppGraphSource::from_source(visibility.cpp());
9052 let visible_candidates = visibility
9053 .visible_identifier_candidates(file, name)
9054 .filter(|candidate| {
9055 candidate.is_class()
9056 || is_type_alias(candidate)
9057 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
9058 })
9059 .collect::<Vec<_>>();
9060 if visible_candidates.is_empty() {
9061 return None;
9062 }
9063 match &binding.target {
9064 EffectiveUsingTarget::Ordinary {
9065 name: imported_name,
9066 ..
9067 } if imported_name == name => {
9068 effective_using_target_tiers(binding)
9069 .into_iter()
9070 .find(|qualified| {
9071 let qualified_name = qualified.join("::");
9072 visible_candidates
9073 .iter()
9074 .any(|candidate| cpp_name_for(candidate) == qualified_name)
9075 })
9076 }
9077 EffectiveUsingTarget::Namespace { .. } => {
9078 visibility.note_using_namespace_lookup_for_test();
9079 let target_tiers = effective_using_target_tiers(binding);
9080 let resolved = target_tiers
9081 .iter()
9082 .find(|namespace_components| {
9083 let namespace = namespace_components.join("::");
9084 visible_candidates.iter().any(|candidate| {
9085 visibility.note_using_name_candidate_inspection_for_test();
9086 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
9087 candidate_namespace == namespace
9088 || candidate_namespace.starts_with(&format!("{namespace}::"))
9089 })
9090 }) || project.directives.iter().any(|candidate| {
9091 candidate.namespace_scope.as_deref()
9092 == Some(namespace_components.as_slice())
9093 }) || project
9094 .ordinary_by_name
9095 .values()
9096 .flatten()
9097 .any(|candidate| {
9098 candidate.namespace_scope.as_deref()
9099 == Some(namespace_components.as_slice())
9100 })
9101 })
9102 .cloned();
9103 resolved.or_else(|| {
9104 (target_tiers.len() == 1)
9110 .then(|| target_tiers.into_iter().next())
9111 .flatten()
9112 })
9113 }
9114 EffectiveUsingTarget::Ordinary { .. } => None,
9115 }
9116}
9117
9118fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
9119 let start = activation.checked_sub(1)?;
9120 let mut node = root.descendant_for_byte_range(start, activation)?;
9121 while node.kind() != "preproc_include" {
9122 node = node.parent()?;
9123 }
9124 Some(node)
9125}
9126
9127fn project_using_bindings(
9128 binding: OrdinaryTypeImport,
9129 visibility: &VisibilityIndex<'_>,
9130 file: &ProjectFile,
9131 root: Node<'_>,
9132 source: &str,
9133) -> Vec<OrdinaryTypeImport> {
9134 if binding.source == *file {
9135 return vec![binding];
9136 }
9137 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
9138 return Vec::new();
9139 }
9140 visibility.note_using_donor_activation_for_test();
9141 let Some(prepared) = visibility.cpp().prepared_syntax(file) else {
9142 return Vec::new();
9143 };
9144 let projections = visibility
9145 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
9146 .map_or_else(
9147 || {
9148 visibility.conditional_include_projections_for_source(
9149 file,
9150 prepared.as_ref(),
9151 &binding.source,
9152 )
9153 },
9154 |activation_byte| {
9155 Arc::from([ConditionalIncludeProjection {
9156 activation_byte,
9157 required_guards: HashSet::default(),
9158 }])
9159 },
9160 );
9161 projections
9162 .iter()
9163 .cloned()
9164 .filter_map(|projection| {
9165 let required_guards =
9166 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
9167 let mut projected = binding.clone();
9168 projected.required_guards = required_guards;
9169 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
9170 })
9171 .collect()
9172}
9173
9174fn project_using_binding_at_activation(
9175 mut binding: OrdinaryTypeImport,
9176 activation: usize,
9177 root: Node<'_>,
9178 source: &str,
9179) -> Option<OrdinaryTypeImport> {
9180 let include = include_node_for_activation(root, activation)?;
9181 let include_namespace = enclosing_namespace_components(include, source);
9182 let mut declaration_namespace = include_namespace.clone();
9183 declaration_namespace.extend(binding.declaration_namespace);
9184 binding.declaration_namespace = declaration_namespace;
9185 binding.declaration_byte = activation;
9186 if let Some(prefix) = using_named_scope(include, source) {
9187 let mut projected = prefix;
9188 projected.extend(binding.namespace_scope.take().unwrap_or_default());
9189 binding.scope_depth = projected.len();
9190 binding.block_scope = false;
9191 binding.lexical_depth = projected.len();
9192 binding.namespace_scope = Some(projected);
9193 binding.scope_start = 0;
9194 binding.scope_end = usize::MAX;
9195 Some(binding)
9196 } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
9197 binding.namespace_scope = None;
9198 binding.scope_start = start;
9199 binding.scope_end = end;
9200 binding.scope_depth = depth;
9201 binding.block_scope = block_scope;
9202 binding.lexical_depth = include_namespace.len();
9203 Some(binding)
9204 } else {
9205 None
9206 }
9207}
9208
9209pub fn effective_using_bindings_for_name(
9215 visibility: &VisibilityIndex<'_>,
9216 imports: &OrdinaryTypeImportCell,
9217 file: &ProjectFile,
9218 node: Node<'_>,
9219 source: &str,
9220 name: &str,
9221) -> Arc<[OrdinaryTypeImport]> {
9222 imports
9223 .projection_cell(name)
9224 .get_or_init(|| {
9225 let project = project_using_index(visibility);
9226 let name_bindings = project.ordinary_by_name.get(name);
9227 if name_bindings.is_none() && project.directives.is_empty() {
9228 return Arc::from(Vec::new());
9229 }
9230 let root = root_node(node);
9231 let mut projected = Vec::new();
9232 for binding in name_bindings
9233 .into_iter()
9234 .flatten()
9235 .chain(project.directives.iter())
9236 {
9237 if !visibility.source_is_visible(file, &binding.source) {
9238 continue;
9239 }
9240 let target_components = using_binding_target_components_for_name(
9241 binding, project, visibility, file, name,
9242 )
9243 .or_else(|| match &binding.target {
9244 EffectiveUsingTarget::Ordinary {
9245 name: imported_name,
9246 target_components,
9247 ..
9248 } if imported_name == name => Some(target_components.clone()),
9249 EffectiveUsingTarget::Ordinary { .. }
9250 | EffectiveUsingTarget::Namespace { .. } => None,
9251 });
9252 let Some(target_components) = target_components else {
9253 continue;
9254 };
9255 let mut binding = binding.clone();
9256 binding.resolved_target_components = Some(target_components);
9257 projected.extend(project_using_bindings(
9258 binding, visibility, file, root, source,
9259 ));
9260 }
9261 Arc::from(projected)
9262 })
9263 .clone()
9264}
9265
9266pub fn initialized_ordinary_type_imports(
9267 root: Node<'_>,
9268 analyzer: &CppGraphSource<'_>,
9269 visibility: &VisibilityIndex<'_>,
9270 file: &ProjectFile,
9271 source: &str,
9272) -> OrdinaryTypeImportCell {
9273 let cell = visibility.ordinary_type_import_cell(file);
9274 let _ = (root, analyzer, source);
9275 cell
9276}
9277
9278fn root_node(mut node: Node<'_>) -> Node<'_> {
9279 while let Some(parent) = node.parent() {
9280 node = parent;
9281 }
9282 node
9283}
9284
9285fn effective_using_binding_active(
9290 binding: &OrdinaryTypeImport,
9291 node: Node<'_>,
9292 lexical_scope: &[String],
9293 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9294 visibility: &VisibilityIndex<'_>,
9295 file: &ProjectFile,
9296) -> bool {
9297 effective_using_binding_guards_active(
9298 binding,
9299 node.start_byte(),
9300 reference_guards,
9301 visibility,
9302 file,
9303 ) && binding.namespace_scope.as_ref().map_or_else(
9304 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
9305 |namespace| lexical_scope.starts_with(namespace),
9306 )
9307}
9308
9309fn effective_using_binding_guards_active(
9310 binding: &OrdinaryTypeImport,
9311 reference_byte: usize,
9312 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9313 visibility: &VisibilityIndex<'_>,
9314 file: &ProjectFile,
9315) -> bool {
9316 binding.declaration_byte <= reference_byte
9317 && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
9318 && visibility.preprocessor_guards_stable_between(
9319 file,
9320 0,
9321 reference_byte,
9322 &binding.required_guards,
9323 )
9324}
9325
9326fn effective_using_binding_guards_compatible(
9327 binding: &OrdinaryTypeImport,
9328 reference_byte: usize,
9329 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9330 visibility: &VisibilityIndex<'_>,
9331 file: &ProjectFile,
9332) -> bool {
9333 binding.source != *file
9334 && !binding.required_guards.is_empty()
9335 && binding.declaration_byte <= reference_byte
9336 && reference_guards.is_some_and(|active| {
9337 !binding.required_guards.is_subset(active)
9338 && merge_preprocessor_guards(&binding.required_guards, active).is_some()
9339 })
9340 && visibility.preprocessor_guards_stable_between(
9341 file,
9342 0,
9343 reference_byte,
9344 &binding.required_guards,
9345 )
9346}
9347
9348fn binding_type_candidates(
9349 binding: &OrdinaryTypeImport,
9350 active_bindings: &[&OrdinaryTypeImport],
9351 visibility: &VisibilityIndex<'_>,
9352 file: &ProjectFile,
9353 name: &str,
9354 direct_target: Option<&CodeUnit>,
9355 reference_byte: usize,
9356) -> Vec<(CodeUnit, Vec<String>)> {
9357 let Some(qualified) = binding.resolved_target_components.clone() else {
9358 return Vec::new();
9359 };
9360 let mut targets = Vec::new();
9361 match binding.target {
9362 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
9363 EffectiveUsingTarget::Namespace { .. } => {
9364 let mut stack = vec![qualified];
9365 let mut visited = HashSet::default();
9366 while let Some(namespace) = stack.pop() {
9367 if !visited.insert(namespace.clone()) {
9368 continue;
9369 }
9370 let mut target = namespace.clone();
9371 target.push(name.to_string());
9372 targets.push(target);
9373 stack.extend(active_bindings.iter().filter_map(|candidate| {
9374 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
9375 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
9376 .then(|| candidate.resolved_target_components.clone())
9377 .flatten()
9378 }));
9379 }
9380 }
9381 }
9382 targets
9383 .into_iter()
9384 .flat_map(|target| {
9385 let mut candidates = visibility
9386 .visible_identifier_candidates(file, name)
9387 .filter(|candidate| {
9388 (candidate.is_class() || is_type_alias(candidate))
9389 && macro_expanded_cpp_name_components(
9390 visibility,
9391 file,
9392 candidate,
9393 reference_byte,
9394 ) == target
9395 })
9396 .cloned()
9397 .collect::<Vec<_>>();
9398 if candidates.is_empty()
9399 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9400 && let Some(target_unit) = direct_target
9401 {
9402 let expanded_target_name = macro_expanded_cpp_name_components(
9403 visibility,
9404 file,
9405 target_unit,
9406 reference_byte,
9407 );
9408 if (target_unit.is_class() || is_type_alias(target_unit))
9409 && expanded_target_name == target
9410 && visibility.external_type_candidate_visible_at(
9411 file,
9412 target_unit,
9413 reference_byte,
9414 )
9415 {
9416 candidates.push(target_unit.clone());
9417 }
9418 }
9419 if candidates.is_empty()
9420 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9421 && let Some(target_unit) = direct_target
9422 {
9423 let visible_types = visibility
9424 .visible_identifier_candidates(file, name)
9425 .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
9426 .collect::<Vec<_>>();
9427 let uniquely_names_target = !visible_types.is_empty()
9428 && visible_types
9429 .iter()
9430 .all(|candidate| same_visible_symbol(candidate, target_unit));
9431 if uniquely_names_target {
9432 candidates.extend(visible_types.into_iter().cloned());
9433 }
9434 }
9435 candidates
9436 .into_iter()
9437 .map(move |candidate| (candidate, target.clone()))
9438 })
9439 .collect()
9440}
9441
9442fn macro_expanded_cpp_name_components(
9443 visibility: &VisibilityIndex<'_>,
9444 file: &ProjectFile,
9445 unit: &CodeUnit,
9446 reference_byte: usize,
9447) -> Vec<String> {
9448 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9449 brokk_bifrost_core::analyzer::Language::Cpp,
9450 &cpp_name_for(unit),
9451 )
9452 .into_iter()
9453 .flat_map(|component| {
9454 let Some(replacement) =
9455 visibility.object_macro_replacement_at(file, &component, reference_byte)
9456 else {
9457 return vec![component];
9458 };
9459 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9460 brokk_bifrost_core::analyzer::Language::Cpp,
9461 &replacement,
9462 );
9463 if expanded.is_empty() {
9464 vec![component]
9465 } else {
9466 expanded
9467 }
9468 })
9469 .collect()
9470}
9471
9472#[allow(clippy::too_many_arguments)]
9473fn resolved_type_import(
9474 candidates: Vec<(CodeUnit, Vec<String>)>,
9475 lexical_depth: usize,
9476 is_direct: bool,
9477 analyzer: &CppGraphSource<'_>,
9478 visibility: &VisibilityIndex<'_>,
9479 file: &ProjectFile,
9480 direct_target: Option<&CodeUnit>,
9481) -> OrdinaryTypeImportResolution {
9482 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
9483 for candidate in candidates {
9484 if !logical
9485 .iter()
9486 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
9487 {
9488 logical.push(candidate);
9489 }
9490 }
9491 let selected = match logical.as_slice() {
9492 [] => return OrdinaryTypeImportResolution::Missing,
9493 [only] => only,
9494 several => {
9499 let units = several
9500 .iter()
9501 .map(|(unit, _)| unit)
9502 .collect::<Vec<&CodeUnit>>();
9503 let Some(spelling) = direct_target.and_then(|target| {
9504 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
9505 }) else {
9506 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
9507 };
9508 several
9509 .iter()
9510 .find(|(unit, _)| same_symbol(unit, spelling))
9511 .expect("the selected spelling is one of the imported candidates")
9512 }
9513 };
9514 OrdinaryTypeImportResolution::Resolved {
9515 target: selected.0.clone(),
9516 target_components: selected.1.clone(),
9517 lexical_depth,
9518 is_direct,
9519 }
9520}
9521
9522#[allow(clippy::too_many_arguments)]
9523fn ordinary_type_import_resolution(
9524 node: Node<'_>,
9525 components: &[String],
9526 global: bool,
9527 analyzer: &CppGraphSource<'_>,
9528 visibility: &VisibilityIndex<'_>,
9529 imports: &OrdinaryTypeImportCell,
9530 file: &ProjectFile,
9531 source: &str,
9532 lexical_scope: &[String],
9533 direct_target: Option<&CodeUnit>,
9534) -> OrdinaryTypeImportResolution {
9535 if global || components.len() != 1 {
9536 return OrdinaryTypeImportResolution::Missing;
9537 }
9538 let name = &components[0];
9539 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
9540 if bindings.is_empty() {
9544 return OrdinaryTypeImportResolution::Missing;
9545 }
9546 let reference_guards = preprocessor_guard_environment(node, source);
9547 let active = bindings
9548 .iter()
9549 .filter(|binding| {
9550 effective_using_binding_active(
9551 binding,
9552 node,
9553 lexical_scope,
9554 reference_guards.as_ref(),
9555 visibility,
9556 file,
9557 )
9558 })
9559 .collect::<Vec<_>>();
9560 let transitive = bindings
9561 .iter()
9562 .filter(|binding| {
9563 effective_using_binding_guards_active(
9564 binding,
9565 node.start_byte(),
9566 reference_guards.as_ref(),
9567 visibility,
9568 file,
9569 ) && (binding.namespace_scope.is_some()
9570 || (binding.scope_start <= node.start_byte()
9571 && node.end_byte() <= binding.scope_end))
9572 })
9573 .collect::<Vec<_>>();
9574 ordinary_type_import_resolution_for_bindings(
9575 node,
9576 name,
9577 analyzer,
9578 visibility,
9579 file,
9580 lexical_scope,
9581 direct_target,
9582 &active,
9583 &transitive,
9584 )
9585}
9586
9587#[allow(clippy::too_many_arguments)]
9588fn compatible_foreign_type_import_resolution(
9589 node: Node<'_>,
9590 components: &[String],
9591 global: bool,
9592 analyzer: &CppGraphSource<'_>,
9593 visibility: &VisibilityIndex<'_>,
9594 imports: &OrdinaryTypeImportCell,
9595 file: &ProjectFile,
9596 source: &str,
9597 lexical_scope: &[String],
9598 direct_target: &CodeUnit,
9599) -> OrdinaryTypeImportResolution {
9600 if global || components.len() != 1 {
9601 return OrdinaryTypeImportResolution::Missing;
9602 }
9603 let name = &components[0];
9604 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
9605 if bindings.is_empty() {
9606 return OrdinaryTypeImportResolution::Missing;
9607 }
9608 let reference_guards = preprocessor_guard_environment(node, source);
9609 let compatible = bindings
9610 .iter()
9611 .filter(|binding| {
9612 effective_using_binding_guards_compatible(
9613 binding,
9614 node.start_byte(),
9615 reference_guards.as_ref(),
9616 visibility,
9617 file,
9618 ) && binding.namespace_scope.as_ref().map_or_else(
9619 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
9620 |namespace| lexical_scope.starts_with(namespace),
9621 )
9622 })
9623 .collect::<Vec<_>>();
9624 let transitive = bindings
9625 .iter()
9626 .filter(|binding| {
9627 effective_using_binding_guards_compatible(
9628 binding,
9629 node.start_byte(),
9630 reference_guards.as_ref(),
9631 visibility,
9632 file,
9633 ) && (binding.namespace_scope.is_some()
9634 || (binding.scope_start <= node.start_byte()
9635 && node.end_byte() <= binding.scope_end))
9636 })
9637 .collect::<Vec<_>>();
9638 ordinary_type_import_resolution_for_bindings(
9639 node,
9640 name,
9641 analyzer,
9642 visibility,
9643 file,
9644 lexical_scope,
9645 Some(direct_target),
9646 &compatible,
9647 &transitive,
9648 )
9649}
9650
9651#[allow(clippy::too_many_arguments)]
9652fn ordinary_type_import_resolution_for_bindings(
9653 node: Node<'_>,
9654 name: &str,
9655 analyzer: &CppGraphSource<'_>,
9656 visibility: &VisibilityIndex<'_>,
9657 file: &ProjectFile,
9658 lexical_scope: &[String],
9659 direct_target: Option<&CodeUnit>,
9660 active: &[&OrdinaryTypeImport],
9661 transitive: &[&OrdinaryTypeImport],
9662) -> OrdinaryTypeImportResolution {
9663 let mut concrete_depths = active
9664 .iter()
9665 .filter(|binding| binding.namespace_scope.is_none())
9666 .map(|binding| binding.scope_depth)
9667 .collect::<Vec<_>>();
9668 concrete_depths.sort_unstable();
9669 concrete_depths.dedup();
9670 for depth in concrete_depths.into_iter().rev() {
9671 let at_tier = active
9672 .iter()
9673 .copied()
9674 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
9675 let direct = at_tier
9676 .clone()
9677 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
9678 .flat_map(|binding| {
9679 binding_type_candidates(
9680 binding,
9681 transitive,
9682 visibility,
9683 file,
9684 name,
9685 direct_target,
9686 node.start_byte(),
9687 )
9688 })
9689 .collect::<Vec<_>>();
9690 if !direct.is_empty() {
9691 return resolved_type_import(
9692 direct,
9693 lexical_scope.len(),
9694 true,
9695 analyzer,
9696 visibility,
9697 file,
9698 direct_target,
9699 );
9700 }
9701 let directives = at_tier
9702 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
9703 .flat_map(|binding| {
9704 binding_type_candidates(
9705 binding,
9706 transitive,
9707 visibility,
9708 file,
9709 name,
9710 direct_target,
9711 node.start_byte(),
9712 )
9713 })
9714 .collect::<Vec<_>>();
9715 if !directives.is_empty() {
9716 return resolved_type_import(
9717 directives,
9718 lexical_scope.len(),
9719 false,
9720 analyzer,
9721 visibility,
9722 file,
9723 direct_target,
9724 );
9725 }
9726 }
9727 for prefix_len in (0..=lexical_scope.len()).rev() {
9728 let tier = &lexical_scope[..prefix_len];
9729 let at_tier = active
9730 .iter()
9731 .copied()
9732 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
9733 let direct = at_tier
9734 .clone()
9735 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
9736 .flat_map(|binding| {
9737 binding_type_candidates(
9738 binding,
9739 transitive,
9740 visibility,
9741 file,
9742 name,
9743 direct_target,
9744 node.start_byte(),
9745 )
9746 })
9747 .collect::<Vec<_>>();
9748 if !direct.is_empty() {
9749 return resolved_type_import(
9750 direct,
9751 prefix_len,
9752 true,
9753 analyzer,
9754 visibility,
9755 file,
9756 direct_target,
9757 );
9758 }
9759 let directives = at_tier
9760 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
9761 .flat_map(|binding| {
9762 binding_type_candidates(
9763 binding,
9764 transitive,
9765 visibility,
9766 file,
9767 name,
9768 direct_target,
9769 node.start_byte(),
9770 )
9771 })
9772 .collect::<Vec<_>>();
9773 if !directives.is_empty() {
9774 return resolved_type_import(
9775 directives,
9776 prefix_len,
9777 false,
9778 analyzer,
9779 visibility,
9780 file,
9781 direct_target,
9782 );
9783 }
9784 }
9785 OrdinaryTypeImportResolution::Missing
9786}
9787
9788#[allow(clippy::too_many_arguments)]
9789pub fn resolve_type_components_lexically_at(
9790 node: Node<'_>,
9791 components: &[String],
9792 global: bool,
9793 analyzer: &CppGraphSource<'_>,
9794 visibility: &VisibilityIndex<'_>,
9795 ordinary_type_imports: &OrdinaryTypeImportCell,
9796 file: &ProjectFile,
9797 source: &str,
9798) -> LexicalTypeResolution {
9799 resolve_type_components_lexically_at_inner(
9800 node,
9801 components,
9802 global,
9803 analyzer,
9804 visibility,
9805 ordinary_type_imports,
9806 file,
9807 source,
9808 None,
9809 false,
9810 false,
9811 None,
9812 )
9813}
9814
9815#[allow(clippy::too_many_arguments)]
9816fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
9817 node: Node<'_>,
9818 components: &[String],
9819 global: bool,
9820 analyzer: &CppGraphSource<'_>,
9821 visibility: &VisibilityIndex<'_>,
9822 ordinary_type_imports: &OrdinaryTypeImportCell,
9823 file: &ProjectFile,
9824 source: &str,
9825 scope_cache: Option<&LexicalScopeCache>,
9826) -> LexicalTypeResolution {
9827 resolve_type_components_lexically_at_inner(
9828 node,
9829 components,
9830 global,
9831 analyzer,
9832 visibility,
9833 ordinary_type_imports,
9834 file,
9835 source,
9836 None,
9837 false,
9838 true,
9839 scope_cache,
9840 )
9841}
9842
9843#[allow(clippy::too_many_arguments)]
9844fn resolve_type_components_lexically_at_for_target_with_scope_cache(
9845 node: Node<'_>,
9846 components: &[String],
9847 global: bool,
9848 analyzer: &CppGraphSource<'_>,
9849 visibility: &VisibilityIndex<'_>,
9850 ordinary_type_imports: &OrdinaryTypeImportCell,
9851 file: &ProjectFile,
9852 source: &str,
9853 target: &CodeUnit,
9854 apply_structured_prefilter: bool,
9855 scope_cache: Option<&LexicalScopeCache>,
9856) -> LexicalTypeResolution {
9857 resolve_type_components_lexically_at_inner(
9858 node,
9859 components,
9860 global,
9861 analyzer,
9862 visibility,
9863 ordinary_type_imports,
9864 file,
9865 source,
9866 Some(target),
9867 apply_structured_prefilter,
9868 false,
9869 scope_cache,
9870 )
9871}
9872
9873#[allow(clippy::too_many_arguments)]
9874fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
9875 node: Node<'_>,
9876 components: &[String],
9877 global: bool,
9878 analyzer: &CppGraphSource<'_>,
9879 visibility: &VisibilityIndex<'_>,
9880 ordinary_type_imports: &OrdinaryTypeImportCell,
9881 file: &ProjectFile,
9882 source: &str,
9883 recovered_scope: &[String],
9884) -> LexicalTypeResolution {
9885 resolve_type_components_lexically_at_scoped(
9886 node,
9887 components,
9888 global,
9889 analyzer,
9890 visibility,
9891 ordinary_type_imports,
9892 file,
9893 source,
9894 None,
9895 false,
9896 true,
9897 recovered_scope.to_vec(),
9898 )
9899}
9900
9901#[allow(clippy::too_many_arguments)]
9902fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
9903 node: Node<'_>,
9904 components: &[String],
9905 global: bool,
9906 analyzer: &CppGraphSource<'_>,
9907 visibility: &VisibilityIndex<'_>,
9908 ordinary_type_imports: &OrdinaryTypeImportCell,
9909 file: &ProjectFile,
9910 source: &str,
9911 target: &CodeUnit,
9912 apply_structured_prefilter: bool,
9913 recovered_scope: &[String],
9914) -> LexicalTypeResolution {
9915 resolve_type_components_lexically_at_scoped(
9916 node,
9917 components,
9918 global,
9919 analyzer,
9920 visibility,
9921 ordinary_type_imports,
9922 file,
9923 source,
9924 Some(target),
9925 apply_structured_prefilter,
9926 false,
9927 recovered_scope.to_vec(),
9928 )
9929}
9930
9931#[allow(clippy::too_many_arguments)]
9932fn resolve_type_components_lexically_at_inner(
9933 node: Node<'_>,
9934 components: &[String],
9935 global: bool,
9936 analyzer: &CppGraphSource<'_>,
9937 visibility: &VisibilityIndex<'_>,
9938 ordinary_type_imports: &OrdinaryTypeImportCell,
9939 file: &ProjectFile,
9940 source: &str,
9941 direct_target: Option<&CodeUnit>,
9942 apply_structured_prefilter: bool,
9943 preserve_alias: bool,
9944 scope_cache: Option<&LexicalScopeCache>,
9945) -> LexicalTypeResolution {
9946 let lexical_scope = if global {
9947 Vec::new()
9948 } else {
9949 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
9950 node,
9951 analyzer,
9952 visibility,
9953 file,
9954 source,
9955 true,
9956 recovered_macro_decorated_declarator_type(node)
9957 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9958 scope_cache,
9959 ) {
9960 LexicalScopeResolution::Resolved(scope) => scope,
9961 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9962 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9963 }
9964 };
9965 resolve_type_components_lexically_at_scoped(
9966 node,
9967 components,
9968 global,
9969 analyzer,
9970 visibility,
9971 ordinary_type_imports,
9972 file,
9973 source,
9974 direct_target,
9975 apply_structured_prefilter,
9976 preserve_alias,
9977 lexical_scope,
9978 )
9979}
9980
9981#[allow(clippy::too_many_arguments)]
9982fn resolve_type_components_lexically_at_scoped(
9983 node: Node<'_>,
9984 components: &[String],
9985 global: bool,
9986 analyzer: &CppGraphSource<'_>,
9987 visibility: &VisibilityIndex<'_>,
9988 ordinary_type_imports: &OrdinaryTypeImportCell,
9989 file: &ProjectFile,
9990 source: &str,
9991 direct_target: Option<&CodeUnit>,
9992 apply_structured_prefilter: bool,
9993 preserve_alias: bool,
9994 mut lexical_scope: Vec<String>,
9995) -> LexicalTypeResolution {
9996 if apply_structured_prefilter
9997 && direct_target.is_some()
9998 && !preserve_alias
9999 && !global
10000 && components.len() == 1
10001 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
10002 {
10003 return LexicalTypeResolution::Missing;
10004 }
10005 if !global
10011 && components.len() == 1
10012 && recovered_macro_decorated_type_node(node).is_some()
10013 && let Some(resolution) = recovered_same_file_type_alias_resolution(
10014 node,
10015 components,
10016 analyzer,
10017 visibility,
10018 file,
10019 direct_target,
10020 &lexical_scope,
10021 )
10022 {
10023 return resolution;
10024 }
10025 if !global
10026 && components.len() == 1
10027 && let Some(target) = direct_target
10030 && lexical_scope
10031 .last()
10032 .is_none_or(|last| last != &components[0])
10033 && has_malformed_wrapper_function_definition_ancestor(node)
10040 && let Some(indexed_namespace) =
10041 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
10042 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
10043 {
10044 lexical_scope = indexed_namespace;
10049 }
10050 if apply_structured_prefilter
10051 && let Some(target) = direct_target
10052 && !preserve_alias
10053 && !visibility.structured_type_reference_may_resolve_to_target(
10054 analyzer,
10055 file,
10056 components,
10057 global,
10058 &lexical_scope,
10059 target,
10060 )
10061 {
10062 return LexicalTypeResolution::Missing;
10063 }
10064 let normal = if preserve_alias {
10065 visibility.resolve_type_components_lexically_for_forward(
10066 analyzer,
10067 file,
10068 components,
10069 global,
10070 &lexical_scope,
10071 )
10072 } else {
10073 direct_target.map_or_else(
10074 || {
10075 visibility.resolve_type_components_lexically(
10076 analyzer,
10077 file,
10078 components,
10079 global,
10080 &lexical_scope,
10081 )
10082 },
10083 |target| {
10084 visibility.resolve_type_components_lexically_for_target(
10085 analyzer,
10086 file,
10087 components,
10088 global,
10089 &lexical_scope,
10090 target,
10091 )
10092 },
10093 )
10094 };
10095 let normal = match normal {
10096 LexicalTypeResolution::Resolved { ref unit, .. }
10097 if !visibility
10098 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10099 {
10100 LexicalTypeResolution::Missing
10101 }
10102 resolution => resolution,
10103 };
10104 let normal_depth = match &normal {
10105 LexicalTypeResolution::Resolved { components, .. } => {
10106 Some(components.len().saturating_sub(1))
10107 }
10108 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
10109 };
10110 match ordinary_type_import_resolution(
10116 node,
10117 components,
10118 global,
10119 analyzer,
10120 visibility,
10121 ordinary_type_imports,
10122 file,
10123 source,
10124 &lexical_scope,
10125 direct_target,
10126 ) {
10127 OrdinaryTypeImportResolution::Missing => normal,
10128 OrdinaryTypeImportResolution::Resolved {
10129 lexical_depth,
10130 is_direct,
10131 ..
10132 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
10133 || normal_depth.is_some_and(|depth| {
10134 depth > lexical_depth || (!is_direct && depth == lexical_depth)
10135 }) =>
10136 {
10137 normal
10138 }
10139 OrdinaryTypeImportResolution::Resolved {
10140 target,
10141 target_components,
10142 ..
10143 } => visibility.resolve_imported_type_candidate(
10144 analyzer,
10145 file,
10146 &target,
10147 &target_components,
10148 direct_target,
10149 preserve_alias,
10150 ),
10151 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
10152 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
10153 {
10154 normal
10155 }
10156 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
10157 }
10158}
10159
10160fn recovered_same_file_type_alias_resolution(
10161 node: Node<'_>,
10162 components: &[String],
10163 analyzer: &CppGraphSource<'_>,
10164 visibility: &VisibilityIndex<'_>,
10165 file: &ProjectFile,
10166 direct_target: Option<&CodeUnit>,
10167 lexical_scope: &[String],
10168) -> Option<LexicalTypeResolution> {
10169 debug_assert_eq!(components.len(), 1);
10170 debug_assert!(recovered_macro_decorated_type_node(node).is_some());
10171 let alias_provider = analyzer.type_alias_provider()?;
10172 for qualified in lexical_component_tiers(components, false, lexical_scope) {
10173 let candidates = visibility
10174 .visible_identifier_candidates(file, &components[0])
10175 .filter(|candidate| {
10176 candidate.source() == file
10177 && canonical_cpp_scope_components(candidate) == qualified
10178 && visibility
10179 .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10180 })
10181 .collect::<Vec<_>>();
10182 if candidates.is_empty() {
10183 continue;
10184 }
10185 if candidates
10186 .iter()
10187 .any(|candidate| !alias_provider.is_type_alias(candidate))
10188 {
10189 return None;
10190 }
10191 let unit = if let Some(target) = direct_target {
10192 visibility.unique_type_candidate_preserving_target(
10193 analyzer,
10194 file,
10195 &candidates,
10196 target,
10197 )?
10198 } else {
10199 let first = candidates[0];
10200 if candidates
10201 .iter()
10202 .any(|candidate| !same_visible_symbol(candidate, first))
10203 {
10204 return None;
10205 }
10206 first.clone()
10207 };
10208 return Some(LexicalTypeResolution::Resolved {
10209 unit,
10210 components: qualified,
10211 candidates: candidates.into_iter().cloned().collect(),
10212 });
10213 }
10214 None
10215}
10216
10217fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10218 matches!(
10219 structured_owner_context_resolution(node, ctx),
10220 StructuredOwnerContextResolution::SelfTarget
10221 | StructuredOwnerContextResolution::InheritedTarget
10222 )
10223}
10224
10225fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10229 matches!(
10230 structured_owner_context_resolution(node, ctx),
10231 StructuredOwnerContextResolution::InheritedTarget
10232 )
10233}
10234
10235fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10236 matches!(
10237 structured_owner_context_resolution(node, ctx),
10238 StructuredOwnerContextResolution::NonTarget
10239 )
10240}
10241
10242fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10243 let Some(target_owner) = ctx.spec.owner.as_ref() else {
10244 return false;
10245 };
10246 let mut current = node.parent();
10247 while let Some(parent) = current {
10248 if parent.kind() == "function_definition" {
10249 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
10250 return false;
10251 };
10252 if let Some(owners) = out_of_line_member_definition_owner(
10253 &ctx.analyzer,
10254 ctx.visibility,
10255 ctx.file,
10256 ctx.source,
10257 owner_lookup,
10258 ) && let Some((_, owner)) = owners.innermost()
10259 {
10260 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
10261 }
10262 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
10263 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
10264 }
10265 return false;
10266 }
10267 current = parent.parent();
10268 }
10269 false
10270}
10271
10272#[derive(Clone, Copy)]
10273enum StructuredOwnerContextResolution {
10274 SelfTarget,
10277 InheritedTarget,
10282 NonTarget,
10283 Ambiguous,
10284 Missing,
10285}
10286
10287fn structured_owner_context_resolution(
10288 node: Node<'_>,
10289 ctx: &ScanCtx<'_>,
10290) -> StructuredOwnerContextResolution {
10291 let Some(target_owner) = ctx.spec.owner.as_ref() else {
10292 return StructuredOwnerContextResolution::Missing;
10293 };
10294 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
10295 return StructuredOwnerContextResolution::Missing;
10296 };
10297 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
10298 return StructuredOwnerContextResolution::SelfTarget;
10299 }
10300 let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
10303 match member_owner {
10304 EnclosingMemberOwnerResolution::Owner(owner)
10305 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
10306 {
10307 StructuredOwnerContextResolution::InheritedTarget
10308 }
10309 EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
10310 EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
10311 EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
10312 }
10313}
10314
10315fn cached_declaring_member_owner(
10316 receiver_owner: &CodeUnit,
10317 ctx: &ScanCtx<'_>,
10318) -> EnclosingMemberOwnerResolution {
10319 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
10320 return cached;
10321 }
10322 let resolved = resolve_declaring_member_owner(
10323 &ctx.analyzer,
10324 ctx.visibility,
10325 ctx.file,
10326 receiver_owner,
10327 &ctx.spec.member_name,
10328 );
10329 let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
10330 indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
10331 } else {
10332 resolved
10333 };
10334 ctx.member_owner_cache
10335 .borrow_mut()
10336 .insert(receiver_owner.clone(), resolved.clone());
10337 resolved
10338}
10339
10340fn indexed_declaring_owner_for_recovered_member(
10347 receiver_owner: &CodeUnit,
10348 ctx: &ScanCtx<'_>,
10349) -> EnclosingMemberOwnerResolution {
10350 let Some(spec_owner) = ctx.spec.owner.as_ref() else {
10351 return EnclosingMemberOwnerResolution::Missing;
10352 };
10353 if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
10354 return EnclosingMemberOwnerResolution::Missing;
10355 }
10356 let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
10357 return EnclosingMemberOwnerResolution::Missing;
10358 };
10359 let Some(receiver_owner) =
10360 ctx.visibility
10361 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
10362 else {
10363 return EnclosingMemberOwnerResolution::Ambiguous;
10364 };
10365 let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
10366 ctx.visibility
10367 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
10368 }) else {
10369 return EnclosingMemberOwnerResolution::Missing;
10370 };
10371
10372 let owner_declares_member = |owner: &CodeUnit| {
10373 if same_visible_symbol(owner, &target_owner) {
10374 return true;
10375 }
10376 let mut member_fq = owner.fq().clone();
10377 member_fq.push(
10378 ctx.spec
10379 .target
10380 .fq()
10381 .last()
10382 .expect("a method target has a terminal member segment"),
10383 );
10384 ctx.analyzer
10385 .definitions(&member_fq.display(segment_interner()))
10386 .any(|child| child.is_function())
10387 };
10388 if owner_declares_member(&receiver_owner) {
10389 return EnclosingMemberOwnerResolution::Owner(receiver_owner);
10390 }
10391
10392 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
10393 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10394 let mut declaring_owner = None;
10395 while let Some(raw_owner) = stack.pop() {
10396 let Some(owner) =
10397 ctx.visibility
10398 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
10399 else {
10400 return EnclosingMemberOwnerResolution::Ambiguous;
10401 };
10402 let propagated = propagated_counts.entry(owner.clone()).or_default();
10403 if *propagated == 2 {
10404 continue;
10405 }
10406 *propagated += 1;
10407 if owner_declares_member(&owner) {
10408 if declaring_owner.is_some() {
10409 return EnclosingMemberOwnerResolution::Ambiguous;
10410 }
10411 declaring_owner = Some(owner);
10412 continue;
10413 }
10414 stack.extend(hierarchy.get_direct_ancestors(&owner));
10415 }
10416 declaring_owner
10417 .map(EnclosingMemberOwnerResolution::Owner)
10418 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
10419}
10420
10421fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10422 if (has_recovered_class_shape_ancestor(node)
10427 || has_malformed_wrapper_function_definition_ancestor(node))
10428 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
10429 {
10430 return Some(owner);
10431 }
10432 let mut current = node.parent();
10433 while let Some(parent) = current {
10434 if parent.kind() == "function_definition" {
10435 let owner_lookup = function_definition_owner_lookup_node(parent);
10436 if let Some(owner_lookup) = owner_lookup
10437 && let Some(owners) = out_of_line_member_definition_owner(
10438 &ctx.analyzer,
10439 ctx.visibility,
10440 ctx.file,
10441 ctx.source,
10442 owner_lookup,
10443 )
10444 && let Some((_, owner)) = owners.innermost()
10445 {
10446 return Some(owner.clone());
10447 }
10448 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
10449 return Some(owner);
10450 }
10451 if let Some(owner) = enclosing_context(parent, ctx)
10452 .owner
10453 .filter(|owner| owner.is_class())
10454 {
10455 return Some(owner);
10456 }
10457 if let Some(owner_lookup) = owner_lookup
10458 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
10459 {
10460 return Some(owner);
10461 }
10462 break;
10463 }
10464 current = parent.parent();
10465 }
10466 enclosing_context(node, ctx)
10467 .owner
10468 .filter(|owner| owner.is_class())
10469}
10470
10471fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
10472 let target_owner = ctx.spec.owner.as_ref()?;
10473 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
10474 let owner_name = owner_components.last()?;
10475 let mut candidates = Vec::new();
10476 for candidate in ctx
10477 .visibility
10478 .visible_identifier_candidates(ctx.file, owner_name)
10479 .filter(|candidate| candidate.is_class())
10480 {
10481 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10482 brokk_bifrost_core::analyzer::Language::Cpp,
10483 &cpp_name_for(candidate),
10484 );
10485 if !components.ends_with(&owner_components)
10486 || candidates
10487 .iter()
10488 .any(|existing| same_logical_symbol(existing, candidate))
10489 {
10490 continue;
10491 }
10492 candidates.push(candidate.clone());
10493 }
10494 let [candidate] = candidates.as_slice() else {
10495 return None;
10496 };
10497 (same_logical_symbol(candidate, target_owner)
10498 && target_group_contains_owner_peer(candidate, ctx))
10499 .then(|| candidate.clone())
10500}