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