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