1use crate::call_match::{
2 CppArgType, cpp_filter_candidates_by_args_with_parameter_types, cpp_literal_arg_type,
3 cpp_signature_param_types, cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6 CppSentinelRecoveredClass, cpp_active_template_type_parameter, cpp_export_macro_token,
7 cpp_sentinel_recovered_scope_for_node, is_recovered_exported_class_base_type_node, node_text,
8 normalize_cpp_whitespace, recovered_macro_return_type_node,
9};
10use crate::graph::CppGraphSource;
11use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
12use crate::graph::hits::{
13 enclosing_context, is_member_field_own_declarator, push_definition_hit, push_hit,
14 push_recursive_reference_hit, push_self_receiver_hit, push_type_hit,
15 push_unproven_definition_hit, push_unproven_hit,
16};
17use crate::graph::resolver::*;
18use crate::graph::syntax::qualified_callable_value;
19use crate::graph_support::CppSource;
20use brokk_bifrost_core::analyzer::fq_name::segment_interner;
21use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
22use brokk_bifrost_core::analyzer::usages::common::same_node;
23use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
24use brokk_bifrost_core::analyzer::usages::local_inference::{
25 LocalInferenceConfig, LocalInferenceEngine,
26};
27use brokk_bifrost_core::analyzer::usages::model::UsageHit;
28use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
29use brokk_bifrost_core::hash::{HashMap, HashSet};
30#[cfg(any(test, feature = "test-support"))]
31use std::cell::Cell;
32use std::cell::{OnceCell, RefCell};
33use std::collections::BTreeSet;
34use std::sync::Arc;
35use tree_sitter::Node;
36
37#[cfg(any(test, feature = "test-support"))]
38thread_local! {
39 pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
40}
41
42pub struct ScanState<'a> {
43 pub max_usages: usize,
44 pub hits: &'a mut BTreeSet<UsageHit>,
45 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
46 pub raw_match_count: &'a mut usize,
47 pub limit_exceeded: &'a mut bool,
48}
49
50pub struct ScanCtx<'a> {
51 pub analyzer: CppGraphSource<'a>,
52 pub visibility: &'a VisibilityIndex<'a>,
53 pub file: &'a ProjectFile,
54 pub source: &'a str,
55 ordinary_type_imports: OrdinaryTypeImportCell,
56 recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
57 class_ranges: Option<&'a ClassRangeIndex>,
58 pub line_starts: &'a [usize],
59 pub spec: &'a TargetSpec,
60 pub target_group: &'a HashSet<CodeUnit>,
61 pub has_physically_visible_type_target: bool,
62 type_reference_component_names: HashSet<String>,
63 pub target_declaration_ranges: Vec<Range>,
64 orphaned_namespaces: Vec<OrphanedNamespaceEnvelope>,
65 orphaned_namespace_scopes: OnceCell<OrphanedNamespaceTypeScopeIndex>,
66 pub bindings: LocalInferenceEngine<CppScanBinding>,
67 local_shadows: LocalInferenceEngine<()>,
68 using_enum_owners: ScopedUsingEnumOwners,
69 semantic_using_enum_owners: SemanticUsingEnumOwners,
70 needs_using_enum_member_resolution: bool,
71 pub hits: &'a mut BTreeSet<UsageHit>,
72 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
73 pub raw_match_count: &'a mut usize,
74 pub max_usages: usize,
75 pub limit_exceeded: &'a mut bool,
76 pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
77 pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
78 lexical_scope_cache: LexicalScopeCache,
79 lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
80 member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
81 global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
82 receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
83}
84
85impl ScanCtx<'_> {
86 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
87 cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
88 }
89}
90
91#[derive(Clone, Default)]
92pub struct EnclosingContext {
93 pub enclosing: Option<CodeUnit>,
94 pub owner: Option<CodeUnit>,
95}
96
97pub fn prepare_file(cpp: &dyn CppSource, file: &ProjectFile) -> Option<Arc<PreparedSyntaxTree>> {
98 cpp.prepared_syntax(file)
99}
100
101#[allow(clippy::too_many_arguments)]
102pub fn scan_prepared_file(
103 analyzer: &CppGraphSource<'_>,
104 visibility: &VisibilityIndex<'_>,
105 file: &ProjectFile,
106 prepared: &PreparedSyntaxTree,
107 recovered_sentinel_classes: &[CppSentinelRecoveredClass],
108 class_ranges: Option<&ClassRangeIndex>,
109 spec: &TargetSpec,
110 target_group: &HashSet<CodeUnit>,
111 state: &mut ScanState<'_>,
112) {
113 if *state.limit_exceeded {
114 return;
115 }
116 let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
117 let has_physically_visible_type_target = spec.kind == TargetKind::Type
118 && target_group.iter().any(|target| {
119 same_logical_symbol(target, &spec.target)
120 && visibility.is_physically_visible(file, target)
121 });
122 if spec.kind == TargetKind::Type
123 && !has_physically_visible_type_target
124 && visibility
125 .visible_identifier_candidates(file, spec.target.identifier())
126 .any(|candidate| {
127 candidate != &spec.target
128 && !target_group.contains(candidate)
129 && same_logical_symbol(candidate, &spec.target)
130 && visibility.is_physically_visible(file, candidate)
131 })
132 {
133 return;
134 }
135 let target_declaration_ranges = if spec.kind == TargetKind::Type {
136 target_group
137 .iter()
138 .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
139 .flat_map(|target| analyzer.ranges(target))
140 .collect()
141 } else if spec.target.source() == file {
142 analyzer.ranges(&spec.target)
143 } else {
144 Vec::new()
145 };
146 let ordinary_type_imports = initialized_ordinary_type_imports(
147 prepared.tree().root_node(),
148 analyzer,
149 visibility,
150 file,
151 prepared.source(),
152 );
153 let type_reference_component_names = if spec.kind == TargetKind::Type {
154 visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
155 } else {
156 HashSet::default()
157 };
158 let orphaned_namespaces = if spec.kind == TargetKind::Type
159 && prepared.tree().root_node().has_error()
160 {
161 collect_orphaned_namespace_type_envelopes(prepared.tree().root_node(), prepared.source())
162 } else {
163 Vec::new()
164 };
165 let mut ctx = ScanCtx {
166 analyzer: *analyzer,
167 visibility,
168 file,
169 source: prepared.source(),
170 ordinary_type_imports,
171 recovered_sentinel_classes,
172 class_ranges,
173 line_starts: prepared.line_starts(),
174 spec,
175 target_group,
176 has_physically_visible_type_target,
177 type_reference_component_names,
178 target_declaration_ranges,
179 orphaned_namespaces,
180 orphaned_namespace_scopes: OnceCell::new(),
181 bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
182 local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
183 using_enum_owners: ScopedUsingEnumOwners::new(),
184 semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
185 needs_using_enum_member_resolution,
186 hits: state.hits,
187 unproven_hits: state.unproven_hits,
188 raw_match_count: state.raw_match_count,
189 max_usages: state.max_usages,
190 limit_exceeded: state.limit_exceeded,
191 enclosing_cache: RefCell::new(HashMap::default()),
192 enclosing_owner_cache: RefCell::new(HashMap::default()),
193 lexical_scope_cache: RefCell::new(HashMap::default()),
194 lexical_free_function_cache: RefCell::new(HashMap::default()),
195 member_owner_cache: RefCell::new(HashMap::default()),
196 global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
197 receiver_canonical_type_cache: RefCell::new(HashMap::default()),
198 };
199 if needs_using_enum_member_resolution {
200 collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
201 }
202 scan_node(prepared.tree().root_node(), &mut ctx);
203}
204
205enum UsingEnumDeclarationScope {
206 Block,
207 Class(CodeUnit),
208 Namespace(Vec<String>),
209 UnsupportedClass,
210}
211
212fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
213 let mut current = node.parent();
214 while let Some(parent) = current {
215 if matches!(
216 parent.kind(),
217 "compound_statement"
218 | "function_definition"
219 | "lambda_expression"
220 | "for_statement"
221 | "while_statement"
222 | "if_statement"
223 ) {
224 return UsingEnumDeclarationScope::Block;
225 }
226 if matches!(
227 parent.kind(),
228 "class_specifier" | "struct_specifier" | "union_specifier"
229 ) {
230 let resolution = enclosing_lexical_scope_components(
231 node,
232 &ctx.analyzer,
233 ctx.visibility,
234 ctx.file,
235 ctx.source,
236 );
237 if let LexicalScopeResolution::Resolved(components) = resolution
238 && let LexicalTypeResolution::Resolved { unit, .. } =
239 ctx.visibility.resolve_type_components_lexically(
240 &ctx.analyzer,
241 ctx.file,
242 &components,
243 true,
244 &[],
245 )
246 {
247 return UsingEnumDeclarationScope::Class(unit);
248 }
249 return UsingEnumDeclarationScope::UnsupportedClass;
250 }
251 current = parent.parent();
252 }
253 UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
254}
255
256fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
257 let mut stack = vec![root];
258 while let Some(node) = stack.pop() {
259 if node.kind() == "using_declaration"
260 && let LexicalTypeResolution::Resolved { unit, .. } =
261 resolve_using_enum_declaration_owner(
262 node,
263 &ctx.analyzer,
264 ctx.visibility,
265 &ctx.ordinary_type_imports,
266 ctx.file,
267 ctx.source,
268 )
269 {
270 match using_enum_declaration_scope(node, ctx) {
271 UsingEnumDeclarationScope::Block => {}
272 UsingEnumDeclarationScope::Class(class) => {
273 ctx.semantic_using_enum_owners.import_class(class, unit);
274 }
275 UsingEnumDeclarationScope::Namespace(namespace) => {
276 ctx.semantic_using_enum_owners.import_namespace(
277 namespace,
278 node.start_byte(),
279 unit,
280 );
281 }
282 UsingEnumDeclarationScope::UnsupportedClass => {}
283 }
284 }
285 for index in (0..node.named_child_count()).rev() {
286 if let Some(child) = node.named_child(index) {
287 stack.push(child);
288 }
289 }
290 }
291}
292
293fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
294 if *ctx.limit_exceeded {
295 return;
296 }
297 let enters_scope = matches!(
298 node.kind(),
299 "compound_statement"
300 | "function_definition"
301 | "lambda_expression"
302 | "for_statement"
303 | "for_range_loop"
304 | "while_statement"
305 | "if_statement"
306 );
307 let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
308 && (enters_scope
309 || matches!(
310 node.kind(),
311 "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
312 ));
313 if enters_scope {
314 ctx.bindings.enter_scope();
315 ctx.local_shadows.enter_scope();
316 }
317 if enters_using_enum_scope {
318 ctx.using_enum_owners.enter_scope();
319 }
320
321 seed_declarations(node, ctx);
322 maybe_record_hit(node, ctx);
323
324 let mut cursor = node.walk();
325 for child in node.named_children(&mut cursor) {
326 scan_node(child, ctx);
327 if *ctx.limit_exceeded {
328 break;
329 }
330 }
331
332 if enters_scope {
333 ctx.bindings.exit_scope();
334 ctx.local_shadows.exit_scope();
335 }
336 if enters_using_enum_scope {
337 ctx.using_enum_owners.exit_scope();
338 }
339}
340
341fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
342 if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
343 || (ctx.spec.kind != TargetKind::Type
344 && indexed_recovered_class_field_declaration(node, ctx))
345 {
346 return;
347 }
348 match node.kind() {
349 "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
350 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
351 "for_range_loop" => seed_range_binding(node, ctx),
352 "expression_statement" => seed_function_macro_local_binding(node, ctx),
353 "using_declaration" => seed_using_enum(node, ctx),
354 _ => {}
355 }
356}
357
358fn seed_function_macro_local_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
359 let Some(binding) = ctx
360 .visibility
361 .function_macro_local_binding(ctx.file, node, ctx.source)
362 else {
363 return;
364 };
365 if ctx.spec.kind == TargetKind::Type {
366 ctx.bindings.declare_shadow(binding.name);
367 return;
368 }
369 let normalized = normalize_cpp_type_name(&binding.type_name);
370 let unit = binding
371 .type_node
372 .and_then(|type_node| {
373 ctx.visibility
374 .resolve_type_node_result(ctx.file, type_node, ctx.source)
375 .ok()
376 .flatten()
377 })
378 .or_else(|| {
379 ctx.visibility
380 .canonical_type_for_reference(ctx.file, &normalized)
381 })
382 .or_else(|| ctx.visibility.resolve_type(ctx.file, &normalized));
383 ctx.bindings.seed_symbol(
384 binding.name,
385 CppScanBinding::from_type_name(
386 normalized,
387 unit,
388 binding.pointer_depth + cpp_type_text_pointer_depth(&binding.type_name),
389 ),
390 );
391}
392
393fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
394 if node.kind() != "declaration"
395 || !has_function_scope_ancestor(node)
396 || has_ancestor_kind(node, "lambda_expression")
397 || !(has_recovered_class_shape_ancestor(node)
398 || has_malformed_wrapper_function_definition_ancestor(node))
399 {
400 return false;
401 }
402 let context = enclosing_context(node, ctx);
403 context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
404 && context.owner.as_ref().is_some_and(CodeUnit::is_class)
405}
406
407fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
408 if !ctx.needs_using_enum_member_resolution {
409 return;
410 }
411 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
412 node,
413 &ctx.analyzer,
414 ctx.visibility,
415 &ctx.ordinary_type_imports,
416 ctx.file,
417 ctx.source,
418 ) && matches!(
419 using_enum_declaration_scope(node, ctx),
420 UsingEnumDeclarationScope::Block
421 ) {
422 ctx.using_enum_owners.import(unit);
423 }
424}
425
426fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
427 if node.kind() == "field_declaration" {
432 return;
433 }
434 let type_node = node
435 .child_by_field_name("type")
436 .or_else(|| first_type_child(node));
437 let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
438 let mut cursor = node.walk();
439 for child in node.named_children(&mut cursor) {
440 let declarator = if child.kind() == "init_declarator" {
441 child.child_by_field_name("declarator")
442 } else if is_declarator_node(child) {
443 Some(child)
444 } else {
445 None
446 };
447 let Some(declarator) = declarator else {
448 continue;
449 };
450 let Some(name) = extract_variable_name(declarator, ctx.source) else {
451 continue;
452 };
453 if declarator.kind() == "function_declarator"
454 && !constructor_style_local_declaration(
455 ctx.visibility,
456 ctx.file,
457 ctx.source,
458 declarator,
459 type_text.as_deref(),
460 &ctx.bindings,
461 )
462 {
463 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
464 ctx.local_shadows.declare_shadow(name);
465 }
466 continue;
467 }
468 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
469 ctx.local_shadows.declare_shadow(name.clone());
470 }
471 if ctx.spec.kind == TargetKind::Type {
472 ctx.bindings.declare_shadow(name);
473 continue;
474 }
475 let value = child.child_by_field_name("value");
476 seed_binding_from_type_or_value(&name, type_node, value, ctx);
477 }
478}
479
480fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
481 if !parameter_belongs_to_callable_scope(node) {
482 return;
483 }
484 let Some(declarator) = node.child_by_field_name("declarator") else {
485 return;
486 };
487 let Some(name) = extract_variable_name(declarator, ctx.source) else {
488 return;
489 };
490 if has_function_scope_ancestor(node) {
491 ctx.local_shadows.declare_shadow(name.clone());
492 }
493 if ctx.spec.kind == TargetKind::Type {
494 ctx.bindings.declare_shadow(name);
495 return;
496 }
497 let type_node = node
498 .child_by_field_name("type")
499 .or_else(|| first_type_child(node));
500 seed_binding_from_type_or_value(&name, type_node, None, ctx);
501}
502
503fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
504 let Some(declarator) = node.child_by_field_name("declarator") else {
505 return;
506 };
507 let Some(name) = extract_variable_name(declarator, ctx.source) else {
508 return;
509 };
510 if has_function_scope_ancestor(node) {
511 ctx.local_shadows.declare_shadow(name.clone());
512 }
513 if ctx.spec.kind == TargetKind::Type {
514 ctx.bindings.declare_shadow(name);
515 return;
516 }
517 let type_node = node
518 .child_by_field_name("type")
519 .or_else(|| first_type_child(node));
520 seed_binding_from_type_or_value(&name, type_node, None, ctx);
521}
522
523fn has_function_scope_ancestor(node: Node<'_>) -> bool {
524 let mut current = node.parent();
525 while let Some(parent) = current {
526 if parent.kind() == "namespace_definition" {
534 return false;
535 }
536 if parent.kind() == "function_definition" {
537 return !is_malformed_wrapper_function_definition(parent);
544 }
545 if parent.kind() == "lambda_expression" {
546 return true;
547 }
548 current = parent.parent();
549 }
550 false
551}
552
553fn seed_binding_from_type_or_value(
554 name: &str,
555 type_node: Option<Node<'_>>,
556 value: Option<Node<'_>>,
557 ctx: &mut ScanCtx<'_>,
558) {
559 if name.is_empty() {
560 return;
561 }
562 let resolved = type_node
563 .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
564 .map(|node| {
565 let text = node_text(node, ctx.source);
566 let name = normalize_cpp_type_name(text);
567 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
572 if cpp_template_reference_arguments(node, ctx.source).is_some() {
573 return None;
574 }
575 match enclosing_lexical_scope_components(
576 node,
577 &ctx.analyzer,
578 ctx.visibility,
579 ctx.file,
580 ctx.source,
581 ) {
582 LexicalScopeResolution::Resolved(scope) => Some(scope),
583 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
584 }
585 });
586 let unit = lexical_scope
587 .as_deref()
588 .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
589 .or_else(|| {
590 match ctx
591 .visibility
592 .resolve_type_node_result(ctx.file, node, ctx.source)
593 {
594 Ok(Some(unit)) => Some(unit),
595 Ok(None) => ctx
596 .visibility
597 .canonical_type_for_reference(ctx.file, &name)
598 .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
599 Err(_) => None,
600 }
601 });
602 CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
603 })
604 .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
605
606 if let Some(resolved) = resolved {
607 ctx.bindings.seed_symbol(name.to_string(), resolved);
608 } else if let Some(value) = value
609 && value.kind() == "identifier"
610 {
611 ctx.bindings
612 .alias_symbol(name.to_string(), node_text(value, ctx.source));
613 } else {
614 ctx.bindings.declare_shadow(name.to_string());
615 }
616}
617
618fn resolve_seed_type_node_lexically(
619 node: Node<'_>,
620 ctx: &ScanCtx<'_>,
621 scope: &[String],
622) -> Option<CodeUnit> {
623 let (components, global) = type_reference_components(node, ctx.source)?;
624 match ctx.visibility.resolve_type_components_lexically(
625 &ctx.analyzer,
626 ctx.file,
627 &components,
628 global,
629 scope,
630 ) {
631 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
632 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
633 }
634}
635
636const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
637
638fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
639 infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
640}
641
642fn infer_type_from_value_with_budget(
643 node: Node<'_>,
644 ctx: &ScanCtx<'_>,
645 remaining_call_depth: usize,
646) -> Option<CppScanBinding> {
647 match node.kind() {
648 "new_expression" | "call_expression" if remaining_call_depth == 0 => {
649 infer_cpp_initializer_binding(
650 &ctx.analyzer,
651 ctx.visibility,
652 ctx.file,
653 ctx.source,
654 node,
655 None,
656 )
657 }
658 "new_expression" | "call_expression" => infer_cpp_initializer_binding(
659 &ctx.analyzer,
660 ctx.visibility,
661 ctx.file,
662 ctx.source,
663 node,
664 Some(&|receiver, source| {
665 receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
666 }),
667 ),
668 "initializer_list" => None,
669 "identifier" => {
670 let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
671 resolved
672 .as_precise()?
673 .iter()
674 .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
675 .cloned()
676 }
677 _ => {
678 let text = node_text(node, ctx.source);
679 let name = normalize_cpp_type_name(text);
680 ctx.visibility
681 .resolve_type(ctx.file, &name)
682 .map(|unit| CppScanBinding::from_unit(unit, 0))
683 }
684 }
685}
686
687fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
688 match ctx.spec.kind {
689 TargetKind::Type => maybe_record_type_hit(node, ctx),
690 TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
691 TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
692 TargetKind::Method => maybe_record_method_hit(node, ctx),
693 TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
694 TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
695 TargetKind::Macro => maybe_record_macro_hit(node, ctx),
696 }
697}
698
699fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
700 if node_text(node, ctx.source) != ctx.spec.member_name {
701 return;
702 }
703 if is_ordinary_macro_reference_node(node) {
704 match ctx.visibility.resolve_ordinary_macro_reference(
705 &ctx.analyzer,
706 ctx.file,
707 node,
708 ctx.source,
709 ) {
710 OrdinaryMacroReferenceResolution::Resolved(unit)
711 if ctx.target_group.contains(&unit) =>
712 {
713 *ctx.raw_match_count += 1;
714 push_hit(node, ctx);
715 }
716 OrdinaryMacroReferenceResolution::Ambiguous
717 if ctx
718 .visibility
719 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target) =>
720 {
721 *ctx.raw_match_count += 1;
722 push_unproven_hit(node, ctx);
723 }
724 OrdinaryMacroReferenceResolution::Resolved(_)
725 | OrdinaryMacroReferenceResolution::Ambiguous
726 | OrdinaryMacroReferenceResolution::Missing => {}
727 }
728 return;
729 }
730 if !matches!(
731 node.kind(),
732 "identifier"
733 | "field_identifier"
734 | "type_identifier"
735 | "namespace_identifier"
736 | "preproc_arg"
737 ) || node.parent().is_some_and(|parent| {
738 matches!(parent.kind(), "preproc_def" | "preproc_function_def")
739 && parent
740 .child_by_field_name("name")
741 .is_some_and(|name| same_node(name, node))
742 }) {
743 return;
744 }
745 if ctx.visibility.macro_binding_matches_target_at(
746 &ctx.analyzer,
747 ctx.file,
748 &ctx.spec.member_name,
749 node.start_byte(),
750 &ctx.spec.target,
751 ) {
752 *ctx.raw_match_count += 1;
753 push_hit(node, ctx);
754 } else if ctx.visibility.macro_name_may_be_bound_at(
755 ctx.file,
756 &ctx.spec.member_name,
757 node.start_byte(),
758 ) && ctx
759 .visibility
760 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
761 {
762 *ctx.raw_match_count += 1;
763 push_unproven_hit(node, ctx);
764 }
765}
766
767fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
768 let recovered_exported_class_base =
769 is_recovered_exported_class_base_type_node(node, ctx.source);
770 if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
771 maybe_record_recovered_macro_return_type_hit(return_type, ctx);
772 return;
773 }
774 if let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source) {
775 if ctx
782 .analyzer
783 .type_alias_provider()
784 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
785 && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
786 && let Some(terminal) = owner.nodes.last().copied()
787 {
788 if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
789 return;
790 }
791 if ctx.visibility.external_type_candidate_visible_in_context(
792 &ctx.analyzer,
793 ctx.file,
794 &ctx.spec.target,
795 terminal,
796 ) || ctx
797 .visibility
798 .dependent_member_pointer_alias_visible_in_context(
799 &ctx.analyzer,
800 ctx.file,
801 &ctx.spec.target,
802 &owner.names,
803 terminal,
804 )
805 {
806 *ctx.raw_match_count += 1;
807 push_type_hit(terminal, ctx);
808 }
809 return;
810 }
811 if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
812 *ctx.raw_match_count += 1;
813 for scope in scopes {
814 push_type_hit(scope, ctx);
815 }
816 return;
817 }
818 return;
819 }
820 if node.kind() == "pointer_expression"
821 && let Some(value) = qualified_callable_value(node)
822 && let Some(scope) =
823 target_guided_unproven_qualified_value_owner_scope(value.qualified, ctx)
824 {
825 *ctx.raw_match_count += 1;
826 push_unproven_hit(scope, ctx);
827 return;
828 }
829 if node.kind() == "call_expression" {
830 maybe_record_direct_temporary_type_hit(node, ctx);
831 return;
832 }
833 if node.parent().is_some_and(|parent| {
834 parent.kind() == "operator_cast"
835 && parent
836 .child_by_field_name("type")
837 .is_some_and(|target| same_node(target, node))
838 }) {
839 return;
840 }
841 if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
842 *ctx.raw_match_count += 1;
843 push_type_hit(hit, ctx);
844 return;
845 }
846 if matches!(node.kind(), "identifier" | "template_function")
847 && call_for_function_node(node).is_some()
848 {
849 return;
850 }
851 if node.kind() == "using_declaration" {
852 let (resolution, type_node) =
853 if let Some(type_node) = using_enum_declaration_type_node(node) {
854 (
855 resolve_using_enum_declaration_owner(
856 node,
857 &ctx.analyzer,
858 ctx.visibility,
859 &ctx.ordinary_type_imports,
860 ctx.file,
861 ctx.source,
862 ),
863 type_node,
864 )
865 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
866 (
867 resolve_ordinary_using_declaration_owner(
868 node,
869 &ctx.analyzer,
870 ctx.visibility,
871 ctx.file,
872 ctx.source,
873 ),
874 type_node,
875 )
876 } else {
877 return;
878 };
879 if let LexicalTypeResolution::Resolved { unit, .. } = resolution
880 && same_visible_symbol(&unit, &ctx.spec.target)
881 {
882 *ctx.raw_match_count += 1;
883 push_type_hit(type_node, ctx);
884 }
885 return;
886 }
887 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
888 let mut matching = Vec::new();
896 for candidate in [node, type_node] {
897 if matching
898 .iter()
899 .any(|existing| same_node(*existing, candidate))
900 {
901 continue;
902 }
903 if let LexicalTypeResolution::Resolved {
904 unit, candidates, ..
905 } = resolve_type_node_lexically_for_target(
906 candidate,
907 &ctx.analyzer,
908 ctx.visibility,
909 &ctx.ordinary_type_imports,
910 ctx.file,
911 ctx.source,
912 &ctx.spec.target,
913 Some(&ctx.lexical_scope_cache),
914 ctx.recovered_sentinel_scope(candidate).as_deref(),
915 ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
916 {
917 matching.push(candidate);
918 }
919 }
920 match matching.as_slice() {
921 [candidate] if !same_node(*candidate, node) => {
922 maybe_record_type_hit(*candidate, ctx);
923 return;
924 }
925 [candidate] if same_node(*candidate, node) => {}
926 [] => {}
927 _ => return,
928 }
929 }
930 let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
931 let recovered_qualified_friend =
932 is_recovered_qualified_friend_class_type_reference(node, ctx.source);
933 if !recovered_type
934 && !matches!(
935 node.kind(),
936 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
937 )
938 {
939 return;
940 }
941 if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
942 components.len() == 1 && !global && local_type_name_shadows(node, ctx)
943 }) {
944 return;
945 }
946 if !recovered_type
947 && !recovered_qualified_friend
948 && !recovered_exported_class_base
949 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
950 && is_declaration_name(node)
951 && let Some(owners) = out_of_line_member_definition_owner(
952 &ctx.analyzer,
953 ctx.visibility,
954 ctx.file,
955 ctx.source,
956 node,
957 )
958 {
959 let terminal_destructor = out_of_line_destructor_type_reference(node);
960 let innermost = owners.innermost().map(|(_, owner)| owner.clone());
961 *ctx.raw_match_count += 1;
962 let mut matched_owner = false;
963 for (owner_node, owner) in owners.owners {
964 if same_visible_symbol(&owner, &ctx.spec.target) {
965 matched_owner = true;
966 push_guarded_owner_hit(owner_node, &owner, node, ctx);
967 }
968 }
969 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
970 for scope in scopes {
971 push_hit(scope, ctx);
972 }
973 } else if !matched_owner
974 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
975 {
976 push_unproven_hit(scope, ctx);
977 }
978 if let Some(terminal_destructor) = terminal_destructor
979 && innermost
980 .as_ref()
981 .is_some_and(|owner| same_visible_symbol(owner, &ctx.spec.target))
982 {
983 push_hit(terminal_destructor, ctx);
984 }
985 return;
986 }
987 if !recovered_type
988 && !recovered_qualified_friend
989 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
990 && is_declaration_name(node)
991 && let Some(owner) = indexed_out_of_line_template_owner_hit(node, ctx)
992 {
993 *ctx.raw_match_count += 1;
994 push_type_hit(owner, ctx);
995 if let Some(destructor) = out_of_line_destructor_type_reference(node) {
996 push_type_hit(destructor, ctx);
997 }
998 return;
999 }
1000 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1006 && matches!(
1007 node.kind(),
1008 "qualified_identifier" | "scoped_type_identifier"
1009 )
1010 && node
1011 .child_by_field_name("name")
1012 .is_some_and(|name| name.kind() == "template_type")
1013 {
1014 return;
1015 }
1016 if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
1017 *ctx.raw_match_count += 1;
1018 push_unproven_hit(scope, ctx);
1019 return;
1020 }
1021 if !recovered_type
1022 && !is_nested_type_node(node)
1023 && matches!(
1024 node.kind(),
1025 "qualified_identifier" | "scoped_type_identifier"
1026 )
1027 && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx)
1028 {
1029 *ctx.raw_match_count += 1;
1030 for scope in scopes {
1031 push_type_hit(scope, ctx);
1032 }
1033 return;
1034 }
1035 if !recovered_type && is_nested_type_node(node) {
1036 if let Some(hit) = out_of_line_dependent_return_template_owner(node, ctx) {
1037 *ctx.raw_match_count += 1;
1038 push_type_hit(hit, ctx);
1039 return;
1040 }
1041 if let Some(hit) = target_guided_nested_type_terminal_hit(node, ctx) {
1042 *ctx.raw_match_count += 1;
1043 push_type_hit(hit, ctx);
1044 return;
1045 }
1046 let nested_template = if node.kind() == "template_type" {
1053 Some(node)
1054 } else {
1055 node.parent().filter(|parent| {
1056 parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
1057 })
1058 };
1059 let nested_alias_qualifier = nested_template.is_some_and(|template| {
1066 let enclosing_qualified_type_owns_range = template.parent().is_some_and(|parent| {
1067 matches!(
1068 parent.kind(),
1069 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1070 ) && parent.child_by_field_name("name") == Some(template)
1071 });
1072 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
1073 return false;
1074 };
1075 let Some(name) = template_reference_name_node(template) else {
1076 return false;
1077 };
1078 let alias_candidates = ctx
1079 .visibility
1080 .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
1081 .filter(|candidate| alias_provider.is_type_alias(candidate))
1082 .cloned()
1083 .collect::<Vec<_>>();
1084 let direct_target_alias_visible = !alias_candidates
1085 .iter()
1086 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1087 || ctx.visibility.external_type_candidate_visible_in_context(
1088 &ctx.analyzer,
1089 ctx.file,
1090 &ctx.spec.target,
1091 template,
1092 );
1093 !enclosing_qualified_type_owns_range
1094 && direct_target_alias_visible
1095 && template_reference_candidates_select_target(
1096 template,
1097 &alias_candidates,
1098 &ctx.analyzer,
1099 ctx.visibility,
1100 ctx.file,
1101 ctx.source,
1102 &ctx.spec.target,
1103 )
1104 });
1105 if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
1106 return;
1107 }
1108 if nested_alias_qualifier {
1109 let template = nested_template.expect("a nested alias qualifier has a template node");
1110 *ctx.raw_match_count += 1;
1111 let hit = target_guided_missing_alias_rhs_type_leaf(template, ctx)
1112 .unwrap_or_else(|| type_reference_hit_node(template));
1113 push_type_hit(hit, ctx);
1114 return;
1115 }
1116 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1123 && let Some(template) = nested_template
1124 && template_type_component_preserves_target(
1125 template,
1126 &ctx.visibility
1127 .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1128 .cloned()
1129 .collect::<Vec<_>>(),
1130 ctx,
1131 )
1132 {
1133 *ctx.raw_match_count += 1;
1134 let hit = template
1135 .child_by_field_name("name")
1136 .filter(|name| name.kind() == "type_identifier")
1137 .unwrap_or(template);
1138 push_type_hit(hit, ctx);
1139 return;
1140 }
1141 if let Some(template) = nested_template
1142 && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1143 {
1144 *ctx.raw_match_count += 1;
1145 let hit =
1146 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1147 if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1148 template
1149 .child_by_field_name("name")
1150 .filter(|name| name.kind() == "type_identifier")
1151 .unwrap_or(template)
1152 } else {
1153 type_reference_hit_node(template)
1154 }
1155 });
1156 push_type_hit(hit, ctx);
1157 }
1158 return;
1159 }
1160 if !recovered_type && !type_reference_components_may_name_target(node, ctx) {
1161 return;
1162 }
1163 if !recovered_type && let Some(call) = call_for_function_node(node) {
1164 let direct_target = resolve_qualified_call_target(
1165 call,
1166 node,
1167 &ctx.analyzer,
1168 ctx.visibility,
1169 &ctx.ordinary_type_imports,
1170 ctx.file,
1171 ctx.source,
1172 );
1173 if matches!(direct_target, BareCallTargetResolution::Type(_))
1174 && let LexicalTypeResolution::Resolved {
1175 unit, candidates, ..
1176 } = resolve_type_node_lexically_for_target(
1177 node,
1178 &ctx.analyzer,
1179 ctx.visibility,
1180 &ctx.ordinary_type_imports,
1181 ctx.file,
1182 ctx.source,
1183 &ctx.spec.target,
1184 Some(&ctx.lexical_scope_cache),
1185 ctx.recovered_sentinel_scope(node).as_deref(),
1186 )
1187 && type_resolution_matches_target(node, &unit, &candidates, ctx)
1188 {
1189 *ctx.raw_match_count += 1;
1190 push_type_hit(type_reference_hit_node(node), ctx);
1191 } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1192 *ctx.raw_match_count += 1;
1193 for scope in scopes {
1194 push_type_hit(scope, ctx);
1195 }
1196 } else if let Some(scope) = target_guided_unproven_qualified_value_owner_scope(node, ctx) {
1197 *ctx.raw_match_count += 1;
1198 push_unproven_hit(scope, ctx);
1199 }
1200 return;
1201 }
1202 if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1203 *ctx.raw_match_count += 1;
1204 if proven {
1205 push_type_hit(hit, ctx);
1206 } else {
1207 push_unproven_hit(hit, ctx);
1208 }
1209 return;
1210 }
1211 if !recovered_type
1212 && !recovered_qualified_friend
1213 && !recovered_exported_class_base
1214 && is_declaration_name(node)
1215 {
1216 let mut matched_owner = false;
1217 if let Some(owners) = out_of_line_member_definition_owner(
1218 &ctx.analyzer,
1219 ctx.visibility,
1220 ctx.file,
1221 ctx.source,
1222 node,
1223 ) {
1224 for (owner_node, owner) in owners.owners {
1225 if same_visible_symbol(&owner, &ctx.spec.target) {
1226 matched_owner = true;
1227 *ctx.raw_match_count += 1;
1228 push_guarded_owner_hit(owner_node, &owner, node, ctx);
1229 }
1230 }
1231 }
1232 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1233 *ctx.raw_match_count += 1;
1234 for scope in scopes {
1235 push_hit(scope, ctx);
1236 }
1237 } else if !matched_owner
1238 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1239 {
1240 *ctx.raw_match_count += 1;
1241 push_unproven_hit(scope, ctx);
1242 }
1243 return;
1244 }
1245 let hit_node = node;
1246 let text = node_text(hit_node, ctx.source);
1247 let type_resolution = if hit_node.kind() == "template_type"
1248 && ctx.visibility.is_template_specialization(&ctx.spec.target)
1249 {
1250 resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1251 resolve_type_node_lexically_for_target(
1252 hit_node,
1253 &ctx.analyzer,
1254 ctx.visibility,
1255 &ctx.ordinary_type_imports,
1256 ctx.file,
1257 ctx.source,
1258 &ctx.spec.target,
1259 Some(&ctx.lexical_scope_cache),
1260 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1261 )
1262 })
1263 } else {
1264 resolve_type_node_lexically_for_target(
1265 hit_node,
1266 &ctx.analyzer,
1267 ctx.visibility,
1268 &ctx.ordinary_type_imports,
1269 ctx.file,
1270 ctx.source,
1271 &ctx.spec.target,
1272 Some(&ctx.lexical_scope_cache),
1273 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1274 )
1275 };
1276 let type_resolution = if matches!(type_resolution, LexicalTypeResolution::Missing) {
1277 orphaned_namespace_alias_type_resolution(hit_node, ctx).unwrap_or(type_resolution)
1278 } else {
1279 type_resolution
1280 };
1281 match type_resolution {
1282 LexicalTypeResolution::Resolved {
1283 unit, candidates, ..
1284 } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1285 *ctx.raw_match_count += 1;
1286 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1287 for scope in scopes {
1288 push_type_hit(scope, ctx);
1289 }
1290 } else {
1291 let hit_node = if recovered_type && hit_node.kind() == "template_type" {
1292 hit_node
1293 .child_by_field_name("name")
1294 .filter(|name| name.kind() == "type_identifier")
1295 .unwrap_or(hit_node)
1296 } else if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1297 hit_node
1298 .child_by_field_name("name")
1299 .filter(|name| name.kind() == "type_identifier")
1300 .unwrap_or(hit_node)
1301 } else if ctx
1302 .analyzer
1303 .type_alias_provider()
1304 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1305 && ctx
1306 .analyzer
1307 .parent_of(&ctx.spec.target)
1308 .is_some_and(|owner| owner.is_class())
1309 && ctx
1310 .visibility
1311 .is_exhaustive_same_fqn_type_declaration_family(
1312 &ctx.analyzer,
1313 ctx.file,
1314 &ctx.spec.target,
1315 )
1316 && matches!(
1317 hit_node.kind(),
1318 "qualified_identifier" | "scoped_type_identifier"
1319 )
1320 {
1321 hit_node.child_by_field_name("name").unwrap_or(hit_node)
1322 } else if cpp_template_reference_arguments(hit_node, ctx.source).is_some()
1323 && ctx.analyzer.type_alias_provider().is_some_and(|provider| {
1324 candidates.iter().any(|candidate| {
1325 provider.is_type_alias(candidate)
1326 && ctx
1327 .visibility
1328 .is_exhaustive_same_fqn_type_declaration_family(
1329 &ctx.analyzer,
1330 ctx.file,
1331 candidate,
1332 )
1333 })
1334 })
1335 {
1336 template_reference_name_node(hit_node)
1337 .map(function_terminal_node)
1338 .unwrap_or(hit_node)
1339 } else if qualified_type_scope_contains_template(hit_node) {
1340 function_terminal_node(hit_node)
1341 } else {
1342 hit_node
1343 };
1344 let hit_node = target_guided_missing_orphaned_namespace_type_leaf(hit_node, ctx)
1345 .unwrap_or(hit_node);
1346 push_type_hit(type_reference_hit_node(hit_node), ctx);
1347 }
1348 return;
1349 }
1350 LexicalTypeResolution::Resolved {
1351 unit: _,
1352 candidates,
1353 ..
1354 } => {
1355 if let Some(leaf) = target_guided_missing_orphaned_namespace_type_leaf(node, ctx) {
1356 *ctx.raw_match_count += 1;
1357 push_type_hit(leaf, ctx);
1358 } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1359 *ctx.raw_match_count += 1;
1360 for scope in scopes {
1361 push_type_hit(scope, ctx);
1362 }
1363 } else if let Some(hit) =
1364 target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1365 {
1366 *ctx.raw_match_count += 1;
1367 push_unproven_hit(hit, ctx);
1368 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1369 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1370 {
1371 *ctx.raw_match_count += 1;
1372 push_type_hit(leaf, ctx);
1373 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1374 *ctx.raw_match_count += 1;
1375 push_unproven_hit(leaf, ctx);
1376 }
1377 return;
1378 }
1379 LexicalTypeResolution::Ambiguous => {
1380 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1381 *ctx.raw_match_count += 1;
1382 for scope in scopes {
1383 push_type_hit(scope, ctx);
1384 }
1385 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1386 *ctx.raw_match_count += 1;
1387 push_unproven_hit(leaf, ctx);
1388 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1389 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1390 .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1391 {
1392 *ctx.raw_match_count += 1;
1393 push_type_hit(leaf, ctx);
1394 }
1395 return;
1396 }
1397 LexicalTypeResolution::Missing => {
1398 if let Some(unit) = ctx.visibility.unique_visible_parameter_type_fallback(
1399 &ctx.analyzer,
1400 ctx.file,
1401 hit_node,
1402 ctx.source,
1403 ) && same_visible_symbol(&unit, &ctx.spec.target)
1404 {
1405 *ctx.raw_match_count += 1;
1406 push_type_hit(hit_node, ctx);
1407 return;
1408 }
1409 if let Some(leaf) = target_guided_compatible_foreign_import_type_leaf(node, ctx) {
1410 *ctx.raw_match_count += 1;
1411 push_unproven_hit(leaf, ctx);
1412 return;
1413 }
1414 if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1415 *ctx.raw_match_count += 1;
1416 push_unproven_hit(leaf, ctx);
1417 return;
1418 }
1419 if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1420 *ctx.raw_match_count += 1;
1421 push_type_hit(leaf, ctx);
1422 return;
1423 }
1424 let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1425 hit_node,
1426 &ctx.analyzer,
1427 ctx.visibility,
1428 ctx.file,
1429 ctx.source,
1430 &ctx.spec.target,
1431 );
1432 let raw_matches = matches!(
1433 raw_resolution,
1434 LexicalTypeResolution::Resolved {
1435 ref unit,
1436 ref candidates,
1437 ..
1438 } if type_resolution_identifies_unit_target(
1439 hit_node,
1440 unit,
1441 candidates,
1442 &ctx.spec.target,
1443 ctx,
1444 )
1445 );
1446 if raw_matches
1447 || type_node_has_exact_target_identity_without_visibility(
1448 hit_node,
1449 &ctx.analyzer,
1450 ctx.visibility,
1451 ctx.file,
1452 ctx.source,
1453 &ctx.spec.target,
1454 )
1455 {
1456 *ctx.raw_match_count += 1;
1457 push_unproven_hit(type_reference_hit_node(hit_node), ctx);
1458 return;
1459 }
1460 }
1461 }
1462 if ctx
1467 .visibility
1468 .parser_alias_resolves_to_type(&ctx.analyzer, ctx.file, text, &ctx.spec.target)
1469 {
1470 *ctx.raw_match_count += 1;
1471 push_type_hit(type_reference_hit_node(hit_node), ctx);
1472 return;
1473 }
1474 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1475 *ctx.raw_match_count += 1;
1476 for scope in scopes {
1477 push_type_hit(scope, ctx);
1478 }
1479 return;
1480 }
1481 if !name_mentions(text, &ctx.spec.member_name) {
1482 return;
1483 }
1484 *ctx.raw_match_count += 1;
1485 if !ctx.visibility.external_type_candidate_visible_in_context(
1486 &ctx.analyzer,
1487 ctx.file,
1488 &ctx.spec.target,
1489 hit_node,
1490 ) {
1491 if let Some(scope) = static_qualifier_name_scope(node, ctx) {
1492 push_unproven_hit(scope, ctx);
1493 } else {
1494 push_unproven_hit(hit_node, ctx);
1495 }
1496 }
1497}
1498
1499fn target_guided_compatible_foreign_import_type_leaf<'tree>(
1500 node: Node<'tree>,
1501 ctx: &ScanCtx<'_>,
1502) -> Option<Node<'tree>> {
1503 let (components, global) = type_reference_components(node, ctx.source)?;
1504 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
1505 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
1506 node,
1507 &ctx.analyzer,
1508 ctx.visibility,
1509 ctx.file,
1510 ctx.source,
1511 false,
1512 false,
1513 Some(&ctx.lexical_scope_cache),
1514 ) {
1515 LexicalScopeResolution::Resolved(scope) => Some(scope),
1516 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
1517 }
1518 })?;
1519 let OrdinaryTypeImportResolution::Resolved { target, .. } =
1520 compatible_foreign_type_import_resolution(
1521 node,
1522 &components,
1523 global,
1524 &ctx.analyzer,
1525 ctx.visibility,
1526 &ctx.ordinary_type_imports,
1527 ctx.file,
1528 ctx.source,
1529 &lexical_scope,
1530 &ctx.spec.target,
1531 )
1532 else {
1533 return None;
1534 };
1535 same_visible_symbol(&target, &ctx.spec.target).then(|| type_reference_hit_node(node))
1536}
1537
1538fn target_guided_nested_type_terminal_hit<'tree>(
1544 node: Node<'tree>,
1545 ctx: &ScanCtx<'_>,
1546) -> Option<Node<'tree>> {
1547 let qualified = node.parent().filter(|parent| {
1548 matches!(
1549 parent.kind(),
1550 "qualified_identifier" | "scoped_type_identifier"
1551 ) && parent.child_by_field_name("name") == Some(node)
1552 })?;
1553 let mut complete = qualified;
1554 while let Some(parent) = complete.parent().filter(|parent| {
1555 matches!(
1556 parent.kind(),
1557 "qualified_identifier" | "scoped_type_identifier"
1558 )
1559 }) {
1560 complete = parent;
1561 }
1562 let owner = qualified_owner_components(complete, ctx.source)?;
1563
1564 if let LexicalTypeResolution::Resolved {
1565 unit, candidates, ..
1566 } = resolve_type_node_lexically_for_target(
1567 complete,
1568 &ctx.analyzer,
1569 ctx.visibility,
1570 &ctx.ordinary_type_imports,
1571 ctx.file,
1572 ctx.source,
1573 &ctx.spec.target,
1574 Some(&ctx.lexical_scope_cache),
1575 ctx.recovered_sentinel_scope(complete).as_deref(),
1576 ) && type_resolution_matches_target(complete, &unit, &candidates, ctx)
1577 && ctx
1578 .analyzer
1579 .parent_of(&ctx.spec.target)
1580 .is_some_and(|parent| parent.is_class())
1581 && !ctx
1582 .analyzer
1583 .type_alias_provider()
1584 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1585 && is_namespace_scope_type_reference(complete)
1586 && namespace_import_can_name_nested_target(complete, ctx)
1587 {
1588 return Some(node);
1589 }
1590
1591 let target_owner = ctx
1592 .analyzer
1593 .parent_of(&ctx.spec.target)
1594 .filter(CodeUnit::is_class);
1595 let owner_spelling_can_name_target = target_owner.as_ref().is_some_and(|target_owner| {
1596 let target_components = canonical_cpp_scope_components(target_owner);
1597 if owner.global {
1598 target_components == owner.names
1599 } else {
1600 target_components.ends_with(&owner.names)
1601 }
1602 });
1603 let target_member_visible = |target_owner: &CodeUnit| {
1604 ctx.visibility
1605 .visible_members_for_owner_name(ctx.file, target_owner, node_text(node, ctx.source))
1606 .into_iter()
1607 .any(|candidate| {
1608 same_visible_symbol(candidate, &ctx.spec.target)
1609 && (ctx
1610 .visibility
1611 .external_type_candidate_guard_compatible_in_context(
1612 &ctx.analyzer,
1613 ctx.file,
1614 candidate,
1615 complete,
1616 )
1617 || (ctx
1618 .visibility
1619 .is_exhaustive_same_fqn_type_declaration_family(
1620 &ctx.analyzer,
1621 ctx.file,
1622 candidate,
1623 )
1624 && ctx.visibility.external_type_candidate_visible_in_context(
1625 &ctx.analyzer,
1626 ctx.file,
1627 candidate,
1628 complete,
1629 )))
1630 })
1631 };
1632
1633 if owner_spelling_can_name_target
1634 && (is_namespace_scope_type_reference(complete)
1635 || (is_compound_type_reference(complete)
1636 && namespace_import_can_name_nested_target(complete, ctx)))
1637 && let Some(target_owner) = target_owner.as_ref()
1638 && let LexicalTypeResolution::Resolved { unit, .. } =
1639 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
1640 complete,
1641 &owner.names,
1642 owner.global,
1643 &ctx.analyzer,
1644 ctx.visibility,
1645 &ctx.ordinary_type_imports,
1646 ctx.file,
1647 ctx.source,
1648 Some(&ctx.lexical_scope_cache),
1649 )
1650 && ctx
1651 .visibility
1652 .same_template_owner_identity(&unit, target_owner)
1653 && target_member_visible(target_owner)
1654 {
1655 return Some(node);
1656 }
1657
1658 if ctx
1659 .analyzer
1660 .type_alias_provider()
1661 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1662 && !ctx
1663 .analyzer
1664 .parent_of(&ctx.spec.target)
1665 .is_some_and(|owner| owner.is_class())
1666 && let Some(target_owner) = target_owner.as_ref()
1667 && let LexicalTypeResolution::Resolved {
1668 unit, candidates, ..
1669 } = resolve_type_components_lexically_at_for_target_with_scope_cache(
1670 complete,
1671 &owner.names,
1672 owner.global,
1673 &ctx.analyzer,
1674 ctx.visibility,
1675 &ctx.ordinary_type_imports,
1676 ctx.file,
1677 ctx.source,
1678 target_owner,
1679 false,
1680 Some(&ctx.lexical_scope_cache),
1681 )
1682 && (ctx
1683 .visibility
1684 .same_template_owner_identity(&unit, target_owner)
1685 || candidates.iter().any(|candidate| {
1686 ctx.visibility
1687 .same_template_owner_identity(candidate, target_owner)
1688 }))
1689 && target_member_visible(target_owner)
1690 {
1691 return Some(node);
1692 }
1693
1694 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
1695 let lexical_scope = canonical_cpp_scope_components(&enclosing_owner);
1696 let owner_resolution = ctx.visibility.resolve_type_components_lexically(
1697 &ctx.analyzer,
1698 ctx.file,
1699 &owner.names,
1700 owner.global,
1701 &lexical_scope,
1702 );
1703 let owner_unit = match owner_resolution {
1704 LexicalTypeResolution::Resolved { unit, .. } => unit,
1705 LexicalTypeResolution::Missing if !owner.global && owner.names.len() == 1 => {
1706 ctx.visibility.inherited_injected_class_owner(
1707 &ctx.analyzer,
1708 ctx.file,
1709 &enclosing_owner,
1710 owner.names.first()?,
1711 )?
1712 }
1713 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => return None,
1714 };
1715 let name = node_text(node, ctx.source);
1716 ctx.visibility
1717 .visible_members_for_owner_name(ctx.file, &owner_unit, name)
1718 .into_iter()
1719 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
1720 .then_some(complete)
1721}
1722
1723fn is_namespace_scope_type_reference(node: Node<'_>) -> bool {
1724 let mut current = node.parent();
1729 while let Some(parent) = current {
1730 match parent.kind() {
1731 "call_expression" | "new_expression" => return true,
1732 "parameter_declaration"
1733 | "optional_parameter_declaration"
1734 | "field_declaration"
1735 | "function_definition"
1736 | "function_declarator"
1737 | "class_specifier"
1738 | "struct_specifier"
1739 | "union_specifier"
1740 | "compound_statement" => return false,
1741 "declaration" => {
1742 let mut cursor = parent.walk();
1743 if parent
1744 .named_children(&mut cursor)
1745 .any(|child| child.kind() == "function_declarator")
1746 {
1747 return false;
1748 }
1749 }
1750 _ => {}
1751 }
1752 current = parent.parent();
1753 }
1754 true
1755}
1756
1757fn is_compound_type_reference(node: Node<'_>) -> bool {
1758 let mut current = node.parent();
1759 while let Some(parent) = current {
1760 match parent.kind() {
1761 "compound_statement" => return true,
1762 "namespace_definition" | "translation_unit" => return false,
1763 _ => current = parent.parent(),
1764 }
1765 }
1766 false
1767}
1768
1769fn namespace_import_can_name_nested_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
1770 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
1771 return false;
1772 };
1773 effective_using_bindings_for_name(
1774 ctx.visibility,
1775 &ctx.ordinary_type_imports,
1776 ctx.file,
1777 node,
1778 ctx.source,
1779 target_owner.identifier(),
1780 )
1781 .iter()
1782 .any(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
1783}
1784
1785fn out_of_line_dependent_return_template_owner<'tree>(
1795 node: Node<'tree>,
1796 ctx: &ScanCtx<'_>,
1797) -> Option<Node<'tree>> {
1798 if node.kind() != "template_type" {
1799 return None;
1800 }
1801 let template_name = template_reference_name_node(node)?;
1802 let mut scope = node;
1803 while let Some(parent) = scope.parent().filter(|parent| {
1804 matches!(
1805 parent.kind(),
1806 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1807 ) && parent.child_by_field_name("name") == Some(scope)
1808 }) {
1809 scope = parent;
1810 }
1811 let qualified = scope.parent().filter(|parent| {
1812 matches!(
1813 parent.kind(),
1814 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1815 ) && parent.child_by_field_name("scope") == Some(scope)
1816 })?;
1817 let mut function = qualified.parent();
1818 let function = loop {
1819 let candidate = function?;
1820 if candidate.kind() == "function_definition" {
1821 break candidate;
1822 }
1823 function = candidate.parent();
1824 };
1825 let return_type = function.child_by_field_name("type")?;
1826 if return_type.start_byte() > node.start_byte() || node.end_byte() > return_type.end_byte() {
1827 return None;
1828 }
1829 let declarator_name = function
1830 .child_by_field_name("declarator")
1831 .and_then(declarator_name_node)?;
1832 if node_text(template_name, ctx.source) != ctx.spec.target.identifier() {
1833 return None;
1834 }
1835 let resolved_owner_matches = out_of_line_member_definition_owner(
1836 &ctx.analyzer,
1837 ctx.visibility,
1838 ctx.file,
1839 ctx.source,
1840 declarator_name,
1841 )
1842 .is_some_and(|owners| {
1843 owners
1844 .owners
1845 .iter()
1846 .any(|(_, owner)| same_logical_symbol(owner, &ctx.spec.target))
1847 });
1848 let indexed_owner_matches =
1849 indexed_out_of_line_template_owner_hit(declarator_name, ctx).is_some();
1850 let target_guided_owner_matches =
1851 target_guided_qualifier_type_scopes(declarator_name, ctx).is_some();
1852 (resolved_owner_matches || indexed_owner_matches || target_guided_owner_matches)
1853 .then_some(template_name)
1854}
1855
1856fn indexed_out_of_line_template_owner_hit<'tree>(
1859 node: Node<'tree>,
1860 ctx: &ScanCtx<'_>,
1861) -> Option<Node<'tree>> {
1862 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
1863 || !is_declaration_name(node)
1864 {
1865 return None;
1866 }
1867 let mut function = node.parent();
1868 let function = loop {
1869 let candidate = function?;
1870 if candidate.kind() == "function_definition" {
1871 break candidate;
1872 }
1873 function = candidate.parent();
1874 };
1875 if function
1876 .child_by_field_name("declarator")
1877 .and_then(declarator_name_node)
1878 != Some(node)
1879 {
1880 return None;
1881 }
1882 let target = physically_visible_type_target(ctx)?;
1883 let qualified = qualified_owner_components(node, ctx.source)?;
1884 if qualified.names.last().map(String::as_str) != Some(target.identifier()) {
1885 return None;
1886 }
1887 if indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?
1888 != canonical_cpp_scope_components(target)
1889 {
1890 return None;
1891 }
1892 let owner = qualified.nodes.last().copied()?;
1893 let template = if owner.kind() == "template_type" {
1894 owner
1895 } else {
1896 owner
1897 .parent()
1898 .filter(|parent| parent.kind() == "template_type")?
1899 };
1900 template_reference_name_node(template)
1901}
1902
1903fn push_guarded_owner_hit(
1904 owner_node: Node<'_>,
1905 owner: &CodeUnit,
1906 reference: Node<'_>,
1907 ctx: &mut ScanCtx<'_>,
1908) {
1909 if ctx
1910 .visibility
1911 .external_type_candidate_guard_compatible_in_context(
1912 &ctx.analyzer,
1913 ctx.file,
1914 owner,
1915 reference,
1916 )
1917 {
1918 push_hit(owner_node, ctx);
1919 } else {
1920 push_unproven_hit(owner_node, ctx);
1921 }
1922}
1923
1924fn target_guided_alias_template_reference<'tree>(
1927 node: Node<'tree>,
1928 ctx: &ScanCtx<'_>,
1929) -> Option<(Node<'tree>, bool)> {
1930 let alias_provider = ctx.analyzer.type_alias_provider()?;
1931 if !matches!(
1932 node.kind(),
1933 "qualified_identifier" | "scoped_type_identifier"
1934 ) || !alias_provider.is_type_alias(&ctx.spec.target)
1935 {
1936 return None;
1937 }
1938 cpp_template_reference_arguments(node, ctx.source)?;
1939 let (components, global) = type_reference_components(node, ctx.source)?;
1940 if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
1941 return None;
1942 }
1943 let target = physically_visible_type_target(ctx)?;
1944 let target_components = canonical_cpp_scope_components(target);
1945 let parser_namespace = enclosing_namespace_components(node, ctx.source);
1946 let path_matches = if global {
1947 components == target_components
1948 || (!parser_namespace.is_empty()
1949 && target_components.starts_with(&parser_namespace)
1950 && target_components[parser_namespace.len()..] == components)
1951 } else {
1952 let lexical_scope = match enclosing_lexical_scope_components(
1953 node,
1954 &ctx.analyzer,
1955 ctx.visibility,
1956 ctx.file,
1957 ctx.source,
1958 ) {
1959 LexicalScopeResolution::Resolved(scope) => scope,
1960 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
1961 };
1962 lexical_component_tiers(&components, false, &lexical_scope)
1963 .any(|scope| scope == target_components)
1964 };
1965 let scoped_candidates = ctx
1966 .visibility
1967 .visible_identifier_candidates(ctx.file, target.identifier())
1968 .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
1969 .collect::<Vec<_>>();
1970 if !path_matches
1971 || scoped_candidates.is_empty()
1972 || scoped_candidates
1973 .iter()
1974 .any(|candidate| !same_visible_symbol(candidate, target))
1975 || !ctx.visibility.structured_alias_primary_preserves_target(
1976 &ctx.analyzer,
1977 ctx.file,
1978 target,
1979 target,
1980 )
1981 {
1982 return None;
1983 }
1984 let proven = ctx.visibility.external_type_candidate_visible_in_context(
1985 &ctx.analyzer,
1986 ctx.file,
1987 target,
1988 node,
1989 );
1990 Some((node, proven))
1991}
1992
1993fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
1999 let name = node_text(return_type, ctx.source);
2000 if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
2001 return;
2002 }
2003 if physically_visible_type_target(ctx).is_some()
2004 && type_alias_owner_encloses_structured_reference(return_type, ctx)
2005 && !nearer_type_name_shadows_structured_reference(return_type, ctx)
2006 && ctx.visibility.external_type_candidate_visible_in_context(
2007 &ctx.analyzer,
2008 ctx.file,
2009 &ctx.spec.target,
2010 return_type,
2011 )
2012 {
2013 *ctx.raw_match_count += 1;
2014 push_type_hit(return_type, ctx);
2015 return;
2016 }
2017 let Some(scope) = ctx
2018 .recovered_sentinel_scope(return_type)
2019 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
2020 else {
2021 return;
2022 };
2023 let components = [name.to_string()];
2024 let resolution = ctx.visibility.resolve_type_components_lexically(
2025 &ctx.analyzer,
2026 ctx.file,
2027 &components,
2028 false,
2029 &scope,
2030 );
2031 if let LexicalTypeResolution::Resolved {
2032 unit, candidates, ..
2033 } = resolution
2034 && (same_visible_symbol(&unit, &ctx.spec.target)
2035 || candidates
2036 .iter()
2037 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2038 {
2039 *ctx.raw_match_count += 1;
2040 push_type_hit(return_type, ctx);
2041 }
2042}
2043
2044fn member_pointer_owner_components<'tree>(
2049 node: Node<'tree>,
2050 source: &str,
2051) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
2052 if node.kind() != "qualified_identifier" {
2053 return None;
2054 }
2055 let declarator = node.child_by_field_name("name")?;
2056 if !matches!(
2057 declarator.kind(),
2058 "pointer_type_declarator" | "abstract_pointer_declarator"
2059 ) {
2060 return None;
2061 }
2062 let scope = node.child_by_field_name("scope")?;
2063 let mut saw_scope = false;
2070 let has_scope_separator = (0..node.child_count()).any(|index| {
2071 let Some(child) = node.child(index) else {
2072 return false;
2073 };
2074 if same_node(child, scope) {
2075 saw_scope = true;
2076 return false;
2077 }
2078 saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
2079 });
2080 if !has_scope_separator {
2081 return None;
2082 }
2083 let mut nodes = cpp_name_component_nodes(scope)?;
2084 let mut outer = node;
2085 while let Some(parent) = outer.parent()
2086 && parent.kind() == "qualified_identifier"
2087 && parent.child_by_field_name("name") == Some(outer)
2088 {
2089 let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
2090 prefix.append(&mut nodes);
2091 nodes = prefix;
2092 outer = parent;
2093 }
2094 let names = nodes
2095 .iter()
2096 .map(|component| node_text(*component, source).to_string())
2097 .collect();
2098 Some((
2099 QualifiedOwnerComponents {
2100 nodes,
2101 names,
2102 global: is_globally_qualified_cpp_name(outer),
2103 },
2104 outer,
2105 ))
2106}
2107
2108fn member_pointer_alias_owner_prefix_matches(
2109 node: Node<'_>,
2110 owner: &QualifiedOwnerComponents<'_>,
2111 ctx: &ScanCtx<'_>,
2112) -> bool {
2113 let Some((_, owner_prefix)) = owner.names.split_last() else {
2114 return false;
2115 };
2116 let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
2117 return false;
2118 };
2119 let recovered_scope = ctx.recovered_sentinel_scope(node);
2120 let resolution = if let Some(recovered_scope) = recovered_scope {
2121 resolve_type_components_lexically_at_for_target_with_recovered_scope(
2122 node,
2123 owner_prefix,
2124 owner.global,
2125 &ctx.analyzer,
2126 ctx.visibility,
2127 &ctx.ordinary_type_imports,
2128 ctx.file,
2129 ctx.source,
2130 &parent,
2131 false,
2132 &recovered_scope,
2133 )
2134 } else {
2135 resolve_type_components_lexically_at_for_target_with_scope_cache(
2136 node,
2137 owner_prefix,
2138 owner.global,
2139 &ctx.analyzer,
2140 ctx.visibility,
2141 &ctx.ordinary_type_imports,
2142 ctx.file,
2143 ctx.source,
2144 &parent,
2145 false,
2146 Some(&ctx.lexical_scope_cache),
2147 )
2148 };
2149 let LexicalTypeResolution::Resolved {
2150 unit, candidates, ..
2151 } = resolution
2152 else {
2153 return false;
2154 };
2155 same_member_pointer_owner_identity(&unit, &parent)
2156 || candidates
2157 .iter()
2158 .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
2159}
2160
2161fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
2162 same_visible_symbol(left, right)
2163 || (left.kind() == right.kind()
2164 && left.fq_name() == right.fq_name()
2165 && left.source() == right.source())
2166}
2167
2168fn resolve_nested_template_type_for_target(
2175 node: Node<'_>,
2176 ctx: &ScanCtx<'_>,
2177) -> Option<LexicalTypeResolution> {
2178 let reference_node = node
2179 .parent()
2180 .filter(|parent| {
2181 parent.kind() == "qualified_identifier"
2182 && parent.child_by_field_name("name") == Some(node)
2183 })
2184 .unwrap_or(node);
2185 let target_resolution = resolve_type_node_lexically_for_target(
2186 reference_node,
2187 &ctx.analyzer,
2188 ctx.visibility,
2189 &ctx.ordinary_type_imports,
2190 ctx.file,
2191 ctx.source,
2192 &ctx.spec.target,
2193 Some(&ctx.lexical_scope_cache),
2194 ctx.recovered_sentinel_scope(reference_node).as_deref(),
2195 );
2196 if let LexicalTypeResolution::Resolved {
2197 unit, candidates, ..
2198 } = target_resolution
2199 && template_reference_candidates_select_target(
2200 reference_node,
2201 &candidates,
2202 &ctx.analyzer,
2203 ctx.visibility,
2204 ctx.file,
2205 ctx.source,
2206 &ctx.spec.target,
2207 )
2208 {
2209 return Some(LexicalTypeResolution::Resolved {
2210 unit,
2211 components: Vec::new(),
2212 candidates,
2213 });
2214 }
2215
2216 let normal_resolution = resolve_type_node_lexically(
2217 reference_node,
2218 &ctx.analyzer,
2219 ctx.visibility,
2220 &ctx.ordinary_type_imports,
2221 ctx.file,
2222 ctx.source,
2223 );
2224 let LexicalTypeResolution::Resolved {
2225 unit,
2226 components,
2227 candidates,
2228 } = normal_resolution
2229 else {
2230 return None;
2231 };
2232 let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
2233 let specialized = ctx
2234 .visibility
2235 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
2236 .ok()
2237 .unwrap_or(unit);
2238 (same_visible_symbol(&specialized, &ctx.spec.target)
2239 || template_type_component_preserves_target(reference_node, &candidates, ctx))
2240 .then_some(LexicalTypeResolution::Resolved {
2241 unit: specialized,
2242 components,
2243 candidates,
2244 })
2245}
2246
2247fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
2248 let Some((components, _)) = type_reference_components(node, ctx.source) else {
2249 return false;
2250 };
2251 components.iter().any(|component| {
2252 ctx.type_reference_component_names.contains(component)
2253 || (component == ctx.spec.target.identifier()
2254 && matches!(
2255 node.kind(),
2256 "qualified_identifier" | "scoped_type_identifier"
2257 )
2258 && cpp_template_reference_arguments(node, ctx.source).is_some()
2259 && ctx
2260 .analyzer
2261 .type_alias_provider()
2262 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2263 && physically_visible_type_target(ctx).is_some())
2264 })
2265}
2266
2267fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
2268 let Some(scope) = node.child_by_field_name("scope") else {
2269 return false;
2270 };
2271 let mut pending = vec![scope];
2272 while let Some(candidate) = pending.pop() {
2273 if candidate.kind() == "template_type" {
2274 return true;
2275 }
2276 if matches!(
2277 candidate.kind(),
2278 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2279 ) {
2280 if let Some(scope) = candidate.child_by_field_name("scope") {
2281 pending.push(scope);
2282 }
2283 if let Some(name) = candidate.child_by_field_name("name") {
2284 pending.push(name);
2285 }
2286 }
2287 }
2288 false
2289}
2290
2291fn call_for_function_node(node: Node<'_>) -> Option<Node<'_>> {
2292 let parent = node.parent()?;
2293 (parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node))
2294 .then_some(parent)
2295}
2296
2297fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
2298 ctx.target_group.iter().find(|target| {
2299 same_logical_symbol(target, &ctx.spec.target)
2300 && ctx.visibility.is_physically_visible(ctx.file, target)
2301 })
2302}
2303
2304fn target_guided_missing_direct_temporary_type<'tree>(
2305 function: Node<'tree>,
2306 ctx: &ScanCtx<'_>,
2307) -> Option<Node<'tree>> {
2308 let target = physically_visible_type_target(ctx)?;
2309 let component_nodes = cpp_name_component_nodes(function)?;
2310 let terminal = component_nodes.last().copied()?;
2311 if node_text(terminal, ctx.source) != target.identifier() {
2312 return None;
2313 }
2314 let components = component_nodes
2315 .iter()
2316 .map(|component| node_text(*component, ctx.source).to_string())
2317 .collect::<Vec<_>>();
2318 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
2319 indexed_scope_matches_target_name(
2320 &indexed_scope,
2321 &components,
2322 is_globally_qualified_cpp_name(function),
2323 target,
2324 )
2325 .then_some(terminal)
2326}
2327
2328fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
2329 let Some(function) = call.child_by_field_name("function") else {
2330 return;
2331 };
2332 if !matches!(
2333 function.kind(),
2334 "identifier"
2335 | "type_identifier"
2336 | "template_function"
2337 | "template_type"
2338 | "qualified_identifier"
2339 | "scoped_identifier"
2340 | "scoped_type_identifier"
2341 ) {
2342 return;
2343 }
2344 if !type_reference_components_may_name_target(function, ctx) {
2345 return;
2346 }
2347 if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
2348 *ctx.raw_match_count += 1;
2349 for scope in scopes {
2350 push_type_hit(scope, ctx);
2351 }
2352 return;
2353 }
2354 let terminal = function_terminal_node(function);
2355 let name = node_text(terminal, ctx.source);
2356 if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
2357 return;
2358 }
2359 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
2360 match resolve_declaring_member_owner(
2361 &ctx.analyzer,
2362 ctx.visibility,
2363 ctx.file,
2364 &enclosing_owner,
2365 name,
2366 ) {
2367 EnclosingMemberOwnerResolution::Owner(owner)
2368 if matches!(
2369 ctx.visibility
2370 .visible_member_for_owner_name(ctx.file, &owner, name,),
2371 VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
2372 ) =>
2373 {
2374 return;
2375 }
2376 EnclosingMemberOwnerResolution::Ambiguous => return,
2377 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
2378 }
2379 }
2380
2381 if ctx
2388 .analyzer
2389 .type_alias_provider()
2390 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2391 && name == ctx.spec.target.identifier()
2392 && physically_visible_type_target(ctx).is_some()
2393 && !local_type_name_shadows(function, ctx)
2394 && type_alias_owner_matches_structured_reference(function, ctx)
2395 && ctx.visibility.external_type_candidate_visible_in_context(
2396 &ctx.analyzer,
2397 ctx.file,
2398 &ctx.spec.target,
2399 function,
2400 )
2401 {
2402 *ctx.raw_match_count += 1;
2403 push_type_hit(terminal, ctx);
2404 return;
2405 }
2406
2407 let call_resolution = resolve_qualified_call_target(
2408 call,
2409 function,
2410 &ctx.analyzer,
2411 ctx.visibility,
2412 &ctx.ordinary_type_imports,
2413 ctx.file,
2414 ctx.source,
2415 );
2416 match call_resolution {
2417 BareCallTargetResolution::Type(unit) => {
2418 if same_visible_symbol(&unit, &ctx.spec.target) {
2419 *ctx.raw_match_count += 1;
2420 push_type_hit(function, ctx);
2421 return;
2422 }
2423 }
2424 BareCallTargetResolution::FreeFunctions(units)
2425 if units.iter().all(|unit| {
2426 unit.fq_name() == ctx.spec.target.fq_name()
2427 && ctx
2428 .visibility
2429 .callable_is_constructor_declaration(&ctx.analyzer, unit)
2430 }) => {}
2431 BareCallTargetResolution::Ambiguous => {
2432 push_unproven_hit(function, ctx);
2433 return;
2434 }
2435 BareCallTargetResolution::FreeFunctions(_)
2436 | BareCallTargetResolution::UnprovenFreeFunctions(_)
2437 | BareCallTargetResolution::CallableShadow => return,
2438 BareCallTargetResolution::Missing => {}
2443 }
2444 let target_resolution = resolve_type_node_lexically_for_target(
2445 function,
2446 &ctx.analyzer,
2447 ctx.visibility,
2448 &ctx.ordinary_type_imports,
2449 ctx.file,
2450 ctx.source,
2451 &ctx.spec.target,
2452 Some(&ctx.lexical_scope_cache),
2453 ctx.recovered_sentinel_scope(function).as_deref(),
2454 );
2455 match target_resolution {
2456 LexicalTypeResolution::Resolved {
2457 unit, candidates, ..
2458 } if same_visible_symbol(&unit, &ctx.spec.target)
2459 || candidates
2460 .iter()
2461 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
2462 {
2463 *ctx.raw_match_count += 1;
2464 push_type_hit(function, ctx);
2465 }
2466 LexicalTypeResolution::Missing => {
2467 if let Some(hit) = target_guided_nested_type_terminal_hit(terminal, ctx)
2468 .or_else(|| target_guided_missing_direct_temporary_type(function, ctx))
2469 {
2470 *ctx.raw_match_count += 1;
2471 push_type_hit(hit, ctx);
2472 }
2473 }
2474 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
2475 }
2476}
2477
2478pub enum BareCallTargetResolution {
2479 Type(CodeUnit),
2480 FreeFunctions(Vec<CodeUnit>),
2481 UnprovenFreeFunctions(Vec<CodeUnit>),
2482 CallableShadow,
2483 Ambiguous,
2484 Missing,
2485}
2486
2487pub enum BlockUsingCallTargetResolution {
2488 Target(BareCallTargetResolution),
2489 Unindexed(Vec<String>),
2490 Ambiguous,
2491}
2492
2493#[allow(clippy::too_many_arguments)]
2494fn resolve_qualified_call_target(
2495 call: Node<'_>,
2496 function: Node<'_>,
2497 analyzer: &CppGraphSource<'_>,
2498 visibility: &VisibilityIndex,
2499 ordinary_type_imports: &OrdinaryTypeImportCell,
2500 file: &ProjectFile,
2501 source: &str,
2502) -> BareCallTargetResolution {
2503 if matches!(function.kind(), "identifier" | "template_function") {
2504 return resolve_bare_call_target(
2505 call,
2506 function,
2507 analyzer,
2508 visibility,
2509 ordinary_type_imports,
2510 file,
2511 source,
2512 );
2513 }
2514 if !matches!(
2515 function.kind(),
2516 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
2517 ) {
2518 return BareCallTargetResolution::Missing;
2519 }
2520 let terminal = function_terminal_node(function);
2521 let name = node_text(terminal, source);
2522 let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
2523 return BareCallTargetResolution::Missing;
2524 };
2525 components.push(name.to_string());
2526 let qualified_name = components.join("::");
2527 let type_resolution = resolve_type_node_lexically(
2528 function,
2529 analyzer,
2530 visibility,
2531 ordinary_type_imports,
2532 file,
2533 source,
2534 );
2535 let same_name_resolves_to_type = matches!(
2536 &type_resolution,
2537 LexicalTypeResolution::Resolved {
2538 unit,
2539 candidates,
2540 ..
2541 } if cpp_name_for(unit) == qualified_name
2542 || candidates
2543 .iter()
2544 .any(|candidate| cpp_name_for(candidate) == qualified_name)
2545 );
2546 let has_explicit_template_arguments =
2547 cpp_template_reference_arguments(function, source).is_some();
2548 let candidates = visibility
2549 .visible_identifier_candidates(file, name)
2550 .filter(|candidate| {
2551 candidate.is_function()
2552 && type_owner_of(analyzer, candidate).is_none()
2553 && !(same_name_resolves_to_type
2554 && (visibility.callable_is_constructor_declaration(analyzer, candidate)
2555 || has_explicit_template_arguments
2556 && visibility
2557 .callable_is_deduction_guide_declaration(analyzer, candidate)))
2558 && cpp_name_for(candidate) == qualified_name
2559 && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
2560 })
2561 .cloned()
2562 .collect::<Vec<_>>();
2563 if !candidates.is_empty() {
2564 return resolve_callable_candidates(
2565 candidates,
2566 visibility.call_arity_evidence(file, call, source).exact(),
2567 call.start_byte(),
2568 analyzer,
2569 visibility,
2570 file,
2571 );
2572 }
2573 match type_resolution {
2574 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2575 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2576 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2577 }
2578}
2579
2580fn binding_free_function_candidates(
2581 binding: &OrdinaryTypeImport,
2582 active_bindings: &[&OrdinaryTypeImport],
2583 analyzer: &CppGraphSource<'_>,
2584 visibility: &VisibilityIndex<'_>,
2585 file: &ProjectFile,
2586 name: &str,
2587 reference_byte: usize,
2588) -> Vec<CodeUnit> {
2589 let Some(qualified) = binding.resolved_target_components.as_ref() else {
2590 return Vec::new();
2591 };
2592 let mut targets = Vec::new();
2593 match binding.target {
2594 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
2595 EffectiveUsingTarget::Namespace { .. } => {
2596 let mut stack = vec![qualified.clone()];
2597 let mut visited = HashSet::default();
2598 while let Some(namespace) = stack.pop() {
2599 if !visited.insert(namespace.clone()) {
2600 continue;
2601 }
2602 let mut target = namespace.clone();
2603 target.push(name.to_string());
2604 targets.push(target);
2605 stack.extend(active_bindings.iter().filter_map(|candidate| {
2606 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
2607 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
2608 .then(|| candidate.resolved_target_components.clone())
2609 .flatten()
2610 }));
2611 }
2612 }
2613 }
2614 targets
2615 .into_iter()
2616 .flat_map(|target| {
2617 let qualified_name = target.join("::");
2618 visibility
2619 .visible_identifier_candidates(file, name)
2620 .filter(move |candidate| {
2621 candidate.is_function()
2622 && type_owner_of(analyzer, candidate).is_none()
2623 && cpp_name_for(candidate) == qualified_name
2624 && visibility.declaration_visible_at(
2625 analyzer,
2626 file,
2627 candidate,
2628 reference_byte,
2629 )
2630 })
2631 .cloned()
2632 })
2633 .collect()
2634}
2635
2636fn dedupe_callable_candidates(
2646 candidates: &mut Vec<CodeUnit>,
2647 analyzer: &CppGraphSource<'_>,
2648 visibility: &VisibilityIndex<'_>,
2649) {
2650 let mut deduped = Vec::with_capacity(candidates.len());
2651 for candidate in candidates.drain(..) {
2652 if !deduped
2653 .iter()
2654 .any(|existing| visibility.same_logical_callable(analyzer, existing, &candidate))
2655 {
2656 deduped.push(candidate);
2657 }
2658 }
2659 *candidates = deduped;
2660}
2661
2662fn resolve_callable_candidates(
2663 candidates: Vec<CodeUnit>,
2664 call_arity: Option<usize>,
2665 reference_byte: usize,
2666 analyzer: &CppGraphSource<'_>,
2667 visibility: &VisibilityIndex<'_>,
2668 file: &ProjectFile,
2669) -> BareCallTargetResolution {
2670 let mut candidates = candidates;
2671 dedupe_callable_candidates(&mut candidates, analyzer, visibility);
2672 if candidates.is_empty() {
2673 return BareCallTargetResolution::Missing;
2674 }
2675 let Some(call_arity) = call_arity else {
2676 if candidates.len() == 1 {
2683 return BareCallTargetResolution::FreeFunctions(candidates);
2684 }
2685 return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
2686 };
2687 let applicable = candidates
2688 .into_iter()
2689 .filter(|candidate| {
2690 visibility
2691 .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
2692 .is_some_and(|arity| arity.accepts(call_arity))
2693 })
2694 .collect::<Vec<_>>();
2695 if applicable.is_empty() {
2696 BareCallTargetResolution::CallableShadow
2697 } else {
2698 BareCallTargetResolution::FreeFunctions(applicable)
2699 }
2700}
2701
2702fn resolve_direct_type_candidates(
2703 candidates: Vec<(CodeUnit, Vec<String>)>,
2704 analyzer: &CppGraphSource<'_>,
2705 visibility: &VisibilityIndex<'_>,
2706 file: &ProjectFile,
2707) -> BareCallTargetResolution {
2708 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
2709 for candidate in candidates {
2710 if !logical
2711 .iter()
2712 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
2713 {
2714 logical.push(candidate);
2715 }
2716 }
2717 let [(target, components)] = logical.as_slice() else {
2718 return if logical.is_empty() {
2719 BareCallTargetResolution::Missing
2720 } else {
2721 BareCallTargetResolution::Ambiguous
2722 };
2723 };
2724 match visibility
2725 .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
2726 {
2727 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2728 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2729 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2730 }
2731}
2732
2733#[allow(clippy::too_many_arguments)]
2738pub fn resolve_block_using_call_target(
2739 call: Node<'_>,
2740 function: Node<'_>,
2741 analyzer: &CppGraphSource<'_>,
2742 visibility: &VisibilityIndex<'_>,
2743 ordinary_type_imports: &OrdinaryTypeImportCell,
2744 file: &ProjectFile,
2745 source: &str,
2746) -> Option<BlockUsingCallTargetResolution> {
2747 if !matches!(function.kind(), "identifier" | "template_function") {
2748 return None;
2749 }
2750 let name = node_text(function_terminal_node(function), source);
2751 if name.is_empty() {
2752 return None;
2753 }
2754 let bindings = effective_using_bindings_for_name(
2755 visibility,
2756 ordinary_type_imports,
2757 file,
2758 function,
2759 source,
2760 name,
2761 );
2762 let block_bindings = bindings
2763 .iter()
2764 .filter(|binding| {
2765 binding.namespace_scope.is_none()
2766 && binding.block_scope
2767 && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
2768 })
2769 .collect::<Vec<_>>();
2770 if block_bindings.is_empty() {
2771 return None;
2772 }
2773 let lexical_scope =
2774 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2775 LexicalScopeResolution::Resolved(scope) => scope,
2776 LexicalScopeResolution::Ambiguous => {
2777 return Some(BlockUsingCallTargetResolution::Ambiguous);
2778 }
2779 LexicalScopeResolution::Missing => return None,
2780 };
2781 let reference_guards = preprocessor_guard_environment(function, source);
2782 let active = block_bindings
2783 .into_iter()
2784 .filter(|binding| {
2785 effective_using_binding_active(
2786 binding,
2787 function,
2788 &lexical_scope,
2789 reference_guards.as_ref(),
2790 visibility,
2791 file,
2792 )
2793 })
2794 .collect::<Vec<_>>();
2795 let depth = active.iter().map(|binding| binding.scope_depth).max()?;
2796 let at_tier = active
2797 .into_iter()
2798 .filter(|binding| binding.scope_depth == depth)
2799 .collect::<Vec<_>>();
2800 let callable_candidates = at_tier
2801 .iter()
2802 .flat_map(|binding| {
2803 binding_free_function_candidates(
2804 binding,
2805 &[],
2806 analyzer,
2807 visibility,
2808 file,
2809 name,
2810 call.start_byte(),
2811 )
2812 })
2813 .collect::<Vec<_>>();
2814 if !callable_candidates.is_empty() {
2815 return Some(BlockUsingCallTargetResolution::Target(
2816 resolve_callable_candidates(
2817 callable_candidates,
2818 visibility.call_arity_evidence(file, call, source).exact(),
2819 call.start_byte(),
2820 analyzer,
2821 visibility,
2822 file,
2823 ),
2824 ));
2825 }
2826 let type_candidates = at_tier
2827 .iter()
2828 .flat_map(|binding| {
2829 binding_type_candidates(
2830 binding,
2831 &[],
2832 analyzer,
2833 visibility,
2834 file,
2835 name,
2836 None,
2837 call.start_byte(),
2838 )
2839 })
2840 .collect::<Vec<_>>();
2841 if !type_candidates.is_empty() {
2842 return Some(BlockUsingCallTargetResolution::Target(
2843 resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
2844 ));
2845 }
2846
2847 let mut unindexed = Vec::new();
2848 for binding in at_tier {
2849 let Some(components) = binding.resolved_target_components.as_ref() else {
2850 continue;
2851 };
2852 if !unindexed.contains(components) {
2853 unindexed.push(components.clone());
2854 }
2855 }
2856 match unindexed.as_slice() {
2857 [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
2858 [] => None,
2859 _ => Some(BlockUsingCallTargetResolution::Ambiguous),
2860 }
2861}
2862
2863#[allow(clippy::too_many_arguments)]
2864pub fn resolve_bare_call_target(
2865 call: Node<'_>,
2866 function: Node<'_>,
2867 analyzer: &CppGraphSource<'_>,
2868 visibility: &VisibilityIndex<'_>,
2869 ordinary_type_imports: &OrdinaryTypeImportCell,
2870 file: &ProjectFile,
2871 source: &str,
2872) -> BareCallTargetResolution {
2873 if !matches!(function.kind(), "identifier" | "template_function") {
2874 return BareCallTargetResolution::Missing;
2875 }
2876 let terminal = function_terminal_node(function);
2877 let name = node_text(terminal, source);
2878 if name.is_empty() {
2879 return BareCallTargetResolution::Missing;
2880 }
2881 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
2882 let lexical_scope =
2883 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2884 LexicalScopeResolution::Resolved(scope) => scope,
2885 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
2886 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
2887 };
2888 let type_resolution = resolve_type_node_lexically(
2889 function,
2890 analyzer,
2891 visibility,
2892 ordinary_type_imports,
2893 file,
2894 source,
2895 );
2896 let type_components = match &type_resolution {
2897 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2898 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2899 };
2900 let direct_type_resolution = visibility.resolve_type_components_lexically(
2901 analyzer,
2902 file,
2903 &[name.to_string()],
2904 false,
2905 &lexical_scope,
2906 );
2907 let direct_type_components = match &direct_type_resolution {
2908 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2909 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2910 };
2911 let has_explicit_template_arguments =
2912 cpp_template_reference_arguments(function, source).is_some();
2913 let bindings = effective_using_bindings_for_name(
2914 visibility,
2915 ordinary_type_imports,
2916 file,
2917 function,
2918 source,
2919 name,
2920 );
2921 let function_guards = if bindings.is_empty() {
2925 None
2926 } else {
2927 preprocessor_guard_environment(function, source)
2928 };
2929 let active_bindings = bindings
2930 .iter()
2931 .filter(|binding| {
2932 effective_using_binding_active(
2933 binding,
2934 function,
2935 &lexical_scope,
2936 function_guards.as_ref(),
2937 visibility,
2938 file,
2939 )
2940 })
2941 .collect::<Vec<_>>();
2942 let transitive_bindings = bindings
2943 .iter()
2944 .filter(|binding| {
2945 effective_using_binding_guards_active(
2946 binding,
2947 function.start_byte(),
2948 function_guards.as_ref(),
2949 visibility,
2950 file,
2951 ) && (binding.namespace_scope.is_some()
2952 || (binding.scope_start <= function.start_byte()
2953 && function.end_byte() <= binding.scope_end))
2954 })
2955 .collect::<Vec<_>>();
2956 let mut concrete_depths = active_bindings
2957 .iter()
2958 .filter(|binding| binding.namespace_scope.is_none())
2959 .map(|binding| binding.scope_depth)
2960 .collect::<Vec<_>>();
2961 concrete_depths.sort_unstable();
2962 concrete_depths.dedup();
2963 for depth in concrete_depths.into_iter().rev() {
2964 let at_tier = active_bindings
2965 .iter()
2966 .copied()
2967 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
2968 let direct = at_tier
2969 .clone()
2970 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2971 .flat_map(|binding| {
2972 binding_free_function_candidates(
2973 binding,
2974 &transitive_bindings,
2975 analyzer,
2976 visibility,
2977 file,
2978 name,
2979 call.start_byte(),
2980 )
2981 })
2982 .collect::<Vec<_>>();
2983 if !direct.is_empty() {
2984 return resolve_callable_candidates(
2985 direct,
2986 call_arity,
2987 call.start_byte(),
2988 analyzer,
2989 visibility,
2990 file,
2991 );
2992 }
2993 let direct_types = at_tier
2994 .clone()
2995 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2996 .flat_map(|binding| {
2997 binding_type_candidates(
2998 binding,
2999 &transitive_bindings,
3000 analyzer,
3001 visibility,
3002 file,
3003 name,
3004 None,
3005 call.start_byte(),
3006 )
3007 })
3008 .collect::<Vec<_>>();
3009 if !direct_types.is_empty() {
3010 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3015 }
3016 let directives = at_tier
3017 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3018 .flat_map(|binding| {
3019 binding_free_function_candidates(
3020 binding,
3021 &transitive_bindings,
3022 analyzer,
3023 visibility,
3024 file,
3025 name,
3026 call.start_byte(),
3027 )
3028 })
3029 .collect::<Vec<_>>();
3030 if !directives.is_empty() {
3031 return resolve_callable_candidates(
3032 directives,
3033 call_arity,
3034 call.start_byte(),
3035 analyzer,
3036 visibility,
3037 file,
3038 );
3039 }
3040 }
3041 for prefix_len in (0..=lexical_scope.len()).rev() {
3042 let mut qualified = lexical_scope[..prefix_len].to_vec();
3043 qualified.push(name.to_string());
3044 let same_name_resolves_to_type = direct_type_components
3045 .is_some_and(|components| components == qualified.as_slice())
3046 || type_components.is_some_and(|components| components == qualified.as_slice());
3047 let mut direct = visibility
3048 .visible_identifier_candidates(file, name)
3049 .filter(|candidate| {
3050 candidate.is_function()
3051 && type_owner_of(analyzer, candidate).is_none()
3052 && !(same_name_resolves_to_type
3053 && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3054 || has_explicit_template_arguments
3055 && visibility
3056 .callable_is_deduction_guide_declaration(analyzer, candidate)))
3057 && cpp_name_for(candidate) == qualified.join("::")
3058 && visibility.declaration_visible_at(
3059 analyzer,
3060 file,
3061 candidate,
3062 call.start_byte(),
3063 )
3064 })
3065 .cloned()
3066 .collect::<Vec<_>>();
3067 let at_tier = active_bindings.iter().copied().filter(|binding| {
3068 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3069 });
3070 direct.extend(
3071 at_tier
3072 .clone()
3073 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3074 .flat_map(|binding| {
3075 binding_free_function_candidates(
3076 binding,
3077 &transitive_bindings,
3078 analyzer,
3079 visibility,
3080 file,
3081 name,
3082 call.start_byte(),
3083 )
3084 }),
3085 );
3086 if !direct.is_empty() {
3087 return resolve_callable_candidates(
3088 direct,
3089 call_arity,
3090 call.start_byte(),
3091 analyzer,
3092 visibility,
3093 file,
3094 );
3095 }
3096 let mut direct_types = at_tier
3097 .clone()
3098 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3099 .flat_map(|binding| {
3100 binding_type_candidates(
3101 binding,
3102 &transitive_bindings,
3103 analyzer,
3104 visibility,
3105 file,
3106 name,
3107 None,
3108 call.start_byte(),
3109 )
3110 })
3111 .collect::<Vec<_>>();
3112 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3113 && let LexicalTypeResolution::Resolved {
3114 unit, components, ..
3115 } = &direct_type_resolution
3116 {
3117 direct_types.push((unit.clone(), components.clone()));
3118 }
3119 if !direct_types.is_empty() {
3120 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3125 }
3126 let directives = at_tier
3127 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3128 .flat_map(|binding| {
3129 binding_free_function_candidates(
3130 binding,
3131 &transitive_bindings,
3132 analyzer,
3133 visibility,
3134 file,
3135 name,
3136 call.start_byte(),
3137 )
3138 })
3139 .collect::<Vec<_>>();
3140 if !directives.is_empty() {
3141 return resolve_callable_candidates(
3142 directives,
3143 call_arity,
3144 call.start_byte(),
3145 analyzer,
3146 visibility,
3147 file,
3148 );
3149 }
3150 if type_components.is_some_and(|components| components == qualified.as_slice()) {
3151 return match type_resolution {
3155 LexicalTypeResolution::Resolved { unit, .. } => {
3156 BareCallTargetResolution::Type(unit)
3157 }
3158 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3159 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3160 };
3161 }
3162 }
3163 match type_resolution {
3172 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3173 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3174 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3175 }
3176}
3177
3178fn static_qualifier_type_scopes<'tree>(
3179 node: Node<'tree>,
3180 ctx: &ScanCtx<'_>,
3181) -> Option<Vec<Node<'tree>>> {
3182 if !matches!(
3183 node.kind(),
3184 "qualified_identifier" | "scoped_type_identifier"
3185 ) {
3186 return None;
3187 }
3188 debug_assert!(!is_nested_type_node(node));
3191 let qualified = qualified_owner_components(node, ctx.source)?;
3192 static_qualifier_type_scopes_for_components(node, qualified, ctx)
3193}
3194
3195fn static_qualifier_type_scopes_for_components<'tree>(
3196 node: Node<'tree>,
3197 qualified: QualifiedOwnerComponents<'tree>,
3198 ctx: &ScanCtx<'_>,
3199) -> Option<Vec<Node<'tree>>> {
3200 if !qualified.global
3201 && qualified.names.first().is_some_and(|name| {
3202 name == ctx.spec.target.identifier()
3203 && qualified
3204 .nodes
3205 .first()
3206 .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
3207 })
3208 {
3209 return None;
3210 }
3211 let mut matches = Vec::new();
3212 let mut inherited_injected_name_is_shadowed = false;
3213 for component_count in 1..=qualified.names.len() {
3214 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3215 node,
3216 &qualified.names[..component_count],
3217 qualified.global,
3218 &ctx.analyzer,
3219 ctx.visibility,
3220 &ctx.ordinary_type_imports,
3221 ctx.file,
3222 ctx.source,
3223 &ctx.spec.target,
3224 false,
3225 Some(&ctx.lexical_scope_cache),
3226 );
3227 match resolution {
3228 LexicalTypeResolution::Resolved {
3229 unit, candidates, ..
3230 } if (!ctx
3231 .analyzer
3232 .type_alias_provider()
3233 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3234 || ctx.visibility.external_type_candidate_visible_in_context(
3235 &ctx.analyzer,
3236 ctx.file,
3237 &ctx.spec.target,
3238 node,
3239 ))
3240 && (same_visible_symbol(&unit, &ctx.spec.target)
3241 || candidates
3242 .iter()
3243 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3244 && target_alias_candidates_visible(&candidates, node, ctx) =>
3245 {
3246 let matched =
3247 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3248 if !template_type_component_preserves_target(matched, &candidates, ctx) {
3249 continue;
3250 }
3251 if !matches.iter().any(|existing: &Node<'_>| {
3252 existing.start_byte() == matched.start_byte()
3253 && existing.end_byte() == matched.end_byte()
3254 }) {
3255 matches.push(matched);
3256 }
3257 }
3258 LexicalTypeResolution::Ambiguous => {
3264 return (!inherited_injected_name_is_shadowed)
3265 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3266 .flatten()
3267 .map(|scope| vec![scope])
3268 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3269 }
3270 LexicalTypeResolution::Resolved { .. } => {
3271 if let Some(matched) =
3272 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3273 {
3274 matches.push(matched);
3275 }
3276 inherited_injected_name_is_shadowed |= component_count == 1;
3277 }
3278 LexicalTypeResolution::Missing => {
3279 if let Some(matched) =
3280 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3281 {
3282 matches.push(matched);
3283 }
3284 }
3285 }
3286 }
3287 if matches.is_empty() {
3288 (!inherited_injected_name_is_shadowed)
3289 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3290 .flatten()
3291 .map(|scope| vec![scope])
3292 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3293 } else {
3294 Some(matches)
3295 }
3296}
3297
3298fn target_guided_unproven_qualified_value_owner_scope<'tree>(
3301 node: Node<'tree>,
3302 ctx: &ScanCtx<'_>,
3303) -> Option<Node<'tree>> {
3304 let target = physically_visible_type_target(ctx)?;
3305 if !target.is_class() {
3306 return None;
3307 }
3308 let qualified = qualified_owner_components(node, ctx.source)?;
3309 let lexical_scope = match enclosing_lexical_scope_components(
3310 node,
3311 &ctx.analyzer,
3312 ctx.visibility,
3313 ctx.file,
3314 ctx.source,
3315 ) {
3316 LexicalScopeResolution::Resolved(scope) => scope,
3317 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3318 enclosing_namespace_components(node, ctx.source)
3319 }
3320 };
3321 let LexicalTypeResolution::Resolved {
3322 unit, candidates, ..
3323 } = ctx.visibility.resolve_type_components_lexically_for_target(
3324 &ctx.analyzer,
3325 ctx.file,
3326 &qualified.names,
3327 qualified.global,
3328 &lexical_scope,
3329 target,
3330 )
3331 else {
3332 return None;
3333 };
3334 (same_visible_symbol(&unit, target)
3335 || candidates
3336 .iter()
3337 .any(|candidate| same_visible_symbol(candidate, target)))
3338 .then(|| qualified.nodes.last().copied())
3339 .flatten()
3340}
3341
3342fn target_guided_nested_alias_type_scope<'tree>(
3348 node: Node<'tree>,
3349 qualified: &QualifiedOwnerComponents<'tree>,
3350 component_count: usize,
3351 ctx: &ScanCtx<'_>,
3352) -> Option<Node<'tree>> {
3353 if component_count < 2 {
3354 return None;
3355 }
3356 let (owner_components, member_name) =
3357 qualified.names[..component_count].split_at(component_count - 1);
3358 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
3359 node,
3360 owner_components,
3361 qualified.global,
3362 &ctx.analyzer,
3363 ctx.visibility,
3364 &ctx.ordinary_type_imports,
3365 ctx.file,
3366 ctx.source,
3367 ) else {
3368 return None;
3369 };
3370 let member_name = member_name.first()?;
3371 let alias_provider = ctx.analyzer.type_alias_provider()?;
3372 ctx.visibility
3373 .visible_members_for_owner_name(ctx.file, &owner, member_name)
3374 .into_iter()
3375 .filter(|member| alias_provider.is_type_alias(member))
3376 .find(|member| {
3377 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
3378 &ctx.analyzer,
3379 ctx.file,
3380 member,
3381 node,
3382 ) || ctx
3383 .visibility
3384 .external_type_candidate_guard_compatible_in_context(
3385 &ctx.analyzer,
3386 ctx.file,
3387 member,
3388 node,
3389 );
3390 if !member_visible {
3391 return false;
3392 }
3393 same_visible_symbol(member, &ctx.spec.target)
3394 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
3395 })
3396 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
3397}
3398
3399fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
3400 if ctx.visibility.structured_class_alias_resolves_to_target(
3401 &ctx.analyzer,
3402 ctx.file,
3403 candidate,
3404 &ctx.spec.target,
3405 ) {
3406 return ctx.spec.target.clone();
3407 }
3408 let structured = ctx
3409 .visibility
3410 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
3411 if let Some(canonical) = structured
3412 .as_ref()
3413 .filter(|canonical| !same_visible_symbol(canonical, candidate))
3414 {
3415 return canonical.clone();
3416 }
3417 structured.unwrap_or_else(|| candidate.clone())
3418}
3419
3420fn target_guided_dependent_alias_qualifier_scope<'tree>(
3424 node: Node<'tree>,
3425 ctx: &ScanCtx<'_>,
3426) -> Option<Node<'tree>> {
3427 if !matches!(
3428 node.kind(),
3429 "qualified_identifier" | "scoped_type_identifier"
3430 ) {
3431 return None;
3432 }
3433 let target = physically_visible_type_target(ctx)?;
3434 let alias_provider = ctx.analyzer.type_alias_provider()?;
3435 if !target.is_class() || alias_provider.is_type_alias(target) {
3436 return None;
3437 }
3438 let nodes = cpp_name_component_nodes(node)?;
3439 let names = nodes
3440 .iter()
3441 .map(|component| node_text(*component, ctx.source).to_string())
3442 .collect::<Vec<_>>();
3443 let global = is_globally_qualified_cpp_name(node);
3444 let lexical_scope = match enclosing_lexical_scope_components(
3445 node,
3446 &ctx.analyzer,
3447 ctx.visibility,
3448 ctx.file,
3449 ctx.source,
3450 ) {
3451 LexicalScopeResolution::Resolved(scope) => scope,
3452 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3453 enclosing_namespace_components(node, ctx.source)
3454 }
3455 };
3456 for component_count in 1..=names.len() {
3457 let components = &names[..component_count];
3458 let name = components.last()?;
3459 let candidates = ctx
3460 .visibility
3461 .visible_identifier_candidates(ctx.file, name)
3462 .filter(|candidate| alias_provider.is_type_alias(candidate))
3463 .filter(|candidate| {
3464 ctx.visibility.is_physically_visible(ctx.file, candidate)
3465 && ctx
3466 .visibility
3467 .external_type_candidate_guard_compatible_in_context(
3468 &ctx.analyzer,
3469 ctx.file,
3470 candidate,
3471 node,
3472 )
3473 })
3474 .filter(|candidate| {
3475 let candidate_components = canonical_cpp_scope_components(candidate);
3476 lexical_component_tiers(components, global, &lexical_scope)
3477 .any(|tier| tier == candidate_components)
3478 || (component_count == 1
3479 && member_alias_owner_matches_reference_for(
3480 candidate,
3481 nodes[component_count - 1],
3482 ctx,
3483 ))
3484 })
3485 .filter(|candidate| {
3486 ctx.visibility.structured_class_alias_path_preserves_target(
3487 &ctx.analyzer,
3488 ctx.file,
3489 candidate,
3490 target,
3491 )
3492 });
3493 let mut aliases: Vec<&CodeUnit> = Vec::new();
3494 for candidate in candidates {
3495 if !aliases
3496 .iter()
3497 .any(|existing| same_logical_symbol(existing, candidate))
3498 {
3499 aliases.push(candidate);
3500 }
3501 }
3502 if aliases.len() == 1 {
3503 return nodes.get(component_count - 1).copied();
3504 }
3505 if aliases.len() > 1 {
3506 return None;
3507 }
3508 }
3509 None
3510}
3511
3512fn target_guided_dependent_class_alias_leaf<'tree>(
3515 node: Node<'tree>,
3516 ctx: &ScanCtx<'_>,
3517) -> Option<Node<'tree>> {
3518 if node.kind() != "type_identifier"
3519 || is_declaration_name(node)
3520 || local_type_name_shadows(node, ctx)
3521 {
3522 return None;
3523 }
3524 let target = physically_visible_type_target(ctx)?;
3525 let alias_provider = ctx.analyzer.type_alias_provider()?;
3526 if !target.is_class() || alias_provider.is_type_alias(target) {
3527 return None;
3528 }
3529 let name = node_text(node, ctx.source);
3530 let aliases = ctx
3531 .visibility
3532 .visible_identifier_candidates(ctx.file, name)
3533 .filter(|candidate| alias_provider.is_type_alias(candidate))
3534 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3535 .filter(|candidate| {
3536 ctx.visibility.is_physically_visible(ctx.file, candidate)
3537 && ctx
3538 .visibility
3539 .external_type_candidate_guard_compatible_in_context(
3540 &ctx.analyzer,
3541 ctx.file,
3542 candidate,
3543 node,
3544 )
3545 })
3546 .filter(|candidate| {
3547 ctx.visibility.structured_class_alias_path_preserves_target(
3548 &ctx.analyzer,
3549 ctx.file,
3550 candidate,
3551 target,
3552 )
3553 })
3554 .collect::<Vec<_>>();
3555 matches!(aliases.as_slice(), [_]).then_some(node)
3556}
3557
3558fn target_guided_unproven_alias_type_reference<'tree>(
3561 node: Node<'tree>,
3562 candidates: &[CodeUnit],
3563 ctx: &ScanCtx<'_>,
3564) -> Option<Node<'tree>> {
3565 let template_arguments = cpp_template_reference_arguments(node, ctx.source);
3566 let target = physically_visible_type_target(ctx)?;
3567 if !target.is_class() {
3568 return None;
3569 }
3570 let alias_provider = ctx.analyzer.type_alias_provider()?;
3571 let (components, _) = type_reference_components(node, ctx.source)?;
3572 let hit = template_arguments
3573 .as_ref()
3574 .and_then(|_| template_reference_name_node(node))
3575 .map(function_terminal_node)
3576 .unwrap_or_else(|| function_terminal_node(node));
3577 candidates
3578 .iter()
3579 .filter(|candidate| {
3580 alias_provider.is_type_alias(candidate)
3581 && ctx.visibility.is_physically_visible(ctx.file, candidate)
3582 && canonical_cpp_scope_components(candidate) == components
3583 })
3584 .find(|candidate| {
3585 template_arguments.as_ref().map_or_else(
3586 || {
3587 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3588 || ctx.visibility.structured_alias_primary_preserves_target(
3589 &ctx.analyzer,
3590 ctx.file,
3591 candidate,
3592 target,
3593 )
3594 },
3595 |arguments| {
3596 ctx.visibility.template_alias_arguments_preserve_target(
3597 &ctx.analyzer,
3598 ctx.file,
3599 candidate,
3600 arguments,
3601 target,
3602 )
3603 },
3604 )
3605 })
3606 .map(|_| hit)
3607}
3608
3609fn target_alias_candidates_visible(
3610 candidates: &[CodeUnit],
3611 reference: Node<'_>,
3612 ctx: &ScanCtx<'_>,
3613) -> bool {
3614 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
3615 return true;
3616 };
3617 if candidates.iter().any(|candidate| {
3618 !alias_provider.is_type_alias(candidate)
3619 && ctx.visibility.same_template_member_identity(
3620 &ctx.analyzer,
3621 candidate,
3622 &ctx.spec.target,
3623 )
3624 }) {
3625 return true;
3626 }
3627 let target_aliases = candidates
3628 .iter()
3629 .filter(|candidate| {
3630 alias_provider.is_type_alias(candidate)
3631 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3632 })
3633 .collect::<Vec<_>>();
3634 target_aliases.is_empty()
3635 || target_aliases
3636 .iter()
3637 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
3638}
3639
3640fn type_candidate_visible_at_reference(
3641 candidate: &CodeUnit,
3642 reference: Node<'_>,
3643 ctx: &ScanCtx<'_>,
3644) -> bool {
3645 let class_owned_alias = ctx
3646 .analyzer
3647 .type_alias_provider()
3648 .is_some_and(|provider| provider.is_type_alias(candidate))
3649 && ctx
3650 .analyzer
3651 .parent_of(candidate)
3652 .is_some_and(|owner| owner.is_class());
3653 if class_owned_alias {
3654 let conditional_family = ctx
3655 .visibility
3656 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
3657 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
3658 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
3659 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
3660 let guard_match = ctx
3661 .visibility
3662 .external_type_candidate_guard_compatible_in_context(
3663 &ctx.analyzer,
3664 ctx.file,
3665 candidate,
3666 reference,
3667 );
3668 let general_match = conditional_family
3669 && ctx.visibility.external_type_candidate_visible_in_context(
3670 &ctx.analyzer,
3671 ctx.file,
3672 candidate,
3673 reference,
3674 );
3675 return owner_match && (guard_match || general_match);
3676 }
3677 ctx.visibility.external_type_candidate_visible_in_context(
3678 &ctx.analyzer,
3679 ctx.file,
3680 candidate,
3681 reference,
3682 )
3683}
3684
3685fn unqualified_reference_selects_inherited_alias(
3686 candidate: &CodeUnit,
3687 reference: Node<'_>,
3688 ctx: &ScanCtx<'_>,
3689) -> bool {
3690 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3691 return false;
3692 };
3693 if global || components.len() != 1 {
3694 return false;
3695 }
3696 matches!(
3697 resolve_type_node_lexically_for_target(
3698 reference,
3699 &ctx.analyzer,
3700 ctx.visibility,
3701 &ctx.ordinary_type_imports,
3702 ctx.file,
3703 ctx.source,
3704 candidate,
3705 Some(&ctx.lexical_scope_cache),
3706 ctx.recovered_sentinel_scope(reference).as_deref(),
3707 ),
3708 LexicalTypeResolution::Resolved {
3709 ref unit,
3710 ref candidates,
3711 ..
3712 } if ctx
3713 .visibility
3714 .same_template_member_identity(&ctx.analyzer, unit, candidate)
3715 || candidates.iter().any(|resolved| {
3716 ctx.visibility.same_template_member_identity(
3717 &ctx.analyzer,
3718 resolved,
3719 candidate,
3720 )
3721 })
3722 )
3723}
3724
3725fn qualified_reference_selects_type_candidate(
3726 candidate: &CodeUnit,
3727 reference: Node<'_>,
3728 ctx: &ScanCtx<'_>,
3729) -> bool {
3730 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3731 return false;
3732 };
3733 if components.len() < 2 {
3734 return false;
3735 }
3736 let candidate_components = canonical_cpp_scope_components(candidate);
3737 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3738 match enclosing_lexical_scope_components(
3739 reference,
3740 &ctx.analyzer,
3741 ctx.visibility,
3742 ctx.file,
3743 ctx.source,
3744 ) {
3745 LexicalScopeResolution::Resolved(scope) => scope,
3746 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3747 enclosing_namespace_components(reference, ctx.source)
3748 }
3749 }
3750 });
3751 lexical_component_tiers(&components, global, &lexical_scope)
3752 .any(|qualified| qualified == candidate_components)
3753}
3754
3755fn qualified_type_component_hit_node<'tree>(
3756 component: Node<'tree>,
3757 qualified: Node<'tree>,
3758) -> Node<'tree> {
3759 let mut current = component;
3760 while let Some(parent) = current.parent() {
3761 let is_type_name = matches!(
3762 parent.kind(),
3763 "template_type"
3764 | "qualified_identifier"
3765 | "scoped_identifier"
3766 | "scoped_type_identifier"
3767 ) && parent
3768 .child_by_field_name("name")
3769 .is_some_and(|name| same_node(name, current));
3770 if !is_type_name {
3771 break;
3772 }
3773 current = parent;
3774 if same_node(parent, qualified) {
3775 break;
3776 }
3777 }
3778 current
3779}
3780
3781fn template_type_component_preserves_target(
3782 node: Node<'_>,
3783 candidates: &[CodeUnit],
3784 ctx: &ScanCtx<'_>,
3785) -> bool {
3786 template_reference_candidates_select_target(
3787 node,
3788 candidates,
3789 &ctx.analyzer,
3790 ctx.visibility,
3791 ctx.file,
3792 ctx.source,
3793 &ctx.spec.target,
3794 )
3795}
3796
3797fn template_reference_candidates_select_target(
3798 node: Node<'_>,
3799 candidates: &[CodeUnit],
3800 analyzer: &CppGraphSource<'_>,
3801 visibility: &VisibilityIndex<'_>,
3802 file: &ProjectFile,
3803 source: &str,
3804 target: &CodeUnit,
3805) -> bool {
3806 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3807 return !visibility.is_template_specialization(target);
3808 };
3809 let direct_template_name =
3810 template_reference_name_node(node).map(|name| node_text(name, source));
3811 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
3812 analyzer.type_alias_provider().is_some_and(|provider| {
3813 visibility
3814 .visible_identifier_candidates(file, name)
3815 .filter(|candidate| provider.is_type_alias(candidate))
3816 .any(|candidate| {
3817 visibility.template_alias_arguments_preserve_target(
3818 analyzer, file, candidate, &arguments, target,
3819 )
3820 })
3821 })
3822 });
3823 named_alias_selects_target
3824 || candidates.iter().any(|candidate| {
3825 (same_visible_symbol(candidate, target)
3826 && visibility.is_primary_template(target)
3827 && direct_template_name == Some(candidate.identifier()))
3828 || visibility.template_alias_arguments_preserve_target(
3829 analyzer, file, candidate, &arguments, target,
3830 )
3831 || visibility
3832 .resolve_template_arguments(file, candidate.clone(), &arguments)
3833 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
3834 })
3835}
3836
3837fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
3838 let template = if node.kind() == "template_type" {
3839 node
3840 } else {
3841 node.child_by_field_name("name")
3842 .filter(|name| name.kind() == "template_type")?
3843 };
3844 template.child_by_field_name("name")
3845}
3846
3847fn type_resolution_matches_target(
3848 node: Node<'_>,
3849 unit: &CodeUnit,
3850 candidates: &[CodeUnit],
3851 ctx: &ScanCtx<'_>,
3852) -> bool {
3853 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
3854}
3855
3856fn type_resolution_matches_unit_target(
3857 node: Node<'_>,
3858 unit: &CodeUnit,
3859 candidates: &[CodeUnit],
3860 target: &CodeUnit,
3861 ctx: &ScanCtx<'_>,
3862) -> bool {
3863 target_alias_candidates_visible(candidates, node, ctx)
3864 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
3865}
3866
3867fn type_resolution_identifies_unit_target(
3875 node: Node<'_>,
3876 unit: &CodeUnit,
3877 candidates: &[CodeUnit],
3878 target: &CodeUnit,
3879 ctx: &ScanCtx<'_>,
3880) -> bool {
3881 if !template_alias_owner_matches_reference(node, target, ctx) {
3882 return false;
3883 }
3884 if ctx.visibility.is_template_specialization(target)
3885 && cpp_template_reference_arguments(node, ctx.source).is_some()
3886 {
3887 let selected_unit =
3888 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
3889 ctx.visibility
3890 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
3891 .ok()
3892 });
3893 return selected_unit
3894 .as_ref()
3895 .is_some_and(|selected| same_visible_symbol(selected, target))
3896 || template_reference_candidates_select_target(
3897 node,
3898 candidates,
3899 &ctx.analyzer,
3900 ctx.visibility,
3901 ctx.file,
3902 ctx.source,
3903 target,
3904 );
3905 }
3906 ctx.visibility
3907 .same_template_member_identity(&ctx.analyzer, unit, target)
3908 || ctx.visibility.structured_class_alias_resolves_to_target(
3909 &ctx.analyzer,
3910 ctx.file,
3911 unit,
3912 target,
3913 )
3914 || candidates.iter().any(|candidate| {
3915 ctx.visibility
3916 .same_template_member_identity(&ctx.analyzer, candidate, target)
3917 || ctx.visibility.structured_class_alias_resolves_to_target(
3918 &ctx.analyzer,
3919 ctx.file,
3920 candidate,
3921 target,
3922 )
3923 })
3924}
3925
3926fn template_alias_owner_matches_reference(
3932 node: Node<'_>,
3933 target: &CodeUnit,
3934 ctx: &ScanCtx<'_>,
3935) -> bool {
3936 if !ctx
3937 .analyzer
3938 .type_alias_provider()
3939 .is_some_and(|provider| provider.is_type_alias(target))
3940 {
3941 return true;
3942 }
3943 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
3944 return true;
3945 };
3946 if !target_owner.is_class() {
3947 return true;
3948 }
3949 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
3950 return true;
3951 };
3952 if !ctx.visibility.is_template_specialization(&target_owner)
3953 && !ctx.visibility.is_template_specialization(&reference_owner)
3954 {
3955 return true;
3956 }
3957 same_visible_symbol(&target_owner, &reference_owner)
3958}
3959
3960fn inherited_injected_class_qualifier_scope<'tree>(
3961 node: Node<'tree>,
3962 ctx: &ScanCtx<'_>,
3963) -> Option<Node<'tree>> {
3964 let qualified = qualified_owner_components(node, ctx.source)?;
3965 if qualified.global || qualified.names.is_empty() {
3966 return None;
3967 }
3968 let injected_name = &qualified.names[0];
3969 if !ctx.spec.target.is_class()
3970 || ctx.spec.target.identifier() != injected_name
3971 || physically_visible_type_target(ctx).is_none()
3972 {
3973 return None;
3974 }
3975 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
3976 let owner = ctx.visibility.inherited_injected_class_owner(
3977 &ctx.analyzer,
3978 ctx.file,
3979 &enclosing_owner,
3980 injected_name,
3981 )?;
3982 same_visible_symbol(&owner, &ctx.spec.target)
3983 .then(|| qualified.nodes.first().copied())
3984 .flatten()
3985}
3986
3987fn target_guided_qualifier_type_scopes<'tree>(
3990 node: Node<'tree>,
3991 ctx: &ScanCtx<'_>,
3992) -> Option<Vec<Node<'tree>>> {
3993 if !matches!(
3994 node.kind(),
3995 "qualified_identifier" | "scoped_type_identifier"
3996 ) {
3997 return None;
3998 }
3999 let target = physically_visible_type_target(ctx)?;
4000 let qualified = qualified_owner_components(node, ctx.source)?;
4001 let lexical_scope = match enclosing_lexical_scope_components(
4010 node,
4011 &ctx.analyzer,
4012 ctx.visibility,
4013 ctx.file,
4014 ctx.source,
4015 ) {
4016 LexicalScopeResolution::Resolved(scope) => scope,
4017 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4018 enclosing_namespace_components(node, ctx.source)
4019 }
4020 };
4021 let indexed_owner_scope =
4022 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4023 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4024 let mut matches = Vec::new();
4025 for component_count in 1..=qualified.names.len() {
4026 let components = &qualified.names[..component_count];
4027 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4028 .collect::<Vec<_>>();
4029 let name = components.last()?;
4030 let mut candidates = Vec::new();
4031 let mut exact_candidates = Vec::new();
4032 for candidate in ctx
4033 .visibility
4034 .visible_identifier_candidates(ctx.file, name)
4035 .filter(|candidate| candidate.is_class())
4036 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4037 {
4038 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4039 brokk_bifrost_core::analyzer::Language::Cpp,
4040 &cpp_name_for(candidate),
4041 );
4042 if !candidate_components.ends_with(components)
4043 || candidates
4044 .iter()
4045 .any(|existing| same_logical_symbol(existing, candidate))
4046 {
4047 continue;
4048 }
4049 let exact_lexical_scope = lexical_tiers
4050 .iter()
4051 .any(|expected| expected == &candidate_components);
4052 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4053 let structured_owner_match = indexed_owner_scope
4054 .as_ref()
4055 .is_some_and(|owner| owner.starts_with(candidate_owner))
4056 || recovered_owner_scope
4057 .as_ref()
4058 .is_some_and(|owner| owner.starts_with(candidate_owner));
4059 let class_alias_owner_match = ctx
4060 .analyzer
4061 .type_alias_provider()
4062 .is_some_and(|provider| provider.is_type_alias(candidate))
4063 && member_alias_owner_matches_reference_for(candidate, node, ctx);
4064 let macro_namespace_owner_match = is_declaration_name(node)
4065 && macro_namespace_scope_matches(candidate_owner, node, ctx);
4066 if components.len() == 1
4067 && candidate_components != components
4068 && !exact_lexical_scope
4069 && !structured_owner_match
4070 && !class_alias_owner_match
4071 && !macro_namespace_owner_match
4072 {
4073 continue;
4074 }
4075 candidates.push(candidate.clone());
4076 if exact_lexical_scope {
4077 exact_candidates.push(candidate.clone());
4078 }
4079 }
4080 if !exact_candidates.is_empty() {
4081 candidates = exact_candidates;
4082 }
4083 let canonical_alias_target_matches = matches!(
4088 candidates.as_slice(),
4089 [candidate]
4090 if ctx
4091 .analyzer
4092 .type_alias_provider()
4093 .is_some_and(|provider| provider.is_type_alias(candidate))
4094 && type_candidate_visible_at_reference(candidate, node, ctx)
4095 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4096 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4097 brokk_bifrost_core::analyzer::Language::Cpp,
4098 &cpp_name_for(candidate),
4099 ) == components
4100 || member_alias_owner_matches_reference_for(candidate, node, ctx))
4101 );
4102 let direct_alias_target = ctx
4103 .analyzer
4104 .type_alias_provider()
4105 .is_some_and(|provider| provider.is_type_alias(target))
4106 && candidates
4107 .iter()
4108 .any(|candidate| same_symbol(candidate, target));
4109 let unique_target = matches!(
4110 candidates.as_slice(),
4111 [candidate] if same_visible_symbol(candidate, target)
4112 );
4113 if direct_alias_target || unique_target || canonical_alias_target_matches {
4114 let matched = if ctx
4115 .analyzer
4116 .type_alias_provider()
4117 .is_some_and(|provider| provider.is_type_alias(target))
4118 && ctx
4119 .analyzer
4120 .parent_of(target)
4121 .is_some_and(|owner| owner.is_class())
4122 && ctx
4123 .visibility
4124 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4125 && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4126 {
4127 qualified.nodes[component_count - 1]
4131 } else {
4132 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4133 };
4134 if template_type_component_preserves_target(matched, &candidates, ctx) {
4135 matches.push(matched);
4136 }
4137 }
4138 }
4139 (!matches.is_empty()).then_some(matches)
4140}
4141
4142fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4143 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4144 return false;
4145 };
4146 ctx.visibility
4147 .visible_identifier_candidates(ctx.file, target.identifier())
4148 .any(|candidate| {
4149 alias_provider.is_type_alias(candidate)
4150 && candidate.identifier() == target.identifier()
4151 && !same_visible_symbol(candidate, target)
4152 && ctx
4153 .analyzer
4154 .parent_of(candidate)
4155 .is_some_and(|owner| owner.is_class())
4156 })
4157}
4158
4159fn target_guided_unproven_out_of_line_owner<'tree>(
4162 node: Node<'tree>,
4163 ctx: &ScanCtx<'_>,
4164) -> Option<Node<'tree>> {
4165 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4166 || !is_declaration_name(node)
4167 {
4168 return None;
4169 }
4170 let target = physically_visible_type_target(ctx)?;
4171 if !target.is_class() {
4172 return None;
4173 }
4174 let qualified = qualified_owner_components(node, ctx.source)?;
4175 let target_components = canonical_cpp_scope_components(target);
4176 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4177 let parser_scope = enclosing_namespace_components(node, ctx.source);
4178 let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4179 if !parser_scope.is_empty() || target_namespace.is_empty() {
4180 Some(parser_scope)
4181 } else {
4182 indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4183 }
4184 })?;
4185 if has_malformed_wrapper_function_definition_ancestor(node)
4186 && target_namespace.starts_with(&scope)
4187 && target_namespace.len() > scope.len()
4188 {
4189 scope = target_namespace.to_vec();
4190 }
4191 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4192 .any(|components| components == target_components)
4193 {
4194 return None;
4195 }
4196 let owner_name = qualified.names.last()?;
4197 let candidates = ctx
4198 .visibility
4199 .visible_identifier_candidates(ctx.file, owner_name)
4200 .filter(|candidate| {
4201 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4202 })
4203 .collect::<Vec<_>>();
4204 if candidates.is_empty()
4205 || candidates
4206 .iter()
4207 .any(|candidate| !same_visible_symbol(candidate, target))
4208 {
4209 return None;
4210 }
4211 qualified.nodes.last().copied()
4212}
4213
4214fn macro_namespace_scope_matches(
4215 candidate_owner: &[String],
4216 node: Node<'_>,
4217 ctx: &ScanCtx<'_>,
4218) -> bool {
4219 let namespace = enclosing_namespace_components(node, ctx.source);
4220 if namespace.is_empty() || candidate_owner.is_empty() {
4221 return false;
4222 }
4223 let mut expanded_owner = Vec::new();
4224 for component in candidate_owner {
4225 if let Some(replacement) =
4226 ctx.visibility
4227 .object_macro_replacement_at(ctx.file, component, node.start_byte())
4228 {
4229 let replacement_components =
4230 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4231 brokk_bifrost_core::analyzer::Language::Cpp,
4232 &replacement,
4233 );
4234 if replacement_components.is_empty() {
4235 return false;
4236 }
4237 expanded_owner.extend(replacement_components);
4238 } else {
4239 expanded_owner.push(component.clone());
4240 }
4241 }
4242 expanded_owner == namespace
4243}
4244
4245fn target_guided_missing_type_leaf<'tree>(
4246 node: Node<'tree>,
4247 ctx: &ScanCtx<'_>,
4248) -> Option<Node<'tree>> {
4249 physically_visible_type_target(ctx)?;
4250 target_guided_missing_dependent_nested_type_leaf(node, ctx)
4251 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
4252 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
4253 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
4254 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
4255 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4256 .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
4257}
4258
4259fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4263 node: Node<'tree>,
4264 ctx: &ScanCtx<'_>,
4265) -> Option<Node<'tree>> {
4266 if node.kind() != "type_identifier"
4267 || is_declaration_name(node)
4268 || local_type_name_shadows(node, ctx)
4269 {
4270 return None;
4271 }
4272 let alias_provider = ctx.analyzer.type_alias_provider()?;
4273 let name = node_text(node, ctx.source);
4274 let aliases = ctx
4275 .visibility
4276 .visible_identifier_candidates(ctx.file, name)
4277 .filter(|candidate| alias_provider.is_type_alias(candidate))
4278 .filter(|candidate| {
4279 ctx.analyzer
4280 .parent_of(candidate)
4281 .is_some_and(|owner| owner.is_class())
4282 })
4283 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4284 .filter(|candidate| {
4285 ctx.visibility
4286 .external_type_candidate_guard_compatible_in_context(
4287 &ctx.analyzer,
4288 ctx.file,
4289 candidate,
4290 node,
4291 )
4292 })
4293 .collect::<Vec<_>>();
4294 (!aliases.is_empty()
4295 && aliases.iter().all(|candidate| {
4296 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4297 }))
4298 .then_some(node)
4299}
4300
4301fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
4305 node: Node<'tree>,
4306 ctx: &ScanCtx<'_>,
4307) -> Option<Node<'tree>> {
4308 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
4309 matches!(
4310 declaration.kind(),
4311 "parameter_declaration" | "optional_parameter_declaration"
4312 )
4313 });
4314 let placement_new_type = node.parent().is_some_and(|parent| {
4315 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
4316 });
4317 if !parameter && !placement_new_type {
4318 return None;
4319 }
4320 if !ctx
4321 .analyzer
4322 .type_alias_provider()
4323 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4324 || !type_alias_owner_matches_structured_reference(node, ctx)
4325 {
4326 return None;
4327 }
4328 target_guided_missing_declaration_type_leaf(node, ctx)
4329}
4330
4331fn target_guided_missing_dependent_nested_type_leaf<'tree>(
4338 node: Node<'tree>,
4339 ctx: &ScanCtx<'_>,
4340) -> Option<Node<'tree>> {
4341 if !matches!(
4342 node.kind(),
4343 "qualified_identifier" | "scoped_type_identifier"
4344 ) || !qualified_type_scope_contains_template(node)
4345 {
4346 return None;
4347 }
4348 let name = node
4349 .child_by_field_name("name")
4350 .filter(|name| name.kind() == "type_identifier")?;
4351 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
4352 return None;
4353 }
4354 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
4355 if !owner_target.is_class() {
4356 return None;
4357 }
4358 let owner = node.child_by_field_name("scope")?;
4359 let owner_resolution = resolve_type_node_lexically_for_target(
4360 owner,
4361 &ctx.analyzer,
4362 ctx.visibility,
4363 &ctx.ordinary_type_imports,
4364 ctx.file,
4365 ctx.source,
4366 &owner_target,
4367 Some(&ctx.lexical_scope_cache),
4368 ctx.recovered_sentinel_scope(owner).as_deref(),
4369 );
4370 if matches!(
4371 owner_resolution,
4372 LexicalTypeResolution::Resolved {
4373 ref unit,
4374 ref candidates,
4375 ..
4376 } if type_resolution_matches_unit_target(
4377 owner,
4378 unit,
4379 candidates,
4380 &owner_target,
4381 ctx,
4382 )
4383 ) {
4384 return Some(name);
4385 }
4386
4387 let qualified = qualified_owner_components(node, ctx.source)?;
4388 let parser_namespace = enclosing_namespace_components(node, ctx.source);
4389 let orphaned_namespace = ctx
4390 .orphaned_namespaces
4391 .iter()
4392 .filter(|envelope| envelope.body_end < node.start_byte())
4393 .max_by_key(|envelope| envelope.body_end)
4394 .map(|envelope| envelope.components.clone());
4395 let indexed_scope = ctx
4396 .recovered_sentinel_scope(node)
4397 .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
4398 .or(orphaned_namespace)
4399 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
4400 let owner_components = canonical_cpp_scope_components(&owner_target);
4401 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
4402 .any(|components| components == owner_components)
4403 {
4404 return None;
4405 }
4406 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
4407 .into_iter()
4408 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
4409 .collect::<Vec<_>>();
4410 (!scoped_candidates.is_empty()
4411 && scoped_candidates
4412 .iter()
4413 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
4414 .then_some(name)
4415}
4416
4417fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
4434 node: Node<'tree>,
4435 ctx: &ScanCtx<'_>,
4436) -> Option<Node<'tree>> {
4437 let target = physically_visible_type_target(ctx)?;
4438 let leaf = template_reference_name_node(node).unwrap_or(node);
4439 let name = node_text(leaf, ctx.source);
4440 let (components, global) = type_reference_components(node, ctx.source)?;
4441 if global || components.len() != 1 || components[0] != name {
4442 return None;
4443 }
4444 if !target.is_class()
4445 || ctx
4446 .analyzer
4447 .type_alias_provider()
4448 .is_some_and(|provider| provider.is_type_alias(target))
4449 || is_declaration_name(leaf)
4450 || name != target.identifier()
4451 || ctx.local_shadows.is_shadowed(name)
4452 || local_type_name_shadows(leaf, ctx)
4453 || !ctx.visibility.is_physically_visible(ctx.file, target)
4454 || !ctx.visibility.external_type_candidate_visible_in_context(
4455 &ctx.analyzer,
4456 ctx.file,
4457 target,
4458 leaf,
4459 )
4460 {
4461 return None;
4462 }
4463
4464 let target_components = canonical_cpp_scope_components(target);
4465 let target_package_len = target_components.len().checked_sub(1)?;
4466 let target_package = &target_components[..target_package_len];
4467 let surviving_namespace = enclosing_namespace_components(leaf, ctx.source);
4468 if !target_package.starts_with(&surviving_namespace) {
4469 return None;
4470 }
4471 let bounded_macro_type = recovered_macro_decorated_declarator_type(leaf).is_some();
4472 let (body_end, namespace_components) = if bounded_macro_type {
4473 let mut root = leaf;
4474 while let Some(parent) = root.parent() {
4475 root = parent;
4476 }
4477 ctx.orphaned_namespace_scopes
4478 .get_or_init(|| OrphanedNamespaceTypeScopeIndex::build(root, ctx.source))
4479 .scope_at(leaf.start_byte())?
4480 } else {
4481 let envelope = ctx
4482 .orphaned_namespaces
4483 .iter()
4484 .filter(|envelope| {
4485 envelope.body_end < leaf.start_byte()
4486 && envelope.components.as_slice() == target_package
4487 })
4488 .max_by_key(|envelope| envelope.body_end)?;
4489 (envelope.body_end, envelope.components.as_slice())
4490 };
4491 if namespace_components != target_package {
4492 return None;
4493 }
4494
4495 if target.source() == ctx.file
4496 && !ctx.target_declaration_ranges.iter().any(|range| {
4497 range.start_byte < body_end
4498 && range.end_byte <= body_end.saturating_add(1)
4501 })
4502 {
4503 return None;
4504 }
4505
4506 let candidates = visible_type_identifier_candidates(ctx, name)
4511 .into_iter()
4512 .filter(|candidate| {
4513 let components = canonical_cpp_scope_components(candidate);
4514 components == target_components
4515 || components
4516 .get(..components.len().saturating_sub(1))
4517 .is_some_and(|package| package == surviving_namespace)
4518 })
4519 .collect::<Vec<_>>();
4520 let target_is_visible = candidates
4521 .iter()
4522 .any(|candidate| same_visible_symbol(candidate, target));
4523 let ordinary_candidates_are_unanimous = candidates
4524 .iter()
4525 .all(|candidate| same_visible_symbol(candidate, target));
4526 if !target_is_visible || (!bounded_macro_type && !ordinary_candidates_are_unanimous) {
4527 return None;
4528 }
4529 Some(leaf)
4530}
4531
4532fn orphaned_namespace_alias_type_resolution(
4537 node: Node<'_>,
4538 ctx: &ScanCtx<'_>,
4539) -> Option<LexicalTypeResolution> {
4540 let (reference_components, global) = type_reference_components(node, ctx.source)?;
4541 if global
4542 || reference_components.len() != 1
4543 || is_declaration_name(node)
4544 || local_type_name_shadows(node, ctx)
4545 {
4546 return None;
4547 }
4548
4549 let surviving_namespace = enclosing_namespace_components(node, ctx.source);
4550 let envelope = ctx
4551 .orphaned_namespaces
4552 .iter()
4553 .filter(|envelope| {
4554 envelope.body_end < node.start_byte()
4555 && envelope.components.starts_with(&surviving_namespace)
4556 })
4557 .max_by_key(|envelope| envelope.body_end)?;
4558 let resolution = resolve_type_node_lexically_for_target(
4559 node,
4560 &ctx.analyzer,
4561 ctx.visibility,
4562 &ctx.ordinary_type_imports,
4563 ctx.file,
4564 ctx.source,
4565 &ctx.spec.target,
4566 Some(&ctx.lexical_scope_cache),
4567 Some(&envelope.components),
4568 );
4569 let LexicalTypeResolution::Resolved {
4570 ref unit,
4571 ref components,
4572 ref candidates,
4573 } = resolution
4574 else {
4575 return None;
4576 };
4577 let alias_provider = ctx.analyzer.type_alias_provider()?;
4578 if components.len() != envelope.components.len() + 1
4579 || !components.starts_with(&envelope.components)
4580 || candidates.is_empty()
4581 || candidates.iter().any(|candidate| {
4582 !alias_provider.is_type_alias(candidate)
4583 || canonical_cpp_scope_components(candidate) != *components
4584 })
4585 || !type_resolution_matches_target(node, unit, candidates, ctx)
4586 {
4587 return None;
4588 }
4589 Some(resolution)
4590}
4591
4592fn target_guided_missing_member_alias_type_leaf<'tree>(
4598 node: Node<'tree>,
4599 ctx: &ScanCtx<'_>,
4600) -> Option<Node<'tree>> {
4601 if !is_cpp_template_argument_type_leaf(node)
4602 || is_declaration_name(node)
4603 || ctx
4604 .target_declaration_ranges
4605 .iter()
4606 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
4607 || node_text(node, ctx.source) != ctx.spec.target.identifier()
4608 || local_type_name_shadows(node, ctx)
4609 || !ctx
4610 .analyzer
4611 .type_alias_provider()
4612 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4613 {
4614 return None;
4615 }
4616 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
4617 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
4618 if !owner_scope_matches
4619 && !indexed_scope.is_some_and(|scope| {
4620 indexed_scope_matches_target_name(
4621 &scope,
4622 &[ctx.spec.target.identifier().to_string()],
4623 false,
4624 &ctx.spec.target,
4625 )
4626 })
4627 {
4628 return None;
4629 }
4630 if !(ctx.visibility.external_type_candidate_visible_in_context(
4637 &ctx.analyzer,
4638 ctx.file,
4639 &ctx.spec.target,
4640 node,
4641 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
4642 {
4643 return None;
4644 }
4645 let target_visible = ctx
4646 .visibility
4647 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4648 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4649 target_visible.then_some(node)
4650}
4651
4652fn target_guided_missing_template_argument_type_leaf<'tree>(
4658 node: Node<'tree>,
4659 ctx: &ScanCtx<'_>,
4660) -> Option<Node<'tree>> {
4661 let target = &ctx.spec.target;
4662 let name = node_text(node, ctx.source);
4663 if !target.is_class()
4664 || !is_cpp_template_argument_type_leaf(node)
4665 || is_declaration_name(node)
4666 || name != target.identifier()
4667 || ctx.local_shadows.is_shadowed(name)
4668 || local_type_name_shadows(node, ctx)
4669 || !ctx.visibility.is_physically_visible(ctx.file, target)
4670 || ctx
4671 .analyzer
4672 .type_alias_provider()
4673 .is_some_and(|provider| provider.is_type_alias(target))
4674 {
4675 return None;
4676 }
4677
4678 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4679 let target_components = canonical_cpp_scope_components(target);
4680 if target_components.last().map(String::as_str) != Some(name)
4681 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4682 .any(|components| components == target_components)
4683 {
4684 return None;
4685 }
4686
4687 let candidates = visible_type_identifier_candidates(ctx, name);
4691 if candidates.is_empty()
4692 || candidates.iter().any(|candidate| {
4693 !candidate.is_class()
4694 || ctx
4695 .analyzer
4696 .type_alias_provider()
4697 .is_some_and(|provider| provider.is_type_alias(candidate))
4698 || (!same_visible_symbol(candidate, target)
4699 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4700 .any(|components| components == canonical_cpp_scope_components(candidate)))
4701 })
4702 || !candidates
4703 .iter()
4704 .any(|candidate| same_visible_symbol(candidate, target))
4705 {
4706 return None;
4707 }
4708
4709 ctx.visibility
4712 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
4713 .then_some(node)
4714}
4715
4716fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
4719 let mut function = node;
4720 while function.kind() != "function_definition" {
4721 function = function.parent()?;
4722 }
4723 let macro_name = function_definition_name_node(function)?;
4724 if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
4725 return None;
4726 }
4727
4728 let declaration = function.prev_named_sibling()?;
4734 if declaration.kind() != "declaration"
4735 || !declaration.has_error()
4736 || function.start_position().row > declaration.end_position().row + 1
4737 {
4738 return None;
4739 }
4740 let mut missing_semicolon = false;
4741 let mut real_semicolon = false;
4742 for index in 0..declaration.child_count() {
4743 let Some(child) = declaration.child(index) else {
4744 continue;
4745 };
4746 if child.kind() == ";" {
4747 missing_semicolon |= child.is_missing();
4748 real_semicolon |= !child.is_missing();
4749 }
4750 }
4751 if !missing_semicolon || real_semicolon {
4752 return None;
4753 }
4754
4755 let initializer = declaration.child_by_field_name("declarator")?;
4756 if initializer.kind() != "init_declarator"
4757 || initializer
4758 .child_by_field_name("value")
4759 .is_none_or(|value| value.kind() != "argument_list")
4760 {
4761 return None;
4762 }
4763 let qualified_name = initializer
4764 .child_by_field_name("declarator")
4765 .and_then(declarator_name_node)?;
4766 let qualified = qualified_owner_components(qualified_name, ctx.source)?;
4767 let lexical_scope = enclosing_namespace_components(function, ctx.source);
4768 match ctx.visibility.resolve_type_components_lexically(
4769 &ctx.analyzer,
4770 ctx.file,
4771 &qualified.names,
4772 qualified.global,
4773 &lexical_scope,
4774 ) {
4775 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
4776 LexicalTypeResolution::Resolved { .. }
4777 | LexicalTypeResolution::Ambiguous
4778 | LexicalTypeResolution::Missing => None,
4779 }
4780}
4781
4782fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4787 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
4788}
4789
4790fn member_alias_owner_matches_reference_for(
4791 target: &CodeUnit,
4792 node: Node<'_>,
4793 ctx: &ScanCtx<'_>,
4794) -> bool {
4795 let Some(owner) = ctx.analyzer.parent_of(target) else {
4796 return false;
4797 };
4798 if !owner.is_class() {
4799 return false;
4800 }
4801 let reference_owner = ctx
4802 .class_ranges
4803 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
4804 .or_else(|| structured_enclosing_owner(node, ctx));
4805 if reference_owner.as_ref().is_some_and(|reference_owner| {
4806 ctx.visibility
4807 .same_template_owner_identity(&owner, reference_owner)
4808 }) {
4809 return true;
4810 }
4811 if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
4812 ctx.visibility
4813 .same_template_owner_identity(&owner, &reference_owner)
4814 }) {
4815 return true;
4816 }
4817 if reference_owner.is_some_and(|reference_owner| {
4818 matches!(
4819 resolve_declaring_member_owner(
4820 &ctx.analyzer,
4821 ctx.visibility,
4822 ctx.file,
4823 &reference_owner,
4824 target.identifier(),
4825 ),
4826 EnclosingMemberOwnerResolution::Owner(declaring_owner)
4827 if ctx
4828 .visibility
4829 .same_template_owner_identity(&owner, &declaring_owner)
4830 )
4831 }) {
4832 return true;
4833 }
4834 let range = Range {
4835 start_byte: node.start_byte(),
4836 end_byte: node.end_byte(),
4837 start_line: node.start_position().row + 1,
4838 end_line: node.end_position().row + 1,
4839 };
4840 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
4841 while let Some(candidate) = indexed_enclosing {
4842 if candidate.is_class()
4843 && ctx
4844 .visibility
4845 .same_template_owner_identity(&owner, &candidate)
4846 {
4847 return true;
4848 }
4849 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
4850 }
4851 if let Some(reference_body) = malformed_recovered_class_body(node) {
4852 let mut root = node;
4853 while let Some(parent) = root.parent() {
4854 root = parent;
4855 }
4856 if ctx.analyzer.ranges(target).iter().any(|range| {
4857 root.descendant_for_byte_range(range.start_byte, range.end_byte)
4858 .and_then(malformed_recovered_class_body)
4859 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
4860 }) {
4861 return true;
4862 }
4863 }
4864 if structured_enclosing_owner(node, ctx)
4865 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
4866 {
4867 return true;
4868 }
4869 let owner_components = canonical_cpp_scope_components(&owner);
4870 if ctx
4871 .recovered_sentinel_scope(node)
4872 .is_some_and(|scope| scope == owner_components)
4873 {
4874 return true;
4875 }
4876 if matches!(
4877 cached_enclosing_lexical_scope_components_with_unresolved_owner(
4878 node,
4879 &ctx.analyzer,
4880 ctx.visibility,
4881 ctx.file,
4882 ctx.source,
4883 false,
4884 false,
4885 Some(&ctx.lexical_scope_cache),
4886 ),
4887 LexicalScopeResolution::Resolved(reference_scope)
4888 if reference_scope == owner_components
4889 ) {
4890 return true;
4891 }
4892 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4893 else {
4894 return false;
4895 };
4896 !owner_components.is_empty() && reference_scope == owner_components
4897}
4898
4899fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
4900 loop {
4901 if node.kind() == "compound_statement"
4902 && node
4903 .parent()
4904 .is_some_and(|parent| parent.kind() == "declaration_list")
4905 && node.prev_named_sibling().is_some_and(|header| {
4906 header.kind() == "ERROR"
4907 && header.end_byte() <= node.start_byte()
4908 && error_contains_class_header(header)
4909 })
4910 {
4911 return Some(node);
4912 }
4913 node = node.parent()?;
4914 }
4915}
4916
4917fn error_contains_class_header(node: Node<'_>) -> bool {
4918 let mut pending = vec![(node, 0usize)];
4919 while let Some((current, depth)) = pending.pop() {
4920 if matches!(current.kind(), "class" | "struct" | "union") {
4921 let mut sibling = current.next_sibling();
4922 let mut saw_name = false;
4923 while let Some(candidate) = sibling {
4924 match candidate.kind() {
4925 "comment" => {}
4926 "{" | "base_class_clause" | ":" => return saw_name,
4927 "identifier" | "type_identifier" if !saw_name => saw_name = true,
4928 _ if !candidate.is_named() => {}
4929 _ => break,
4930 }
4931 sibling = candidate.next_sibling();
4932 }
4933 }
4934 if depth >= 1 {
4935 continue;
4936 }
4937 let mut cursor = current.walk();
4938 pending.extend(
4939 current
4940 .children(&mut cursor)
4941 .filter(|child| child.kind() != "compound_statement")
4942 .map(|child| (child, depth + 1)),
4943 );
4944 }
4945 false
4946}
4947
4948fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4949 has_ancestor_kind(node, "compound_statement")
4950 && ctx
4951 .visibility
4952 .external_type_candidate_guard_compatible_in_context(
4953 &ctx.analyzer,
4954 ctx.file,
4955 &ctx.spec.target,
4956 node,
4957 )
4958}
4959
4960fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4961 ctx.analyzer
4962 .type_alias_provider()
4963 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4964 && member_alias_owner_matches_reference(node, ctx)
4965}
4966
4967fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4971 if !ctx
4972 .analyzer
4973 .type_alias_provider()
4974 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4975 {
4976 return false;
4977 }
4978 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4979 return false;
4980 };
4981 let mut reference_owner = structured_enclosing_owner(node, ctx);
4982 while let Some(owner) = reference_owner {
4983 if same_logical_symbol(&target_owner, &owner) {
4984 return true;
4985 }
4986 reference_owner = ctx.analyzer.parent_of(&owner);
4987 }
4988 false
4989}
4990
4991fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4996 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4997 return false;
4998 };
4999 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
5000 return false;
5001 };
5002 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
5003 return false;
5004 };
5005 let candidates = ctx
5006 .visibility
5007 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
5008 .filter(|candidate| {
5009 candidate.is_class()
5010 && alias_provider.is_type_alias(candidate)
5011 && !same_visible_symbol(candidate, &ctx.spec.target)
5012 })
5013 .cloned()
5014 .collect::<Vec<_>>();
5015
5016 let mut owner = Some(reference_owner);
5017 while let Some(owner_unit) = owner {
5018 if same_logical_symbol(&target_owner, &owner_unit) {
5019 return false;
5020 }
5021 if candidates.iter().any(|candidate| {
5022 ctx.analyzer
5023 .parent_of(candidate)
5024 .is_some_and(|candidate_owner| {
5025 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5026 })
5027 && ctx
5028 .visibility
5029 .external_type_candidate_guard_compatible_in_context(
5030 &ctx.analyzer,
5031 ctx.file,
5032 candidate,
5033 node,
5034 )
5035 }) {
5036 return true;
5037 }
5038 owner = ctx.analyzer.parent_of(&owner_unit);
5039 }
5040 false
5041}
5042
5043fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5044 if cpp_active_template_type_parameter(node, ctx.spec.target.identifier(), ctx.source) {
5045 return true;
5046 }
5047 let Some(callable) = nearest_callable_scope(node) else {
5048 return false;
5049 };
5050 let mut root_callable = callable;
5051 let mut ancestor = callable.parent();
5052 while let Some(current) = ancestor {
5053 if matches!(current.kind(), "function_definition" | "lambda_expression") {
5054 root_callable = current;
5055 }
5056 ancestor = current.parent();
5057 }
5058
5059 let mut stack = vec![root_callable];
5060 while let Some(current) = stack.pop() {
5061 if current.start_byte() >= node.start_byte() {
5062 continue;
5063 }
5064 if let Some(name) = local_type_name_declaration_node(current)
5065 && node_text(name, ctx.source) == ctx.spec.target.identifier()
5066 && nearest_callable_scope(current).is_some_and(|owner| {
5067 !is_malformed_wrapper_function_definition(owner)
5068 && owner.start_byte() <= callable.start_byte()
5069 && callable.end_byte() <= owner.end_byte()
5070 })
5071 && local_alias_scope_contains_node(current, node)
5072 {
5073 return true;
5074 }
5075 let mut cursor = current.walk();
5076 stack.extend(current.named_children(&mut cursor));
5077 }
5078 false
5079}
5080
5081fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5082 local_type_alias_name_node(node).or_else(|| match node.kind() {
5083 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5084 .child_by_field_name("name")
5085 .filter(|name| is_declaration_name(*name)),
5086 _ => None,
5087 })
5088}
5089
5090fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5091 loop {
5092 if matches!(node.kind(), "function_definition" | "lambda_expression") {
5093 return Some(node);
5094 }
5095 node = node.parent()?;
5096 }
5097}
5098
5099fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5100 match node.kind() {
5101 "alias_declaration" => node.child_by_field_name("name"),
5102 "type_definition" => node
5103 .child_by_field_name("declarator")
5104 .and_then(declarator_name_node),
5105 _ => None,
5106 }
5107}
5108
5109fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5110 let mut current = alias.parent();
5111 while let Some(parent) = current {
5112 if matches!(
5113 parent.kind(),
5114 "class_specifier" | "struct_specifier" | "union_specifier"
5115 ) {
5116 return false;
5117 }
5118 if parent.kind() == "compound_statement" {
5119 return parent.start_byte() <= node.start_byte()
5120 && node.end_byte() <= parent.end_byte();
5121 }
5122 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5123 let Some(body) = parent.child_by_field_name("body") else {
5124 return false;
5125 };
5126 return node_is_within(body, alias) && node_is_within(body, node);
5127 }
5128 current = parent.parent();
5129 }
5130 false
5131}
5132
5133fn target_guided_missing_declaration_type_leaf<'tree>(
5134 node: Node<'tree>,
5135 ctx: &ScanCtx<'_>,
5136) -> Option<Node<'tree>> {
5137 if is_declaration_name(node) {
5138 return None;
5139 }
5140 let component_nodes = cpp_name_component_nodes(node)?;
5141 let name_node = component_nodes.last().copied()?;
5142 let name = node_text(name_node, ctx.source);
5143 if name != ctx.spec.target.identifier() {
5144 return None;
5145 }
5146 let inside_target_declaration = ctx
5147 .target_declaration_ranges
5148 .iter()
5149 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5150 if !inside_target_declaration
5151 && !ctx.visibility.external_type_candidate_visible_in_context(
5152 &ctx.analyzer,
5153 ctx.file,
5154 &ctx.spec.target,
5155 node,
5156 )
5157 {
5158 return None;
5159 }
5160 let components = component_nodes
5161 .iter()
5162 .map(|component| node_text(*component, ctx.source).to_string())
5163 .collect::<Vec<_>>();
5164 let local_alias_shadow = local_type_name_shadows(node, ctx);
5165 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5166 let indexed_alias_owner = ctx
5167 .analyzer
5168 .type_alias_provider()
5169 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5170 && member_alias_owner_matches_reference(node, ctx);
5171 let target_alias_self_reference = inside_target_declaration
5172 && ctx
5173 .analyzer
5174 .type_alias_provider()
5175 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5176 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5177 &ctx.analyzer,
5178 ctx.file,
5179 &ctx.spec.target,
5180 node,
5181 ) || member_alias_complete_class_context(node, ctx);
5182 if !target_alias_self_reference
5183 && !local_alias_shadow
5184 && member_alias_visible
5185 && (structured_alias_owner || indexed_alias_owner)
5186 {
5187 return Some(node);
5188 }
5189 let declaration = nearest_declaration_type_context(node)?;
5190 let candidates = visible_type_identifier_candidates(ctx, name);
5191 let unique_visible_target = !candidates.is_empty()
5192 && candidates
5193 .iter()
5194 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5195 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5196 let exact_scope_match = indexed_scope_matches_target_name(
5197 &indexed_scope,
5198 &components,
5199 is_globally_qualified_cpp_name(node),
5200 &ctx.spec.target,
5201 );
5202 if matches!(declaration.kind(), "field_declaration" | "declaration") {
5203 let parser_lost_declaration_scope =
5204 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5205 && unique_visible_target;
5206 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5207 }
5208 let lost_namespace_parameter_context =
5209 matches!(
5210 declaration.kind(),
5211 "parameter_declaration" | "optional_parameter_declaration"
5212 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5213 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5214 return Some(node);
5215 }
5216 None
5217}
5218
5219fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5220 node: Node<'tree>,
5221 ctx: &ScanCtx<'_>,
5222) -> Option<Node<'tree>> {
5223 let mut stack = vec![node];
5224 while let Some(candidate) = stack.pop() {
5225 if candidate.kind() == "type_identifier"
5226 && !is_declaration_name(candidate)
5227 && matches!(
5228 candidate.parent().map(|parent| parent.kind()),
5229 Some("template_type")
5230 )
5231 {
5232 let mut current = candidate.parent();
5233 let mut saw_qualified = false;
5234 let mut saw_dependent = false;
5235 let mut saw_type_descriptor = false;
5236 let mut saw_alias_declaration = false;
5237 while let Some(ancestor) = current {
5238 match ancestor.kind() {
5239 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5240 "dependent_type" => saw_dependent = true,
5241 "type_descriptor" => saw_type_descriptor = true,
5242 "alias_declaration" => {
5243 saw_alias_declaration = true;
5244 break;
5245 }
5246 "template_type"
5247 | "template_argument_list"
5248 | "typename"
5249 | "template_declaration" => {}
5250 _ => {}
5251 }
5252 current = ancestor.parent();
5253 }
5254 let name = node_text(candidate, ctx.source);
5255 let visible_candidates = visible_type_identifier_candidates(ctx, name);
5256 let canonical_alias_target = visible_candidates
5257 .iter()
5258 .filter_map(|alias| ctx.visibility.alias_target(alias))
5259 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5260 let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
5261 &ctx.analyzer,
5262 ctx.file,
5263 name,
5264 &ctx.spec.target,
5265 ) || canonical_alias_target;
5266 if saw_qualified
5267 && saw_dependent
5268 && saw_type_descriptor
5269 && saw_alias_declaration
5270 && alias_resolves
5271 && ctx.visibility.external_type_candidate_visible_in_context(
5272 &ctx.analyzer,
5273 ctx.file,
5274 &ctx.spec.target,
5275 candidate,
5276 )
5277 {
5278 return Some(candidate);
5279 }
5280 }
5281 for index in (0..candidate.named_child_count()).rev() {
5282 if let Some(child) = candidate.named_child(index) {
5283 stack.push(child);
5284 }
5285 }
5286 }
5287 None
5288}
5289
5290fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5291 let mut current = Some(node);
5292 while let Some(ancestor) = current {
5293 if matches!(
5294 ancestor.kind(),
5295 "field_declaration"
5296 | "parameter_declaration"
5297 | "optional_parameter_declaration"
5298 | "declaration"
5299 | "type_descriptor"
5300 ) {
5301 let contains_type = ancestor
5302 .child_by_field_name("type")
5303 .is_some_and(|type_node| {
5304 type_node.start_byte() <= node.start_byte()
5305 && node.end_byte() <= type_node.end_byte()
5306 });
5307 if contains_type
5308 && !(ancestor.kind() == "type_descriptor"
5309 && is_cpp_template_argument_type_leaf(node))
5310 {
5311 return Some(ancestor);
5312 }
5313 if ancestor.kind() == "type_descriptor"
5314 && ancestor.parent().is_some_and(|parent| {
5315 matches!(
5316 parent.kind(),
5317 "cast_expression"
5318 | "new_expression"
5319 | "sizeof_expression"
5320 | "alignof_expression"
5321 | "typeid_expression"
5322 )
5323 })
5324 {
5325 return Some(ancestor);
5326 }
5327 }
5328 if matches!(
5329 ancestor.kind(),
5330 "compound_statement"
5331 | "translation_unit"
5332 | "namespace_definition"
5333 | "alias_declaration"
5334 | "type_definition"
5335 | "base_class_clause"
5336 ) {
5337 return None;
5338 }
5339 current = ancestor.parent();
5340 }
5341 None
5342}
5343
5344fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5345 let mut candidates = Vec::new();
5346 for candidate in ctx
5347 .visibility
5348 .visible_identifier_candidates(ctx.file, name)
5349 .filter(|candidate| {
5350 candidate.is_class()
5351 || ctx
5352 .analyzer
5353 .type_alias_provider()
5354 .is_some_and(|provider| provider.is_type_alias(candidate))
5355 })
5356 {
5357 if !candidates
5358 .iter()
5359 .any(|existing| same_logical_symbol(existing, candidate))
5360 {
5361 candidates.push(candidate.clone());
5362 }
5363 }
5364 candidates
5365}
5366
5367fn target_guided_static_cast_alias_type_descriptor<'tree>(
5372 node: Node<'tree>,
5373 ctx: &ScanCtx<'_>,
5374) -> Option<Node<'tree>> {
5375 if node.kind() != "type_descriptor" {
5376 return None;
5377 }
5378 let argument_list = node.parent().filter(|parent| {
5379 parent.kind() == "template_argument_list"
5380 && parent.named_child_count() == 1
5381 && parent.named_child(0) == Some(node)
5382 })?;
5383 let template = argument_list.parent().filter(|parent| {
5384 parent.kind() == "template_function"
5385 && parent.child_by_field_name("arguments") == Some(argument_list)
5386 })?;
5387 let name = template.child_by_field_name("name")?;
5388 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5389 return None;
5390 }
5391 let target = &ctx.spec.target;
5392 if node_text(node, ctx.source) != target.identifier()
5393 || !ctx
5394 .analyzer
5395 .type_alias_provider()
5396 .is_some_and(|provider| provider.is_type_alias(target))
5397 || !ctx.visibility.is_physically_visible(ctx.file, target)
5398 || !ctx.visibility.external_type_candidate_visible_in_context(
5399 &ctx.analyzer,
5400 ctx.file,
5401 target,
5402 node,
5403 )
5404 {
5405 return None;
5406 }
5407
5408 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5409 let target_scope = canonical_cpp_scope_components(target);
5410 let name_components = [target.identifier().to_string()];
5411 if !lexical_component_tiers(&name_components, false, &indexed_scope)
5412 .any(|components| components == target_scope)
5413 {
5414 return None;
5415 }
5416
5417 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5418 if !candidates
5419 .iter()
5420 .any(|candidate| same_visible_symbol(candidate, target))
5421 {
5422 return None;
5423 }
5424 if candidates.iter().any(|candidate| {
5425 !same_visible_symbol(candidate, target)
5426 && lexical_component_tiers(&name_components, false, &indexed_scope)
5427 .any(|components| components == canonical_cpp_scope_components(candidate))
5428 }) {
5429 return None;
5430 }
5431 Some(node)
5432}
5433
5434fn indexed_scope_matches_target_name(
5435 indexed_scope: &[String],
5436 components: &[String],
5437 global: bool,
5438 target: &CodeUnit,
5439) -> bool {
5440 let target_name = cpp_name_for(target);
5441 lexical_component_tiers(components, global, indexed_scope)
5442 .any(|qualified| qualified.join("::") == target_name)
5443}
5444
5445fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5446 if target.package_name().is_empty() {
5447 return false;
5448 }
5449 if indexed_scope.len() <= 1 {
5450 return true;
5451 }
5452 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5453 brokk_bifrost_core::analyzer::Language::Cpp,
5454 &cpp_name_for(target),
5455 );
5456 target_scope.pop();
5457 (1..indexed_scope.len())
5458 .rev()
5459 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5460}
5461
5462fn indexed_enclosing_lexical_scope(
5463 analyzer: &CppGraphSource<'_>,
5464 file: &ProjectFile,
5465 node: Node<'_>,
5466) -> Option<Vec<String>> {
5467 let range = Range {
5468 start_byte: node.start_byte(),
5469 end_byte: node.end_byte(),
5470 start_line: node.start_position().row,
5471 end_line: node.end_position().row,
5472 };
5473 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
5474 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5475 brokk_bifrost_core::analyzer::Language::Cpp,
5476 &cpp_name_for(&enclosing),
5477 );
5478 if !enclosing.is_class() && !enclosing.is_module() {
5479 components.pop();
5480 }
5481 Some(components)
5482}
5483
5484fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
5485 if node.kind() != "qualified_identifier" {
5486 return None;
5487 }
5488 let mut stack = vec![node];
5489 while let Some(current) = stack.pop() {
5490 if current.kind() != "qualified_identifier" {
5491 continue;
5492 }
5493 if let Some(scope) = current.child_by_field_name("scope") {
5494 let text = qualified_scope_text(scope, ctx.source);
5495 if name_mentions(&text, &ctx.spec.member_name) {
5496 return Some(scope);
5497 }
5498 }
5499 let mut cursor = current.walk();
5500 for child in current.named_children(&mut cursor) {
5501 if child.kind() == "qualified_identifier" {
5502 stack.push(child);
5503 }
5504 }
5505 }
5506 None
5507}
5508
5509fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
5510 let mut parts = vec![node_text(scope, source).to_string()];
5511 let mut current = scope.parent();
5512 while let Some(qualified) = current {
5513 let Some(parent) = qualified.parent() else {
5514 break;
5515 };
5516 if parent.kind() != "qualified_identifier"
5517 || parent.child_by_field_name("name") != Some(qualified)
5518 {
5519 break;
5520 }
5521 if let Some(outer_scope) = parent.child_by_field_name("scope") {
5522 parts.push(node_text(outer_scope, source).to_string());
5523 }
5524 current = Some(parent);
5525 }
5526 parts.reverse();
5527 parts.join("::")
5528}
5529
5530fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5531 if node.kind() == "using_declaration" {
5532 maybe_record_using_callable_hit(node, ctx);
5533 return;
5534 }
5535 if has_ancestor_kind(node, "using_declaration") {
5536 return;
5537 }
5538 if node.kind() == "function_definition" {
5539 return;
5540 }
5541 if !matches!(
5542 node.kind(),
5543 "call_expression"
5544 | "new_expression"
5545 | "compound_literal_expression"
5546 | "declaration"
5547 | "field_initializer"
5548 ) {
5549 return;
5550 }
5551 let Some(owner) = ctx.spec.owner.as_ref() else {
5552 return;
5553 };
5554 if node.kind() == "field_initializer" {
5555 if !field_initializer_constructs_target(node, ctx, owner) {
5556 return;
5557 }
5558 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5559 match ctx
5560 .visibility
5561 .call_arity_evidence(ctx.file, node, ctx.source)
5562 .accepts(expected)
5563 {
5564 Some(true) => {}
5565 Some(false) => return,
5566 None => {
5567 push_unproven_hit(node, ctx);
5568 return;
5569 }
5570 }
5571 }
5572 push_hit(node, ctx);
5573 return;
5574 }
5575 if node.kind() == "declaration" {
5576 if declaration_is_object_construction_candidate(node, ctx)
5577 && declaration_mentions_type(node, ctx, owner)
5578 && ctx
5579 .spec
5580 .callable_arity_at(node.start_byte())
5581 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
5582 {
5583 push_hit(node, ctx);
5584 }
5585 return;
5586 }
5587 let Some(type_node) = constructor_type_node(node) else {
5588 return;
5589 };
5590 let hit_node = function_terminal_node(type_node);
5591 let text = node_text(type_node, ctx.source);
5592 if !name_mentions(text, &ctx.spec.member_name) {
5593 return;
5594 }
5595 *ctx.raw_match_count += 1;
5596 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5597 match ctx
5598 .visibility
5599 .call_arity_evidence(ctx.file, node, ctx.source)
5600 .accepts(expected)
5601 {
5602 Some(true) => {}
5603 Some(false) => return,
5604 None => {
5605 push_unproven_hit(hit_node, ctx);
5606 return;
5607 }
5608 }
5609 }
5610 let structured_resolution = resolve_type_node_lexically_for_target(
5611 type_node,
5612 &ctx.analyzer,
5613 ctx.visibility,
5614 &ctx.ordinary_type_imports,
5615 ctx.file,
5616 ctx.source,
5617 owner,
5618 Some(&ctx.lexical_scope_cache),
5619 ctx.recovered_sentinel_scope(type_node).as_deref(),
5620 );
5621 let structurally_resolves = matches!(
5622 &structured_resolution,
5623 LexicalTypeResolution::Resolved {
5624 unit, candidates, ..
5625 } if same_visible_symbol(unit, owner)
5626 || candidates
5627 .iter()
5628 .any(|candidate| same_visible_symbol(candidate, owner))
5629 );
5630 if structurally_resolves
5631 || matches!(structured_resolution, LexicalTypeResolution::Missing)
5632 && ctx
5633 .visibility
5634 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
5635 {
5636 push_hit(hit_node, ctx);
5637 } else {
5638 push_unproven_hit(hit_node, ctx);
5639 }
5640}
5641
5642fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5643 if node.kind() == "function_definition" {
5644 maybe_record_free_function_definition_hit(node, ctx);
5645 return;
5646 }
5647 if node.kind() == "identifier" {
5648 maybe_record_free_function_value_reference(node, ctx);
5649 return;
5650 }
5651 if node.kind() != "call_expression" {
5652 return;
5653 }
5654 let Some(function) = node
5655 .child_by_field_name("function")
5656 .or_else(|| node.named_child(0))
5657 else {
5658 return;
5659 };
5660 let text = node_text(function, ctx.source);
5661 if !name_matches_callable(text, &ctx.spec.member_name) {
5662 return;
5663 }
5664 *ctx.raw_match_count += 1;
5665 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5666 match ctx
5667 .visibility
5668 .call_arity_evidence(ctx.file, node, ctx.source)
5669 .accepts(expected)
5670 {
5671 Some(true) => {}
5672 Some(false) => return,
5673 None => {
5674 if !bare_name_binds_only_target(node, function, text, ctx) {
5681 push_unproven_hit(function_terminal_node(function), ctx);
5682 return;
5683 }
5684 }
5685 }
5686 }
5687 if matches!(function.kind(), "identifier" | "template_function") {
5688 let terminal = function_terminal_node(function);
5689 let name = node_text(terminal, ctx.source);
5690 if ctx.local_shadows.is_shadowed(name) {
5691 return;
5692 }
5693 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
5694 && !matches!(
5695 resolve_declaring_member_owner(
5696 &ctx.analyzer,
5697 ctx.visibility,
5698 ctx.file,
5699 &enclosing_owner,
5700 name,
5701 ),
5702 EnclosingMemberOwnerResolution::Missing
5703 )
5704 {
5705 return;
5706 }
5707 match resolve_bare_call_target(
5708 node,
5709 function,
5710 &ctx.analyzer,
5711 ctx.visibility,
5712 &ctx.ordinary_type_imports,
5713 ctx.file,
5714 ctx.source,
5715 ) {
5716 BareCallTargetResolution::FreeFunctions(units)
5717 if units
5718 .iter()
5719 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5720 {
5721 if free_function_call_may_target(node, text, ctx) {
5722 let recursive = enclosing_context(terminal, ctx)
5723 .enclosing
5724 .as_ref()
5725 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
5726 if recursive {
5727 push_recursive_reference_hit(terminal, ctx);
5728 } else {
5729 push_hit(terminal, ctx);
5730 }
5731 }
5732 }
5733 BareCallTargetResolution::UnprovenFreeFunctions(units)
5734 if units
5735 .iter()
5736 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5737 {
5738 push_unproven_hit(terminal, ctx);
5739 }
5740 BareCallTargetResolution::FreeFunctions(_)
5741 | BareCallTargetResolution::UnprovenFreeFunctions(_)
5742 | BareCallTargetResolution::Type(_)
5743 | BareCallTargetResolution::CallableShadow => {}
5744 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
5745 push_unproven_hit(terminal, ctx);
5746 }
5747 }
5748 return;
5749 }
5750 if !free_function_call_may_target(node, text, ctx) {
5751 return;
5752 }
5753 if ctx.visibility.contains_named_symbol(
5754 ctx.file,
5755 text,
5756 TargetKind::FreeFunction,
5757 &ctx.spec.target,
5758 ) {
5759 push_hit(function_terminal_node(function), ctx);
5760 } else if ctx.visibility.resolve_known_non_target(
5761 ctx.file,
5762 text,
5763 TargetKind::FreeFunction,
5764 &ctx.spec.target,
5765 ) {
5766 } else {
5769 push_unproven_hit(function_terminal_node(function), ctx);
5770 }
5771}
5772
5773fn bare_name_binds_only_target(
5782 call: Node<'_>,
5783 function: Node<'_>,
5784 text: &str,
5785 ctx: &ScanCtx<'_>,
5786) -> bool {
5787 if !matches!(function.kind(), "identifier" | "template_function") {
5788 return false;
5789 }
5790 let mut candidates = ctx
5791 .visibility
5792 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5793 candidates.retain(|candidate| {
5794 ctx.visibility
5795 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5796 });
5797 dedupe_callable_candidates(&mut candidates, &ctx.analyzer, ctx.visibility);
5798 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
5799}
5800
5801fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
5802 if ctx.spec.param_types.is_none() {
5803 return true;
5804 }
5805 let mut candidates = ctx
5806 .visibility
5807 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5808 candidates.retain(|candidate| {
5809 ctx.visibility
5810 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5811 });
5812 let Some(arity) = ctx
5813 .visibility
5814 .call_arity_evidence(ctx.file, call, ctx.source)
5815 .exact()
5816 else {
5817 return true;
5818 };
5819 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5820 if candidates.is_empty()
5821 || !candidates
5822 .iter()
5823 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5824 {
5825 return true;
5826 }
5827 let arg_types = call_argument_types(call, ctx);
5828 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
5829 candidates,
5830 &arg_types,
5831 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
5832 &|name| ctx.visibility.resolve_type(ctx.file, name),
5833 &|left, right| same_visible_symbol(left, right),
5834 );
5835 filtered
5836 .iter()
5837 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5838}
5839
5840fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
5841 let Some(args) = call
5842 .child_by_field_name("arguments")
5843 .or_else(|| call.child_by_field_name("parameters"))
5844 .or_else(|| call.child_by_field_name("value"))
5845 else {
5846 return Vec::new();
5847 };
5848 argument_children(args)
5849 .map(|arg| expression_arg_type(arg, ctx))
5850 .collect()
5851}
5852
5853fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
5854 match node.kind() {
5855 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
5856 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
5857 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
5858 literal
5859 }),
5860 "identifier" => ctx
5861 .bindings
5862 .resolve_symbol(node_text(node, ctx.source))
5863 .as_precise()
5864 .and_then(|bindings| bindings.iter().find_map(CppScanBinding::as_arg_type)),
5865 "parenthesized_expression" => node
5866 .child_by_field_name("argument")
5867 .or_else(|| node.named_child(0))
5868 .and_then(|inner| expression_arg_type(inner, ctx)),
5869 "pointer_expression" => {
5870 let delta = match node.child_by_field_name("operator")?.kind() {
5871 "&" => 1,
5872 "*" => -1,
5873 _ => return None,
5874 };
5875 let inner = node
5876 .child_by_field_name("argument")
5877 .or_else(|| node.named_child(0))?;
5878 let mut arg_type = expression_arg_type(inner, ctx)?;
5879 arg_type.indirection += delta;
5880 Some(arg_type)
5881 }
5882 _ => None,
5883 }
5884}
5885
5886fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5891 let text = node_text(node, ctx.source);
5892 if !name_matches_callable(text, &ctx.spec.member_name) {
5893 return;
5894 }
5895 if is_declaration_name(node) || is_call_callee_node(node) {
5896 return;
5897 }
5898 *ctx.raw_match_count += 1;
5899 if ctx.visibility.contains_named_symbol(
5900 ctx.file,
5901 text,
5902 TargetKind::FreeFunction,
5903 &ctx.spec.target,
5904 ) {
5905 push_hit(node, ctx);
5906 } else if ctx.visibility.resolve_known_non_target(
5907 ctx.file,
5908 text,
5909 TargetKind::FreeFunction,
5910 &ctx.spec.target,
5911 ) {
5912 } else {
5914 push_unproven_hit(node, ctx);
5915 }
5916}
5917
5918fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5919 let Some(function) = function_definition_name_node(node) else {
5920 return;
5921 };
5922 let text = node_text(function, ctx.source);
5923 if !name_matches_callable(text, &ctx.spec.member_name) {
5924 return;
5925 }
5926 *ctx.raw_match_count += 1;
5927 if !function_definition_signature_matches_target(node, ctx) {
5928 return;
5929 }
5930 if definition_name_candidates(function, ctx)
5931 .iter()
5932 .any(|name| {
5933 ctx.visibility.contains_named_symbol(
5934 ctx.file,
5935 name,
5936 TargetKind::FreeFunction,
5937 &ctx.spec.target,
5938 )
5939 })
5940 {
5941 push_definition_hit(function, ctx);
5942 } else if definition_name_candidates(function, ctx)
5943 .iter()
5944 .any(|name| {
5945 ctx.visibility.resolve_known_non_target(
5946 ctx.file,
5947 name,
5948 TargetKind::FreeFunction,
5949 &ctx.spec.target,
5950 )
5951 })
5952 {
5953 } else {
5955 push_unproven_definition_hit(function, ctx);
5956 }
5957}
5958
5959fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5960 if node.kind() == "using_declaration" {
5961 maybe_record_using_callable_hit(node, ctx);
5962 return;
5963 }
5964 if has_ancestor_kind(node, "using_declaration") {
5965 return;
5966 }
5967 if node.kind() == "function_definition" {
5968 maybe_record_method_definition_hit(node, ctx);
5969 return;
5970 }
5971 if is_declaration_name(node) {
5972 return;
5973 }
5974 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
5975 maybe_record_qualified_method_value_hit(node, member, ctx);
5976 return;
5977 }
5978 if let Some(value) = qualified_callable_value(node) {
5979 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
5980 return;
5981 }
5982 if let Some(call) = recovered_relational_template_member_call(node) {
5983 maybe_record_recovered_relational_template_method_hit(call, ctx);
5984 return;
5985 }
5986 if node.kind() != "call_expression" {
5987 return;
5988 }
5989 if let Some((receiver, operator)) = explicit_operator_call(node) {
5990 let text = node_text(operator, ctx.source);
5991 if !name_matches_callable(text, &ctx.spec.member_name) {
5992 return;
5993 }
5994 *ctx.raw_match_count += 1;
5995 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5996 match ctx
5997 .visibility
5998 .call_arity_evidence(ctx.file, node, ctx.source)
5999 .accepts(expected)
6000 {
6001 Some(true) => {}
6002 Some(false) => return,
6003 None => {
6004 push_unproven_hit(operator, ctx);
6005 return;
6006 }
6007 }
6008 }
6009 match explicit_receiver_target_resolution(
6010 receiver,
6011 ctx.visibility
6012 .call_arity_evidence(ctx.file, node, ctx.source)
6013 .exact(),
6014 ctx,
6015 ) {
6016 MethodReceiverTargetResolution::Target
6017 if receiver_is_self_like(
6018 receiver,
6019 ctx.analyzer.reference_uses_c_semantics(ctx.file),
6020 ) =>
6021 {
6022 push_self_receiver_hit(operator, ctx);
6023 }
6024 MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
6025 MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
6026 MethodReceiverTargetResolution::NonTarget
6027 | MethodReceiverTargetResolution::Ambiguous => {}
6028 }
6029 return;
6030 }
6031 let Some(function) = node
6032 .child_by_field_name("function")
6033 .or_else(|| node.named_child(0))
6034 else {
6035 return;
6036 };
6037 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6038 return;
6039 }
6040 if function.kind() == "identifier"
6041 && ctx
6042 .local_shadows
6043 .is_shadowed(node_text(function, ctx.source))
6044 {
6045 return;
6046 }
6047 *ctx.raw_match_count += 1;
6048 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6049 match ctx
6050 .visibility
6051 .call_arity_evidence(ctx.file, node, ctx.source)
6052 .accepts(expected)
6053 {
6054 Some(true) => {}
6055 Some(false) => return,
6056 None => {
6057 push_unproven_hit(function_terminal_node(function), ctx);
6058 return;
6059 }
6060 }
6061 }
6062 if !method_call_may_target(node, ctx) {
6063 return;
6064 }
6065 if is_structurally_qualified(function) {
6066 match qualified_owner_resolution(function, ctx) {
6067 QualifiedOwnerResolution::Target => {
6068 push_hit(function_terminal_node(function), ctx);
6069 }
6070 QualifiedOwnerResolution::NonTarget => {}
6071 QualifiedOwnerResolution::Unresolved => {
6072 push_unproven_hit(function_terminal_node(function), ctx);
6073 }
6074 }
6075 return;
6076 }
6077 match call_function_target_resolution(function, ctx) {
6078 MethodReceiverTargetResolution::Target
6079 if call_function_has_direct_self_receiver(
6080 function,
6081 ctx.analyzer.reference_uses_c_semantics(ctx.file),
6082 ) =>
6083 {
6084 push_self_receiver_hit(function_terminal_node(function), ctx);
6085 }
6086 MethodReceiverTargetResolution::Target => {
6087 push_hit(function_terminal_node(function), ctx);
6088 }
6089 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
6090 MethodReceiverTargetResolution::Missing
6096 if inherited_target_owner_context(function, ctx) =>
6097 {
6098 push_hit(function_terminal_node(function), ctx);
6099 }
6100 MethodReceiverTargetResolution::Missing
6101 if same_owner_context(function, ctx)
6102 || out_of_line_target_owner_context(function, ctx) =>
6103 {
6104 push_self_receiver_hit(function_terminal_node(function), ctx);
6105 }
6106 MethodReceiverTargetResolution::Missing
6107 if function.kind() == "identifier"
6108 && resolves_to_lexical_free_function(function, ctx) =>
6109 {
6110 }
6113 MethodReceiverTargetResolution::Missing
6114 if !receiver_has_known_non_target(function, ctx)
6115 && !known_non_target_owner_context(function, ctx) =>
6116 {
6117 push_unproven_hit(function_terminal_node(function), ctx);
6118 }
6119 MethodReceiverTargetResolution::Missing => {}
6120 }
6121}
6122
6123fn maybe_record_recovered_relational_template_method_hit(
6124 call: RecoveredRelationalTemplateMemberCall<'_>,
6125 ctx: &mut ScanCtx<'_>,
6126) {
6127 if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
6128 return;
6129 }
6130 *ctx.raw_match_count += 1;
6131 if !ctx
6132 .visibility
6133 .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
6134 || ctx
6135 .spec
6136 .callable_arity_at(call.member.start_byte())
6137 .is_some_and(|arity| !arity.accepts(call.arity))
6138 {
6139 return;
6140 }
6141 match explicit_receiver_target_resolution(call.receiver, Some(call.arity), ctx) {
6142 MethodReceiverTargetResolution::Target
6143 if receiver_is_self_like(
6144 call.receiver,
6145 ctx.analyzer.reference_uses_c_semantics(ctx.file),
6146 ) =>
6147 {
6148 push_self_receiver_hit(call.member, ctx);
6149 }
6150 MethodReceiverTargetResolution::Target => push_hit(call.member, ctx),
6151 MethodReceiverTargetResolution::Missing => push_unproven_hit(call.member, ctx),
6152 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
6153 }
6154}
6155
6156fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
6157 if node.kind() != "qualified_identifier" {
6158 return None;
6159 }
6160 let parameter = node
6161 .parent()
6162 .filter(|parent| parent.kind() == "parameter_declaration")?;
6163 let parameter_declarator = parameter.child_by_field_name("declarator")?;
6164 if parameter.child_by_field_name("type") != Some(node)
6169 || parameter_declarator.kind() != "abstract_function_declarator"
6170 {
6171 return None;
6172 }
6173 let parameter_list = parameter
6174 .parent()
6175 .filter(|parent| parent.kind() == "parameter_list")?;
6176 if parameter_list.named_child_count() != 1 {
6177 return None;
6178 }
6179 let function_declarator = parameter_list
6180 .parent()
6181 .filter(|parent| parent.kind() == "function_declarator")?;
6182 if function_declarator
6183 .child_by_field_name("declarator")
6184 .is_none_or(|declarator| declarator.kind() != "identifier")
6185 || function_declarator
6186 .parent()
6187 .is_none_or(|parent| parent.kind() != "declaration")
6188 {
6189 return None;
6190 }
6191 node.child_by_field_name("name")
6192}
6193
6194fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6195 let Some(imported) = ordinary_using_declaration_type_node(node) else {
6196 return;
6197 };
6198 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
6199 return;
6200 }
6201 let Some(target_owner) = ctx.spec.owner.as_ref() else {
6202 return;
6203 };
6204 *ctx.raw_match_count += 1;
6205 let owner_resolution = qualified_owner_components(imported, ctx.source)
6206 .map(|qualified| {
6207 let lexical_scope = match enclosing_lexical_scope_components(
6208 imported,
6209 &ctx.analyzer,
6210 ctx.visibility,
6211 ctx.file,
6212 ctx.source,
6213 ) {
6214 LexicalScopeResolution::Resolved(scope) => scope,
6215 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
6216 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
6217 };
6218 ctx.visibility.resolve_type_components_lexically(
6219 &ctx.analyzer,
6220 ctx.file,
6221 &qualified.names,
6222 qualified.global,
6223 &lexical_scope,
6224 )
6225 })
6226 .unwrap_or(LexicalTypeResolution::Missing);
6227 let matches_target_owner = matches!(
6228 owner_resolution,
6229 LexicalTypeResolution::Resolved {
6230 ref unit,
6231 ref candidates,
6232 ..
6233 } if same_visible_symbol(unit, target_owner)
6234 || candidates
6235 .iter()
6236 .any(|candidate| same_visible_symbol(candidate, target_owner))
6237 );
6238 if !matches_target_owner {
6239 match owner_resolution {
6240 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
6241 push_unproven_hit(imported, ctx);
6242 }
6243 LexicalTypeResolution::Resolved { .. } => {}
6244 }
6245 return;
6246 }
6247 match ctx.visibility.visible_member_for_owner_name(
6248 ctx.file,
6249 target_owner,
6250 &ctx.spec.member_name,
6251 ) {
6252 VisibleMemberResolution::Callable(candidates)
6253 if candidates.iter().all(|candidate| {
6254 ctx.target_group.contains(candidate)
6255 || ctx
6256 .target_group
6257 .iter()
6258 .any(|target| same_visible_symbol(candidate, target))
6259 }) =>
6260 {
6261 push_hit(imported, ctx);
6262 }
6263 VisibleMemberResolution::NonCallable => {}
6264 VisibleMemberResolution::Callable(_)
6265 | VisibleMemberResolution::AmbiguousKind
6266 | VisibleMemberResolution::Missing => {
6267 push_unproven_hit(imported, ctx);
6268 }
6269 }
6270}
6271
6272fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6273 let name = node_text(node, ctx.source);
6274 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
6275 let key = (namespace.clone(), name.to_string());
6276 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
6277 return resolved;
6278 }
6279 let resolved = ctx
6280 .visibility
6281 .visible_identifier_candidates(ctx.file, name)
6282 .any(|unit| {
6283 unit.is_function()
6284 && type_owner_of(&ctx.analyzer, unit).is_none()
6285 && unit.package_name() == namespace
6286 });
6287 ctx.lexical_free_function_cache
6288 .borrow_mut()
6289 .insert(key, resolved);
6290 resolved
6291}
6292
6293fn maybe_record_qualified_method_value_hit(
6294 qualified: Node<'_>,
6295 member: Node<'_>,
6296 ctx: &mut ScanCtx<'_>,
6297) {
6298 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
6299 return;
6300 }
6301 *ctx.raw_match_count += 1;
6302 let resolution =
6303 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
6304 match resolution {
6305 LexicalCallableValueResolution::Type(resolved_owner) => {
6306 let Some(owner) = ctx.spec.owner.as_ref() else {
6307 push_unproven_hit(member, ctx);
6308 return;
6309 };
6310 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
6311 if same_visible_symbol(&resolved_owner, owner) {
6312 push_unproven_hit(member, ctx);
6313 }
6314 return;
6315 }
6316 match ctx.visibility.visible_member_for_owner_name(
6317 ctx.file,
6318 owner,
6319 &ctx.spec.member_name,
6320 ) {
6321 VisibleMemberResolution::Callable(candidates)
6322 if candidates.iter().all(|candidate| {
6323 ctx.target_group.contains(candidate)
6324 || ctx
6325 .target_group
6326 .iter()
6327 .any(|target| same_visible_symbol(candidate, target))
6328 }) =>
6329 {
6330 push_hit(member, ctx);
6333 }
6334 VisibleMemberResolution::NonCallable => {}
6335 VisibleMemberResolution::Callable(_)
6336 | VisibleMemberResolution::AmbiguousKind
6337 | VisibleMemberResolution::Missing => {
6338 push_unproven_hit(member, ctx);
6339 }
6340 }
6341 }
6342 LexicalCallableValueResolution::FreeFunction(_) => {}
6343 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
6344 push_unproven_hit(member, ctx);
6345 }
6346 }
6347}
6348
6349fn qualified_callable_value_resolution(
6350 qualified: Node<'_>,
6351 member_name: &str,
6352 ctx: &ScanCtx<'_>,
6353) -> LexicalCallableValueResolution {
6354 let Some((owner_components, global)) =
6355 qualified_callable_owner_components(qualified, ctx.source)
6356 else {
6357 return LexicalCallableValueResolution::Missing;
6358 };
6359 let lexical_scope = if global {
6360 Vec::new()
6361 } else {
6362 match enclosing_lexical_scope_components(
6363 qualified,
6364 &ctx.analyzer,
6365 ctx.visibility,
6366 ctx.file,
6367 ctx.source,
6368 ) {
6369 LexicalScopeResolution::Resolved(scope) => scope,
6370 LexicalScopeResolution::Ambiguous => {
6371 return LexicalCallableValueResolution::Ambiguous;
6372 }
6373 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
6374 }
6375 };
6376 if let Some(target_owner) = ctx.spec.owner.as_ref()
6377 && let LexicalTypeResolution::Resolved { unit, .. } =
6378 resolve_type_components_lexically_at_for_target_with_scope_cache(
6379 qualified,
6380 &owner_components,
6381 global,
6382 &ctx.analyzer,
6383 ctx.visibility,
6384 &ctx.ordinary_type_imports,
6385 ctx.file,
6386 ctx.source,
6387 target_owner,
6388 false,
6389 Some(&ctx.lexical_scope_cache),
6390 )
6391 {
6392 return LexicalCallableValueResolution::Type(unit);
6393 }
6394 ctx.visibility.resolve_callable_value_components_lexically(
6395 &ctx.analyzer,
6396 ctx.file,
6397 &owner_components,
6398 member_name,
6399 global,
6400 &lexical_scope,
6401 )
6402}
6403
6404fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6405 let Some(owner) = ctx.spec.owner.as_ref() else {
6406 return true;
6407 };
6408 if ctx.spec.param_types.is_none() {
6409 return true;
6410 }
6411 let mut candidates = ctx
6412 .visibility
6413 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6414 .into_iter()
6415 .filter(|unit| unit.is_function())
6416 .cloned()
6417 .collect::<Vec<_>>();
6418 let Some(arity) = ctx
6419 .visibility
6420 .call_arity_evidence(ctx.file, call, ctx.source)
6421 .exact()
6422 else {
6423 return true;
6424 };
6425 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
6426 if candidates.is_empty()
6427 || !candidates
6428 .iter()
6429 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6430 {
6431 return true;
6432 }
6433 let arg_types = call_argument_types(call, ctx);
6434 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6435 candidates,
6436 &arg_types,
6437 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6438 &|name| ctx.visibility.resolve_type(ctx.file, name),
6439 &|left, right| same_visible_symbol(left, right),
6440 );
6441 filtered
6442 .iter()
6443 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6444}
6445
6446fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6447 let Some(function) = function_definition_name_node(node) else {
6448 return;
6449 };
6450 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6451 return;
6452 }
6453 *ctx.raw_match_count += 1;
6454 if !function_definition_signature_matches_target(node, ctx) {
6455 return;
6456 }
6457 if node_inside_target_declaration(function, ctx) {
6458 return;
6459 }
6460 if is_structurally_qualified(function) {
6461 match qualified_owner_resolution(function, ctx) {
6462 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
6463 QualifiedOwnerResolution::NonTarget => {}
6464 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
6465 }
6466 return;
6467 }
6468 if definition_name_candidates(function, ctx)
6469 .iter()
6470 .any(|name| {
6471 name.contains("::")
6472 && ctx.visibility.contains_named_symbol(
6473 ctx.file,
6474 name,
6475 TargetKind::Method,
6476 &ctx.spec.target,
6477 )
6478 })
6479 {
6480 push_definition_hit(function, ctx);
6481 } else if definition_name_candidates(function, ctx)
6482 .iter()
6483 .any(|name| {
6484 ctx.visibility.resolve_known_non_target(
6485 ctx.file,
6486 name,
6487 TargetKind::Method,
6488 &ctx.spec.target,
6489 )
6490 })
6491 || known_non_target_owner_context(function, ctx)
6492 {
6493 } else {
6495 push_unproven_definition_hit(function, ctx);
6496 }
6497}
6498
6499fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6500 ctx.target_declaration_ranges
6501 .iter()
6502 .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
6503}
6504
6505fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
6506 let mut receiver = None;
6507 let mut cursor = node.walk();
6508 for child in node.named_children(&mut cursor) {
6509 if child.kind() == "argument_list" {
6510 continue;
6511 }
6512 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
6513 return receiver.map(|receiver| (receiver, operator));
6514 }
6515 if receiver.is_none() {
6516 receiver = Some(child);
6517 }
6518 }
6519 None
6520}
6521
6522fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
6523 if node.kind() != "function_definition" {
6524 return None;
6525 }
6526 node.child_by_field_name("declarator")
6527 .and_then(declarator_name_node)
6528}
6529
6530fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
6531 function_definition_name_node(node)
6532}
6533
6534fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6535 let definition = node_text(node, ctx.source);
6536 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
6537 return true;
6538 };
6539 if !expected.accepts(signature_arity(Some(definition))) {
6540 return false;
6541 }
6542 let Some(target_signature) = ctx.spec.target.signature() else {
6543 return true;
6544 };
6545 cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
6546}
6547
6548fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
6549 name_matches_callable(node_text(function_terminal_node(node), source), expected)
6550}
6551
6552fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
6553 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
6554 if raw.is_empty() {
6555 return Vec::new();
6556 }
6557 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
6558 return vec![raw];
6559 };
6560 if !raw.contains("::") {
6561 return vec![format!("{namespace}::{raw}")];
6562 }
6563 if raw
6569 .split("::")
6570 .next()
6571 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
6572 {
6573 vec![format!("{namespace}::{raw}"), raw]
6574 } else {
6575 vec![raw]
6576 }
6577}
6578
6579fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6580 if node.kind() == kind {
6581 return Some(node);
6582 }
6583 let mut cursor = node.walk();
6584 for child in node.named_children(&mut cursor) {
6585 if let Some(found) = first_descendant_of_kind(child, kind) {
6586 return Some(found);
6587 }
6588 }
6589 None
6590}
6591
6592fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6593 if matches!(node.kind(), "identifier" | "field_identifier")
6594 && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
6595 {
6596 return;
6597 }
6598 if !matches!(
6599 node.kind(),
6600 "identifier" | "field_identifier" | "qualified_identifier"
6601 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6602 || is_declaration_name(node)
6603 || is_member_field_own_declarator(node, ctx)
6604 || is_selected_field_expression_member_descendant(node)
6605 || is_nested_in_qualified_identifier(node)
6606 {
6607 return;
6608 }
6609 *ctx.raw_match_count += 1;
6610 if global_field_resolves_to_target(node, ctx) {
6611 push_hit(node, ctx);
6612 } else if global_field_is_known_non_target(node, ctx) {
6613 } else {
6614 push_unproven_hit(node, ctx);
6615 }
6616}
6617
6618fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
6624 let candidate = node;
6625 while let Some(parent) = node.parent() {
6626 if parent.kind() == "field_expression" {
6627 if let Some(field) = parent.child_by_field_name("field")
6628 && node_is_within(field, candidate)
6629 {
6630 let selected_name = match field.kind() {
6631 "template_method" => field.child_by_field_name("name").unwrap_or(field),
6632 _ => field,
6633 };
6634 if node_is_within(selected_name, candidate) {
6635 return true;
6636 }
6637 node = parent;
6641 continue;
6642 }
6643 let receiver = parent
6644 .child_by_field_name("argument")
6645 .or_else(|| parent.child_by_field_name("object"))
6646 .or_else(|| parent.named_child(0));
6647 if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
6648 return true;
6651 }
6652 }
6653 node = parent;
6654 }
6655 false
6656}
6657
6658fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
6659 parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
6660}
6661
6662fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6663 let text = node_text(node, ctx.source);
6664 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6665 return false;
6666 }
6667 if text.contains("::") {
6668 return ctx.visibility.contains_named_symbol(
6669 ctx.file,
6670 text,
6671 TargetKind::GlobalField,
6672 &ctx.spec.target,
6673 );
6674 }
6675 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
6676 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
6677 {
6678 return ctx.visibility.contains_named_symbol(
6679 ctx.file,
6680 text,
6681 TargetKind::GlobalField,
6682 &ctx.spec.target,
6683 );
6684 }
6685 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
6686 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
6687 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6688 brokk_bifrost_core::analyzer::Language::Cpp,
6689 &namespace,
6690 ) == indexed_scope
6691 })
6692 {
6693 return ctx.visibility.contains_named_symbol(
6694 ctx.file,
6695 text,
6696 TargetKind::GlobalField,
6697 &ctx.spec.target,
6698 );
6699 }
6700 bare_global_field_uniquely_resolves_to_target(text, ctx)
6701}
6702
6703fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
6704 let mut matched_target = false;
6705 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
6706 if !has_persisted_global_field_identity(unit)
6707 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
6708 {
6709 continue;
6710 }
6711 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
6712 continue;
6713 }
6714 if same_visible_global_field_symbol(
6715 &ctx.analyzer,
6716 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6717 unit,
6718 &ctx.spec.target,
6719 ) {
6720 matched_target = true;
6721 } else {
6722 return false;
6723 }
6724 }
6725 matched_target
6726}
6727
6728fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
6729 unit.is_field() && !unit.short_name().contains('.')
6734}
6735
6736fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6737 let text = node_text(node, ctx.source);
6738 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6739 return true;
6740 }
6741 if text.contains("::") {
6742 return ctx.visibility.resolve_known_non_target(
6743 ctx.file,
6744 text,
6745 TargetKind::GlobalField,
6746 &ctx.spec.target,
6747 );
6748 }
6749 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
6750 return false;
6751 };
6752 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
6753 && ctx
6754 .visibility
6755 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6756 .any(|unit| {
6757 has_persisted_global_field_identity(unit)
6758 && unit.identifier() == ctx.spec.member_name
6759 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
6760 && !same_visible_global_field_symbol(
6761 &ctx.analyzer,
6762 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6763 unit,
6764 &ctx.spec.target,
6765 )
6766 })
6767}
6768
6769fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6770 if node.kind() == "field_expression" {
6771 let Some(field) = node.child_by_field_name("field") else {
6772 return;
6773 };
6774 if node_text(field, ctx.source) != ctx.spec.member_name {
6775 return;
6776 }
6777 *ctx.raw_match_count += 1;
6778 let receiver = node
6779 .child_by_field_name("argument")
6780 .or_else(|| node.child_by_field_name("object"));
6781 match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx)) {
6782 Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
6783 Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
6784 Some(
6785 MethodReceiverTargetResolution::NonTarget
6786 | MethodReceiverTargetResolution::Ambiguous,
6787 ) => {}
6788 }
6789 return;
6790 }
6791
6792 if matches!(node.kind(), "identifier" | "field_identifier")
6793 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6794 && let Some(designator_owner) =
6795 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
6796 {
6797 *ctx.raw_match_count += 1;
6798 match designator_owner {
6799 DesignatedInitializerOwner::Resolved(owner)
6800 if ctx
6801 .spec
6802 .owner
6803 .as_ref()
6804 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
6805 {
6806 push_hit(node, ctx);
6807 }
6808 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
6809 DesignatedInitializerOwner::Resolved(_) => {}
6810 }
6811 return;
6812 }
6813
6814 let qualified_member_name_matches =
6815 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
6816 && cpp_name_component_nodes(node)
6817 .and_then(|components| components.last().copied())
6818 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
6819 if !matches!(
6820 node.kind(),
6821 "identifier" | "field_identifier" | "qualified_identifier" | "scoped_identifier"
6822 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6823 && !qualified_member_name_matches)
6824 || is_declaration_name(node)
6825 || is_member_field_own_declarator(node, ctx)
6826 || is_selected_field_expression_member_descendant(node)
6827 || is_nested_in_qualified_identifier(node)
6828 {
6829 return;
6830 }
6831 *ctx.raw_match_count += 1;
6832 if is_structurally_qualified(node) {
6833 match qualified_owner_resolution(node, ctx) {
6834 QualifiedOwnerResolution::Target => push_hit(node, ctx),
6835 QualifiedOwnerResolution::NonTarget => {}
6836 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
6837 }
6838 return;
6839 }
6840 let text = node_text(node, ctx.source);
6841 if ctx.local_shadows.is_shadowed(text) {
6842 return;
6843 }
6844 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
6845 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
6846 let owner_context = structured_owner_context_resolution(node, ctx);
6847 if matches!(
6848 owner_context,
6849 StructuredOwnerContextResolution::SelfTarget
6850 | StructuredOwnerContextResolution::InheritedTarget
6851 ) || unscoped_enum_match
6852 {
6853 push_hit(node, ctx);
6854 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
6855 .then_some(ctx.spec.owner.as_ref())
6856 .flatten()
6857 {
6858 let resolution =
6859 match resolve_active_using_enum_member(node, ctx) {
6860 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
6861 ActiveUsingEnumMemberResolution::Class(resolution) => {
6862 if direct_class_member_shadows(node, ctx) {
6863 return;
6864 }
6865 resolution
6866 }
6867 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
6868 if let Some(owner) = structured_enclosing_owner(node, ctx) {
6869 if direct_class_member_shadows(node, ctx) {
6870 return;
6871 }
6872 let complete_same_file_leaf =
6873 owner.source() == ctx.file
6874 && ctx.analyzer.type_hierarchy_provider().is_some_and(
6875 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
6876 );
6877 if !complete_same_file_leaf {
6878 push_unproven_hit(node, ctx);
6879 return;
6880 }
6881 }
6882 match owner_context {
6883 StructuredOwnerContextResolution::SelfTarget
6884 | StructuredOwnerContextResolution::InheritedTarget
6885 | StructuredOwnerContextResolution::NonTarget => return,
6886 StructuredOwnerContextResolution::Ambiguous => {
6887 push_unproven_hit(node, ctx);
6888 return;
6889 }
6890 StructuredOwnerContextResolution::Missing => {}
6891 }
6892 if namespace_value_shadows(node, ctx) {
6893 return;
6894 }
6895 resolution
6896 }
6897 ActiveUsingEnumMemberResolution::Missing => {
6898 if direct_class_member_shadows(node, ctx)
6899 || (structured_enclosing_owner(node, ctx).is_none()
6900 && namespace_value_shadows(node, ctx))
6901 {
6902 return;
6903 }
6904 UsingEnumMemberResolution::Missing
6905 }
6906 };
6907 match resolution {
6908 UsingEnumMemberResolution::Resolved { owner, member }
6909 if same_visible_symbol(&owner, target_owner)
6910 && same_visible_symbol(&member, &ctx.spec.target) =>
6911 {
6912 push_hit(node, ctx);
6913 }
6914 UsingEnumMemberResolution::Resolved { .. } => {}
6915 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
6916 push_unproven_hit(node, ctx)
6917 }
6918 }
6919 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
6920 push_unproven_hit(node, ctx);
6921 }
6922}
6923
6924enum ActiveUsingEnumMemberResolution {
6925 Block(UsingEnumMemberResolution),
6926 Class(UsingEnumMemberResolution),
6927 Namespace(UsingEnumMemberResolution),
6928 Missing,
6929}
6930
6931fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6932 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
6933 ctx.visibility
6934 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
6935 .into_iter()
6936 .next()
6937 .is_some()
6938 })
6939}
6940
6941fn resolve_active_using_enum_member(
6942 node: Node<'_>,
6943 ctx: &ScanCtx<'_>,
6944) -> ActiveUsingEnumMemberResolution {
6945 let block =
6946 ctx.using_enum_owners
6947 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
6948 if !matches!(block, UsingEnumMemberResolution::Missing) {
6949 return ActiveUsingEnumMemberResolution::Block(block);
6950 }
6951 let class = structured_enclosing_owner(node, ctx);
6952 let namespace = enclosing_namespace_components(node, ctx.source);
6953 match ctx.semantic_using_enum_owners.resolve_member(
6954 ctx.visibility,
6955 ctx.file,
6956 class.as_ref(),
6957 &namespace,
6958 node.start_byte(),
6959 &ctx.spec.member_name,
6960 ) {
6961 SemanticUsingEnumMemberResolution::Class(resolution) => {
6962 ActiveUsingEnumMemberResolution::Class(resolution)
6963 }
6964 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
6965 ActiveUsingEnumMemberResolution::Namespace(resolution)
6966 }
6967 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
6968 }
6969}
6970
6971fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6972 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
6973 !matches!(
6974 resolve_namespace_value(
6975 &ctx.analyzer,
6976 ctx.visibility,
6977 ctx.file,
6978 &namespace,
6979 &ctx.spec.member_name,
6980 node.start_byte(),
6981 ),
6982 NamespaceValueResolution::Missing
6983 )
6984}
6985
6986fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
6987 if node.kind() == "qualified_identifier" {
6988 return false;
6989 }
6990 let mut current = node.parent();
6991 while let Some(parent) = current {
6992 if parent.kind() == "ERROR" {
7000 return false;
7001 }
7002 if parent.kind() == "template_argument_list" {
7007 return false;
7008 }
7009 if parent.kind() == "qualified_identifier" {
7010 return true;
7011 }
7012 current = parent.parent();
7013 }
7014 false
7015}
7016
7017fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
7018 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
7019}
7020
7021fn receiver_type_units_with_budget(
7022 node: Node<'_>,
7023 source: &str,
7024 ctx: &ScanCtx<'_>,
7025 remaining_call_depth: usize,
7026) -> Vec<CodeUnit> {
7027 let mut current = node;
7028 let mut member_chain = Vec::new();
7029 let mut base_units = loop {
7030 match current.kind() {
7031 "field_expression" => {
7032 let Some(member) = current.child_by_field_name("field") else {
7033 return Vec::new();
7034 };
7035 let Some(receiver) = current
7036 .child_by_field_name("argument")
7037 .or_else(|| current.child_by_field_name("object"))
7038 .or_else(|| current.named_child(0))
7039 else {
7040 return Vec::new();
7041 };
7042 member_chain.push(node_text(member, source));
7043 current = receiver;
7044 }
7045 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
7046 let Some(inner) = current
7047 .child_by_field_name("argument")
7048 .or_else(|| current.named_child(0))
7049 else {
7050 return Vec::new();
7051 };
7052 current = inner;
7053 }
7054 "identifier" | "field_identifier" => {
7061 let name = node_text(current, source);
7062 let local = ctx.bindings.resolve_symbol(name);
7063 if let Some(bindings) = local.as_precise() {
7064 break receiver_units_from_bindings(current, bindings, ctx);
7065 }
7066 if ctx.bindings.is_shadowed(name) {
7067 return Vec::new();
7068 }
7069 let owner = structured_enclosing_owner(current, ctx)
7070 .filter(CodeUnit::is_class)
7071 .or_else(|| {
7072 enclosing_context(current, ctx)
7073 .owner
7074 .filter(CodeUnit::is_class)
7075 });
7076 if let Some(owner) = owner {
7077 let implicit_fields = ctx
7078 .visibility
7079 .visible_members_for_owner_name(ctx.file, &owner, name)
7080 .into_iter()
7081 .filter(|unit| unit.is_field())
7082 .collect::<Vec<_>>();
7083 if !implicit_fields.is_empty() {
7084 break receiver_units_from_declared_fields(implicit_fields, current, ctx);
7085 }
7086 }
7087 let global_fields = ctx
7088 .visibility
7089 .visible_identifier_candidates(ctx.file, name)
7090 .filter(|unit| {
7091 has_persisted_global_field_identity(unit) && unit.identifier() == name
7092 })
7093 .collect::<Vec<_>>();
7094 if global_fields.is_empty() {
7095 break ctx
7096 .visibility
7097 .resolve_type(ctx.file, name)
7098 .into_iter()
7099 .collect();
7100 }
7101 if let Some(first) = global_fields.first()
7102 && global_fields
7103 .iter()
7104 .skip(1)
7105 .any(|field| !same_visible_symbol(first, field))
7106 {
7107 return Vec::new();
7108 }
7109 break receiver_units_from_declared_fields(global_fields, current, ctx);
7110 }
7111 "call_expression" | "new_expression" => {
7112 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
7113 .and_then(|binding| binding.unit)
7114 .into_iter()
7115 .collect();
7116 }
7117 "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => {
7118 let name = node_text(current, source);
7119 let local = ctx.bindings.resolve_symbol(name);
7120 if let Some(bindings) = local.as_precise() {
7121 break receiver_units_from_bindings(current, bindings, ctx);
7122 }
7123 return Vec::new();
7124 }
7125 "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
7126 "qualified_identifier" | "scoped_identifier" => {
7127 let reference = node_text(current, source);
7128 let fields = ctx
7129 .visibility
7130 .named_candidates(ctx.file, reference, TargetKind::GlobalField)
7131 .into_iter()
7132 .filter(has_persisted_global_field_identity)
7133 .collect::<Vec<_>>();
7134 if fields.is_empty() {
7135 break ctx
7136 .visibility
7137 .resolve_type(ctx.file, reference)
7138 .into_iter()
7139 .collect();
7140 }
7141 break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
7142 }
7143 _ => {
7144 break ctx
7145 .visibility
7146 .resolve_type(ctx.file, node_text(current, source))
7147 .into_iter()
7148 .collect();
7149 }
7150 }
7151 };
7152
7153 base_units = canonical_receiver_units(base_units, ctx);
7154 if base_units.is_empty() {
7155 return Vec::new();
7156 }
7157
7158 while let Some(member_name) = member_chain.pop() {
7159 let mut next_units = Vec::new();
7160 for owner in &base_units {
7161 let declaring_owner = match resolve_declaring_member_owner(
7162 &ctx.analyzer,
7163 ctx.visibility,
7164 ctx.file,
7165 owner,
7166 member_name,
7167 ) {
7168 EnclosingMemberOwnerResolution::Owner(owner) => owner,
7169 EnclosingMemberOwnerResolution::Missing => continue,
7170 EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
7171 };
7172 let fields = ctx.visibility.visible_members_for_owner_name(
7173 ctx.file,
7174 &declaring_owner,
7175 member_name,
7176 );
7177 for field in fields.into_iter().filter(|unit| unit.is_field()) {
7178 let Some(unit) =
7179 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
7180 .and_then(|binding| binding.unit)
7181 .or_else(|| recovered_receiver_field_type(current, field, ctx))
7182 else {
7183 continue;
7184 };
7185 if !next_units
7186 .iter()
7187 .any(|existing| same_visible_symbol(existing, &unit))
7188 {
7189 next_units.push(unit);
7190 }
7191 }
7192 }
7193 if next_units.is_empty() {
7194 return Vec::new();
7195 }
7196 base_units = unanimous_receiver_units(next_units);
7197 if base_units.is_empty() {
7198 return Vec::new();
7199 }
7200 }
7201 base_units
7202}
7203
7204fn receiver_units_from_bindings(
7205 node: Node<'_>,
7206 bindings: &HashSet<CppScanBinding>,
7207 ctx: &ScanCtx<'_>,
7208) -> Vec<CodeUnit> {
7209 let mut units = Vec::new();
7210 for binding in bindings {
7211 let raw_unit = if let Some(unit) = &binding.unit {
7212 unit.clone()
7213 } else {
7214 let Some(type_name) = binding.type_name.as_deref() else {
7215 return Vec::new();
7216 };
7217 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
7218 return Vec::new();
7219 };
7220 unit
7221 };
7222 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
7223 if same_visible_symbol(&unit, &raw_unit)
7229 && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
7230 {
7231 units.push(recovered);
7232 continue;
7233 }
7234 units.push(unit);
7235 continue;
7236 }
7237 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
7238 units.push(unit);
7239 continue;
7240 }
7241 return Vec::new();
7242 }
7243 unanimous_receiver_units(units)
7244}
7245
7246fn recovered_receiver_alias_target(
7251 reference: Node<'_>,
7252 alias: &CodeUnit,
7253 ctx: &ScanCtx<'_>,
7254) -> Option<CodeUnit> {
7255 if !ctx
7256 .analyzer
7257 .type_alias_provider()
7258 .is_some_and(|provider| provider.is_type_alias(alias))
7259 {
7260 return None;
7261 }
7262 let target = ctx.spec.owner.as_ref()?.clone();
7263 if !target.is_class() || alias.source() != ctx.file {
7264 return None;
7265 }
7266 let range = ctx
7267 .analyzer
7268 .ranges(alias)
7269 .into_iter()
7270 .find(|range| range.start_byte < range.end_byte)?;
7271 let mut node =
7272 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
7273 while !matches!(node.kind(), "alias_declaration" | "type_definition") {
7274 node = node.parent()?;
7275 }
7276 let type_descriptor = node.child_by_field_name("type")?;
7277 let type_node = receiver_type_node_base(type_descriptor);
7278 let resolution = resolve_type_node_lexically_for_target(
7279 type_node,
7280 &ctx.analyzer,
7281 ctx.visibility,
7282 &ctx.ordinary_type_imports,
7283 ctx.file,
7284 ctx.source,
7285 &target,
7286 Some(&ctx.lexical_scope_cache),
7287 ctx.recovered_sentinel_scope(type_node).as_deref(),
7288 );
7289 if let LexicalTypeResolution::Resolved {
7290 unit, candidates, ..
7291 } = resolution
7292 && (same_visible_symbol(&unit, &target)
7293 || candidates
7294 .iter()
7295 .any(|candidate| same_visible_symbol(candidate, &target)))
7296 {
7297 return Some(target);
7298 }
7299 let (components, global) = type_reference_components(type_node, ctx.source)?;
7300 if !global
7301 && components.len() == 2
7302 && cpp_active_template_type_parameter(type_node, &components[0], ctx.source)
7303 {
7304 let alias_provider = ctx.analyzer.type_alias_provider()?;
7305 let concrete = ctx
7306 .visibility
7307 .visible_identifier_candidates(ctx.file, &components[1])
7308 .filter(|candidate| {
7309 alias_provider.is_type_alias(candidate)
7310 && !same_visible_symbol(candidate, alias)
7311 && type_owner_of(&ctx.analyzer, candidate).is_some_and(|owner| owner.is_class())
7312 && ctx.visibility.is_physically_visible(ctx.file, candidate)
7313 && ctx
7314 .visibility
7315 .external_type_candidate_guard_compatible_in_context(
7316 &ctx.analyzer,
7317 ctx.file,
7318 candidate,
7319 type_node,
7320 )
7321 })
7322 .filter_map(|candidate| {
7323 let canonical = ctx.visibility.canonical_visible_full_type_unit(
7324 &ctx.analyzer,
7325 ctx.file,
7326 candidate,
7327 )?;
7328 (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
7333 })
7334 .collect::<Vec<_>>();
7335 if let [unit] = unanimous_receiver_units(concrete).as_slice()
7336 && same_visible_symbol(unit, &target)
7337 {
7338 return Some(target);
7339 }
7340 }
7341 let scope = ctx
7342 .recovered_sentinel_scope(type_node)
7343 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
7344 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
7345 let visible = ctx.visibility.external_type_candidate_visible_in_context(
7346 &ctx.analyzer,
7347 ctx.file,
7348 &target,
7349 type_node,
7350 );
7351 (path_matches && visible).then_some(target)
7352}
7353
7354fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7355 let normalized = normalize_cpp_type_name(type_name);
7356 if normalized.is_empty() {
7357 return None;
7358 }
7359
7360 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
7365 let alias_type = receiver_type_node_base(alias_type);
7366 match ctx
7367 .visibility
7368 .resolve_type_node_result(ctx.file, alias_type, ctx.source)
7369 {
7370 Ok(Some(unit)) => return Some(unit),
7371 Err(_) => return None,
7372 Ok(None) => {}
7373 }
7374 if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
7375 return Some(unit);
7376 }
7377 }
7378
7379 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
7380 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
7381 LexicalTypeResolution::Ambiguous => return None,
7382 LexicalTypeResolution::Missing => {}
7383 }
7384 let candidates = ctx
7385 .visibility
7386 .type_name_candidates(ctx.file, &normalized)
7387 .into_iter()
7388 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
7389 .collect();
7390 unanimous_receiver_units(candidates).into_iter().next()
7391}
7392
7393fn resolve_receiver_type_node_lexically(
7394 type_node: Node<'_>,
7395 ctx: &ScanCtx<'_>,
7396) -> Option<CodeUnit> {
7397 let type_node = receiver_type_node_base(type_node);
7398 let components = cpp_type_name_components(type_node, ctx.source)?;
7399 let lexical_scope = match enclosing_lexical_scope_components(
7400 type_node,
7401 &ctx.analyzer,
7402 ctx.visibility,
7403 ctx.file,
7404 ctx.source,
7405 ) {
7406 LexicalScopeResolution::Resolved(scope) => scope,
7407 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
7408 };
7409 match ctx.visibility.resolve_type_components_lexically(
7410 &ctx.analyzer,
7411 ctx.file,
7412 &components,
7413 is_globally_qualified_cpp_name(type_node),
7414 &lexical_scope,
7415 ) {
7416 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
7417 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
7418 }
7419}
7420
7421fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
7422 while matches!(node.kind(), "type_descriptor" | "dependent_type") {
7423 let Some(inner) = node.child_by_field_name("type").or_else(|| {
7424 if node.kind() == "dependent_type" {
7425 node.named_child(0)
7426 } else {
7427 None
7428 }
7429 }) else {
7430 break;
7431 };
7432 node = inner;
7433 }
7434 node
7435}
7436
7437fn resolve_receiver_type_name_lexically(
7438 node: Node<'_>,
7439 normalized: &str,
7440 ctx: &ScanCtx<'_>,
7441) -> LexicalTypeResolution {
7442 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7443 brokk_bifrost_core::analyzer::Language::Cpp,
7444 normalized,
7445 );
7446 if components.is_empty() {
7447 return LexicalTypeResolution::Missing;
7448 }
7449 let lexical_scope = match enclosing_lexical_scope_components(
7450 node,
7451 &ctx.analyzer,
7452 ctx.visibility,
7453 ctx.file,
7454 ctx.source,
7455 ) {
7456 LexicalScopeResolution::Resolved(scope) => scope,
7457 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7458 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7459 };
7460 ctx.visibility.resolve_type_components_lexically(
7461 &ctx.analyzer,
7462 ctx.file,
7463 &components,
7464 normalized.starts_with("::"),
7465 &lexical_scope,
7466 )
7467}
7468
7469fn local_receiver_alias_type_node<'tree>(
7470 node: Node<'tree>,
7471 name: &str,
7472 ctx: &ScanCtx<'_>,
7473) -> Option<Node<'tree>> {
7474 let callable = nearest_callable_scope(node)?;
7475 let mut root_callable = callable;
7476 let mut ancestor = callable.parent();
7477 while let Some(current) = ancestor {
7478 if matches!(current.kind(), "function_definition" | "lambda_expression") {
7479 root_callable = current;
7480 }
7481 ancestor = current.parent();
7482 }
7483
7484 let mut stack = vec![root_callable];
7485 let mut best = None;
7486 while let Some(current) = stack.pop() {
7487 if current.start_byte() >= node.start_byte() {
7488 continue;
7489 }
7490 if local_type_alias_name_node(current)
7491 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
7492 && local_alias_scope_contains_node(current, node)
7493 {
7494 let replace = best
7495 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
7496 if replace {
7497 best = current.child_by_field_name("type");
7498 }
7499 }
7500 let mut cursor = current.walk();
7501 stack.extend(current.named_children(&mut cursor));
7502 }
7503 best
7504}
7505
7506fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
7507 let mut canonical = Vec::with_capacity(units.len());
7508 for unit in units {
7509 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
7510 return Vec::new();
7511 };
7512 canonical.push(unit);
7513 }
7514 unanimous_receiver_units(canonical)
7515}
7516
7517fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7518 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
7519 return cached.clone();
7520 }
7521 let canonical = ctx
7522 .visibility
7523 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
7524 ctx.receiver_canonical_type_cache
7525 .borrow_mut()
7526 .insert(unit.clone(), canonical.clone());
7527 canonical
7528}
7529
7530fn receiver_units_from_declared_fields(
7531 fields: Vec<&CodeUnit>,
7532 reference: Node<'_>,
7533 ctx: &ScanCtx<'_>,
7534) -> Vec<CodeUnit> {
7535 let Some(first) = fields.first() else {
7536 return Vec::new();
7537 };
7538 if fields
7539 .iter()
7540 .skip(1)
7541 .any(|field| !same_visible_symbol(first, field))
7542 {
7543 return Vec::new();
7544 }
7545 unanimous_receiver_units(
7546 fields
7547 .into_iter()
7548 .filter_map(|field| {
7549 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
7550 .and_then(|binding| binding.unit)
7551 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
7552 })
7553 .collect(),
7554 )
7555}
7556
7557fn recovered_receiver_field_type(
7562 reference: Node<'_>,
7563 field: &CodeUnit,
7564 ctx: &ScanCtx<'_>,
7565) -> Option<CodeUnit> {
7566 let target = ctx.spec.owner.as_ref()?.clone();
7567 if !target.is_class() || field.source() != ctx.file {
7568 return None;
7569 }
7570 let range = ctx
7571 .analyzer
7572 .ranges(field)
7573 .into_iter()
7574 .find(|range| range.start_byte < range.end_byte)?;
7575 let mut declaration =
7576 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
7577 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
7578 declaration = declaration.parent()?;
7579 }
7580 let type_node = first_type_child(declaration)?;
7581 let resolution = resolve_type_node_lexically_for_target(
7582 type_node,
7583 &ctx.analyzer,
7584 ctx.visibility,
7585 &ctx.ordinary_type_imports,
7586 ctx.file,
7587 ctx.source,
7588 &target,
7589 Some(&ctx.lexical_scope_cache),
7590 ctx.recovered_sentinel_scope(type_node).as_deref(),
7591 );
7592 if let LexicalTypeResolution::Resolved {
7593 unit, candidates, ..
7594 } = resolution
7595 && (same_visible_symbol(&unit, &target)
7596 || candidates
7597 .iter()
7598 .any(|candidate| same_visible_symbol(candidate, &target)))
7599 {
7600 return Some(target);
7601 }
7602 let type_node = receiver_type_node_base(type_node);
7603 let (components, global) = type_reference_components(type_node, ctx.source)?;
7604 let scope = ctx
7605 .recovered_sentinel_scope(type_node)
7606 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
7607 (indexed_scope_matches_target_name(&scope, &components, global, &target)
7608 && ctx.visibility.external_type_candidate_visible_in_context(
7609 &ctx.analyzer,
7610 ctx.file,
7611 &target,
7612 type_node,
7613 ))
7614 .then_some(target)
7615}
7616
7617fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
7618 let mut unique = Vec::new();
7619 for unit in units {
7620 if !unique
7621 .iter()
7622 .any(|existing| same_visible_symbol(existing, &unit))
7623 {
7624 unique.push(unit);
7625 if unique.len() > 1 {
7626 return Vec::new();
7627 }
7628 }
7629 }
7630 unique
7631}
7632
7633fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7634 let Some(owner) = ctx.spec.owner.as_ref() else {
7635 return false;
7636 };
7637 match node.kind() {
7638 "field_expression" => node
7639 .child_by_field_name("argument")
7640 .or_else(|| node.child_by_field_name("object"))
7641 .is_some_and(|receiver| {
7642 receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file))
7643 && same_owner_context(receiver, ctx)
7644 || receiver_type_units(receiver, ctx.source, ctx)
7645 .iter()
7646 .any(|target| {
7647 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7648 })
7649 }),
7650 "call_expression" => node
7651 .child_by_field_name("function")
7652 .is_some_and(|function| receiver_matches_target(function, ctx)),
7653 "pointer_expression" | "parenthesized_expression" => node
7654 .child_by_field_name("argument")
7655 .or_else(|| node.named_child(0))
7656 .is_some_and(|child| receiver_matches_target(child, ctx)),
7657 "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
7658 .bindings
7659 .resolve_symbol(node_text(node, ctx.source))
7660 .as_precise()
7661 .is_some_and(|targets| {
7662 targets
7663 .iter()
7664 .filter_map(|target| target.unit.as_ref())
7665 .any(|target| {
7666 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7667 })
7668 }),
7669 "this" => same_owner_context(node, ctx),
7670 _ => qualified_owner_matches(node, ctx),
7671 }
7672}
7673
7674fn declaring_owner_for_explicit_receiver(
7675 receiver: Node<'_>,
7676 call_arity: Option<usize>,
7677 ctx: &ScanCtx<'_>,
7678) -> EnclosingMemberOwnerResolution {
7679 if receiver_is_self_like(receiver, ctx.analyzer.reference_uses_c_semantics(ctx.file)) {
7680 return EnclosingMemberOwnerResolution::Missing;
7681 }
7682 let receiver_units = receiver_type_units(receiver, ctx.source, ctx);
7683 let mut declaring_owner = None;
7684 for receiver_owner in receiver_units {
7685 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
7686 receiver_owner_matches_target(&receiver_owner, target_owner, receiver.start_byte(), ctx)
7687 }) {
7688 if declaring_owner
7689 .as_ref()
7690 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
7691 {
7692 return EnclosingMemberOwnerResolution::Ambiguous;
7693 }
7694 declaring_owner = Some(receiver_owner);
7695 continue;
7696 }
7697 let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
7698 let owner_resolution = match call_arity {
7699 Some(arity) => resolve_declaring_callable_owner(
7700 &ctx.analyzer,
7701 ctx.visibility,
7702 ctx.file,
7703 ordinary,
7704 &ctx.spec.member_name,
7705 arity,
7706 ),
7707 None => ordinary,
7708 };
7709 match owner_resolution {
7710 EnclosingMemberOwnerResolution::Owner(owner) => {
7711 if declaring_owner
7712 .as_ref()
7713 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
7714 {
7715 return EnclosingMemberOwnerResolution::Ambiguous;
7716 }
7717 declaring_owner = Some(owner);
7718 }
7719 EnclosingMemberOwnerResolution::Ambiguous => {
7720 return EnclosingMemberOwnerResolution::Ambiguous;
7721 }
7722 EnclosingMemberOwnerResolution::Missing => {}
7723 }
7724 }
7725 declaring_owner
7726 .map(EnclosingMemberOwnerResolution::Owner)
7727 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
7728}
7729
7730fn declaring_owner_from_call_function(
7731 function: Node<'_>,
7732 call_arity: Option<usize>,
7733 ctx: &ScanCtx<'_>,
7734) -> Option<EnclosingMemberOwnerResolution> {
7735 match function.kind() {
7736 "field_expression" => function
7737 .child_by_field_name("argument")
7738 .or_else(|| function.child_by_field_name("object"))
7739 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
7740 .or(Some(EnclosingMemberOwnerResolution::Missing)),
7741 "call_expression" => function
7742 .child_by_field_name("function")
7743 .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
7744 _ => None,
7745 }
7746}
7747
7748enum MethodReceiverTargetResolution {
7749 Target,
7750 NonTarget,
7751 Ambiguous,
7752 Missing,
7753}
7754
7755fn method_receiver_target_resolution(
7756 node: Node<'_>,
7757 declaring_owner: EnclosingMemberOwnerResolution,
7758 ctx: &ScanCtx<'_>,
7759) -> MethodReceiverTargetResolution {
7760 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7761 return MethodReceiverTargetResolution::Missing;
7762 };
7763 match declaring_owner {
7764 EnclosingMemberOwnerResolution::Owner(owner)
7765 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
7766 {
7767 MethodReceiverTargetResolution::Target
7768 }
7769 EnclosingMemberOwnerResolution::Owner(owner)
7770 if receiver_owner_is_known_non_target(&owner, target_owner, node.start_byte(), ctx) =>
7771 {
7772 MethodReceiverTargetResolution::NonTarget
7773 }
7774 EnclosingMemberOwnerResolution::Owner(_) => MethodReceiverTargetResolution::Missing,
7775 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
7776 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
7777 MethodReceiverTargetResolution::Target
7778 }
7779 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
7780 MethodReceiverTargetResolution::NonTarget
7781 }
7782 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
7783 }
7784}
7785
7786fn explicit_receiver_target_resolution(
7787 receiver: Node<'_>,
7788 call_arity: Option<usize>,
7789 ctx: &ScanCtx<'_>,
7790) -> MethodReceiverTargetResolution {
7791 method_receiver_target_resolution(
7792 receiver,
7793 declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
7794 ctx,
7795 )
7796}
7797
7798fn call_function_target_resolution(
7799 function: Node<'_>,
7800 ctx: &ScanCtx<'_>,
7801) -> MethodReceiverTargetResolution {
7802 let call_arity = function.parent().and_then(|call| {
7803 (call.kind() == "call_expression")
7804 .then(|| {
7805 ctx.visibility
7806 .call_arity_evidence(ctx.file, call, ctx.source)
7807 .exact()
7808 })
7809 .flatten()
7810 });
7811 let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
7812 else {
7813 return MethodReceiverTargetResolution::Missing;
7817 };
7818 method_receiver_target_resolution(function, declaring_owner, ctx)
7819}
7820
7821fn receiver_owner_matches_target(
7822 receiver_owner: &CodeUnit,
7823 target_owner: &CodeUnit,
7824 reference_byte: usize,
7825 ctx: &ScanCtx<'_>,
7826) -> bool {
7827 same_symbol(receiver_owner, target_owner)
7828 || same_logical_symbol(receiver_owner, target_owner)
7829 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
7830 || (ctx.spec.owner_is_forward_declaration
7831 && ctx
7832 .visibility
7833 .is_physically_visible(ctx.file, receiver_owner))
7834 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
7835 || target_group_contains_owner_peer(receiver_owner, ctx))
7836}
7837
7838fn receiver_owner_is_known_non_target(
7839 receiver_owner: &CodeUnit,
7840 target_owner: &CodeUnit,
7841 reference_byte: usize,
7842 ctx: &ScanCtx<'_>,
7843) -> bool {
7844 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
7845 return false;
7846 }
7847 if !same_logical_symbol(receiver_owner, target_owner) {
7848 return true;
7849 }
7850 !ctx.target_group.iter().any(|target| {
7851 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
7852 })
7853}
7854
7855fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
7856 ctx.visibility
7857 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
7858 && ctx.target_group.iter().any(|target| {
7859 type_owner_of(&ctx.analyzer, target)
7860 .as_ref()
7861 .is_some_and(|target_owner| {
7862 same_symbol(target_owner, owner)
7863 || (same_logical_symbol(target_owner, owner)
7864 && target_owner.source() == owner.source())
7865 })
7866 })
7867}
7868
7869fn visible_target_peer_matches_owner(
7870 owner: &CodeUnit,
7871 reference_byte: usize,
7872 ctx: &ScanCtx<'_>,
7873) -> bool {
7874 ctx.visibility
7875 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
7876 && ctx
7877 .visibility
7878 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7879 .any(|candidate| {
7880 cpp_callable_definitions_share_identity_evidence(
7881 &ctx.analyzer,
7882 candidate,
7883 &ctx.spec.target,
7884 ) && ctx.visibility.declaration_visible_at(
7885 &ctx.analyzer,
7886 ctx.file,
7887 candidate,
7888 reference_byte,
7889 ) && type_owner_of(&ctx.analyzer, candidate)
7890 .as_ref()
7891 .is_some_and(|candidate_owner| {
7892 same_symbol(candidate_owner, owner)
7893 || (same_logical_symbol(candidate_owner, owner)
7894 && candidate_owner.source() == owner.source())
7895 })
7896 })
7897}
7898
7899fn receiver_is_self_like(node: Node<'_>, reference_is_c: bool) -> bool {
7906 match node.kind() {
7907 "this" => !reference_is_c,
7908 "pointer_expression" | "parenthesized_expression" => node
7909 .child_by_field_name("argument")
7910 .or_else(|| node.named_child(0))
7911 .is_some_and(|inner| receiver_is_self_like(inner, reference_is_c)),
7912 _ => false,
7913 }
7914}
7915
7916fn call_function_has_direct_self_receiver(function: Node<'_>, reference_is_c: bool) -> bool {
7917 match function.kind() {
7918 "field_expression" => function
7919 .child_by_field_name("argument")
7920 .or_else(|| function.child_by_field_name("object"))
7921 .is_some_and(|receiver| receiver_is_self_like(receiver, reference_is_c)),
7922 _ => receiver_is_self_like(function, reference_is_c),
7923 }
7924}
7925
7926fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7927 let Some(owner) = ctx.spec.owner.as_ref() else {
7928 return false;
7929 };
7930 match node.kind() {
7931 "field_expression" => node
7932 .child_by_field_name("argument")
7933 .or_else(|| node.child_by_field_name("object"))
7934 .is_some_and(|receiver| {
7935 let units = receiver_type_units(receiver, ctx.source, ctx);
7936 !units.is_empty()
7937 && units.iter().all(|target| {
7938 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7939 })
7940 }),
7941 "call_expression" => node
7942 .child_by_field_name("function")
7943 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
7944 "pointer_expression" | "parenthesized_expression" => node
7945 .child_by_field_name("argument")
7946 .or_else(|| node.named_child(0))
7947 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
7948 "identifier" | "this" if ctx.analyzer.reference_uses_c_semantics(ctx.file) => ctx
7949 .bindings
7950 .resolve_symbol(node_text(node, ctx.source))
7951 .as_precise()
7952 .is_some_and(|targets| {
7953 let units = targets
7954 .iter()
7955 .filter_map(|target| target.unit.as_ref())
7956 .collect::<Vec<_>>();
7957 !units.is_empty()
7958 && units.iter().all(|target| {
7959 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7960 })
7961 }),
7962 "this" => known_non_target_owner_context(node, ctx),
7963 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
7964 qualified_owner_is_known_non_target(node, ctx)
7965 }
7966 _ => false,
7967 }
7968}
7969
7970#[derive(Clone, Copy, PartialEq, Eq)]
7971enum QualifiedOwnerResolution {
7972 Target,
7973 NonTarget,
7974 Unresolved,
7975}
7976
7977#[derive(Clone)]
7978pub enum LexicalScopeResolution {
7979 Resolved(Vec<String>),
7980 Ambiguous,
7981 Missing,
7982}
7983
7984type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
7985
7986fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7987 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
7988}
7989
7990fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7991 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
7992}
7993
7994fn is_structurally_qualified(node: Node<'_>) -> bool {
7995 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7996 && qualified_name_has_concrete_scope_separators(node)
7997}
7998
7999fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
8000 let Some(target_owner) = ctx.spec.owner.as_ref() else {
8001 return QualifiedOwnerResolution::Unresolved;
8002 };
8003 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
8004 return QualifiedOwnerResolution::Unresolved;
8005 };
8006 if !global
8012 && !matches!(
8013 enclosing_lexical_scope_components(
8014 node,
8015 &ctx.analyzer,
8016 ctx.visibility,
8017 ctx.file,
8018 ctx.source,
8019 ),
8020 LexicalScopeResolution::Resolved(_)
8021 )
8022 {
8023 return QualifiedOwnerResolution::Unresolved;
8024 }
8025 match resolve_type_components_lexically_at_for_target_with_scope_cache(
8026 node,
8027 &components,
8028 global,
8029 &ctx.analyzer,
8030 ctx.visibility,
8031 &ctx.ordinary_type_imports,
8032 ctx.file,
8033 ctx.source,
8034 target_owner,
8035 false,
8036 Some(&ctx.lexical_scope_cache),
8037 ) {
8038 LexicalTypeResolution::Resolved { unit: owner, .. } => {
8039 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
8040 return QualifiedOwnerResolution::Target;
8041 }
8042 match cached_declaring_member_owner(&owner, ctx) {
8043 EnclosingMemberOwnerResolution::Owner(declaring_owner)
8044 if receiver_owner_matches_target(
8045 &declaring_owner,
8046 target_owner,
8047 node.start_byte(),
8048 ctx,
8049 ) =>
8050 {
8051 QualifiedOwnerResolution::Target
8052 }
8053 EnclosingMemberOwnerResolution::Owner(declaring_owner)
8054 if receiver_owner_is_known_non_target(
8055 &declaring_owner,
8056 target_owner,
8057 node.start_byte(),
8058 ctx,
8059 ) =>
8060 {
8061 QualifiedOwnerResolution::NonTarget
8062 }
8063 EnclosingMemberOwnerResolution::Owner(_)
8064 | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
8065 EnclosingMemberOwnerResolution::Missing
8066 if same_visible_symbol(&owner, target_owner) =>
8067 {
8068 QualifiedOwnerResolution::Unresolved
8069 }
8070 EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
8071 }
8072 }
8073 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
8074 QualifiedOwnerResolution::Unresolved
8075 }
8076 }
8077}
8078
8079fn qualified_callable_owner_components(
8080 node: Node<'_>,
8081 source: &str,
8082) -> Option<(Vec<String>, bool)> {
8083 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8084 || !qualified_name_has_concrete_scope_separators(node)
8085 {
8086 return None;
8087 }
8088 let global = is_globally_qualified_cpp_name(node);
8089 let mut components = Vec::new();
8090 append_cpp_name_components(node, source, &mut components)?;
8091 components.pop()?;
8092 (!components.is_empty()).then_some((components, global))
8093}
8094
8095fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
8096 if !matches!(
8097 node.kind(),
8098 "identifier"
8099 | "type_identifier"
8100 | "namespace_identifier"
8101 | "qualified_identifier"
8102 | "scoped_identifier"
8103 | "scoped_type_identifier"
8104 | "template_type"
8105 | "template_function"
8106 ) {
8107 return None;
8108 }
8109 let mut components = Vec::new();
8110 append_cpp_name_components(node, source, &mut components)?;
8111 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
8112}
8113
8114pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
8115 let mut namespaces = Vec::new();
8116 let mut current = node.parent();
8117 while let Some(parent) = current {
8118 if parent.kind() == "namespace_definition"
8119 && let Some(name) = parent.child_by_field_name("name")
8120 {
8121 let mut components = Vec::new();
8122 if append_cpp_name_components(name, source, &mut components).is_some() {
8123 namespaces.push(components);
8124 }
8125 }
8126 current = parent.parent();
8127 }
8128 namespaces.reverse();
8129 namespaces.into_iter().flatten().collect()
8130}
8131
8132pub fn enclosing_lexical_scope_components(
8133 node: Node<'_>,
8134 analyzer: &CppGraphSource<'_>,
8135 visibility: &VisibilityIndex<'_>,
8136 file: &ProjectFile,
8137 source: &str,
8138) -> LexicalScopeResolution {
8139 enclosing_lexical_scope_components_with_unresolved_owner(
8140 node, analyzer, visibility, file, source, false, false,
8141 )
8142}
8143
8144#[allow(clippy::too_many_arguments)]
8145fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
8146 node: Node<'_>,
8147 analyzer: &CppGraphSource<'_>,
8148 visibility: &VisibilityIndex<'_>,
8149 file: &ProjectFile,
8150 source: &str,
8151 allow_structured_unresolved_owner: bool,
8152 ignore_function_owner: bool,
8153 cache: Option<&LexicalScopeCache>,
8154) -> LexicalScopeResolution {
8155 let Some(cache) = cache else {
8156 return enclosing_lexical_scope_components_with_unresolved_owner(
8157 node,
8158 analyzer,
8159 visibility,
8160 file,
8161 source,
8162 allow_structured_unresolved_owner,
8163 ignore_function_owner,
8164 );
8165 };
8166 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
8167 let key = (
8168 anchor_start,
8169 anchor_end,
8170 allow_structured_unresolved_owner,
8171 ignore_function_owner,
8172 );
8173 if let Some(cached) = cache.borrow().get(&key).cloned() {
8174 return cached;
8175 }
8176 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
8177 node,
8178 analyzer,
8179 visibility,
8180 file,
8181 source,
8182 allow_structured_unresolved_owner,
8183 ignore_function_owner,
8184 );
8185 cache.borrow_mut().insert(key, resolved.clone());
8186 resolved
8187}
8188
8189fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
8190 let mut current = node;
8191 loop {
8192 if matches!(
8193 current.kind(),
8194 "function_definition"
8195 | "class_specifier"
8196 | "struct_specifier"
8197 | "union_specifier"
8198 | "namespace_definition"
8199 | "translation_unit"
8200 ) {
8201 return (current.start_byte(), current.end_byte());
8202 }
8203 let Some(parent) = current.parent() else {
8204 return (current.start_byte(), current.end_byte());
8205 };
8206 current = parent;
8207 }
8208}
8209
8210fn enclosing_lexical_scope_components_with_unresolved_owner(
8211 node: Node<'_>,
8212 analyzer: &CppGraphSource<'_>,
8213 visibility: &VisibilityIndex<'_>,
8214 file: &ProjectFile,
8215 source: &str,
8216 allow_structured_unresolved_owner: bool,
8217 ignore_function_owner: bool,
8218) -> LexicalScopeResolution {
8219 #[cfg(any(test, feature = "test-support"))]
8220 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
8221 let mut namespaces = Vec::new();
8227 let mut classes = Vec::new();
8228 let mut function_definition = None;
8229 let mut displaced_class_scope = false;
8230 let mut current = node.parent();
8231 while let Some(parent) = current {
8232 match parent.kind() {
8233 "namespace_definition" => {
8234 if let Some(name) = parent.child_by_field_name("name") {
8235 let mut components = Vec::new();
8236 if append_cpp_name_components(name, source, &mut components).is_some() {
8237 namespaces.push(components);
8238 }
8239 }
8240 }
8241 "class_specifier" | "struct_specifier" | "union_specifier" => {
8242 if let Some(name) = parent.child_by_field_name("name") {
8243 let mut components = Vec::new();
8244 if append_cpp_name_components(name, source, &mut components).is_some() {
8245 classes.push(components);
8246 }
8247 }
8248 }
8249 "function_definition" => {
8250 if function_definition.is_none() {
8251 function_definition = Some(parent);
8252 }
8253 displaced_class_scope = displaced_class_scope
8254 || parent.child_by_field_name("type").is_some_and(|type_node| {
8255 matches!(
8256 type_node.kind(),
8257 "class_specifier" | "struct_specifier" | "union_specifier"
8258 )
8259 })
8260 || is_malformed_wrapper_function_definition(parent);
8261 }
8262 _ => {}
8263 }
8264 current = parent.parent();
8265 }
8266 namespaces.reverse();
8267 let namespace: Vec<String> = namespaces.into_iter().flatten().collect();
8268 let mut scope = namespace.clone();
8269 let has_qualified_function_owner = function_definition
8274 .filter(|function| !is_malformed_wrapper_function_definition(*function))
8275 .and_then(function_definition_owner_lookup_node)
8276 .is_some_and(|owner| {
8277 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
8278 });
8279 let indexed_scope = displaced_class_scope
8280 .then(|| {
8281 indexed_structural_class_scope(visibility, file, node, source)
8282 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
8283 })
8284 .flatten()
8285 .or_else(|| {
8286 (has_qualified_function_owner && function_definition.is_some())
8293 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
8294 .flatten()
8295 .filter(|indexed| {
8296 qualified_owner_scope_is_recoverable(
8297 indexed,
8298 &namespace,
8299 &classes,
8300 function_definition
8301 .and_then(function_definition_owner_lookup_node)
8302 .and_then(|owner| qualified_callable_owner_components(owner, source))
8303 .map(|(components, _)| components),
8304 )
8305 })
8306 })
8307 .or_else(|| {
8308 indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
8316 indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
8317 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
8318 })
8319 })
8320 })
8321 .or_else(|| {
8322 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
8326 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
8327 .flatten()
8328 .filter(|indexed| indexed.len() > namespace.len())
8329 });
8330 if let Some(indexed_scope) = indexed_scope.as_ref() {
8331 scope = indexed_scope.clone();
8336 classes.clear();
8337 }
8338
8339 if !ignore_function_owner
8340 && has_qualified_function_owner
8341 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
8342 {
8343 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
8344 return LexicalScopeResolution::Missing;
8345 };
8346 let imports = visibility.ordinary_type_import_cell(file);
8352 let owner_resolution = resolve_type_components_lexically_at_scoped(
8353 function,
8354 &owner,
8355 global,
8356 analyzer,
8357 visibility,
8358 &imports,
8359 file,
8360 source,
8361 None,
8362 false,
8363 false,
8364 namespace.clone(),
8365 );
8366 match owner_resolution {
8367 LexicalTypeResolution::Resolved {
8368 unit, components, ..
8369 } if is_indexed_class_owner(analyzer, &unit) => {
8370 scope = components;
8371 classes.clear();
8372 }
8373 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
8374 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
8375 match visibility
8376 .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
8377 {
8378 LexicalTypeResolution::Resolved { components, .. } => scope = components,
8379 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
8380 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
8381 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
8382 qualified_owner_scope_is_recoverable(
8383 indexed,
8384 &namespace,
8385 &classes,
8386 Some(owner.clone()),
8387 )
8388 }) {
8389 scope = indexed.clone();
8390 } else {
8391 scope = if global || owner.starts_with(&namespace) {
8392 owner
8393 } else {
8394 let mut relative = namespace;
8395 relative.extend(owner);
8396 relative
8397 };
8398 }
8399 }
8400 LexicalTypeResolution::Missing => {
8401 match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
8411 Some(indexed) => scope = indexed,
8412 None => return LexicalScopeResolution::Missing,
8413 }
8414 }
8415 }
8416 }
8417 }
8418 }
8419
8420 classes.reverse();
8421 scope.extend(classes.into_iter().flatten());
8422 LexicalScopeResolution::Resolved(scope)
8423}
8424
8425fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
8426 let mut current = node.parent();
8427 while let Some(parent) = current {
8428 if parent.kind() == "function_definition"
8429 && parent.child_by_field_name("type").is_some_and(|type_node| {
8430 matches!(
8431 type_node.kind(),
8432 "class_specifier" | "struct_specifier" | "union_specifier"
8433 )
8434 })
8435 {
8436 return true;
8437 }
8438 current = parent.parent();
8439 }
8440 false
8441}
8442
8443fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
8444 let mut current = node.parent();
8445 while let Some(parent) = current {
8446 if parent.kind() == "function_definition"
8447 && is_malformed_wrapper_function_definition(parent)
8448 {
8449 return true;
8450 }
8451 current = parent.parent();
8452 }
8453 false
8454}
8455
8456fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
8457 node.has_error()
8458 && node
8459 .child_by_field_name("declarator")
8460 .is_some_and(|declarator| {
8461 declarator.kind() != "function_declarator"
8462 && first_descendant_of_kind(declarator, "function_declarator").is_none()
8463 })
8464}
8465
8466fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
8472 node.child_by_field_name("scope")
8473 .and_then(|scope| recovered_macro_decorated_type_node(scope))
8474 .is_some()
8475}
8476
8477fn indexed_structural_class_scope(
8478 visibility: &VisibilityIndex<'_>,
8479 file: &ProjectFile,
8480 node: Node<'_>,
8481 source: &str,
8482) -> Option<Vec<String>> {
8483 let mut current = node.parent();
8484 while let Some(parent) = current {
8485 if matches!(
8486 parent.kind(),
8487 "class_specifier" | "struct_specifier" | "union_specifier"
8488 ) {
8489 return visibility.indexed_structural_class_scope(file, parent, source);
8490 }
8491 current = parent.parent();
8492 }
8493 None
8494}
8495
8496fn qualified_owner_scope_is_recoverable(
8507 indexed: &[String],
8508 namespace: &[String],
8509 classes: &[Vec<String>],
8510 qualified_owner: Option<Vec<String>>,
8511) -> bool {
8512 if let Some(owner) = qualified_owner {
8513 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
8514 return false;
8515 }
8516 if namespace.is_empty() {
8522 return true;
8523 }
8524 if indexed.len() <= namespace.len() {
8525 return false;
8526 }
8527 let mut prefix = indexed.iter();
8528 return namespace
8529 .iter()
8530 .all(|component| prefix.any(|candidate| candidate == component));
8531 }
8532 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
8533 if !class_components.is_empty() {
8534 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
8535 }
8536 if namespace.is_empty() || indexed.len() <= namespace.len() {
8537 return false;
8538 }
8539 let mut prefix = indexed.iter();
8540 namespace
8541 .iter()
8542 .all(|component| prefix.any(|candidate| candidate == component))
8543}
8544
8545fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
8548 unit.is_class()
8549 && !analyzer
8550 .type_alias_provider()
8551 .is_some_and(|provider| provider.is_type_alias(unit))
8552}
8553
8554fn indexed_enclosing_owner_scope(
8568 analyzer: &CppGraphSource<'_>,
8569 visibility: &VisibilityIndex<'_>,
8570 file: &ProjectFile,
8571 node: Node<'_>,
8572) -> Option<Vec<String>> {
8573 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
8574}
8575
8576fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8577 let start = enclosing_context(node, ctx).enclosing?;
8578 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
8579 ctx.analyzer.parent_of(unit)
8580 })
8581 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
8582}
8583
8584pub fn resolve_type_node_lexically(
8585 node: Node<'_>,
8586 analyzer: &CppGraphSource<'_>,
8587 visibility: &VisibilityIndex<'_>,
8588 ordinary_type_imports: &OrdinaryTypeImportCell,
8589 file: &ProjectFile,
8590 source: &str,
8591) -> LexicalTypeResolution {
8592 let Some((components, global)) = type_reference_components(node, source) else {
8593 return LexicalTypeResolution::Missing;
8594 };
8595 let resolution = resolve_type_components_lexically_at(
8596 node,
8597 &components,
8598 global,
8599 analyzer,
8600 visibility,
8601 ordinary_type_imports,
8602 file,
8603 source,
8604 );
8605 if !is_cpp_template_argument_type_leaf(node) {
8606 return resolution;
8607 }
8608
8609 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8616 return resolution;
8617 };
8618 let namespace_scope = enclosing_namespace_components(node, source);
8619 if indexed_scope.len() <= namespace_scope.len() {
8620 return resolution;
8621 }
8622 let indexed = visibility.resolve_type_components_lexically(
8623 analyzer,
8624 file,
8625 &components,
8626 global,
8627 &indexed_scope,
8628 );
8629 match indexed {
8630 LexicalTypeResolution::Resolved { ref unit, .. }
8631 if !visibility
8632 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8633 {
8634 resolution
8635 }
8636 LexicalTypeResolution::Resolved { .. } => indexed,
8637 _ => resolution,
8638 }
8639}
8640
8641#[allow(clippy::too_many_arguments)]
8642pub fn resolve_type_node_lexically_for_target(
8643 node: Node<'_>,
8644 analyzer: &CppGraphSource<'_>,
8645 visibility: &VisibilityIndex<'_>,
8646 ordinary_type_imports: &OrdinaryTypeImportCell,
8647 file: &ProjectFile,
8648 source: &str,
8649 target: &CodeUnit,
8650 scope_cache: Option<&LexicalScopeCache>,
8651 recovered_scope: Option<&[String]>,
8652) -> LexicalTypeResolution {
8653 let Some((reference_components, global)) = type_reference_components(node, source) else {
8654 return LexicalTypeResolution::Missing;
8655 };
8656 let terminal = reference_components
8657 .last()
8658 .expect("type reference components are non-empty");
8659 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
8660 return LexicalTypeResolution::Missing;
8661 }
8662 let template_arguments = cpp_template_reference_arguments(node, source);
8663 let selects_concrete_specialization =
8664 template_arguments.is_some() && visibility.is_template_specialization(target);
8665 if !selects_concrete_specialization
8666 && !visibility.structured_type_reference_may_resolve_to_target(
8667 analyzer,
8668 file,
8669 std::slice::from_ref(terminal),
8670 false,
8671 &[],
8672 target,
8673 )
8674 {
8675 return LexicalTypeResolution::Missing;
8676 }
8677 if let Some(arguments) = template_arguments.as_ref() {
8678 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
8679 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
8680 node,
8681 &reference_components,
8682 global,
8683 analyzer,
8684 visibility,
8685 ordinary_type_imports,
8686 file,
8687 source,
8688 recovered_scope,
8689 )
8690 } else {
8691 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
8692 node,
8693 &reference_components,
8694 global,
8695 analyzer,
8696 visibility,
8697 ordinary_type_imports,
8698 file,
8699 source,
8700 scope_cache,
8701 )
8702 };
8703 return match alias_resolution {
8704 LexicalTypeResolution::Resolved {
8705 unit,
8706 components,
8707 candidates,
8708 } if visibility.template_alias_arguments_preserve_target(
8709 analyzer, file, &unit, arguments, target,
8710 ) =>
8711 {
8712 LexicalTypeResolution::Resolved {
8713 unit: target.clone(),
8714 components,
8715 candidates,
8716 }
8717 }
8718 LexicalTypeResolution::Resolved {
8719 unit,
8720 components,
8721 candidates,
8722 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
8723 Ok(resolved_unit) => {
8724 let target_guided = (!same_visible_symbol(&resolved_unit, target))
8725 .then(|| {
8726 target_guided_malformed_template_alias_resolution(
8727 node,
8728 analyzer,
8729 visibility,
8730 file,
8731 arguments,
8732 &reference_components,
8733 target,
8734 )
8735 })
8736 .flatten();
8737 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
8738 unit: resolved_unit,
8739 components,
8740 candidates,
8741 })
8742 }
8743 Err(_) => LexicalTypeResolution::Ambiguous,
8744 },
8745 LexicalTypeResolution::Missing => {
8746 let target_preserving = if let Some(recovered_scope) = recovered_scope {
8747 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8748 node,
8749 &reference_components,
8750 global,
8751 analyzer,
8752 visibility,
8753 ordinary_type_imports,
8754 file,
8755 source,
8756 target,
8757 true,
8758 recovered_scope,
8759 )
8760 } else {
8761 resolve_type_components_lexically_at_for_target_with_scope_cache(
8762 node,
8763 &reference_components,
8764 global,
8765 analyzer,
8766 visibility,
8767 ordinary_type_imports,
8768 file,
8769 source,
8770 target,
8771 true,
8772 scope_cache,
8773 )
8774 };
8775 match target_preserving {
8776 LexicalTypeResolution::Resolved {
8777 unit: _,
8778 components,
8779 candidates,
8780 } if template_reference_candidates_select_target(
8781 node,
8782 &candidates,
8783 analyzer,
8784 visibility,
8785 file,
8786 source,
8787 target,
8788 ) =>
8789 {
8790 LexicalTypeResolution::Resolved {
8791 unit: target.clone(),
8792 components,
8793 candidates,
8794 }
8795 }
8796 _ => target_guided_malformed_template_alias_resolution(
8797 node,
8798 analyzer,
8799 visibility,
8800 file,
8801 arguments,
8802 &reference_components,
8803 target,
8804 )
8805 .unwrap_or(LexicalTypeResolution::Missing),
8806 }
8807 }
8808 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
8809 };
8810 }
8811 let resolution = if let Some(recovered_scope) = recovered_scope {
8812 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8813 node,
8814 &reference_components,
8815 global,
8816 analyzer,
8817 visibility,
8818 ordinary_type_imports,
8819 file,
8820 source,
8821 target,
8822 true,
8823 recovered_scope,
8824 )
8825 } else {
8826 resolve_type_components_lexically_at_for_target_with_scope_cache(
8827 node,
8828 &reference_components,
8829 global,
8830 analyzer,
8831 visibility,
8832 ordinary_type_imports,
8833 file,
8834 source,
8835 target,
8836 true,
8837 scope_cache,
8838 )
8839 };
8840 let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
8841 target_guided_qualified_namespace_function_type_resolution(
8842 node,
8843 &reference_components,
8844 global,
8845 analyzer,
8846 visibility,
8847 ordinary_type_imports,
8848 file,
8849 source,
8850 target,
8851 )
8852 .unwrap_or(resolution)
8853 } else {
8854 resolution
8855 };
8856 if !is_cpp_template_argument_type_leaf(node) {
8857 return resolution;
8858 }
8859
8860 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8868 return resolution;
8869 };
8870 let namespace_scope = enclosing_namespace_components(node, source);
8871 if indexed_scope.len() <= namespace_scope.len() {
8872 return resolution;
8873 }
8874 let indexed = visibility.resolve_type_components_lexically_for_target(
8875 analyzer,
8876 file,
8877 &reference_components,
8878 global,
8879 &indexed_scope,
8880 target,
8881 );
8882 match indexed {
8883 LexicalTypeResolution::Resolved {
8884 ref unit,
8885 ref candidates,
8886 ..
8887 } if (same_visible_symbol(unit, target)
8888 || candidates
8889 .iter()
8890 .any(|candidate| same_visible_symbol(candidate, target)))
8891 && visibility
8892 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8893 {
8894 indexed
8895 }
8896 _ => resolution,
8897 }
8898}
8899
8900#[allow(clippy::too_many_arguments)]
8910fn target_guided_qualified_namespace_function_type_resolution(
8911 node: Node<'_>,
8912 components: &[String],
8913 global: bool,
8914 analyzer: &CppGraphSource<'_>,
8915 visibility: &VisibilityIndex<'_>,
8916 ordinary_type_imports: &OrdinaryTypeImportCell,
8917 file: &ProjectFile,
8918 source: &str,
8919 target: &CodeUnit,
8920) -> Option<LexicalTypeResolution> {
8921 let function_definition = std::iter::successors(Some(node), |current| current.parent())
8922 .find(|current| current.kind() == "function_definition")?;
8923 let function = function_definition_name_node(function_definition)?;
8924 let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
8925 let target_namespace = target.package_name();
8926 if target_namespace.is_empty() {
8927 return None;
8928 }
8929 let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8930 brokk_bifrost_core::analyzer::Language::Cpp,
8931 target_namespace,
8932 );
8933 if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
8934 {
8935 return None;
8936 }
8937
8938 let function_name = node_text(function_terminal_node(function), source);
8939 let definition_arity = signature_arity(Some(node_text(function_definition, source)));
8940 let declaration_proves_namespace = visibility
8941 .visible_identifier_candidates(file, function_name)
8942 .filter(|candidate| {
8943 candidate.is_function()
8944 && type_owner_of(analyzer, candidate).is_none()
8945 && candidate.package_name() == target_namespace
8946 })
8947 .any(|candidate| cpp_callable_arity(analyzer, candidate).accepts(definition_arity));
8948 if !declaration_proves_namespace {
8949 return None;
8950 }
8951
8952 let resolution = resolve_type_components_lexically_at_scoped(
8953 node,
8954 components,
8955 global,
8956 analyzer,
8957 visibility,
8958 ordinary_type_imports,
8959 file,
8960 source,
8961 Some(target),
8962 true,
8963 false,
8964 target_scope,
8965 );
8966 match &resolution {
8967 LexicalTypeResolution::Resolved {
8968 unit, candidates, ..
8969 } if (same_visible_symbol(unit, target)
8970 || candidates
8971 .iter()
8972 .any(|candidate| same_visible_symbol(candidate, target)))
8973 && visibility
8974 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8975 {
8976 Some(resolution)
8977 }
8978 LexicalTypeResolution::Resolved { .. }
8979 | LexicalTypeResolution::Ambiguous
8980 | LexicalTypeResolution::Missing => None,
8981 }
8982}
8983
8984#[allow(clippy::too_many_arguments)]
8985fn target_guided_malformed_template_alias_resolution(
8986 node: Node<'_>,
8987 analyzer: &CppGraphSource<'_>,
8988 visibility: &VisibilityIndex<'_>,
8989 file: &ProjectFile,
8990 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
8991 components: &[String],
8992 target: &CodeUnit,
8993) -> Option<LexicalTypeResolution> {
8994 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
8995 return None;
8996 }
8997
8998 let identifier = &components[0];
8999 let namespace =
9000 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
9001 let namespace_name = namespace.join("::");
9002 let candidates = visibility
9003 .visible_identifier_candidates(file, identifier)
9004 .filter(|candidate| {
9005 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
9006 && visibility.type_candidate_may_be_visible_before_reference(
9007 analyzer,
9008 file,
9009 candidate,
9010 node.start_byte(),
9011 )
9012 })
9013 .cloned()
9014 .collect::<Vec<_>>();
9015 let first = candidates.first()?;
9016 if !candidates
9017 .iter()
9018 .all(|candidate| same_logical_symbol(first, candidate))
9019 || !candidates.iter().all(|candidate| {
9020 visibility.template_alias_arguments_preserve_target(
9021 analyzer, file, candidate, arguments, target,
9022 )
9023 })
9024 {
9025 return None;
9026 }
9027
9028 let mut resolved_components = namespace;
9029 resolved_components.push(identifier.clone());
9030 Some(LexicalTypeResolution::Resolved {
9031 unit: target.clone(),
9032 components: resolved_components,
9033 candidates,
9034 })
9035}
9036
9037fn resolve_type_node_lexically_for_target_without_visibility(
9038 node: Node<'_>,
9039 analyzer: &CppGraphSource<'_>,
9040 visibility: &VisibilityIndex<'_>,
9041 file: &ProjectFile,
9042 source: &str,
9043 target: &CodeUnit,
9044) -> LexicalTypeResolution {
9045 let Some((components, global)) = type_reference_components(node, source) else {
9046 return LexicalTypeResolution::Missing;
9047 };
9048 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
9049 node,
9050 analyzer,
9051 visibility,
9052 file,
9053 source,
9054 true,
9055 recovered_macro_decorated_declarator_type(node)
9056 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9057 ) {
9058 LexicalScopeResolution::Resolved(scope) => scope,
9059 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9060 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9061 };
9062 visibility.resolve_type_components_lexically_for_target(
9063 analyzer,
9064 file,
9065 &components,
9066 global,
9067 &lexical_scope,
9068 target,
9069 )
9070}
9071
9072fn type_node_has_exact_target_identity_without_visibility(
9073 node: Node<'_>,
9074 analyzer: &CppGraphSource<'_>,
9075 visibility: &VisibilityIndex<'_>,
9076 file: &ProjectFile,
9077 source: &str,
9078 target: &CodeUnit,
9079) -> bool {
9080 let Some((components, global)) = type_reference_components(node, source) else {
9081 return false;
9082 };
9083 let LexicalScopeResolution::Resolved(lexical_scope) =
9084 enclosing_lexical_scope_components_with_unresolved_owner(
9085 node,
9086 analyzer,
9087 visibility,
9088 file,
9089 source,
9090 true,
9091 recovered_macro_decorated_declarator_type(node)
9092 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9093 )
9094 else {
9095 return false;
9096 };
9097 let target_name = cpp_name_for(target);
9098 lexical_component_tiers(&components, global, &lexical_scope)
9099 .any(|qualified| qualified.join("::") == target_name)
9100}
9101
9102pub fn resolve_using_enum_declaration_owner(
9103 node: Node<'_>,
9104 analyzer: &CppGraphSource<'_>,
9105 visibility: &VisibilityIndex<'_>,
9106 ordinary_type_imports: &OrdinaryTypeImportCell,
9107 file: &ProjectFile,
9108 source: &str,
9109) -> LexicalTypeResolution {
9110 let Some(type_node) = using_enum_declaration_type_node(node) else {
9111 return LexicalTypeResolution::Missing;
9112 };
9113 let mut components = Vec::new();
9114 if append_cpp_name_components(type_node, source, &mut components).is_none()
9115 || components.is_empty()
9116 {
9117 return LexicalTypeResolution::Missing;
9118 }
9119 resolve_type_components_lexically_at(
9120 type_node,
9121 &components,
9122 is_globally_qualified_cpp_name(type_node),
9123 analyzer,
9124 visibility,
9125 ordinary_type_imports,
9126 file,
9127 source,
9128 )
9129}
9130
9131pub fn resolve_ordinary_using_declaration_owner(
9132 node: Node<'_>,
9133 analyzer: &CppGraphSource<'_>,
9134 visibility: &VisibilityIndex<'_>,
9135 file: &ProjectFile,
9136 source: &str,
9137) -> LexicalTypeResolution {
9138 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
9139 return LexicalTypeResolution::Missing;
9140 };
9141 let mut components = Vec::new();
9142 if append_cpp_name_components(type_node, source, &mut components).is_none()
9143 || components.len() < 2
9144 {
9145 return LexicalTypeResolution::Missing;
9146 }
9147 let lexical_scope =
9148 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
9149 LexicalScopeResolution::Resolved(scope) => scope,
9150 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9151 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9152 };
9153 visibility.resolve_type_components_lexically(
9154 analyzer,
9155 file,
9156 &components,
9157 is_globally_qualified_cpp_name(type_node),
9158 &lexical_scope,
9159 )
9160}
9161
9162pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
9163 (node.kind() == "using_declaration"
9164 && (0..node.child_count()).any(|index| {
9165 node.child(index)
9166 .is_some_and(|child| child.kind() == "enum")
9167 }))
9168 .then(|| node.named_child(0))
9169 .flatten()
9170}
9171
9172pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
9173 (node.kind() == "using_declaration"
9174 && using_enum_declaration_type_node(node).is_none()
9175 && using_namespace_directive_name_node(node).is_none())
9176 .then(|| node.named_child(0))
9177 .flatten()
9178}
9179
9180fn recovered_macro_using_declaration_type_node<'tree>(
9187 node: Node<'tree>,
9188 source: &str,
9189) -> Option<(Node<'tree>, bool)> {
9190 if node.kind() != "declaration" {
9191 return None;
9192 }
9193 let macro_type = node.child_by_field_name("type")?;
9194 if macro_type.kind() != "type_identifier"
9195 || !cpp_export_macro_token(node_text(macro_type, source))
9196 {
9197 return None;
9198 }
9199 let declarator = node.child_by_field_name("declarator")?;
9200 if declarator.kind() != "qualified_identifier" {
9201 return None;
9202 }
9203 let scope = declarator.child_by_field_name("scope")?;
9204 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
9205 return None;
9206 }
9207 let target = declarator.child_by_field_name("name")?;
9208 let mut components = Vec::new();
9209 append_cpp_name_components(target, source, &mut components)?;
9210 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
9211}
9212
9213fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
9214 let is_directive = node.kind() == "using_directive"
9215 || (node.kind() == "using_declaration"
9216 && (0..node.child_count()).any(|index| {
9217 node.child(index)
9218 .is_some_and(|child| child.kind() == "namespace")
9219 }));
9220 if !is_directive {
9221 return None;
9222 }
9223 node.child_by_field_name("name")
9224 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
9225}
9226
9227fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
9228 let mut current = node.parent();
9229 while let Some(parent) = current {
9230 if matches!(
9231 parent.kind(),
9232 "compound_statement"
9233 | "function_definition"
9234 | "lambda_expression"
9235 | "for_statement"
9236 | "while_statement"
9237 | "if_statement"
9238 | "class_specifier"
9239 | "struct_specifier"
9240 | "union_specifier"
9241 ) {
9242 return None;
9243 }
9244 current = parent.parent();
9245 }
9246 Some(enclosing_namespace_components(node, source))
9247}
9248
9249fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
9250 let mut current = node.parent();
9251 while let Some(scope) = current {
9252 if matches!(
9253 scope.kind(),
9254 "compound_statement"
9255 | "declaration_list"
9256 | "field_declaration_list"
9257 | "translation_unit"
9258 ) {
9259 let mut depth = 0;
9260 let mut ancestor = scope.parent();
9261 while let Some(parent) = ancestor {
9262 depth += 1;
9263 ancestor = parent.parent();
9264 }
9265 return Some((
9266 scope.start_byte(),
9267 scope.end_byte(),
9268 depth,
9269 scope.kind() == "compound_statement",
9270 ));
9271 }
9272 current = scope.parent();
9273 }
9274 None
9275}
9276
9277pub fn build_source_using_index(cpp: &dyn CppSource, file: &ProjectFile) -> SourceUsingIndex {
9284 let Some(prepared) = cpp.prepared_syntax(file) else {
9285 return SourceUsingIndex::default();
9286 };
9287 collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
9288}
9289
9290fn collect_source_using_index(
9291 cpp: &dyn CppSource,
9292 source_file: &ProjectFile,
9293 root: Node<'_>,
9294 source: &str,
9295) -> SourceUsingIndex {
9296 #[cfg(not(any(test, feature = "test-support")))]
9297 let _ = cpp;
9298 let mut index = SourceUsingIndex::default();
9299 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
9300 let mut stack = vec![root];
9301 while let Some(node) = stack.pop() {
9302 let target = match node.kind() {
9303 "using_directive" | "using_declaration" => {
9304 if let Some(namespace_node) = using_namespace_directive_name_node(node) {
9305 let mut namespace_components = Vec::new();
9306 append_cpp_name_components(namespace_node, source, &mut namespace_components)
9307 .map(|_| EffectiveUsingTarget::Namespace {
9308 namespace_components,
9309 global: is_globally_qualified_cpp_name(namespace_node),
9310 })
9311 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
9312 let mut target_components = Vec::new();
9313 (append_cpp_name_components(type_node, source, &mut target_components)
9314 .is_some()
9315 && target_components.len() >= 2)
9316 .then(|| EffectiveUsingTarget::Ordinary {
9317 name: target_components
9318 .last()
9319 .expect("ordinary using has a terminal component")
9320 .clone(),
9321 target_components,
9322 global: is_globally_qualified_cpp_name(type_node),
9323 })
9324 } else {
9325 None
9326 }
9327 }
9328 "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
9329 |(type_node, global)| {
9330 let mut target_components = Vec::new();
9331 (append_cpp_name_components(type_node, source, &mut target_components)
9332 .is_some()
9333 && target_components.len() >= 2)
9334 .then(|| EffectiveUsingTarget::Ordinary {
9335 name: target_components
9336 .last()
9337 .expect("recovered ordinary using has a terminal component")
9338 .clone(),
9339 target_components,
9340 global,
9341 })
9342 },
9343 ),
9344 _ => None,
9345 };
9346 if let Some(target) = target {
9347 #[cfg(any(test, feature = "test-support"))]
9353 cpp.record_using_guard_context_inspection_for_test();
9354 let required_guards = if callable_preprocessor_context_is_visible(node, source) {
9355 Some(HashSet::default())
9356 } else {
9357 preprocessor_guard_environment(node, source)
9358 };
9359 let Some(required_guards) = required_guards else {
9360 let mut cursor = node.walk();
9361 stack.extend(node.children(&mut cursor));
9362 continue;
9363 };
9364 if let Some((scope_start, scope_end, scope_depth, block_scope)) =
9365 ordinary_using_scope(node)
9366 {
9367 let declaration_namespace = enclosing_namespace_components(node, source);
9368 let declaration_namespace = if declaration_namespace.is_empty() {
9369 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
9370 .unwrap_or(declaration_namespace)
9371 } else {
9372 declaration_namespace
9373 };
9374 let namespace_scope = using_named_scope(node, source);
9375 let lexical_depth = declaration_namespace.len();
9376 let binding = OrdinaryTypeImport {
9377 target,
9378 source: source_file.clone(),
9379 declaration_byte: node.end_byte(),
9380 scope_start,
9381 scope_end,
9382 scope_depth,
9383 block_scope,
9384 lexical_depth,
9385 declaration_namespace,
9386 namespace_scope,
9387 resolved_target_components: None,
9388 required_guards,
9389 };
9390 match &binding.target {
9391 EffectiveUsingTarget::Ordinary { name, .. } => index
9392 .ordinary_by_name
9393 .entry(name.clone())
9394 .or_default()
9395 .push(binding),
9396 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
9397 }
9398 }
9399 }
9400 let mut cursor = node.walk();
9401 stack.extend(node.children(&mut cursor));
9402 }
9403 index
9404}
9405
9406struct OrphanedNamespaceEnvelope {
9407 body_end: usize,
9408 components: Vec<String>,
9409 class_names: HashSet<String>,
9410}
9411
9412fn collect_orphaned_namespace_envelopes(
9419 root: Node<'_>,
9420 source: &str,
9421) -> Vec<OrphanedNamespaceEnvelope> {
9422 let mut envelopes = Vec::new();
9423 let mut stack = vec![root];
9424 while let Some(current) = stack.pop() {
9425 if current.kind() == "namespace_definition"
9426 && let Some(body) = current.child_by_field_name("body")
9427 && current.end_byte() == body.end_byte()
9428 && let Some(name) = current.child_by_field_name("name")
9429 {
9430 let mut components = enclosing_namespace_components(current, source);
9431 if append_cpp_name_components(name, source, &mut components).is_some()
9432 && !components.is_empty()
9433 {
9434 let mut class_names = HashSet::default();
9435 let mut body_stack = vec![body];
9436 while let Some(node) = body_stack.pop() {
9437 if let Some(name) = orphaned_class_definition_name(node, source) {
9438 class_names.insert(name);
9439 }
9440 let mut cursor = node.walk();
9441 if node.kind() == "ERROR" {
9442 body_stack.extend(node.children(&mut cursor));
9443 } else {
9444 body_stack.extend(node.named_children(&mut cursor));
9445 }
9446 }
9447 envelopes.push(OrphanedNamespaceEnvelope {
9448 body_end: body.end_byte(),
9449 components,
9450 class_names,
9451 });
9452 }
9453 }
9454 let mut cursor = current.walk();
9455 stack.extend(current.named_children(&mut cursor));
9456 }
9457 envelopes
9458}
9459
9460fn collect_orphaned_namespace_type_envelopes(
9465 root: Node<'_>,
9466 source: &str,
9467) -> Vec<OrphanedNamespaceEnvelope> {
9468 let mut envelopes = Vec::new();
9469 let mut stack = vec![root];
9470 while let Some(current) = stack.pop() {
9471 if current.kind() == "namespace_definition"
9472 && current.has_error()
9473 && let Some(body) = current.child_by_field_name("body")
9474 && current.end_byte() == body.end_byte()
9475 && let Some(name) = current.child_by_field_name("name")
9476 {
9477 let mut components = enclosing_namespace_components(current, source);
9478 if append_cpp_name_components(name, source, &mut components).is_some() {
9479 envelopes.push(OrphanedNamespaceEnvelope {
9480 body_end: body.end_byte(),
9481 components,
9482 class_names: HashSet::default(),
9483 });
9484 }
9485 }
9486 if !current.has_error() {
9487 continue;
9488 }
9489 let mut cursor = current.walk();
9490 stack.extend(
9491 current
9492 .named_children(&mut cursor)
9493 .filter(|child| child.has_error()),
9494 );
9495 }
9496 envelopes
9497}
9498
9499fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
9500 if matches!(
9501 node.kind(),
9502 "class_specifier" | "struct_specifier" | "union_specifier"
9503 ) {
9504 let body = node.child_by_field_name("body")?;
9505 let name = node.child_by_field_name("name")?;
9506 return (!name.is_missing() && !body.is_missing())
9507 .then(|| node_text(name, source).to_string());
9508 }
9509 if node.kind() != "ERROR" {
9510 return None;
9511 }
9512
9513 for index in 0..node.child_count() {
9519 let Some(keyword) = node.child(index) else {
9520 continue;
9521 };
9522 if !matches!(keyword.kind(), "class" | "struct" | "union") {
9523 continue;
9524 }
9525 let mut name = None;
9526 for next_index in (index + 1)..node.child_count() {
9527 let Some(next) = node.child(next_index) else {
9528 continue;
9529 };
9530 if next.kind() == ";" {
9531 break;
9532 }
9533 if next.kind() == "{" {
9534 return name
9535 .filter(|name_node: &Node<'_>| !name_node.is_missing())
9536 .map(|name_node| node_text(name_node, source).to_string());
9537 }
9538 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
9539 name = Some(next);
9540 }
9541 }
9542 }
9543 None
9544}
9545
9546fn recovered_orphaned_namespace_components(
9547 node: Node<'_>,
9548 source: &str,
9549 envelopes: &[OrphanedNamespaceEnvelope],
9550) -> Option<Vec<String>> {
9551 let owner_name = orphaned_using_owner_name(node, source)?;
9552 envelopes
9553 .iter()
9554 .filter(|envelope| {
9555 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
9556 })
9557 .max_by_key(|envelope| envelope.body_end)
9558 .map(|envelope| envelope.components.clone())
9559}
9560
9561fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
9562 let function = std::iter::successors(node.parent(), |current| current.parent())
9563 .find(|current| current.kind() == "function_definition")?;
9564 let owner = function_definition_owner_lookup_node(function)?;
9565 let scope = owner.child_by_field_name("scope")?;
9566 let mut components = Vec::new();
9567 append_cpp_name_components(scope, source, &mut components)?;
9568 if components.len() != 1 {
9575 return None;
9576 }
9577 components.pop()
9578}
9579
9580fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
9581 let mut project = ProjectUsingIndex::default();
9582 for source_file in visibility.all_visible_source_files() {
9583 let source_index = visibility.cpp().source_using_index(&source_file);
9587 for (name, bindings) in &source_index.ordinary_by_name {
9588 project
9589 .ordinary_by_name
9590 .entry(name.clone())
9591 .or_default()
9592 .extend(bindings.iter().cloned());
9593 }
9594 project
9595 .directives
9596 .extend(source_index.directives.iter().cloned());
9597 }
9598 project
9599}
9600
9601fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
9602 visibility.project_using_index(|| build_project_using_index(visibility))
9603}
9604
9605pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
9611 let _ = project_using_index(visibility);
9612}
9613
9614fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
9615 let (components, global) = match &binding.target {
9616 EffectiveUsingTarget::Ordinary {
9617 target_components,
9618 global,
9619 ..
9620 } => (target_components, *global),
9621 EffectiveUsingTarget::Namespace {
9622 namespace_components,
9623 global,
9624 } => (namespace_components, *global),
9625 };
9626 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
9627}
9628
9629fn using_binding_target_components_for_name(
9630 binding: &OrdinaryTypeImport,
9631 project: &ProjectUsingIndex,
9632 visibility: &VisibilityIndex<'_>,
9633 file: &ProjectFile,
9634 name: &str,
9635) -> Option<Vec<String>> {
9636 let cpp_source = CppGraphSource::from_source(visibility.cpp());
9639 let visible_candidates = visibility
9640 .visible_identifier_candidates(file, name)
9641 .filter(|candidate| {
9642 candidate.is_class()
9643 || is_type_alias(candidate)
9644 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
9645 })
9646 .collect::<Vec<_>>();
9647 if visible_candidates.is_empty() {
9648 return None;
9649 }
9650 match &binding.target {
9651 EffectiveUsingTarget::Ordinary {
9652 name: imported_name,
9653 ..
9654 } if imported_name == name => {
9655 effective_using_target_tiers(binding)
9656 .into_iter()
9657 .find(|qualified| {
9658 let qualified_name = qualified.join("::");
9659 visible_candidates
9660 .iter()
9661 .any(|candidate| cpp_name_for(candidate) == qualified_name)
9662 })
9663 }
9664 EffectiveUsingTarget::Namespace { .. } => {
9665 visibility.note_using_namespace_lookup_for_test();
9666 let target_tiers = effective_using_target_tiers(binding);
9667 let resolved = target_tiers
9668 .iter()
9669 .find(|namespace_components| {
9670 let namespace = namespace_components.join("::");
9671 visible_candidates.iter().any(|candidate| {
9672 visibility.note_using_name_candidate_inspection_for_test();
9673 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
9674 candidate_namespace == namespace
9675 || candidate_namespace.starts_with(&format!("{namespace}::"))
9676 })
9677 }) || project.directives.iter().any(|candidate| {
9678 candidate.namespace_scope.as_deref()
9679 == Some(namespace_components.as_slice())
9680 }) || project
9681 .ordinary_by_name
9682 .values()
9683 .flatten()
9684 .any(|candidate| {
9685 candidate.namespace_scope.as_deref()
9686 == Some(namespace_components.as_slice())
9687 })
9688 })
9689 .cloned();
9690 resolved.or_else(|| {
9691 (target_tiers.len() == 1)
9697 .then(|| target_tiers.into_iter().next())
9698 .flatten()
9699 })
9700 }
9701 EffectiveUsingTarget::Ordinary { .. } => None,
9702 }
9703}
9704
9705fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
9706 let start = activation.checked_sub(1)?;
9707 let mut node = root.descendant_for_byte_range(start, activation)?;
9708 while node.kind() != "preproc_include" {
9709 node = node.parent()?;
9710 }
9711 Some(node)
9712}
9713
9714fn project_using_bindings(
9715 binding: OrdinaryTypeImport,
9716 visibility: &VisibilityIndex<'_>,
9717 file: &ProjectFile,
9718 root: Node<'_>,
9719 source: &str,
9720) -> Vec<OrdinaryTypeImport> {
9721 if binding.source == *file {
9722 return vec![binding];
9723 }
9724 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
9725 return Vec::new();
9726 }
9727 visibility.note_using_donor_activation_for_test();
9728 let Some(prepared) = visibility.cpp().prepared_syntax(file) else {
9729 return Vec::new();
9730 };
9731 let projections = visibility
9732 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
9733 .map_or_else(
9734 || {
9735 visibility.conditional_include_projections_for_source(
9736 file,
9737 prepared.as_ref(),
9738 &binding.source,
9739 )
9740 },
9741 |activation_byte| {
9742 Arc::from([ConditionalIncludeProjection {
9743 activation_byte,
9744 required_guards: HashSet::default(),
9745 }])
9746 },
9747 );
9748 projections
9749 .iter()
9750 .cloned()
9751 .filter_map(|projection| {
9752 let required_guards =
9753 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
9754 let mut projected = binding.clone();
9755 projected.required_guards = required_guards;
9756 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
9757 })
9758 .collect()
9759}
9760
9761fn project_using_binding_at_activation(
9762 mut binding: OrdinaryTypeImport,
9763 activation: usize,
9764 root: Node<'_>,
9765 source: &str,
9766) -> Option<OrdinaryTypeImport> {
9767 let include = include_node_for_activation(root, activation)?;
9768 let include_namespace = enclosing_namespace_components(include, source);
9769 let mut declaration_namespace = include_namespace.clone();
9770 declaration_namespace.extend(binding.declaration_namespace);
9771 binding.declaration_namespace = declaration_namespace;
9772 binding.declaration_byte = activation;
9773 if let Some(prefix) = using_named_scope(include, source) {
9774 let mut projected = prefix;
9775 projected.extend(binding.namespace_scope.take().unwrap_or_default());
9776 binding.scope_depth = projected.len();
9777 binding.block_scope = false;
9778 binding.lexical_depth = projected.len();
9779 binding.namespace_scope = Some(projected);
9780 binding.scope_start = 0;
9781 binding.scope_end = usize::MAX;
9782 Some(binding)
9783 } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
9784 binding.namespace_scope = None;
9785 binding.scope_start = start;
9786 binding.scope_end = end;
9787 binding.scope_depth = depth;
9788 binding.block_scope = block_scope;
9789 binding.lexical_depth = include_namespace.len();
9790 Some(binding)
9791 } else {
9792 None
9793 }
9794}
9795
9796pub fn effective_using_bindings_for_name(
9802 visibility: &VisibilityIndex<'_>,
9803 imports: &OrdinaryTypeImportCell,
9804 file: &ProjectFile,
9805 node: Node<'_>,
9806 source: &str,
9807 name: &str,
9808) -> Arc<[OrdinaryTypeImport]> {
9809 imports
9810 .projection_cell(name)
9811 .get_or_init(|| {
9812 let project = project_using_index(visibility);
9813 let name_bindings = project.ordinary_by_name.get(name);
9814 if name_bindings.is_none() && project.directives.is_empty() {
9815 return Arc::from(Vec::new());
9816 }
9817 let root = root_node(node);
9818 let mut projected = Vec::new();
9819 for binding in name_bindings
9820 .into_iter()
9821 .flatten()
9822 .chain(project.directives.iter())
9823 {
9824 if !visibility.source_is_visible(file, &binding.source) {
9825 continue;
9826 }
9827 let target_components = using_binding_target_components_for_name(
9828 binding, project, visibility, file, name,
9829 )
9830 .or_else(|| match &binding.target {
9831 EffectiveUsingTarget::Ordinary {
9832 name: imported_name,
9833 target_components,
9834 ..
9835 } if imported_name == name => Some(target_components.clone()),
9836 EffectiveUsingTarget::Ordinary { .. }
9837 | EffectiveUsingTarget::Namespace { .. } => None,
9838 });
9839 let Some(target_components) = target_components else {
9840 continue;
9841 };
9842 let mut binding = binding.clone();
9843 binding.resolved_target_components = Some(target_components);
9844 projected.extend(project_using_bindings(
9845 binding, visibility, file, root, source,
9846 ));
9847 }
9848 Arc::from(projected)
9849 })
9850 .clone()
9851}
9852
9853pub fn initialized_ordinary_type_imports(
9854 root: Node<'_>,
9855 analyzer: &CppGraphSource<'_>,
9856 visibility: &VisibilityIndex<'_>,
9857 file: &ProjectFile,
9858 source: &str,
9859) -> OrdinaryTypeImportCell {
9860 let cell = visibility.ordinary_type_import_cell(file);
9861 let _ = (root, analyzer, source);
9862 cell
9863}
9864
9865fn root_node(mut node: Node<'_>) -> Node<'_> {
9866 while let Some(parent) = node.parent() {
9867 node = parent;
9868 }
9869 node
9870}
9871
9872fn effective_using_binding_active(
9877 binding: &OrdinaryTypeImport,
9878 node: Node<'_>,
9879 lexical_scope: &[String],
9880 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9881 visibility: &VisibilityIndex<'_>,
9882 file: &ProjectFile,
9883) -> bool {
9884 effective_using_binding_guards_active(
9885 binding,
9886 node.start_byte(),
9887 reference_guards,
9888 visibility,
9889 file,
9890 ) && binding.namespace_scope.as_ref().map_or_else(
9891 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
9892 |namespace| lexical_scope.starts_with(namespace),
9893 )
9894}
9895
9896fn effective_using_binding_guards_active(
9897 binding: &OrdinaryTypeImport,
9898 reference_byte: usize,
9899 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9900 visibility: &VisibilityIndex<'_>,
9901 file: &ProjectFile,
9902) -> bool {
9903 binding.declaration_byte <= reference_byte
9904 && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
9905 && visibility.preprocessor_guards_stable_between(
9906 file,
9907 0,
9908 reference_byte,
9909 &binding.required_guards,
9910 )
9911}
9912
9913fn effective_using_binding_guards_compatible(
9914 binding: &OrdinaryTypeImport,
9915 reference_byte: usize,
9916 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9917 visibility: &VisibilityIndex<'_>,
9918 file: &ProjectFile,
9919) -> bool {
9920 binding.source != *file
9921 && !binding.required_guards.is_empty()
9922 && binding.declaration_byte <= reference_byte
9923 && reference_guards.is_some_and(|active| {
9924 !binding.required_guards.is_subset(active)
9925 && merge_preprocessor_guards(&binding.required_guards, active).is_some()
9926 })
9927 && visibility.preprocessor_guards_stable_between(
9928 file,
9929 0,
9930 reference_byte,
9931 &binding.required_guards,
9932 )
9933}
9934
9935#[allow(clippy::too_many_arguments)]
9936fn binding_type_candidates(
9937 binding: &OrdinaryTypeImport,
9938 active_bindings: &[&OrdinaryTypeImport],
9939 analyzer: &CppGraphSource<'_>,
9940 visibility: &VisibilityIndex<'_>,
9941 file: &ProjectFile,
9942 name: &str,
9943 direct_target: Option<&CodeUnit>,
9944 reference_byte: usize,
9945) -> Vec<(CodeUnit, Vec<String>)> {
9946 let Some(qualified) = binding.resolved_target_components.clone() else {
9947 return Vec::new();
9948 };
9949 let mut targets = Vec::new();
9950 match binding.target {
9951 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
9952 EffectiveUsingTarget::Namespace { .. } => {
9953 let mut stack = vec![qualified];
9954 let mut visited = HashSet::default();
9955 while let Some(namespace) = stack.pop() {
9956 if !visited.insert(namespace.clone()) {
9957 continue;
9958 }
9959 let mut target = namespace.clone();
9960 target.push(name.to_string());
9961 targets.push(target);
9962 stack.extend(active_bindings.iter().filter_map(|candidate| {
9963 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
9964 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
9965 .then(|| candidate.resolved_target_components.clone())
9966 .flatten()
9967 }));
9968 }
9969 }
9970 }
9971 targets
9972 .into_iter()
9973 .flat_map(|target| {
9974 let mut candidates = visibility
9975 .visible_identifier_candidates(file, name)
9976 .filter(|candidate| {
9977 (candidate.is_class() || is_type_alias(candidate))
9978 && type_candidate_matches_lookup_components(
9979 analyzer,
9980 visibility,
9981 file,
9982 candidate,
9983 reference_byte,
9984 &target,
9985 )
9986 })
9987 .cloned()
9988 .collect::<Vec<_>>();
9989 if candidates.is_empty()
9990 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9991 && let Some(target_unit) = direct_target
9992 {
9993 let expanded_target_name = macro_expanded_cpp_name_components(
9994 visibility,
9995 file,
9996 target_unit,
9997 reference_byte,
9998 );
9999 if (target_unit.is_class() || is_type_alias(target_unit))
10000 && expanded_target_name == target
10001 && visibility.external_type_candidate_visible_at(
10002 file,
10003 target_unit,
10004 reference_byte,
10005 )
10006 {
10007 candidates.push(target_unit.clone());
10008 }
10009 }
10010 if candidates.is_empty()
10011 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
10012 && let Some(target_unit) = direct_target
10013 {
10014 let visible_types = visibility
10015 .visible_identifier_candidates(file, name)
10016 .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
10017 .collect::<Vec<_>>();
10018 let uniquely_names_target = !visible_types.is_empty()
10019 && visible_types
10020 .iter()
10021 .all(|candidate| same_visible_symbol(candidate, target_unit));
10022 if uniquely_names_target {
10023 candidates.extend(visible_types.into_iter().cloned());
10024 }
10025 }
10026 candidates
10027 .into_iter()
10028 .map(move |candidate| (candidate, target.clone()))
10029 })
10030 .collect()
10031}
10032
10033fn macro_expanded_cpp_name_components(
10034 visibility: &VisibilityIndex<'_>,
10035 file: &ProjectFile,
10036 unit: &CodeUnit,
10037 reference_byte: usize,
10038) -> Vec<String> {
10039 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10040 brokk_bifrost_core::analyzer::Language::Cpp,
10041 &cpp_name_for(unit),
10042 )
10043 .into_iter()
10044 .flat_map(|component| {
10045 macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
10046 })
10047 .collect()
10048}
10049
10050fn macro_expanded_cpp_name_component(
10051 visibility: &VisibilityIndex<'_>,
10052 file: &ProjectFile,
10053 component: String,
10054 reference_byte: usize,
10055) -> Vec<String> {
10056 let Some(replacement) =
10057 visibility.object_macro_replacement_at(file, &component, reference_byte)
10058 else {
10059 return vec![component];
10060 };
10061 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10062 brokk_bifrost_core::analyzer::Language::Cpp,
10063 &replacement,
10064 );
10065 if expanded.is_empty() {
10066 vec![component]
10067 } else {
10068 expanded
10069 }
10070}
10071
10072fn type_candidate_matches_lookup_components(
10080 analyzer: &CppGraphSource<'_>,
10081 visibility: &VisibilityIndex<'_>,
10082 file: &ProjectFile,
10083 candidate: &CodeUnit,
10084 reference_byte: usize,
10085 target: &[String],
10086) -> bool {
10087 let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
10088 if expanded == target {
10089 return true;
10090 }
10091 let Some(cpp) = analyzer.cpp else {
10092 return false;
10093 };
10094 let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
10095 return false;
10096 };
10097 let root = prepared.tree().root_node();
10098 for range in analyzer.ranges(candidate) {
10099 let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
10100 else {
10101 continue;
10102 };
10103 let mut namespaces = Vec::<(Vec<String>, bool)>::new();
10104 loop {
10105 if current.kind() == "namespace_definition"
10106 && let Some(name) = current.child_by_field_name("name")
10107 {
10108 let mut components = Vec::new();
10109 if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
10110 && !components.is_empty()
10111 {
10112 let inline = (0..current.child_count())
10113 .filter_map(|index| current.child(index))
10114 .any(|child| !child.is_named() && child.kind() == "inline");
10115 namespaces.push((components, inline));
10116 }
10117 }
10118 let Some(parent) = current.parent() else {
10119 break;
10120 };
10121 current = parent;
10122 }
10123 namespaces.reverse();
10124 let mut namespace_components = Vec::new();
10125 let mut inline_indexes = HashSet::default();
10126 for (components, inline) in namespaces {
10127 for component in components {
10128 let expanded_component =
10129 macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
10130 if inline {
10131 inline_indexes.extend(
10132 namespace_components.len()
10133 ..namespace_components.len() + expanded_component.len(),
10134 );
10135 }
10136 namespace_components.extend(expanded_component);
10137 }
10138 }
10139 if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
10140 continue;
10141 }
10142 let mut reachable = vec![false; target.len() + 1];
10146 reachable[0] = true;
10147 for (index, component) in expanded.iter().enumerate() {
10148 let mut next = vec![false; target.len() + 1];
10149 for (target_index, reached) in reachable.iter().copied().enumerate() {
10150 if !reached {
10151 continue;
10152 }
10153 if inline_indexes.contains(&index) {
10154 next[target_index] = true;
10155 }
10156 if target
10157 .get(target_index)
10158 .is_some_and(|target_component| target_component == component)
10159 {
10160 next[target_index + 1] = true;
10161 }
10162 }
10163 reachable = next;
10164 }
10165 if reachable[target.len()] {
10166 return true;
10167 }
10168 }
10169 false
10170}
10171
10172#[allow(clippy::too_many_arguments)]
10173fn resolved_type_import(
10174 candidates: Vec<(CodeUnit, Vec<String>)>,
10175 lexical_depth: usize,
10176 is_direct: bool,
10177 analyzer: &CppGraphSource<'_>,
10178 visibility: &VisibilityIndex<'_>,
10179 file: &ProjectFile,
10180 direct_target: Option<&CodeUnit>,
10181) -> OrdinaryTypeImportResolution {
10182 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
10183 for candidate in candidates {
10184 if !logical
10185 .iter()
10186 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
10187 {
10188 logical.push(candidate);
10189 }
10190 }
10191 let selected = match logical.as_slice() {
10192 [] => return OrdinaryTypeImportResolution::Missing,
10193 [only] => only,
10194 several => {
10199 let units = several
10200 .iter()
10201 .map(|(unit, _)| unit)
10202 .collect::<Vec<&CodeUnit>>();
10203 let Some(spelling) = direct_target.and_then(|target| {
10204 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
10205 }) else {
10206 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
10207 };
10208 several
10209 .iter()
10210 .find(|(unit, _)| same_symbol(unit, spelling))
10211 .expect("the selected spelling is one of the imported candidates")
10212 }
10213 };
10214 OrdinaryTypeImportResolution::Resolved {
10215 target: selected.0.clone(),
10216 target_components: selected.1.clone(),
10217 lexical_depth,
10218 is_direct,
10219 }
10220}
10221
10222#[allow(clippy::too_many_arguments)]
10223fn ordinary_type_import_resolution(
10224 node: Node<'_>,
10225 components: &[String],
10226 global: bool,
10227 analyzer: &CppGraphSource<'_>,
10228 visibility: &VisibilityIndex<'_>,
10229 imports: &OrdinaryTypeImportCell,
10230 file: &ProjectFile,
10231 source: &str,
10232 lexical_scope: &[String],
10233 direct_target: Option<&CodeUnit>,
10234) -> OrdinaryTypeImportResolution {
10235 if global || components.len() != 1 {
10236 return OrdinaryTypeImportResolution::Missing;
10237 }
10238 let name = &components[0];
10239 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
10240 if bindings.is_empty() {
10244 return OrdinaryTypeImportResolution::Missing;
10245 }
10246 let reference_guards = preprocessor_guard_environment(node, source);
10247 let active = bindings
10248 .iter()
10249 .filter(|binding| {
10250 effective_using_binding_active(
10251 binding,
10252 node,
10253 lexical_scope,
10254 reference_guards.as_ref(),
10255 visibility,
10256 file,
10257 )
10258 })
10259 .collect::<Vec<_>>();
10260 let transitive = bindings
10261 .iter()
10262 .filter(|binding| {
10263 effective_using_binding_guards_active(
10264 binding,
10265 node.start_byte(),
10266 reference_guards.as_ref(),
10267 visibility,
10268 file,
10269 ) && (binding.namespace_scope.is_some()
10270 || (binding.scope_start <= node.start_byte()
10271 && node.end_byte() <= binding.scope_end))
10272 })
10273 .collect::<Vec<_>>();
10274 ordinary_type_import_resolution_for_bindings(
10275 node,
10276 name,
10277 analyzer,
10278 visibility,
10279 file,
10280 lexical_scope,
10281 direct_target,
10282 &active,
10283 &transitive,
10284 )
10285}
10286
10287#[allow(clippy::too_many_arguments)]
10288fn compatible_foreign_type_import_resolution(
10289 node: Node<'_>,
10290 components: &[String],
10291 global: bool,
10292 analyzer: &CppGraphSource<'_>,
10293 visibility: &VisibilityIndex<'_>,
10294 imports: &OrdinaryTypeImportCell,
10295 file: &ProjectFile,
10296 source: &str,
10297 lexical_scope: &[String],
10298 direct_target: &CodeUnit,
10299) -> OrdinaryTypeImportResolution {
10300 if global || components.len() != 1 {
10301 return OrdinaryTypeImportResolution::Missing;
10302 }
10303 let name = &components[0];
10304 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
10305 if bindings.is_empty() {
10306 return OrdinaryTypeImportResolution::Missing;
10307 }
10308 let reference_guards = preprocessor_guard_environment(node, source);
10309 let compatible = bindings
10310 .iter()
10311 .filter(|binding| {
10312 effective_using_binding_guards_compatible(
10313 binding,
10314 node.start_byte(),
10315 reference_guards.as_ref(),
10316 visibility,
10317 file,
10318 ) && binding.namespace_scope.as_ref().map_or_else(
10319 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
10320 |namespace| lexical_scope.starts_with(namespace),
10321 )
10322 })
10323 .collect::<Vec<_>>();
10324 let transitive = bindings
10325 .iter()
10326 .filter(|binding| {
10327 effective_using_binding_guards_compatible(
10328 binding,
10329 node.start_byte(),
10330 reference_guards.as_ref(),
10331 visibility,
10332 file,
10333 ) && (binding.namespace_scope.is_some()
10334 || (binding.scope_start <= node.start_byte()
10335 && node.end_byte() <= binding.scope_end))
10336 })
10337 .collect::<Vec<_>>();
10338 ordinary_type_import_resolution_for_bindings(
10339 node,
10340 name,
10341 analyzer,
10342 visibility,
10343 file,
10344 lexical_scope,
10345 Some(direct_target),
10346 &compatible,
10347 &transitive,
10348 )
10349}
10350
10351#[allow(clippy::too_many_arguments)]
10352fn ordinary_type_import_resolution_for_bindings(
10353 node: Node<'_>,
10354 name: &str,
10355 analyzer: &CppGraphSource<'_>,
10356 visibility: &VisibilityIndex<'_>,
10357 file: &ProjectFile,
10358 lexical_scope: &[String],
10359 direct_target: Option<&CodeUnit>,
10360 active: &[&OrdinaryTypeImport],
10361 transitive: &[&OrdinaryTypeImport],
10362) -> OrdinaryTypeImportResolution {
10363 let mut concrete_depths = active
10364 .iter()
10365 .filter(|binding| binding.namespace_scope.is_none())
10366 .map(|binding| binding.scope_depth)
10367 .collect::<Vec<_>>();
10368 concrete_depths.sort_unstable();
10369 concrete_depths.dedup();
10370 for depth in concrete_depths.into_iter().rev() {
10371 let at_tier = active
10372 .iter()
10373 .copied()
10374 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
10375 let direct = at_tier
10376 .clone()
10377 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
10378 .flat_map(|binding| {
10379 binding_type_candidates(
10380 binding,
10381 transitive,
10382 analyzer,
10383 visibility,
10384 file,
10385 name,
10386 direct_target,
10387 node.start_byte(),
10388 )
10389 })
10390 .collect::<Vec<_>>();
10391 if !direct.is_empty() {
10392 return resolved_type_import(
10393 direct,
10394 lexical_scope.len(),
10395 true,
10396 analyzer,
10397 visibility,
10398 file,
10399 direct_target,
10400 );
10401 }
10402 let directives = at_tier
10403 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
10404 .flat_map(|binding| {
10405 binding_type_candidates(
10406 binding,
10407 transitive,
10408 analyzer,
10409 visibility,
10410 file,
10411 name,
10412 direct_target,
10413 node.start_byte(),
10414 )
10415 })
10416 .collect::<Vec<_>>();
10417 if !directives.is_empty() {
10418 return resolved_type_import(
10419 directives,
10420 lexical_scope.len(),
10421 false,
10422 analyzer,
10423 visibility,
10424 file,
10425 direct_target,
10426 );
10427 }
10428 }
10429 for prefix_len in (0..=lexical_scope.len()).rev() {
10430 let tier = &lexical_scope[..prefix_len];
10431 let at_tier = active
10432 .iter()
10433 .copied()
10434 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
10435 let direct = at_tier
10436 .clone()
10437 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
10438 .flat_map(|binding| {
10439 binding_type_candidates(
10440 binding,
10441 transitive,
10442 analyzer,
10443 visibility,
10444 file,
10445 name,
10446 direct_target,
10447 node.start_byte(),
10448 )
10449 })
10450 .collect::<Vec<_>>();
10451 if !direct.is_empty() {
10452 return resolved_type_import(
10453 direct,
10454 prefix_len,
10455 true,
10456 analyzer,
10457 visibility,
10458 file,
10459 direct_target,
10460 );
10461 }
10462 let directives = at_tier
10463 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
10464 .flat_map(|binding| {
10465 binding_type_candidates(
10466 binding,
10467 transitive,
10468 analyzer,
10469 visibility,
10470 file,
10471 name,
10472 direct_target,
10473 node.start_byte(),
10474 )
10475 })
10476 .collect::<Vec<_>>();
10477 if !directives.is_empty() {
10478 return resolved_type_import(
10479 directives,
10480 prefix_len,
10481 false,
10482 analyzer,
10483 visibility,
10484 file,
10485 direct_target,
10486 );
10487 }
10488 }
10489 OrdinaryTypeImportResolution::Missing
10490}
10491
10492#[allow(clippy::too_many_arguments)]
10493pub fn resolve_type_components_lexically_at(
10494 node: Node<'_>,
10495 components: &[String],
10496 global: bool,
10497 analyzer: &CppGraphSource<'_>,
10498 visibility: &VisibilityIndex<'_>,
10499 ordinary_type_imports: &OrdinaryTypeImportCell,
10500 file: &ProjectFile,
10501 source: &str,
10502) -> LexicalTypeResolution {
10503 resolve_type_components_lexically_at_inner(
10504 node,
10505 components,
10506 global,
10507 analyzer,
10508 visibility,
10509 ordinary_type_imports,
10510 file,
10511 source,
10512 None,
10513 false,
10514 false,
10515 None,
10516 )
10517}
10518
10519#[allow(clippy::too_many_arguments)]
10527pub fn resolve_type_components_lexically_at_preserving_alias(
10528 node: Node<'_>,
10529 components: &[String],
10530 global: bool,
10531 analyzer: &CppGraphSource<'_>,
10532 visibility: &VisibilityIndex<'_>,
10533 file: &ProjectFile,
10534 source: &str,
10535) -> LexicalTypeResolution {
10536 let ordinary_type_imports =
10537 initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
10538 resolve_type_components_lexically_at_inner(
10539 node,
10540 components,
10541 global,
10542 analyzer,
10543 visibility,
10544 &ordinary_type_imports,
10545 file,
10546 source,
10547 None,
10548 false,
10549 true,
10550 None,
10551 )
10552}
10553
10554#[allow(clippy::too_many_arguments)]
10555fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10556 node: Node<'_>,
10557 components: &[String],
10558 global: bool,
10559 analyzer: &CppGraphSource<'_>,
10560 visibility: &VisibilityIndex<'_>,
10561 ordinary_type_imports: &OrdinaryTypeImportCell,
10562 file: &ProjectFile,
10563 source: &str,
10564 scope_cache: Option<&LexicalScopeCache>,
10565) -> LexicalTypeResolution {
10566 resolve_type_components_lexically_at_inner(
10567 node,
10568 components,
10569 global,
10570 analyzer,
10571 visibility,
10572 ordinary_type_imports,
10573 file,
10574 source,
10575 None,
10576 false,
10577 true,
10578 scope_cache,
10579 )
10580}
10581
10582#[allow(clippy::too_many_arguments)]
10583fn resolve_type_components_lexically_at_for_target_with_scope_cache(
10584 node: Node<'_>,
10585 components: &[String],
10586 global: bool,
10587 analyzer: &CppGraphSource<'_>,
10588 visibility: &VisibilityIndex<'_>,
10589 ordinary_type_imports: &OrdinaryTypeImportCell,
10590 file: &ProjectFile,
10591 source: &str,
10592 target: &CodeUnit,
10593 apply_structured_prefilter: bool,
10594 scope_cache: Option<&LexicalScopeCache>,
10595) -> LexicalTypeResolution {
10596 resolve_type_components_lexically_at_inner(
10597 node,
10598 components,
10599 global,
10600 analyzer,
10601 visibility,
10602 ordinary_type_imports,
10603 file,
10604 source,
10605 Some(target),
10606 apply_structured_prefilter,
10607 false,
10608 scope_cache,
10609 )
10610}
10611
10612#[allow(clippy::too_many_arguments)]
10613fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10614 node: Node<'_>,
10615 components: &[String],
10616 global: bool,
10617 analyzer: &CppGraphSource<'_>,
10618 visibility: &VisibilityIndex<'_>,
10619 ordinary_type_imports: &OrdinaryTypeImportCell,
10620 file: &ProjectFile,
10621 source: &str,
10622 recovered_scope: &[String],
10623) -> LexicalTypeResolution {
10624 resolve_type_components_in_authoritative_scope(
10625 node,
10626 components,
10627 global,
10628 analyzer,
10629 visibility,
10630 ordinary_type_imports,
10631 file,
10632 source,
10633 None,
10634 false,
10635 true,
10636 recovered_scope.to_vec(),
10637 )
10638}
10639
10640#[allow(clippy::too_many_arguments)]
10641fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
10642 node: Node<'_>,
10643 components: &[String],
10644 global: bool,
10645 analyzer: &CppGraphSource<'_>,
10646 visibility: &VisibilityIndex<'_>,
10647 ordinary_type_imports: &OrdinaryTypeImportCell,
10648 file: &ProjectFile,
10649 source: &str,
10650 target: &CodeUnit,
10651 apply_structured_prefilter: bool,
10652 recovered_scope: &[String],
10653) -> LexicalTypeResolution {
10654 resolve_type_components_in_authoritative_scope(
10655 node,
10656 components,
10657 global,
10658 analyzer,
10659 visibility,
10660 ordinary_type_imports,
10661 file,
10662 source,
10663 Some(target),
10664 apply_structured_prefilter,
10665 false,
10666 recovered_scope.to_vec(),
10667 )
10668}
10669
10670#[allow(clippy::too_many_arguments)]
10671fn resolve_type_components_lexically_at_inner(
10672 node: Node<'_>,
10673 components: &[String],
10674 global: bool,
10675 analyzer: &CppGraphSource<'_>,
10676 visibility: &VisibilityIndex<'_>,
10677 ordinary_type_imports: &OrdinaryTypeImportCell,
10678 file: &ProjectFile,
10679 source: &str,
10680 direct_target: Option<&CodeUnit>,
10681 apply_structured_prefilter: bool,
10682 preserve_alias: bool,
10683 scope_cache: Option<&LexicalScopeCache>,
10684) -> LexicalTypeResolution {
10685 let lexical_scope = if global {
10686 Vec::new()
10687 } else {
10688 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
10689 node,
10690 analyzer,
10691 visibility,
10692 file,
10693 source,
10694 true,
10695 recovered_macro_decorated_declarator_type(node)
10696 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10697 scope_cache,
10698 ) {
10699 LexicalScopeResolution::Resolved(scope) => scope,
10700 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10701 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10702 }
10703 };
10704 resolve_type_components_lexically_at_scoped(
10705 node,
10706 components,
10707 global,
10708 analyzer,
10709 visibility,
10710 ordinary_type_imports,
10711 file,
10712 source,
10713 direct_target,
10714 apply_structured_prefilter,
10715 preserve_alias,
10716 lexical_scope,
10717 )
10718}
10719
10720#[allow(clippy::too_many_arguments)]
10721fn resolve_type_components_lexically_at_scoped(
10722 node: Node<'_>,
10723 components: &[String],
10724 global: bool,
10725 analyzer: &CppGraphSource<'_>,
10726 visibility: &VisibilityIndex<'_>,
10727 ordinary_type_imports: &OrdinaryTypeImportCell,
10728 file: &ProjectFile,
10729 source: &str,
10730 direct_target: Option<&CodeUnit>,
10731 apply_structured_prefilter: bool,
10732 preserve_alias: bool,
10733 mut lexical_scope: Vec<String>,
10734) -> LexicalTypeResolution {
10735 if !global
10736 && components.len() == 1
10737 && let Some(target) = direct_target
10740 && lexical_scope
10741 .last()
10742 .is_none_or(|last| last != &components[0])
10743 && has_malformed_wrapper_function_definition_ancestor(node)
10748 && let Some(indexed_namespace) =
10749 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
10750 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
10751 {
10752 lexical_scope = indexed_namespace;
10753 }
10754 resolve_type_components_in_authoritative_scope(
10755 node,
10756 components,
10757 global,
10758 analyzer,
10759 visibility,
10760 ordinary_type_imports,
10761 file,
10762 source,
10763 direct_target,
10764 apply_structured_prefilter,
10765 preserve_alias,
10766 lexical_scope,
10767 )
10768}
10769
10770#[allow(clippy::too_many_arguments)]
10776fn resolve_type_components_in_authoritative_scope(
10777 node: Node<'_>,
10778 components: &[String],
10779 global: bool,
10780 analyzer: &CppGraphSource<'_>,
10781 visibility: &VisibilityIndex<'_>,
10782 ordinary_type_imports: &OrdinaryTypeImportCell,
10783 file: &ProjectFile,
10784 source: &str,
10785 direct_target: Option<&CodeUnit>,
10786 apply_structured_prefilter: bool,
10787 preserve_alias: bool,
10788 lexical_scope: Vec<String>,
10789) -> LexicalTypeResolution {
10790 if apply_structured_prefilter
10791 && direct_target.is_some()
10792 && !preserve_alias
10793 && !global
10794 && components.len() == 1
10795 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
10796 {
10797 return LexicalTypeResolution::Missing;
10798 }
10799 if !global
10805 && components.len() == 1
10806 && recovered_macro_decorated_type_node(node).is_some()
10807 && let Some(resolution) = recovered_same_file_type_alias_resolution(
10808 node,
10809 components,
10810 analyzer,
10811 visibility,
10812 file,
10813 direct_target,
10814 &lexical_scope,
10815 )
10816 {
10817 return resolution;
10818 }
10819 if apply_structured_prefilter
10820 && let Some(target) = direct_target
10821 && !preserve_alias
10822 && !visibility.structured_type_reference_may_resolve_to_target(
10823 analyzer,
10824 file,
10825 components,
10826 global,
10827 &lexical_scope,
10828 target,
10829 )
10830 {
10831 return LexicalTypeResolution::Missing;
10832 }
10833 let normal = if preserve_alias {
10834 visibility.resolve_type_components_lexically_for_forward(
10835 analyzer,
10836 file,
10837 components,
10838 global,
10839 &lexical_scope,
10840 )
10841 } else {
10842 direct_target.map_or_else(
10843 || {
10844 visibility.resolve_type_components_lexically(
10845 analyzer,
10846 file,
10847 components,
10848 global,
10849 &lexical_scope,
10850 )
10851 },
10852 |target| {
10853 visibility.resolve_type_components_lexically_for_target(
10854 analyzer,
10855 file,
10856 components,
10857 global,
10858 &lexical_scope,
10859 target,
10860 )
10861 },
10862 )
10863 };
10864 let normal = match normal {
10865 LexicalTypeResolution::Resolved { ref unit, .. }
10866 if !visibility
10867 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10868 {
10869 LexicalTypeResolution::Missing
10870 }
10871 resolution => resolution,
10872 };
10873 let normal_depth = match &normal {
10874 LexicalTypeResolution::Resolved { components, .. } => {
10875 Some(components.len().saturating_sub(1))
10876 }
10877 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
10878 };
10879 match ordinary_type_import_resolution(
10885 node,
10886 components,
10887 global,
10888 analyzer,
10889 visibility,
10890 ordinary_type_imports,
10891 file,
10892 source,
10893 &lexical_scope,
10894 direct_target,
10895 ) {
10896 OrdinaryTypeImportResolution::Missing => normal,
10897 OrdinaryTypeImportResolution::Resolved {
10898 lexical_depth,
10899 is_direct,
10900 ..
10901 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
10902 || normal_depth.is_some_and(|depth| {
10903 depth > lexical_depth || (!is_direct && depth == lexical_depth)
10904 }) =>
10905 {
10906 normal
10907 }
10908 OrdinaryTypeImportResolution::Resolved {
10909 target,
10910 target_components,
10911 ..
10912 } => visibility.resolve_imported_type_candidate(
10913 analyzer,
10914 file,
10915 &target,
10916 &target_components,
10917 direct_target,
10918 preserve_alias,
10919 ),
10920 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
10921 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
10922 {
10923 normal
10924 }
10925 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
10926 }
10927}
10928
10929fn recovered_same_file_type_alias_resolution(
10930 node: Node<'_>,
10931 components: &[String],
10932 analyzer: &CppGraphSource<'_>,
10933 visibility: &VisibilityIndex<'_>,
10934 file: &ProjectFile,
10935 direct_target: Option<&CodeUnit>,
10936 lexical_scope: &[String],
10937) -> Option<LexicalTypeResolution> {
10938 debug_assert_eq!(components.len(), 1);
10939 debug_assert!(recovered_macro_decorated_type_node(node).is_some());
10940 let alias_provider = analyzer.type_alias_provider()?;
10941 for qualified in lexical_component_tiers(components, false, lexical_scope) {
10942 let candidates = visibility
10943 .visible_identifier_candidates(file, &components[0])
10944 .filter(|candidate| {
10945 candidate.source() == file
10946 && canonical_cpp_scope_components(candidate) == qualified
10947 && visibility
10948 .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10949 })
10950 .collect::<Vec<_>>();
10951 if candidates.is_empty() {
10952 continue;
10953 }
10954 if candidates
10955 .iter()
10956 .any(|candidate| !alias_provider.is_type_alias(candidate))
10957 {
10958 return None;
10959 }
10960 let unit = if let Some(target) = direct_target {
10961 visibility.unique_type_candidate_preserving_target(
10962 analyzer,
10963 file,
10964 &candidates,
10965 target,
10966 )?
10967 } else {
10968 let first = candidates[0];
10969 if candidates
10970 .iter()
10971 .any(|candidate| !same_visible_symbol(candidate, first))
10972 {
10973 return None;
10974 }
10975 first.clone()
10976 };
10977 return Some(LexicalTypeResolution::Resolved {
10978 unit,
10979 components: qualified,
10980 candidates: candidates.into_iter().cloned().collect(),
10981 });
10982 }
10983 None
10984}
10985
10986fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10987 matches!(
10988 structured_owner_context_resolution(node, ctx),
10989 StructuredOwnerContextResolution::SelfTarget
10990 | StructuredOwnerContextResolution::InheritedTarget
10991 )
10992}
10993
10994fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10998 let Some(call) = node.parent().filter(|parent| {
10999 parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
11000 }) else {
11001 return matches!(
11002 structured_owner_context_resolution(node, ctx),
11003 StructuredOwnerContextResolution::InheritedTarget
11004 );
11005 };
11006 let Some(target_owner) = ctx.spec.owner.as_ref() else {
11007 return false;
11008 };
11009 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
11010 return false;
11011 };
11012 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
11013 return false;
11014 }
11015 let Some(arity) = ctx
11016 .visibility
11017 .call_arity_evidence(ctx.file, call, ctx.source)
11018 .exact()
11019 else {
11020 return false;
11021 };
11022 matches!(
11023 resolve_declaring_callable_owner(
11024 &ctx.analyzer,
11025 ctx.visibility,
11026 ctx.file,
11027 cached_declaring_member_owner(&enclosing_owner, ctx),
11028 &ctx.spec.member_name,
11029 arity,
11030 ),
11031 EnclosingMemberOwnerResolution::Owner(owner)
11032 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
11033 )
11034}
11035
11036fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11037 matches!(
11038 structured_owner_context_resolution(node, ctx),
11039 StructuredOwnerContextResolution::NonTarget
11040 )
11041}
11042
11043fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11044 let Some(target_owner) = ctx.spec.owner.as_ref() else {
11045 return false;
11046 };
11047 let mut current = node.parent();
11048 while let Some(parent) = current {
11049 if parent.kind() == "function_definition" {
11050 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
11051 return false;
11052 };
11053 if let Some(owners) = out_of_line_member_definition_owner(
11054 &ctx.analyzer,
11055 ctx.visibility,
11056 ctx.file,
11057 ctx.source,
11058 owner_lookup,
11059 ) && let Some((_, owner)) = owners.innermost()
11060 {
11061 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
11062 }
11063 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
11064 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
11065 }
11066 return false;
11067 }
11068 current = parent.parent();
11069 }
11070 false
11071}
11072
11073#[derive(Clone, Copy)]
11074enum StructuredOwnerContextResolution {
11075 SelfTarget,
11078 InheritedTarget,
11083 NonTarget,
11084 Ambiguous,
11085 Missing,
11086}
11087
11088fn structured_owner_context_resolution(
11089 node: Node<'_>,
11090 ctx: &ScanCtx<'_>,
11091) -> StructuredOwnerContextResolution {
11092 let Some(target_owner) = ctx.spec.owner.as_ref() else {
11093 return StructuredOwnerContextResolution::Missing;
11094 };
11095 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
11096 return StructuredOwnerContextResolution::Missing;
11097 };
11098 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
11099 return StructuredOwnerContextResolution::SelfTarget;
11100 }
11101 let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
11104 match member_owner {
11105 EnclosingMemberOwnerResolution::Owner(owner)
11106 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
11107 {
11108 StructuredOwnerContextResolution::InheritedTarget
11109 }
11110 EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
11111 EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
11112 EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
11113 }
11114}
11115
11116fn cached_declaring_member_owner(
11117 receiver_owner: &CodeUnit,
11118 ctx: &ScanCtx<'_>,
11119) -> EnclosingMemberOwnerResolution {
11120 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
11121 return cached;
11122 }
11123 let resolved = resolve_declaring_member_owner(
11124 &ctx.analyzer,
11125 ctx.visibility,
11126 ctx.file,
11127 receiver_owner,
11128 &ctx.spec.member_name,
11129 );
11130 let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
11131 indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
11132 } else {
11133 resolved
11134 };
11135 ctx.member_owner_cache
11136 .borrow_mut()
11137 .insert(receiver_owner.clone(), resolved.clone());
11138 resolved
11139}
11140
11141fn indexed_declaring_owner_for_recovered_member(
11148 receiver_owner: &CodeUnit,
11149 ctx: &ScanCtx<'_>,
11150) -> EnclosingMemberOwnerResolution {
11151 let Some(spec_owner) = ctx.spec.owner.as_ref() else {
11152 return EnclosingMemberOwnerResolution::Missing;
11153 };
11154 if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
11155 return EnclosingMemberOwnerResolution::Missing;
11156 }
11157 let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
11158 return EnclosingMemberOwnerResolution::Missing;
11159 };
11160 let Some(receiver_owner) =
11161 ctx.visibility
11162 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
11163 else {
11164 return EnclosingMemberOwnerResolution::Ambiguous;
11165 };
11166 let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
11167 ctx.visibility
11168 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
11169 }) else {
11170 return EnclosingMemberOwnerResolution::Missing;
11171 };
11172
11173 let owner_declares_member = |owner: &CodeUnit| {
11174 if same_visible_symbol(owner, &target_owner) {
11175 return true;
11176 }
11177 let mut member_fq = owner.fq().clone();
11178 member_fq.push(
11179 ctx.spec
11180 .target
11181 .fq()
11182 .last()
11183 .expect("a method target has a terminal member segment"),
11184 );
11185 ctx.analyzer
11186 .definitions(&member_fq.display(segment_interner()))
11187 .any(|child| child.is_function())
11188 };
11189 if owner_declares_member(&receiver_owner) {
11190 return EnclosingMemberOwnerResolution::Owner(receiver_owner);
11191 }
11192
11193 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
11194 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
11195 let mut declaring_owner = None;
11196 while let Some(raw_owner) = stack.pop() {
11197 let Some(owner) =
11198 ctx.visibility
11199 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
11200 else {
11201 return EnclosingMemberOwnerResolution::Ambiguous;
11202 };
11203 let propagated = propagated_counts.entry(owner.clone()).or_default();
11204 if *propagated == 2 {
11205 continue;
11206 }
11207 *propagated += 1;
11208 if owner_declares_member(&owner) {
11209 if declaring_owner.is_some() {
11210 return EnclosingMemberOwnerResolution::Ambiguous;
11211 }
11212 declaring_owner = Some(owner);
11213 continue;
11214 }
11215 stack.extend(hierarchy.get_direct_ancestors(&owner));
11216 }
11217 declaring_owner
11218 .map(EnclosingMemberOwnerResolution::Owner)
11219 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
11220}
11221
11222fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
11223 if (has_recovered_class_shape_ancestor(node)
11228 || has_malformed_wrapper_function_definition_ancestor(node))
11229 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
11230 {
11231 return Some(owner);
11232 }
11233 let mut current = node.parent();
11234 while let Some(parent) = current {
11235 if parent.kind() == "function_definition" {
11236 let owner_lookup = function_definition_owner_lookup_node(parent);
11237 if let Some(owner_lookup) = owner_lookup
11238 && let Some(owners) = out_of_line_member_definition_owner(
11239 &ctx.analyzer,
11240 ctx.visibility,
11241 ctx.file,
11242 ctx.source,
11243 owner_lookup,
11244 )
11245 && let Some((_, owner)) = owners.innermost()
11246 {
11247 return Some(owner.clone());
11248 }
11249 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
11250 return Some(owner);
11251 }
11252 if let Some(owner) = enclosing_context(parent, ctx)
11253 .owner
11254 .filter(|owner| owner.is_class())
11255 {
11256 return Some(owner);
11257 }
11258 if let Some(owner_lookup) = owner_lookup
11259 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
11260 {
11261 return Some(owner);
11262 }
11263 break;
11264 }
11265 current = parent.parent();
11266 }
11267 enclosing_context(node, ctx)
11268 .owner
11269 .filter(|owner| owner.is_class())
11270}
11271
11272fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
11273 let target_owner = ctx.spec.owner.as_ref()?;
11274 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
11275 let owner_name = owner_components.last()?;
11276 let mut candidates = Vec::new();
11277 for candidate in ctx
11278 .visibility
11279 .visible_identifier_candidates(ctx.file, owner_name)
11280 .filter(|candidate| candidate.is_class())
11281 {
11282 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11283 brokk_bifrost_core::analyzer::Language::Cpp,
11284 &cpp_name_for(candidate),
11285 );
11286 if !components.ends_with(&owner_components)
11287 || candidates
11288 .iter()
11289 .any(|existing| same_logical_symbol(existing, candidate))
11290 {
11291 continue;
11292 }
11293 candidates.push(candidate.clone());
11294 }
11295 let [candidate] = candidates.as_slice() else {
11296 return None;
11297 };
11298 (same_logical_symbol(candidate, target_owner)
11299 && target_group_contains_owner_peer(candidate, ctx))
11300 .then(|| candidate.clone())
11301}