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