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 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 reference_guards = preprocessor_guard_environment(function, source);
2350 let active = block_bindings
2351 .into_iter()
2352 .filter(|binding| {
2353 effective_using_binding_active(
2354 binding,
2355 function,
2356 &lexical_scope,
2357 reference_guards.as_ref(),
2358 visibility,
2359 file,
2360 )
2361 })
2362 .collect::<Vec<_>>();
2363 let depth = active.iter().map(|binding| binding.scope_depth).max()?;
2364 let at_tier = active
2365 .into_iter()
2366 .filter(|binding| binding.scope_depth == depth)
2367 .collect::<Vec<_>>();
2368 let callable_candidates = at_tier
2369 .iter()
2370 .flat_map(|binding| {
2371 binding_free_function_candidates(
2372 binding,
2373 &[],
2374 analyzer,
2375 visibility,
2376 file,
2377 name,
2378 call.start_byte(),
2379 )
2380 })
2381 .collect::<Vec<_>>();
2382 if !callable_candidates.is_empty() {
2383 return Some(BlockUsingCallTargetResolution::Target(
2384 resolve_callable_candidates(
2385 callable_candidates,
2386 visibility.call_arity_evidence(file, call, source).exact(),
2387 call.start_byte(),
2388 analyzer,
2389 visibility,
2390 file,
2391 ),
2392 ));
2393 }
2394 let type_candidates = at_tier
2395 .iter()
2396 .flat_map(|binding| {
2397 binding_type_candidates(
2398 binding,
2399 &[],
2400 visibility,
2401 file,
2402 name,
2403 None,
2404 call.start_byte(),
2405 )
2406 })
2407 .collect::<Vec<_>>();
2408 if !type_candidates.is_empty() {
2409 return Some(BlockUsingCallTargetResolution::Target(
2410 resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
2411 ));
2412 }
2413
2414 let mut unindexed = Vec::new();
2415 for binding in at_tier {
2416 let Some(components) = binding.resolved_target_components.as_ref() else {
2417 continue;
2418 };
2419 if !unindexed.contains(components) {
2420 unindexed.push(components.clone());
2421 }
2422 }
2423 match unindexed.as_slice() {
2424 [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
2425 [] => None,
2426 _ => Some(BlockUsingCallTargetResolution::Ambiguous),
2427 }
2428}
2429
2430#[allow(clippy::too_many_arguments)]
2431pub fn resolve_bare_call_target(
2432 call: Node<'_>,
2433 function: Node<'_>,
2434 analyzer: &CppGraphSource<'_>,
2435 visibility: &VisibilityIndex<'_>,
2436 ordinary_type_imports: &OrdinaryTypeImportCell,
2437 file: &ProjectFile,
2438 source: &str,
2439) -> BareCallTargetResolution {
2440 if !matches!(function.kind(), "identifier" | "template_function") {
2441 return BareCallTargetResolution::Missing;
2442 }
2443 let terminal = function_terminal_node(function);
2444 let name = node_text(terminal, source);
2445 if name.is_empty() {
2446 return BareCallTargetResolution::Missing;
2447 }
2448 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
2449 let lexical_scope =
2450 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2451 LexicalScopeResolution::Resolved(scope) => scope,
2452 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
2453 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
2454 };
2455 let type_resolution = resolve_type_node_lexically(
2456 function,
2457 analyzer,
2458 visibility,
2459 ordinary_type_imports,
2460 file,
2461 source,
2462 );
2463 let type_components = match &type_resolution {
2464 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2465 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2466 };
2467 let direct_type_resolution = visibility.resolve_type_components_lexically(
2468 analyzer,
2469 file,
2470 &[name.to_string()],
2471 false,
2472 &lexical_scope,
2473 );
2474 let direct_type_components = match &direct_type_resolution {
2475 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2476 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2477 };
2478 let bindings = effective_using_bindings_for_name(
2479 visibility,
2480 ordinary_type_imports,
2481 file,
2482 function,
2483 source,
2484 name,
2485 );
2486 let function_guards = if bindings.is_empty() {
2490 None
2491 } else {
2492 preprocessor_guard_environment(function, source)
2493 };
2494 let active_bindings = bindings
2495 .iter()
2496 .filter(|binding| {
2497 effective_using_binding_active(
2498 binding,
2499 function,
2500 &lexical_scope,
2501 function_guards.as_ref(),
2502 visibility,
2503 file,
2504 )
2505 })
2506 .collect::<Vec<_>>();
2507 let transitive_bindings = bindings
2508 .iter()
2509 .filter(|binding| {
2510 binding.declaration_byte <= function.start_byte()
2511 && function_guards
2512 .as_ref()
2513 .is_some_and(|active| binding.required_guards.is_subset(active))
2514 && visibility.preprocessor_guards_stable_between(
2515 file,
2516 0,
2517 function.start_byte(),
2518 &binding.required_guards,
2519 )
2520 && (binding.namespace_scope.is_some()
2521 || (binding.scope_start <= function.start_byte()
2522 && function.end_byte() <= binding.scope_end))
2523 })
2524 .collect::<Vec<_>>();
2525 let mut concrete_depths = active_bindings
2526 .iter()
2527 .filter(|binding| binding.namespace_scope.is_none())
2528 .map(|binding| binding.scope_depth)
2529 .collect::<Vec<_>>();
2530 concrete_depths.sort_unstable();
2531 concrete_depths.dedup();
2532 for depth in concrete_depths.into_iter().rev() {
2533 let at_tier = active_bindings
2534 .iter()
2535 .copied()
2536 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
2537 let direct = at_tier
2538 .clone()
2539 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2540 .flat_map(|binding| {
2541 binding_free_function_candidates(
2542 binding,
2543 &transitive_bindings,
2544 analyzer,
2545 visibility,
2546 file,
2547 name,
2548 call.start_byte(),
2549 )
2550 })
2551 .collect::<Vec<_>>();
2552 if !direct.is_empty() {
2553 return resolve_callable_candidates(
2554 direct,
2555 call_arity,
2556 call.start_byte(),
2557 analyzer,
2558 visibility,
2559 file,
2560 );
2561 }
2562 let direct_types = at_tier
2563 .clone()
2564 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2565 .flat_map(|binding| {
2566 binding_type_candidates(
2567 binding,
2568 &transitive_bindings,
2569 visibility,
2570 file,
2571 name,
2572 None,
2573 call.start_byte(),
2574 )
2575 })
2576 .collect::<Vec<_>>();
2577 if !direct_types.is_empty() {
2578 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2583 }
2584 let directives = at_tier
2585 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2586 .flat_map(|binding| {
2587 binding_free_function_candidates(
2588 binding,
2589 &transitive_bindings,
2590 analyzer,
2591 visibility,
2592 file,
2593 name,
2594 call.start_byte(),
2595 )
2596 })
2597 .collect::<Vec<_>>();
2598 if !directives.is_empty() {
2599 return resolve_callable_candidates(
2600 directives,
2601 call_arity,
2602 call.start_byte(),
2603 analyzer,
2604 visibility,
2605 file,
2606 );
2607 }
2608 }
2609 for prefix_len in (0..=lexical_scope.len()).rev() {
2610 let mut qualified = lexical_scope[..prefix_len].to_vec();
2611 qualified.push(name.to_string());
2612 let same_name_resolves_to_type = direct_type_components
2613 .is_some_and(|components| components == qualified.as_slice())
2614 || type_components.is_some_and(|components| components == qualified.as_slice());
2615 let mut direct = visibility
2616 .visible_identifier_candidates(file, name)
2617 .filter(|candidate| {
2618 candidate.is_function()
2619 && type_owner_of(analyzer, candidate).is_none()
2620 && !(same_name_resolves_to_type
2621 && visibility.callable_is_constructor_declaration(analyzer, candidate))
2622 && cpp_name_for(candidate) == qualified.join("::")
2623 && visibility.declaration_visible_at(
2624 analyzer,
2625 file,
2626 candidate,
2627 call.start_byte(),
2628 )
2629 })
2630 .cloned()
2631 .collect::<Vec<_>>();
2632 let at_tier = active_bindings.iter().copied().filter(|binding| {
2633 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
2634 });
2635 direct.extend(
2636 at_tier
2637 .clone()
2638 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2639 .flat_map(|binding| {
2640 binding_free_function_candidates(
2641 binding,
2642 &transitive_bindings,
2643 analyzer,
2644 visibility,
2645 file,
2646 name,
2647 call.start_byte(),
2648 )
2649 }),
2650 );
2651 if !direct.is_empty() {
2652 return resolve_callable_candidates(
2653 direct,
2654 call_arity,
2655 call.start_byte(),
2656 analyzer,
2657 visibility,
2658 file,
2659 );
2660 }
2661 let mut direct_types = at_tier
2662 .clone()
2663 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2664 .flat_map(|binding| {
2665 binding_type_candidates(
2666 binding,
2667 &transitive_bindings,
2668 visibility,
2669 file,
2670 name,
2671 None,
2672 call.start_byte(),
2673 )
2674 })
2675 .collect::<Vec<_>>();
2676 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
2677 && let LexicalTypeResolution::Resolved {
2678 unit, components, ..
2679 } = &direct_type_resolution
2680 {
2681 direct_types.push((unit.clone(), components.clone()));
2682 }
2683 if !direct_types.is_empty() {
2684 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2689 }
2690 let directives = at_tier
2691 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2692 .flat_map(|binding| {
2693 binding_free_function_candidates(
2694 binding,
2695 &transitive_bindings,
2696 analyzer,
2697 visibility,
2698 file,
2699 name,
2700 call.start_byte(),
2701 )
2702 })
2703 .collect::<Vec<_>>();
2704 if !directives.is_empty() {
2705 return resolve_callable_candidates(
2706 directives,
2707 call_arity,
2708 call.start_byte(),
2709 analyzer,
2710 visibility,
2711 file,
2712 );
2713 }
2714 if type_components.is_some_and(|components| components == qualified.as_slice()) {
2715 return match type_resolution {
2719 LexicalTypeResolution::Resolved { unit, .. } => {
2720 BareCallTargetResolution::Type(unit)
2721 }
2722 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2723 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2724 };
2725 }
2726 }
2727 match type_resolution {
2736 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2737 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2738 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2739 }
2740}
2741
2742fn static_qualifier_type_scopes<'tree>(
2743 node: Node<'tree>,
2744 ctx: &ScanCtx<'_>,
2745) -> Option<Vec<Node<'tree>>> {
2746 if !matches!(
2747 node.kind(),
2748 "qualified_identifier" | "scoped_type_identifier"
2749 ) {
2750 return None;
2751 }
2752 debug_assert!(!is_nested_type_node(node));
2755 let qualified = qualified_owner_components(node, ctx.source)?;
2756 static_qualifier_type_scopes_for_components(node, qualified, ctx)
2757}
2758
2759fn static_qualifier_type_scopes_for_components<'tree>(
2760 node: Node<'tree>,
2761 qualified: QualifiedOwnerComponents<'tree>,
2762 ctx: &ScanCtx<'_>,
2763) -> Option<Vec<Node<'tree>>> {
2764 if !qualified.global
2765 && qualified.names.first().is_some_and(|name| {
2766 name == ctx.spec.target.identifier()
2767 && qualified
2768 .nodes
2769 .first()
2770 .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
2771 })
2772 {
2773 return None;
2774 }
2775 let mut matches = Vec::new();
2776 let mut inherited_injected_name_is_shadowed = false;
2777 for component_count in 1..=qualified.names.len() {
2778 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
2779 node,
2780 &qualified.names[..component_count],
2781 qualified.global,
2782 &ctx.analyzer,
2783 ctx.visibility,
2784 &ctx.ordinary_type_imports,
2785 ctx.file,
2786 ctx.source,
2787 &ctx.spec.target,
2788 false,
2789 Some(&ctx.lexical_scope_cache),
2790 );
2791 match resolution {
2792 LexicalTypeResolution::Resolved {
2793 unit, candidates, ..
2794 } if (!ctx
2795 .analyzer
2796 .type_alias_provider()
2797 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2798 || ctx.visibility.external_type_candidate_visible_in_context(
2799 &ctx.analyzer,
2800 ctx.file,
2801 &ctx.spec.target,
2802 node,
2803 ))
2804 && (same_visible_symbol(&unit, &ctx.spec.target)
2805 || candidates
2806 .iter()
2807 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2808 && target_alias_candidates_visible(&candidates, node, ctx) =>
2809 {
2810 let matched =
2811 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
2812 if !template_type_component_preserves_target(matched, &candidates, ctx) {
2813 continue;
2814 }
2815 if !matches.iter().any(|existing: &Node<'_>| {
2816 existing.start_byte() == matched.start_byte()
2817 && existing.end_byte() == matched.end_byte()
2818 }) {
2819 matches.push(matched);
2820 }
2821 }
2822 LexicalTypeResolution::Ambiguous => {
2828 return (!inherited_injected_name_is_shadowed)
2829 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
2830 .flatten()
2831 .map(|scope| vec![scope])
2832 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
2833 }
2834 LexicalTypeResolution::Resolved { .. } => {
2835 if let Some(matched) =
2836 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
2837 {
2838 matches.push(matched);
2839 }
2840 inherited_injected_name_is_shadowed |= component_count == 1;
2841 }
2842 LexicalTypeResolution::Missing => {
2843 if let Some(matched) =
2844 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
2845 {
2846 matches.push(matched);
2847 }
2848 }
2849 }
2850 }
2851 if matches.is_empty() {
2852 (!inherited_injected_name_is_shadowed)
2853 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
2854 .flatten()
2855 .map(|scope| vec![scope])
2856 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
2857 } else {
2858 Some(matches)
2859 }
2860}
2861
2862fn target_guided_unproven_qualified_call_owner_scope<'tree>(
2865 node: Node<'tree>,
2866 ctx: &ScanCtx<'_>,
2867) -> Option<Node<'tree>> {
2868 let target = physically_visible_type_target(ctx)?;
2869 if !target.is_class() {
2870 return None;
2871 }
2872 let qualified = qualified_owner_components(node, ctx.source)?;
2873 let lexical_scope = match enclosing_lexical_scope_components(
2874 node,
2875 &ctx.analyzer,
2876 ctx.visibility,
2877 ctx.file,
2878 ctx.source,
2879 ) {
2880 LexicalScopeResolution::Resolved(scope) => scope,
2881 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
2882 enclosing_namespace_components(node, ctx.source)
2883 }
2884 };
2885 let LexicalTypeResolution::Resolved {
2886 unit, candidates, ..
2887 } = ctx.visibility.resolve_type_components_lexically_for_target(
2888 &ctx.analyzer,
2889 ctx.file,
2890 &qualified.names,
2891 qualified.global,
2892 &lexical_scope,
2893 target,
2894 )
2895 else {
2896 return None;
2897 };
2898 (same_visible_symbol(&unit, target)
2899 || candidates
2900 .iter()
2901 .any(|candidate| same_visible_symbol(candidate, target)))
2902 .then(|| qualified.nodes.last().copied())
2903 .flatten()
2904}
2905
2906fn target_guided_nested_alias_type_scope<'tree>(
2912 node: Node<'tree>,
2913 qualified: &QualifiedOwnerComponents<'tree>,
2914 component_count: usize,
2915 ctx: &ScanCtx<'_>,
2916) -> Option<Node<'tree>> {
2917 if component_count < 2 {
2918 return None;
2919 }
2920 let (owner_components, member_name) =
2921 qualified.names[..component_count].split_at(component_count - 1);
2922 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
2923 node,
2924 owner_components,
2925 qualified.global,
2926 &ctx.analyzer,
2927 ctx.visibility,
2928 &ctx.ordinary_type_imports,
2929 ctx.file,
2930 ctx.source,
2931 ) else {
2932 return None;
2933 };
2934 let member_name = member_name.first()?;
2935 let alias_provider = ctx.analyzer.type_alias_provider()?;
2936 ctx.visibility
2937 .visible_members_for_owner_name(ctx.file, &owner, member_name)
2938 .into_iter()
2939 .filter(|member| alias_provider.is_type_alias(member))
2940 .find(|member| {
2941 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
2942 &ctx.analyzer,
2943 ctx.file,
2944 member,
2945 node,
2946 ) || ctx
2947 .visibility
2948 .external_type_candidate_guard_compatible_in_context(
2949 &ctx.analyzer,
2950 ctx.file,
2951 member,
2952 node,
2953 );
2954 if !member_visible {
2955 return false;
2956 }
2957 same_visible_symbol(member, &ctx.spec.target)
2958 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
2959 })
2960 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
2961}
2962
2963fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
2964 if ctx.visibility.structured_class_alias_resolves_to_target(
2965 &ctx.analyzer,
2966 ctx.file,
2967 candidate,
2968 &ctx.spec.target,
2969 ) {
2970 return ctx.spec.target.clone();
2971 }
2972 let structured = ctx
2973 .visibility
2974 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
2975 if let Some(canonical) = structured
2976 .as_ref()
2977 .filter(|canonical| !same_visible_symbol(canonical, candidate))
2978 {
2979 return canonical.clone();
2980 }
2981 structured.unwrap_or_else(|| candidate.clone())
2982}
2983
2984fn target_guided_dependent_alias_qualifier_scope<'tree>(
2988 node: Node<'tree>,
2989 ctx: &ScanCtx<'_>,
2990) -> Option<Node<'tree>> {
2991 if !matches!(
2992 node.kind(),
2993 "qualified_identifier" | "scoped_type_identifier"
2994 ) {
2995 return None;
2996 }
2997 let target = physically_visible_type_target(ctx)?;
2998 let alias_provider = ctx.analyzer.type_alias_provider()?;
2999 if !target.is_class() || alias_provider.is_type_alias(target) {
3000 return None;
3001 }
3002 let nodes = cpp_name_component_nodes(node)?;
3003 let names = nodes
3004 .iter()
3005 .map(|component| node_text(*component, ctx.source).to_string())
3006 .collect::<Vec<_>>();
3007 let global = is_globally_qualified_cpp_name(node);
3008 let lexical_scope = match enclosing_lexical_scope_components(
3009 node,
3010 &ctx.analyzer,
3011 ctx.visibility,
3012 ctx.file,
3013 ctx.source,
3014 ) {
3015 LexicalScopeResolution::Resolved(scope) => scope,
3016 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3017 enclosing_namespace_components(node, ctx.source)
3018 }
3019 };
3020 for component_count in 1..=names.len() {
3021 let components = &names[..component_count];
3022 let name = components.last()?;
3023 let candidates = ctx
3024 .visibility
3025 .visible_identifier_candidates(ctx.file, name)
3026 .filter(|candidate| alias_provider.is_type_alias(candidate))
3027 .filter(|candidate| {
3028 ctx.visibility.is_physically_visible(ctx.file, candidate)
3029 && ctx
3030 .visibility
3031 .external_type_candidate_guard_compatible_in_context(
3032 &ctx.analyzer,
3033 ctx.file,
3034 candidate,
3035 node,
3036 )
3037 })
3038 .filter(|candidate| {
3039 let candidate_components = canonical_cpp_scope_components(candidate);
3040 lexical_component_tiers(components, global, &lexical_scope)
3041 .any(|tier| tier == candidate_components)
3042 || (component_count == 1
3043 && member_alias_owner_matches_reference_for(
3044 candidate,
3045 nodes[component_count - 1],
3046 ctx,
3047 ))
3048 })
3049 .filter(|candidate| {
3050 ctx.visibility.structured_class_alias_path_preserves_target(
3051 &ctx.analyzer,
3052 ctx.file,
3053 candidate,
3054 target,
3055 )
3056 });
3057 let mut aliases: Vec<&CodeUnit> = Vec::new();
3058 for candidate in candidates {
3059 if !aliases
3060 .iter()
3061 .any(|existing| same_logical_symbol(existing, candidate))
3062 {
3063 aliases.push(candidate);
3064 }
3065 }
3066 if aliases.len() == 1 {
3067 return nodes.get(component_count - 1).copied();
3068 }
3069 if aliases.len() > 1 {
3070 return None;
3071 }
3072 }
3073 None
3074}
3075
3076fn target_guided_dependent_class_alias_leaf<'tree>(
3079 node: Node<'tree>,
3080 ctx: &ScanCtx<'_>,
3081) -> Option<Node<'tree>> {
3082 if node.kind() != "type_identifier"
3083 || is_declaration_name(node)
3084 || local_type_name_shadows(node, ctx)
3085 {
3086 return None;
3087 }
3088 let target = physically_visible_type_target(ctx)?;
3089 let alias_provider = ctx.analyzer.type_alias_provider()?;
3090 if !target.is_class() || alias_provider.is_type_alias(target) {
3091 return None;
3092 }
3093 let name = node_text(node, ctx.source);
3094 let aliases = ctx
3095 .visibility
3096 .visible_identifier_candidates(ctx.file, name)
3097 .filter(|candidate| alias_provider.is_type_alias(candidate))
3098 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3099 .filter(|candidate| {
3100 ctx.visibility.is_physically_visible(ctx.file, candidate)
3101 && ctx
3102 .visibility
3103 .external_type_candidate_guard_compatible_in_context(
3104 &ctx.analyzer,
3105 ctx.file,
3106 candidate,
3107 node,
3108 )
3109 })
3110 .filter(|candidate| {
3111 ctx.visibility.structured_class_alias_path_preserves_target(
3112 &ctx.analyzer,
3113 ctx.file,
3114 candidate,
3115 target,
3116 )
3117 })
3118 .collect::<Vec<_>>();
3119 matches!(aliases.as_slice(), [_]).then_some(node)
3120}
3121
3122fn target_guided_unproven_alias_type_reference<'tree>(
3125 node: Node<'tree>,
3126 candidates: &[CodeUnit],
3127 ctx: &ScanCtx<'_>,
3128) -> Option<Node<'tree>> {
3129 if cpp_template_reference_arguments(node, ctx.source).is_some() {
3130 return None;
3131 }
3132 let target = physically_visible_type_target(ctx)?;
3133 if !target.is_class() {
3134 return None;
3135 }
3136 let alias_provider = ctx.analyzer.type_alias_provider()?;
3137 let (components, _) = type_reference_components(node, ctx.source)?;
3138 let hit = function_terminal_node(node);
3139 candidates
3140 .iter()
3141 .filter(|candidate| {
3142 alias_provider.is_type_alias(candidate)
3143 && ctx.visibility.is_physically_visible(ctx.file, candidate)
3144 && canonical_cpp_scope_components(candidate) == components
3145 })
3146 .find(|candidate| {
3147 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3148 || ctx.visibility.structured_alias_primary_preserves_target(
3149 &ctx.analyzer,
3150 ctx.file,
3151 candidate,
3152 target,
3153 )
3154 })
3155 .map(|_| hit)
3156}
3157
3158fn target_alias_candidates_visible(
3159 candidates: &[CodeUnit],
3160 reference: Node<'_>,
3161 ctx: &ScanCtx<'_>,
3162) -> bool {
3163 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
3164 return true;
3165 };
3166 if candidates.iter().any(|candidate| {
3167 !alias_provider.is_type_alias(candidate)
3168 && ctx.visibility.same_template_member_identity(
3169 &ctx.analyzer,
3170 candidate,
3171 &ctx.spec.target,
3172 )
3173 }) {
3174 return true;
3175 }
3176 let target_aliases = candidates
3177 .iter()
3178 .filter(|candidate| {
3179 alias_provider.is_type_alias(candidate)
3180 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3181 })
3182 .collect::<Vec<_>>();
3183 target_aliases.is_empty()
3184 || target_aliases
3185 .iter()
3186 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
3187}
3188
3189fn type_candidate_visible_at_reference(
3190 candidate: &CodeUnit,
3191 reference: Node<'_>,
3192 ctx: &ScanCtx<'_>,
3193) -> bool {
3194 let class_owned_alias = ctx
3195 .analyzer
3196 .type_alias_provider()
3197 .is_some_and(|provider| provider.is_type_alias(candidate))
3198 && ctx
3199 .analyzer
3200 .parent_of(candidate)
3201 .is_some_and(|owner| owner.is_class());
3202 if class_owned_alias {
3203 let conditional_family = ctx
3204 .visibility
3205 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
3206 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
3207 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
3208 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
3209 let guard_match = ctx
3210 .visibility
3211 .external_type_candidate_guard_compatible_in_context(
3212 &ctx.analyzer,
3213 ctx.file,
3214 candidate,
3215 reference,
3216 );
3217 let general_match = conditional_family
3218 && ctx.visibility.external_type_candidate_visible_in_context(
3219 &ctx.analyzer,
3220 ctx.file,
3221 candidate,
3222 reference,
3223 );
3224 return owner_match && (guard_match || general_match);
3225 }
3226 ctx.visibility.external_type_candidate_visible_in_context(
3227 &ctx.analyzer,
3228 ctx.file,
3229 candidate,
3230 reference,
3231 )
3232}
3233
3234fn unqualified_reference_selects_inherited_alias(
3235 candidate: &CodeUnit,
3236 reference: Node<'_>,
3237 ctx: &ScanCtx<'_>,
3238) -> bool {
3239 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3240 return false;
3241 };
3242 if global || components.len() != 1 {
3243 return false;
3244 }
3245 matches!(
3246 resolve_type_node_lexically_for_target(
3247 reference,
3248 &ctx.analyzer,
3249 ctx.visibility,
3250 &ctx.ordinary_type_imports,
3251 ctx.file,
3252 ctx.source,
3253 candidate,
3254 Some(&ctx.lexical_scope_cache),
3255 ctx.recovered_sentinel_scope(reference).as_deref(),
3256 ),
3257 LexicalTypeResolution::Resolved {
3258 ref unit,
3259 ref candidates,
3260 ..
3261 } if ctx
3262 .visibility
3263 .same_template_member_identity(&ctx.analyzer, unit, candidate)
3264 || candidates.iter().any(|resolved| {
3265 ctx.visibility.same_template_member_identity(
3266 &ctx.analyzer,
3267 resolved,
3268 candidate,
3269 )
3270 })
3271 )
3272}
3273
3274fn qualified_reference_selects_type_candidate(
3275 candidate: &CodeUnit,
3276 reference: Node<'_>,
3277 ctx: &ScanCtx<'_>,
3278) -> bool {
3279 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3280 return false;
3281 };
3282 if components.len() < 2 {
3283 return false;
3284 }
3285 let candidate_components = canonical_cpp_scope_components(candidate);
3286 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3287 match enclosing_lexical_scope_components(
3288 reference,
3289 &ctx.analyzer,
3290 ctx.visibility,
3291 ctx.file,
3292 ctx.source,
3293 ) {
3294 LexicalScopeResolution::Resolved(scope) => scope,
3295 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3296 enclosing_namespace_components(reference, ctx.source)
3297 }
3298 }
3299 });
3300 lexical_component_tiers(&components, global, &lexical_scope)
3301 .any(|qualified| qualified == candidate_components)
3302}
3303
3304fn qualified_type_component_hit_node<'tree>(
3305 component: Node<'tree>,
3306 qualified: Node<'tree>,
3307) -> Node<'tree> {
3308 let mut current = component;
3309 while let Some(parent) = current.parent() {
3310 let is_type_name = matches!(
3311 parent.kind(),
3312 "template_type"
3313 | "qualified_identifier"
3314 | "scoped_identifier"
3315 | "scoped_type_identifier"
3316 ) && parent
3317 .child_by_field_name("name")
3318 .is_some_and(|name| same_node(name, current));
3319 if !is_type_name {
3320 break;
3321 }
3322 current = parent;
3323 if same_node(parent, qualified) {
3324 break;
3325 }
3326 }
3327 current
3328}
3329
3330fn template_type_component_preserves_target(
3331 node: Node<'_>,
3332 candidates: &[CodeUnit],
3333 ctx: &ScanCtx<'_>,
3334) -> bool {
3335 template_reference_candidates_select_target(
3336 node,
3337 candidates,
3338 &ctx.analyzer,
3339 ctx.visibility,
3340 ctx.file,
3341 ctx.source,
3342 &ctx.spec.target,
3343 )
3344}
3345
3346fn template_reference_candidates_select_target(
3347 node: Node<'_>,
3348 candidates: &[CodeUnit],
3349 analyzer: &CppGraphSource<'_>,
3350 visibility: &VisibilityIndex<'_>,
3351 file: &ProjectFile,
3352 source: &str,
3353 target: &CodeUnit,
3354) -> bool {
3355 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3356 return !visibility.is_template_specialization(target);
3357 };
3358 let direct_template_name =
3359 template_reference_name_node(node).map(|name| node_text(name, source));
3360 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
3361 analyzer.type_alias_provider().is_some_and(|provider| {
3362 visibility
3363 .visible_identifier_candidates(file, name)
3364 .filter(|candidate| provider.is_type_alias(candidate))
3365 .any(|candidate| {
3366 visibility.template_alias_arguments_preserve_target(
3367 analyzer, file, candidate, &arguments, target,
3368 )
3369 })
3370 })
3371 });
3372 named_alias_selects_target
3373 || candidates.iter().any(|candidate| {
3374 (same_visible_symbol(candidate, target)
3375 && visibility.is_primary_template(target)
3376 && direct_template_name == Some(candidate.identifier()))
3377 || visibility.template_alias_arguments_preserve_target(
3378 analyzer, file, candidate, &arguments, target,
3379 )
3380 || visibility
3381 .resolve_template_arguments(file, candidate.clone(), &arguments)
3382 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
3383 })
3384}
3385
3386fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
3387 let template = if node.kind() == "template_type" {
3388 node
3389 } else {
3390 node.child_by_field_name("name")
3391 .filter(|name| name.kind() == "template_type")?
3392 };
3393 template.child_by_field_name("name")
3394}
3395
3396fn type_resolution_matches_target(
3397 node: Node<'_>,
3398 unit: &CodeUnit,
3399 candidates: &[CodeUnit],
3400 ctx: &ScanCtx<'_>,
3401) -> bool {
3402 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
3403}
3404
3405fn type_resolution_matches_unit_target(
3406 node: Node<'_>,
3407 unit: &CodeUnit,
3408 candidates: &[CodeUnit],
3409 target: &CodeUnit,
3410 ctx: &ScanCtx<'_>,
3411) -> bool {
3412 target_alias_candidates_visible(candidates, node, ctx)
3413 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
3414}
3415
3416fn type_resolution_identifies_unit_target(
3424 node: Node<'_>,
3425 unit: &CodeUnit,
3426 candidates: &[CodeUnit],
3427 target: &CodeUnit,
3428 ctx: &ScanCtx<'_>,
3429) -> bool {
3430 if !template_alias_owner_matches_reference(node, target, ctx) {
3431 return false;
3432 }
3433 if ctx.visibility.is_template_specialization(target)
3434 && cpp_template_reference_arguments(node, ctx.source).is_some()
3435 {
3436 let selected_unit =
3437 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
3438 ctx.visibility
3439 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
3440 .ok()
3441 });
3442 return selected_unit
3443 .as_ref()
3444 .is_some_and(|selected| same_visible_symbol(selected, target))
3445 || template_reference_candidates_select_target(
3446 node,
3447 candidates,
3448 &ctx.analyzer,
3449 ctx.visibility,
3450 ctx.file,
3451 ctx.source,
3452 target,
3453 );
3454 }
3455 ctx.visibility
3456 .same_template_member_identity(&ctx.analyzer, unit, target)
3457 || ctx.visibility.structured_class_alias_resolves_to_target(
3458 &ctx.analyzer,
3459 ctx.file,
3460 unit,
3461 target,
3462 )
3463 || candidates.iter().any(|candidate| {
3464 ctx.visibility
3465 .same_template_member_identity(&ctx.analyzer, candidate, target)
3466 || ctx.visibility.structured_class_alias_resolves_to_target(
3467 &ctx.analyzer,
3468 ctx.file,
3469 candidate,
3470 target,
3471 )
3472 })
3473}
3474
3475fn template_alias_owner_matches_reference(
3481 node: Node<'_>,
3482 target: &CodeUnit,
3483 ctx: &ScanCtx<'_>,
3484) -> bool {
3485 if !ctx
3486 .analyzer
3487 .type_alias_provider()
3488 .is_some_and(|provider| provider.is_type_alias(target))
3489 {
3490 return true;
3491 }
3492 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
3493 return true;
3494 };
3495 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
3496 return true;
3497 };
3498 if !ctx.visibility.is_template_specialization(&target_owner)
3499 && !ctx.visibility.is_template_specialization(&reference_owner)
3500 {
3501 return true;
3502 }
3503 same_visible_symbol(&target_owner, &reference_owner)
3504}
3505
3506fn inherited_injected_class_qualifier_scope<'tree>(
3507 node: Node<'tree>,
3508 ctx: &ScanCtx<'_>,
3509) -> Option<Node<'tree>> {
3510 let qualified = qualified_owner_components(node, ctx.source)?;
3511 if qualified.global || qualified.names.is_empty() {
3512 return None;
3513 }
3514 let injected_name = &qualified.names[0];
3515 if !ctx.spec.target.is_class()
3516 || ctx.spec.target.identifier() != injected_name
3517 || physically_visible_type_target(ctx).is_none()
3518 {
3519 return None;
3520 }
3521 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
3522 let owner = ctx.visibility.inherited_injected_class_owner(
3523 &ctx.analyzer,
3524 ctx.file,
3525 &enclosing_owner,
3526 injected_name,
3527 )?;
3528 same_visible_symbol(&owner, &ctx.spec.target)
3529 .then(|| qualified.nodes.first().copied())
3530 .flatten()
3531}
3532
3533fn target_guided_qualifier_type_scopes<'tree>(
3536 node: Node<'tree>,
3537 ctx: &ScanCtx<'_>,
3538) -> Option<Vec<Node<'tree>>> {
3539 if !matches!(
3540 node.kind(),
3541 "qualified_identifier" | "scoped_type_identifier"
3542 ) {
3543 return None;
3544 }
3545 let target = physically_visible_type_target(ctx)?;
3546 let qualified = qualified_owner_components(node, ctx.source)?;
3547 let lexical_scope = match enclosing_lexical_scope_components(
3556 node,
3557 &ctx.analyzer,
3558 ctx.visibility,
3559 ctx.file,
3560 ctx.source,
3561 ) {
3562 LexicalScopeResolution::Resolved(scope) => scope,
3563 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3564 enclosing_namespace_components(node, ctx.source)
3565 }
3566 };
3567 let indexed_owner_scope =
3568 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
3569 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
3570 let mut matches = Vec::new();
3571 for component_count in 1..=qualified.names.len() {
3572 let components = &qualified.names[..component_count];
3573 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
3574 .collect::<Vec<_>>();
3575 let name = components.last()?;
3576 let mut candidates = Vec::new();
3577 let mut exact_candidates = Vec::new();
3578 for candidate in ctx
3579 .visibility
3580 .visible_identifier_candidates(ctx.file, name)
3581 .filter(|candidate| candidate.is_class())
3582 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
3583 {
3584 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3585 brokk_bifrost_core::analyzer::Language::Cpp,
3586 &cpp_name_for(candidate),
3587 );
3588 if !candidate_components.ends_with(components)
3589 || candidates
3590 .iter()
3591 .any(|existing| same_logical_symbol(existing, candidate))
3592 {
3593 continue;
3594 }
3595 let exact_lexical_scope = lexical_tiers
3596 .iter()
3597 .any(|expected| expected == &candidate_components);
3598 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
3599 let structured_owner_match = indexed_owner_scope
3600 .as_ref()
3601 .is_some_and(|owner| owner.starts_with(candidate_owner))
3602 || recovered_owner_scope
3603 .as_ref()
3604 .is_some_and(|owner| owner.starts_with(candidate_owner));
3605 let class_alias_owner_match = ctx
3606 .analyzer
3607 .type_alias_provider()
3608 .is_some_and(|provider| provider.is_type_alias(candidate))
3609 && member_alias_owner_matches_reference_for(candidate, node, ctx);
3610 let macro_namespace_owner_match = is_declaration_name(node)
3611 && macro_namespace_scope_matches(candidate_owner, node, ctx);
3612 if components.len() == 1
3613 && candidate_components != components
3614 && !exact_lexical_scope
3615 && !structured_owner_match
3616 && !class_alias_owner_match
3617 && !macro_namespace_owner_match
3618 {
3619 continue;
3620 }
3621 candidates.push(candidate.clone());
3622 if exact_lexical_scope {
3623 exact_candidates.push(candidate.clone());
3624 }
3625 }
3626 if !exact_candidates.is_empty() {
3627 candidates = exact_candidates;
3628 }
3629 let canonical_alias_target_matches = matches!(
3634 candidates.as_slice(),
3635 [candidate]
3636 if ctx
3637 .analyzer
3638 .type_alias_provider()
3639 .is_some_and(|provider| provider.is_type_alias(candidate))
3640 && type_candidate_visible_at_reference(candidate, node, ctx)
3641 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3642 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3643 brokk_bifrost_core::analyzer::Language::Cpp,
3644 &cpp_name_for(candidate),
3645 ) == components
3646 || member_alias_owner_matches_reference_for(candidate, node, ctx))
3647 );
3648 let direct_alias_target = ctx
3649 .analyzer
3650 .type_alias_provider()
3651 .is_some_and(|provider| provider.is_type_alias(target))
3652 && candidates
3653 .iter()
3654 .any(|candidate| same_symbol(candidate, target));
3655 let unique_target = matches!(
3656 candidates.as_slice(),
3657 [candidate] if same_visible_symbol(candidate, target)
3658 );
3659 if direct_alias_target || unique_target || canonical_alias_target_matches {
3660 let matched = if ctx
3661 .analyzer
3662 .type_alias_provider()
3663 .is_some_and(|provider| provider.is_type_alias(target))
3664 && ctx
3665 .analyzer
3666 .parent_of(target)
3667 .is_some_and(|owner| owner.is_class())
3668 && ctx
3669 .visibility
3670 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
3671 {
3672 qualified.nodes[component_count - 1]
3676 } else {
3677 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
3678 };
3679 if template_type_component_preserves_target(matched, &candidates, ctx) {
3680 matches.push(matched);
3681 }
3682 }
3683 }
3684 (!matches.is_empty()).then_some(matches)
3685}
3686
3687fn target_guided_unproven_out_of_line_owner<'tree>(
3690 node: Node<'tree>,
3691 ctx: &ScanCtx<'_>,
3692) -> Option<Node<'tree>> {
3693 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
3694 || !is_declaration_name(node)
3695 {
3696 return None;
3697 }
3698 let target = physically_visible_type_target(ctx)?;
3699 if !target.is_class() {
3700 return None;
3701 }
3702 let qualified = qualified_owner_components(node, ctx.source)?;
3703 let target_components = canonical_cpp_scope_components(target);
3704 let mut scope = ctx
3705 .recovered_sentinel_scope(node)
3706 .or_else(|| {
3707 let parser_scope = enclosing_namespace_components(node, ctx.source);
3708 (!parser_scope.is_empty()).then_some(parser_scope)
3709 })
3710 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
3711 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
3712 if has_malformed_wrapper_function_definition_ancestor(node)
3713 && target_namespace.starts_with(&scope)
3714 && target_namespace.len() > scope.len()
3715 {
3716 scope = target_namespace.to_vec();
3717 }
3718 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
3719 .any(|components| components == target_components)
3720 {
3721 return None;
3722 }
3723 let owner_name = qualified.names.last()?;
3724 let candidates = ctx
3725 .visibility
3726 .visible_identifier_candidates(ctx.file, owner_name)
3727 .filter(|candidate| {
3728 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
3729 })
3730 .collect::<Vec<_>>();
3731 if candidates.is_empty()
3732 || candidates
3733 .iter()
3734 .any(|candidate| !same_visible_symbol(candidate, target))
3735 {
3736 return None;
3737 }
3738 qualified.nodes.last().copied()
3739}
3740
3741fn macro_namespace_scope_matches(
3742 candidate_owner: &[String],
3743 node: Node<'_>,
3744 ctx: &ScanCtx<'_>,
3745) -> bool {
3746 let namespace = enclosing_namespace_components(node, ctx.source);
3747 if namespace.is_empty() || candidate_owner.is_empty() {
3748 return false;
3749 }
3750 let mut expanded_owner = Vec::new();
3751 for component in candidate_owner {
3752 if let Some(replacement) =
3753 ctx.visibility
3754 .object_macro_replacement_at(ctx.file, component, node.start_byte())
3755 {
3756 let replacement_components =
3757 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3758 brokk_bifrost_core::analyzer::Language::Cpp,
3759 &replacement,
3760 );
3761 if replacement_components.is_empty() {
3762 return false;
3763 }
3764 expanded_owner.extend(replacement_components);
3765 } else {
3766 expanded_owner.push(component.clone());
3767 }
3768 }
3769 expanded_owner == namespace
3770}
3771
3772fn target_guided_missing_type_leaf<'tree>(
3773 node: Node<'tree>,
3774 ctx: &ScanCtx<'_>,
3775) -> Option<Node<'tree>> {
3776 physically_visible_type_target(ctx)?;
3777 target_guided_missing_dependent_nested_type_leaf(node, ctx)
3778 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
3779 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
3780 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
3781 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
3782 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
3783 .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
3784}
3785
3786fn target_guided_missing_class_alias_target_type_leaf<'tree>(
3790 node: Node<'tree>,
3791 ctx: &ScanCtx<'_>,
3792) -> Option<Node<'tree>> {
3793 if node.kind() != "type_identifier"
3794 || is_declaration_name(node)
3795 || local_type_name_shadows(node, ctx)
3796 {
3797 return None;
3798 }
3799 let alias_provider = ctx.analyzer.type_alias_provider()?;
3800 let name = node_text(node, ctx.source);
3801 let aliases = ctx
3802 .visibility
3803 .visible_identifier_candidates(ctx.file, name)
3804 .filter(|candidate| alias_provider.is_type_alias(candidate))
3805 .filter(|candidate| {
3806 ctx.analyzer
3807 .parent_of(candidate)
3808 .is_some_and(|owner| owner.is_class())
3809 })
3810 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3811 .filter(|candidate| {
3812 ctx.visibility
3813 .external_type_candidate_guard_compatible_in_context(
3814 &ctx.analyzer,
3815 ctx.file,
3816 candidate,
3817 node,
3818 )
3819 })
3820 .collect::<Vec<_>>();
3821 (!aliases.is_empty()
3822 && aliases.iter().all(|candidate| {
3823 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3824 }))
3825 .then_some(node)
3826}
3827
3828fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
3832 node: Node<'tree>,
3833 ctx: &ScanCtx<'_>,
3834) -> Option<Node<'tree>> {
3835 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
3836 matches!(
3837 declaration.kind(),
3838 "parameter_declaration" | "optional_parameter_declaration"
3839 )
3840 });
3841 let placement_new_type = node.parent().is_some_and(|parent| {
3842 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
3843 });
3844 if !parameter && !placement_new_type {
3845 return None;
3846 }
3847 if !ctx
3848 .analyzer
3849 .type_alias_provider()
3850 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3851 || !type_alias_owner_matches_structured_reference(node, ctx)
3852 {
3853 return None;
3854 }
3855 target_guided_missing_declaration_type_leaf(node, ctx)
3856}
3857
3858fn target_guided_missing_dependent_nested_type_leaf<'tree>(
3865 node: Node<'tree>,
3866 ctx: &ScanCtx<'_>,
3867) -> Option<Node<'tree>> {
3868 if !matches!(
3869 node.kind(),
3870 "qualified_identifier" | "scoped_type_identifier"
3871 ) || !qualified_type_scope_contains_template(node)
3872 {
3873 return None;
3874 }
3875 let name = node
3876 .child_by_field_name("name")
3877 .filter(|name| name.kind() == "type_identifier")?;
3878 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
3879 return None;
3880 }
3881 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
3882 if !owner_target.is_class() {
3883 return None;
3884 }
3885 let owner = node.child_by_field_name("scope")?;
3886 let owner_resolution = resolve_type_node_lexically_for_target(
3887 owner,
3888 &ctx.analyzer,
3889 ctx.visibility,
3890 &ctx.ordinary_type_imports,
3891 ctx.file,
3892 ctx.source,
3893 &owner_target,
3894 Some(&ctx.lexical_scope_cache),
3895 ctx.recovered_sentinel_scope(owner).as_deref(),
3896 );
3897 if matches!(
3898 owner_resolution,
3899 LexicalTypeResolution::Resolved {
3900 ref unit,
3901 ref candidates,
3902 ..
3903 } if type_resolution_matches_unit_target(
3904 owner,
3905 unit,
3906 candidates,
3907 &owner_target,
3908 ctx,
3909 )
3910 ) {
3911 return Some(name);
3912 }
3913
3914 let qualified = qualified_owner_components(node, ctx.source)?;
3915 let parser_namespace = enclosing_namespace_components(node, ctx.source);
3916 let orphaned_namespace = ctx
3917 .orphaned_namespaces
3918 .iter()
3919 .filter(|envelope| envelope.error_marked && envelope.body_end < node.start_byte())
3920 .max_by_key(|envelope| envelope.body_end)
3921 .map(|envelope| envelope.components.clone());
3922 let indexed_scope = ctx
3923 .recovered_sentinel_scope(node)
3924 .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
3925 .or(orphaned_namespace)
3926 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
3927 let owner_components = canonical_cpp_scope_components(&owner_target);
3928 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
3929 .any(|components| components == owner_components)
3930 {
3931 return None;
3932 }
3933 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
3934 .into_iter()
3935 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
3936 .collect::<Vec<_>>();
3937 (!scoped_candidates.is_empty()
3938 && scoped_candidates
3939 .iter()
3940 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
3941 .then_some(name)
3942}
3943
3944fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
3959 node: Node<'tree>,
3960 ctx: &ScanCtx<'_>,
3961) -> Option<Node<'tree>> {
3962 let target = &ctx.spec.target;
3963 let name = node_text(node, ctx.source);
3964 let (components, global) = type_reference_components(node, ctx.source)?;
3965 if global || components.len() != 1 || components[0] != name {
3966 return None;
3967 }
3968 if !target.is_class()
3969 || ctx
3970 .analyzer
3971 .type_alias_provider()
3972 .is_some_and(|provider| provider.is_type_alias(target))
3973 || is_declaration_name(node)
3974 || name != target.identifier()
3975 || ctx.local_shadows.is_shadowed(name)
3976 || local_type_name_shadows(node, ctx)
3977 || !ctx.visibility.is_physically_visible(ctx.file, target)
3978 || !ctx.visibility.external_type_candidate_visible_in_context(
3979 &ctx.analyzer,
3980 ctx.file,
3981 target,
3982 node,
3983 )
3984 {
3985 return None;
3986 }
3987
3988 let target_components = canonical_cpp_scope_components(target);
3989 let target_package_len = target_components.len().checked_sub(1)?;
3990 let target_package = &target_components[..target_package_len];
3991 let surviving_namespace = enclosing_namespace_components(node, ctx.source);
3992 if !target_package.starts_with(&surviving_namespace) {
3993 return None;
3994 }
3995 let envelope = ctx
3996 .orphaned_namespaces
3997 .iter()
3998 .filter(|envelope| envelope.error_marked && envelope.body_end < node.start_byte())
3999 .max_by_key(|envelope| envelope.body_end)?;
4000 if envelope.components.as_slice() != target_package {
4001 return None;
4002 }
4003
4004 if target.source() == ctx.file
4005 && !ctx.analyzer.ranges(target).iter().any(|range| {
4006 range.start_byte < envelope.body_end && range.end_byte <= envelope.body_end
4007 })
4008 {
4009 return None;
4010 }
4011
4012 let candidates = visible_type_identifier_candidates(ctx, name);
4013 if candidates.is_empty()
4014 || candidates
4015 .iter()
4016 .any(|candidate| !same_visible_symbol(candidate, target))
4017 {
4018 return None;
4019 }
4020 Some(node)
4021}
4022
4023fn target_guided_missing_member_alias_type_leaf<'tree>(
4029 node: Node<'tree>,
4030 ctx: &ScanCtx<'_>,
4031) -> Option<Node<'tree>> {
4032 if !is_template_argument_type_leaf(node)
4033 || is_declaration_name(node)
4034 || ctx
4035 .target_declaration_ranges
4036 .iter()
4037 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
4038 || node_text(node, ctx.source) != ctx.spec.target.identifier()
4039 || local_type_name_shadows(node, ctx)
4040 || !ctx
4041 .analyzer
4042 .type_alias_provider()
4043 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4044 {
4045 return None;
4046 }
4047 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
4048 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
4049 if !owner_scope_matches
4050 && !indexed_scope.is_some_and(|scope| {
4051 indexed_scope_matches_target_name(
4052 &scope,
4053 &[ctx.spec.target.identifier().to_string()],
4054 false,
4055 &ctx.spec.target,
4056 )
4057 })
4058 {
4059 return None;
4060 }
4061 if !(ctx.visibility.external_type_candidate_visible_in_context(
4068 &ctx.analyzer,
4069 ctx.file,
4070 &ctx.spec.target,
4071 node,
4072 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
4073 {
4074 return None;
4075 }
4076 let target_visible = ctx
4077 .visibility
4078 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4079 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4080 target_visible.then_some(node)
4081}
4082
4083fn target_guided_missing_template_argument_type_leaf<'tree>(
4089 node: Node<'tree>,
4090 ctx: &ScanCtx<'_>,
4091) -> Option<Node<'tree>> {
4092 let target = &ctx.spec.target;
4093 let name = node_text(node, ctx.source);
4094 if !target.is_class()
4095 || !is_template_argument_type_leaf(node)
4096 || is_declaration_name(node)
4097 || name != target.identifier()
4098 || ctx.local_shadows.is_shadowed(name)
4099 || local_type_name_shadows(node, ctx)
4100 || !ctx.visibility.is_physically_visible(ctx.file, target)
4101 || ctx
4102 .analyzer
4103 .type_alias_provider()
4104 .is_some_and(|provider| provider.is_type_alias(target))
4105 {
4106 return None;
4107 }
4108
4109 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4110 let target_components = canonical_cpp_scope_components(target);
4111 if target_components.last().map(String::as_str) != Some(name)
4112 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4113 .any(|components| components == target_components)
4114 {
4115 return None;
4116 }
4117
4118 let candidates = visible_type_identifier_candidates(ctx, name);
4122 if candidates.is_empty()
4123 || candidates.iter().any(|candidate| {
4124 !candidate.is_class()
4125 || ctx
4126 .analyzer
4127 .type_alias_provider()
4128 .is_some_and(|provider| provider.is_type_alias(candidate))
4129 || (!same_visible_symbol(candidate, target)
4130 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4131 .any(|components| components == canonical_cpp_scope_components(candidate)))
4132 })
4133 || !candidates
4134 .iter()
4135 .any(|candidate| same_visible_symbol(candidate, target))
4136 {
4137 return None;
4138 }
4139
4140 ctx.visibility
4143 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
4144 .then_some(node)
4145}
4146
4147fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4152 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
4153}
4154
4155fn member_alias_owner_matches_reference_for(
4156 target: &CodeUnit,
4157 node: Node<'_>,
4158 ctx: &ScanCtx<'_>,
4159) -> bool {
4160 let Some(owner) = ctx.analyzer.parent_of(target) else {
4161 return false;
4162 };
4163 if !owner.is_class() {
4164 return false;
4165 }
4166 let reference_owner = ctx
4167 .class_ranges
4168 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
4169 .or_else(|| structured_enclosing_owner(node, ctx));
4170 if reference_owner.as_ref().is_some_and(|reference_owner| {
4171 ctx.visibility
4172 .same_template_owner_identity(&owner, reference_owner)
4173 }) {
4174 return true;
4175 }
4176 if reference_owner.is_some_and(|reference_owner| {
4177 matches!(
4178 resolve_declaring_member_owner(
4179 &ctx.analyzer,
4180 ctx.visibility,
4181 ctx.file,
4182 &reference_owner,
4183 target.identifier(),
4184 ),
4185 EnclosingMemberOwnerResolution::Owner(declaring_owner)
4186 if ctx
4187 .visibility
4188 .same_template_owner_identity(&owner, &declaring_owner)
4189 )
4190 }) {
4191 return true;
4192 }
4193 let range = Range {
4194 start_byte: node.start_byte(),
4195 end_byte: node.end_byte(),
4196 start_line: node.start_position().row + 1,
4197 end_line: node.end_position().row + 1,
4198 };
4199 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
4200 while let Some(candidate) = indexed_enclosing {
4201 if candidate.is_class()
4202 && ctx
4203 .visibility
4204 .same_template_owner_identity(&owner, &candidate)
4205 {
4206 return true;
4207 }
4208 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
4209 }
4210 if let Some(reference_body) = malformed_recovered_class_body(node) {
4211 let mut root = node;
4212 while let Some(parent) = root.parent() {
4213 root = parent;
4214 }
4215 if ctx.analyzer.ranges(target).iter().any(|range| {
4216 root.descendant_for_byte_range(range.start_byte, range.end_byte)
4217 .and_then(malformed_recovered_class_body)
4218 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
4219 }) {
4220 return true;
4221 }
4222 }
4223 if structured_enclosing_owner(node, ctx)
4224 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
4225 {
4226 return true;
4227 }
4228 let owner_components = canonical_cpp_scope_components(&owner);
4229 if ctx
4230 .recovered_sentinel_scope(node)
4231 .is_some_and(|scope| scope == owner_components)
4232 {
4233 return true;
4234 }
4235 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4236 else {
4237 return false;
4238 };
4239 !owner_components.is_empty() && reference_scope == owner_components
4240}
4241
4242fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
4243 loop {
4244 if node.kind() == "compound_statement"
4245 && node
4246 .parent()
4247 .is_some_and(|parent| parent.kind() == "declaration_list")
4248 && node.prev_named_sibling().is_some_and(|header| {
4249 header.kind() == "ERROR"
4250 && header.end_byte() <= node.start_byte()
4251 && error_contains_class_header(header)
4252 })
4253 {
4254 return Some(node);
4255 }
4256 node = node.parent()?;
4257 }
4258}
4259
4260fn error_contains_class_header(node: Node<'_>) -> bool {
4261 let mut pending = vec![(node, 0usize)];
4262 while let Some((current, depth)) = pending.pop() {
4263 if matches!(current.kind(), "class" | "struct" | "union") {
4264 let mut sibling = current.next_sibling();
4265 let mut saw_name = false;
4266 while let Some(candidate) = sibling {
4267 match candidate.kind() {
4268 "comment" => {}
4269 "{" | "base_class_clause" | ":" => return saw_name,
4270 "identifier" | "type_identifier" if !saw_name => saw_name = true,
4271 _ if !candidate.is_named() => {}
4272 _ => break,
4273 }
4274 sibling = candidate.next_sibling();
4275 }
4276 }
4277 if depth >= 1 {
4278 continue;
4279 }
4280 let mut cursor = current.walk();
4281 pending.extend(
4282 current
4283 .children(&mut cursor)
4284 .filter(|child| child.kind() != "compound_statement")
4285 .map(|child| (child, depth + 1)),
4286 );
4287 }
4288 false
4289}
4290
4291fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4292 has_ancestor_kind(node, "compound_statement")
4293 && ctx
4294 .visibility
4295 .external_type_candidate_guard_compatible_in_context(
4296 &ctx.analyzer,
4297 ctx.file,
4298 &ctx.spec.target,
4299 node,
4300 )
4301}
4302
4303fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4304 ctx.analyzer
4305 .type_alias_provider()
4306 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4307 && member_alias_owner_matches_reference(node, ctx)
4308}
4309
4310fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4314 if !ctx
4315 .analyzer
4316 .type_alias_provider()
4317 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4318 {
4319 return false;
4320 }
4321 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4322 return false;
4323 };
4324 let mut reference_owner = structured_enclosing_owner(node, ctx);
4325 while let Some(owner) = reference_owner {
4326 if same_logical_symbol(&target_owner, &owner) {
4327 return true;
4328 }
4329 reference_owner = ctx.analyzer.parent_of(&owner);
4330 }
4331 false
4332}
4333
4334fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4339 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4340 return false;
4341 };
4342 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4343 return false;
4344 };
4345 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4346 return false;
4347 };
4348 let candidates = ctx
4349 .visibility
4350 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4351 .filter(|candidate| {
4352 candidate.is_class()
4353 && alias_provider.is_type_alias(candidate)
4354 && !same_visible_symbol(candidate, &ctx.spec.target)
4355 })
4356 .cloned()
4357 .collect::<Vec<_>>();
4358
4359 let mut owner = Some(reference_owner);
4360 while let Some(owner_unit) = owner {
4361 if same_logical_symbol(&target_owner, &owner_unit) {
4362 return false;
4363 }
4364 if candidates.iter().any(|candidate| {
4365 ctx.analyzer
4366 .parent_of(candidate)
4367 .is_some_and(|candidate_owner| {
4368 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
4369 })
4370 && ctx
4371 .visibility
4372 .external_type_candidate_guard_compatible_in_context(
4373 &ctx.analyzer,
4374 ctx.file,
4375 candidate,
4376 node,
4377 )
4378 }) {
4379 return true;
4380 }
4381 owner = ctx.analyzer.parent_of(&owner_unit);
4382 }
4383 false
4384}
4385
4386fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4387 let Some(callable) = nearest_callable_scope(node) else {
4388 return false;
4389 };
4390 let mut root_callable = callable;
4391 let mut ancestor = callable.parent();
4392 while let Some(current) = ancestor {
4393 if matches!(current.kind(), "function_definition" | "lambda_expression") {
4394 root_callable = current;
4395 }
4396 ancestor = current.parent();
4397 }
4398
4399 let mut stack = vec![root_callable];
4400 while let Some(current) = stack.pop() {
4401 if current.start_byte() >= node.start_byte() {
4402 continue;
4403 }
4404 if let Some(name) = local_type_name_declaration_node(current)
4405 && node_text(name, ctx.source) == ctx.spec.target.identifier()
4406 && nearest_callable_scope(current).is_some_and(|owner| {
4407 !is_malformed_wrapper_function_definition(owner)
4408 && owner.start_byte() <= callable.start_byte()
4409 && callable.end_byte() <= owner.end_byte()
4410 })
4411 && local_alias_scope_contains_node(current, node)
4412 {
4413 return true;
4414 }
4415 let mut cursor = current.walk();
4416 stack.extend(current.named_children(&mut cursor));
4417 }
4418 false
4419}
4420
4421fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
4422 local_type_alias_name_node(node).or_else(|| match node.kind() {
4423 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
4424 .child_by_field_name("name")
4425 .filter(|name| is_declaration_name(*name)),
4426 _ => None,
4427 })
4428}
4429
4430fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
4431 loop {
4432 if matches!(node.kind(), "function_definition" | "lambda_expression") {
4433 return Some(node);
4434 }
4435 node = node.parent()?;
4436 }
4437}
4438
4439fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
4440 match node.kind() {
4441 "alias_declaration" => node.child_by_field_name("name"),
4442 "type_definition" => node
4443 .child_by_field_name("declarator")
4444 .and_then(declarator_name_node),
4445 _ => None,
4446 }
4447}
4448
4449fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
4450 let mut current = alias.parent();
4451 while let Some(parent) = current {
4452 if matches!(
4453 parent.kind(),
4454 "class_specifier" | "struct_specifier" | "union_specifier"
4455 ) {
4456 return false;
4457 }
4458 if parent.kind() == "compound_statement" {
4459 return parent.start_byte() <= node.start_byte()
4460 && node.end_byte() <= parent.end_byte();
4461 }
4462 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
4463 let Some(body) = parent.child_by_field_name("body") else {
4464 return false;
4465 };
4466 return node_is_within(body, alias) && node_is_within(body, node);
4467 }
4468 current = parent.parent();
4469 }
4470 false
4471}
4472
4473fn target_guided_missing_declaration_type_leaf<'tree>(
4474 node: Node<'tree>,
4475 ctx: &ScanCtx<'_>,
4476) -> Option<Node<'tree>> {
4477 if is_declaration_name(node) {
4478 return None;
4479 }
4480 let component_nodes = cpp_name_component_nodes(node)?;
4481 let name_node = component_nodes.last().copied()?;
4482 let name = node_text(name_node, ctx.source);
4483 if name != ctx.spec.target.identifier() {
4484 return None;
4485 }
4486 let inside_target_declaration = ctx
4487 .target_declaration_ranges
4488 .iter()
4489 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
4490 if !inside_target_declaration
4491 && !ctx.visibility.external_type_candidate_visible_in_context(
4492 &ctx.analyzer,
4493 ctx.file,
4494 &ctx.spec.target,
4495 node,
4496 )
4497 {
4498 return None;
4499 }
4500 let components = component_nodes
4501 .iter()
4502 .map(|component| node_text(*component, ctx.source).to_string())
4503 .collect::<Vec<_>>();
4504 let local_alias_shadow = local_type_name_shadows(node, ctx);
4505 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
4506 let indexed_alias_owner = ctx
4507 .analyzer
4508 .type_alias_provider()
4509 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4510 && member_alias_owner_matches_reference(node, ctx);
4511 let target_alias_self_reference = inside_target_declaration
4512 && ctx
4513 .analyzer
4514 .type_alias_provider()
4515 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
4516 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
4517 &ctx.analyzer,
4518 ctx.file,
4519 &ctx.spec.target,
4520 node,
4521 ) || member_alias_complete_class_context(node, ctx);
4522 if !target_alias_self_reference
4523 && !local_alias_shadow
4524 && member_alias_visible
4525 && (structured_alias_owner || indexed_alias_owner)
4526 {
4527 return Some(node);
4528 }
4529 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4530 let declaration = nearest_declaration_type_context(node)?;
4531 let exact_scope_match = indexed_scope_matches_target_name(
4532 &indexed_scope,
4533 &components,
4534 is_globally_qualified_cpp_name(node),
4535 &ctx.spec.target,
4536 );
4537 let candidates = visible_type_identifier_candidates(ctx, name);
4538 let unique_visible_target = !candidates.is_empty()
4539 && candidates
4540 .iter()
4541 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4542 if matches!(declaration.kind(), "field_declaration" | "declaration") {
4543 let parser_lost_declaration_scope =
4544 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
4545 && unique_visible_target;
4546 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
4547 }
4548 let lost_namespace_parameter_context =
4549 matches!(
4550 declaration.kind(),
4551 "parameter_declaration" | "optional_parameter_declaration"
4552 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
4553 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
4554 return Some(node);
4555 }
4556 None
4557}
4558
4559fn target_guided_missing_alias_rhs_type_leaf<'tree>(
4560 node: Node<'tree>,
4561 ctx: &ScanCtx<'_>,
4562) -> Option<Node<'tree>> {
4563 let mut stack = vec![node];
4564 while let Some(candidate) = stack.pop() {
4565 if candidate.kind() == "type_identifier"
4566 && !is_declaration_name(candidate)
4567 && matches!(
4568 candidate.parent().map(|parent| parent.kind()),
4569 Some("template_type")
4570 )
4571 {
4572 let mut current = candidate.parent();
4573 let mut saw_qualified = false;
4574 let mut saw_dependent = false;
4575 let mut saw_type_descriptor = false;
4576 let mut saw_alias_declaration = false;
4577 while let Some(ancestor) = current {
4578 match ancestor.kind() {
4579 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
4580 "dependent_type" => saw_dependent = true,
4581 "type_descriptor" => saw_type_descriptor = true,
4582 "alias_declaration" => {
4583 saw_alias_declaration = true;
4584 break;
4585 }
4586 "template_type"
4587 | "template_argument_list"
4588 | "typename"
4589 | "template_declaration" => {}
4590 _ => {}
4591 }
4592 current = ancestor.parent();
4593 }
4594 let name = node_text(candidate, ctx.source);
4595 let visible_candidates = visible_type_identifier_candidates(ctx, name);
4596 let canonical_alias_target = visible_candidates
4597 .iter()
4598 .filter_map(|alias| ctx.visibility.alias_target(alias))
4599 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
4600 let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
4601 &ctx.analyzer,
4602 ctx.file,
4603 name,
4604 &ctx.spec.target,
4605 ) || canonical_alias_target;
4606 if saw_qualified
4607 && saw_dependent
4608 && saw_type_descriptor
4609 && saw_alias_declaration
4610 && alias_resolves
4611 && ctx.visibility.external_type_candidate_visible_in_context(
4612 &ctx.analyzer,
4613 ctx.file,
4614 &ctx.spec.target,
4615 candidate,
4616 )
4617 {
4618 return Some(candidate);
4619 }
4620 }
4621 for index in (0..candidate.named_child_count()).rev() {
4622 if let Some(child) = candidate.named_child(index) {
4623 stack.push(child);
4624 }
4625 }
4626 }
4627 None
4628}
4629
4630fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
4631 let mut current = Some(node);
4632 while let Some(ancestor) = current {
4633 if matches!(
4634 ancestor.kind(),
4635 "field_declaration"
4636 | "parameter_declaration"
4637 | "optional_parameter_declaration"
4638 | "declaration"
4639 | "type_descriptor"
4640 ) {
4641 let contains_type = ancestor
4642 .child_by_field_name("type")
4643 .is_some_and(|type_node| {
4644 type_node.start_byte() <= node.start_byte()
4645 && node.end_byte() <= type_node.end_byte()
4646 });
4647 if contains_type
4648 && !(ancestor.kind() == "type_descriptor" && is_template_argument_type_leaf(node))
4649 {
4650 return Some(ancestor);
4651 }
4652 if ancestor.kind() == "type_descriptor"
4653 && ancestor.parent().is_some_and(|parent| {
4654 matches!(
4655 parent.kind(),
4656 "cast_expression"
4657 | "new_expression"
4658 | "sizeof_expression"
4659 | "alignof_expression"
4660 | "typeid_expression"
4661 )
4662 })
4663 {
4664 return Some(ancestor);
4665 }
4666 }
4667 if matches!(
4668 ancestor.kind(),
4669 "compound_statement"
4670 | "translation_unit"
4671 | "namespace_definition"
4672 | "alias_declaration"
4673 | "type_definition"
4674 | "base_class_clause"
4675 ) {
4676 return None;
4677 }
4678 current = ancestor.parent();
4679 }
4680 None
4681}
4682
4683fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
4684 let mut candidates = Vec::new();
4685 for candidate in ctx
4686 .visibility
4687 .visible_identifier_candidates(ctx.file, name)
4688 .filter(|candidate| {
4689 candidate.is_class()
4690 || ctx
4691 .analyzer
4692 .type_alias_provider()
4693 .is_some_and(|provider| provider.is_type_alias(candidate))
4694 })
4695 {
4696 if !candidates
4697 .iter()
4698 .any(|existing| same_logical_symbol(existing, candidate))
4699 {
4700 candidates.push(candidate.clone());
4701 }
4702 }
4703 candidates
4704}
4705
4706fn target_guided_static_cast_alias_type_descriptor<'tree>(
4711 node: Node<'tree>,
4712 ctx: &ScanCtx<'_>,
4713) -> Option<Node<'tree>> {
4714 if node.kind() != "type_descriptor" {
4715 return None;
4716 }
4717 let argument_list = node.parent().filter(|parent| {
4718 parent.kind() == "template_argument_list"
4719 && parent.named_child_count() == 1
4720 && parent.named_child(0) == Some(node)
4721 })?;
4722 let template = argument_list.parent().filter(|parent| {
4723 parent.kind() == "template_function"
4724 && parent.child_by_field_name("arguments") == Some(argument_list)
4725 })?;
4726 let name = template.child_by_field_name("name")?;
4727 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
4728 return None;
4729 }
4730 let target = &ctx.spec.target;
4731 if node_text(node, ctx.source) != target.identifier()
4732 || !ctx
4733 .analyzer
4734 .type_alias_provider()
4735 .is_some_and(|provider| provider.is_type_alias(target))
4736 || !ctx.visibility.is_physically_visible(ctx.file, target)
4737 || !ctx.visibility.external_type_candidate_visible_in_context(
4738 &ctx.analyzer,
4739 ctx.file,
4740 target,
4741 node,
4742 )
4743 {
4744 return None;
4745 }
4746
4747 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4748 let target_scope = canonical_cpp_scope_components(target);
4749 let name_components = [target.identifier().to_string()];
4750 if !lexical_component_tiers(&name_components, false, &indexed_scope)
4751 .any(|components| components == target_scope)
4752 {
4753 return None;
4754 }
4755
4756 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
4757 if !candidates
4758 .iter()
4759 .any(|candidate| same_visible_symbol(candidate, target))
4760 {
4761 return None;
4762 }
4763 if candidates.iter().any(|candidate| {
4764 !same_visible_symbol(candidate, target)
4765 && lexical_component_tiers(&name_components, false, &indexed_scope)
4766 .any(|components| components == canonical_cpp_scope_components(candidate))
4767 }) {
4768 return None;
4769 }
4770 Some(node)
4771}
4772
4773fn indexed_scope_matches_target_name(
4774 indexed_scope: &[String],
4775 components: &[String],
4776 global: bool,
4777 target: &CodeUnit,
4778) -> bool {
4779 let target_name = cpp_name_for(target);
4780 lexical_component_tiers(components, global, indexed_scope)
4781 .any(|qualified| qualified.join("::") == target_name)
4782}
4783
4784fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
4785 if target.package_name().is_empty() {
4786 return false;
4787 }
4788 if indexed_scope.len() <= 1 {
4789 return true;
4790 }
4791 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4792 brokk_bifrost_core::analyzer::Language::Cpp,
4793 &cpp_name_for(target),
4794 );
4795 target_scope.pop();
4796 (1..indexed_scope.len())
4797 .rev()
4798 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
4799}
4800
4801fn indexed_enclosing_lexical_scope(
4802 analyzer: &CppGraphSource<'_>,
4803 file: &ProjectFile,
4804 node: Node<'_>,
4805) -> Option<Vec<String>> {
4806 let range = Range {
4807 start_byte: node.start_byte(),
4808 end_byte: node.end_byte(),
4809 start_line: node.start_position().row,
4810 end_line: node.end_position().row,
4811 };
4812 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
4813 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4814 brokk_bifrost_core::analyzer::Language::Cpp,
4815 &cpp_name_for(&enclosing),
4816 );
4817 if !enclosing.is_class() && !enclosing.is_module() {
4818 components.pop();
4819 }
4820 Some(components)
4821}
4822
4823fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
4824 if node.kind() != "qualified_identifier" {
4825 return None;
4826 }
4827 let mut stack = vec![node];
4828 while let Some(current) = stack.pop() {
4829 if current.kind() != "qualified_identifier" {
4830 continue;
4831 }
4832 if let Some(scope) = current.child_by_field_name("scope") {
4833 let text = qualified_scope_text(scope, ctx.source);
4834 if name_mentions(&text, &ctx.spec.member_name) {
4835 return Some(scope);
4836 }
4837 }
4838 let mut cursor = current.walk();
4839 for child in current.named_children(&mut cursor) {
4840 if child.kind() == "qualified_identifier" {
4841 stack.push(child);
4842 }
4843 }
4844 }
4845 None
4846}
4847
4848fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
4849 let mut parts = vec![node_text(scope, source).to_string()];
4850 let mut current = scope.parent();
4851 while let Some(qualified) = current {
4852 let Some(parent) = qualified.parent() else {
4853 break;
4854 };
4855 if parent.kind() != "qualified_identifier"
4856 || parent.child_by_field_name("name") != Some(qualified)
4857 {
4858 break;
4859 }
4860 if let Some(outer_scope) = parent.child_by_field_name("scope") {
4861 parts.push(node_text(outer_scope, source).to_string());
4862 }
4863 current = Some(parent);
4864 }
4865 parts.reverse();
4866 parts.join("::")
4867}
4868
4869fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4870 if node.kind() == "function_definition" {
4871 return;
4872 }
4873 if !matches!(
4874 node.kind(),
4875 "call_expression"
4876 | "new_expression"
4877 | "compound_literal_expression"
4878 | "declaration"
4879 | "field_initializer"
4880 ) {
4881 return;
4882 }
4883 let Some(owner) = ctx.spec.owner.as_ref() else {
4884 return;
4885 };
4886 if node.kind() == "field_initializer" {
4887 if !field_initializer_constructs_target(node, ctx, owner) {
4888 return;
4889 }
4890 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4891 match ctx
4892 .visibility
4893 .call_arity_evidence(ctx.file, node, ctx.source)
4894 .accepts(expected)
4895 {
4896 Some(true) => {}
4897 Some(false) => return,
4898 None => {
4899 push_unproven_hit(node, ctx);
4900 return;
4901 }
4902 }
4903 }
4904 push_hit(node, ctx);
4905 return;
4906 }
4907 if node.kind() == "declaration" {
4908 if declaration_is_object_construction_candidate(node, ctx)
4909 && declaration_mentions_type(node, ctx, owner)
4910 && ctx
4911 .spec
4912 .callable_arity_at(node.start_byte())
4913 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
4914 {
4915 push_hit(node, ctx);
4916 }
4917 return;
4918 }
4919 let Some(type_node) = constructor_type_node(node) else {
4920 return;
4921 };
4922 let hit_node = function_terminal_node(type_node);
4923 let text = node_text(type_node, ctx.source);
4924 if !name_mentions(text, &ctx.spec.member_name) {
4925 return;
4926 }
4927 *ctx.raw_match_count += 1;
4928 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4929 match ctx
4930 .visibility
4931 .call_arity_evidence(ctx.file, node, ctx.source)
4932 .accepts(expected)
4933 {
4934 Some(true) => {}
4935 Some(false) => return,
4936 None => {
4937 push_unproven_hit(hit_node, ctx);
4938 return;
4939 }
4940 }
4941 }
4942 let structured_resolution = resolve_type_node_lexically_for_target(
4943 type_node,
4944 &ctx.analyzer,
4945 ctx.visibility,
4946 &ctx.ordinary_type_imports,
4947 ctx.file,
4948 ctx.source,
4949 owner,
4950 Some(&ctx.lexical_scope_cache),
4951 ctx.recovered_sentinel_scope(type_node).as_deref(),
4952 );
4953 let structurally_resolves = matches!(
4954 &structured_resolution,
4955 LexicalTypeResolution::Resolved {
4956 unit, candidates, ..
4957 } if same_visible_symbol(unit, owner)
4958 || candidates
4959 .iter()
4960 .any(|candidate| same_visible_symbol(candidate, owner))
4961 );
4962 if structurally_resolves
4963 || matches!(structured_resolution, LexicalTypeResolution::Missing)
4964 && ctx
4965 .visibility
4966 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
4967 {
4968 push_hit(hit_node, ctx);
4969 } else {
4970 push_unproven_hit(hit_node, ctx);
4971 }
4972}
4973
4974fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4975 if node.kind() == "function_definition" {
4976 maybe_record_free_function_definition_hit(node, ctx);
4977 return;
4978 }
4979 if node.kind() == "identifier" {
4980 maybe_record_free_function_value_reference(node, ctx);
4981 return;
4982 }
4983 if node.kind() != "call_expression" {
4984 return;
4985 }
4986 let Some(function) = node
4987 .child_by_field_name("function")
4988 .or_else(|| node.named_child(0))
4989 else {
4990 return;
4991 };
4992 let text = node_text(function, ctx.source);
4993 if !name_matches_callable(text, &ctx.spec.member_name) {
4994 return;
4995 }
4996 *ctx.raw_match_count += 1;
4997 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4998 match ctx
4999 .visibility
5000 .call_arity_evidence(ctx.file, node, ctx.source)
5001 .accepts(expected)
5002 {
5003 Some(true) => {}
5004 Some(false) => return,
5005 None => {
5006 if !bare_name_binds_only_target(node, function, text, ctx) {
5013 push_unproven_hit(function_terminal_node(function), ctx);
5014 return;
5015 }
5016 }
5017 }
5018 }
5019 if matches!(function.kind(), "identifier" | "template_function") {
5020 let terminal = function_terminal_node(function);
5021 let name = node_text(terminal, ctx.source);
5022 if ctx.local_shadows.is_shadowed(name) {
5023 return;
5024 }
5025 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
5026 && !matches!(
5027 resolve_declaring_member_owner(
5028 &ctx.analyzer,
5029 ctx.visibility,
5030 ctx.file,
5031 &enclosing_owner,
5032 name,
5033 ),
5034 EnclosingMemberOwnerResolution::Missing
5035 )
5036 {
5037 return;
5038 }
5039 match resolve_bare_call_target(
5040 node,
5041 function,
5042 &ctx.analyzer,
5043 ctx.visibility,
5044 &ctx.ordinary_type_imports,
5045 ctx.file,
5046 ctx.source,
5047 ) {
5048 BareCallTargetResolution::FreeFunctions(units)
5049 if units
5050 .iter()
5051 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5052 {
5053 if free_function_call_may_target(node, text, ctx) {
5054 let recursive = enclosing_context(terminal, ctx)
5055 .enclosing
5056 .as_ref()
5057 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
5058 if recursive {
5059 push_recursive_reference_hit(terminal, ctx);
5060 } else {
5061 push_hit(terminal, ctx);
5062 }
5063 }
5064 }
5065 BareCallTargetResolution::UnprovenFreeFunctions(units)
5066 if units
5067 .iter()
5068 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5069 {
5070 push_unproven_hit(terminal, ctx);
5071 }
5072 BareCallTargetResolution::FreeFunctions(_)
5073 | BareCallTargetResolution::UnprovenFreeFunctions(_)
5074 | BareCallTargetResolution::Type(_)
5075 | BareCallTargetResolution::CallableShadow => {}
5076 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
5077 push_unproven_hit(terminal, ctx);
5078 }
5079 }
5080 return;
5081 }
5082 if !free_function_call_may_target(node, text, ctx) {
5083 return;
5084 }
5085 if ctx.visibility.contains_named_symbol(
5086 ctx.file,
5087 text,
5088 TargetKind::FreeFunction,
5089 &ctx.spec.target,
5090 ) {
5091 push_hit(function_terminal_node(function), ctx);
5092 } else if ctx.visibility.resolve_known_non_target(
5093 ctx.file,
5094 text,
5095 TargetKind::FreeFunction,
5096 &ctx.spec.target,
5097 ) {
5098 } else {
5101 push_unproven_hit(function_terminal_node(function), ctx);
5102 }
5103}
5104
5105fn bare_name_binds_only_target(
5114 call: Node<'_>,
5115 function: Node<'_>,
5116 text: &str,
5117 ctx: &ScanCtx<'_>,
5118) -> bool {
5119 if !matches!(function.kind(), "identifier" | "template_function") {
5120 return false;
5121 }
5122 let mut candidates = ctx
5123 .visibility
5124 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5125 candidates.retain(|candidate| {
5126 ctx.visibility
5127 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5128 });
5129 dedupe_callable_candidates(&mut candidates);
5130 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
5131}
5132
5133fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
5134 if ctx.spec.param_types.is_none() {
5135 return true;
5136 }
5137 let mut candidates = ctx
5138 .visibility
5139 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5140 candidates.retain(|candidate| {
5141 ctx.visibility
5142 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5143 });
5144 let Some(arity) = ctx
5145 .visibility
5146 .call_arity_evidence(ctx.file, call, ctx.source)
5147 .exact()
5148 else {
5149 return true;
5150 };
5151 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5152 if candidates.is_empty()
5153 || !candidates
5154 .iter()
5155 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5156 {
5157 return true;
5158 }
5159 let arg_types = call_argument_types(call, ctx);
5160 let filtered = cpp_filter_candidates_by_args(
5161 candidates,
5162 &arg_types,
5163 &|name| ctx.visibility.resolve_type(ctx.file, name),
5164 &|left, right| same_visible_symbol(left, right),
5165 );
5166 filtered
5167 .iter()
5168 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5169}
5170
5171fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
5172 let Some(args) = call
5173 .child_by_field_name("arguments")
5174 .or_else(|| call.child_by_field_name("parameters"))
5175 .or_else(|| call.child_by_field_name("value"))
5176 else {
5177 return Vec::new();
5178 };
5179 argument_children(args)
5180 .map(|arg| expression_arg_type(arg, ctx))
5181 .collect()
5182}
5183
5184fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
5185 match node.kind() {
5186 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
5187 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
5188 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
5189 literal
5190 }),
5191 "identifier" => ctx
5192 .bindings
5193 .resolve_symbol(node_text(node, ctx.source))
5194 .as_precise()
5195 .and_then(|bindings| bindings.iter().find_map(CppScanBinding::as_arg_type)),
5196 "parenthesized_expression" => node
5197 .child_by_field_name("argument")
5198 .or_else(|| node.named_child(0))
5199 .and_then(|inner| expression_arg_type(inner, ctx)),
5200 "pointer_expression" => {
5201 let delta = match node.child_by_field_name("operator")?.kind() {
5202 "&" => 1,
5203 "*" => -1,
5204 _ => return None,
5205 };
5206 let inner = node
5207 .child_by_field_name("argument")
5208 .or_else(|| node.named_child(0))?;
5209 let mut arg_type = expression_arg_type(inner, ctx)?;
5210 arg_type.indirection += delta;
5211 Some(arg_type)
5212 }
5213 _ => None,
5214 }
5215}
5216
5217fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5222 let text = node_text(node, ctx.source);
5223 if !name_matches_callable(text, &ctx.spec.member_name) {
5224 return;
5225 }
5226 if is_declaration_name(node) || is_call_callee_node(node) {
5227 return;
5228 }
5229 *ctx.raw_match_count += 1;
5230 if ctx.visibility.contains_named_symbol(
5231 ctx.file,
5232 text,
5233 TargetKind::FreeFunction,
5234 &ctx.spec.target,
5235 ) {
5236 push_hit(node, ctx);
5237 } else if ctx.visibility.resolve_known_non_target(
5238 ctx.file,
5239 text,
5240 TargetKind::FreeFunction,
5241 &ctx.spec.target,
5242 ) {
5243 } else {
5245 push_unproven_hit(node, ctx);
5246 }
5247}
5248
5249fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5250 let Some(function) = function_definition_name_node(node) else {
5251 return;
5252 };
5253 let text = node_text(function, ctx.source);
5254 if !name_matches_callable(text, &ctx.spec.member_name) {
5255 return;
5256 }
5257 *ctx.raw_match_count += 1;
5258 if !function_definition_signature_matches_target(node, ctx) {
5259 return;
5260 }
5261 if definition_name_candidates(function, ctx)
5262 .iter()
5263 .any(|name| {
5264 ctx.visibility.contains_named_symbol(
5265 ctx.file,
5266 name,
5267 TargetKind::FreeFunction,
5268 &ctx.spec.target,
5269 )
5270 })
5271 {
5272 push_definition_hit(function, ctx);
5273 } else if definition_name_candidates(function, ctx)
5274 .iter()
5275 .any(|name| {
5276 ctx.visibility.resolve_known_non_target(
5277 ctx.file,
5278 name,
5279 TargetKind::FreeFunction,
5280 &ctx.spec.target,
5281 )
5282 })
5283 {
5284 } else {
5286 push_unproven_definition_hit(function, ctx);
5287 }
5288}
5289
5290fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5291 if node.kind() == "using_declaration" {
5292 maybe_record_using_member_hit(node, ctx);
5293 return;
5294 }
5295 if has_ancestor_kind(node, "using_declaration") {
5296 return;
5297 }
5298 if node.kind() == "function_definition" {
5299 maybe_record_method_definition_hit(node, ctx);
5300 return;
5301 }
5302 if node_inside_function_definition_declarator(node) {
5303 return;
5304 }
5305 if is_declaration_name(node) {
5306 return;
5307 }
5308 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
5309 maybe_record_qualified_method_value_hit(node, member, ctx);
5310 return;
5311 }
5312 if let Some(value) = qualified_callable_value(node) {
5313 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
5314 return;
5315 }
5316 if node.kind() != "call_expression" {
5317 return;
5318 }
5319 if let Some((receiver, operator)) = explicit_operator_call(node) {
5320 let text = node_text(operator, ctx.source);
5321 if !name_matches_callable(text, &ctx.spec.member_name) {
5322 return;
5323 }
5324 *ctx.raw_match_count += 1;
5325 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5326 match ctx
5327 .visibility
5328 .call_arity_evidence(ctx.file, node, ctx.source)
5329 .accepts(expected)
5330 {
5331 Some(true) => {}
5332 Some(false) => return,
5333 None => {
5334 push_unproven_hit(operator, ctx);
5335 return;
5336 }
5337 }
5338 }
5339 match explicit_receiver_target_resolution(receiver, ctx) {
5340 MethodReceiverTargetResolution::Target if receiver_is_self_like(receiver, ctx.file) => {
5341 push_self_receiver_hit(operator, ctx);
5342 }
5343 MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
5344 MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
5345 MethodReceiverTargetResolution::NonTarget
5346 | MethodReceiverTargetResolution::Ambiguous => {}
5347 }
5348 return;
5349 }
5350 let Some(function) = node
5351 .child_by_field_name("function")
5352 .or_else(|| node.named_child(0))
5353 else {
5354 return;
5355 };
5356 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
5357 return;
5358 }
5359 if function.kind() == "identifier"
5360 && ctx
5361 .local_shadows
5362 .is_shadowed(node_text(function, ctx.source))
5363 {
5364 return;
5365 }
5366 *ctx.raw_match_count += 1;
5367 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5368 match ctx
5369 .visibility
5370 .call_arity_evidence(ctx.file, node, ctx.source)
5371 .accepts(expected)
5372 {
5373 Some(true) => {}
5374 Some(false) => return,
5375 None => {
5376 push_unproven_hit(function_terminal_node(function), ctx);
5377 return;
5378 }
5379 }
5380 }
5381 if !method_call_may_target(node, ctx) {
5382 return;
5383 }
5384 if is_structurally_qualified(function) {
5385 match qualified_owner_resolution(function, ctx) {
5386 QualifiedOwnerResolution::Target => {
5387 push_hit(function_terminal_node(function), ctx);
5388 }
5389 QualifiedOwnerResolution::NonTarget => {}
5390 QualifiedOwnerResolution::Unresolved => {
5391 push_unproven_hit(function_terminal_node(function), ctx);
5392 }
5393 }
5394 return;
5395 }
5396 match call_function_target_resolution(function, ctx) {
5397 MethodReceiverTargetResolution::Target
5398 if call_function_has_direct_self_receiver(function, ctx.file) =>
5399 {
5400 push_self_receiver_hit(function_terminal_node(function), ctx);
5401 }
5402 MethodReceiverTargetResolution::Target => {
5403 push_hit(function_terminal_node(function), ctx);
5404 }
5405 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
5406 MethodReceiverTargetResolution::Missing
5412 if inherited_target_owner_context(function, ctx) =>
5413 {
5414 push_hit(function_terminal_node(function), ctx);
5415 }
5416 MethodReceiverTargetResolution::Missing
5417 if same_owner_context(function, ctx)
5418 || out_of_line_target_owner_context(function, ctx) =>
5419 {
5420 push_self_receiver_hit(function_terminal_node(function), ctx);
5421 }
5422 MethodReceiverTargetResolution::Missing
5423 if function.kind() == "identifier"
5424 && resolves_to_lexical_free_function(function, ctx) =>
5425 {
5426 }
5429 MethodReceiverTargetResolution::Missing
5430 if !receiver_has_known_non_target(function, ctx)
5431 && !known_non_target_owner_context(function, ctx) =>
5432 {
5433 push_unproven_hit(function_terminal_node(function), ctx);
5434 }
5435 MethodReceiverTargetResolution::Missing => {}
5436 }
5437}
5438
5439fn node_inside_function_definition_declarator(node: Node<'_>) -> bool {
5440 let mut current = node.parent();
5441 while let Some(parent) = current {
5442 if parent.kind() == "function_definition" {
5443 return parent
5444 .child_by_field_name("declarator")
5445 .is_some_and(|declarator| {
5446 node.start_byte() >= declarator.start_byte()
5447 && node.end_byte() <= declarator.end_byte()
5448 });
5449 }
5450 current = parent.parent();
5451 }
5452 false
5453}
5454
5455fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
5456 if node.kind() != "qualified_identifier" {
5457 return None;
5458 }
5459 let parameter = node
5460 .parent()
5461 .filter(|parent| parent.kind() == "parameter_declaration")?;
5462 let parameter_declarator = parameter.child_by_field_name("declarator")?;
5463 if parameter.child_by_field_name("type") != Some(node)
5468 || parameter_declarator.kind() != "abstract_function_declarator"
5469 {
5470 return None;
5471 }
5472 let parameter_list = parameter
5473 .parent()
5474 .filter(|parent| parent.kind() == "parameter_list")?;
5475 if parameter_list.named_child_count() != 1 {
5476 return None;
5477 }
5478 let function_declarator = parameter_list
5479 .parent()
5480 .filter(|parent| parent.kind() == "function_declarator")?;
5481 if function_declarator
5482 .child_by_field_name("declarator")
5483 .is_none_or(|declarator| declarator.kind() != "identifier")
5484 || function_declarator
5485 .parent()
5486 .is_none_or(|parent| parent.kind() != "declaration")
5487 {
5488 return None;
5489 }
5490 node.child_by_field_name("name")
5491}
5492
5493fn maybe_record_using_member_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5494 let Some(imported) = ordinary_using_declaration_type_node(node) else {
5495 return;
5496 };
5497 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
5498 return;
5499 }
5500 let Some(target_owner) = ctx.spec.owner.as_ref() else {
5501 return;
5502 };
5503 *ctx.raw_match_count += 1;
5504 let owner_resolution = qualified_owner_components(imported, ctx.source)
5505 .map(|qualified| {
5506 let lexical_scope = match enclosing_lexical_scope_components(
5507 imported,
5508 &ctx.analyzer,
5509 ctx.visibility,
5510 ctx.file,
5511 ctx.source,
5512 ) {
5513 LexicalScopeResolution::Resolved(scope) => scope,
5514 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
5515 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
5516 };
5517 ctx.visibility.resolve_type_components_lexically(
5518 &ctx.analyzer,
5519 ctx.file,
5520 &qualified.names,
5521 qualified.global,
5522 &lexical_scope,
5523 )
5524 })
5525 .unwrap_or(LexicalTypeResolution::Missing);
5526 let matches_target_owner = matches!(
5527 owner_resolution,
5528 LexicalTypeResolution::Resolved {
5529 ref unit,
5530 ref candidates,
5531 ..
5532 } if same_visible_symbol(unit, target_owner)
5533 || candidates
5534 .iter()
5535 .any(|candidate| same_visible_symbol(candidate, target_owner))
5536 );
5537 if !matches_target_owner {
5538 match owner_resolution {
5539 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5540 push_unproven_hit(imported, ctx);
5541 }
5542 LexicalTypeResolution::Resolved { .. } => {}
5543 }
5544 return;
5545 }
5546 match ctx.visibility.visible_member_for_owner_name(
5547 ctx.file,
5548 target_owner,
5549 &ctx.spec.member_name,
5550 ) {
5551 VisibleMemberResolution::Callable(candidates)
5552 if candidates.iter().all(|candidate| {
5553 ctx.target_group.contains(candidate)
5554 || ctx
5555 .target_group
5556 .iter()
5557 .any(|target| same_visible_symbol(candidate, target))
5558 }) =>
5559 {
5560 push_hit(imported, ctx);
5561 }
5562 VisibleMemberResolution::NonCallable => {}
5563 VisibleMemberResolution::Callable(_)
5564 | VisibleMemberResolution::AmbiguousKind
5565 | VisibleMemberResolution::Missing => {
5566 push_unproven_hit(imported, ctx);
5567 }
5568 }
5569}
5570
5571fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5572 let name = node_text(node, ctx.source);
5573 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
5574 let key = (namespace.clone(), name.to_string());
5575 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
5576 return resolved;
5577 }
5578 let resolved = ctx
5579 .visibility
5580 .visible_identifier_candidates(ctx.file, name)
5581 .any(|unit| {
5582 unit.is_function()
5583 && type_owner_of(&ctx.analyzer, unit).is_none()
5584 && unit.package_name() == namespace
5585 });
5586 ctx.lexical_free_function_cache
5587 .borrow_mut()
5588 .insert(key, resolved);
5589 resolved
5590}
5591
5592fn maybe_record_qualified_method_value_hit(
5593 qualified: Node<'_>,
5594 member: Node<'_>,
5595 ctx: &mut ScanCtx<'_>,
5596) {
5597 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
5598 return;
5599 }
5600 *ctx.raw_match_count += 1;
5601 let resolution =
5602 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
5603 match resolution {
5604 LexicalCallableValueResolution::Type(resolved_owner) => {
5605 let Some(owner) = ctx.spec.owner.as_ref() else {
5606 push_unproven_hit(member, ctx);
5607 return;
5608 };
5609 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
5610 if same_visible_symbol(&resolved_owner, owner) {
5611 push_unproven_hit(member, ctx);
5612 }
5613 return;
5614 }
5615 match ctx.visibility.visible_member_for_owner_name(
5616 ctx.file,
5617 owner,
5618 &ctx.spec.member_name,
5619 ) {
5620 VisibleMemberResolution::Callable(candidates)
5621 if candidates.iter().all(|candidate| {
5622 ctx.target_group.contains(candidate)
5623 || ctx
5624 .target_group
5625 .iter()
5626 .any(|target| same_visible_symbol(candidate, target))
5627 }) =>
5628 {
5629 push_hit(member, ctx);
5632 }
5633 VisibleMemberResolution::NonCallable => {}
5634 VisibleMemberResolution::Callable(_)
5635 | VisibleMemberResolution::AmbiguousKind
5636 | VisibleMemberResolution::Missing => {
5637 push_unproven_hit(member, ctx);
5638 }
5639 }
5640 }
5641 LexicalCallableValueResolution::FreeFunction(_) => {}
5642 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
5643 push_unproven_hit(member, ctx);
5644 }
5645 }
5646}
5647
5648fn qualified_callable_value_resolution(
5649 qualified: Node<'_>,
5650 member_name: &str,
5651 ctx: &ScanCtx<'_>,
5652) -> LexicalCallableValueResolution {
5653 let Some((owner_components, global)) =
5654 qualified_callable_owner_components(qualified, ctx.source)
5655 else {
5656 return LexicalCallableValueResolution::Missing;
5657 };
5658 let lexical_scope = if global {
5659 Vec::new()
5660 } else {
5661 match enclosing_lexical_scope_components(
5662 qualified,
5663 &ctx.analyzer,
5664 ctx.visibility,
5665 ctx.file,
5666 ctx.source,
5667 ) {
5668 LexicalScopeResolution::Resolved(scope) => scope,
5669 LexicalScopeResolution::Ambiguous => {
5670 return LexicalCallableValueResolution::Ambiguous;
5671 }
5672 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
5673 }
5674 };
5675 if let Some(target_owner) = ctx.spec.owner.as_ref()
5676 && let LexicalTypeResolution::Resolved { unit, .. } =
5677 resolve_type_components_lexically_at_for_target_with_scope_cache(
5678 qualified,
5679 &owner_components,
5680 global,
5681 &ctx.analyzer,
5682 ctx.visibility,
5683 &ctx.ordinary_type_imports,
5684 ctx.file,
5685 ctx.source,
5686 target_owner,
5687 false,
5688 Some(&ctx.lexical_scope_cache),
5689 )
5690 {
5691 return LexicalCallableValueResolution::Type(unit);
5692 }
5693 ctx.visibility.resolve_callable_value_components_lexically(
5694 &ctx.analyzer,
5695 ctx.file,
5696 &owner_components,
5697 member_name,
5698 global,
5699 &lexical_scope,
5700 )
5701}
5702
5703fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5704 let Some(owner) = ctx.spec.owner.as_ref() else {
5705 return true;
5706 };
5707 if ctx.spec.param_types.is_none() {
5708 return true;
5709 }
5710 let mut candidates = ctx
5711 .visibility
5712 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
5713 .into_iter()
5714 .filter(|unit| unit.is_function())
5715 .cloned()
5716 .collect::<Vec<_>>();
5717 let Some(arity) = ctx
5718 .visibility
5719 .call_arity_evidence(ctx.file, call, ctx.source)
5720 .exact()
5721 else {
5722 return true;
5723 };
5724 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5725 if candidates.is_empty()
5726 || !candidates
5727 .iter()
5728 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5729 {
5730 return true;
5731 }
5732 let arg_types = call_argument_types(call, ctx);
5733 let filtered = cpp_filter_candidates_by_args(
5734 candidates,
5735 &arg_types,
5736 &|name| ctx.visibility.resolve_type(ctx.file, name),
5737 &|left, right| same_visible_symbol(left, right),
5738 );
5739 filtered
5740 .iter()
5741 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5742}
5743
5744fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5745 let Some(function) = function_definition_name_node(node) else {
5746 return;
5747 };
5748 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
5749 return;
5750 }
5751 *ctx.raw_match_count += 1;
5752 if !function_definition_signature_matches_target(node, ctx) {
5753 return;
5754 }
5755 if node_inside_target_declaration(function, ctx) {
5756 return;
5757 }
5758 if is_structurally_qualified(function) {
5759 match qualified_owner_resolution(function, ctx) {
5760 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
5761 QualifiedOwnerResolution::NonTarget => {}
5762 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
5763 }
5764 return;
5765 }
5766 if definition_name_candidates(function, ctx)
5767 .iter()
5768 .any(|name| {
5769 name.contains("::")
5770 && ctx.visibility.contains_named_symbol(
5771 ctx.file,
5772 name,
5773 TargetKind::Method,
5774 &ctx.spec.target,
5775 )
5776 })
5777 {
5778 push_definition_hit(function, ctx);
5779 } else if definition_name_candidates(function, ctx)
5780 .iter()
5781 .any(|name| {
5782 ctx.visibility.resolve_known_non_target(
5783 ctx.file,
5784 name,
5785 TargetKind::Method,
5786 &ctx.spec.target,
5787 )
5788 })
5789 || known_non_target_owner_context(function, ctx)
5790 {
5791 } else {
5793 push_unproven_definition_hit(function, ctx);
5794 }
5795}
5796
5797fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5798 ctx.target_declaration_ranges
5799 .iter()
5800 .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
5801}
5802
5803fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
5804 let mut receiver = None;
5805 let mut cursor = node.walk();
5806 for child in node.named_children(&mut cursor) {
5807 if child.kind() == "argument_list" {
5808 continue;
5809 }
5810 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
5811 return receiver.map(|receiver| (receiver, operator));
5812 }
5813 if receiver.is_none() {
5814 receiver = Some(child);
5815 }
5816 }
5817 None
5818}
5819
5820fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
5821 if node.kind() != "function_definition" {
5822 return None;
5823 }
5824 node.child_by_field_name("declarator")
5825 .and_then(declarator_name_node)
5826}
5827
5828fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
5829 function_definition_name_node(node)
5830}
5831
5832fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5833 let definition = node_text(node, ctx.source);
5834 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
5835 return true;
5836 };
5837 if !expected.accepts(signature_arity(Some(definition))) {
5838 return false;
5839 }
5840 let Some(target_signature) = ctx.spec.target.signature() else {
5841 return true;
5842 };
5843 cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
5844}
5845
5846fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
5847 name_matches_callable(node_text(function_terminal_node(node), source), expected)
5848}
5849
5850fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
5851 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
5852 if raw.is_empty() {
5853 return Vec::new();
5854 }
5855 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
5856 return vec![raw];
5857 };
5858 if !raw.contains("::") {
5859 return vec![format!("{namespace}::{raw}")];
5860 }
5861 if raw
5867 .split("::")
5868 .next()
5869 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
5870 {
5871 vec![format!("{namespace}::{raw}"), raw]
5872 } else {
5873 vec![raw]
5874 }
5875}
5876
5877fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
5878 if node.kind() == kind {
5879 return Some(node);
5880 }
5881 let mut cursor = node.walk();
5882 for child in node.named_children(&mut cursor) {
5883 if let Some(found) = first_descendant_of_kind(child, kind) {
5884 return Some(found);
5885 }
5886 }
5887 None
5888}
5889
5890fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5891 if matches!(node.kind(), "identifier" | "field_identifier")
5892 && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
5893 {
5894 return;
5895 }
5896 if !matches!(
5897 node.kind(),
5898 "identifier" | "field_identifier" | "qualified_identifier"
5899 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
5900 || is_declaration_name(node)
5901 || is_member_field_own_declarator(node, ctx)
5902 || is_selected_field_expression_member_descendant(node)
5903 || is_nested_in_qualified_identifier(node)
5904 {
5905 return;
5906 }
5907 *ctx.raw_match_count += 1;
5908 if global_field_resolves_to_target(node, ctx) {
5909 push_hit(node, ctx);
5910 } else if global_field_is_known_non_target(node, ctx) {
5911 } else {
5912 push_unproven_hit(node, ctx);
5913 }
5914}
5915
5916fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
5922 while let Some(parent) = node.parent() {
5923 if parent.kind() == "field_expression" {
5924 if parent
5925 .child_by_field_name("field")
5926 .is_some_and(|field| node_is_within(field, node))
5927 {
5928 return true;
5929 }
5930 let receiver = parent
5931 .child_by_field_name("argument")
5932 .or_else(|| parent.child_by_field_name("object"))
5933 .or_else(|| parent.named_child(0));
5934 if !receiver.is_some_and(|receiver| node_is_within(receiver, node)) {
5935 return true;
5938 }
5939 }
5940 node = parent;
5941 }
5942 false
5943}
5944
5945fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
5946 parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
5947}
5948
5949fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5950 let text = node_text(node, ctx.source);
5951 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
5952 return false;
5953 }
5954 if text.contains("::") {
5955 return ctx.visibility.contains_named_symbol(
5956 ctx.file,
5957 text,
5958 TargetKind::GlobalField,
5959 &ctx.spec.target,
5960 );
5961 }
5962 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
5963 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
5964 {
5965 return ctx.visibility.contains_named_symbol(
5966 ctx.file,
5967 text,
5968 TargetKind::GlobalField,
5969 &ctx.spec.target,
5970 );
5971 }
5972 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5973 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
5974 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5975 brokk_bifrost_core::analyzer::Language::Cpp,
5976 &namespace,
5977 ) == indexed_scope
5978 })
5979 {
5980 return ctx.visibility.contains_named_symbol(
5981 ctx.file,
5982 text,
5983 TargetKind::GlobalField,
5984 &ctx.spec.target,
5985 );
5986 }
5987 bare_global_field_uniquely_resolves_to_target(text, ctx)
5988}
5989
5990fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
5991 let mut matched_target = false;
5992 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
5993 if !has_persisted_global_field_identity(unit)
5994 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
5995 {
5996 continue;
5997 }
5998 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
5999 continue;
6000 }
6001 if same_visible_global_field_symbol(
6002 &ctx.analyzer,
6003 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6004 unit,
6005 &ctx.spec.target,
6006 ) {
6007 matched_target = true;
6008 } else {
6009 return false;
6010 }
6011 }
6012 matched_target
6013}
6014
6015fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
6016 unit.is_field() && !unit.short_name().contains('.')
6021}
6022
6023fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6024 let text = node_text(node, ctx.source);
6025 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6026 return true;
6027 }
6028 if text.contains("::") {
6029 return ctx.visibility.resolve_known_non_target(
6030 ctx.file,
6031 text,
6032 TargetKind::GlobalField,
6033 &ctx.spec.target,
6034 );
6035 }
6036 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
6037 return false;
6038 };
6039 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
6040 && ctx
6041 .visibility
6042 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6043 .any(|unit| {
6044 has_persisted_global_field_identity(unit)
6045 && unit.identifier() == ctx.spec.member_name
6046 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
6047 && !same_visible_global_field_symbol(
6048 &ctx.analyzer,
6049 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6050 unit,
6051 &ctx.spec.target,
6052 )
6053 })
6054}
6055
6056fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6057 if node.kind() == "field_expression" {
6058 let Some(field) = node.child_by_field_name("field") else {
6059 return;
6060 };
6061 if node_text(field, ctx.source) != ctx.spec.member_name {
6062 return;
6063 }
6064 *ctx.raw_match_count += 1;
6065 let receiver = node
6066 .child_by_field_name("argument")
6067 .or_else(|| node.child_by_field_name("object"));
6068 match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, ctx)) {
6069 Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
6070 Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
6071 Some(
6072 MethodReceiverTargetResolution::NonTarget
6073 | MethodReceiverTargetResolution::Ambiguous,
6074 ) => {}
6075 }
6076 return;
6077 }
6078
6079 if matches!(node.kind(), "identifier" | "field_identifier")
6080 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6081 && let Some(designator_owner) =
6082 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
6083 {
6084 *ctx.raw_match_count += 1;
6085 match designator_owner {
6086 DesignatedInitializerOwner::Resolved(owner)
6087 if ctx
6088 .spec
6089 .owner
6090 .as_ref()
6091 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
6092 {
6093 push_hit(node, ctx);
6094 }
6095 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
6096 DesignatedInitializerOwner::Resolved(_) => {}
6097 }
6098 return;
6099 }
6100
6101 let qualified_member_name_matches =
6102 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
6103 && cpp_name_component_nodes(node)
6104 .and_then(|components| components.last().copied())
6105 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
6106 if !matches!(
6107 node.kind(),
6108 "identifier" | "field_identifier" | "qualified_identifier" | "scoped_identifier"
6109 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6110 && !qualified_member_name_matches)
6111 || is_declaration_name(node)
6112 || is_member_field_own_declarator(node, ctx)
6113 || is_selected_field_expression_member_descendant(node)
6114 || is_nested_in_qualified_identifier(node)
6115 {
6116 return;
6117 }
6118 *ctx.raw_match_count += 1;
6119 if is_structurally_qualified(node) {
6120 match qualified_owner_resolution(node, ctx) {
6121 QualifiedOwnerResolution::Target => push_hit(node, ctx),
6122 QualifiedOwnerResolution::NonTarget => {}
6123 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
6124 }
6125 return;
6126 }
6127 let text = node_text(node, ctx.source);
6128 if ctx.local_shadows.is_shadowed(text) {
6129 return;
6130 }
6131 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
6132 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
6133 let owner_context = structured_owner_context_resolution(node, ctx);
6134 if matches!(
6135 owner_context,
6136 StructuredOwnerContextResolution::SelfTarget
6137 | StructuredOwnerContextResolution::InheritedTarget
6138 ) || unscoped_enum_match
6139 {
6140 push_hit(node, ctx);
6141 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
6142 .then_some(ctx.spec.owner.as_ref())
6143 .flatten()
6144 {
6145 let resolution =
6146 match resolve_active_using_enum_member(node, ctx) {
6147 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
6148 ActiveUsingEnumMemberResolution::Class(resolution) => {
6149 if direct_class_member_shadows(node, ctx) {
6150 return;
6151 }
6152 resolution
6153 }
6154 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
6155 if let Some(owner) = structured_enclosing_owner(node, ctx) {
6156 if direct_class_member_shadows(node, ctx) {
6157 return;
6158 }
6159 let complete_same_file_leaf =
6160 owner.source() == ctx.file
6161 && ctx.analyzer.type_hierarchy_provider().is_some_and(
6162 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
6163 );
6164 if !complete_same_file_leaf {
6165 push_unproven_hit(node, ctx);
6166 return;
6167 }
6168 }
6169 match owner_context {
6170 StructuredOwnerContextResolution::SelfTarget
6171 | StructuredOwnerContextResolution::InheritedTarget
6172 | StructuredOwnerContextResolution::NonTarget => return,
6173 StructuredOwnerContextResolution::Ambiguous => {
6174 push_unproven_hit(node, ctx);
6175 return;
6176 }
6177 StructuredOwnerContextResolution::Missing => {}
6178 }
6179 if namespace_value_shadows(node, ctx) {
6180 return;
6181 }
6182 resolution
6183 }
6184 ActiveUsingEnumMemberResolution::Missing => {
6185 if direct_class_member_shadows(node, ctx)
6186 || (structured_enclosing_owner(node, ctx).is_none()
6187 && namespace_value_shadows(node, ctx))
6188 {
6189 return;
6190 }
6191 UsingEnumMemberResolution::Missing
6192 }
6193 };
6194 match resolution {
6195 UsingEnumMemberResolution::Resolved { owner, member }
6196 if same_visible_symbol(&owner, target_owner)
6197 && same_visible_symbol(&member, &ctx.spec.target) =>
6198 {
6199 push_hit(node, ctx);
6200 }
6201 UsingEnumMemberResolution::Resolved { .. } => {}
6202 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
6203 push_unproven_hit(node, ctx)
6204 }
6205 }
6206 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
6207 push_unproven_hit(node, ctx);
6208 }
6209}
6210
6211enum ActiveUsingEnumMemberResolution {
6212 Block(UsingEnumMemberResolution),
6213 Class(UsingEnumMemberResolution),
6214 Namespace(UsingEnumMemberResolution),
6215 Missing,
6216}
6217
6218fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6219 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
6220 ctx.visibility
6221 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
6222 .into_iter()
6223 .next()
6224 .is_some()
6225 })
6226}
6227
6228fn resolve_active_using_enum_member(
6229 node: Node<'_>,
6230 ctx: &ScanCtx<'_>,
6231) -> ActiveUsingEnumMemberResolution {
6232 let block =
6233 ctx.using_enum_owners
6234 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
6235 if !matches!(block, UsingEnumMemberResolution::Missing) {
6236 return ActiveUsingEnumMemberResolution::Block(block);
6237 }
6238 let class = structured_enclosing_owner(node, ctx);
6239 let namespace = enclosing_namespace_components(node, ctx.source);
6240 match ctx.semantic_using_enum_owners.resolve_member(
6241 ctx.visibility,
6242 ctx.file,
6243 class.as_ref(),
6244 &namespace,
6245 node.start_byte(),
6246 &ctx.spec.member_name,
6247 ) {
6248 SemanticUsingEnumMemberResolution::Class(resolution) => {
6249 ActiveUsingEnumMemberResolution::Class(resolution)
6250 }
6251 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
6252 ActiveUsingEnumMemberResolution::Namespace(resolution)
6253 }
6254 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
6255 }
6256}
6257
6258fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6259 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
6260 !matches!(
6261 resolve_namespace_value(
6262 &ctx.analyzer,
6263 ctx.visibility,
6264 ctx.file,
6265 &namespace,
6266 &ctx.spec.member_name,
6267 node.start_byte(),
6268 ),
6269 NamespaceValueResolution::Missing
6270 )
6271}
6272
6273fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
6274 if node.kind() == "qualified_identifier" {
6275 return false;
6276 }
6277 let mut current = node.parent();
6278 while let Some(parent) = current {
6279 if parent.kind() == "ERROR" {
6287 return false;
6288 }
6289 if parent.kind() == "qualified_identifier" {
6290 return true;
6291 }
6292 current = parent.parent();
6293 }
6294 false
6295}
6296
6297fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6298 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
6299}
6300
6301fn receiver_type_units_with_budget(
6302 node: Node<'_>,
6303 source: &str,
6304 ctx: &ScanCtx<'_>,
6305 remaining_call_depth: usize,
6306) -> Vec<CodeUnit> {
6307 let mut current = node;
6308 let mut member_chain = Vec::new();
6309 let mut base_units = loop {
6310 match current.kind() {
6311 "field_expression" => {
6312 let Some(member) = current.child_by_field_name("field") else {
6313 return Vec::new();
6314 };
6315 let Some(receiver) = current
6316 .child_by_field_name("argument")
6317 .or_else(|| current.child_by_field_name("object"))
6318 .or_else(|| current.named_child(0))
6319 else {
6320 return Vec::new();
6321 };
6322 member_chain.push(node_text(member, source));
6323 current = receiver;
6324 }
6325 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
6326 let Some(inner) = current
6327 .child_by_field_name("argument")
6328 .or_else(|| current.named_child(0))
6329 else {
6330 return Vec::new();
6331 };
6332 current = inner;
6333 }
6334 "identifier" | "field_identifier" => {
6341 let name = node_text(current, source);
6342 let local = ctx.bindings.resolve_symbol(name);
6343 if let Some(bindings) = local.as_precise() {
6344 break receiver_units_from_bindings(current, bindings, ctx);
6345 }
6346 if ctx.bindings.is_shadowed(name) {
6347 return Vec::new();
6348 }
6349 let owner = structured_enclosing_owner(current, ctx)
6350 .filter(CodeUnit::is_class)
6351 .or_else(|| {
6352 enclosing_context(current, ctx)
6353 .owner
6354 .filter(CodeUnit::is_class)
6355 });
6356 if let Some(owner) = owner {
6357 let implicit_fields = ctx
6358 .visibility
6359 .visible_members_for_owner_name(ctx.file, &owner, name)
6360 .into_iter()
6361 .filter(|unit| unit.is_field())
6362 .collect::<Vec<_>>();
6363 if !implicit_fields.is_empty() {
6364 break receiver_units_from_declared_fields(implicit_fields, current, ctx);
6365 }
6366 }
6367 let global_fields = ctx
6368 .visibility
6369 .visible_identifier_candidates(ctx.file, name)
6370 .filter(|unit| {
6371 has_persisted_global_field_identity(unit) && unit.identifier() == name
6372 })
6373 .collect::<Vec<_>>();
6374 if global_fields.is_empty() {
6375 break ctx
6376 .visibility
6377 .resolve_type(ctx.file, name)
6378 .into_iter()
6379 .collect();
6380 }
6381 if let Some(first) = global_fields.first()
6382 && global_fields
6383 .iter()
6384 .skip(1)
6385 .any(|field| !same_visible_symbol(first, field))
6386 {
6387 return Vec::new();
6388 }
6389 break receiver_units_from_declared_fields(global_fields, current, ctx);
6390 }
6391 "call_expression" | "new_expression" => {
6392 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
6393 .and_then(|binding| binding.unit)
6394 .into_iter()
6395 .collect();
6396 }
6397 "this" if is_c_source_file(ctx.file) => {
6398 let name = node_text(current, source);
6399 let local = ctx.bindings.resolve_symbol(name);
6400 if let Some(bindings) = local.as_precise() {
6401 break receiver_units_from_bindings(current, bindings, ctx);
6402 }
6403 return Vec::new();
6404 }
6405 "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
6406 "qualified_identifier" | "scoped_identifier" => {
6407 let reference = node_text(current, source);
6408 let fields = ctx
6409 .visibility
6410 .named_candidates(ctx.file, reference, TargetKind::GlobalField)
6411 .into_iter()
6412 .filter(has_persisted_global_field_identity)
6413 .collect::<Vec<_>>();
6414 if fields.is_empty() {
6415 break ctx
6416 .visibility
6417 .resolve_type(ctx.file, reference)
6418 .into_iter()
6419 .collect();
6420 }
6421 break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
6422 }
6423 _ => {
6424 break ctx
6425 .visibility
6426 .resolve_type(ctx.file, node_text(current, source))
6427 .into_iter()
6428 .collect();
6429 }
6430 }
6431 };
6432
6433 base_units = canonical_receiver_units(base_units, ctx);
6434 if base_units.is_empty() {
6435 return Vec::new();
6436 }
6437
6438 while let Some(member_name) = member_chain.pop() {
6439 let mut next_units = Vec::new();
6440 for owner in &base_units {
6441 let fields =
6442 ctx.visibility
6443 .visible_members_for_owner_name(ctx.file, owner, member_name);
6444 for field in fields.into_iter().filter(|unit| unit.is_field()) {
6445 let Some(unit) =
6446 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
6447 .and_then(|binding| binding.unit)
6448 .or_else(|| recovered_receiver_field_type(current, field, ctx))
6449 else {
6450 continue;
6451 };
6452 if !next_units
6453 .iter()
6454 .any(|existing| same_visible_symbol(existing, &unit))
6455 {
6456 next_units.push(unit);
6457 }
6458 }
6459 }
6460 if next_units.is_empty() {
6461 return Vec::new();
6462 }
6463 base_units = unanimous_receiver_units(next_units);
6464 if base_units.is_empty() {
6465 return Vec::new();
6466 }
6467 }
6468 base_units
6469}
6470
6471fn receiver_units_from_bindings(
6472 node: Node<'_>,
6473 bindings: &HashSet<CppScanBinding>,
6474 ctx: &ScanCtx<'_>,
6475) -> Vec<CodeUnit> {
6476 let mut units = Vec::new();
6477 for binding in bindings {
6478 let raw_unit = if let Some(unit) = &binding.unit {
6479 unit.clone()
6480 } else {
6481 let Some(type_name) = binding.type_name.as_deref() else {
6482 return Vec::new();
6483 };
6484 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
6485 return Vec::new();
6486 };
6487 unit
6488 };
6489 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
6490 units.push(unit);
6491 continue;
6492 }
6493 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
6494 units.push(unit);
6495 continue;
6496 }
6497 return Vec::new();
6498 }
6499 unanimous_receiver_units(units)
6500}
6501
6502fn recovered_receiver_alias_target(
6507 reference: Node<'_>,
6508 alias: &CodeUnit,
6509 ctx: &ScanCtx<'_>,
6510) -> Option<CodeUnit> {
6511 if !ctx
6512 .analyzer
6513 .type_alias_provider()
6514 .is_some_and(|provider| provider.is_type_alias(alias))
6515 {
6516 return None;
6517 }
6518 let target = ctx.spec.owner.as_ref()?.clone();
6519 if !target.is_class() || alias.source() != ctx.file {
6520 return None;
6521 }
6522 let range = ctx
6523 .analyzer
6524 .ranges(alias)
6525 .into_iter()
6526 .find(|range| range.start_byte < range.end_byte)?;
6527 let mut node =
6528 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
6529 while node.kind() != "alias_declaration" {
6530 node = node.parent()?;
6531 }
6532 let type_descriptor = node.child_by_field_name("type")?;
6533 let type_node = first_type_child(type_descriptor).unwrap_or(type_descriptor);
6534 let resolution = resolve_type_node_lexically_for_target(
6535 type_node,
6536 &ctx.analyzer,
6537 ctx.visibility,
6538 &ctx.ordinary_type_imports,
6539 ctx.file,
6540 ctx.source,
6541 &target,
6542 Some(&ctx.lexical_scope_cache),
6543 ctx.recovered_sentinel_scope(type_node).as_deref(),
6544 );
6545 if let LexicalTypeResolution::Resolved {
6546 unit, candidates, ..
6547 } = resolution
6548 && (same_visible_symbol(&unit, &target)
6549 || candidates
6550 .iter()
6551 .any(|candidate| same_visible_symbol(candidate, &target)))
6552 {
6553 return Some(target);
6554 }
6555 let (components, global) = type_reference_components(type_node, ctx.source)?;
6556 let scope = ctx
6557 .recovered_sentinel_scope(type_node)
6558 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
6559 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
6560 let visible = ctx.visibility.external_type_candidate_visible_in_context(
6561 &ctx.analyzer,
6562 ctx.file,
6563 &target,
6564 type_node,
6565 );
6566 (path_matches && visible).then_some(target)
6567}
6568
6569fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
6570 let normalized = normalize_cpp_type_name(type_name);
6571 if normalized.is_empty() {
6572 return None;
6573 }
6574
6575 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
6580 let alias_type = receiver_type_node_base(alias_type);
6581 match ctx
6582 .visibility
6583 .resolve_type_node_result(ctx.file, alias_type, ctx.source)
6584 {
6585 Ok(Some(unit)) => return Some(unit),
6586 Err(_) => return None,
6587 Ok(None) => {}
6588 }
6589 if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
6590 return Some(unit);
6591 }
6592 }
6593
6594 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
6595 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
6596 LexicalTypeResolution::Ambiguous => return None,
6597 LexicalTypeResolution::Missing => {}
6598 }
6599 let candidates = ctx
6600 .visibility
6601 .type_name_candidates(ctx.file, &normalized)
6602 .into_iter()
6603 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
6604 .collect();
6605 unanimous_receiver_units(candidates).into_iter().next()
6606}
6607
6608fn resolve_receiver_type_node_lexically(
6609 type_node: Node<'_>,
6610 ctx: &ScanCtx<'_>,
6611) -> Option<CodeUnit> {
6612 let type_node = receiver_type_node_base(type_node);
6613 let components = cpp_type_name_components(type_node, ctx.source)?;
6614 let lexical_scope = match enclosing_lexical_scope_components(
6615 type_node,
6616 &ctx.analyzer,
6617 ctx.visibility,
6618 ctx.file,
6619 ctx.source,
6620 ) {
6621 LexicalScopeResolution::Resolved(scope) => scope,
6622 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
6623 };
6624 match ctx.visibility.resolve_type_components_lexically(
6625 &ctx.analyzer,
6626 ctx.file,
6627 &components,
6628 is_globally_qualified_cpp_name(type_node),
6629 &lexical_scope,
6630 ) {
6631 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6632 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6633 }
6634}
6635
6636fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
6637 while node.kind() == "type_descriptor" {
6638 let Some(inner) = node.child_by_field_name("type") else {
6639 break;
6640 };
6641 node = inner;
6642 }
6643 node
6644}
6645
6646fn resolve_receiver_type_name_lexically(
6647 node: Node<'_>,
6648 normalized: &str,
6649 ctx: &ScanCtx<'_>,
6650) -> LexicalTypeResolution {
6651 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6652 brokk_bifrost_core::analyzer::Language::Cpp,
6653 normalized,
6654 );
6655 if components.is_empty() {
6656 return LexicalTypeResolution::Missing;
6657 }
6658 let lexical_scope = match enclosing_lexical_scope_components(
6659 node,
6660 &ctx.analyzer,
6661 ctx.visibility,
6662 ctx.file,
6663 ctx.source,
6664 ) {
6665 LexicalScopeResolution::Resolved(scope) => scope,
6666 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
6667 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
6668 };
6669 ctx.visibility.resolve_type_components_lexically(
6670 &ctx.analyzer,
6671 ctx.file,
6672 &components,
6673 normalized.starts_with("::"),
6674 &lexical_scope,
6675 )
6676}
6677
6678fn local_receiver_alias_type_node<'tree>(
6679 node: Node<'tree>,
6680 name: &str,
6681 ctx: &ScanCtx<'_>,
6682) -> Option<Node<'tree>> {
6683 let callable = nearest_callable_scope(node)?;
6684 let mut root_callable = callable;
6685 let mut ancestor = callable.parent();
6686 while let Some(current) = ancestor {
6687 if matches!(current.kind(), "function_definition" | "lambda_expression") {
6688 root_callable = current;
6689 }
6690 ancestor = current.parent();
6691 }
6692
6693 let mut stack = vec![root_callable];
6694 let mut best = None;
6695 while let Some(current) = stack.pop() {
6696 if current.start_byte() >= node.start_byte() {
6697 continue;
6698 }
6699 if local_type_alias_name_node(current)
6700 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
6701 && local_alias_scope_contains_node(current, node)
6702 {
6703 let replace = best
6704 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
6705 if replace {
6706 best = current.child_by_field_name("type");
6707 }
6708 }
6709 let mut cursor = current.walk();
6710 stack.extend(current.named_children(&mut cursor));
6711 }
6712 best
6713}
6714
6715fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6716 let mut canonical = Vec::with_capacity(units.len());
6717 for unit in units {
6718 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
6719 return Vec::new();
6720 };
6721 canonical.push(unit);
6722 }
6723 unanimous_receiver_units(canonical)
6724}
6725
6726fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
6727 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
6728 return cached.clone();
6729 }
6730 let canonical = ctx
6731 .visibility
6732 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
6733 ctx.receiver_canonical_type_cache
6734 .borrow_mut()
6735 .insert(unit.clone(), canonical.clone());
6736 canonical
6737}
6738
6739fn receiver_units_from_declared_fields(
6740 fields: Vec<&CodeUnit>,
6741 reference: Node<'_>,
6742 ctx: &ScanCtx<'_>,
6743) -> Vec<CodeUnit> {
6744 let Some(first) = fields.first() else {
6745 return Vec::new();
6746 };
6747 if fields
6748 .iter()
6749 .skip(1)
6750 .any(|field| !same_visible_symbol(first, field))
6751 {
6752 return Vec::new();
6753 }
6754 unanimous_receiver_units(
6755 fields
6756 .into_iter()
6757 .filter_map(|field| {
6758 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
6759 .and_then(|binding| binding.unit)
6760 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
6761 })
6762 .collect(),
6763 )
6764}
6765
6766fn recovered_receiver_field_type(
6771 reference: Node<'_>,
6772 field: &CodeUnit,
6773 ctx: &ScanCtx<'_>,
6774) -> Option<CodeUnit> {
6775 let target = ctx.spec.owner.as_ref()?.clone();
6776 if !target.is_class() || field.source() != ctx.file {
6777 return None;
6778 }
6779 let range = ctx
6780 .analyzer
6781 .ranges(field)
6782 .into_iter()
6783 .find(|range| range.start_byte < range.end_byte)?;
6784 let mut declaration =
6785 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
6786 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
6787 declaration = declaration.parent()?;
6788 }
6789 let type_node = first_type_child(declaration)?;
6790 let resolution = resolve_type_node_lexically_for_target(
6791 type_node,
6792 &ctx.analyzer,
6793 ctx.visibility,
6794 &ctx.ordinary_type_imports,
6795 ctx.file,
6796 ctx.source,
6797 &target,
6798 Some(&ctx.lexical_scope_cache),
6799 ctx.recovered_sentinel_scope(type_node).as_deref(),
6800 );
6801 if let LexicalTypeResolution::Resolved {
6802 unit, candidates, ..
6803 } = resolution
6804 && (same_visible_symbol(&unit, &target)
6805 || candidates
6806 .iter()
6807 .any(|candidate| same_visible_symbol(candidate, &target)))
6808 {
6809 return Some(target);
6810 }
6811 let type_node = receiver_type_node_base(type_node);
6812 let (components, global) = type_reference_components(type_node, ctx.source)?;
6813 let scope = ctx
6814 .recovered_sentinel_scope(type_node)
6815 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
6816 (indexed_scope_matches_target_name(&scope, &components, global, &target)
6817 && ctx.visibility.external_type_candidate_visible_in_context(
6818 &ctx.analyzer,
6819 ctx.file,
6820 &target,
6821 type_node,
6822 ))
6823 .then_some(target)
6824}
6825
6826fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
6827 let mut unique = Vec::new();
6828 for unit in units {
6829 if !unique
6830 .iter()
6831 .any(|existing| same_visible_symbol(existing, &unit))
6832 {
6833 unique.push(unit);
6834 if unique.len() > 1 {
6835 return Vec::new();
6836 }
6837 }
6838 }
6839 unique
6840}
6841
6842fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6843 let Some(owner) = ctx.spec.owner.as_ref() else {
6844 return false;
6845 };
6846 match node.kind() {
6847 "field_expression" => node
6848 .child_by_field_name("argument")
6849 .or_else(|| node.child_by_field_name("object"))
6850 .is_some_and(|receiver| {
6851 receiver_is_self_like(receiver, ctx.file) && same_owner_context(receiver, ctx)
6852 || receiver_type_units(receiver, ctx.source, ctx)
6853 .iter()
6854 .any(|target| {
6855 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
6856 })
6857 }),
6858 "call_expression" => node
6859 .child_by_field_name("function")
6860 .is_some_and(|function| receiver_matches_target(function, ctx)),
6861 "pointer_expression" | "parenthesized_expression" => node
6862 .child_by_field_name("argument")
6863 .or_else(|| node.named_child(0))
6864 .is_some_and(|child| receiver_matches_target(child, ctx)),
6865 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
6866 .bindings
6867 .resolve_symbol(node_text(node, ctx.source))
6868 .as_precise()
6869 .is_some_and(|targets| {
6870 targets
6871 .iter()
6872 .filter_map(|target| target.unit.as_ref())
6873 .any(|target| {
6874 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
6875 })
6876 }),
6877 "this" => same_owner_context(node, ctx),
6878 _ => qualified_owner_matches(node, ctx),
6879 }
6880}
6881
6882fn declaring_owner_for_explicit_receiver(
6883 receiver: Node<'_>,
6884 ctx: &ScanCtx<'_>,
6885) -> EnclosingMemberOwnerResolution {
6886 if receiver_is_self_like(receiver, ctx.file) {
6887 return EnclosingMemberOwnerResolution::Missing;
6888 }
6889 let receiver_units = receiver_type_units(receiver, ctx.source, ctx);
6890 let mut declaring_owner = None;
6891 for receiver_owner in receiver_units {
6892 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
6893 receiver_owner_matches_target(&receiver_owner, target_owner, receiver.start_byte(), ctx)
6894 }) {
6895 if declaring_owner
6896 .as_ref()
6897 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
6898 {
6899 return EnclosingMemberOwnerResolution::Ambiguous;
6900 }
6901 declaring_owner = Some(receiver_owner);
6902 continue;
6903 }
6904 match cached_declaring_member_owner(&receiver_owner, ctx) {
6905 EnclosingMemberOwnerResolution::Owner(owner) => {
6906 if declaring_owner
6907 .as_ref()
6908 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
6909 {
6910 return EnclosingMemberOwnerResolution::Ambiguous;
6911 }
6912 declaring_owner = Some(owner);
6913 }
6914 EnclosingMemberOwnerResolution::Ambiguous => {
6915 return EnclosingMemberOwnerResolution::Ambiguous;
6916 }
6917 EnclosingMemberOwnerResolution::Missing => {}
6918 }
6919 }
6920 declaring_owner
6921 .map(EnclosingMemberOwnerResolution::Owner)
6922 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
6923}
6924
6925fn declaring_owner_from_call_function(
6926 function: Node<'_>,
6927 ctx: &ScanCtx<'_>,
6928) -> Option<EnclosingMemberOwnerResolution> {
6929 match function.kind() {
6930 "field_expression" => function
6931 .child_by_field_name("argument")
6932 .or_else(|| function.child_by_field_name("object"))
6933 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, ctx))
6934 .or(Some(EnclosingMemberOwnerResolution::Missing)),
6935 "call_expression" => function
6936 .child_by_field_name("function")
6937 .and_then(|inner| declaring_owner_from_call_function(inner, ctx)),
6938 _ => None,
6939 }
6940}
6941
6942enum MethodReceiverTargetResolution {
6943 Target,
6944 NonTarget,
6945 Ambiguous,
6946 Missing,
6947}
6948
6949fn method_receiver_target_resolution(
6950 node: Node<'_>,
6951 declaring_owner: EnclosingMemberOwnerResolution,
6952 ctx: &ScanCtx<'_>,
6953) -> MethodReceiverTargetResolution {
6954 let Some(target_owner) = ctx.spec.owner.as_ref() else {
6955 return MethodReceiverTargetResolution::Missing;
6956 };
6957 match declaring_owner {
6958 EnclosingMemberOwnerResolution::Owner(owner)
6959 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
6960 {
6961 MethodReceiverTargetResolution::Target
6962 }
6963 EnclosingMemberOwnerResolution::Owner(owner)
6964 if receiver_owner_is_known_non_target(&owner, target_owner, node.start_byte(), ctx) =>
6965 {
6966 MethodReceiverTargetResolution::NonTarget
6967 }
6968 EnclosingMemberOwnerResolution::Owner(_) => MethodReceiverTargetResolution::Missing,
6969 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
6970 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
6971 MethodReceiverTargetResolution::Target
6972 }
6973 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
6974 MethodReceiverTargetResolution::NonTarget
6975 }
6976 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
6977 }
6978}
6979
6980fn explicit_receiver_target_resolution(
6981 receiver: Node<'_>,
6982 ctx: &ScanCtx<'_>,
6983) -> MethodReceiverTargetResolution {
6984 method_receiver_target_resolution(
6985 receiver,
6986 declaring_owner_for_explicit_receiver(receiver, ctx),
6987 ctx,
6988 )
6989}
6990
6991fn call_function_target_resolution(
6992 function: Node<'_>,
6993 ctx: &ScanCtx<'_>,
6994) -> MethodReceiverTargetResolution {
6995 let Some(declaring_owner) = declaring_owner_from_call_function(function, ctx) else {
6996 return MethodReceiverTargetResolution::Missing;
7000 };
7001 method_receiver_target_resolution(function, declaring_owner, ctx)
7002}
7003
7004fn receiver_owner_matches_target(
7005 receiver_owner: &CodeUnit,
7006 target_owner: &CodeUnit,
7007 reference_byte: usize,
7008 ctx: &ScanCtx<'_>,
7009) -> bool {
7010 same_symbol(receiver_owner, target_owner)
7011 || same_logical_symbol(receiver_owner, target_owner)
7012 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
7013 || (ctx.spec.owner_is_forward_declaration
7014 && ctx
7015 .visibility
7016 .is_physically_visible(ctx.file, receiver_owner))
7017 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
7018 || target_group_contains_owner_peer(receiver_owner, ctx))
7019}
7020
7021fn receiver_owner_is_known_non_target(
7022 receiver_owner: &CodeUnit,
7023 target_owner: &CodeUnit,
7024 reference_byte: usize,
7025 ctx: &ScanCtx<'_>,
7026) -> bool {
7027 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
7028 return false;
7029 }
7030 if !same_logical_symbol(receiver_owner, target_owner) {
7031 return true;
7032 }
7033 !ctx.target_group.iter().any(|target| {
7034 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
7035 })
7036}
7037
7038fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
7039 ctx.visibility
7040 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
7041 && ctx.target_group.iter().any(|target| {
7042 type_owner_of(&ctx.analyzer, target)
7043 .as_ref()
7044 .is_some_and(|target_owner| {
7045 same_symbol(target_owner, owner)
7046 || (same_logical_symbol(target_owner, owner)
7047 && target_owner.source() == owner.source())
7048 })
7049 })
7050}
7051
7052fn visible_target_peer_matches_owner(
7053 owner: &CodeUnit,
7054 reference_byte: usize,
7055 ctx: &ScanCtx<'_>,
7056) -> bool {
7057 ctx.visibility
7058 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
7059 && ctx
7060 .visibility
7061 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7062 .any(|candidate| {
7063 cpp_callable_definitions_share_identity_evidence(
7064 &ctx.analyzer,
7065 candidate,
7066 &ctx.spec.target,
7067 ) && ctx.visibility.declaration_visible_at(
7068 &ctx.analyzer,
7069 ctx.file,
7070 candidate,
7071 reference_byte,
7072 ) && type_owner_of(&ctx.analyzer, candidate)
7073 .as_ref()
7074 .is_some_and(|candidate_owner| {
7075 same_symbol(candidate_owner, owner)
7076 || (same_logical_symbol(candidate_owner, owner)
7077 && candidate_owner.source() == owner.source())
7078 })
7079 })
7080}
7081
7082fn receiver_is_self_like(node: Node<'_>, file: &ProjectFile) -> bool {
7083 match node.kind() {
7084 "this" => !is_c_source_file(file),
7085 "pointer_expression" | "parenthesized_expression" => node
7086 .child_by_field_name("argument")
7087 .or_else(|| node.named_child(0))
7088 .is_some_and(|inner| receiver_is_self_like(inner, file)),
7089 _ => false,
7090 }
7091}
7092
7093fn call_function_has_direct_self_receiver(function: Node<'_>, file: &ProjectFile) -> bool {
7094 match function.kind() {
7095 "field_expression" => function
7096 .child_by_field_name("argument")
7097 .or_else(|| function.child_by_field_name("object"))
7098 .is_some_and(|receiver| receiver_is_self_like(receiver, file)),
7099 _ => receiver_is_self_like(function, file),
7100 }
7101}
7102
7103fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7104 let Some(owner) = ctx.spec.owner.as_ref() else {
7105 return false;
7106 };
7107 match node.kind() {
7108 "field_expression" => node
7109 .child_by_field_name("argument")
7110 .or_else(|| node.child_by_field_name("object"))
7111 .is_some_and(|receiver| {
7112 let units = receiver_type_units(receiver, ctx.source, ctx);
7113 !units.is_empty()
7114 && units.iter().all(|target| {
7115 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7116 })
7117 }),
7118 "call_expression" => node
7119 .child_by_field_name("function")
7120 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
7121 "pointer_expression" | "parenthesized_expression" => node
7122 .child_by_field_name("argument")
7123 .or_else(|| node.named_child(0))
7124 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
7125 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
7126 .bindings
7127 .resolve_symbol(node_text(node, ctx.source))
7128 .as_precise()
7129 .is_some_and(|targets| {
7130 let units = targets
7131 .iter()
7132 .filter_map(|target| target.unit.as_ref())
7133 .collect::<Vec<_>>();
7134 !units.is_empty()
7135 && units.iter().all(|target| {
7136 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7137 })
7138 }),
7139 "this" => known_non_target_owner_context(node, ctx),
7140 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
7141 qualified_owner_is_known_non_target(node, ctx)
7142 }
7143 _ => false,
7144 }
7145}
7146
7147#[derive(Clone, Copy, PartialEq, Eq)]
7148enum QualifiedOwnerResolution {
7149 Target,
7150 NonTarget,
7151 Unresolved,
7152}
7153
7154#[derive(Clone)]
7155pub enum LexicalScopeResolution {
7156 Resolved(Vec<String>),
7157 Ambiguous,
7158 Missing,
7159}
7160
7161type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
7162
7163fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7164 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
7165}
7166
7167fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7168 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
7169}
7170
7171fn is_structurally_qualified(node: Node<'_>) -> bool {
7172 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7173 && qualified_name_has_concrete_scope_separators(node)
7174}
7175
7176fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
7177 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7178 return QualifiedOwnerResolution::Unresolved;
7179 };
7180 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
7181 return QualifiedOwnerResolution::Unresolved;
7182 };
7183 if !global
7184 && !matches!(
7185 enclosing_lexical_scope_components(
7186 node,
7187 &ctx.analyzer,
7188 ctx.visibility,
7189 ctx.file,
7190 ctx.source,
7191 ),
7192 LexicalScopeResolution::Resolved(_)
7193 )
7194 {
7195 return QualifiedOwnerResolution::Unresolved;
7196 }
7197 match resolve_type_components_lexically_at_for_target_with_scope_cache(
7198 node,
7199 &components,
7200 global,
7201 &ctx.analyzer,
7202 ctx.visibility,
7203 &ctx.ordinary_type_imports,
7204 ctx.file,
7205 ctx.source,
7206 target_owner,
7207 false,
7208 Some(&ctx.lexical_scope_cache),
7209 ) {
7210 LexicalTypeResolution::Resolved { unit: owner, .. }
7211 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
7212 {
7213 QualifiedOwnerResolution::Target
7214 }
7215 LexicalTypeResolution::Resolved { unit: owner, .. }
7216 if same_visible_symbol(&owner, target_owner) =>
7217 {
7218 QualifiedOwnerResolution::Unresolved
7219 }
7220 LexicalTypeResolution::Resolved { .. } => QualifiedOwnerResolution::NonTarget,
7221 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7222 QualifiedOwnerResolution::Unresolved
7223 }
7224 }
7225}
7226
7227fn qualified_callable_owner_components(
7228 node: Node<'_>,
7229 source: &str,
7230) -> Option<(Vec<String>, bool)> {
7231 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7232 || !qualified_name_has_concrete_scope_separators(node)
7233 {
7234 return None;
7235 }
7236 let global = is_globally_qualified_cpp_name(node);
7237 let mut components = Vec::new();
7238 append_cpp_name_components(node, source, &mut components)?;
7239 components.pop()?;
7240 (!components.is_empty()).then_some((components, global))
7241}
7242
7243fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
7244 if !matches!(
7245 node.kind(),
7246 "identifier"
7247 | "type_identifier"
7248 | "namespace_identifier"
7249 | "qualified_identifier"
7250 | "scoped_identifier"
7251 | "scoped_type_identifier"
7252 | "template_type"
7253 | "template_function"
7254 ) {
7255 return None;
7256 }
7257 let mut components = Vec::new();
7258 append_cpp_name_components(node, source, &mut components)?;
7259 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
7260}
7261
7262pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
7263 let mut namespaces = Vec::new();
7264 let mut current = node.parent();
7265 while let Some(parent) = current {
7266 if parent.kind() == "namespace_definition"
7267 && let Some(name) = parent.child_by_field_name("name")
7268 {
7269 let mut components = Vec::new();
7270 if append_cpp_name_components(name, source, &mut components).is_some() {
7271 namespaces.push(components);
7272 }
7273 }
7274 current = parent.parent();
7275 }
7276 namespaces.reverse();
7277 namespaces.into_iter().flatten().collect()
7278}
7279
7280pub fn enclosing_lexical_scope_components(
7281 node: Node<'_>,
7282 analyzer: &CppGraphSource<'_>,
7283 visibility: &VisibilityIndex<'_>,
7284 file: &ProjectFile,
7285 source: &str,
7286) -> LexicalScopeResolution {
7287 enclosing_lexical_scope_components_with_unresolved_owner(
7288 node, analyzer, visibility, file, source, false, false,
7289 )
7290}
7291
7292#[allow(clippy::too_many_arguments)]
7293fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
7294 node: Node<'_>,
7295 analyzer: &CppGraphSource<'_>,
7296 visibility: &VisibilityIndex<'_>,
7297 file: &ProjectFile,
7298 source: &str,
7299 allow_structured_unresolved_owner: bool,
7300 ignore_function_owner: bool,
7301 cache: Option<&LexicalScopeCache>,
7302) -> LexicalScopeResolution {
7303 let Some(cache) = cache else {
7304 return enclosing_lexical_scope_components_with_unresolved_owner(
7305 node,
7306 analyzer,
7307 visibility,
7308 file,
7309 source,
7310 allow_structured_unresolved_owner,
7311 ignore_function_owner,
7312 );
7313 };
7314 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
7315 let key = (
7316 anchor_start,
7317 anchor_end,
7318 allow_structured_unresolved_owner,
7319 ignore_function_owner,
7320 );
7321 if let Some(cached) = cache.borrow().get(&key).cloned() {
7322 return cached;
7323 }
7324 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
7325 node,
7326 analyzer,
7327 visibility,
7328 file,
7329 source,
7330 allow_structured_unresolved_owner,
7331 ignore_function_owner,
7332 );
7333 cache.borrow_mut().insert(key, resolved.clone());
7334 resolved
7335}
7336
7337fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
7338 let mut current = node;
7339 loop {
7340 if matches!(
7341 current.kind(),
7342 "function_definition"
7343 | "class_specifier"
7344 | "struct_specifier"
7345 | "union_specifier"
7346 | "namespace_definition"
7347 | "translation_unit"
7348 ) {
7349 return (current.start_byte(), current.end_byte());
7350 }
7351 let Some(parent) = current.parent() else {
7352 return (current.start_byte(), current.end_byte());
7353 };
7354 current = parent;
7355 }
7356}
7357
7358fn enclosing_lexical_scope_components_with_unresolved_owner(
7359 node: Node<'_>,
7360 analyzer: &CppGraphSource<'_>,
7361 visibility: &VisibilityIndex<'_>,
7362 file: &ProjectFile,
7363 source: &str,
7364 allow_structured_unresolved_owner: bool,
7365 ignore_function_owner: bool,
7366) -> LexicalScopeResolution {
7367 #[cfg(any(test, feature = "test-support"))]
7368 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
7369 let mut namespaces = Vec::new();
7375 let mut classes = Vec::new();
7376 let mut function_definition = None;
7377 let mut displaced_class_scope = false;
7378 let mut current = node.parent();
7379 while let Some(parent) = current {
7380 match parent.kind() {
7381 "namespace_definition" => {
7382 if let Some(name) = parent.child_by_field_name("name") {
7383 let mut components = Vec::new();
7384 if append_cpp_name_components(name, source, &mut components).is_some() {
7385 namespaces.push(components);
7386 }
7387 }
7388 }
7389 "class_specifier" | "struct_specifier" | "union_specifier" => {
7390 if let Some(name) = parent.child_by_field_name("name") {
7391 let mut components = Vec::new();
7392 if append_cpp_name_components(name, source, &mut components).is_some() {
7393 classes.push(components);
7394 }
7395 }
7396 }
7397 "function_definition" => {
7398 if function_definition.is_none() {
7399 function_definition = Some(parent);
7400 }
7401 displaced_class_scope = displaced_class_scope
7402 || parent.child_by_field_name("type").is_some_and(|type_node| {
7403 matches!(
7404 type_node.kind(),
7405 "class_specifier" | "struct_specifier" | "union_specifier"
7406 )
7407 })
7408 || is_malformed_wrapper_function_definition(parent);
7409 }
7410 _ => {}
7411 }
7412 current = parent.parent();
7413 }
7414 namespaces.reverse();
7415 let namespace: Vec<String> = namespaces.into_iter().flatten().collect();
7416 let mut scope = namespace.clone();
7417 let has_qualified_function_owner = function_definition
7418 .and_then(function_definition_owner_lookup_node)
7419 .is_some_and(|owner| {
7420 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
7421 });
7422 let indexed_scope = displaced_class_scope
7423 .then(|| {
7424 indexed_structural_class_scope(visibility, file, node, source)
7425 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7426 })
7427 .flatten()
7428 .or_else(|| {
7429 (has_qualified_function_owner && function_definition.is_some())
7436 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7437 .flatten()
7438 .filter(|indexed| {
7439 qualified_owner_scope_is_recoverable(
7440 indexed,
7441 &namespace,
7442 &classes,
7443 function_definition
7444 .and_then(function_definition_owner_lookup_node)
7445 .and_then(|owner| qualified_callable_owner_components(owner, source))
7446 .map(|(components, _)| components),
7447 )
7448 })
7449 })
7450 .or_else(|| {
7451 indexed_structural_class_scope(visibility, file, node, source)
7457 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7458 .filter(|indexed| {
7459 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
7460 })
7461 })
7462 .or_else(|| {
7463 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
7467 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
7468 .flatten()
7469 .filter(|indexed| indexed.len() > namespace.len())
7470 });
7471 if let Some(indexed_scope) = indexed_scope.as_ref() {
7472 scope = indexed_scope.clone();
7477 classes.clear();
7478 }
7479
7480 if !ignore_function_owner
7481 && has_qualified_function_owner
7482 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
7483 {
7484 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
7485 return LexicalScopeResolution::Missing;
7486 };
7487 let imports = visibility.ordinary_type_import_cell(file);
7493 let owner_resolution = resolve_type_components_lexically_at_scoped(
7494 function,
7495 &owner,
7496 global,
7497 analyzer,
7498 visibility,
7499 &imports,
7500 file,
7501 source,
7502 None,
7503 false,
7504 false,
7505 namespace.clone(),
7506 );
7507 match owner_resolution {
7508 LexicalTypeResolution::Resolved {
7509 unit, components, ..
7510 } if is_indexed_class_owner(analyzer, &unit) => {
7511 scope = components;
7512 classes.clear();
7513 }
7514 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
7515 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
7516 match visibility
7517 .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
7518 {
7519 LexicalTypeResolution::Resolved { components, .. } => scope = components,
7520 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
7521 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
7522 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
7523 qualified_owner_scope_is_recoverable(
7524 indexed,
7525 &namespace,
7526 &classes,
7527 Some(owner.clone()),
7528 )
7529 }) {
7530 scope = indexed.clone();
7531 } else {
7532 scope = if global || owner.starts_with(&namespace) {
7533 owner
7534 } else {
7535 let mut relative = namespace;
7536 relative.extend(owner);
7537 relative
7538 };
7539 }
7540 }
7541 LexicalTypeResolution::Missing => {
7542 match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
7552 Some(indexed) => scope = indexed,
7553 None => return LexicalScopeResolution::Missing,
7554 }
7555 }
7556 }
7557 }
7558 }
7559 }
7560
7561 classes.reverse();
7562 scope.extend(classes.into_iter().flatten());
7563 LexicalScopeResolution::Resolved(scope)
7564}
7565
7566fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
7567 let mut current = node.parent();
7568 while let Some(parent) = current {
7569 if parent.kind() == "function_definition"
7570 && parent.child_by_field_name("type").is_some_and(|type_node| {
7571 matches!(
7572 type_node.kind(),
7573 "class_specifier" | "struct_specifier" | "union_specifier"
7574 )
7575 })
7576 {
7577 return true;
7578 }
7579 current = parent.parent();
7580 }
7581 false
7582}
7583
7584fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
7585 let mut current = node.parent();
7586 while let Some(parent) = current {
7587 if parent.kind() == "function_definition"
7588 && is_malformed_wrapper_function_definition(parent)
7589 {
7590 return true;
7591 }
7592 current = parent.parent();
7593 }
7594 false
7595}
7596
7597fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
7598 node.has_error()
7599 && node
7600 .child_by_field_name("declarator")
7601 .is_some_and(|declarator| {
7602 declarator.kind() != "function_declarator"
7603 && first_descendant_of_kind(declarator, "function_declarator").is_none()
7604 })
7605}
7606
7607fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
7613 node.child_by_field_name("scope")
7614 .and_then(|scope| recovered_macro_decorated_type_node(scope))
7615 .is_some()
7616}
7617
7618fn indexed_structural_class_scope(
7619 visibility: &VisibilityIndex<'_>,
7620 file: &ProjectFile,
7621 node: Node<'_>,
7622 source: &str,
7623) -> Option<Vec<String>> {
7624 let mut current = node.parent();
7625 while let Some(parent) = current {
7626 if matches!(
7627 parent.kind(),
7628 "class_specifier" | "struct_specifier" | "union_specifier"
7629 ) {
7630 return visibility.indexed_structural_class_scope(file, parent, source);
7631 }
7632 current = parent.parent();
7633 }
7634 None
7635}
7636
7637fn qualified_owner_scope_is_recoverable(
7648 indexed: &[String],
7649 namespace: &[String],
7650 classes: &[Vec<String>],
7651 qualified_owner: Option<Vec<String>>,
7652) -> bool {
7653 if let Some(owner) = qualified_owner {
7654 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
7655 return false;
7656 }
7657 if namespace.is_empty() {
7663 return true;
7664 }
7665 if indexed.len() <= namespace.len() {
7666 return false;
7667 }
7668 let mut prefix = indexed.iter();
7669 return namespace
7670 .iter()
7671 .all(|component| prefix.any(|candidate| candidate == component));
7672 }
7673 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
7674 if !class_components.is_empty() {
7675 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
7676 }
7677 if namespace.is_empty() || indexed.len() <= namespace.len() {
7678 return false;
7679 }
7680 let mut prefix = indexed.iter();
7681 namespace
7682 .iter()
7683 .all(|component| prefix.any(|candidate| candidate == component))
7684}
7685
7686fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
7689 unit.is_class()
7690 && !analyzer
7691 .type_alias_provider()
7692 .is_some_and(|provider| provider.is_type_alias(unit))
7693}
7694
7695fn indexed_enclosing_owner_scope(
7709 analyzer: &CppGraphSource<'_>,
7710 visibility: &VisibilityIndex<'_>,
7711 file: &ProjectFile,
7712 node: Node<'_>,
7713) -> Option<Vec<String>> {
7714 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
7715}
7716
7717fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7718 let start = enclosing_context(node, ctx).enclosing?;
7719 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
7720 ctx.analyzer.parent_of(unit)
7721 })
7722 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
7723}
7724
7725pub fn resolve_type_node_lexically(
7726 node: Node<'_>,
7727 analyzer: &CppGraphSource<'_>,
7728 visibility: &VisibilityIndex<'_>,
7729 ordinary_type_imports: &OrdinaryTypeImportCell,
7730 file: &ProjectFile,
7731 source: &str,
7732) -> LexicalTypeResolution {
7733 let Some((components, global)) = type_reference_components(node, source) else {
7734 return LexicalTypeResolution::Missing;
7735 };
7736 let resolution = resolve_type_components_lexically_at(
7737 node,
7738 &components,
7739 global,
7740 analyzer,
7741 visibility,
7742 ordinary_type_imports,
7743 file,
7744 source,
7745 );
7746 if !is_template_argument_type_leaf(node) {
7747 return resolution;
7748 }
7749
7750 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
7757 return resolution;
7758 };
7759 let namespace_scope = enclosing_namespace_components(node, source);
7760 if indexed_scope.len() <= namespace_scope.len() {
7761 return resolution;
7762 }
7763 let indexed = visibility.resolve_type_components_lexically(
7764 analyzer,
7765 file,
7766 &components,
7767 global,
7768 &indexed_scope,
7769 );
7770 match indexed {
7771 LexicalTypeResolution::Resolved { ref unit, .. }
7772 if !visibility
7773 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
7774 {
7775 resolution
7776 }
7777 LexicalTypeResolution::Resolved { .. } => indexed,
7778 _ => resolution,
7779 }
7780}
7781
7782fn is_template_argument_type_leaf(node: Node<'_>) -> bool {
7783 let Some(type_descriptor) = node.parent() else {
7784 return false;
7785 };
7786 if type_descriptor.kind() != "type_descriptor"
7787 || type_descriptor.child_by_field_name("type") != Some(node)
7788 {
7789 return false;
7790 }
7791 let Some(arguments) = type_descriptor.parent() else {
7792 return false;
7793 };
7794 if arguments.kind() != "template_argument_list" {
7795 return false;
7796 }
7797 arguments.parent().is_some_and(|parent| {
7798 matches!(parent.kind(), "template_type" | "template_function")
7799 && parent.child_by_field_name("arguments") == Some(arguments)
7800 })
7801}
7802
7803#[allow(clippy::too_many_arguments)]
7804pub fn resolve_type_node_lexically_for_target(
7805 node: Node<'_>,
7806 analyzer: &CppGraphSource<'_>,
7807 visibility: &VisibilityIndex<'_>,
7808 ordinary_type_imports: &OrdinaryTypeImportCell,
7809 file: &ProjectFile,
7810 source: &str,
7811 target: &CodeUnit,
7812 scope_cache: Option<&LexicalScopeCache>,
7813 recovered_scope: Option<&[String]>,
7814) -> LexicalTypeResolution {
7815 let Some((reference_components, global)) = type_reference_components(node, source) else {
7816 return LexicalTypeResolution::Missing;
7817 };
7818 let terminal = reference_components
7819 .last()
7820 .expect("type reference components are non-empty");
7821 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
7822 return LexicalTypeResolution::Missing;
7823 }
7824 let template_arguments = cpp_template_reference_arguments(node, source);
7825 let selects_concrete_specialization =
7826 template_arguments.is_some() && visibility.is_template_specialization(target);
7827 if !selects_concrete_specialization
7828 && !visibility.structured_type_reference_may_resolve_to_target(
7829 analyzer,
7830 file,
7831 std::slice::from_ref(terminal),
7832 false,
7833 &[],
7834 target,
7835 )
7836 {
7837 return LexicalTypeResolution::Missing;
7838 }
7839 if let Some(arguments) = template_arguments.as_ref() {
7840 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
7841 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
7842 node,
7843 &reference_components,
7844 global,
7845 analyzer,
7846 visibility,
7847 ordinary_type_imports,
7848 file,
7849 source,
7850 recovered_scope,
7851 )
7852 } else {
7853 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
7854 node,
7855 &reference_components,
7856 global,
7857 analyzer,
7858 visibility,
7859 ordinary_type_imports,
7860 file,
7861 source,
7862 scope_cache,
7863 )
7864 };
7865 return match alias_resolution {
7866 LexicalTypeResolution::Resolved {
7867 unit,
7868 components,
7869 candidates,
7870 } if visibility.template_alias_arguments_preserve_target(
7871 analyzer, file, &unit, arguments, target,
7872 ) =>
7873 {
7874 LexicalTypeResolution::Resolved {
7875 unit: target.clone(),
7876 components,
7877 candidates,
7878 }
7879 }
7880 LexicalTypeResolution::Resolved {
7881 unit,
7882 components,
7883 candidates,
7884 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
7885 Ok(resolved_unit) => {
7886 let target_guided = (!same_visible_symbol(&resolved_unit, target))
7887 .then(|| {
7888 target_guided_malformed_template_alias_resolution(
7889 node,
7890 analyzer,
7891 visibility,
7892 file,
7893 arguments,
7894 &reference_components,
7895 target,
7896 )
7897 })
7898 .flatten();
7899 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
7900 unit: resolved_unit,
7901 components,
7902 candidates,
7903 })
7904 }
7905 Err(_) => LexicalTypeResolution::Ambiguous,
7906 },
7907 LexicalTypeResolution::Missing => {
7908 let target_preserving = if let Some(recovered_scope) = recovered_scope {
7909 resolve_type_components_lexically_at_for_target_with_recovered_scope(
7910 node,
7911 &reference_components,
7912 global,
7913 analyzer,
7914 visibility,
7915 ordinary_type_imports,
7916 file,
7917 source,
7918 target,
7919 true,
7920 recovered_scope,
7921 )
7922 } else {
7923 resolve_type_components_lexically_at_for_target_with_scope_cache(
7924 node,
7925 &reference_components,
7926 global,
7927 analyzer,
7928 visibility,
7929 ordinary_type_imports,
7930 file,
7931 source,
7932 target,
7933 true,
7934 scope_cache,
7935 )
7936 };
7937 match target_preserving {
7938 LexicalTypeResolution::Resolved {
7939 unit: _,
7940 components,
7941 candidates,
7942 } if template_reference_candidates_select_target(
7943 node,
7944 &candidates,
7945 analyzer,
7946 visibility,
7947 file,
7948 source,
7949 target,
7950 ) =>
7951 {
7952 LexicalTypeResolution::Resolved {
7953 unit: target.clone(),
7954 components,
7955 candidates,
7956 }
7957 }
7958 _ => target_guided_malformed_template_alias_resolution(
7959 node,
7960 analyzer,
7961 visibility,
7962 file,
7963 arguments,
7964 &reference_components,
7965 target,
7966 )
7967 .unwrap_or(LexicalTypeResolution::Missing),
7968 }
7969 }
7970 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
7971 };
7972 }
7973 let resolution = if let Some(recovered_scope) = recovered_scope {
7974 resolve_type_components_lexically_at_for_target_with_recovered_scope(
7975 node,
7976 &reference_components,
7977 global,
7978 analyzer,
7979 visibility,
7980 ordinary_type_imports,
7981 file,
7982 source,
7983 target,
7984 true,
7985 recovered_scope,
7986 )
7987 } else {
7988 resolve_type_components_lexically_at_for_target_with_scope_cache(
7989 node,
7990 &reference_components,
7991 global,
7992 analyzer,
7993 visibility,
7994 ordinary_type_imports,
7995 file,
7996 source,
7997 target,
7998 true,
7999 scope_cache,
8000 )
8001 };
8002 if !is_template_argument_type_leaf(node) {
8003 return resolution;
8004 }
8005
8006 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8014 return resolution;
8015 };
8016 let namespace_scope = enclosing_namespace_components(node, source);
8017 if indexed_scope.len() <= namespace_scope.len() {
8018 return resolution;
8019 }
8020 let indexed = visibility.resolve_type_components_lexically_for_target(
8021 analyzer,
8022 file,
8023 &reference_components,
8024 global,
8025 &indexed_scope,
8026 target,
8027 );
8028 match indexed {
8029 LexicalTypeResolution::Resolved {
8030 ref unit,
8031 ref candidates,
8032 ..
8033 } if (same_visible_symbol(unit, target)
8034 || candidates
8035 .iter()
8036 .any(|candidate| same_visible_symbol(candidate, target)))
8037 && visibility
8038 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8039 {
8040 indexed
8041 }
8042 _ => resolution,
8043 }
8044}
8045
8046#[allow(clippy::too_many_arguments)]
8047fn target_guided_malformed_template_alias_resolution(
8048 node: Node<'_>,
8049 analyzer: &CppGraphSource<'_>,
8050 visibility: &VisibilityIndex<'_>,
8051 file: &ProjectFile,
8052 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
8053 components: &[String],
8054 target: &CodeUnit,
8055) -> Option<LexicalTypeResolution> {
8056 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
8057 return None;
8058 }
8059
8060 let identifier = &components[0];
8061 let namespace =
8062 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
8063 let namespace_name = namespace.join("::");
8064 let candidates = visibility
8065 .visible_identifier_candidates(file, identifier)
8066 .filter(|candidate| {
8067 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
8068 && visibility.type_candidate_may_be_visible_before_reference(
8069 analyzer,
8070 file,
8071 candidate,
8072 node.start_byte(),
8073 )
8074 })
8075 .cloned()
8076 .collect::<Vec<_>>();
8077 let first = candidates.first()?;
8078 if !candidates
8079 .iter()
8080 .all(|candidate| same_logical_symbol(first, candidate))
8081 || !candidates.iter().all(|candidate| {
8082 visibility.template_alias_arguments_preserve_target(
8083 analyzer, file, candidate, arguments, target,
8084 )
8085 })
8086 {
8087 return None;
8088 }
8089
8090 let mut resolved_components = namespace;
8091 resolved_components.push(identifier.clone());
8092 Some(LexicalTypeResolution::Resolved {
8093 unit: target.clone(),
8094 components: resolved_components,
8095 candidates,
8096 })
8097}
8098
8099fn resolve_type_node_lexically_for_target_without_visibility(
8100 node: Node<'_>,
8101 analyzer: &CppGraphSource<'_>,
8102 visibility: &VisibilityIndex<'_>,
8103 file: &ProjectFile,
8104 source: &str,
8105 target: &CodeUnit,
8106) -> LexicalTypeResolution {
8107 let Some((components, global)) = type_reference_components(node, source) else {
8108 return LexicalTypeResolution::Missing;
8109 };
8110 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
8111 node,
8112 analyzer,
8113 visibility,
8114 file,
8115 source,
8116 true,
8117 recovered_macro_decorated_declarator_type(node)
8118 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
8119 ) {
8120 LexicalScopeResolution::Resolved(scope) => scope,
8121 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
8122 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
8123 };
8124 visibility.resolve_type_components_lexically_for_target(
8125 analyzer,
8126 file,
8127 &components,
8128 global,
8129 &lexical_scope,
8130 target,
8131 )
8132}
8133
8134fn type_node_has_exact_target_identity_without_visibility(
8135 node: Node<'_>,
8136 analyzer: &CppGraphSource<'_>,
8137 visibility: &VisibilityIndex<'_>,
8138 file: &ProjectFile,
8139 source: &str,
8140 target: &CodeUnit,
8141) -> bool {
8142 let Some((components, global)) = type_reference_components(node, source) else {
8143 return false;
8144 };
8145 let LexicalScopeResolution::Resolved(lexical_scope) =
8146 enclosing_lexical_scope_components_with_unresolved_owner(
8147 node,
8148 analyzer,
8149 visibility,
8150 file,
8151 source,
8152 true,
8153 recovered_macro_decorated_declarator_type(node)
8154 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
8155 )
8156 else {
8157 return false;
8158 };
8159 let target_name = cpp_name_for(target);
8160 lexical_component_tiers(&components, global, &lexical_scope)
8161 .any(|qualified| qualified.join("::") == target_name)
8162}
8163
8164pub fn resolve_using_enum_declaration_owner(
8165 node: Node<'_>,
8166 analyzer: &CppGraphSource<'_>,
8167 visibility: &VisibilityIndex<'_>,
8168 ordinary_type_imports: &OrdinaryTypeImportCell,
8169 file: &ProjectFile,
8170 source: &str,
8171) -> LexicalTypeResolution {
8172 let Some(type_node) = using_enum_declaration_type_node(node) else {
8173 return LexicalTypeResolution::Missing;
8174 };
8175 let mut components = Vec::new();
8176 if append_cpp_name_components(type_node, source, &mut components).is_none()
8177 || components.is_empty()
8178 {
8179 return LexicalTypeResolution::Missing;
8180 }
8181 resolve_type_components_lexically_at(
8182 type_node,
8183 &components,
8184 is_globally_qualified_cpp_name(type_node),
8185 analyzer,
8186 visibility,
8187 ordinary_type_imports,
8188 file,
8189 source,
8190 )
8191}
8192
8193pub fn resolve_ordinary_using_declaration_owner(
8194 node: Node<'_>,
8195 analyzer: &CppGraphSource<'_>,
8196 visibility: &VisibilityIndex<'_>,
8197 file: &ProjectFile,
8198 source: &str,
8199) -> LexicalTypeResolution {
8200 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
8201 return LexicalTypeResolution::Missing;
8202 };
8203 let mut components = Vec::new();
8204 if append_cpp_name_components(type_node, source, &mut components).is_none()
8205 || components.len() < 2
8206 {
8207 return LexicalTypeResolution::Missing;
8208 }
8209 let lexical_scope =
8210 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
8211 LexicalScopeResolution::Resolved(scope) => scope,
8212 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
8213 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
8214 };
8215 visibility.resolve_type_components_lexically(
8216 analyzer,
8217 file,
8218 &components,
8219 is_globally_qualified_cpp_name(type_node),
8220 &lexical_scope,
8221 )
8222}
8223
8224pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
8225 (node.kind() == "using_declaration"
8226 && (0..node.child_count()).any(|index| {
8227 node.child(index)
8228 .is_some_and(|child| child.kind() == "enum")
8229 }))
8230 .then(|| node.named_child(0))
8231 .flatten()
8232}
8233
8234pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
8235 (node.kind() == "using_declaration"
8236 && using_enum_declaration_type_node(node).is_none()
8237 && using_namespace_directive_name_node(node).is_none())
8238 .then(|| node.named_child(0))
8239 .flatten()
8240}
8241
8242fn recovered_macro_using_declaration_type_node<'tree>(
8249 node: Node<'tree>,
8250 source: &str,
8251) -> Option<(Node<'tree>, bool)> {
8252 if node.kind() != "declaration" {
8253 return None;
8254 }
8255 let macro_type = node.child_by_field_name("type")?;
8256 if macro_type.kind() != "type_identifier"
8257 || !cpp_export_macro_token(node_text(macro_type, source))
8258 {
8259 return None;
8260 }
8261 let declarator = node.child_by_field_name("declarator")?;
8262 if declarator.kind() != "qualified_identifier" {
8263 return None;
8264 }
8265 let scope = declarator.child_by_field_name("scope")?;
8266 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
8267 return None;
8268 }
8269 let target = declarator.child_by_field_name("name")?;
8270 let mut components = Vec::new();
8271 append_cpp_name_components(target, source, &mut components)?;
8272 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
8273}
8274
8275fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
8276 let is_directive = node.kind() == "using_directive"
8277 || (node.kind() == "using_declaration"
8278 && (0..node.child_count()).any(|index| {
8279 node.child(index)
8280 .is_some_and(|child| child.kind() == "namespace")
8281 }));
8282 if !is_directive {
8283 return None;
8284 }
8285 node.child_by_field_name("name")
8286 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
8287}
8288
8289fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
8290 let mut current = node.parent();
8291 while let Some(parent) = current {
8292 if matches!(
8293 parent.kind(),
8294 "compound_statement"
8295 | "function_definition"
8296 | "lambda_expression"
8297 | "for_statement"
8298 | "while_statement"
8299 | "if_statement"
8300 | "class_specifier"
8301 | "struct_specifier"
8302 | "union_specifier"
8303 ) {
8304 return None;
8305 }
8306 current = parent.parent();
8307 }
8308 Some(enclosing_namespace_components(node, source))
8309}
8310
8311fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
8312 let mut current = node.parent();
8313 while let Some(scope) = current {
8314 if matches!(
8315 scope.kind(),
8316 "compound_statement"
8317 | "declaration_list"
8318 | "field_declaration_list"
8319 | "translation_unit"
8320 ) {
8321 let mut depth = 0;
8322 let mut ancestor = scope.parent();
8323 while let Some(parent) = ancestor {
8324 depth += 1;
8325 ancestor = parent.parent();
8326 }
8327 return Some((
8328 scope.start_byte(),
8329 scope.end_byte(),
8330 depth,
8331 scope.kind() == "compound_statement",
8332 ));
8333 }
8334 current = scope.parent();
8335 }
8336 None
8337}
8338
8339pub fn build_source_using_index(cpp: &dyn CppSource, file: &ProjectFile) -> SourceUsingIndex {
8346 let Some(prepared) = cpp.prepared_syntax(file) else {
8347 return SourceUsingIndex::default();
8348 };
8349 collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
8350}
8351
8352fn collect_source_using_index(
8353 cpp: &dyn CppSource,
8354 source_file: &ProjectFile,
8355 root: Node<'_>,
8356 source: &str,
8357) -> SourceUsingIndex {
8358 #[cfg(not(any(test, feature = "test-support")))]
8359 let _ = cpp;
8360 let mut index = SourceUsingIndex::default();
8361 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
8362 let mut stack = vec![root];
8363 while let Some(node) = stack.pop() {
8364 let target = match node.kind() {
8365 "using_directive" | "using_declaration" => {
8366 if let Some(namespace_node) = using_namespace_directive_name_node(node) {
8367 let mut namespace_components = Vec::new();
8368 append_cpp_name_components(namespace_node, source, &mut namespace_components)
8369 .map(|_| EffectiveUsingTarget::Namespace {
8370 namespace_components,
8371 global: is_globally_qualified_cpp_name(namespace_node),
8372 })
8373 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
8374 let mut target_components = Vec::new();
8375 (append_cpp_name_components(type_node, source, &mut target_components)
8376 .is_some()
8377 && target_components.len() >= 2)
8378 .then(|| EffectiveUsingTarget::Ordinary {
8379 name: target_components
8380 .last()
8381 .expect("ordinary using has a terminal component")
8382 .clone(),
8383 target_components,
8384 global: is_globally_qualified_cpp_name(type_node),
8385 })
8386 } else {
8387 None
8388 }
8389 }
8390 "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
8391 |(type_node, global)| {
8392 let mut target_components = Vec::new();
8393 (append_cpp_name_components(type_node, source, &mut target_components)
8394 .is_some()
8395 && target_components.len() >= 2)
8396 .then(|| EffectiveUsingTarget::Ordinary {
8397 name: target_components
8398 .last()
8399 .expect("recovered ordinary using has a terminal component")
8400 .clone(),
8401 target_components,
8402 global,
8403 })
8404 },
8405 ),
8406 _ => None,
8407 };
8408 if let Some(target) = target {
8409 #[cfg(any(test, feature = "test-support"))]
8415 cpp.record_using_guard_context_inspection_for_test();
8416 let required_guards = if callable_preprocessor_context_is_visible(node, source) {
8417 Some(HashSet::default())
8418 } else {
8419 preprocessor_guard_environment(node, source)
8420 };
8421 let Some(required_guards) = required_guards else {
8422 let mut cursor = node.walk();
8423 stack.extend(node.children(&mut cursor));
8424 continue;
8425 };
8426 if let Some((scope_start, scope_end, scope_depth, block_scope)) =
8427 ordinary_using_scope(node)
8428 {
8429 let declaration_namespace = enclosing_namespace_components(node, source);
8430 let declaration_namespace = if declaration_namespace.is_empty() {
8431 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
8432 .unwrap_or(declaration_namespace)
8433 } else {
8434 declaration_namespace
8435 };
8436 let namespace_scope = using_named_scope(node, source);
8437 let lexical_depth = declaration_namespace.len();
8438 let binding = OrdinaryTypeImport {
8439 target,
8440 source: source_file.clone(),
8441 declaration_byte: node.end_byte(),
8442 scope_start,
8443 scope_end,
8444 scope_depth,
8445 block_scope,
8446 lexical_depth,
8447 declaration_namespace,
8448 namespace_scope,
8449 resolved_target_components: None,
8450 required_guards,
8451 };
8452 match &binding.target {
8453 EffectiveUsingTarget::Ordinary { name, .. } => index
8454 .ordinary_by_name
8455 .entry(name.clone())
8456 .or_default()
8457 .push(binding),
8458 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
8459 }
8460 }
8461 }
8462 let mut cursor = node.walk();
8463 stack.extend(node.children(&mut cursor));
8464 }
8465 index
8466}
8467
8468struct OrphanedNamespaceEnvelope {
8469 body_end: usize,
8470 components: Vec<String>,
8471 class_names: HashSet<String>,
8472 error_marked: bool,
8473}
8474
8475fn collect_orphaned_namespace_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 && let Some(body) = current.child_by_field_name("body")
8490 && current.end_byte() == body.end_byte()
8491 && let Some(name) = current.child_by_field_name("name")
8492 {
8493 let mut components = enclosing_namespace_components(current, source);
8494 if append_cpp_name_components(name, source, &mut components).is_some()
8495 && !components.is_empty()
8496 {
8497 let mut class_names = HashSet::default();
8498 let mut body_stack = vec![body];
8499 while let Some(node) = body_stack.pop() {
8500 if let Some(name) = orphaned_class_definition_name(node, source) {
8501 class_names.insert(name);
8502 }
8503 let mut cursor = node.walk();
8504 if node.kind() == "ERROR" {
8505 body_stack.extend(node.children(&mut cursor));
8506 } else {
8507 body_stack.extend(node.named_children(&mut cursor));
8508 }
8509 }
8510 envelopes.push(OrphanedNamespaceEnvelope {
8511 body_end: body.end_byte(),
8512 components,
8513 class_names,
8514 error_marked: current.has_error(),
8515 });
8516 }
8517 }
8518 let mut cursor = current.walk();
8519 stack.extend(current.named_children(&mut cursor));
8520 }
8521 envelopes
8522}
8523
8524fn collect_orphaned_namespace_type_envelopes(
8531 root: Node<'_>,
8532 source: &str,
8533) -> Vec<OrphanedNamespaceEnvelope> {
8534 let mut envelopes = Vec::new();
8535 let mut stack = vec![root];
8536 while let Some(current) = stack.pop() {
8537 if current.kind() == "namespace_definition"
8538 && current.has_error()
8539 && let Some(body) = current.child_by_field_name("body")
8540 && current.end_byte() == body.end_byte()
8541 && let Some(name) = current.child_by_field_name("name")
8542 {
8543 let mut components = enclosing_namespace_components(current, source);
8544 if append_cpp_name_components(name, source, &mut components).is_some() {
8545 envelopes.push(OrphanedNamespaceEnvelope {
8546 body_end: body.end_byte(),
8547 components,
8548 class_names: HashSet::default(),
8549 error_marked: true,
8550 });
8551 }
8552 }
8553 if !current.has_error() {
8554 continue;
8555 }
8556 let mut cursor = current.walk();
8557 stack.extend(
8558 current
8559 .named_children(&mut cursor)
8560 .filter(|child| child.has_error()),
8561 );
8562 }
8563 envelopes
8564}
8565
8566fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
8567 if matches!(
8568 node.kind(),
8569 "class_specifier" | "struct_specifier" | "union_specifier"
8570 ) {
8571 let body = node.child_by_field_name("body")?;
8572 let name = node.child_by_field_name("name")?;
8573 return (!name.is_missing() && !body.is_missing())
8574 .then(|| node_text(name, source).to_string());
8575 }
8576 if node.kind() != "ERROR" {
8577 return None;
8578 }
8579
8580 for index in 0..node.child_count() {
8586 let Some(keyword) = node.child(index) else {
8587 continue;
8588 };
8589 if !matches!(keyword.kind(), "class" | "struct" | "union") {
8590 continue;
8591 }
8592 let mut name = None;
8593 for next_index in (index + 1)..node.child_count() {
8594 let Some(next) = node.child(next_index) else {
8595 continue;
8596 };
8597 if next.kind() == ";" {
8598 break;
8599 }
8600 if next.kind() == "{" {
8601 return name
8602 .filter(|name_node: &Node<'_>| !name_node.is_missing())
8603 .map(|name_node| node_text(name_node, source).to_string());
8604 }
8605 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
8606 name = Some(next);
8607 }
8608 }
8609 }
8610 None
8611}
8612
8613fn recovered_orphaned_namespace_components(
8614 node: Node<'_>,
8615 source: &str,
8616 envelopes: &[OrphanedNamespaceEnvelope],
8617) -> Option<Vec<String>> {
8618 let owner_name = orphaned_using_owner_name(node, source)?;
8619 envelopes
8620 .iter()
8621 .filter(|envelope| {
8622 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
8623 })
8624 .max_by_key(|envelope| envelope.body_end)
8625 .map(|envelope| envelope.components.clone())
8626}
8627
8628fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
8629 let function = std::iter::successors(node.parent(), |current| current.parent())
8630 .find(|current| current.kind() == "function_definition")?;
8631 let owner = function_definition_owner_lookup_node(function)?;
8632 let scope = owner.child_by_field_name("scope")?;
8633 let mut components = Vec::new();
8634 append_cpp_name_components(scope, source, &mut components)?;
8635 if components.len() != 1 {
8642 return None;
8643 }
8644 components.pop()
8645}
8646
8647fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
8648 let mut project = ProjectUsingIndex::default();
8649 for source_file in visibility.all_visible_source_files() {
8650 let source_index = visibility.cpp().source_using_index(&source_file);
8654 for (name, bindings) in &source_index.ordinary_by_name {
8655 project
8656 .ordinary_by_name
8657 .entry(name.clone())
8658 .or_default()
8659 .extend(bindings.iter().cloned());
8660 }
8661 project
8662 .directives
8663 .extend(source_index.directives.iter().cloned());
8664 }
8665 project
8666}
8667
8668fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
8669 visibility.project_using_index(|| build_project_using_index(visibility))
8670}
8671
8672pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
8678 let _ = project_using_index(visibility);
8679}
8680
8681fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
8682 let (components, global) = match &binding.target {
8683 EffectiveUsingTarget::Ordinary {
8684 target_components,
8685 global,
8686 ..
8687 } => (target_components, *global),
8688 EffectiveUsingTarget::Namespace {
8689 namespace_components,
8690 global,
8691 } => (namespace_components, *global),
8692 };
8693 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
8694}
8695
8696fn using_binding_target_components_for_name(
8697 binding: &OrdinaryTypeImport,
8698 project: &ProjectUsingIndex,
8699 visibility: &VisibilityIndex<'_>,
8700 file: &ProjectFile,
8701 name: &str,
8702) -> Option<Vec<String>> {
8703 let cpp_source = CppGraphSource::from_source(visibility.cpp());
8706 let visible_candidates = visibility
8707 .visible_identifier_candidates(file, name)
8708 .filter(|candidate| {
8709 candidate.is_class()
8710 || is_type_alias(candidate)
8711 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
8712 })
8713 .collect::<Vec<_>>();
8714 if visible_candidates.is_empty() {
8715 return None;
8716 }
8717 match &binding.target {
8718 EffectiveUsingTarget::Ordinary {
8719 name: imported_name,
8720 ..
8721 } if imported_name == name => {
8722 effective_using_target_tiers(binding)
8723 .into_iter()
8724 .find(|qualified| {
8725 let qualified_name = qualified.join("::");
8726 visible_candidates
8727 .iter()
8728 .any(|candidate| cpp_name_for(candidate) == qualified_name)
8729 })
8730 }
8731 EffectiveUsingTarget::Namespace { .. } => {
8732 visibility.note_using_namespace_lookup_for_test();
8733 let target_tiers = effective_using_target_tiers(binding);
8734 let resolved = target_tiers
8735 .iter()
8736 .find(|namespace_components| {
8737 let namespace = namespace_components.join("::");
8738 visible_candidates.iter().any(|candidate| {
8739 visibility.note_using_name_candidate_inspection_for_test();
8740 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
8741 candidate_namespace == namespace
8742 || candidate_namespace.starts_with(&format!("{namespace}::"))
8743 })
8744 }) || project.directives.iter().any(|candidate| {
8745 candidate.namespace_scope.as_deref()
8746 == Some(namespace_components.as_slice())
8747 }) || project
8748 .ordinary_by_name
8749 .values()
8750 .flatten()
8751 .any(|candidate| {
8752 candidate.namespace_scope.as_deref()
8753 == Some(namespace_components.as_slice())
8754 })
8755 })
8756 .cloned();
8757 resolved.or_else(|| {
8758 (target_tiers.len() == 1)
8764 .then(|| target_tiers.into_iter().next())
8765 .flatten()
8766 })
8767 }
8768 EffectiveUsingTarget::Ordinary { .. } => None,
8769 }
8770}
8771
8772fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
8773 let start = activation.checked_sub(1)?;
8774 let mut node = root.descendant_for_byte_range(start, activation)?;
8775 while node.kind() != "preproc_include" {
8776 node = node.parent()?;
8777 }
8778 Some(node)
8779}
8780
8781fn project_using_bindings(
8782 binding: OrdinaryTypeImport,
8783 visibility: &VisibilityIndex<'_>,
8784 file: &ProjectFile,
8785 root: Node<'_>,
8786 source: &str,
8787) -> Vec<OrdinaryTypeImport> {
8788 if binding.source == *file {
8789 return vec![binding];
8790 }
8791 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
8792 return Vec::new();
8793 }
8794 visibility.note_using_donor_activation_for_test();
8795 let Some(prepared) = visibility.cpp().prepared_syntax(file) else {
8796 return Vec::new();
8797 };
8798 let projections = visibility
8799 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
8800 .map_or_else(
8801 || {
8802 visibility.conditional_include_projections_for_source(
8803 file,
8804 prepared.as_ref(),
8805 &binding.source,
8806 )
8807 },
8808 |activation_byte| {
8809 Arc::from([ConditionalIncludeProjection {
8810 activation_byte,
8811 required_guards: HashSet::default(),
8812 }])
8813 },
8814 );
8815 projections
8816 .iter()
8817 .cloned()
8818 .filter_map(|projection| {
8819 let required_guards =
8820 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
8821 let mut projected = binding.clone();
8822 projected.required_guards = required_guards;
8823 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
8824 })
8825 .collect()
8826}
8827
8828fn project_using_binding_at_activation(
8829 mut binding: OrdinaryTypeImport,
8830 activation: usize,
8831 root: Node<'_>,
8832 source: &str,
8833) -> Option<OrdinaryTypeImport> {
8834 let include = include_node_for_activation(root, activation)?;
8835 let include_namespace = enclosing_namespace_components(include, source);
8836 let mut declaration_namespace = include_namespace.clone();
8837 declaration_namespace.extend(binding.declaration_namespace);
8838 binding.declaration_namespace = declaration_namespace;
8839 binding.declaration_byte = activation;
8840 if let Some(prefix) = using_named_scope(include, source) {
8841 let mut projected = prefix;
8842 projected.extend(binding.namespace_scope.take().unwrap_or_default());
8843 binding.scope_depth = projected.len();
8844 binding.block_scope = false;
8845 binding.lexical_depth = projected.len();
8846 binding.namespace_scope = Some(projected);
8847 binding.scope_start = 0;
8848 binding.scope_end = usize::MAX;
8849 Some(binding)
8850 } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
8851 binding.namespace_scope = None;
8852 binding.scope_start = start;
8853 binding.scope_end = end;
8854 binding.scope_depth = depth;
8855 binding.block_scope = block_scope;
8856 binding.lexical_depth = include_namespace.len();
8857 Some(binding)
8858 } else {
8859 None
8860 }
8861}
8862
8863pub fn effective_using_bindings_for_name(
8869 visibility: &VisibilityIndex<'_>,
8870 imports: &OrdinaryTypeImportCell,
8871 file: &ProjectFile,
8872 node: Node<'_>,
8873 source: &str,
8874 name: &str,
8875) -> Arc<[OrdinaryTypeImport]> {
8876 imports
8877 .projection_cell(name)
8878 .get_or_init(|| {
8879 let project = project_using_index(visibility);
8880 let name_bindings = project.ordinary_by_name.get(name);
8881 if name_bindings.is_none() && project.directives.is_empty() {
8882 return Arc::from(Vec::new());
8883 }
8884 let root = root_node(node);
8885 let mut projected = Vec::new();
8886 for binding in name_bindings
8887 .into_iter()
8888 .flatten()
8889 .chain(project.directives.iter())
8890 {
8891 if !visibility.source_is_visible(file, &binding.source) {
8892 continue;
8893 }
8894 let target_components = using_binding_target_components_for_name(
8895 binding, project, visibility, file, name,
8896 )
8897 .or_else(|| match &binding.target {
8898 EffectiveUsingTarget::Ordinary {
8899 name: imported_name,
8900 target_components,
8901 ..
8902 } if imported_name == name => Some(target_components.clone()),
8903 EffectiveUsingTarget::Ordinary { .. }
8904 | EffectiveUsingTarget::Namespace { .. } => None,
8905 });
8906 let Some(target_components) = target_components else {
8907 continue;
8908 };
8909 let mut binding = binding.clone();
8910 binding.resolved_target_components = Some(target_components);
8911 projected.extend(project_using_bindings(
8912 binding, visibility, file, root, source,
8913 ));
8914 }
8915 Arc::from(projected)
8916 })
8917 .clone()
8918}
8919
8920pub fn initialized_ordinary_type_imports(
8921 root: Node<'_>,
8922 analyzer: &CppGraphSource<'_>,
8923 visibility: &VisibilityIndex<'_>,
8924 file: &ProjectFile,
8925 source: &str,
8926) -> OrdinaryTypeImportCell {
8927 let cell = visibility.ordinary_type_import_cell(file);
8928 let _ = (root, analyzer, source);
8929 cell
8930}
8931
8932fn root_node(mut node: Node<'_>) -> Node<'_> {
8933 while let Some(parent) = node.parent() {
8934 node = parent;
8935 }
8936 node
8937}
8938
8939fn effective_using_binding_active(
8944 binding: &OrdinaryTypeImport,
8945 node: Node<'_>,
8946 lexical_scope: &[String],
8947 reference_guards: Option<&HashSet<PreprocessorGuard>>,
8948 visibility: &VisibilityIndex<'_>,
8949 file: &ProjectFile,
8950) -> bool {
8951 binding.declaration_byte <= node.start_byte()
8952 && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
8953 && visibility.preprocessor_guards_stable_between(
8954 file,
8955 0,
8956 node.start_byte(),
8957 &binding.required_guards,
8958 )
8959 && binding.namespace_scope.as_ref().map_or_else(
8960 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
8961 |namespace| lexical_scope.starts_with(namespace),
8962 )
8963}
8964
8965fn binding_type_candidates(
8966 binding: &OrdinaryTypeImport,
8967 active_bindings: &[&OrdinaryTypeImport],
8968 visibility: &VisibilityIndex<'_>,
8969 file: &ProjectFile,
8970 name: &str,
8971 direct_target: Option<&CodeUnit>,
8972 reference_byte: usize,
8973) -> Vec<(CodeUnit, Vec<String>)> {
8974 let Some(qualified) = binding.resolved_target_components.clone() else {
8975 return Vec::new();
8976 };
8977 let mut targets = Vec::new();
8978 match binding.target {
8979 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
8980 EffectiveUsingTarget::Namespace { .. } => {
8981 let mut stack = vec![qualified];
8982 let mut visited = HashSet::default();
8983 while let Some(namespace) = stack.pop() {
8984 if !visited.insert(namespace.clone()) {
8985 continue;
8986 }
8987 let mut target = namespace.clone();
8988 target.push(name.to_string());
8989 targets.push(target);
8990 stack.extend(active_bindings.iter().filter_map(|candidate| {
8991 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
8992 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
8993 .then(|| candidate.resolved_target_components.clone())
8994 .flatten()
8995 }));
8996 }
8997 }
8998 }
8999 targets
9000 .into_iter()
9001 .flat_map(|target| {
9002 let mut candidates = visibility
9003 .visible_identifier_candidates(file, name)
9004 .filter(|candidate| {
9005 (candidate.is_class() || is_type_alias(candidate))
9006 && macro_expanded_cpp_name_components(
9007 visibility,
9008 file,
9009 candidate,
9010 reference_byte,
9011 ) == target
9012 })
9013 .cloned()
9014 .collect::<Vec<_>>();
9015 if candidates.is_empty()
9016 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9017 && let Some(target_unit) = direct_target
9018 {
9019 let expanded_target_name = macro_expanded_cpp_name_components(
9020 visibility,
9021 file,
9022 target_unit,
9023 reference_byte,
9024 );
9025 if (target_unit.is_class() || is_type_alias(target_unit))
9026 && expanded_target_name == target
9027 && visibility.external_type_candidate_visible_at(
9028 file,
9029 target_unit,
9030 reference_byte,
9031 )
9032 {
9033 candidates.push(target_unit.clone());
9034 }
9035 }
9036 if candidates.is_empty()
9037 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9038 && let Some(target_unit) = direct_target
9039 {
9040 let visible_types = visibility
9041 .visible_identifier_candidates(file, name)
9042 .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
9043 .collect::<Vec<_>>();
9044 let uniquely_names_target = !visible_types.is_empty()
9045 && visible_types
9046 .iter()
9047 .all(|candidate| same_visible_symbol(candidate, target_unit));
9048 if uniquely_names_target {
9049 candidates.extend(visible_types.into_iter().cloned());
9050 }
9051 }
9052 candidates
9053 .into_iter()
9054 .map(move |candidate| (candidate, target.clone()))
9055 })
9056 .collect()
9057}
9058
9059fn macro_expanded_cpp_name_components(
9060 visibility: &VisibilityIndex<'_>,
9061 file: &ProjectFile,
9062 unit: &CodeUnit,
9063 reference_byte: usize,
9064) -> Vec<String> {
9065 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9066 brokk_bifrost_core::analyzer::Language::Cpp,
9067 &cpp_name_for(unit),
9068 )
9069 .into_iter()
9070 .flat_map(|component| {
9071 let Some(replacement) =
9072 visibility.object_macro_replacement_at(file, &component, reference_byte)
9073 else {
9074 return vec![component];
9075 };
9076 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9077 brokk_bifrost_core::analyzer::Language::Cpp,
9078 &replacement,
9079 );
9080 if expanded.is_empty() {
9081 vec![component]
9082 } else {
9083 expanded
9084 }
9085 })
9086 .collect()
9087}
9088
9089#[allow(clippy::too_many_arguments)]
9090fn resolved_type_import(
9091 candidates: Vec<(CodeUnit, Vec<String>)>,
9092 lexical_depth: usize,
9093 is_direct: bool,
9094 analyzer: &CppGraphSource<'_>,
9095 visibility: &VisibilityIndex<'_>,
9096 file: &ProjectFile,
9097 direct_target: Option<&CodeUnit>,
9098) -> OrdinaryTypeImportResolution {
9099 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
9100 for candidate in candidates {
9101 if !logical
9102 .iter()
9103 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
9104 {
9105 logical.push(candidate);
9106 }
9107 }
9108 let selected = match logical.as_slice() {
9109 [] => return OrdinaryTypeImportResolution::Missing,
9110 [only] => only,
9111 several => {
9116 let units = several
9117 .iter()
9118 .map(|(unit, _)| unit)
9119 .collect::<Vec<&CodeUnit>>();
9120 let Some(spelling) = direct_target.and_then(|target| {
9121 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
9122 }) else {
9123 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
9124 };
9125 several
9126 .iter()
9127 .find(|(unit, _)| same_symbol(unit, spelling))
9128 .expect("the selected spelling is one of the imported candidates")
9129 }
9130 };
9131 OrdinaryTypeImportResolution::Resolved {
9132 target: selected.0.clone(),
9133 target_components: selected.1.clone(),
9134 lexical_depth,
9135 is_direct,
9136 }
9137}
9138
9139#[allow(clippy::too_many_arguments)]
9140fn ordinary_type_import_resolution(
9141 node: Node<'_>,
9142 components: &[String],
9143 global: bool,
9144 analyzer: &CppGraphSource<'_>,
9145 visibility: &VisibilityIndex<'_>,
9146 imports: &OrdinaryTypeImportCell,
9147 file: &ProjectFile,
9148 source: &str,
9149 lexical_scope: &[String],
9150 direct_target: Option<&CodeUnit>,
9151) -> OrdinaryTypeImportResolution {
9152 if global || components.len() != 1 {
9153 return OrdinaryTypeImportResolution::Missing;
9154 }
9155 let name = &components[0];
9156 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
9157 if bindings.is_empty() {
9161 return OrdinaryTypeImportResolution::Missing;
9162 }
9163 let reference_guards = preprocessor_guard_environment(node, source);
9164 let active = bindings
9165 .iter()
9166 .filter(|binding| {
9167 effective_using_binding_active(
9168 binding,
9169 node,
9170 lexical_scope,
9171 reference_guards.as_ref(),
9172 visibility,
9173 file,
9174 )
9175 })
9176 .collect::<Vec<_>>();
9177 let transitive = bindings
9178 .iter()
9179 .filter(|binding| {
9180 binding.declaration_byte <= node.start_byte()
9181 && reference_guards
9182 .as_ref()
9183 .is_some_and(|active| binding.required_guards.is_subset(active))
9184 && visibility.preprocessor_guards_stable_between(
9185 file,
9186 0,
9187 node.start_byte(),
9188 &binding.required_guards,
9189 )
9190 && (binding.namespace_scope.is_some()
9191 || (binding.scope_start <= node.start_byte()
9192 && node.end_byte() <= binding.scope_end))
9193 })
9194 .collect::<Vec<_>>();
9195 let mut concrete_depths = active
9196 .iter()
9197 .filter(|binding| binding.namespace_scope.is_none())
9198 .map(|binding| binding.scope_depth)
9199 .collect::<Vec<_>>();
9200 concrete_depths.sort_unstable();
9201 concrete_depths.dedup();
9202 for depth in concrete_depths.into_iter().rev() {
9203 let at_tier = active
9204 .iter()
9205 .copied()
9206 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
9207 let direct = at_tier
9208 .clone()
9209 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
9210 .flat_map(|binding| {
9211 binding_type_candidates(
9212 binding,
9213 &transitive,
9214 visibility,
9215 file,
9216 name,
9217 direct_target,
9218 node.start_byte(),
9219 )
9220 })
9221 .collect::<Vec<_>>();
9222 if !direct.is_empty() {
9223 return resolved_type_import(
9224 direct,
9225 lexical_scope.len(),
9226 true,
9227 analyzer,
9228 visibility,
9229 file,
9230 direct_target,
9231 );
9232 }
9233 let directives = at_tier
9234 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
9235 .flat_map(|binding| {
9236 binding_type_candidates(
9237 binding,
9238 &transitive,
9239 visibility,
9240 file,
9241 name,
9242 direct_target,
9243 node.start_byte(),
9244 )
9245 })
9246 .collect::<Vec<_>>();
9247 if !directives.is_empty() {
9248 return resolved_type_import(
9249 directives,
9250 lexical_scope.len(),
9251 false,
9252 analyzer,
9253 visibility,
9254 file,
9255 direct_target,
9256 );
9257 }
9258 }
9259 for prefix_len in (0..=lexical_scope.len()).rev() {
9260 let tier = &lexical_scope[..prefix_len];
9261 let at_tier = active
9262 .iter()
9263 .copied()
9264 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
9265 let direct = at_tier
9266 .clone()
9267 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
9268 .flat_map(|binding| {
9269 binding_type_candidates(
9270 binding,
9271 &transitive,
9272 visibility,
9273 file,
9274 name,
9275 direct_target,
9276 node.start_byte(),
9277 )
9278 })
9279 .collect::<Vec<_>>();
9280 if !direct.is_empty() {
9281 return resolved_type_import(
9282 direct,
9283 prefix_len,
9284 true,
9285 analyzer,
9286 visibility,
9287 file,
9288 direct_target,
9289 );
9290 }
9291 let directives = at_tier
9292 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
9293 .flat_map(|binding| {
9294 binding_type_candidates(
9295 binding,
9296 &transitive,
9297 visibility,
9298 file,
9299 name,
9300 direct_target,
9301 node.start_byte(),
9302 )
9303 })
9304 .collect::<Vec<_>>();
9305 if !directives.is_empty() {
9306 return resolved_type_import(
9307 directives,
9308 prefix_len,
9309 false,
9310 analyzer,
9311 visibility,
9312 file,
9313 direct_target,
9314 );
9315 }
9316 }
9317 OrdinaryTypeImportResolution::Missing
9318}
9319
9320#[allow(clippy::too_many_arguments)]
9321pub fn resolve_type_components_lexically_at(
9322 node: Node<'_>,
9323 components: &[String],
9324 global: bool,
9325 analyzer: &CppGraphSource<'_>,
9326 visibility: &VisibilityIndex<'_>,
9327 ordinary_type_imports: &OrdinaryTypeImportCell,
9328 file: &ProjectFile,
9329 source: &str,
9330) -> LexicalTypeResolution {
9331 resolve_type_components_lexically_at_inner(
9332 node,
9333 components,
9334 global,
9335 analyzer,
9336 visibility,
9337 ordinary_type_imports,
9338 file,
9339 source,
9340 None,
9341 false,
9342 false,
9343 None,
9344 )
9345}
9346
9347#[allow(clippy::too_many_arguments)]
9348fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
9349 node: Node<'_>,
9350 components: &[String],
9351 global: bool,
9352 analyzer: &CppGraphSource<'_>,
9353 visibility: &VisibilityIndex<'_>,
9354 ordinary_type_imports: &OrdinaryTypeImportCell,
9355 file: &ProjectFile,
9356 source: &str,
9357 scope_cache: Option<&LexicalScopeCache>,
9358) -> LexicalTypeResolution {
9359 resolve_type_components_lexically_at_inner(
9360 node,
9361 components,
9362 global,
9363 analyzer,
9364 visibility,
9365 ordinary_type_imports,
9366 file,
9367 source,
9368 None,
9369 false,
9370 true,
9371 scope_cache,
9372 )
9373}
9374
9375#[allow(clippy::too_many_arguments)]
9376fn resolve_type_components_lexically_at_for_target_with_scope_cache(
9377 node: Node<'_>,
9378 components: &[String],
9379 global: bool,
9380 analyzer: &CppGraphSource<'_>,
9381 visibility: &VisibilityIndex<'_>,
9382 ordinary_type_imports: &OrdinaryTypeImportCell,
9383 file: &ProjectFile,
9384 source: &str,
9385 target: &CodeUnit,
9386 apply_structured_prefilter: bool,
9387 scope_cache: Option<&LexicalScopeCache>,
9388) -> LexicalTypeResolution {
9389 resolve_type_components_lexically_at_inner(
9390 node,
9391 components,
9392 global,
9393 analyzer,
9394 visibility,
9395 ordinary_type_imports,
9396 file,
9397 source,
9398 Some(target),
9399 apply_structured_prefilter,
9400 false,
9401 scope_cache,
9402 )
9403}
9404
9405#[allow(clippy::too_many_arguments)]
9406fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
9407 node: Node<'_>,
9408 components: &[String],
9409 global: bool,
9410 analyzer: &CppGraphSource<'_>,
9411 visibility: &VisibilityIndex<'_>,
9412 ordinary_type_imports: &OrdinaryTypeImportCell,
9413 file: &ProjectFile,
9414 source: &str,
9415 recovered_scope: &[String],
9416) -> LexicalTypeResolution {
9417 resolve_type_components_lexically_at_scoped(
9418 node,
9419 components,
9420 global,
9421 analyzer,
9422 visibility,
9423 ordinary_type_imports,
9424 file,
9425 source,
9426 None,
9427 false,
9428 true,
9429 recovered_scope.to_vec(),
9430 )
9431}
9432
9433#[allow(clippy::too_many_arguments)]
9434fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
9435 node: Node<'_>,
9436 components: &[String],
9437 global: bool,
9438 analyzer: &CppGraphSource<'_>,
9439 visibility: &VisibilityIndex<'_>,
9440 ordinary_type_imports: &OrdinaryTypeImportCell,
9441 file: &ProjectFile,
9442 source: &str,
9443 target: &CodeUnit,
9444 apply_structured_prefilter: bool,
9445 recovered_scope: &[String],
9446) -> LexicalTypeResolution {
9447 resolve_type_components_lexically_at_scoped(
9448 node,
9449 components,
9450 global,
9451 analyzer,
9452 visibility,
9453 ordinary_type_imports,
9454 file,
9455 source,
9456 Some(target),
9457 apply_structured_prefilter,
9458 false,
9459 recovered_scope.to_vec(),
9460 )
9461}
9462
9463#[allow(clippy::too_many_arguments)]
9464fn resolve_type_components_lexically_at_inner(
9465 node: Node<'_>,
9466 components: &[String],
9467 global: bool,
9468 analyzer: &CppGraphSource<'_>,
9469 visibility: &VisibilityIndex<'_>,
9470 ordinary_type_imports: &OrdinaryTypeImportCell,
9471 file: &ProjectFile,
9472 source: &str,
9473 direct_target: Option<&CodeUnit>,
9474 apply_structured_prefilter: bool,
9475 preserve_alias: bool,
9476 scope_cache: Option<&LexicalScopeCache>,
9477) -> LexicalTypeResolution {
9478 let lexical_scope = if global {
9479 Vec::new()
9480 } else {
9481 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
9482 node,
9483 analyzer,
9484 visibility,
9485 file,
9486 source,
9487 true,
9488 recovered_macro_decorated_declarator_type(node)
9489 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9490 scope_cache,
9491 ) {
9492 LexicalScopeResolution::Resolved(scope) => scope,
9493 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9494 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9495 }
9496 };
9497 resolve_type_components_lexically_at_scoped(
9498 node,
9499 components,
9500 global,
9501 analyzer,
9502 visibility,
9503 ordinary_type_imports,
9504 file,
9505 source,
9506 direct_target,
9507 apply_structured_prefilter,
9508 preserve_alias,
9509 lexical_scope,
9510 )
9511}
9512
9513#[allow(clippy::too_many_arguments)]
9514fn resolve_type_components_lexically_at_scoped(
9515 node: Node<'_>,
9516 components: &[String],
9517 global: bool,
9518 analyzer: &CppGraphSource<'_>,
9519 visibility: &VisibilityIndex<'_>,
9520 ordinary_type_imports: &OrdinaryTypeImportCell,
9521 file: &ProjectFile,
9522 source: &str,
9523 direct_target: Option<&CodeUnit>,
9524 apply_structured_prefilter: bool,
9525 preserve_alias: bool,
9526 mut lexical_scope: Vec<String>,
9527) -> LexicalTypeResolution {
9528 if apply_structured_prefilter
9529 && direct_target.is_some()
9530 && !preserve_alias
9531 && !global
9532 && components.len() == 1
9533 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
9534 {
9535 return LexicalTypeResolution::Missing;
9536 }
9537 if !global
9538 && components.len() == 1
9539 && let Some(target) = direct_target
9542 && lexical_scope
9543 .last()
9544 .is_none_or(|last| last != &components[0])
9545 && has_malformed_wrapper_function_definition_ancestor(node)
9552 && let Some(indexed_namespace) =
9553 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
9554 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
9555 {
9556 lexical_scope = indexed_namespace;
9561 }
9562 if apply_structured_prefilter
9563 && let Some(target) = direct_target
9564 && !preserve_alias
9565 && !visibility.structured_type_reference_may_resolve_to_target(
9566 analyzer,
9567 file,
9568 components,
9569 global,
9570 &lexical_scope,
9571 target,
9572 )
9573 {
9574 return LexicalTypeResolution::Missing;
9575 }
9576 let normal = if preserve_alias {
9577 visibility.resolve_type_components_lexically_for_forward(
9578 analyzer,
9579 file,
9580 components,
9581 global,
9582 &lexical_scope,
9583 )
9584 } else {
9585 direct_target.map_or_else(
9586 || {
9587 visibility.resolve_type_components_lexically(
9588 analyzer,
9589 file,
9590 components,
9591 global,
9592 &lexical_scope,
9593 )
9594 },
9595 |target| {
9596 visibility.resolve_type_components_lexically_for_target(
9597 analyzer,
9598 file,
9599 components,
9600 global,
9601 &lexical_scope,
9602 target,
9603 )
9604 },
9605 )
9606 };
9607 let normal = match normal {
9608 LexicalTypeResolution::Resolved { ref unit, .. }
9609 if !visibility
9610 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
9611 {
9612 LexicalTypeResolution::Missing
9613 }
9614 resolution => resolution,
9615 };
9616 let normal_depth = match &normal {
9617 LexicalTypeResolution::Resolved { components, .. } => {
9618 Some(components.len().saturating_sub(1))
9619 }
9620 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9621 };
9622 match ordinary_type_import_resolution(
9628 node,
9629 components,
9630 global,
9631 analyzer,
9632 visibility,
9633 ordinary_type_imports,
9634 file,
9635 source,
9636 &lexical_scope,
9637 direct_target,
9638 ) {
9639 OrdinaryTypeImportResolution::Missing => normal,
9640 OrdinaryTypeImportResolution::Resolved {
9641 lexical_depth,
9642 is_direct,
9643 ..
9644 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
9645 || normal_depth.is_some_and(|depth| {
9646 depth > lexical_depth || (!is_direct && depth == lexical_depth)
9647 }) =>
9648 {
9649 normal
9650 }
9651 OrdinaryTypeImportResolution::Resolved {
9652 target,
9653 target_components,
9654 ..
9655 } => visibility.resolve_imported_type_candidate(
9656 analyzer,
9657 file,
9658 &target,
9659 &target_components,
9660 direct_target,
9661 preserve_alias,
9662 ),
9663 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
9664 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
9665 {
9666 normal
9667 }
9668 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
9669 }
9670}
9671
9672fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9673 matches!(
9674 structured_owner_context_resolution(node, ctx),
9675 StructuredOwnerContextResolution::SelfTarget
9676 | StructuredOwnerContextResolution::InheritedTarget
9677 )
9678}
9679
9680fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9684 matches!(
9685 structured_owner_context_resolution(node, ctx),
9686 StructuredOwnerContextResolution::InheritedTarget
9687 )
9688}
9689
9690fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9691 matches!(
9692 structured_owner_context_resolution(node, ctx),
9693 StructuredOwnerContextResolution::NonTarget
9694 )
9695}
9696
9697fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9698 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9699 return false;
9700 };
9701 let mut current = node.parent();
9702 while let Some(parent) = current {
9703 if parent.kind() == "function_definition" {
9704 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
9705 return false;
9706 };
9707 if let Some(owners) = out_of_line_member_definition_owner(
9708 &ctx.analyzer,
9709 ctx.visibility,
9710 ctx.file,
9711 ctx.source,
9712 owner_lookup,
9713 ) && let Some((_, owner)) = owners.innermost()
9714 {
9715 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
9716 }
9717 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
9718 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
9719 }
9720 return false;
9721 }
9722 current = parent.parent();
9723 }
9724 false
9725}
9726
9727#[derive(Clone, Copy)]
9728enum StructuredOwnerContextResolution {
9729 SelfTarget,
9732 InheritedTarget,
9737 NonTarget,
9738 Ambiguous,
9739 Missing,
9740}
9741
9742fn structured_owner_context_resolution(
9743 node: Node<'_>,
9744 ctx: &ScanCtx<'_>,
9745) -> StructuredOwnerContextResolution {
9746 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9747 return StructuredOwnerContextResolution::Missing;
9748 };
9749 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
9750 return StructuredOwnerContextResolution::Missing;
9751 };
9752 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
9753 return StructuredOwnerContextResolution::SelfTarget;
9754 }
9755 let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
9758 match member_owner {
9759 EnclosingMemberOwnerResolution::Owner(owner)
9760 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
9761 {
9762 StructuredOwnerContextResolution::InheritedTarget
9763 }
9764 EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
9765 EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
9766 EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
9767 }
9768}
9769
9770fn cached_declaring_member_owner(
9771 receiver_owner: &CodeUnit,
9772 ctx: &ScanCtx<'_>,
9773) -> EnclosingMemberOwnerResolution {
9774 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
9775 return cached;
9776 }
9777 let resolved = resolve_declaring_member_owner(
9778 &ctx.analyzer,
9779 ctx.visibility,
9780 ctx.file,
9781 receiver_owner,
9782 &ctx.spec.member_name,
9783 );
9784 ctx.member_owner_cache
9785 .borrow_mut()
9786 .insert(receiver_owner.clone(), resolved.clone());
9787 resolved
9788}
9789
9790fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9791 if (has_recovered_class_shape_ancestor(node)
9796 || has_malformed_wrapper_function_definition_ancestor(node))
9797 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
9798 {
9799 return Some(owner);
9800 }
9801 let mut current = node.parent();
9802 while let Some(parent) = current {
9803 if parent.kind() == "function_definition" {
9804 let owner_lookup = function_definition_owner_lookup_node(parent);
9805 if let Some(owner_lookup) = owner_lookup
9806 && let Some(owners) = out_of_line_member_definition_owner(
9807 &ctx.analyzer,
9808 ctx.visibility,
9809 ctx.file,
9810 ctx.source,
9811 owner_lookup,
9812 )
9813 && let Some((_, owner)) = owners.innermost()
9814 {
9815 return Some(owner.clone());
9816 }
9817 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
9818 return Some(owner);
9819 }
9820 if let Some(owner) = enclosing_context(parent, ctx)
9821 .owner
9822 .filter(|owner| owner.is_class())
9823 {
9824 return Some(owner);
9825 }
9826 if let Some(owner_lookup) = owner_lookup
9827 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
9828 {
9829 return Some(owner);
9830 }
9831 break;
9832 }
9833 current = parent.parent();
9834 }
9835 enclosing_context(node, ctx)
9836 .owner
9837 .filter(|owner| owner.is_class())
9838}
9839
9840fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9841 let target_owner = ctx.spec.owner.as_ref()?;
9842 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
9843 let owner_name = owner_components.last()?;
9844 let mut candidates = Vec::new();
9845 for candidate in ctx
9846 .visibility
9847 .visible_identifier_candidates(ctx.file, owner_name)
9848 .filter(|candidate| candidate.is_class())
9849 {
9850 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9851 brokk_bifrost_core::analyzer::Language::Cpp,
9852 &cpp_name_for(candidate),
9853 );
9854 if !components.ends_with(&owner_components)
9855 || candidates
9856 .iter()
9857 .any(|existing| same_logical_symbol(existing, candidate))
9858 {
9859 continue;
9860 }
9861 candidates.push(candidate.clone());
9862 }
9863 let [candidate] = candidates.as_slice() else {
9864 return None;
9865 };
9866 (same_logical_symbol(candidate, target_owner)
9867 && target_group_contains_owner_peer(candidate, ctx))
9868 .then(|| candidate.clone())
9869}