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