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(candidates: &mut Vec<CodeUnit>) {
2637 let mut deduped = Vec::with_capacity(candidates.len());
2638 for candidate in candidates.drain(..) {
2639 if !deduped
2640 .iter()
2641 .any(|existing| same_logical_symbol(existing, &candidate))
2642 {
2643 deduped.push(candidate);
2644 }
2645 }
2646 *candidates = deduped;
2647}
2648
2649fn resolve_callable_candidates(
2650 candidates: Vec<CodeUnit>,
2651 call_arity: Option<usize>,
2652 reference_byte: usize,
2653 analyzer: &CppGraphSource<'_>,
2654 visibility: &VisibilityIndex<'_>,
2655 file: &ProjectFile,
2656) -> BareCallTargetResolution {
2657 let mut candidates = candidates;
2658 dedupe_callable_candidates(&mut candidates);
2659 if candidates.is_empty() {
2660 return BareCallTargetResolution::Missing;
2661 }
2662 let Some(call_arity) = call_arity else {
2663 if candidates.len() == 1 {
2670 return BareCallTargetResolution::FreeFunctions(candidates);
2671 }
2672 return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
2673 };
2674 let applicable = candidates
2675 .into_iter()
2676 .filter(|candidate| {
2677 visibility
2678 .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
2679 .is_some_and(|arity| arity.accepts(call_arity))
2680 })
2681 .collect::<Vec<_>>();
2682 if applicable.is_empty() {
2683 BareCallTargetResolution::CallableShadow
2684 } else {
2685 BareCallTargetResolution::FreeFunctions(applicable)
2686 }
2687}
2688
2689fn resolve_direct_type_candidates(
2690 candidates: Vec<(CodeUnit, Vec<String>)>,
2691 analyzer: &CppGraphSource<'_>,
2692 visibility: &VisibilityIndex<'_>,
2693 file: &ProjectFile,
2694) -> BareCallTargetResolution {
2695 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
2696 for candidate in candidates {
2697 if !logical
2698 .iter()
2699 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
2700 {
2701 logical.push(candidate);
2702 }
2703 }
2704 let [(target, components)] = logical.as_slice() else {
2705 return if logical.is_empty() {
2706 BareCallTargetResolution::Missing
2707 } else {
2708 BareCallTargetResolution::Ambiguous
2709 };
2710 };
2711 match visibility
2712 .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
2713 {
2714 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2715 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2716 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2717 }
2718}
2719
2720#[allow(clippy::too_many_arguments)]
2725pub fn resolve_block_using_call_target(
2726 call: Node<'_>,
2727 function: Node<'_>,
2728 analyzer: &CppGraphSource<'_>,
2729 visibility: &VisibilityIndex<'_>,
2730 ordinary_type_imports: &OrdinaryTypeImportCell,
2731 file: &ProjectFile,
2732 source: &str,
2733) -> Option<BlockUsingCallTargetResolution> {
2734 if !matches!(function.kind(), "identifier" | "template_function") {
2735 return None;
2736 }
2737 let name = node_text(function_terminal_node(function), source);
2738 if name.is_empty() {
2739 return None;
2740 }
2741 let bindings = effective_using_bindings_for_name(
2742 visibility,
2743 ordinary_type_imports,
2744 file,
2745 function,
2746 source,
2747 name,
2748 );
2749 let block_bindings = bindings
2750 .iter()
2751 .filter(|binding| {
2752 binding.namespace_scope.is_none()
2753 && binding.block_scope
2754 && matches!(binding.target, EffectiveUsingTarget::Ordinary { .. })
2755 })
2756 .collect::<Vec<_>>();
2757 if block_bindings.is_empty() {
2758 return None;
2759 }
2760 let lexical_scope =
2761 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2762 LexicalScopeResolution::Resolved(scope) => scope,
2763 LexicalScopeResolution::Ambiguous => {
2764 return Some(BlockUsingCallTargetResolution::Ambiguous);
2765 }
2766 LexicalScopeResolution::Missing => return None,
2767 };
2768 let reference_guards = preprocessor_guard_environment(function, source);
2769 let active = block_bindings
2770 .into_iter()
2771 .filter(|binding| {
2772 effective_using_binding_active(
2773 binding,
2774 function,
2775 &lexical_scope,
2776 reference_guards.as_ref(),
2777 visibility,
2778 file,
2779 )
2780 })
2781 .collect::<Vec<_>>();
2782 let depth = active.iter().map(|binding| binding.scope_depth).max()?;
2783 let at_tier = active
2784 .into_iter()
2785 .filter(|binding| binding.scope_depth == depth)
2786 .collect::<Vec<_>>();
2787 let callable_candidates = at_tier
2788 .iter()
2789 .flat_map(|binding| {
2790 binding_free_function_candidates(
2791 binding,
2792 &[],
2793 analyzer,
2794 visibility,
2795 file,
2796 name,
2797 call.start_byte(),
2798 )
2799 })
2800 .collect::<Vec<_>>();
2801 if !callable_candidates.is_empty() {
2802 return Some(BlockUsingCallTargetResolution::Target(
2803 resolve_callable_candidates(
2804 callable_candidates,
2805 visibility.call_arity_evidence(file, call, source).exact(),
2806 call.start_byte(),
2807 analyzer,
2808 visibility,
2809 file,
2810 ),
2811 ));
2812 }
2813 let type_candidates = at_tier
2814 .iter()
2815 .flat_map(|binding| {
2816 binding_type_candidates(
2817 binding,
2818 &[],
2819 analyzer,
2820 visibility,
2821 file,
2822 name,
2823 None,
2824 call.start_byte(),
2825 )
2826 })
2827 .collect::<Vec<_>>();
2828 if !type_candidates.is_empty() {
2829 return Some(BlockUsingCallTargetResolution::Target(
2830 resolve_direct_type_candidates(type_candidates, analyzer, visibility, file),
2831 ));
2832 }
2833
2834 let mut unindexed = Vec::new();
2835 for binding in at_tier {
2836 let Some(components) = binding.resolved_target_components.as_ref() else {
2837 continue;
2838 };
2839 if !unindexed.contains(components) {
2840 unindexed.push(components.clone());
2841 }
2842 }
2843 match unindexed.as_slice() {
2844 [target] => Some(BlockUsingCallTargetResolution::Unindexed(target.clone())),
2845 [] => None,
2846 _ => Some(BlockUsingCallTargetResolution::Ambiguous),
2847 }
2848}
2849
2850#[allow(clippy::too_many_arguments)]
2851pub fn resolve_bare_call_target(
2852 call: Node<'_>,
2853 function: Node<'_>,
2854 analyzer: &CppGraphSource<'_>,
2855 visibility: &VisibilityIndex<'_>,
2856 ordinary_type_imports: &OrdinaryTypeImportCell,
2857 file: &ProjectFile,
2858 source: &str,
2859) -> BareCallTargetResolution {
2860 if !matches!(function.kind(), "identifier" | "template_function") {
2861 return BareCallTargetResolution::Missing;
2862 }
2863 let terminal = function_terminal_node(function);
2864 let name = node_text(terminal, source);
2865 if name.is_empty() {
2866 return BareCallTargetResolution::Missing;
2867 }
2868 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
2869 let lexical_scope =
2870 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2871 LexicalScopeResolution::Resolved(scope) => scope,
2872 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
2873 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
2874 };
2875 let type_resolution = resolve_type_node_lexically(
2876 function,
2877 analyzer,
2878 visibility,
2879 ordinary_type_imports,
2880 file,
2881 source,
2882 );
2883 let type_components = match &type_resolution {
2884 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2885 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2886 };
2887 let direct_type_resolution = visibility.resolve_type_components_lexically(
2888 analyzer,
2889 file,
2890 &[name.to_string()],
2891 false,
2892 &lexical_scope,
2893 );
2894 let direct_type_components = match &direct_type_resolution {
2895 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2896 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2897 };
2898 let has_explicit_template_arguments =
2899 cpp_template_reference_arguments(function, source).is_some();
2900 let bindings = effective_using_bindings_for_name(
2901 visibility,
2902 ordinary_type_imports,
2903 file,
2904 function,
2905 source,
2906 name,
2907 );
2908 let function_guards = if bindings.is_empty() {
2912 None
2913 } else {
2914 preprocessor_guard_environment(function, source)
2915 };
2916 let active_bindings = bindings
2917 .iter()
2918 .filter(|binding| {
2919 effective_using_binding_active(
2920 binding,
2921 function,
2922 &lexical_scope,
2923 function_guards.as_ref(),
2924 visibility,
2925 file,
2926 )
2927 })
2928 .collect::<Vec<_>>();
2929 let transitive_bindings = bindings
2930 .iter()
2931 .filter(|binding| {
2932 effective_using_binding_guards_active(
2933 binding,
2934 function.start_byte(),
2935 function_guards.as_ref(),
2936 visibility,
2937 file,
2938 ) && (binding.namespace_scope.is_some()
2939 || (binding.scope_start <= function.start_byte()
2940 && function.end_byte() <= binding.scope_end))
2941 })
2942 .collect::<Vec<_>>();
2943 let mut concrete_depths = active_bindings
2944 .iter()
2945 .filter(|binding| binding.namespace_scope.is_none())
2946 .map(|binding| binding.scope_depth)
2947 .collect::<Vec<_>>();
2948 concrete_depths.sort_unstable();
2949 concrete_depths.dedup();
2950 for depth in concrete_depths.into_iter().rev() {
2951 let at_tier = active_bindings
2952 .iter()
2953 .copied()
2954 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
2955 let direct = at_tier
2956 .clone()
2957 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2958 .flat_map(|binding| {
2959 binding_free_function_candidates(
2960 binding,
2961 &transitive_bindings,
2962 analyzer,
2963 visibility,
2964 file,
2965 name,
2966 call.start_byte(),
2967 )
2968 })
2969 .collect::<Vec<_>>();
2970 if !direct.is_empty() {
2971 return resolve_callable_candidates(
2972 direct,
2973 call_arity,
2974 call.start_byte(),
2975 analyzer,
2976 visibility,
2977 file,
2978 );
2979 }
2980 let direct_types = at_tier
2981 .clone()
2982 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2983 .flat_map(|binding| {
2984 binding_type_candidates(
2985 binding,
2986 &transitive_bindings,
2987 analyzer,
2988 visibility,
2989 file,
2990 name,
2991 None,
2992 call.start_byte(),
2993 )
2994 })
2995 .collect::<Vec<_>>();
2996 if !direct_types.is_empty() {
2997 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3002 }
3003 let directives = at_tier
3004 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3005 .flat_map(|binding| {
3006 binding_free_function_candidates(
3007 binding,
3008 &transitive_bindings,
3009 analyzer,
3010 visibility,
3011 file,
3012 name,
3013 call.start_byte(),
3014 )
3015 })
3016 .collect::<Vec<_>>();
3017 if !directives.is_empty() {
3018 return resolve_callable_candidates(
3019 directives,
3020 call_arity,
3021 call.start_byte(),
3022 analyzer,
3023 visibility,
3024 file,
3025 );
3026 }
3027 }
3028 for prefix_len in (0..=lexical_scope.len()).rev() {
3029 let mut qualified = lexical_scope[..prefix_len].to_vec();
3030 qualified.push(name.to_string());
3031 let same_name_resolves_to_type = direct_type_components
3032 .is_some_and(|components| components == qualified.as_slice())
3033 || type_components.is_some_and(|components| components == qualified.as_slice());
3034 let mut direct = visibility
3035 .visible_identifier_candidates(file, name)
3036 .filter(|candidate| {
3037 candidate.is_function()
3038 && type_owner_of(analyzer, candidate).is_none()
3039 && !(same_name_resolves_to_type
3040 && (visibility.callable_is_constructor_declaration(analyzer, candidate)
3041 || has_explicit_template_arguments
3042 && visibility
3043 .callable_is_deduction_guide_declaration(analyzer, candidate)))
3044 && cpp_name_for(candidate) == qualified.join("::")
3045 && visibility.declaration_visible_at(
3046 analyzer,
3047 file,
3048 candidate,
3049 call.start_byte(),
3050 )
3051 })
3052 .cloned()
3053 .collect::<Vec<_>>();
3054 let at_tier = active_bindings.iter().copied().filter(|binding| {
3055 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
3056 });
3057 direct.extend(
3058 at_tier
3059 .clone()
3060 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3061 .flat_map(|binding| {
3062 binding_free_function_candidates(
3063 binding,
3064 &transitive_bindings,
3065 analyzer,
3066 visibility,
3067 file,
3068 name,
3069 call.start_byte(),
3070 )
3071 }),
3072 );
3073 if !direct.is_empty() {
3074 return resolve_callable_candidates(
3075 direct,
3076 call_arity,
3077 call.start_byte(),
3078 analyzer,
3079 visibility,
3080 file,
3081 );
3082 }
3083 let mut direct_types = at_tier
3084 .clone()
3085 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
3086 .flat_map(|binding| {
3087 binding_type_candidates(
3088 binding,
3089 &transitive_bindings,
3090 analyzer,
3091 visibility,
3092 file,
3093 name,
3094 None,
3095 call.start_byte(),
3096 )
3097 })
3098 .collect::<Vec<_>>();
3099 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
3100 && let LexicalTypeResolution::Resolved {
3101 unit, components, ..
3102 } = &direct_type_resolution
3103 {
3104 direct_types.push((unit.clone(), components.clone()));
3105 }
3106 if !direct_types.is_empty() {
3107 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
3112 }
3113 let directives = at_tier
3114 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
3115 .flat_map(|binding| {
3116 binding_free_function_candidates(
3117 binding,
3118 &transitive_bindings,
3119 analyzer,
3120 visibility,
3121 file,
3122 name,
3123 call.start_byte(),
3124 )
3125 })
3126 .collect::<Vec<_>>();
3127 if !directives.is_empty() {
3128 return resolve_callable_candidates(
3129 directives,
3130 call_arity,
3131 call.start_byte(),
3132 analyzer,
3133 visibility,
3134 file,
3135 );
3136 }
3137 if type_components.is_some_and(|components| components == qualified.as_slice()) {
3138 return match type_resolution {
3142 LexicalTypeResolution::Resolved { unit, .. } => {
3143 BareCallTargetResolution::Type(unit)
3144 }
3145 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3146 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3147 };
3148 }
3149 }
3150 match type_resolution {
3159 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
3160 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
3161 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
3162 }
3163}
3164
3165fn static_qualifier_type_scopes<'tree>(
3166 node: Node<'tree>,
3167 ctx: &ScanCtx<'_>,
3168) -> Option<Vec<Node<'tree>>> {
3169 if !matches!(
3170 node.kind(),
3171 "qualified_identifier" | "scoped_type_identifier"
3172 ) {
3173 return None;
3174 }
3175 debug_assert!(!is_nested_type_node(node));
3178 let qualified = qualified_owner_components(node, ctx.source)?;
3179 static_qualifier_type_scopes_for_components(node, qualified, ctx)
3180}
3181
3182fn static_qualifier_type_scopes_for_components<'tree>(
3183 node: Node<'tree>,
3184 qualified: QualifiedOwnerComponents<'tree>,
3185 ctx: &ScanCtx<'_>,
3186) -> Option<Vec<Node<'tree>>> {
3187 if !qualified.global
3188 && qualified.names.first().is_some_and(|name| {
3189 name == ctx.spec.target.identifier()
3190 && qualified
3191 .nodes
3192 .first()
3193 .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
3194 })
3195 {
3196 return None;
3197 }
3198 let mut matches = Vec::new();
3199 let mut inherited_injected_name_is_shadowed = false;
3200 for component_count in 1..=qualified.names.len() {
3201 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
3202 node,
3203 &qualified.names[..component_count],
3204 qualified.global,
3205 &ctx.analyzer,
3206 ctx.visibility,
3207 &ctx.ordinary_type_imports,
3208 ctx.file,
3209 ctx.source,
3210 &ctx.spec.target,
3211 false,
3212 Some(&ctx.lexical_scope_cache),
3213 );
3214 match resolution {
3215 LexicalTypeResolution::Resolved {
3216 unit, candidates, ..
3217 } if (!ctx
3218 .analyzer
3219 .type_alias_provider()
3220 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3221 || ctx.visibility.external_type_candidate_visible_in_context(
3222 &ctx.analyzer,
3223 ctx.file,
3224 &ctx.spec.target,
3225 node,
3226 ))
3227 && (same_visible_symbol(&unit, &ctx.spec.target)
3228 || candidates
3229 .iter()
3230 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
3231 && target_alias_candidates_visible(&candidates, node, ctx) =>
3232 {
3233 let matched =
3234 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
3235 if !template_type_component_preserves_target(matched, &candidates, ctx) {
3236 continue;
3237 }
3238 if !matches.iter().any(|existing: &Node<'_>| {
3239 existing.start_byte() == matched.start_byte()
3240 && existing.end_byte() == matched.end_byte()
3241 }) {
3242 matches.push(matched);
3243 }
3244 }
3245 LexicalTypeResolution::Ambiguous => {
3251 return (!inherited_injected_name_is_shadowed)
3252 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3253 .flatten()
3254 .map(|scope| vec![scope])
3255 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
3256 }
3257 LexicalTypeResolution::Resolved { .. } => {
3258 if let Some(matched) =
3259 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3260 {
3261 matches.push(matched);
3262 }
3263 inherited_injected_name_is_shadowed |= component_count == 1;
3264 }
3265 LexicalTypeResolution::Missing => {
3266 if let Some(matched) =
3267 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
3268 {
3269 matches.push(matched);
3270 }
3271 }
3272 }
3273 }
3274 if matches.is_empty() {
3275 (!inherited_injected_name_is_shadowed)
3276 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
3277 .flatten()
3278 .map(|scope| vec![scope])
3279 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
3280 } else {
3281 Some(matches)
3282 }
3283}
3284
3285fn target_guided_unproven_qualified_value_owner_scope<'tree>(
3288 node: Node<'tree>,
3289 ctx: &ScanCtx<'_>,
3290) -> Option<Node<'tree>> {
3291 let target = physically_visible_type_target(ctx)?;
3292 if !target.is_class() {
3293 return None;
3294 }
3295 let qualified = qualified_owner_components(node, ctx.source)?;
3296 let lexical_scope = match enclosing_lexical_scope_components(
3297 node,
3298 &ctx.analyzer,
3299 ctx.visibility,
3300 ctx.file,
3301 ctx.source,
3302 ) {
3303 LexicalScopeResolution::Resolved(scope) => scope,
3304 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3305 enclosing_namespace_components(node, ctx.source)
3306 }
3307 };
3308 let LexicalTypeResolution::Resolved {
3309 unit, candidates, ..
3310 } = ctx.visibility.resolve_type_components_lexically_for_target(
3311 &ctx.analyzer,
3312 ctx.file,
3313 &qualified.names,
3314 qualified.global,
3315 &lexical_scope,
3316 target,
3317 )
3318 else {
3319 return None;
3320 };
3321 (same_visible_symbol(&unit, target)
3322 || candidates
3323 .iter()
3324 .any(|candidate| same_visible_symbol(candidate, target)))
3325 .then(|| qualified.nodes.last().copied())
3326 .flatten()
3327}
3328
3329fn target_guided_nested_alias_type_scope<'tree>(
3335 node: Node<'tree>,
3336 qualified: &QualifiedOwnerComponents<'tree>,
3337 component_count: usize,
3338 ctx: &ScanCtx<'_>,
3339) -> Option<Node<'tree>> {
3340 if component_count < 2 {
3341 return None;
3342 }
3343 let (owner_components, member_name) =
3344 qualified.names[..component_count].split_at(component_count - 1);
3345 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
3346 node,
3347 owner_components,
3348 qualified.global,
3349 &ctx.analyzer,
3350 ctx.visibility,
3351 &ctx.ordinary_type_imports,
3352 ctx.file,
3353 ctx.source,
3354 ) else {
3355 return None;
3356 };
3357 let member_name = member_name.first()?;
3358 let alias_provider = ctx.analyzer.type_alias_provider()?;
3359 ctx.visibility
3360 .visible_members_for_owner_name(ctx.file, &owner, member_name)
3361 .into_iter()
3362 .filter(|member| alias_provider.is_type_alias(member))
3363 .find(|member| {
3364 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
3365 &ctx.analyzer,
3366 ctx.file,
3367 member,
3368 node,
3369 ) || ctx
3370 .visibility
3371 .external_type_candidate_guard_compatible_in_context(
3372 &ctx.analyzer,
3373 ctx.file,
3374 member,
3375 node,
3376 );
3377 if !member_visible {
3378 return false;
3379 }
3380 same_visible_symbol(member, &ctx.spec.target)
3381 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
3382 })
3383 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
3384}
3385
3386fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
3387 if ctx.visibility.structured_class_alias_resolves_to_target(
3388 &ctx.analyzer,
3389 ctx.file,
3390 candidate,
3391 &ctx.spec.target,
3392 ) {
3393 return ctx.spec.target.clone();
3394 }
3395 let structured = ctx
3396 .visibility
3397 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
3398 if let Some(canonical) = structured
3399 .as_ref()
3400 .filter(|canonical| !same_visible_symbol(canonical, candidate))
3401 {
3402 return canonical.clone();
3403 }
3404 structured.unwrap_or_else(|| candidate.clone())
3405}
3406
3407fn target_guided_dependent_alias_qualifier_scope<'tree>(
3411 node: Node<'tree>,
3412 ctx: &ScanCtx<'_>,
3413) -> Option<Node<'tree>> {
3414 if !matches!(
3415 node.kind(),
3416 "qualified_identifier" | "scoped_type_identifier"
3417 ) {
3418 return None;
3419 }
3420 let target = physically_visible_type_target(ctx)?;
3421 let alias_provider = ctx.analyzer.type_alias_provider()?;
3422 if !target.is_class() || alias_provider.is_type_alias(target) {
3423 return None;
3424 }
3425 let nodes = cpp_name_component_nodes(node)?;
3426 let names = nodes
3427 .iter()
3428 .map(|component| node_text(*component, ctx.source).to_string())
3429 .collect::<Vec<_>>();
3430 let global = is_globally_qualified_cpp_name(node);
3431 let lexical_scope = match enclosing_lexical_scope_components(
3432 node,
3433 &ctx.analyzer,
3434 ctx.visibility,
3435 ctx.file,
3436 ctx.source,
3437 ) {
3438 LexicalScopeResolution::Resolved(scope) => scope,
3439 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3440 enclosing_namespace_components(node, ctx.source)
3441 }
3442 };
3443 for component_count in 1..=names.len() {
3444 let components = &names[..component_count];
3445 let name = components.last()?;
3446 let candidates = ctx
3447 .visibility
3448 .visible_identifier_candidates(ctx.file, name)
3449 .filter(|candidate| alias_provider.is_type_alias(candidate))
3450 .filter(|candidate| {
3451 ctx.visibility.is_physically_visible(ctx.file, candidate)
3452 && ctx
3453 .visibility
3454 .external_type_candidate_guard_compatible_in_context(
3455 &ctx.analyzer,
3456 ctx.file,
3457 candidate,
3458 node,
3459 )
3460 })
3461 .filter(|candidate| {
3462 let candidate_components = canonical_cpp_scope_components(candidate);
3463 lexical_component_tiers(components, global, &lexical_scope)
3464 .any(|tier| tier == candidate_components)
3465 || (component_count == 1
3466 && member_alias_owner_matches_reference_for(
3467 candidate,
3468 nodes[component_count - 1],
3469 ctx,
3470 ))
3471 })
3472 .filter(|candidate| {
3473 ctx.visibility.structured_class_alias_path_preserves_target(
3474 &ctx.analyzer,
3475 ctx.file,
3476 candidate,
3477 target,
3478 )
3479 });
3480 let mut aliases: Vec<&CodeUnit> = Vec::new();
3481 for candidate in candidates {
3482 if !aliases
3483 .iter()
3484 .any(|existing| same_logical_symbol(existing, candidate))
3485 {
3486 aliases.push(candidate);
3487 }
3488 }
3489 if aliases.len() == 1 {
3490 return nodes.get(component_count - 1).copied();
3491 }
3492 if aliases.len() > 1 {
3493 return None;
3494 }
3495 }
3496 None
3497}
3498
3499fn target_guided_dependent_class_alias_leaf<'tree>(
3502 node: Node<'tree>,
3503 ctx: &ScanCtx<'_>,
3504) -> Option<Node<'tree>> {
3505 if node.kind() != "type_identifier"
3506 || is_declaration_name(node)
3507 || local_type_name_shadows(node, ctx)
3508 {
3509 return None;
3510 }
3511 let target = physically_visible_type_target(ctx)?;
3512 let alias_provider = ctx.analyzer.type_alias_provider()?;
3513 if !target.is_class() || alias_provider.is_type_alias(target) {
3514 return None;
3515 }
3516 let name = node_text(node, ctx.source);
3517 let aliases = ctx
3518 .visibility
3519 .visible_identifier_candidates(ctx.file, name)
3520 .filter(|candidate| alias_provider.is_type_alias(candidate))
3521 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3522 .filter(|candidate| {
3523 ctx.visibility.is_physically_visible(ctx.file, candidate)
3524 && ctx
3525 .visibility
3526 .external_type_candidate_guard_compatible_in_context(
3527 &ctx.analyzer,
3528 ctx.file,
3529 candidate,
3530 node,
3531 )
3532 })
3533 .filter(|candidate| {
3534 ctx.visibility.structured_class_alias_path_preserves_target(
3535 &ctx.analyzer,
3536 ctx.file,
3537 candidate,
3538 target,
3539 )
3540 })
3541 .collect::<Vec<_>>();
3542 matches!(aliases.as_slice(), [_]).then_some(node)
3543}
3544
3545fn target_guided_unproven_alias_type_reference<'tree>(
3548 node: Node<'tree>,
3549 candidates: &[CodeUnit],
3550 ctx: &ScanCtx<'_>,
3551) -> Option<Node<'tree>> {
3552 let template_arguments = cpp_template_reference_arguments(node, ctx.source);
3553 let target = physically_visible_type_target(ctx)?;
3554 if !target.is_class() {
3555 return None;
3556 }
3557 let alias_provider = ctx.analyzer.type_alias_provider()?;
3558 let (components, _) = type_reference_components(node, ctx.source)?;
3559 let hit = template_arguments
3560 .as_ref()
3561 .and_then(|_| template_reference_name_node(node))
3562 .map(function_terminal_node)
3563 .unwrap_or_else(|| function_terminal_node(node));
3564 candidates
3565 .iter()
3566 .filter(|candidate| {
3567 alias_provider.is_type_alias(candidate)
3568 && ctx.visibility.is_physically_visible(ctx.file, candidate)
3569 && canonical_cpp_scope_components(candidate) == components
3570 })
3571 .find(|candidate| {
3572 template_arguments.as_ref().map_or_else(
3573 || {
3574 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3575 || ctx.visibility.structured_alias_primary_preserves_target(
3576 &ctx.analyzer,
3577 ctx.file,
3578 candidate,
3579 target,
3580 )
3581 },
3582 |arguments| {
3583 ctx.visibility.template_alias_arguments_preserve_target(
3584 &ctx.analyzer,
3585 ctx.file,
3586 candidate,
3587 arguments,
3588 target,
3589 )
3590 },
3591 )
3592 })
3593 .map(|_| hit)
3594}
3595
3596fn target_alias_candidates_visible(
3597 candidates: &[CodeUnit],
3598 reference: Node<'_>,
3599 ctx: &ScanCtx<'_>,
3600) -> bool {
3601 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
3602 return true;
3603 };
3604 if candidates.iter().any(|candidate| {
3605 !alias_provider.is_type_alias(candidate)
3606 && ctx.visibility.same_template_member_identity(
3607 &ctx.analyzer,
3608 candidate,
3609 &ctx.spec.target,
3610 )
3611 }) {
3612 return true;
3613 }
3614 let target_aliases = candidates
3615 .iter()
3616 .filter(|candidate| {
3617 alias_provider.is_type_alias(candidate)
3618 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3619 })
3620 .collect::<Vec<_>>();
3621 target_aliases.is_empty()
3622 || target_aliases
3623 .iter()
3624 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
3625}
3626
3627fn type_candidate_visible_at_reference(
3628 candidate: &CodeUnit,
3629 reference: Node<'_>,
3630 ctx: &ScanCtx<'_>,
3631) -> bool {
3632 let class_owned_alias = ctx
3633 .analyzer
3634 .type_alias_provider()
3635 .is_some_and(|provider| provider.is_type_alias(candidate))
3636 && ctx
3637 .analyzer
3638 .parent_of(candidate)
3639 .is_some_and(|owner| owner.is_class());
3640 if class_owned_alias {
3641 let conditional_family = ctx
3642 .visibility
3643 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
3644 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
3645 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
3646 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
3647 let guard_match = ctx
3648 .visibility
3649 .external_type_candidate_guard_compatible_in_context(
3650 &ctx.analyzer,
3651 ctx.file,
3652 candidate,
3653 reference,
3654 );
3655 let general_match = conditional_family
3656 && ctx.visibility.external_type_candidate_visible_in_context(
3657 &ctx.analyzer,
3658 ctx.file,
3659 candidate,
3660 reference,
3661 );
3662 return owner_match && (guard_match || general_match);
3663 }
3664 ctx.visibility.external_type_candidate_visible_in_context(
3665 &ctx.analyzer,
3666 ctx.file,
3667 candidate,
3668 reference,
3669 )
3670}
3671
3672fn unqualified_reference_selects_inherited_alias(
3673 candidate: &CodeUnit,
3674 reference: Node<'_>,
3675 ctx: &ScanCtx<'_>,
3676) -> bool {
3677 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3678 return false;
3679 };
3680 if global || components.len() != 1 {
3681 return false;
3682 }
3683 matches!(
3684 resolve_type_node_lexically_for_target(
3685 reference,
3686 &ctx.analyzer,
3687 ctx.visibility,
3688 &ctx.ordinary_type_imports,
3689 ctx.file,
3690 ctx.source,
3691 candidate,
3692 Some(&ctx.lexical_scope_cache),
3693 ctx.recovered_sentinel_scope(reference).as_deref(),
3694 ),
3695 LexicalTypeResolution::Resolved {
3696 ref unit,
3697 ref candidates,
3698 ..
3699 } if ctx
3700 .visibility
3701 .same_template_member_identity(&ctx.analyzer, unit, candidate)
3702 || candidates.iter().any(|resolved| {
3703 ctx.visibility.same_template_member_identity(
3704 &ctx.analyzer,
3705 resolved,
3706 candidate,
3707 )
3708 })
3709 )
3710}
3711
3712fn qualified_reference_selects_type_candidate(
3713 candidate: &CodeUnit,
3714 reference: Node<'_>,
3715 ctx: &ScanCtx<'_>,
3716) -> bool {
3717 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
3718 return false;
3719 };
3720 if components.len() < 2 {
3721 return false;
3722 }
3723 let candidate_components = canonical_cpp_scope_components(candidate);
3724 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3725 match enclosing_lexical_scope_components(
3726 reference,
3727 &ctx.analyzer,
3728 ctx.visibility,
3729 ctx.file,
3730 ctx.source,
3731 ) {
3732 LexicalScopeResolution::Resolved(scope) => scope,
3733 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3734 enclosing_namespace_components(reference, ctx.source)
3735 }
3736 }
3737 });
3738 lexical_component_tiers(&components, global, &lexical_scope)
3739 .any(|qualified| qualified == candidate_components)
3740}
3741
3742fn qualified_type_component_hit_node<'tree>(
3743 component: Node<'tree>,
3744 qualified: Node<'tree>,
3745) -> Node<'tree> {
3746 let mut current = component;
3747 while let Some(parent) = current.parent() {
3748 let is_type_name = matches!(
3749 parent.kind(),
3750 "template_type"
3751 | "qualified_identifier"
3752 | "scoped_identifier"
3753 | "scoped_type_identifier"
3754 ) && parent
3755 .child_by_field_name("name")
3756 .is_some_and(|name| same_node(name, current));
3757 if !is_type_name {
3758 break;
3759 }
3760 current = parent;
3761 if same_node(parent, qualified) {
3762 break;
3763 }
3764 }
3765 current
3766}
3767
3768fn template_type_component_preserves_target(
3769 node: Node<'_>,
3770 candidates: &[CodeUnit],
3771 ctx: &ScanCtx<'_>,
3772) -> bool {
3773 template_reference_candidates_select_target(
3774 node,
3775 candidates,
3776 &ctx.analyzer,
3777 ctx.visibility,
3778 ctx.file,
3779 ctx.source,
3780 &ctx.spec.target,
3781 )
3782}
3783
3784fn template_reference_candidates_select_target(
3785 node: Node<'_>,
3786 candidates: &[CodeUnit],
3787 analyzer: &CppGraphSource<'_>,
3788 visibility: &VisibilityIndex<'_>,
3789 file: &ProjectFile,
3790 source: &str,
3791 target: &CodeUnit,
3792) -> bool {
3793 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3794 return !visibility.is_template_specialization(target);
3795 };
3796 let direct_template_name =
3797 template_reference_name_node(node).map(|name| node_text(name, source));
3798 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
3799 analyzer.type_alias_provider().is_some_and(|provider| {
3800 visibility
3801 .visible_identifier_candidates(file, name)
3802 .filter(|candidate| provider.is_type_alias(candidate))
3803 .any(|candidate| {
3804 visibility.template_alias_arguments_preserve_target(
3805 analyzer, file, candidate, &arguments, target,
3806 )
3807 })
3808 })
3809 });
3810 named_alias_selects_target
3811 || candidates.iter().any(|candidate| {
3812 (same_visible_symbol(candidate, target)
3813 && visibility.is_primary_template(target)
3814 && direct_template_name == Some(candidate.identifier()))
3815 || visibility.template_alias_arguments_preserve_target(
3816 analyzer, file, candidate, &arguments, target,
3817 )
3818 || visibility
3819 .resolve_template_arguments(file, candidate.clone(), &arguments)
3820 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
3821 })
3822}
3823
3824fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
3825 let template = if node.kind() == "template_type" {
3826 node
3827 } else {
3828 node.child_by_field_name("name")
3829 .filter(|name| name.kind() == "template_type")?
3830 };
3831 template.child_by_field_name("name")
3832}
3833
3834fn type_resolution_matches_target(
3835 node: Node<'_>,
3836 unit: &CodeUnit,
3837 candidates: &[CodeUnit],
3838 ctx: &ScanCtx<'_>,
3839) -> bool {
3840 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
3841}
3842
3843fn type_resolution_matches_unit_target(
3844 node: Node<'_>,
3845 unit: &CodeUnit,
3846 candidates: &[CodeUnit],
3847 target: &CodeUnit,
3848 ctx: &ScanCtx<'_>,
3849) -> bool {
3850 target_alias_candidates_visible(candidates, node, ctx)
3851 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
3852}
3853
3854fn type_resolution_identifies_unit_target(
3862 node: Node<'_>,
3863 unit: &CodeUnit,
3864 candidates: &[CodeUnit],
3865 target: &CodeUnit,
3866 ctx: &ScanCtx<'_>,
3867) -> bool {
3868 if !template_alias_owner_matches_reference(node, target, ctx) {
3869 return false;
3870 }
3871 if ctx.visibility.is_template_specialization(target)
3872 && cpp_template_reference_arguments(node, ctx.source).is_some()
3873 {
3874 let selected_unit =
3875 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
3876 ctx.visibility
3877 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
3878 .ok()
3879 });
3880 return selected_unit
3881 .as_ref()
3882 .is_some_and(|selected| same_visible_symbol(selected, target))
3883 || template_reference_candidates_select_target(
3884 node,
3885 candidates,
3886 &ctx.analyzer,
3887 ctx.visibility,
3888 ctx.file,
3889 ctx.source,
3890 target,
3891 );
3892 }
3893 ctx.visibility
3894 .same_template_member_identity(&ctx.analyzer, unit, target)
3895 || ctx.visibility.structured_class_alias_resolves_to_target(
3896 &ctx.analyzer,
3897 ctx.file,
3898 unit,
3899 target,
3900 )
3901 || candidates.iter().any(|candidate| {
3902 ctx.visibility
3903 .same_template_member_identity(&ctx.analyzer, candidate, target)
3904 || ctx.visibility.structured_class_alias_resolves_to_target(
3905 &ctx.analyzer,
3906 ctx.file,
3907 candidate,
3908 target,
3909 )
3910 })
3911}
3912
3913fn template_alias_owner_matches_reference(
3919 node: Node<'_>,
3920 target: &CodeUnit,
3921 ctx: &ScanCtx<'_>,
3922) -> bool {
3923 if !ctx
3924 .analyzer
3925 .type_alias_provider()
3926 .is_some_and(|provider| provider.is_type_alias(target))
3927 {
3928 return true;
3929 }
3930 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
3931 return true;
3932 };
3933 if !target_owner.is_class() {
3934 return true;
3935 }
3936 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
3937 return true;
3938 };
3939 if !ctx.visibility.is_template_specialization(&target_owner)
3940 && !ctx.visibility.is_template_specialization(&reference_owner)
3941 {
3942 return true;
3943 }
3944 same_visible_symbol(&target_owner, &reference_owner)
3945}
3946
3947fn inherited_injected_class_qualifier_scope<'tree>(
3948 node: Node<'tree>,
3949 ctx: &ScanCtx<'_>,
3950) -> Option<Node<'tree>> {
3951 let qualified = qualified_owner_components(node, ctx.source)?;
3952 if qualified.global || qualified.names.is_empty() {
3953 return None;
3954 }
3955 let injected_name = &qualified.names[0];
3956 if !ctx.spec.target.is_class()
3957 || ctx.spec.target.identifier() != injected_name
3958 || physically_visible_type_target(ctx).is_none()
3959 {
3960 return None;
3961 }
3962 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
3963 let owner = ctx.visibility.inherited_injected_class_owner(
3964 &ctx.analyzer,
3965 ctx.file,
3966 &enclosing_owner,
3967 injected_name,
3968 )?;
3969 same_visible_symbol(&owner, &ctx.spec.target)
3970 .then(|| qualified.nodes.first().copied())
3971 .flatten()
3972}
3973
3974fn target_guided_qualifier_type_scopes<'tree>(
3977 node: Node<'tree>,
3978 ctx: &ScanCtx<'_>,
3979) -> Option<Vec<Node<'tree>>> {
3980 if !matches!(
3981 node.kind(),
3982 "qualified_identifier" | "scoped_type_identifier"
3983 ) {
3984 return None;
3985 }
3986 let target = physically_visible_type_target(ctx)?;
3987 let qualified = qualified_owner_components(node, ctx.source)?;
3988 let lexical_scope = match enclosing_lexical_scope_components(
3997 node,
3998 &ctx.analyzer,
3999 ctx.visibility,
4000 ctx.file,
4001 ctx.source,
4002 ) {
4003 LexicalScopeResolution::Resolved(scope) => scope,
4004 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
4005 enclosing_namespace_components(node, ctx.source)
4006 }
4007 };
4008 let indexed_owner_scope =
4009 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
4010 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
4011 let mut matches = Vec::new();
4012 for component_count in 1..=qualified.names.len() {
4013 let components = &qualified.names[..component_count];
4014 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
4015 .collect::<Vec<_>>();
4016 let name = components.last()?;
4017 let mut candidates = Vec::new();
4018 let mut exact_candidates = Vec::new();
4019 for candidate in ctx
4020 .visibility
4021 .visible_identifier_candidates(ctx.file, name)
4022 .filter(|candidate| candidate.is_class())
4023 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
4024 {
4025 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4026 brokk_bifrost_core::analyzer::Language::Cpp,
4027 &cpp_name_for(candidate),
4028 );
4029 if !candidate_components.ends_with(components)
4030 || candidates
4031 .iter()
4032 .any(|existing| same_logical_symbol(existing, candidate))
4033 {
4034 continue;
4035 }
4036 let exact_lexical_scope = lexical_tiers
4037 .iter()
4038 .any(|expected| expected == &candidate_components);
4039 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
4040 let structured_owner_match = indexed_owner_scope
4041 .as_ref()
4042 .is_some_and(|owner| owner.starts_with(candidate_owner))
4043 || recovered_owner_scope
4044 .as_ref()
4045 .is_some_and(|owner| owner.starts_with(candidate_owner));
4046 let class_alias_owner_match = ctx
4047 .analyzer
4048 .type_alias_provider()
4049 .is_some_and(|provider| provider.is_type_alias(candidate))
4050 && member_alias_owner_matches_reference_for(candidate, node, ctx);
4051 let macro_namespace_owner_match = is_declaration_name(node)
4052 && macro_namespace_scope_matches(candidate_owner, node, ctx);
4053 if components.len() == 1
4054 && candidate_components != components
4055 && !exact_lexical_scope
4056 && !structured_owner_match
4057 && !class_alias_owner_match
4058 && !macro_namespace_owner_match
4059 {
4060 continue;
4061 }
4062 candidates.push(candidate.clone());
4063 if exact_lexical_scope {
4064 exact_candidates.push(candidate.clone());
4065 }
4066 }
4067 if !exact_candidates.is_empty() {
4068 candidates = exact_candidates;
4069 }
4070 let canonical_alias_target_matches = matches!(
4075 candidates.as_slice(),
4076 [candidate]
4077 if ctx
4078 .analyzer
4079 .type_alias_provider()
4080 .is_some_and(|provider| provider.is_type_alias(candidate))
4081 && type_candidate_visible_at_reference(candidate, node, ctx)
4082 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
4083 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4084 brokk_bifrost_core::analyzer::Language::Cpp,
4085 &cpp_name_for(candidate),
4086 ) == components
4087 || member_alias_owner_matches_reference_for(candidate, node, ctx))
4088 );
4089 let direct_alias_target = ctx
4090 .analyzer
4091 .type_alias_provider()
4092 .is_some_and(|provider| provider.is_type_alias(target))
4093 && candidates
4094 .iter()
4095 .any(|candidate| same_symbol(candidate, target));
4096 let unique_target = matches!(
4097 candidates.as_slice(),
4098 [candidate] if same_visible_symbol(candidate, target)
4099 );
4100 if direct_alias_target || unique_target || canonical_alias_target_matches {
4101 let matched = if ctx
4102 .analyzer
4103 .type_alias_provider()
4104 .is_some_and(|provider| provider.is_type_alias(target))
4105 && ctx
4106 .analyzer
4107 .parent_of(target)
4108 .is_some_and(|owner| owner.is_class())
4109 && ctx
4110 .visibility
4111 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
4112 && !class_owned_alias_has_distinct_visible_sibling(target, ctx)
4113 {
4114 qualified.nodes[component_count - 1]
4118 } else {
4119 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
4120 };
4121 if template_type_component_preserves_target(matched, &candidates, ctx) {
4122 matches.push(matched);
4123 }
4124 }
4125 }
4126 (!matches.is_empty()).then_some(matches)
4127}
4128
4129fn class_owned_alias_has_distinct_visible_sibling(target: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
4130 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4131 return false;
4132 };
4133 ctx.visibility
4134 .visible_identifier_candidates(ctx.file, target.identifier())
4135 .any(|candidate| {
4136 alias_provider.is_type_alias(candidate)
4137 && candidate.identifier() == target.identifier()
4138 && !same_visible_symbol(candidate, target)
4139 && ctx
4140 .analyzer
4141 .parent_of(candidate)
4142 .is_some_and(|owner| owner.is_class())
4143 })
4144}
4145
4146fn target_guided_unproven_out_of_line_owner<'tree>(
4149 node: Node<'tree>,
4150 ctx: &ScanCtx<'_>,
4151) -> Option<Node<'tree>> {
4152 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
4153 || !is_declaration_name(node)
4154 {
4155 return None;
4156 }
4157 let target = physically_visible_type_target(ctx)?;
4158 if !target.is_class() {
4159 return None;
4160 }
4161 let qualified = qualified_owner_components(node, ctx.source)?;
4162 let target_components = canonical_cpp_scope_components(target);
4163 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
4164 let parser_scope = enclosing_namespace_components(node, ctx.source);
4165 let mut scope = ctx.recovered_sentinel_scope(node).or_else(|| {
4166 if !parser_scope.is_empty() || target_namespace.is_empty() {
4167 Some(parser_scope)
4168 } else {
4169 indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4170 }
4171 })?;
4172 if has_malformed_wrapper_function_definition_ancestor(node)
4173 && target_namespace.starts_with(&scope)
4174 && target_namespace.len() > scope.len()
4175 {
4176 scope = target_namespace.to_vec();
4177 }
4178 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
4179 .any(|components| components == target_components)
4180 {
4181 return None;
4182 }
4183 let owner_name = qualified.names.last()?;
4184 let candidates = ctx
4185 .visibility
4186 .visible_identifier_candidates(ctx.file, owner_name)
4187 .filter(|candidate| {
4188 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
4189 })
4190 .collect::<Vec<_>>();
4191 if candidates.is_empty()
4192 || candidates
4193 .iter()
4194 .any(|candidate| !same_visible_symbol(candidate, target))
4195 {
4196 return None;
4197 }
4198 qualified.nodes.last().copied()
4199}
4200
4201fn macro_namespace_scope_matches(
4202 candidate_owner: &[String],
4203 node: Node<'_>,
4204 ctx: &ScanCtx<'_>,
4205) -> bool {
4206 let namespace = enclosing_namespace_components(node, ctx.source);
4207 if namespace.is_empty() || candidate_owner.is_empty() {
4208 return false;
4209 }
4210 let mut expanded_owner = Vec::new();
4211 for component in candidate_owner {
4212 if let Some(replacement) =
4213 ctx.visibility
4214 .object_macro_replacement_at(ctx.file, component, node.start_byte())
4215 {
4216 let replacement_components =
4217 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4218 brokk_bifrost_core::analyzer::Language::Cpp,
4219 &replacement,
4220 );
4221 if replacement_components.is_empty() {
4222 return false;
4223 }
4224 expanded_owner.extend(replacement_components);
4225 } else {
4226 expanded_owner.push(component.clone());
4227 }
4228 }
4229 expanded_owner == namespace
4230}
4231
4232fn target_guided_missing_type_leaf<'tree>(
4233 node: Node<'tree>,
4234 ctx: &ScanCtx<'_>,
4235) -> Option<Node<'tree>> {
4236 physically_visible_type_target(ctx)?;
4237 target_guided_missing_dependent_nested_type_leaf(node, ctx)
4238 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
4239 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
4240 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
4241 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
4242 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
4243 .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
4244}
4245
4246fn target_guided_missing_class_alias_target_type_leaf<'tree>(
4250 node: Node<'tree>,
4251 ctx: &ScanCtx<'_>,
4252) -> Option<Node<'tree>> {
4253 if node.kind() != "type_identifier"
4254 || is_declaration_name(node)
4255 || local_type_name_shadows(node, ctx)
4256 {
4257 return None;
4258 }
4259 let alias_provider = ctx.analyzer.type_alias_provider()?;
4260 let name = node_text(node, ctx.source);
4261 let aliases = ctx
4262 .visibility
4263 .visible_identifier_candidates(ctx.file, name)
4264 .filter(|candidate| alias_provider.is_type_alias(candidate))
4265 .filter(|candidate| {
4266 ctx.analyzer
4267 .parent_of(candidate)
4268 .is_some_and(|owner| owner.is_class())
4269 })
4270 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
4271 .filter(|candidate| {
4272 ctx.visibility
4273 .external_type_candidate_guard_compatible_in_context(
4274 &ctx.analyzer,
4275 ctx.file,
4276 candidate,
4277 node,
4278 )
4279 })
4280 .collect::<Vec<_>>();
4281 (!aliases.is_empty()
4282 && aliases.iter().all(|candidate| {
4283 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
4284 }))
4285 .then_some(node)
4286}
4287
4288fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
4292 node: Node<'tree>,
4293 ctx: &ScanCtx<'_>,
4294) -> Option<Node<'tree>> {
4295 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
4296 matches!(
4297 declaration.kind(),
4298 "parameter_declaration" | "optional_parameter_declaration"
4299 )
4300 });
4301 let placement_new_type = node.parent().is_some_and(|parent| {
4302 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
4303 });
4304 if !parameter && !placement_new_type {
4305 return None;
4306 }
4307 if !ctx
4308 .analyzer
4309 .type_alias_provider()
4310 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4311 || !type_alias_owner_matches_structured_reference(node, ctx)
4312 {
4313 return None;
4314 }
4315 target_guided_missing_declaration_type_leaf(node, ctx)
4316}
4317
4318fn target_guided_missing_dependent_nested_type_leaf<'tree>(
4325 node: Node<'tree>,
4326 ctx: &ScanCtx<'_>,
4327) -> Option<Node<'tree>> {
4328 if !matches!(
4329 node.kind(),
4330 "qualified_identifier" | "scoped_type_identifier"
4331 ) || !qualified_type_scope_contains_template(node)
4332 {
4333 return None;
4334 }
4335 let name = node
4336 .child_by_field_name("name")
4337 .filter(|name| name.kind() == "type_identifier")?;
4338 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
4339 return None;
4340 }
4341 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
4342 if !owner_target.is_class() {
4343 return None;
4344 }
4345 let owner = node.child_by_field_name("scope")?;
4346 let owner_resolution = resolve_type_node_lexically_for_target(
4347 owner,
4348 &ctx.analyzer,
4349 ctx.visibility,
4350 &ctx.ordinary_type_imports,
4351 ctx.file,
4352 ctx.source,
4353 &owner_target,
4354 Some(&ctx.lexical_scope_cache),
4355 ctx.recovered_sentinel_scope(owner).as_deref(),
4356 );
4357 if matches!(
4358 owner_resolution,
4359 LexicalTypeResolution::Resolved {
4360 ref unit,
4361 ref candidates,
4362 ..
4363 } if type_resolution_matches_unit_target(
4364 owner,
4365 unit,
4366 candidates,
4367 &owner_target,
4368 ctx,
4369 )
4370 ) {
4371 return Some(name);
4372 }
4373
4374 let qualified = qualified_owner_components(node, ctx.source)?;
4375 let parser_namespace = enclosing_namespace_components(node, ctx.source);
4376 let orphaned_namespace = ctx
4377 .orphaned_namespaces
4378 .iter()
4379 .filter(|envelope| envelope.body_end < node.start_byte())
4380 .max_by_key(|envelope| envelope.body_end)
4381 .map(|envelope| envelope.components.clone());
4382 let indexed_scope = ctx
4383 .recovered_sentinel_scope(node)
4384 .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
4385 .or(orphaned_namespace)
4386 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
4387 let owner_components = canonical_cpp_scope_components(&owner_target);
4388 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
4389 .any(|components| components == owner_components)
4390 {
4391 return None;
4392 }
4393 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
4394 .into_iter()
4395 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
4396 .collect::<Vec<_>>();
4397 (!scoped_candidates.is_empty()
4398 && scoped_candidates
4399 .iter()
4400 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
4401 .then_some(name)
4402}
4403
4404fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
4421 node: Node<'tree>,
4422 ctx: &ScanCtx<'_>,
4423) -> Option<Node<'tree>> {
4424 let target = physically_visible_type_target(ctx)?;
4425 let leaf = template_reference_name_node(node).unwrap_or(node);
4426 let name = node_text(leaf, ctx.source);
4427 let (components, global) = type_reference_components(node, ctx.source)?;
4428 if global || components.len() != 1 || components[0] != name {
4429 return None;
4430 }
4431 if !target.is_class()
4432 || ctx
4433 .analyzer
4434 .type_alias_provider()
4435 .is_some_and(|provider| provider.is_type_alias(target))
4436 || is_declaration_name(leaf)
4437 || name != target.identifier()
4438 || ctx.local_shadows.is_shadowed(name)
4439 || local_type_name_shadows(leaf, ctx)
4440 || !ctx.visibility.is_physically_visible(ctx.file, target)
4441 || !ctx.visibility.external_type_candidate_visible_in_context(
4442 &ctx.analyzer,
4443 ctx.file,
4444 target,
4445 leaf,
4446 )
4447 {
4448 return None;
4449 }
4450
4451 let target_components = canonical_cpp_scope_components(target);
4452 let target_package_len = target_components.len().checked_sub(1)?;
4453 let target_package = &target_components[..target_package_len];
4454 let surviving_namespace = enclosing_namespace_components(leaf, ctx.source);
4455 if !target_package.starts_with(&surviving_namespace) {
4456 return None;
4457 }
4458 let bounded_macro_type = recovered_macro_decorated_declarator_type(leaf).is_some();
4459 let (body_end, namespace_components) = if bounded_macro_type {
4460 let mut root = leaf;
4461 while let Some(parent) = root.parent() {
4462 root = parent;
4463 }
4464 ctx.orphaned_namespace_scopes
4465 .get_or_init(|| OrphanedNamespaceTypeScopeIndex::build(root, ctx.source))
4466 .scope_at(leaf.start_byte())?
4467 } else {
4468 let envelope = ctx
4469 .orphaned_namespaces
4470 .iter()
4471 .filter(|envelope| {
4472 envelope.body_end < leaf.start_byte()
4473 && envelope.components.as_slice() == target_package
4474 })
4475 .max_by_key(|envelope| envelope.body_end)?;
4476 (envelope.body_end, envelope.components.as_slice())
4477 };
4478 if namespace_components != target_package {
4479 return None;
4480 }
4481
4482 if target.source() == ctx.file
4483 && !ctx.target_declaration_ranges.iter().any(|range| {
4484 range.start_byte < body_end
4485 && range.end_byte <= body_end.saturating_add(1)
4488 })
4489 {
4490 return None;
4491 }
4492
4493 let candidates = visible_type_identifier_candidates(ctx, name)
4498 .into_iter()
4499 .filter(|candidate| {
4500 let components = canonical_cpp_scope_components(candidate);
4501 components == target_components
4502 || components
4503 .get(..components.len().saturating_sub(1))
4504 .is_some_and(|package| package == surviving_namespace)
4505 })
4506 .collect::<Vec<_>>();
4507 let target_is_visible = candidates
4508 .iter()
4509 .any(|candidate| same_visible_symbol(candidate, target));
4510 let ordinary_candidates_are_unanimous = candidates
4511 .iter()
4512 .all(|candidate| same_visible_symbol(candidate, target));
4513 if !target_is_visible || (!bounded_macro_type && !ordinary_candidates_are_unanimous) {
4514 return None;
4515 }
4516 Some(leaf)
4517}
4518
4519fn orphaned_namespace_alias_type_resolution(
4524 node: Node<'_>,
4525 ctx: &ScanCtx<'_>,
4526) -> Option<LexicalTypeResolution> {
4527 let (reference_components, global) = type_reference_components(node, ctx.source)?;
4528 if global
4529 || reference_components.len() != 1
4530 || is_declaration_name(node)
4531 || local_type_name_shadows(node, ctx)
4532 {
4533 return None;
4534 }
4535
4536 let surviving_namespace = enclosing_namespace_components(node, ctx.source);
4537 let envelope = ctx
4538 .orphaned_namespaces
4539 .iter()
4540 .filter(|envelope| {
4541 envelope.body_end < node.start_byte()
4542 && envelope.components.starts_with(&surviving_namespace)
4543 })
4544 .max_by_key(|envelope| envelope.body_end)?;
4545 let resolution = resolve_type_node_lexically_for_target(
4546 node,
4547 &ctx.analyzer,
4548 ctx.visibility,
4549 &ctx.ordinary_type_imports,
4550 ctx.file,
4551 ctx.source,
4552 &ctx.spec.target,
4553 Some(&ctx.lexical_scope_cache),
4554 Some(&envelope.components),
4555 );
4556 let LexicalTypeResolution::Resolved {
4557 ref unit,
4558 ref components,
4559 ref candidates,
4560 } = resolution
4561 else {
4562 return None;
4563 };
4564 let alias_provider = ctx.analyzer.type_alias_provider()?;
4565 if components.len() != envelope.components.len() + 1
4566 || !components.starts_with(&envelope.components)
4567 || candidates.is_empty()
4568 || candidates.iter().any(|candidate| {
4569 !alias_provider.is_type_alias(candidate)
4570 || canonical_cpp_scope_components(candidate) != *components
4571 })
4572 || !type_resolution_matches_target(node, unit, candidates, ctx)
4573 {
4574 return None;
4575 }
4576 Some(resolution)
4577}
4578
4579fn target_guided_missing_member_alias_type_leaf<'tree>(
4585 node: Node<'tree>,
4586 ctx: &ScanCtx<'_>,
4587) -> Option<Node<'tree>> {
4588 if !is_cpp_template_argument_type_leaf(node)
4589 || is_declaration_name(node)
4590 || ctx
4591 .target_declaration_ranges
4592 .iter()
4593 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
4594 || node_text(node, ctx.source) != ctx.spec.target.identifier()
4595 || local_type_name_shadows(node, ctx)
4596 || !ctx
4597 .analyzer
4598 .type_alias_provider()
4599 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4600 {
4601 return None;
4602 }
4603 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
4604 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
4605 if !owner_scope_matches
4606 && !indexed_scope.is_some_and(|scope| {
4607 indexed_scope_matches_target_name(
4608 &scope,
4609 &[ctx.spec.target.identifier().to_string()],
4610 false,
4611 &ctx.spec.target,
4612 )
4613 })
4614 {
4615 return None;
4616 }
4617 if !(ctx.visibility.external_type_candidate_visible_in_context(
4624 &ctx.analyzer,
4625 ctx.file,
4626 &ctx.spec.target,
4627 node,
4628 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
4629 {
4630 return None;
4631 }
4632 let target_visible = ctx
4633 .visibility
4634 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4635 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4636 target_visible.then_some(node)
4637}
4638
4639fn target_guided_missing_template_argument_type_leaf<'tree>(
4645 node: Node<'tree>,
4646 ctx: &ScanCtx<'_>,
4647) -> Option<Node<'tree>> {
4648 let target = &ctx.spec.target;
4649 let name = node_text(node, ctx.source);
4650 if !target.is_class()
4651 || !is_cpp_template_argument_type_leaf(node)
4652 || is_declaration_name(node)
4653 || name != target.identifier()
4654 || ctx.local_shadows.is_shadowed(name)
4655 || local_type_name_shadows(node, ctx)
4656 || !ctx.visibility.is_physically_visible(ctx.file, target)
4657 || ctx
4658 .analyzer
4659 .type_alias_provider()
4660 .is_some_and(|provider| provider.is_type_alias(target))
4661 {
4662 return None;
4663 }
4664
4665 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4666 let target_components = canonical_cpp_scope_components(target);
4667 if target_components.last().map(String::as_str) != Some(name)
4668 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4669 .any(|components| components == target_components)
4670 {
4671 return None;
4672 }
4673
4674 let candidates = visible_type_identifier_candidates(ctx, name);
4678 if candidates.is_empty()
4679 || candidates.iter().any(|candidate| {
4680 !candidate.is_class()
4681 || ctx
4682 .analyzer
4683 .type_alias_provider()
4684 .is_some_and(|provider| provider.is_type_alias(candidate))
4685 || (!same_visible_symbol(candidate, target)
4686 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
4687 .any(|components| components == canonical_cpp_scope_components(candidate)))
4688 })
4689 || !candidates
4690 .iter()
4691 .any(|candidate| same_visible_symbol(candidate, target))
4692 {
4693 return None;
4694 }
4695
4696 ctx.visibility
4699 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
4700 .then_some(node)
4701}
4702
4703fn split_macro_attribute_out_of_line_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
4706 let mut function = node;
4707 while function.kind() != "function_definition" {
4708 function = function.parent()?;
4709 }
4710 let macro_name = function_definition_name_node(function)?;
4711 if !cpp_export_macro_token(&normalize_cpp_whitespace(node_text(macro_name, ctx.source))) {
4712 return None;
4713 }
4714
4715 let declaration = function.prev_named_sibling()?;
4721 if declaration.kind() != "declaration"
4722 || !declaration.has_error()
4723 || function.start_position().row > declaration.end_position().row + 1
4724 {
4725 return None;
4726 }
4727 let mut missing_semicolon = false;
4728 let mut real_semicolon = false;
4729 for index in 0..declaration.child_count() {
4730 let Some(child) = declaration.child(index) else {
4731 continue;
4732 };
4733 if child.kind() == ";" {
4734 missing_semicolon |= child.is_missing();
4735 real_semicolon |= !child.is_missing();
4736 }
4737 }
4738 if !missing_semicolon || real_semicolon {
4739 return None;
4740 }
4741
4742 let initializer = declaration.child_by_field_name("declarator")?;
4743 if initializer.kind() != "init_declarator"
4744 || initializer
4745 .child_by_field_name("value")
4746 .is_none_or(|value| value.kind() != "argument_list")
4747 {
4748 return None;
4749 }
4750 let qualified_name = initializer
4751 .child_by_field_name("declarator")
4752 .and_then(declarator_name_node)?;
4753 let qualified = qualified_owner_components(qualified_name, ctx.source)?;
4754 let lexical_scope = enclosing_namespace_components(function, ctx.source);
4755 match ctx.visibility.resolve_type_components_lexically(
4756 &ctx.analyzer,
4757 ctx.file,
4758 &qualified.names,
4759 qualified.global,
4760 &lexical_scope,
4761 ) {
4762 LexicalTypeResolution::Resolved { unit, .. } if unit.is_class() => Some(unit),
4763 LexicalTypeResolution::Resolved { .. }
4764 | LexicalTypeResolution::Ambiguous
4765 | LexicalTypeResolution::Missing => None,
4766 }
4767}
4768
4769fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4774 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
4775}
4776
4777fn member_alias_owner_matches_reference_for(
4778 target: &CodeUnit,
4779 node: Node<'_>,
4780 ctx: &ScanCtx<'_>,
4781) -> bool {
4782 let Some(owner) = ctx.analyzer.parent_of(target) else {
4783 return false;
4784 };
4785 if !owner.is_class() {
4786 return false;
4787 }
4788 let reference_owner = ctx
4789 .class_ranges
4790 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
4791 .or_else(|| structured_enclosing_owner(node, ctx));
4792 if reference_owner.as_ref().is_some_and(|reference_owner| {
4793 ctx.visibility
4794 .same_template_owner_identity(&owner, reference_owner)
4795 }) {
4796 return true;
4797 }
4798 if split_macro_attribute_out_of_line_owner(node, ctx).is_some_and(|reference_owner| {
4799 ctx.visibility
4800 .same_template_owner_identity(&owner, &reference_owner)
4801 }) {
4802 return true;
4803 }
4804 if reference_owner.is_some_and(|reference_owner| {
4805 matches!(
4806 resolve_declaring_member_owner(
4807 &ctx.analyzer,
4808 ctx.visibility,
4809 ctx.file,
4810 &reference_owner,
4811 target.identifier(),
4812 ),
4813 EnclosingMemberOwnerResolution::Owner(declaring_owner)
4814 if ctx
4815 .visibility
4816 .same_template_owner_identity(&owner, &declaring_owner)
4817 )
4818 }) {
4819 return true;
4820 }
4821 let range = Range {
4822 start_byte: node.start_byte(),
4823 end_byte: node.end_byte(),
4824 start_line: node.start_position().row + 1,
4825 end_line: node.end_position().row + 1,
4826 };
4827 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
4828 while let Some(candidate) = indexed_enclosing {
4829 if candidate.is_class()
4830 && ctx
4831 .visibility
4832 .same_template_owner_identity(&owner, &candidate)
4833 {
4834 return true;
4835 }
4836 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
4837 }
4838 if let Some(reference_body) = malformed_recovered_class_body(node) {
4839 let mut root = node;
4840 while let Some(parent) = root.parent() {
4841 root = parent;
4842 }
4843 if ctx.analyzer.ranges(target).iter().any(|range| {
4844 root.descendant_for_byte_range(range.start_byte, range.end_byte)
4845 .and_then(malformed_recovered_class_body)
4846 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
4847 }) {
4848 return true;
4849 }
4850 }
4851 if structured_enclosing_owner(node, ctx)
4852 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
4853 {
4854 return true;
4855 }
4856 let owner_components = canonical_cpp_scope_components(&owner);
4857 if ctx
4858 .recovered_sentinel_scope(node)
4859 .is_some_and(|scope| scope == owner_components)
4860 {
4861 return true;
4862 }
4863 if matches!(
4864 cached_enclosing_lexical_scope_components_with_unresolved_owner(
4865 node,
4866 &ctx.analyzer,
4867 ctx.visibility,
4868 ctx.file,
4869 ctx.source,
4870 false,
4871 false,
4872 Some(&ctx.lexical_scope_cache),
4873 ),
4874 LexicalScopeResolution::Resolved(reference_scope)
4875 if reference_scope == owner_components
4876 ) {
4877 return true;
4878 }
4879 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
4880 else {
4881 return false;
4882 };
4883 !owner_components.is_empty() && reference_scope == owner_components
4884}
4885
4886fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
4887 loop {
4888 if node.kind() == "compound_statement"
4889 && node
4890 .parent()
4891 .is_some_and(|parent| parent.kind() == "declaration_list")
4892 && node.prev_named_sibling().is_some_and(|header| {
4893 header.kind() == "ERROR"
4894 && header.end_byte() <= node.start_byte()
4895 && error_contains_class_header(header)
4896 })
4897 {
4898 return Some(node);
4899 }
4900 node = node.parent()?;
4901 }
4902}
4903
4904fn error_contains_class_header(node: Node<'_>) -> bool {
4905 let mut pending = vec![(node, 0usize)];
4906 while let Some((current, depth)) = pending.pop() {
4907 if matches!(current.kind(), "class" | "struct" | "union") {
4908 let mut sibling = current.next_sibling();
4909 let mut saw_name = false;
4910 while let Some(candidate) = sibling {
4911 match candidate.kind() {
4912 "comment" => {}
4913 "{" | "base_class_clause" | ":" => return saw_name,
4914 "identifier" | "type_identifier" if !saw_name => saw_name = true,
4915 _ if !candidate.is_named() => {}
4916 _ => break,
4917 }
4918 sibling = candidate.next_sibling();
4919 }
4920 }
4921 if depth >= 1 {
4922 continue;
4923 }
4924 let mut cursor = current.walk();
4925 pending.extend(
4926 current
4927 .children(&mut cursor)
4928 .filter(|child| child.kind() != "compound_statement")
4929 .map(|child| (child, depth + 1)),
4930 );
4931 }
4932 false
4933}
4934
4935fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4936 has_ancestor_kind(node, "compound_statement")
4937 && ctx
4938 .visibility
4939 .external_type_candidate_guard_compatible_in_context(
4940 &ctx.analyzer,
4941 ctx.file,
4942 &ctx.spec.target,
4943 node,
4944 )
4945}
4946
4947fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4948 ctx.analyzer
4949 .type_alias_provider()
4950 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4951 && member_alias_owner_matches_reference(node, ctx)
4952}
4953
4954fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4958 if !ctx
4959 .analyzer
4960 .type_alias_provider()
4961 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4962 {
4963 return false;
4964 }
4965 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4966 return false;
4967 };
4968 let mut reference_owner = structured_enclosing_owner(node, ctx);
4969 while let Some(owner) = reference_owner {
4970 if same_logical_symbol(&target_owner, &owner) {
4971 return true;
4972 }
4973 reference_owner = ctx.analyzer.parent_of(&owner);
4974 }
4975 false
4976}
4977
4978fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4983 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4984 return false;
4985 };
4986 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4987 return false;
4988 };
4989 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4990 return false;
4991 };
4992 let candidates = ctx
4993 .visibility
4994 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4995 .filter(|candidate| {
4996 candidate.is_class()
4997 && alias_provider.is_type_alias(candidate)
4998 && !same_visible_symbol(candidate, &ctx.spec.target)
4999 })
5000 .cloned()
5001 .collect::<Vec<_>>();
5002
5003 let mut owner = Some(reference_owner);
5004 while let Some(owner_unit) = owner {
5005 if same_logical_symbol(&target_owner, &owner_unit) {
5006 return false;
5007 }
5008 if candidates.iter().any(|candidate| {
5009 ctx.analyzer
5010 .parent_of(candidate)
5011 .is_some_and(|candidate_owner| {
5012 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
5013 })
5014 && ctx
5015 .visibility
5016 .external_type_candidate_guard_compatible_in_context(
5017 &ctx.analyzer,
5018 ctx.file,
5019 candidate,
5020 node,
5021 )
5022 }) {
5023 return true;
5024 }
5025 owner = ctx.analyzer.parent_of(&owner_unit);
5026 }
5027 false
5028}
5029
5030fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5031 if cpp_active_template_type_parameter(node, ctx.spec.target.identifier(), ctx.source) {
5032 return true;
5033 }
5034 let Some(callable) = nearest_callable_scope(node) else {
5035 return false;
5036 };
5037 let mut root_callable = callable;
5038 let mut ancestor = callable.parent();
5039 while let Some(current) = ancestor {
5040 if matches!(current.kind(), "function_definition" | "lambda_expression") {
5041 root_callable = current;
5042 }
5043 ancestor = current.parent();
5044 }
5045
5046 let mut stack = vec![root_callable];
5047 while let Some(current) = stack.pop() {
5048 if current.start_byte() >= node.start_byte() {
5049 continue;
5050 }
5051 if let Some(name) = local_type_name_declaration_node(current)
5052 && node_text(name, ctx.source) == ctx.spec.target.identifier()
5053 && nearest_callable_scope(current).is_some_and(|owner| {
5054 !is_malformed_wrapper_function_definition(owner)
5055 && owner.start_byte() <= callable.start_byte()
5056 && callable.end_byte() <= owner.end_byte()
5057 })
5058 && local_alias_scope_contains_node(current, node)
5059 {
5060 return true;
5061 }
5062 let mut cursor = current.walk();
5063 stack.extend(current.named_children(&mut cursor));
5064 }
5065 false
5066}
5067
5068fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
5069 local_type_alias_name_node(node).or_else(|| match node.kind() {
5070 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
5071 .child_by_field_name("name")
5072 .filter(|name| is_declaration_name(*name)),
5073 _ => None,
5074 })
5075}
5076
5077fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
5078 loop {
5079 if matches!(node.kind(), "function_definition" | "lambda_expression") {
5080 return Some(node);
5081 }
5082 node = node.parent()?;
5083 }
5084}
5085
5086fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
5087 match node.kind() {
5088 "alias_declaration" => node.child_by_field_name("name"),
5089 "type_definition" => node
5090 .child_by_field_name("declarator")
5091 .and_then(declarator_name_node),
5092 _ => None,
5093 }
5094}
5095
5096fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
5097 let mut current = alias.parent();
5098 while let Some(parent) = current {
5099 if matches!(
5100 parent.kind(),
5101 "class_specifier" | "struct_specifier" | "union_specifier"
5102 ) {
5103 return false;
5104 }
5105 if parent.kind() == "compound_statement" {
5106 return parent.start_byte() <= node.start_byte()
5107 && node.end_byte() <= parent.end_byte();
5108 }
5109 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
5110 let Some(body) = parent.child_by_field_name("body") else {
5111 return false;
5112 };
5113 return node_is_within(body, alias) && node_is_within(body, node);
5114 }
5115 current = parent.parent();
5116 }
5117 false
5118}
5119
5120fn target_guided_missing_declaration_type_leaf<'tree>(
5121 node: Node<'tree>,
5122 ctx: &ScanCtx<'_>,
5123) -> Option<Node<'tree>> {
5124 if is_declaration_name(node) {
5125 return None;
5126 }
5127 let component_nodes = cpp_name_component_nodes(node)?;
5128 let name_node = component_nodes.last().copied()?;
5129 let name = node_text(name_node, ctx.source);
5130 if name != ctx.spec.target.identifier() {
5131 return None;
5132 }
5133 let inside_target_declaration = ctx
5134 .target_declaration_ranges
5135 .iter()
5136 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
5137 if !inside_target_declaration
5138 && !ctx.visibility.external_type_candidate_visible_in_context(
5139 &ctx.analyzer,
5140 ctx.file,
5141 &ctx.spec.target,
5142 node,
5143 )
5144 {
5145 return None;
5146 }
5147 let components = component_nodes
5148 .iter()
5149 .map(|component| node_text(*component, ctx.source).to_string())
5150 .collect::<Vec<_>>();
5151 let local_alias_shadow = local_type_name_shadows(node, ctx);
5152 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
5153 let indexed_alias_owner = ctx
5154 .analyzer
5155 .type_alias_provider()
5156 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
5157 && member_alias_owner_matches_reference(node, ctx);
5158 let target_alias_self_reference = inside_target_declaration
5159 && ctx
5160 .analyzer
5161 .type_alias_provider()
5162 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
5163 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
5164 &ctx.analyzer,
5165 ctx.file,
5166 &ctx.spec.target,
5167 node,
5168 ) || member_alias_complete_class_context(node, ctx);
5169 if !target_alias_self_reference
5170 && !local_alias_shadow
5171 && member_alias_visible
5172 && (structured_alias_owner || indexed_alias_owner)
5173 {
5174 return Some(node);
5175 }
5176 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5177 let declaration = nearest_declaration_type_context(node)?;
5178 let exact_scope_match = indexed_scope_matches_target_name(
5179 &indexed_scope,
5180 &components,
5181 is_globally_qualified_cpp_name(node),
5182 &ctx.spec.target,
5183 );
5184 let candidates = visible_type_identifier_candidates(ctx, name);
5185 let unique_visible_target = !candidates.is_empty()
5186 && candidates
5187 .iter()
5188 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
5189 if matches!(declaration.kind(), "field_declaration" | "declaration") {
5190 let parser_lost_declaration_scope =
5191 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
5192 && unique_visible_target;
5193 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
5194 }
5195 let lost_namespace_parameter_context =
5196 matches!(
5197 declaration.kind(),
5198 "parameter_declaration" | "optional_parameter_declaration"
5199 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
5200 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
5201 return Some(node);
5202 }
5203 None
5204}
5205
5206fn target_guided_missing_alias_rhs_type_leaf<'tree>(
5207 node: Node<'tree>,
5208 ctx: &ScanCtx<'_>,
5209) -> Option<Node<'tree>> {
5210 let mut stack = vec![node];
5211 while let Some(candidate) = stack.pop() {
5212 if candidate.kind() == "type_identifier"
5213 && !is_declaration_name(candidate)
5214 && matches!(
5215 candidate.parent().map(|parent| parent.kind()),
5216 Some("template_type")
5217 )
5218 {
5219 let mut current = candidate.parent();
5220 let mut saw_qualified = false;
5221 let mut saw_dependent = false;
5222 let mut saw_type_descriptor = false;
5223 let mut saw_alias_declaration = false;
5224 while let Some(ancestor) = current {
5225 match ancestor.kind() {
5226 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
5227 "dependent_type" => saw_dependent = true,
5228 "type_descriptor" => saw_type_descriptor = true,
5229 "alias_declaration" => {
5230 saw_alias_declaration = true;
5231 break;
5232 }
5233 "template_type"
5234 | "template_argument_list"
5235 | "typename"
5236 | "template_declaration" => {}
5237 _ => {}
5238 }
5239 current = ancestor.parent();
5240 }
5241 let name = node_text(candidate, ctx.source);
5242 let visible_candidates = visible_type_identifier_candidates(ctx, name);
5243 let canonical_alias_target = visible_candidates
5244 .iter()
5245 .filter_map(|alias| ctx.visibility.alias_target(alias))
5246 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
5247 let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
5248 &ctx.analyzer,
5249 ctx.file,
5250 name,
5251 &ctx.spec.target,
5252 ) || canonical_alias_target;
5253 if saw_qualified
5254 && saw_dependent
5255 && saw_type_descriptor
5256 && saw_alias_declaration
5257 && alias_resolves
5258 && ctx.visibility.external_type_candidate_visible_in_context(
5259 &ctx.analyzer,
5260 ctx.file,
5261 &ctx.spec.target,
5262 candidate,
5263 )
5264 {
5265 return Some(candidate);
5266 }
5267 }
5268 for index in (0..candidate.named_child_count()).rev() {
5269 if let Some(child) = candidate.named_child(index) {
5270 stack.push(child);
5271 }
5272 }
5273 }
5274 None
5275}
5276
5277fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
5278 let mut current = Some(node);
5279 while let Some(ancestor) = current {
5280 if matches!(
5281 ancestor.kind(),
5282 "field_declaration"
5283 | "parameter_declaration"
5284 | "optional_parameter_declaration"
5285 | "declaration"
5286 | "type_descriptor"
5287 ) {
5288 let contains_type = ancestor
5289 .child_by_field_name("type")
5290 .is_some_and(|type_node| {
5291 type_node.start_byte() <= node.start_byte()
5292 && node.end_byte() <= type_node.end_byte()
5293 });
5294 if contains_type
5295 && !(ancestor.kind() == "type_descriptor"
5296 && is_cpp_template_argument_type_leaf(node))
5297 {
5298 return Some(ancestor);
5299 }
5300 if ancestor.kind() == "type_descriptor"
5301 && ancestor.parent().is_some_and(|parent| {
5302 matches!(
5303 parent.kind(),
5304 "cast_expression"
5305 | "new_expression"
5306 | "sizeof_expression"
5307 | "alignof_expression"
5308 | "typeid_expression"
5309 )
5310 })
5311 {
5312 return Some(ancestor);
5313 }
5314 }
5315 if matches!(
5316 ancestor.kind(),
5317 "compound_statement"
5318 | "translation_unit"
5319 | "namespace_definition"
5320 | "alias_declaration"
5321 | "type_definition"
5322 | "base_class_clause"
5323 ) {
5324 return None;
5325 }
5326 current = ancestor.parent();
5327 }
5328 None
5329}
5330
5331fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
5332 let mut candidates = Vec::new();
5333 for candidate in ctx
5334 .visibility
5335 .visible_identifier_candidates(ctx.file, name)
5336 .filter(|candidate| {
5337 candidate.is_class()
5338 || ctx
5339 .analyzer
5340 .type_alias_provider()
5341 .is_some_and(|provider| provider.is_type_alias(candidate))
5342 })
5343 {
5344 if !candidates
5345 .iter()
5346 .any(|existing| same_logical_symbol(existing, candidate))
5347 {
5348 candidates.push(candidate.clone());
5349 }
5350 }
5351 candidates
5352}
5353
5354fn target_guided_static_cast_alias_type_descriptor<'tree>(
5359 node: Node<'tree>,
5360 ctx: &ScanCtx<'_>,
5361) -> Option<Node<'tree>> {
5362 if node.kind() != "type_descriptor" {
5363 return None;
5364 }
5365 let argument_list = node.parent().filter(|parent| {
5366 parent.kind() == "template_argument_list"
5367 && parent.named_child_count() == 1
5368 && parent.named_child(0) == Some(node)
5369 })?;
5370 let template = argument_list.parent().filter(|parent| {
5371 parent.kind() == "template_function"
5372 && parent.child_by_field_name("arguments") == Some(argument_list)
5373 })?;
5374 let name = template.child_by_field_name("name")?;
5375 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
5376 return None;
5377 }
5378 let target = &ctx.spec.target;
5379 if node_text(node, ctx.source) != target.identifier()
5380 || !ctx
5381 .analyzer
5382 .type_alias_provider()
5383 .is_some_and(|provider| provider.is_type_alias(target))
5384 || !ctx.visibility.is_physically_visible(ctx.file, target)
5385 || !ctx.visibility.external_type_candidate_visible_in_context(
5386 &ctx.analyzer,
5387 ctx.file,
5388 target,
5389 node,
5390 )
5391 {
5392 return None;
5393 }
5394
5395 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
5396 let target_scope = canonical_cpp_scope_components(target);
5397 let name_components = [target.identifier().to_string()];
5398 if !lexical_component_tiers(&name_components, false, &indexed_scope)
5399 .any(|components| components == target_scope)
5400 {
5401 return None;
5402 }
5403
5404 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
5405 if !candidates
5406 .iter()
5407 .any(|candidate| same_visible_symbol(candidate, target))
5408 {
5409 return None;
5410 }
5411 if candidates.iter().any(|candidate| {
5412 !same_visible_symbol(candidate, target)
5413 && lexical_component_tiers(&name_components, false, &indexed_scope)
5414 .any(|components| components == canonical_cpp_scope_components(candidate))
5415 }) {
5416 return None;
5417 }
5418 Some(node)
5419}
5420
5421fn indexed_scope_matches_target_name(
5422 indexed_scope: &[String],
5423 components: &[String],
5424 global: bool,
5425 target: &CodeUnit,
5426) -> bool {
5427 let target_name = cpp_name_for(target);
5428 lexical_component_tiers(components, global, indexed_scope)
5429 .any(|qualified| qualified.join("::") == target_name)
5430}
5431
5432fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
5433 if target.package_name().is_empty() {
5434 return false;
5435 }
5436 if indexed_scope.len() <= 1 {
5437 return true;
5438 }
5439 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5440 brokk_bifrost_core::analyzer::Language::Cpp,
5441 &cpp_name_for(target),
5442 );
5443 target_scope.pop();
5444 (1..indexed_scope.len())
5445 .rev()
5446 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
5447}
5448
5449fn indexed_enclosing_lexical_scope(
5450 analyzer: &CppGraphSource<'_>,
5451 file: &ProjectFile,
5452 node: Node<'_>,
5453) -> Option<Vec<String>> {
5454 let range = Range {
5455 start_byte: node.start_byte(),
5456 end_byte: node.end_byte(),
5457 start_line: node.start_position().row,
5458 end_line: node.end_position().row,
5459 };
5460 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
5461 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5462 brokk_bifrost_core::analyzer::Language::Cpp,
5463 &cpp_name_for(&enclosing),
5464 );
5465 if !enclosing.is_class() && !enclosing.is_module() {
5466 components.pop();
5467 }
5468 Some(components)
5469}
5470
5471fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
5472 if node.kind() != "qualified_identifier" {
5473 return None;
5474 }
5475 let mut stack = vec![node];
5476 while let Some(current) = stack.pop() {
5477 if current.kind() != "qualified_identifier" {
5478 continue;
5479 }
5480 if let Some(scope) = current.child_by_field_name("scope") {
5481 let text = qualified_scope_text(scope, ctx.source);
5482 if name_mentions(&text, &ctx.spec.member_name) {
5483 return Some(scope);
5484 }
5485 }
5486 let mut cursor = current.walk();
5487 for child in current.named_children(&mut cursor) {
5488 if child.kind() == "qualified_identifier" {
5489 stack.push(child);
5490 }
5491 }
5492 }
5493 None
5494}
5495
5496fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
5497 let mut parts = vec![node_text(scope, source).to_string()];
5498 let mut current = scope.parent();
5499 while let Some(qualified) = current {
5500 let Some(parent) = qualified.parent() else {
5501 break;
5502 };
5503 if parent.kind() != "qualified_identifier"
5504 || parent.child_by_field_name("name") != Some(qualified)
5505 {
5506 break;
5507 }
5508 if let Some(outer_scope) = parent.child_by_field_name("scope") {
5509 parts.push(node_text(outer_scope, source).to_string());
5510 }
5511 current = Some(parent);
5512 }
5513 parts.reverse();
5514 parts.join("::")
5515}
5516
5517fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5518 if node.kind() == "using_declaration" {
5519 maybe_record_using_callable_hit(node, ctx);
5520 return;
5521 }
5522 if has_ancestor_kind(node, "using_declaration") {
5523 return;
5524 }
5525 if node.kind() == "function_definition" {
5526 return;
5527 }
5528 if !matches!(
5529 node.kind(),
5530 "call_expression"
5531 | "new_expression"
5532 | "compound_literal_expression"
5533 | "declaration"
5534 | "field_initializer"
5535 ) {
5536 return;
5537 }
5538 let Some(owner) = ctx.spec.owner.as_ref() else {
5539 return;
5540 };
5541 if node.kind() == "field_initializer" {
5542 if !field_initializer_constructs_target(node, ctx, owner) {
5543 return;
5544 }
5545 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5546 match ctx
5547 .visibility
5548 .call_arity_evidence(ctx.file, node, ctx.source)
5549 .accepts(expected)
5550 {
5551 Some(true) => {}
5552 Some(false) => return,
5553 None => {
5554 push_unproven_hit(node, ctx);
5555 return;
5556 }
5557 }
5558 }
5559 push_hit(node, ctx);
5560 return;
5561 }
5562 if node.kind() == "declaration" {
5563 if declaration_is_object_construction_candidate(node, ctx)
5564 && declaration_mentions_type(node, ctx, owner)
5565 && ctx
5566 .spec
5567 .callable_arity_at(node.start_byte())
5568 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
5569 {
5570 push_hit(node, ctx);
5571 }
5572 return;
5573 }
5574 let Some(type_node) = constructor_type_node(node) else {
5575 return;
5576 };
5577 let hit_node = function_terminal_node(type_node);
5578 let text = node_text(type_node, ctx.source);
5579 if !name_mentions(text, &ctx.spec.member_name) {
5580 return;
5581 }
5582 *ctx.raw_match_count += 1;
5583 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5584 match ctx
5585 .visibility
5586 .call_arity_evidence(ctx.file, node, ctx.source)
5587 .accepts(expected)
5588 {
5589 Some(true) => {}
5590 Some(false) => return,
5591 None => {
5592 push_unproven_hit(hit_node, ctx);
5593 return;
5594 }
5595 }
5596 }
5597 let structured_resolution = resolve_type_node_lexically_for_target(
5598 type_node,
5599 &ctx.analyzer,
5600 ctx.visibility,
5601 &ctx.ordinary_type_imports,
5602 ctx.file,
5603 ctx.source,
5604 owner,
5605 Some(&ctx.lexical_scope_cache),
5606 ctx.recovered_sentinel_scope(type_node).as_deref(),
5607 );
5608 let structurally_resolves = matches!(
5609 &structured_resolution,
5610 LexicalTypeResolution::Resolved {
5611 unit, candidates, ..
5612 } if same_visible_symbol(unit, owner)
5613 || candidates
5614 .iter()
5615 .any(|candidate| same_visible_symbol(candidate, owner))
5616 );
5617 if structurally_resolves
5618 || matches!(structured_resolution, LexicalTypeResolution::Missing)
5619 && ctx
5620 .visibility
5621 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
5622 {
5623 push_hit(hit_node, ctx);
5624 } else {
5625 push_unproven_hit(hit_node, ctx);
5626 }
5627}
5628
5629fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5630 if node.kind() == "function_definition" {
5631 maybe_record_free_function_definition_hit(node, ctx);
5632 return;
5633 }
5634 if node.kind() == "identifier" {
5635 maybe_record_free_function_value_reference(node, ctx);
5636 return;
5637 }
5638 if node.kind() != "call_expression" {
5639 return;
5640 }
5641 let Some(function) = node
5642 .child_by_field_name("function")
5643 .or_else(|| node.named_child(0))
5644 else {
5645 return;
5646 };
5647 let text = node_text(function, ctx.source);
5648 if !name_matches_callable(text, &ctx.spec.member_name) {
5649 return;
5650 }
5651 *ctx.raw_match_count += 1;
5652 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5653 match ctx
5654 .visibility
5655 .call_arity_evidence(ctx.file, node, ctx.source)
5656 .accepts(expected)
5657 {
5658 Some(true) => {}
5659 Some(false) => return,
5660 None => {
5661 if !bare_name_binds_only_target(node, function, text, ctx) {
5668 push_unproven_hit(function_terminal_node(function), ctx);
5669 return;
5670 }
5671 }
5672 }
5673 }
5674 if matches!(function.kind(), "identifier" | "template_function") {
5675 let terminal = function_terminal_node(function);
5676 let name = node_text(terminal, ctx.source);
5677 if ctx.local_shadows.is_shadowed(name) {
5678 return;
5679 }
5680 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
5681 && !matches!(
5682 resolve_declaring_member_owner(
5683 &ctx.analyzer,
5684 ctx.visibility,
5685 ctx.file,
5686 &enclosing_owner,
5687 name,
5688 ),
5689 EnclosingMemberOwnerResolution::Missing
5690 )
5691 {
5692 return;
5693 }
5694 match resolve_bare_call_target(
5695 node,
5696 function,
5697 &ctx.analyzer,
5698 ctx.visibility,
5699 &ctx.ordinary_type_imports,
5700 ctx.file,
5701 ctx.source,
5702 ) {
5703 BareCallTargetResolution::FreeFunctions(units)
5704 if units
5705 .iter()
5706 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5707 {
5708 if free_function_call_may_target(node, text, ctx) {
5709 let recursive = enclosing_context(terminal, ctx)
5710 .enclosing
5711 .as_ref()
5712 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
5713 if recursive {
5714 push_recursive_reference_hit(terminal, ctx);
5715 } else {
5716 push_hit(terminal, ctx);
5717 }
5718 }
5719 }
5720 BareCallTargetResolution::UnprovenFreeFunctions(units)
5721 if units
5722 .iter()
5723 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
5724 {
5725 push_unproven_hit(terminal, ctx);
5726 }
5727 BareCallTargetResolution::FreeFunctions(_)
5728 | BareCallTargetResolution::UnprovenFreeFunctions(_)
5729 | BareCallTargetResolution::Type(_)
5730 | BareCallTargetResolution::CallableShadow => {}
5731 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
5732 push_unproven_hit(terminal, ctx);
5733 }
5734 }
5735 return;
5736 }
5737 if !free_function_call_may_target(node, text, ctx) {
5738 return;
5739 }
5740 if ctx.visibility.contains_named_symbol(
5741 ctx.file,
5742 text,
5743 TargetKind::FreeFunction,
5744 &ctx.spec.target,
5745 ) {
5746 push_hit(function_terminal_node(function), ctx);
5747 } else if ctx.visibility.resolve_known_non_target(
5748 ctx.file,
5749 text,
5750 TargetKind::FreeFunction,
5751 &ctx.spec.target,
5752 ) {
5753 } else {
5756 push_unproven_hit(function_terminal_node(function), ctx);
5757 }
5758}
5759
5760fn bare_name_binds_only_target(
5769 call: Node<'_>,
5770 function: Node<'_>,
5771 text: &str,
5772 ctx: &ScanCtx<'_>,
5773) -> bool {
5774 if !matches!(function.kind(), "identifier" | "template_function") {
5775 return false;
5776 }
5777 let mut candidates = ctx
5778 .visibility
5779 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5780 candidates.retain(|candidate| {
5781 ctx.visibility
5782 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5783 });
5784 dedupe_callable_candidates(&mut candidates);
5785 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
5786}
5787
5788fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
5789 if ctx.spec.param_types.is_none() {
5790 return true;
5791 }
5792 let mut candidates = ctx
5793 .visibility
5794 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
5795 candidates.retain(|candidate| {
5796 ctx.visibility
5797 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
5798 });
5799 let Some(arity) = ctx
5800 .visibility
5801 .call_arity_evidence(ctx.file, call, ctx.source)
5802 .exact()
5803 else {
5804 return true;
5805 };
5806 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5807 if candidates.is_empty()
5808 || !candidates
5809 .iter()
5810 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5811 {
5812 return true;
5813 }
5814 let arg_types = call_argument_types(call, ctx);
5815 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
5816 candidates,
5817 &arg_types,
5818 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
5819 &|name| ctx.visibility.resolve_type(ctx.file, name),
5820 &|left, right| same_visible_symbol(left, right),
5821 );
5822 filtered
5823 .iter()
5824 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5825}
5826
5827fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
5828 let Some(args) = call
5829 .child_by_field_name("arguments")
5830 .or_else(|| call.child_by_field_name("parameters"))
5831 .or_else(|| call.child_by_field_name("value"))
5832 else {
5833 return Vec::new();
5834 };
5835 argument_children(args)
5836 .map(|arg| expression_arg_type(arg, ctx))
5837 .collect()
5838}
5839
5840fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
5841 match node.kind() {
5842 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
5843 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
5844 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
5845 literal
5846 }),
5847 "identifier" => ctx
5848 .bindings
5849 .resolve_symbol(node_text(node, ctx.source))
5850 .as_precise()
5851 .and_then(|bindings| bindings.iter().find_map(CppScanBinding::as_arg_type)),
5852 "parenthesized_expression" => node
5853 .child_by_field_name("argument")
5854 .or_else(|| node.named_child(0))
5855 .and_then(|inner| expression_arg_type(inner, ctx)),
5856 "pointer_expression" => {
5857 let delta = match node.child_by_field_name("operator")?.kind() {
5858 "&" => 1,
5859 "*" => -1,
5860 _ => return None,
5861 };
5862 let inner = node
5863 .child_by_field_name("argument")
5864 .or_else(|| node.named_child(0))?;
5865 let mut arg_type = expression_arg_type(inner, ctx)?;
5866 arg_type.indirection += delta;
5867 Some(arg_type)
5868 }
5869 _ => None,
5870 }
5871}
5872
5873fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5878 let text = node_text(node, ctx.source);
5879 if !name_matches_callable(text, &ctx.spec.member_name) {
5880 return;
5881 }
5882 if is_declaration_name(node) || is_call_callee_node(node) {
5883 return;
5884 }
5885 *ctx.raw_match_count += 1;
5886 if ctx.visibility.contains_named_symbol(
5887 ctx.file,
5888 text,
5889 TargetKind::FreeFunction,
5890 &ctx.spec.target,
5891 ) {
5892 push_hit(node, ctx);
5893 } else if ctx.visibility.resolve_known_non_target(
5894 ctx.file,
5895 text,
5896 TargetKind::FreeFunction,
5897 &ctx.spec.target,
5898 ) {
5899 } else {
5901 push_unproven_hit(node, ctx);
5902 }
5903}
5904
5905fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5906 let Some(function) = function_definition_name_node(node) else {
5907 return;
5908 };
5909 let text = node_text(function, ctx.source);
5910 if !name_matches_callable(text, &ctx.spec.member_name) {
5911 return;
5912 }
5913 *ctx.raw_match_count += 1;
5914 if !function_definition_signature_matches_target(node, ctx) {
5915 return;
5916 }
5917 if definition_name_candidates(function, ctx)
5918 .iter()
5919 .any(|name| {
5920 ctx.visibility.contains_named_symbol(
5921 ctx.file,
5922 name,
5923 TargetKind::FreeFunction,
5924 &ctx.spec.target,
5925 )
5926 })
5927 {
5928 push_definition_hit(function, ctx);
5929 } else if definition_name_candidates(function, ctx)
5930 .iter()
5931 .any(|name| {
5932 ctx.visibility.resolve_known_non_target(
5933 ctx.file,
5934 name,
5935 TargetKind::FreeFunction,
5936 &ctx.spec.target,
5937 )
5938 })
5939 {
5940 } else {
5942 push_unproven_definition_hit(function, ctx);
5943 }
5944}
5945
5946fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5947 if node.kind() == "using_declaration" {
5948 maybe_record_using_callable_hit(node, ctx);
5949 return;
5950 }
5951 if has_ancestor_kind(node, "using_declaration") {
5952 return;
5953 }
5954 if node.kind() == "function_definition" {
5955 maybe_record_method_definition_hit(node, ctx);
5956 return;
5957 }
5958 if is_declaration_name(node) {
5959 return;
5960 }
5961 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
5962 maybe_record_qualified_method_value_hit(node, member, ctx);
5963 return;
5964 }
5965 if let Some(value) = qualified_callable_value(node) {
5966 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
5967 return;
5968 }
5969 if let Some(call) = recovered_relational_template_member_call(node) {
5970 maybe_record_recovered_relational_template_method_hit(call, ctx);
5971 return;
5972 }
5973 if node.kind() != "call_expression" {
5974 return;
5975 }
5976 if let Some((receiver, operator)) = explicit_operator_call(node) {
5977 let text = node_text(operator, ctx.source);
5978 if !name_matches_callable(text, &ctx.spec.member_name) {
5979 return;
5980 }
5981 *ctx.raw_match_count += 1;
5982 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5983 match ctx
5984 .visibility
5985 .call_arity_evidence(ctx.file, node, ctx.source)
5986 .accepts(expected)
5987 {
5988 Some(true) => {}
5989 Some(false) => return,
5990 None => {
5991 push_unproven_hit(operator, ctx);
5992 return;
5993 }
5994 }
5995 }
5996 match explicit_receiver_target_resolution(
5997 receiver,
5998 ctx.visibility
5999 .call_arity_evidence(ctx.file, node, ctx.source)
6000 .exact(),
6001 ctx,
6002 ) {
6003 MethodReceiverTargetResolution::Target if receiver_is_self_like(receiver, ctx.file) => {
6004 push_self_receiver_hit(operator, ctx);
6005 }
6006 MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
6007 MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
6008 MethodReceiverTargetResolution::NonTarget
6009 | MethodReceiverTargetResolution::Ambiguous => {}
6010 }
6011 return;
6012 }
6013 let Some(function) = node
6014 .child_by_field_name("function")
6015 .or_else(|| node.named_child(0))
6016 else {
6017 return;
6018 };
6019 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6020 return;
6021 }
6022 if function.kind() == "identifier"
6023 && ctx
6024 .local_shadows
6025 .is_shadowed(node_text(function, ctx.source))
6026 {
6027 return;
6028 }
6029 *ctx.raw_match_count += 1;
6030 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
6031 match ctx
6032 .visibility
6033 .call_arity_evidence(ctx.file, node, ctx.source)
6034 .accepts(expected)
6035 {
6036 Some(true) => {}
6037 Some(false) => return,
6038 None => {
6039 push_unproven_hit(function_terminal_node(function), ctx);
6040 return;
6041 }
6042 }
6043 }
6044 if !method_call_may_target(node, ctx) {
6045 return;
6046 }
6047 if is_structurally_qualified(function) {
6048 match qualified_owner_resolution(function, ctx) {
6049 QualifiedOwnerResolution::Target => {
6050 push_hit(function_terminal_node(function), ctx);
6051 }
6052 QualifiedOwnerResolution::NonTarget => {}
6053 QualifiedOwnerResolution::Unresolved => {
6054 push_unproven_hit(function_terminal_node(function), ctx);
6055 }
6056 }
6057 return;
6058 }
6059 match call_function_target_resolution(function, ctx) {
6060 MethodReceiverTargetResolution::Target
6061 if call_function_has_direct_self_receiver(function, ctx.file) =>
6062 {
6063 push_self_receiver_hit(function_terminal_node(function), ctx);
6064 }
6065 MethodReceiverTargetResolution::Target => {
6066 push_hit(function_terminal_node(function), ctx);
6067 }
6068 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
6069 MethodReceiverTargetResolution::Missing
6075 if inherited_target_owner_context(function, ctx) =>
6076 {
6077 push_hit(function_terminal_node(function), ctx);
6078 }
6079 MethodReceiverTargetResolution::Missing
6080 if same_owner_context(function, ctx)
6081 || out_of_line_target_owner_context(function, ctx) =>
6082 {
6083 push_self_receiver_hit(function_terminal_node(function), ctx);
6084 }
6085 MethodReceiverTargetResolution::Missing
6086 if function.kind() == "identifier"
6087 && resolves_to_lexical_free_function(function, ctx) =>
6088 {
6089 }
6092 MethodReceiverTargetResolution::Missing
6093 if !receiver_has_known_non_target(function, ctx)
6094 && !known_non_target_owner_context(function, ctx) =>
6095 {
6096 push_unproven_hit(function_terminal_node(function), ctx);
6097 }
6098 MethodReceiverTargetResolution::Missing => {}
6099 }
6100}
6101
6102fn maybe_record_recovered_relational_template_method_hit(
6103 call: RecoveredRelationalTemplateMemberCall<'_>,
6104 ctx: &mut ScanCtx<'_>,
6105) {
6106 if !callable_node_matches(call.member, &ctx.spec.member_name, ctx.source) {
6107 return;
6108 }
6109 *ctx.raw_match_count += 1;
6110 if !ctx
6111 .visibility
6112 .callable_is_template_declaration(&ctx.analyzer, &ctx.spec.target)
6113 || ctx
6114 .spec
6115 .callable_arity_at(call.member.start_byte())
6116 .is_some_and(|arity| !arity.accepts(call.arity))
6117 {
6118 return;
6119 }
6120 match explicit_receiver_target_resolution(call.receiver, Some(call.arity), ctx) {
6121 MethodReceiverTargetResolution::Target
6122 if receiver_is_self_like(call.receiver, ctx.file) =>
6123 {
6124 push_self_receiver_hit(call.member, ctx);
6125 }
6126 MethodReceiverTargetResolution::Target => push_hit(call.member, ctx),
6127 MethodReceiverTargetResolution::Missing => push_unproven_hit(call.member, ctx),
6128 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
6129 }
6130}
6131
6132fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
6133 if node.kind() != "qualified_identifier" {
6134 return None;
6135 }
6136 let parameter = node
6137 .parent()
6138 .filter(|parent| parent.kind() == "parameter_declaration")?;
6139 let parameter_declarator = parameter.child_by_field_name("declarator")?;
6140 if parameter.child_by_field_name("type") != Some(node)
6145 || parameter_declarator.kind() != "abstract_function_declarator"
6146 {
6147 return None;
6148 }
6149 let parameter_list = parameter
6150 .parent()
6151 .filter(|parent| parent.kind() == "parameter_list")?;
6152 if parameter_list.named_child_count() != 1 {
6153 return None;
6154 }
6155 let function_declarator = parameter_list
6156 .parent()
6157 .filter(|parent| parent.kind() == "function_declarator")?;
6158 if function_declarator
6159 .child_by_field_name("declarator")
6160 .is_none_or(|declarator| declarator.kind() != "identifier")
6161 || function_declarator
6162 .parent()
6163 .is_none_or(|parent| parent.kind() != "declaration")
6164 {
6165 return None;
6166 }
6167 node.child_by_field_name("name")
6168}
6169
6170fn maybe_record_using_callable_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6171 let Some(imported) = ordinary_using_declaration_type_node(node) else {
6172 return;
6173 };
6174 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
6175 return;
6176 }
6177 let Some(target_owner) = ctx.spec.owner.as_ref() else {
6178 return;
6179 };
6180 *ctx.raw_match_count += 1;
6181 let owner_resolution = qualified_owner_components(imported, ctx.source)
6182 .map(|qualified| {
6183 let lexical_scope = match enclosing_lexical_scope_components(
6184 imported,
6185 &ctx.analyzer,
6186 ctx.visibility,
6187 ctx.file,
6188 ctx.source,
6189 ) {
6190 LexicalScopeResolution::Resolved(scope) => scope,
6191 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
6192 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
6193 };
6194 ctx.visibility.resolve_type_components_lexically(
6195 &ctx.analyzer,
6196 ctx.file,
6197 &qualified.names,
6198 qualified.global,
6199 &lexical_scope,
6200 )
6201 })
6202 .unwrap_or(LexicalTypeResolution::Missing);
6203 let matches_target_owner = matches!(
6204 owner_resolution,
6205 LexicalTypeResolution::Resolved {
6206 ref unit,
6207 ref candidates,
6208 ..
6209 } if same_visible_symbol(unit, target_owner)
6210 || candidates
6211 .iter()
6212 .any(|candidate| same_visible_symbol(candidate, target_owner))
6213 );
6214 if !matches_target_owner {
6215 match owner_resolution {
6216 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
6217 push_unproven_hit(imported, ctx);
6218 }
6219 LexicalTypeResolution::Resolved { .. } => {}
6220 }
6221 return;
6222 }
6223 match ctx.visibility.visible_member_for_owner_name(
6224 ctx.file,
6225 target_owner,
6226 &ctx.spec.member_name,
6227 ) {
6228 VisibleMemberResolution::Callable(candidates)
6229 if candidates.iter().all(|candidate| {
6230 ctx.target_group.contains(candidate)
6231 || ctx
6232 .target_group
6233 .iter()
6234 .any(|target| same_visible_symbol(candidate, target))
6235 }) =>
6236 {
6237 push_hit(imported, ctx);
6238 }
6239 VisibleMemberResolution::NonCallable => {}
6240 VisibleMemberResolution::Callable(_)
6241 | VisibleMemberResolution::AmbiguousKind
6242 | VisibleMemberResolution::Missing => {
6243 push_unproven_hit(imported, ctx);
6244 }
6245 }
6246}
6247
6248fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6249 let name = node_text(node, ctx.source);
6250 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
6251 let key = (namespace.clone(), name.to_string());
6252 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
6253 return resolved;
6254 }
6255 let resolved = ctx
6256 .visibility
6257 .visible_identifier_candidates(ctx.file, name)
6258 .any(|unit| {
6259 unit.is_function()
6260 && type_owner_of(&ctx.analyzer, unit).is_none()
6261 && unit.package_name() == namespace
6262 });
6263 ctx.lexical_free_function_cache
6264 .borrow_mut()
6265 .insert(key, resolved);
6266 resolved
6267}
6268
6269fn maybe_record_qualified_method_value_hit(
6270 qualified: Node<'_>,
6271 member: Node<'_>,
6272 ctx: &mut ScanCtx<'_>,
6273) {
6274 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
6275 return;
6276 }
6277 *ctx.raw_match_count += 1;
6278 let resolution =
6279 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
6280 match resolution {
6281 LexicalCallableValueResolution::Type(resolved_owner) => {
6282 let Some(owner) = ctx.spec.owner.as_ref() else {
6283 push_unproven_hit(member, ctx);
6284 return;
6285 };
6286 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
6287 if same_visible_symbol(&resolved_owner, owner) {
6288 push_unproven_hit(member, ctx);
6289 }
6290 return;
6291 }
6292 match ctx.visibility.visible_member_for_owner_name(
6293 ctx.file,
6294 owner,
6295 &ctx.spec.member_name,
6296 ) {
6297 VisibleMemberResolution::Callable(candidates)
6298 if candidates.iter().all(|candidate| {
6299 ctx.target_group.contains(candidate)
6300 || ctx
6301 .target_group
6302 .iter()
6303 .any(|target| same_visible_symbol(candidate, target))
6304 }) =>
6305 {
6306 push_hit(member, ctx);
6309 }
6310 VisibleMemberResolution::NonCallable => {}
6311 VisibleMemberResolution::Callable(_)
6312 | VisibleMemberResolution::AmbiguousKind
6313 | VisibleMemberResolution::Missing => {
6314 push_unproven_hit(member, ctx);
6315 }
6316 }
6317 }
6318 LexicalCallableValueResolution::FreeFunction(_) => {}
6319 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
6320 push_unproven_hit(member, ctx);
6321 }
6322 }
6323}
6324
6325fn qualified_callable_value_resolution(
6326 qualified: Node<'_>,
6327 member_name: &str,
6328 ctx: &ScanCtx<'_>,
6329) -> LexicalCallableValueResolution {
6330 let Some((owner_components, global)) =
6331 qualified_callable_owner_components(qualified, ctx.source)
6332 else {
6333 return LexicalCallableValueResolution::Missing;
6334 };
6335 let lexical_scope = if global {
6336 Vec::new()
6337 } else {
6338 match enclosing_lexical_scope_components(
6339 qualified,
6340 &ctx.analyzer,
6341 ctx.visibility,
6342 ctx.file,
6343 ctx.source,
6344 ) {
6345 LexicalScopeResolution::Resolved(scope) => scope,
6346 LexicalScopeResolution::Ambiguous => {
6347 return LexicalCallableValueResolution::Ambiguous;
6348 }
6349 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
6350 }
6351 };
6352 if let Some(target_owner) = ctx.spec.owner.as_ref()
6353 && let LexicalTypeResolution::Resolved { unit, .. } =
6354 resolve_type_components_lexically_at_for_target_with_scope_cache(
6355 qualified,
6356 &owner_components,
6357 global,
6358 &ctx.analyzer,
6359 ctx.visibility,
6360 &ctx.ordinary_type_imports,
6361 ctx.file,
6362 ctx.source,
6363 target_owner,
6364 false,
6365 Some(&ctx.lexical_scope_cache),
6366 )
6367 {
6368 return LexicalCallableValueResolution::Type(unit);
6369 }
6370 ctx.visibility.resolve_callable_value_components_lexically(
6371 &ctx.analyzer,
6372 ctx.file,
6373 &owner_components,
6374 member_name,
6375 global,
6376 &lexical_scope,
6377 )
6378}
6379
6380fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6381 let Some(owner) = ctx.spec.owner.as_ref() else {
6382 return true;
6383 };
6384 if ctx.spec.param_types.is_none() {
6385 return true;
6386 }
6387 let mut candidates = ctx
6388 .visibility
6389 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
6390 .into_iter()
6391 .filter(|unit| unit.is_function())
6392 .cloned()
6393 .collect::<Vec<_>>();
6394 let Some(arity) = ctx
6395 .visibility
6396 .call_arity_evidence(ctx.file, call, ctx.source)
6397 .exact()
6398 else {
6399 return true;
6400 };
6401 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
6402 if candidates.is_empty()
6403 || !candidates
6404 .iter()
6405 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6406 {
6407 return true;
6408 }
6409 let arg_types = call_argument_types(call, ctx);
6410 let filtered = cpp_filter_candidates_by_args_with_parameter_types(
6411 candidates,
6412 &arg_types,
6413 &|candidate| cpp_callable_parameter_types(&ctx.analyzer, candidate),
6414 &|name| ctx.visibility.resolve_type(ctx.file, name),
6415 &|left, right| same_visible_symbol(left, right),
6416 );
6417 filtered
6418 .iter()
6419 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
6420}
6421
6422fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6423 let Some(function) = function_definition_name_node(node) else {
6424 return;
6425 };
6426 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
6427 return;
6428 }
6429 *ctx.raw_match_count += 1;
6430 if !function_definition_signature_matches_target(node, ctx) {
6431 return;
6432 }
6433 if node_inside_target_declaration(function, ctx) {
6434 return;
6435 }
6436 if is_structurally_qualified(function) {
6437 match qualified_owner_resolution(function, ctx) {
6438 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
6439 QualifiedOwnerResolution::NonTarget => {}
6440 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
6441 }
6442 return;
6443 }
6444 if definition_name_candidates(function, ctx)
6445 .iter()
6446 .any(|name| {
6447 name.contains("::")
6448 && ctx.visibility.contains_named_symbol(
6449 ctx.file,
6450 name,
6451 TargetKind::Method,
6452 &ctx.spec.target,
6453 )
6454 })
6455 {
6456 push_definition_hit(function, ctx);
6457 } else if definition_name_candidates(function, ctx)
6458 .iter()
6459 .any(|name| {
6460 ctx.visibility.resolve_known_non_target(
6461 ctx.file,
6462 name,
6463 TargetKind::Method,
6464 &ctx.spec.target,
6465 )
6466 })
6467 || known_non_target_owner_context(function, ctx)
6468 {
6469 } else {
6471 push_unproven_definition_hit(function, ctx);
6472 }
6473}
6474
6475fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6476 ctx.target_declaration_ranges
6477 .iter()
6478 .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
6479}
6480
6481fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
6482 let mut receiver = None;
6483 let mut cursor = node.walk();
6484 for child in node.named_children(&mut cursor) {
6485 if child.kind() == "argument_list" {
6486 continue;
6487 }
6488 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
6489 return receiver.map(|receiver| (receiver, operator));
6490 }
6491 if receiver.is_none() {
6492 receiver = Some(child);
6493 }
6494 }
6495 None
6496}
6497
6498fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
6499 if node.kind() != "function_definition" {
6500 return None;
6501 }
6502 node.child_by_field_name("declarator")
6503 .and_then(declarator_name_node)
6504}
6505
6506fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
6507 function_definition_name_node(node)
6508}
6509
6510fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6511 let definition = node_text(node, ctx.source);
6512 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
6513 return true;
6514 };
6515 if !expected.accepts(signature_arity(Some(definition))) {
6516 return false;
6517 }
6518 let Some(target_signature) = ctx.spec.target.signature() else {
6519 return true;
6520 };
6521 cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
6522}
6523
6524fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
6525 name_matches_callable(node_text(function_terminal_node(node), source), expected)
6526}
6527
6528fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
6529 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
6530 if raw.is_empty() {
6531 return Vec::new();
6532 }
6533 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
6534 return vec![raw];
6535 };
6536 if !raw.contains("::") {
6537 return vec![format!("{namespace}::{raw}")];
6538 }
6539 if raw
6545 .split("::")
6546 .next()
6547 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
6548 {
6549 vec![format!("{namespace}::{raw}"), raw]
6550 } else {
6551 vec![raw]
6552 }
6553}
6554
6555fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
6556 if node.kind() == kind {
6557 return Some(node);
6558 }
6559 let mut cursor = node.walk();
6560 for child in node.named_children(&mut cursor) {
6561 if let Some(found) = first_descendant_of_kind(child, kind) {
6562 return Some(found);
6563 }
6564 }
6565 None
6566}
6567
6568fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6569 if matches!(node.kind(), "identifier" | "field_identifier")
6570 && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
6571 {
6572 return;
6573 }
6574 if !matches!(
6575 node.kind(),
6576 "identifier" | "field_identifier" | "qualified_identifier"
6577 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6578 || is_declaration_name(node)
6579 || is_member_field_own_declarator(node, ctx)
6580 || is_selected_field_expression_member_descendant(node)
6581 || is_nested_in_qualified_identifier(node)
6582 {
6583 return;
6584 }
6585 *ctx.raw_match_count += 1;
6586 if global_field_resolves_to_target(node, ctx) {
6587 push_hit(node, ctx);
6588 } else if global_field_is_known_non_target(node, ctx) {
6589 } else {
6590 push_unproven_hit(node, ctx);
6591 }
6592}
6593
6594fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
6600 let candidate = node;
6601 while let Some(parent) = node.parent() {
6602 if parent.kind() == "field_expression" {
6603 if let Some(field) = parent.child_by_field_name("field")
6604 && node_is_within(field, candidate)
6605 {
6606 let selected_name = match field.kind() {
6607 "template_method" => field.child_by_field_name("name").unwrap_or(field),
6608 _ => field,
6609 };
6610 if node_is_within(selected_name, candidate) {
6611 return true;
6612 }
6613 node = parent;
6617 continue;
6618 }
6619 let receiver = parent
6620 .child_by_field_name("argument")
6621 .or_else(|| parent.child_by_field_name("object"))
6622 .or_else(|| parent.named_child(0));
6623 if !receiver.is_some_and(|receiver| node_is_within(receiver, candidate)) {
6624 return true;
6627 }
6628 }
6629 node = parent;
6630 }
6631 false
6632}
6633
6634fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
6635 parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
6636}
6637
6638fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6639 let text = node_text(node, ctx.source);
6640 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6641 return false;
6642 }
6643 if text.contains("::") {
6644 return ctx.visibility.contains_named_symbol(
6645 ctx.file,
6646 text,
6647 TargetKind::GlobalField,
6648 &ctx.spec.target,
6649 );
6650 }
6651 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
6652 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
6653 {
6654 return ctx.visibility.contains_named_symbol(
6655 ctx.file,
6656 text,
6657 TargetKind::GlobalField,
6658 &ctx.spec.target,
6659 );
6660 }
6661 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
6662 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
6663 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6664 brokk_bifrost_core::analyzer::Language::Cpp,
6665 &namespace,
6666 ) == indexed_scope
6667 })
6668 {
6669 return ctx.visibility.contains_named_symbol(
6670 ctx.file,
6671 text,
6672 TargetKind::GlobalField,
6673 &ctx.spec.target,
6674 );
6675 }
6676 bare_global_field_uniquely_resolves_to_target(text, ctx)
6677}
6678
6679fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
6680 let mut matched_target = false;
6681 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
6682 if !has_persisted_global_field_identity(unit)
6683 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
6684 {
6685 continue;
6686 }
6687 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
6688 continue;
6689 }
6690 if same_visible_global_field_symbol(
6691 &ctx.analyzer,
6692 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6693 unit,
6694 &ctx.spec.target,
6695 ) {
6696 matched_target = true;
6697 } else {
6698 return false;
6699 }
6700 }
6701 matched_target
6702}
6703
6704fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
6705 unit.is_field() && !unit.short_name().contains('.')
6710}
6711
6712fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6713 let text = node_text(node, ctx.source);
6714 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
6715 return true;
6716 }
6717 if text.contains("::") {
6718 return ctx.visibility.resolve_known_non_target(
6719 ctx.file,
6720 text,
6721 TargetKind::GlobalField,
6722 &ctx.spec.target,
6723 );
6724 }
6725 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
6726 return false;
6727 };
6728 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
6729 && ctx
6730 .visibility
6731 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6732 .any(|unit| {
6733 has_persisted_global_field_identity(unit)
6734 && unit.identifier() == ctx.spec.member_name
6735 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
6736 && !same_visible_global_field_symbol(
6737 &ctx.analyzer,
6738 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
6739 unit,
6740 &ctx.spec.target,
6741 )
6742 })
6743}
6744
6745fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
6746 if node.kind() == "field_expression" {
6747 let Some(field) = node.child_by_field_name("field") else {
6748 return;
6749 };
6750 if node_text(field, ctx.source) != ctx.spec.member_name {
6751 return;
6752 }
6753 *ctx.raw_match_count += 1;
6754 let receiver = node
6755 .child_by_field_name("argument")
6756 .or_else(|| node.child_by_field_name("object"));
6757 match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, None, ctx)) {
6758 Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
6759 Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
6760 Some(
6761 MethodReceiverTargetResolution::NonTarget
6762 | MethodReceiverTargetResolution::Ambiguous,
6763 ) => {}
6764 }
6765 return;
6766 }
6767
6768 if matches!(node.kind(), "identifier" | "field_identifier")
6769 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6770 && let Some(designator_owner) =
6771 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
6772 {
6773 *ctx.raw_match_count += 1;
6774 match designator_owner {
6775 DesignatedInitializerOwner::Resolved(owner)
6776 if ctx
6777 .spec
6778 .owner
6779 .as_ref()
6780 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
6781 {
6782 push_hit(node, ctx);
6783 }
6784 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
6785 DesignatedInitializerOwner::Resolved(_) => {}
6786 }
6787 return;
6788 }
6789
6790 let qualified_member_name_matches =
6791 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
6792 && cpp_name_component_nodes(node)
6793 .and_then(|components| components.last().copied())
6794 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
6795 if !matches!(
6796 node.kind(),
6797 "identifier" | "field_identifier" | "qualified_identifier" | "scoped_identifier"
6798 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
6799 && !qualified_member_name_matches)
6800 || is_declaration_name(node)
6801 || is_member_field_own_declarator(node, ctx)
6802 || is_selected_field_expression_member_descendant(node)
6803 || is_nested_in_qualified_identifier(node)
6804 {
6805 return;
6806 }
6807 *ctx.raw_match_count += 1;
6808 if is_structurally_qualified(node) {
6809 match qualified_owner_resolution(node, ctx) {
6810 QualifiedOwnerResolution::Target => push_hit(node, ctx),
6811 QualifiedOwnerResolution::NonTarget => {}
6812 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
6813 }
6814 return;
6815 }
6816 let text = node_text(node, ctx.source);
6817 if ctx.local_shadows.is_shadowed(text) {
6818 return;
6819 }
6820 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
6821 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
6822 let owner_context = structured_owner_context_resolution(node, ctx);
6823 if matches!(
6824 owner_context,
6825 StructuredOwnerContextResolution::SelfTarget
6826 | StructuredOwnerContextResolution::InheritedTarget
6827 ) || unscoped_enum_match
6828 {
6829 push_hit(node, ctx);
6830 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
6831 .then_some(ctx.spec.owner.as_ref())
6832 .flatten()
6833 {
6834 let resolution =
6835 match resolve_active_using_enum_member(node, ctx) {
6836 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
6837 ActiveUsingEnumMemberResolution::Class(resolution) => {
6838 if direct_class_member_shadows(node, ctx) {
6839 return;
6840 }
6841 resolution
6842 }
6843 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
6844 if let Some(owner) = structured_enclosing_owner(node, ctx) {
6845 if direct_class_member_shadows(node, ctx) {
6846 return;
6847 }
6848 let complete_same_file_leaf =
6849 owner.source() == ctx.file
6850 && ctx.analyzer.type_hierarchy_provider().is_some_and(
6851 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
6852 );
6853 if !complete_same_file_leaf {
6854 push_unproven_hit(node, ctx);
6855 return;
6856 }
6857 }
6858 match owner_context {
6859 StructuredOwnerContextResolution::SelfTarget
6860 | StructuredOwnerContextResolution::InheritedTarget
6861 | StructuredOwnerContextResolution::NonTarget => return,
6862 StructuredOwnerContextResolution::Ambiguous => {
6863 push_unproven_hit(node, ctx);
6864 return;
6865 }
6866 StructuredOwnerContextResolution::Missing => {}
6867 }
6868 if namespace_value_shadows(node, ctx) {
6869 return;
6870 }
6871 resolution
6872 }
6873 ActiveUsingEnumMemberResolution::Missing => {
6874 if direct_class_member_shadows(node, ctx)
6875 || (structured_enclosing_owner(node, ctx).is_none()
6876 && namespace_value_shadows(node, ctx))
6877 {
6878 return;
6879 }
6880 UsingEnumMemberResolution::Missing
6881 }
6882 };
6883 match resolution {
6884 UsingEnumMemberResolution::Resolved { owner, member }
6885 if same_visible_symbol(&owner, target_owner)
6886 && same_visible_symbol(&member, &ctx.spec.target) =>
6887 {
6888 push_hit(node, ctx);
6889 }
6890 UsingEnumMemberResolution::Resolved { .. } => {}
6891 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
6892 push_unproven_hit(node, ctx)
6893 }
6894 }
6895 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
6896 push_unproven_hit(node, ctx);
6897 }
6898}
6899
6900enum ActiveUsingEnumMemberResolution {
6901 Block(UsingEnumMemberResolution),
6902 Class(UsingEnumMemberResolution),
6903 Namespace(UsingEnumMemberResolution),
6904 Missing,
6905}
6906
6907fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6908 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
6909 ctx.visibility
6910 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
6911 .into_iter()
6912 .next()
6913 .is_some()
6914 })
6915}
6916
6917fn resolve_active_using_enum_member(
6918 node: Node<'_>,
6919 ctx: &ScanCtx<'_>,
6920) -> ActiveUsingEnumMemberResolution {
6921 let block =
6922 ctx.using_enum_owners
6923 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
6924 if !matches!(block, UsingEnumMemberResolution::Missing) {
6925 return ActiveUsingEnumMemberResolution::Block(block);
6926 }
6927 let class = structured_enclosing_owner(node, ctx);
6928 let namespace = enclosing_namespace_components(node, ctx.source);
6929 match ctx.semantic_using_enum_owners.resolve_member(
6930 ctx.visibility,
6931 ctx.file,
6932 class.as_ref(),
6933 &namespace,
6934 node.start_byte(),
6935 &ctx.spec.member_name,
6936 ) {
6937 SemanticUsingEnumMemberResolution::Class(resolution) => {
6938 ActiveUsingEnumMemberResolution::Class(resolution)
6939 }
6940 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
6941 ActiveUsingEnumMemberResolution::Namespace(resolution)
6942 }
6943 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
6944 }
6945}
6946
6947fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6948 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
6949 !matches!(
6950 resolve_namespace_value(
6951 &ctx.analyzer,
6952 ctx.visibility,
6953 ctx.file,
6954 &namespace,
6955 &ctx.spec.member_name,
6956 node.start_byte(),
6957 ),
6958 NamespaceValueResolution::Missing
6959 )
6960}
6961
6962fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
6963 if node.kind() == "qualified_identifier" {
6964 return false;
6965 }
6966 let mut current = node.parent();
6967 while let Some(parent) = current {
6968 if parent.kind() == "ERROR" {
6976 return false;
6977 }
6978 if parent.kind() == "template_argument_list" {
6983 return false;
6984 }
6985 if parent.kind() == "qualified_identifier" {
6986 return true;
6987 }
6988 current = parent.parent();
6989 }
6990 false
6991}
6992
6993fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6994 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
6995}
6996
6997fn receiver_type_units_with_budget(
6998 node: Node<'_>,
6999 source: &str,
7000 ctx: &ScanCtx<'_>,
7001 remaining_call_depth: usize,
7002) -> Vec<CodeUnit> {
7003 let mut current = node;
7004 let mut member_chain = Vec::new();
7005 let mut base_units = loop {
7006 match current.kind() {
7007 "field_expression" => {
7008 let Some(member) = current.child_by_field_name("field") else {
7009 return Vec::new();
7010 };
7011 let Some(receiver) = current
7012 .child_by_field_name("argument")
7013 .or_else(|| current.child_by_field_name("object"))
7014 .or_else(|| current.named_child(0))
7015 else {
7016 return Vec::new();
7017 };
7018 member_chain.push(node_text(member, source));
7019 current = receiver;
7020 }
7021 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
7022 let Some(inner) = current
7023 .child_by_field_name("argument")
7024 .or_else(|| current.named_child(0))
7025 else {
7026 return Vec::new();
7027 };
7028 current = inner;
7029 }
7030 "identifier" | "field_identifier" => {
7037 let name = node_text(current, source);
7038 let local = ctx.bindings.resolve_symbol(name);
7039 if let Some(bindings) = local.as_precise() {
7040 break receiver_units_from_bindings(current, bindings, ctx);
7041 }
7042 if ctx.bindings.is_shadowed(name) {
7043 return Vec::new();
7044 }
7045 let owner = structured_enclosing_owner(current, ctx)
7046 .filter(CodeUnit::is_class)
7047 .or_else(|| {
7048 enclosing_context(current, ctx)
7049 .owner
7050 .filter(CodeUnit::is_class)
7051 });
7052 if let Some(owner) = owner {
7053 let implicit_fields = ctx
7054 .visibility
7055 .visible_members_for_owner_name(ctx.file, &owner, name)
7056 .into_iter()
7057 .filter(|unit| unit.is_field())
7058 .collect::<Vec<_>>();
7059 if !implicit_fields.is_empty() {
7060 break receiver_units_from_declared_fields(implicit_fields, current, ctx);
7061 }
7062 }
7063 let global_fields = ctx
7064 .visibility
7065 .visible_identifier_candidates(ctx.file, name)
7066 .filter(|unit| {
7067 has_persisted_global_field_identity(unit) && unit.identifier() == name
7068 })
7069 .collect::<Vec<_>>();
7070 if global_fields.is_empty() {
7071 break ctx
7072 .visibility
7073 .resolve_type(ctx.file, name)
7074 .into_iter()
7075 .collect();
7076 }
7077 if let Some(first) = global_fields.first()
7078 && global_fields
7079 .iter()
7080 .skip(1)
7081 .any(|field| !same_visible_symbol(first, field))
7082 {
7083 return Vec::new();
7084 }
7085 break receiver_units_from_declared_fields(global_fields, current, ctx);
7086 }
7087 "call_expression" | "new_expression" => {
7088 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
7089 .and_then(|binding| binding.unit)
7090 .into_iter()
7091 .collect();
7092 }
7093 "this" if is_c_source_file(ctx.file) => {
7094 let name = node_text(current, source);
7095 let local = ctx.bindings.resolve_symbol(name);
7096 if let Some(bindings) = local.as_precise() {
7097 break receiver_units_from_bindings(current, bindings, ctx);
7098 }
7099 return Vec::new();
7100 }
7101 "this" => break enclosing_context(current, ctx).owner.into_iter().collect(),
7102 "qualified_identifier" | "scoped_identifier" => {
7103 let reference = node_text(current, source);
7104 let fields = ctx
7105 .visibility
7106 .named_candidates(ctx.file, reference, TargetKind::GlobalField)
7107 .into_iter()
7108 .filter(has_persisted_global_field_identity)
7109 .collect::<Vec<_>>();
7110 if fields.is_empty() {
7111 break ctx
7112 .visibility
7113 .resolve_type(ctx.file, reference)
7114 .into_iter()
7115 .collect();
7116 }
7117 break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
7118 }
7119 _ => {
7120 break ctx
7121 .visibility
7122 .resolve_type(ctx.file, node_text(current, source))
7123 .into_iter()
7124 .collect();
7125 }
7126 }
7127 };
7128
7129 base_units = canonical_receiver_units(base_units, ctx);
7130 if base_units.is_empty() {
7131 return Vec::new();
7132 }
7133
7134 while let Some(member_name) = member_chain.pop() {
7135 let mut next_units = Vec::new();
7136 for owner in &base_units {
7137 let declaring_owner = match resolve_declaring_member_owner(
7138 &ctx.analyzer,
7139 ctx.visibility,
7140 ctx.file,
7141 owner,
7142 member_name,
7143 ) {
7144 EnclosingMemberOwnerResolution::Owner(owner) => owner,
7145 EnclosingMemberOwnerResolution::Missing => continue,
7146 EnclosingMemberOwnerResolution::Ambiguous => return Vec::new(),
7147 };
7148 let fields = ctx.visibility.visible_members_for_owner_name(
7149 ctx.file,
7150 &declaring_owner,
7151 member_name,
7152 );
7153 for field in fields.into_iter().filter(|unit| unit.is_field()) {
7154 let Some(unit) =
7155 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
7156 .and_then(|binding| binding.unit)
7157 .or_else(|| recovered_receiver_field_type(current, field, ctx))
7158 else {
7159 continue;
7160 };
7161 if !next_units
7162 .iter()
7163 .any(|existing| same_visible_symbol(existing, &unit))
7164 {
7165 next_units.push(unit);
7166 }
7167 }
7168 }
7169 if next_units.is_empty() {
7170 return Vec::new();
7171 }
7172 base_units = unanimous_receiver_units(next_units);
7173 if base_units.is_empty() {
7174 return Vec::new();
7175 }
7176 }
7177 base_units
7178}
7179
7180fn receiver_units_from_bindings(
7181 node: Node<'_>,
7182 bindings: &HashSet<CppScanBinding>,
7183 ctx: &ScanCtx<'_>,
7184) -> Vec<CodeUnit> {
7185 let mut units = Vec::new();
7186 for binding in bindings {
7187 let raw_unit = if let Some(unit) = &binding.unit {
7188 unit.clone()
7189 } else {
7190 let Some(type_name) = binding.type_name.as_deref() else {
7191 return Vec::new();
7192 };
7193 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
7194 return Vec::new();
7195 };
7196 unit
7197 };
7198 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
7199 if same_visible_symbol(&unit, &raw_unit)
7205 && let Some(recovered) = recovered_receiver_alias_target(node, &raw_unit, ctx)
7206 {
7207 units.push(recovered);
7208 continue;
7209 }
7210 units.push(unit);
7211 continue;
7212 }
7213 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
7214 units.push(unit);
7215 continue;
7216 }
7217 return Vec::new();
7218 }
7219 unanimous_receiver_units(units)
7220}
7221
7222fn recovered_receiver_alias_target(
7227 reference: Node<'_>,
7228 alias: &CodeUnit,
7229 ctx: &ScanCtx<'_>,
7230) -> Option<CodeUnit> {
7231 if !ctx
7232 .analyzer
7233 .type_alias_provider()
7234 .is_some_and(|provider| provider.is_type_alias(alias))
7235 {
7236 return None;
7237 }
7238 let target = ctx.spec.owner.as_ref()?.clone();
7239 if !target.is_class() || alias.source() != ctx.file {
7240 return None;
7241 }
7242 let range = ctx
7243 .analyzer
7244 .ranges(alias)
7245 .into_iter()
7246 .find(|range| range.start_byte < range.end_byte)?;
7247 let mut node =
7248 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
7249 while !matches!(node.kind(), "alias_declaration" | "type_definition") {
7250 node = node.parent()?;
7251 }
7252 let type_descriptor = node.child_by_field_name("type")?;
7253 let type_node = receiver_type_node_base(type_descriptor);
7254 let resolution = resolve_type_node_lexically_for_target(
7255 type_node,
7256 &ctx.analyzer,
7257 ctx.visibility,
7258 &ctx.ordinary_type_imports,
7259 ctx.file,
7260 ctx.source,
7261 &target,
7262 Some(&ctx.lexical_scope_cache),
7263 ctx.recovered_sentinel_scope(type_node).as_deref(),
7264 );
7265 if let LexicalTypeResolution::Resolved {
7266 unit, candidates, ..
7267 } = resolution
7268 && (same_visible_symbol(&unit, &target)
7269 || candidates
7270 .iter()
7271 .any(|candidate| same_visible_symbol(candidate, &target)))
7272 {
7273 return Some(target);
7274 }
7275 let (components, global) = type_reference_components(type_node, ctx.source)?;
7276 if !global
7277 && components.len() == 2
7278 && cpp_active_template_type_parameter(type_node, &components[0], ctx.source)
7279 {
7280 let alias_provider = ctx.analyzer.type_alias_provider()?;
7281 let concrete = ctx
7282 .visibility
7283 .visible_identifier_candidates(ctx.file, &components[1])
7284 .filter(|candidate| {
7285 alias_provider.is_type_alias(candidate)
7286 && !same_visible_symbol(candidate, alias)
7287 && type_owner_of(&ctx.analyzer, candidate).is_some_and(|owner| owner.is_class())
7288 && ctx.visibility.is_physically_visible(ctx.file, candidate)
7289 && ctx
7290 .visibility
7291 .external_type_candidate_guard_compatible_in_context(
7292 &ctx.analyzer,
7293 ctx.file,
7294 candidate,
7295 type_node,
7296 )
7297 })
7298 .filter_map(|candidate| {
7299 let canonical = ctx.visibility.canonical_visible_full_type_unit(
7300 &ctx.analyzer,
7301 ctx.file,
7302 candidate,
7303 )?;
7304 (!same_visible_symbol(&canonical, candidate)).then_some(canonical)
7309 })
7310 .collect::<Vec<_>>();
7311 if let [unit] = unanimous_receiver_units(concrete).as_slice()
7312 && same_visible_symbol(unit, &target)
7313 {
7314 return Some(target);
7315 }
7316 }
7317 let scope = ctx
7318 .recovered_sentinel_scope(type_node)
7319 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
7320 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
7321 let visible = ctx.visibility.external_type_candidate_visible_in_context(
7322 &ctx.analyzer,
7323 ctx.file,
7324 &target,
7325 type_node,
7326 );
7327 (path_matches && visible).then_some(target)
7328}
7329
7330fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7331 let normalized = normalize_cpp_type_name(type_name);
7332 if normalized.is_empty() {
7333 return None;
7334 }
7335
7336 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
7341 let alias_type = receiver_type_node_base(alias_type);
7342 match ctx
7343 .visibility
7344 .resolve_type_node_result(ctx.file, alias_type, ctx.source)
7345 {
7346 Ok(Some(unit)) => return Some(unit),
7347 Err(_) => return None,
7348 Ok(None) => {}
7349 }
7350 if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
7351 return Some(unit);
7352 }
7353 }
7354
7355 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
7356 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
7357 LexicalTypeResolution::Ambiguous => return None,
7358 LexicalTypeResolution::Missing => {}
7359 }
7360 let candidates = ctx
7361 .visibility
7362 .type_name_candidates(ctx.file, &normalized)
7363 .into_iter()
7364 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
7365 .collect();
7366 unanimous_receiver_units(candidates).into_iter().next()
7367}
7368
7369fn resolve_receiver_type_node_lexically(
7370 type_node: Node<'_>,
7371 ctx: &ScanCtx<'_>,
7372) -> Option<CodeUnit> {
7373 let type_node = receiver_type_node_base(type_node);
7374 let components = cpp_type_name_components(type_node, ctx.source)?;
7375 let lexical_scope = match enclosing_lexical_scope_components(
7376 type_node,
7377 &ctx.analyzer,
7378 ctx.visibility,
7379 ctx.file,
7380 ctx.source,
7381 ) {
7382 LexicalScopeResolution::Resolved(scope) => scope,
7383 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
7384 };
7385 match ctx.visibility.resolve_type_components_lexically(
7386 &ctx.analyzer,
7387 ctx.file,
7388 &components,
7389 is_globally_qualified_cpp_name(type_node),
7390 &lexical_scope,
7391 ) {
7392 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
7393 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
7394 }
7395}
7396
7397fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
7398 while matches!(node.kind(), "type_descriptor" | "dependent_type") {
7399 let Some(inner) = node.child_by_field_name("type").or_else(|| {
7400 if node.kind() == "dependent_type" {
7401 node.named_child(0)
7402 } else {
7403 None
7404 }
7405 }) else {
7406 break;
7407 };
7408 node = inner;
7409 }
7410 node
7411}
7412
7413fn resolve_receiver_type_name_lexically(
7414 node: Node<'_>,
7415 normalized: &str,
7416 ctx: &ScanCtx<'_>,
7417) -> LexicalTypeResolution {
7418 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7419 brokk_bifrost_core::analyzer::Language::Cpp,
7420 normalized,
7421 );
7422 if components.is_empty() {
7423 return LexicalTypeResolution::Missing;
7424 }
7425 let lexical_scope = match enclosing_lexical_scope_components(
7426 node,
7427 &ctx.analyzer,
7428 ctx.visibility,
7429 ctx.file,
7430 ctx.source,
7431 ) {
7432 LexicalScopeResolution::Resolved(scope) => scope,
7433 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7434 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7435 };
7436 ctx.visibility.resolve_type_components_lexically(
7437 &ctx.analyzer,
7438 ctx.file,
7439 &components,
7440 normalized.starts_with("::"),
7441 &lexical_scope,
7442 )
7443}
7444
7445fn local_receiver_alias_type_node<'tree>(
7446 node: Node<'tree>,
7447 name: &str,
7448 ctx: &ScanCtx<'_>,
7449) -> Option<Node<'tree>> {
7450 let callable = nearest_callable_scope(node)?;
7451 let mut root_callable = callable;
7452 let mut ancestor = callable.parent();
7453 while let Some(current) = ancestor {
7454 if matches!(current.kind(), "function_definition" | "lambda_expression") {
7455 root_callable = current;
7456 }
7457 ancestor = current.parent();
7458 }
7459
7460 let mut stack = vec![root_callable];
7461 let mut best = None;
7462 while let Some(current) = stack.pop() {
7463 if current.start_byte() >= node.start_byte() {
7464 continue;
7465 }
7466 if local_type_alias_name_node(current)
7467 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
7468 && local_alias_scope_contains_node(current, node)
7469 {
7470 let replace = best
7471 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
7472 if replace {
7473 best = current.child_by_field_name("type");
7474 }
7475 }
7476 let mut cursor = current.walk();
7477 stack.extend(current.named_children(&mut cursor));
7478 }
7479 best
7480}
7481
7482fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
7483 let mut canonical = Vec::with_capacity(units.len());
7484 for unit in units {
7485 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
7486 return Vec::new();
7487 };
7488 canonical.push(unit);
7489 }
7490 unanimous_receiver_units(canonical)
7491}
7492
7493fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7494 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
7495 return cached.clone();
7496 }
7497 let canonical = ctx
7498 .visibility
7499 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
7500 ctx.receiver_canonical_type_cache
7501 .borrow_mut()
7502 .insert(unit.clone(), canonical.clone());
7503 canonical
7504}
7505
7506fn receiver_units_from_declared_fields(
7507 fields: Vec<&CodeUnit>,
7508 reference: Node<'_>,
7509 ctx: &ScanCtx<'_>,
7510) -> Vec<CodeUnit> {
7511 let Some(first) = fields.first() else {
7512 return Vec::new();
7513 };
7514 if fields
7515 .iter()
7516 .skip(1)
7517 .any(|field| !same_visible_symbol(first, field))
7518 {
7519 return Vec::new();
7520 }
7521 unanimous_receiver_units(
7522 fields
7523 .into_iter()
7524 .filter_map(|field| {
7525 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
7526 .and_then(|binding| binding.unit)
7527 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
7528 })
7529 .collect(),
7530 )
7531}
7532
7533fn recovered_receiver_field_type(
7538 reference: Node<'_>,
7539 field: &CodeUnit,
7540 ctx: &ScanCtx<'_>,
7541) -> Option<CodeUnit> {
7542 let target = ctx.spec.owner.as_ref()?.clone();
7543 if !target.is_class() || field.source() != ctx.file {
7544 return None;
7545 }
7546 let range = ctx
7547 .analyzer
7548 .ranges(field)
7549 .into_iter()
7550 .find(|range| range.start_byte < range.end_byte)?;
7551 let mut declaration =
7552 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
7553 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
7554 declaration = declaration.parent()?;
7555 }
7556 let type_node = first_type_child(declaration)?;
7557 let resolution = resolve_type_node_lexically_for_target(
7558 type_node,
7559 &ctx.analyzer,
7560 ctx.visibility,
7561 &ctx.ordinary_type_imports,
7562 ctx.file,
7563 ctx.source,
7564 &target,
7565 Some(&ctx.lexical_scope_cache),
7566 ctx.recovered_sentinel_scope(type_node).as_deref(),
7567 );
7568 if let LexicalTypeResolution::Resolved {
7569 unit, candidates, ..
7570 } = resolution
7571 && (same_visible_symbol(&unit, &target)
7572 || candidates
7573 .iter()
7574 .any(|candidate| same_visible_symbol(candidate, &target)))
7575 {
7576 return Some(target);
7577 }
7578 let type_node = receiver_type_node_base(type_node);
7579 let (components, global) = type_reference_components(type_node, ctx.source)?;
7580 let scope = ctx
7581 .recovered_sentinel_scope(type_node)
7582 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
7583 (indexed_scope_matches_target_name(&scope, &components, global, &target)
7584 && ctx.visibility.external_type_candidate_visible_in_context(
7585 &ctx.analyzer,
7586 ctx.file,
7587 &target,
7588 type_node,
7589 ))
7590 .then_some(target)
7591}
7592
7593fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
7594 let mut unique = Vec::new();
7595 for unit in units {
7596 if !unique
7597 .iter()
7598 .any(|existing| same_visible_symbol(existing, &unit))
7599 {
7600 unique.push(unit);
7601 if unique.len() > 1 {
7602 return Vec::new();
7603 }
7604 }
7605 }
7606 unique
7607}
7608
7609fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7610 let Some(owner) = ctx.spec.owner.as_ref() else {
7611 return false;
7612 };
7613 match node.kind() {
7614 "field_expression" => node
7615 .child_by_field_name("argument")
7616 .or_else(|| node.child_by_field_name("object"))
7617 .is_some_and(|receiver| {
7618 receiver_is_self_like(receiver, ctx.file) && same_owner_context(receiver, ctx)
7619 || receiver_type_units(receiver, ctx.source, ctx)
7620 .iter()
7621 .any(|target| {
7622 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7623 })
7624 }),
7625 "call_expression" => node
7626 .child_by_field_name("function")
7627 .is_some_and(|function| receiver_matches_target(function, ctx)),
7628 "pointer_expression" | "parenthesized_expression" => node
7629 .child_by_field_name("argument")
7630 .or_else(|| node.named_child(0))
7631 .is_some_and(|child| receiver_matches_target(child, ctx)),
7632 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
7633 .bindings
7634 .resolve_symbol(node_text(node, ctx.source))
7635 .as_precise()
7636 .is_some_and(|targets| {
7637 targets
7638 .iter()
7639 .filter_map(|target| target.unit.as_ref())
7640 .any(|target| {
7641 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
7642 })
7643 }),
7644 "this" => same_owner_context(node, ctx),
7645 _ => qualified_owner_matches(node, ctx),
7646 }
7647}
7648
7649fn declaring_owner_for_explicit_receiver(
7650 receiver: Node<'_>,
7651 call_arity: Option<usize>,
7652 ctx: &ScanCtx<'_>,
7653) -> EnclosingMemberOwnerResolution {
7654 if receiver_is_self_like(receiver, ctx.file) {
7655 return EnclosingMemberOwnerResolution::Missing;
7656 }
7657 let receiver_units = receiver_type_units(receiver, ctx.source, ctx);
7658 let mut declaring_owner = None;
7659 for receiver_owner in receiver_units {
7660 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
7661 receiver_owner_matches_target(&receiver_owner, target_owner, receiver.start_byte(), ctx)
7662 }) {
7663 if declaring_owner
7664 .as_ref()
7665 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
7666 {
7667 return EnclosingMemberOwnerResolution::Ambiguous;
7668 }
7669 declaring_owner = Some(receiver_owner);
7670 continue;
7671 }
7672 let ordinary = cached_declaring_member_owner(&receiver_owner, ctx);
7673 let owner_resolution = match call_arity {
7674 Some(arity) => resolve_declaring_callable_owner(
7675 &ctx.analyzer,
7676 ctx.visibility,
7677 ctx.file,
7678 ordinary,
7679 &ctx.spec.member_name,
7680 arity,
7681 ),
7682 None => ordinary,
7683 };
7684 match owner_resolution {
7685 EnclosingMemberOwnerResolution::Owner(owner) => {
7686 if declaring_owner
7687 .as_ref()
7688 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
7689 {
7690 return EnclosingMemberOwnerResolution::Ambiguous;
7691 }
7692 declaring_owner = Some(owner);
7693 }
7694 EnclosingMemberOwnerResolution::Ambiguous => {
7695 return EnclosingMemberOwnerResolution::Ambiguous;
7696 }
7697 EnclosingMemberOwnerResolution::Missing => {}
7698 }
7699 }
7700 declaring_owner
7701 .map(EnclosingMemberOwnerResolution::Owner)
7702 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
7703}
7704
7705fn declaring_owner_from_call_function(
7706 function: Node<'_>,
7707 call_arity: Option<usize>,
7708 ctx: &ScanCtx<'_>,
7709) -> Option<EnclosingMemberOwnerResolution> {
7710 match function.kind() {
7711 "field_expression" => function
7712 .child_by_field_name("argument")
7713 .or_else(|| function.child_by_field_name("object"))
7714 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, call_arity, ctx))
7715 .or(Some(EnclosingMemberOwnerResolution::Missing)),
7716 "call_expression" => function
7717 .child_by_field_name("function")
7718 .and_then(|inner| declaring_owner_from_call_function(inner, call_arity, ctx)),
7719 _ => None,
7720 }
7721}
7722
7723enum MethodReceiverTargetResolution {
7724 Target,
7725 NonTarget,
7726 Ambiguous,
7727 Missing,
7728}
7729
7730fn method_receiver_target_resolution(
7731 node: Node<'_>,
7732 declaring_owner: EnclosingMemberOwnerResolution,
7733 ctx: &ScanCtx<'_>,
7734) -> MethodReceiverTargetResolution {
7735 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7736 return MethodReceiverTargetResolution::Missing;
7737 };
7738 match declaring_owner {
7739 EnclosingMemberOwnerResolution::Owner(owner)
7740 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
7741 {
7742 MethodReceiverTargetResolution::Target
7743 }
7744 EnclosingMemberOwnerResolution::Owner(owner)
7745 if receiver_owner_is_known_non_target(&owner, target_owner, node.start_byte(), ctx) =>
7746 {
7747 MethodReceiverTargetResolution::NonTarget
7748 }
7749 EnclosingMemberOwnerResolution::Owner(_) => MethodReceiverTargetResolution::Missing,
7750 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
7751 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
7752 MethodReceiverTargetResolution::Target
7753 }
7754 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
7755 MethodReceiverTargetResolution::NonTarget
7756 }
7757 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
7758 }
7759}
7760
7761fn explicit_receiver_target_resolution(
7762 receiver: Node<'_>,
7763 call_arity: Option<usize>,
7764 ctx: &ScanCtx<'_>,
7765) -> MethodReceiverTargetResolution {
7766 method_receiver_target_resolution(
7767 receiver,
7768 declaring_owner_for_explicit_receiver(receiver, call_arity, ctx),
7769 ctx,
7770 )
7771}
7772
7773fn call_function_target_resolution(
7774 function: Node<'_>,
7775 ctx: &ScanCtx<'_>,
7776) -> MethodReceiverTargetResolution {
7777 let call_arity = function.parent().and_then(|call| {
7778 (call.kind() == "call_expression")
7779 .then(|| {
7780 ctx.visibility
7781 .call_arity_evidence(ctx.file, call, ctx.source)
7782 .exact()
7783 })
7784 .flatten()
7785 });
7786 let Some(declaring_owner) = declaring_owner_from_call_function(function, call_arity, ctx)
7787 else {
7788 return MethodReceiverTargetResolution::Missing;
7792 };
7793 method_receiver_target_resolution(function, declaring_owner, ctx)
7794}
7795
7796fn receiver_owner_matches_target(
7797 receiver_owner: &CodeUnit,
7798 target_owner: &CodeUnit,
7799 reference_byte: usize,
7800 ctx: &ScanCtx<'_>,
7801) -> bool {
7802 same_symbol(receiver_owner, target_owner)
7803 || same_logical_symbol(receiver_owner, target_owner)
7804 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
7805 || (ctx.spec.owner_is_forward_declaration
7806 && ctx
7807 .visibility
7808 .is_physically_visible(ctx.file, receiver_owner))
7809 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
7810 || target_group_contains_owner_peer(receiver_owner, ctx))
7811}
7812
7813fn receiver_owner_is_known_non_target(
7814 receiver_owner: &CodeUnit,
7815 target_owner: &CodeUnit,
7816 reference_byte: usize,
7817 ctx: &ScanCtx<'_>,
7818) -> bool {
7819 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
7820 return false;
7821 }
7822 if !same_logical_symbol(receiver_owner, target_owner) {
7823 return true;
7824 }
7825 !ctx.target_group.iter().any(|target| {
7826 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
7827 })
7828}
7829
7830fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
7831 ctx.visibility
7832 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
7833 && ctx.target_group.iter().any(|target| {
7834 type_owner_of(&ctx.analyzer, target)
7835 .as_ref()
7836 .is_some_and(|target_owner| {
7837 same_symbol(target_owner, owner)
7838 || (same_logical_symbol(target_owner, owner)
7839 && target_owner.source() == owner.source())
7840 })
7841 })
7842}
7843
7844fn visible_target_peer_matches_owner(
7845 owner: &CodeUnit,
7846 reference_byte: usize,
7847 ctx: &ScanCtx<'_>,
7848) -> bool {
7849 ctx.visibility
7850 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
7851 && ctx
7852 .visibility
7853 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
7854 .any(|candidate| {
7855 cpp_callable_definitions_share_identity_evidence(
7856 &ctx.analyzer,
7857 candidate,
7858 &ctx.spec.target,
7859 ) && ctx.visibility.declaration_visible_at(
7860 &ctx.analyzer,
7861 ctx.file,
7862 candidate,
7863 reference_byte,
7864 ) && type_owner_of(&ctx.analyzer, candidate)
7865 .as_ref()
7866 .is_some_and(|candidate_owner| {
7867 same_symbol(candidate_owner, owner)
7868 || (same_logical_symbol(candidate_owner, owner)
7869 && candidate_owner.source() == owner.source())
7870 })
7871 })
7872}
7873
7874fn receiver_is_self_like(node: Node<'_>, file: &ProjectFile) -> bool {
7875 match node.kind() {
7876 "this" => !is_c_source_file(file),
7877 "pointer_expression" | "parenthesized_expression" => node
7878 .child_by_field_name("argument")
7879 .or_else(|| node.named_child(0))
7880 .is_some_and(|inner| receiver_is_self_like(inner, file)),
7881 _ => false,
7882 }
7883}
7884
7885fn call_function_has_direct_self_receiver(function: Node<'_>, file: &ProjectFile) -> bool {
7886 match function.kind() {
7887 "field_expression" => function
7888 .child_by_field_name("argument")
7889 .or_else(|| function.child_by_field_name("object"))
7890 .is_some_and(|receiver| receiver_is_self_like(receiver, file)),
7891 _ => receiver_is_self_like(function, file),
7892 }
7893}
7894
7895fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7896 let Some(owner) = ctx.spec.owner.as_ref() else {
7897 return false;
7898 };
7899 match node.kind() {
7900 "field_expression" => node
7901 .child_by_field_name("argument")
7902 .or_else(|| node.child_by_field_name("object"))
7903 .is_some_and(|receiver| {
7904 let units = receiver_type_units(receiver, ctx.source, ctx);
7905 !units.is_empty()
7906 && units.iter().all(|target| {
7907 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7908 })
7909 }),
7910 "call_expression" => node
7911 .child_by_field_name("function")
7912 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
7913 "pointer_expression" | "parenthesized_expression" => node
7914 .child_by_field_name("argument")
7915 .or_else(|| node.named_child(0))
7916 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
7917 "identifier" | "this" if is_c_source_file(ctx.file) => ctx
7918 .bindings
7919 .resolve_symbol(node_text(node, ctx.source))
7920 .as_precise()
7921 .is_some_and(|targets| {
7922 let units = targets
7923 .iter()
7924 .filter_map(|target| target.unit.as_ref())
7925 .collect::<Vec<_>>();
7926 !units.is_empty()
7927 && units.iter().all(|target| {
7928 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
7929 })
7930 }),
7931 "this" => known_non_target_owner_context(node, ctx),
7932 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
7933 qualified_owner_is_known_non_target(node, ctx)
7934 }
7935 _ => false,
7936 }
7937}
7938
7939#[derive(Clone, Copy, PartialEq, Eq)]
7940enum QualifiedOwnerResolution {
7941 Target,
7942 NonTarget,
7943 Unresolved,
7944}
7945
7946#[derive(Clone)]
7947pub enum LexicalScopeResolution {
7948 Resolved(Vec<String>),
7949 Ambiguous,
7950 Missing,
7951}
7952
7953type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
7954
7955fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7956 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
7957}
7958
7959fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7960 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
7961}
7962
7963fn is_structurally_qualified(node: Node<'_>) -> bool {
7964 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
7965 && qualified_name_has_concrete_scope_separators(node)
7966}
7967
7968fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
7969 let Some(target_owner) = ctx.spec.owner.as_ref() else {
7970 return QualifiedOwnerResolution::Unresolved;
7971 };
7972 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
7973 return QualifiedOwnerResolution::Unresolved;
7974 };
7975 if !global
7981 && !matches!(
7982 enclosing_lexical_scope_components(
7983 node,
7984 &ctx.analyzer,
7985 ctx.visibility,
7986 ctx.file,
7987 ctx.source,
7988 ),
7989 LexicalScopeResolution::Resolved(_)
7990 )
7991 {
7992 return QualifiedOwnerResolution::Unresolved;
7993 }
7994 match resolve_type_components_lexically_at_for_target_with_scope_cache(
7995 node,
7996 &components,
7997 global,
7998 &ctx.analyzer,
7999 ctx.visibility,
8000 &ctx.ordinary_type_imports,
8001 ctx.file,
8002 ctx.source,
8003 target_owner,
8004 false,
8005 Some(&ctx.lexical_scope_cache),
8006 ) {
8007 LexicalTypeResolution::Resolved { unit: owner, .. } => {
8008 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) {
8009 return QualifiedOwnerResolution::Target;
8010 }
8011 match cached_declaring_member_owner(&owner, ctx) {
8012 EnclosingMemberOwnerResolution::Owner(declaring_owner)
8013 if receiver_owner_matches_target(
8014 &declaring_owner,
8015 target_owner,
8016 node.start_byte(),
8017 ctx,
8018 ) =>
8019 {
8020 QualifiedOwnerResolution::Target
8021 }
8022 EnclosingMemberOwnerResolution::Owner(declaring_owner)
8023 if receiver_owner_is_known_non_target(
8024 &declaring_owner,
8025 target_owner,
8026 node.start_byte(),
8027 ctx,
8028 ) =>
8029 {
8030 QualifiedOwnerResolution::NonTarget
8031 }
8032 EnclosingMemberOwnerResolution::Owner(_)
8033 | EnclosingMemberOwnerResolution::Ambiguous => QualifiedOwnerResolution::Unresolved,
8034 EnclosingMemberOwnerResolution::Missing
8035 if same_visible_symbol(&owner, target_owner) =>
8036 {
8037 QualifiedOwnerResolution::Unresolved
8038 }
8039 EnclosingMemberOwnerResolution::Missing => QualifiedOwnerResolution::NonTarget,
8040 }
8041 }
8042 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
8043 QualifiedOwnerResolution::Unresolved
8044 }
8045 }
8046}
8047
8048fn qualified_callable_owner_components(
8049 node: Node<'_>,
8050 source: &str,
8051) -> Option<(Vec<String>, bool)> {
8052 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8053 || !qualified_name_has_concrete_scope_separators(node)
8054 {
8055 return None;
8056 }
8057 let global = is_globally_qualified_cpp_name(node);
8058 let mut components = Vec::new();
8059 append_cpp_name_components(node, source, &mut components)?;
8060 components.pop()?;
8061 (!components.is_empty()).then_some((components, global))
8062}
8063
8064fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
8065 if !matches!(
8066 node.kind(),
8067 "identifier"
8068 | "type_identifier"
8069 | "namespace_identifier"
8070 | "qualified_identifier"
8071 | "scoped_identifier"
8072 | "scoped_type_identifier"
8073 | "template_type"
8074 | "template_function"
8075 ) {
8076 return None;
8077 }
8078 let mut components = Vec::new();
8079 append_cpp_name_components(node, source, &mut components)?;
8080 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
8081}
8082
8083pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
8084 let mut namespaces = Vec::new();
8085 let mut current = node.parent();
8086 while let Some(parent) = current {
8087 if parent.kind() == "namespace_definition"
8088 && let Some(name) = parent.child_by_field_name("name")
8089 {
8090 let mut components = Vec::new();
8091 if append_cpp_name_components(name, source, &mut components).is_some() {
8092 namespaces.push(components);
8093 }
8094 }
8095 current = parent.parent();
8096 }
8097 namespaces.reverse();
8098 namespaces.into_iter().flatten().collect()
8099}
8100
8101pub fn enclosing_lexical_scope_components(
8102 node: Node<'_>,
8103 analyzer: &CppGraphSource<'_>,
8104 visibility: &VisibilityIndex<'_>,
8105 file: &ProjectFile,
8106 source: &str,
8107) -> LexicalScopeResolution {
8108 enclosing_lexical_scope_components_with_unresolved_owner(
8109 node, analyzer, visibility, file, source, false, false,
8110 )
8111}
8112
8113#[allow(clippy::too_many_arguments)]
8114fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
8115 node: Node<'_>,
8116 analyzer: &CppGraphSource<'_>,
8117 visibility: &VisibilityIndex<'_>,
8118 file: &ProjectFile,
8119 source: &str,
8120 allow_structured_unresolved_owner: bool,
8121 ignore_function_owner: bool,
8122 cache: Option<&LexicalScopeCache>,
8123) -> LexicalScopeResolution {
8124 let Some(cache) = cache else {
8125 return enclosing_lexical_scope_components_with_unresolved_owner(
8126 node,
8127 analyzer,
8128 visibility,
8129 file,
8130 source,
8131 allow_structured_unresolved_owner,
8132 ignore_function_owner,
8133 );
8134 };
8135 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
8136 let key = (
8137 anchor_start,
8138 anchor_end,
8139 allow_structured_unresolved_owner,
8140 ignore_function_owner,
8141 );
8142 if let Some(cached) = cache.borrow().get(&key).cloned() {
8143 return cached;
8144 }
8145 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
8146 node,
8147 analyzer,
8148 visibility,
8149 file,
8150 source,
8151 allow_structured_unresolved_owner,
8152 ignore_function_owner,
8153 );
8154 cache.borrow_mut().insert(key, resolved.clone());
8155 resolved
8156}
8157
8158fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
8159 let mut current = node;
8160 loop {
8161 if matches!(
8162 current.kind(),
8163 "function_definition"
8164 | "class_specifier"
8165 | "struct_specifier"
8166 | "union_specifier"
8167 | "namespace_definition"
8168 | "translation_unit"
8169 ) {
8170 return (current.start_byte(), current.end_byte());
8171 }
8172 let Some(parent) = current.parent() else {
8173 return (current.start_byte(), current.end_byte());
8174 };
8175 current = parent;
8176 }
8177}
8178
8179fn enclosing_lexical_scope_components_with_unresolved_owner(
8180 node: Node<'_>,
8181 analyzer: &CppGraphSource<'_>,
8182 visibility: &VisibilityIndex<'_>,
8183 file: &ProjectFile,
8184 source: &str,
8185 allow_structured_unresolved_owner: bool,
8186 ignore_function_owner: bool,
8187) -> LexicalScopeResolution {
8188 #[cfg(any(test, feature = "test-support"))]
8189 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
8190 let mut namespaces = Vec::new();
8196 let mut classes = Vec::new();
8197 let mut function_definition = None;
8198 let mut displaced_class_scope = false;
8199 let mut current = node.parent();
8200 while let Some(parent) = current {
8201 match parent.kind() {
8202 "namespace_definition" => {
8203 if let Some(name) = parent.child_by_field_name("name") {
8204 let mut components = Vec::new();
8205 if append_cpp_name_components(name, source, &mut components).is_some() {
8206 namespaces.push(components);
8207 }
8208 }
8209 }
8210 "class_specifier" | "struct_specifier" | "union_specifier" => {
8211 if let Some(name) = parent.child_by_field_name("name") {
8212 let mut components = Vec::new();
8213 if append_cpp_name_components(name, source, &mut components).is_some() {
8214 classes.push(components);
8215 }
8216 }
8217 }
8218 "function_definition" => {
8219 if function_definition.is_none() {
8220 function_definition = Some(parent);
8221 }
8222 displaced_class_scope = displaced_class_scope
8223 || parent.child_by_field_name("type").is_some_and(|type_node| {
8224 matches!(
8225 type_node.kind(),
8226 "class_specifier" | "struct_specifier" | "union_specifier"
8227 )
8228 })
8229 || is_malformed_wrapper_function_definition(parent);
8230 }
8231 _ => {}
8232 }
8233 current = parent.parent();
8234 }
8235 namespaces.reverse();
8236 let namespace: Vec<String> = namespaces.into_iter().flatten().collect();
8237 let mut scope = namespace.clone();
8238 let has_qualified_function_owner = function_definition
8243 .filter(|function| !is_malformed_wrapper_function_definition(*function))
8244 .and_then(function_definition_owner_lookup_node)
8245 .is_some_and(|owner| {
8246 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
8247 });
8248 let indexed_scope = displaced_class_scope
8249 .then(|| {
8250 indexed_structural_class_scope(visibility, file, node, source)
8251 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
8252 })
8253 .flatten()
8254 .or_else(|| {
8255 (has_qualified_function_owner && function_definition.is_some())
8262 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
8263 .flatten()
8264 .filter(|indexed| {
8265 qualified_owner_scope_is_recoverable(
8266 indexed,
8267 &namespace,
8268 &classes,
8269 function_definition
8270 .and_then(function_definition_owner_lookup_node)
8271 .and_then(|owner| qualified_callable_owner_components(owner, source))
8272 .map(|(components, _)| components),
8273 )
8274 })
8275 })
8276 .or_else(|| {
8277 indexed_structural_class_scope(visibility, file, node, source).or_else(|| {
8285 indexed_enclosing_owner_scope(analyzer, visibility, file, node).filter(|indexed| {
8286 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
8287 })
8288 })
8289 })
8290 .or_else(|| {
8291 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
8295 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
8296 .flatten()
8297 .filter(|indexed| indexed.len() > namespace.len())
8298 });
8299 if let Some(indexed_scope) = indexed_scope.as_ref() {
8300 scope = indexed_scope.clone();
8305 classes.clear();
8306 }
8307
8308 if !ignore_function_owner
8309 && has_qualified_function_owner
8310 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
8311 {
8312 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
8313 return LexicalScopeResolution::Missing;
8314 };
8315 let imports = visibility.ordinary_type_import_cell(file);
8321 let owner_resolution = resolve_type_components_lexically_at_scoped(
8322 function,
8323 &owner,
8324 global,
8325 analyzer,
8326 visibility,
8327 &imports,
8328 file,
8329 source,
8330 None,
8331 false,
8332 false,
8333 namespace.clone(),
8334 );
8335 match owner_resolution {
8336 LexicalTypeResolution::Resolved {
8337 unit, components, ..
8338 } if is_indexed_class_owner(analyzer, &unit) => {
8339 scope = components;
8340 classes.clear();
8341 }
8342 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
8343 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Missing => {
8344 match visibility
8345 .resolve_type_components_lexically(analyzer, file, &owner, global, &scope)
8346 {
8347 LexicalTypeResolution::Resolved { components, .. } => scope = components,
8348 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
8349 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
8350 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
8351 qualified_owner_scope_is_recoverable(
8352 indexed,
8353 &namespace,
8354 &classes,
8355 Some(owner.clone()),
8356 )
8357 }) {
8358 scope = indexed.clone();
8359 } else {
8360 scope = if global || owner.starts_with(&namespace) {
8361 owner
8362 } else {
8363 let mut relative = namespace;
8364 relative.extend(owner);
8365 relative
8366 };
8367 }
8368 }
8369 LexicalTypeResolution::Missing => {
8370 match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
8380 Some(indexed) => scope = indexed,
8381 None => return LexicalScopeResolution::Missing,
8382 }
8383 }
8384 }
8385 }
8386 }
8387 }
8388
8389 classes.reverse();
8390 scope.extend(classes.into_iter().flatten());
8391 LexicalScopeResolution::Resolved(scope)
8392}
8393
8394fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
8395 let mut current = node.parent();
8396 while let Some(parent) = current {
8397 if parent.kind() == "function_definition"
8398 && parent.child_by_field_name("type").is_some_and(|type_node| {
8399 matches!(
8400 type_node.kind(),
8401 "class_specifier" | "struct_specifier" | "union_specifier"
8402 )
8403 })
8404 {
8405 return true;
8406 }
8407 current = parent.parent();
8408 }
8409 false
8410}
8411
8412fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
8413 let mut current = node.parent();
8414 while let Some(parent) = current {
8415 if parent.kind() == "function_definition"
8416 && is_malformed_wrapper_function_definition(parent)
8417 {
8418 return true;
8419 }
8420 current = parent.parent();
8421 }
8422 false
8423}
8424
8425fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
8426 node.has_error()
8427 && node
8428 .child_by_field_name("declarator")
8429 .is_some_and(|declarator| {
8430 declarator.kind() != "function_declarator"
8431 && first_descendant_of_kind(declarator, "function_declarator").is_none()
8432 })
8433}
8434
8435fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
8441 node.child_by_field_name("scope")
8442 .and_then(|scope| recovered_macro_decorated_type_node(scope))
8443 .is_some()
8444}
8445
8446fn indexed_structural_class_scope(
8447 visibility: &VisibilityIndex<'_>,
8448 file: &ProjectFile,
8449 node: Node<'_>,
8450 source: &str,
8451) -> Option<Vec<String>> {
8452 let mut current = node.parent();
8453 while let Some(parent) = current {
8454 if matches!(
8455 parent.kind(),
8456 "class_specifier" | "struct_specifier" | "union_specifier"
8457 ) {
8458 return visibility.indexed_structural_class_scope(file, parent, source);
8459 }
8460 current = parent.parent();
8461 }
8462 None
8463}
8464
8465fn qualified_owner_scope_is_recoverable(
8476 indexed: &[String],
8477 namespace: &[String],
8478 classes: &[Vec<String>],
8479 qualified_owner: Option<Vec<String>>,
8480) -> bool {
8481 if let Some(owner) = qualified_owner {
8482 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
8483 return false;
8484 }
8485 if namespace.is_empty() {
8491 return true;
8492 }
8493 if indexed.len() <= namespace.len() {
8494 return false;
8495 }
8496 let mut prefix = indexed.iter();
8497 return namespace
8498 .iter()
8499 .all(|component| prefix.any(|candidate| candidate == component));
8500 }
8501 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
8502 if !class_components.is_empty() {
8503 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
8504 }
8505 if namespace.is_empty() || indexed.len() <= namespace.len() {
8506 return false;
8507 }
8508 let mut prefix = indexed.iter();
8509 namespace
8510 .iter()
8511 .all(|component| prefix.any(|candidate| candidate == component))
8512}
8513
8514fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
8517 unit.is_class()
8518 && !analyzer
8519 .type_alias_provider()
8520 .is_some_and(|provider| provider.is_type_alias(unit))
8521}
8522
8523fn indexed_enclosing_owner_scope(
8537 analyzer: &CppGraphSource<'_>,
8538 visibility: &VisibilityIndex<'_>,
8539 file: &ProjectFile,
8540 node: Node<'_>,
8541) -> Option<Vec<String>> {
8542 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
8543}
8544
8545fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
8546 let start = enclosing_context(node, ctx).enclosing?;
8547 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
8548 ctx.analyzer.parent_of(unit)
8549 })
8550 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
8551}
8552
8553pub fn resolve_type_node_lexically(
8554 node: Node<'_>,
8555 analyzer: &CppGraphSource<'_>,
8556 visibility: &VisibilityIndex<'_>,
8557 ordinary_type_imports: &OrdinaryTypeImportCell,
8558 file: &ProjectFile,
8559 source: &str,
8560) -> LexicalTypeResolution {
8561 let Some((components, global)) = type_reference_components(node, source) else {
8562 return LexicalTypeResolution::Missing;
8563 };
8564 let resolution = resolve_type_components_lexically_at(
8565 node,
8566 &components,
8567 global,
8568 analyzer,
8569 visibility,
8570 ordinary_type_imports,
8571 file,
8572 source,
8573 );
8574 if !is_cpp_template_argument_type_leaf(node) {
8575 return resolution;
8576 }
8577
8578 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8585 return resolution;
8586 };
8587 let namespace_scope = enclosing_namespace_components(node, source);
8588 if indexed_scope.len() <= namespace_scope.len() {
8589 return resolution;
8590 }
8591 let indexed = visibility.resolve_type_components_lexically(
8592 analyzer,
8593 file,
8594 &components,
8595 global,
8596 &indexed_scope,
8597 );
8598 match indexed {
8599 LexicalTypeResolution::Resolved { ref unit, .. }
8600 if !visibility
8601 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8602 {
8603 resolution
8604 }
8605 LexicalTypeResolution::Resolved { .. } => indexed,
8606 _ => resolution,
8607 }
8608}
8609
8610#[allow(clippy::too_many_arguments)]
8611pub fn resolve_type_node_lexically_for_target(
8612 node: Node<'_>,
8613 analyzer: &CppGraphSource<'_>,
8614 visibility: &VisibilityIndex<'_>,
8615 ordinary_type_imports: &OrdinaryTypeImportCell,
8616 file: &ProjectFile,
8617 source: &str,
8618 target: &CodeUnit,
8619 scope_cache: Option<&LexicalScopeCache>,
8620 recovered_scope: Option<&[String]>,
8621) -> LexicalTypeResolution {
8622 let Some((reference_components, global)) = type_reference_components(node, source) else {
8623 return LexicalTypeResolution::Missing;
8624 };
8625 let terminal = reference_components
8626 .last()
8627 .expect("type reference components are non-empty");
8628 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
8629 return LexicalTypeResolution::Missing;
8630 }
8631 let template_arguments = cpp_template_reference_arguments(node, source);
8632 let selects_concrete_specialization =
8633 template_arguments.is_some() && visibility.is_template_specialization(target);
8634 if !selects_concrete_specialization
8635 && !visibility.structured_type_reference_may_resolve_to_target(
8636 analyzer,
8637 file,
8638 std::slice::from_ref(terminal),
8639 false,
8640 &[],
8641 target,
8642 )
8643 {
8644 return LexicalTypeResolution::Missing;
8645 }
8646 if let Some(arguments) = template_arguments.as_ref() {
8647 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
8648 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
8649 node,
8650 &reference_components,
8651 global,
8652 analyzer,
8653 visibility,
8654 ordinary_type_imports,
8655 file,
8656 source,
8657 recovered_scope,
8658 )
8659 } else {
8660 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
8661 node,
8662 &reference_components,
8663 global,
8664 analyzer,
8665 visibility,
8666 ordinary_type_imports,
8667 file,
8668 source,
8669 scope_cache,
8670 )
8671 };
8672 return match alias_resolution {
8673 LexicalTypeResolution::Resolved {
8674 unit,
8675 components,
8676 candidates,
8677 } if visibility.template_alias_arguments_preserve_target(
8678 analyzer, file, &unit, arguments, target,
8679 ) =>
8680 {
8681 LexicalTypeResolution::Resolved {
8682 unit: target.clone(),
8683 components,
8684 candidates,
8685 }
8686 }
8687 LexicalTypeResolution::Resolved {
8688 unit,
8689 components,
8690 candidates,
8691 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
8692 Ok(resolved_unit) => {
8693 let target_guided = (!same_visible_symbol(&resolved_unit, target))
8694 .then(|| {
8695 target_guided_malformed_template_alias_resolution(
8696 node,
8697 analyzer,
8698 visibility,
8699 file,
8700 arguments,
8701 &reference_components,
8702 target,
8703 )
8704 })
8705 .flatten();
8706 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
8707 unit: resolved_unit,
8708 components,
8709 candidates,
8710 })
8711 }
8712 Err(_) => LexicalTypeResolution::Ambiguous,
8713 },
8714 LexicalTypeResolution::Missing => {
8715 let target_preserving = if let Some(recovered_scope) = recovered_scope {
8716 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8717 node,
8718 &reference_components,
8719 global,
8720 analyzer,
8721 visibility,
8722 ordinary_type_imports,
8723 file,
8724 source,
8725 target,
8726 true,
8727 recovered_scope,
8728 )
8729 } else {
8730 resolve_type_components_lexically_at_for_target_with_scope_cache(
8731 node,
8732 &reference_components,
8733 global,
8734 analyzer,
8735 visibility,
8736 ordinary_type_imports,
8737 file,
8738 source,
8739 target,
8740 true,
8741 scope_cache,
8742 )
8743 };
8744 match target_preserving {
8745 LexicalTypeResolution::Resolved {
8746 unit: _,
8747 components,
8748 candidates,
8749 } if template_reference_candidates_select_target(
8750 node,
8751 &candidates,
8752 analyzer,
8753 visibility,
8754 file,
8755 source,
8756 target,
8757 ) =>
8758 {
8759 LexicalTypeResolution::Resolved {
8760 unit: target.clone(),
8761 components,
8762 candidates,
8763 }
8764 }
8765 _ => target_guided_malformed_template_alias_resolution(
8766 node,
8767 analyzer,
8768 visibility,
8769 file,
8770 arguments,
8771 &reference_components,
8772 target,
8773 )
8774 .unwrap_or(LexicalTypeResolution::Missing),
8775 }
8776 }
8777 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
8778 };
8779 }
8780 let resolution = if let Some(recovered_scope) = recovered_scope {
8781 resolve_type_components_lexically_at_for_target_with_recovered_scope(
8782 node,
8783 &reference_components,
8784 global,
8785 analyzer,
8786 visibility,
8787 ordinary_type_imports,
8788 file,
8789 source,
8790 target,
8791 true,
8792 recovered_scope,
8793 )
8794 } else {
8795 resolve_type_components_lexically_at_for_target_with_scope_cache(
8796 node,
8797 &reference_components,
8798 global,
8799 analyzer,
8800 visibility,
8801 ordinary_type_imports,
8802 file,
8803 source,
8804 target,
8805 true,
8806 scope_cache,
8807 )
8808 };
8809 let resolution = if matches!(resolution, LexicalTypeResolution::Missing) {
8810 target_guided_qualified_namespace_function_type_resolution(
8811 node,
8812 &reference_components,
8813 global,
8814 analyzer,
8815 visibility,
8816 ordinary_type_imports,
8817 file,
8818 source,
8819 target,
8820 )
8821 .unwrap_or(resolution)
8822 } else {
8823 resolution
8824 };
8825 if !is_cpp_template_argument_type_leaf(node) {
8826 return resolution;
8827 }
8828
8829 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
8837 return resolution;
8838 };
8839 let namespace_scope = enclosing_namespace_components(node, source);
8840 if indexed_scope.len() <= namespace_scope.len() {
8841 return resolution;
8842 }
8843 let indexed = visibility.resolve_type_components_lexically_for_target(
8844 analyzer,
8845 file,
8846 &reference_components,
8847 global,
8848 &indexed_scope,
8849 target,
8850 );
8851 match indexed {
8852 LexicalTypeResolution::Resolved {
8853 ref unit,
8854 ref candidates,
8855 ..
8856 } if (same_visible_symbol(unit, target)
8857 || candidates
8858 .iter()
8859 .any(|candidate| same_visible_symbol(candidate, target)))
8860 && visibility
8861 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8862 {
8863 indexed
8864 }
8865 _ => resolution,
8866 }
8867}
8868
8869#[allow(clippy::too_many_arguments)]
8879fn target_guided_qualified_namespace_function_type_resolution(
8880 node: Node<'_>,
8881 components: &[String],
8882 global: bool,
8883 analyzer: &CppGraphSource<'_>,
8884 visibility: &VisibilityIndex<'_>,
8885 ordinary_type_imports: &OrdinaryTypeImportCell,
8886 file: &ProjectFile,
8887 source: &str,
8888 target: &CodeUnit,
8889) -> Option<LexicalTypeResolution> {
8890 let function_definition = std::iter::successors(Some(node), |current| current.parent())
8891 .find(|current| current.kind() == "function_definition")?;
8892 let function = function_definition_name_node(function_definition)?;
8893 let (owner, owner_global) = qualified_callable_owner_components(function, source)?;
8894 let target_namespace = target.package_name();
8895 if target_namespace.is_empty() {
8896 return None;
8897 }
8898 let target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8899 brokk_bifrost_core::analyzer::Language::Cpp,
8900 target_namespace,
8901 );
8902 if (owner_global && target_scope != owner) || (!owner_global && !target_scope.ends_with(&owner))
8903 {
8904 return None;
8905 }
8906
8907 let function_name = node_text(function_terminal_node(function), source);
8908 let definition_arity = signature_arity(Some(node_text(function_definition, source)));
8909 let declaration_proves_namespace = visibility
8910 .visible_identifier_candidates(file, function_name)
8911 .filter(|candidate| {
8912 candidate.is_function()
8913 && type_owner_of(analyzer, candidate).is_none()
8914 && candidate.package_name() == target_namespace
8915 })
8916 .any(|candidate| cpp_callable_arity(analyzer, candidate).accepts(definition_arity));
8917 if !declaration_proves_namespace {
8918 return None;
8919 }
8920
8921 let resolution = resolve_type_components_lexically_at_scoped(
8922 node,
8923 components,
8924 global,
8925 analyzer,
8926 visibility,
8927 ordinary_type_imports,
8928 file,
8929 source,
8930 Some(target),
8931 true,
8932 false,
8933 target_scope,
8934 );
8935 match &resolution {
8936 LexicalTypeResolution::Resolved {
8937 unit, candidates, ..
8938 } if (same_visible_symbol(unit, target)
8939 || candidates
8940 .iter()
8941 .any(|candidate| same_visible_symbol(candidate, target)))
8942 && visibility
8943 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
8944 {
8945 Some(resolution)
8946 }
8947 LexicalTypeResolution::Resolved { .. }
8948 | LexicalTypeResolution::Ambiguous
8949 | LexicalTypeResolution::Missing => None,
8950 }
8951}
8952
8953#[allow(clippy::too_many_arguments)]
8954fn target_guided_malformed_template_alias_resolution(
8955 node: Node<'_>,
8956 analyzer: &CppGraphSource<'_>,
8957 visibility: &VisibilityIndex<'_>,
8958 file: &ProjectFile,
8959 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
8960 components: &[String],
8961 target: &CodeUnit,
8962) -> Option<LexicalTypeResolution> {
8963 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
8964 return None;
8965 }
8966
8967 let identifier = &components[0];
8968 let namespace =
8969 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
8970 let namespace_name = namespace.join("::");
8971 let candidates = visibility
8972 .visible_identifier_candidates(file, identifier)
8973 .filter(|candidate| {
8974 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
8975 && visibility.type_candidate_may_be_visible_before_reference(
8976 analyzer,
8977 file,
8978 candidate,
8979 node.start_byte(),
8980 )
8981 })
8982 .cloned()
8983 .collect::<Vec<_>>();
8984 let first = candidates.first()?;
8985 if !candidates
8986 .iter()
8987 .all(|candidate| same_logical_symbol(first, candidate))
8988 || !candidates.iter().all(|candidate| {
8989 visibility.template_alias_arguments_preserve_target(
8990 analyzer, file, candidate, arguments, target,
8991 )
8992 })
8993 {
8994 return None;
8995 }
8996
8997 let mut resolved_components = namespace;
8998 resolved_components.push(identifier.clone());
8999 Some(LexicalTypeResolution::Resolved {
9000 unit: target.clone(),
9001 components: resolved_components,
9002 candidates,
9003 })
9004}
9005
9006fn resolve_type_node_lexically_for_target_without_visibility(
9007 node: Node<'_>,
9008 analyzer: &CppGraphSource<'_>,
9009 visibility: &VisibilityIndex<'_>,
9010 file: &ProjectFile,
9011 source: &str,
9012 target: &CodeUnit,
9013) -> LexicalTypeResolution {
9014 let Some((components, global)) = type_reference_components(node, source) else {
9015 return LexicalTypeResolution::Missing;
9016 };
9017 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
9018 node,
9019 analyzer,
9020 visibility,
9021 file,
9022 source,
9023 true,
9024 recovered_macro_decorated_declarator_type(node)
9025 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9026 ) {
9027 LexicalScopeResolution::Resolved(scope) => scope,
9028 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9029 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9030 };
9031 visibility.resolve_type_components_lexically_for_target(
9032 analyzer,
9033 file,
9034 &components,
9035 global,
9036 &lexical_scope,
9037 target,
9038 )
9039}
9040
9041fn type_node_has_exact_target_identity_without_visibility(
9042 node: Node<'_>,
9043 analyzer: &CppGraphSource<'_>,
9044 visibility: &VisibilityIndex<'_>,
9045 file: &ProjectFile,
9046 source: &str,
9047 target: &CodeUnit,
9048) -> bool {
9049 let Some((components, global)) = type_reference_components(node, source) else {
9050 return false;
9051 };
9052 let LexicalScopeResolution::Resolved(lexical_scope) =
9053 enclosing_lexical_scope_components_with_unresolved_owner(
9054 node,
9055 analyzer,
9056 visibility,
9057 file,
9058 source,
9059 true,
9060 recovered_macro_decorated_declarator_type(node)
9061 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9062 )
9063 else {
9064 return false;
9065 };
9066 let target_name = cpp_name_for(target);
9067 lexical_component_tiers(&components, global, &lexical_scope)
9068 .any(|qualified| qualified.join("::") == target_name)
9069}
9070
9071pub fn resolve_using_enum_declaration_owner(
9072 node: Node<'_>,
9073 analyzer: &CppGraphSource<'_>,
9074 visibility: &VisibilityIndex<'_>,
9075 ordinary_type_imports: &OrdinaryTypeImportCell,
9076 file: &ProjectFile,
9077 source: &str,
9078) -> LexicalTypeResolution {
9079 let Some(type_node) = using_enum_declaration_type_node(node) else {
9080 return LexicalTypeResolution::Missing;
9081 };
9082 let mut components = Vec::new();
9083 if append_cpp_name_components(type_node, source, &mut components).is_none()
9084 || components.is_empty()
9085 {
9086 return LexicalTypeResolution::Missing;
9087 }
9088 resolve_type_components_lexically_at(
9089 type_node,
9090 &components,
9091 is_globally_qualified_cpp_name(type_node),
9092 analyzer,
9093 visibility,
9094 ordinary_type_imports,
9095 file,
9096 source,
9097 )
9098}
9099
9100pub fn resolve_ordinary_using_declaration_owner(
9101 node: Node<'_>,
9102 analyzer: &CppGraphSource<'_>,
9103 visibility: &VisibilityIndex<'_>,
9104 file: &ProjectFile,
9105 source: &str,
9106) -> LexicalTypeResolution {
9107 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
9108 return LexicalTypeResolution::Missing;
9109 };
9110 let mut components = Vec::new();
9111 if append_cpp_name_components(type_node, source, &mut components).is_none()
9112 || components.len() < 2
9113 {
9114 return LexicalTypeResolution::Missing;
9115 }
9116 let lexical_scope =
9117 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
9118 LexicalScopeResolution::Resolved(scope) => scope,
9119 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9120 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9121 };
9122 visibility.resolve_type_components_lexically(
9123 analyzer,
9124 file,
9125 &components,
9126 is_globally_qualified_cpp_name(type_node),
9127 &lexical_scope,
9128 )
9129}
9130
9131pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
9132 (node.kind() == "using_declaration"
9133 && (0..node.child_count()).any(|index| {
9134 node.child(index)
9135 .is_some_and(|child| child.kind() == "enum")
9136 }))
9137 .then(|| node.named_child(0))
9138 .flatten()
9139}
9140
9141pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
9142 (node.kind() == "using_declaration"
9143 && using_enum_declaration_type_node(node).is_none()
9144 && using_namespace_directive_name_node(node).is_none())
9145 .then(|| node.named_child(0))
9146 .flatten()
9147}
9148
9149fn recovered_macro_using_declaration_type_node<'tree>(
9156 node: Node<'tree>,
9157 source: &str,
9158) -> Option<(Node<'tree>, bool)> {
9159 if node.kind() != "declaration" {
9160 return None;
9161 }
9162 let macro_type = node.child_by_field_name("type")?;
9163 if macro_type.kind() != "type_identifier"
9164 || !cpp_export_macro_token(node_text(macro_type, source))
9165 {
9166 return None;
9167 }
9168 let declarator = node.child_by_field_name("declarator")?;
9169 if declarator.kind() != "qualified_identifier" {
9170 return None;
9171 }
9172 let scope = declarator.child_by_field_name("scope")?;
9173 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
9174 return None;
9175 }
9176 let target = declarator.child_by_field_name("name")?;
9177 let mut components = Vec::new();
9178 append_cpp_name_components(target, source, &mut components)?;
9179 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
9180}
9181
9182fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
9183 let is_directive = node.kind() == "using_directive"
9184 || (node.kind() == "using_declaration"
9185 && (0..node.child_count()).any(|index| {
9186 node.child(index)
9187 .is_some_and(|child| child.kind() == "namespace")
9188 }));
9189 if !is_directive {
9190 return None;
9191 }
9192 node.child_by_field_name("name")
9193 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
9194}
9195
9196fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
9197 let mut current = node.parent();
9198 while let Some(parent) = current {
9199 if matches!(
9200 parent.kind(),
9201 "compound_statement"
9202 | "function_definition"
9203 | "lambda_expression"
9204 | "for_statement"
9205 | "while_statement"
9206 | "if_statement"
9207 | "class_specifier"
9208 | "struct_specifier"
9209 | "union_specifier"
9210 ) {
9211 return None;
9212 }
9213 current = parent.parent();
9214 }
9215 Some(enclosing_namespace_components(node, source))
9216}
9217
9218fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize, bool)> {
9219 let mut current = node.parent();
9220 while let Some(scope) = current {
9221 if matches!(
9222 scope.kind(),
9223 "compound_statement"
9224 | "declaration_list"
9225 | "field_declaration_list"
9226 | "translation_unit"
9227 ) {
9228 let mut depth = 0;
9229 let mut ancestor = scope.parent();
9230 while let Some(parent) = ancestor {
9231 depth += 1;
9232 ancestor = parent.parent();
9233 }
9234 return Some((
9235 scope.start_byte(),
9236 scope.end_byte(),
9237 depth,
9238 scope.kind() == "compound_statement",
9239 ));
9240 }
9241 current = scope.parent();
9242 }
9243 None
9244}
9245
9246pub fn build_source_using_index(cpp: &dyn CppSource, file: &ProjectFile) -> SourceUsingIndex {
9253 let Some(prepared) = cpp.prepared_syntax(file) else {
9254 return SourceUsingIndex::default();
9255 };
9256 collect_source_using_index(cpp, file, prepared.tree().root_node(), prepared.source())
9257}
9258
9259fn collect_source_using_index(
9260 cpp: &dyn CppSource,
9261 source_file: &ProjectFile,
9262 root: Node<'_>,
9263 source: &str,
9264) -> SourceUsingIndex {
9265 #[cfg(not(any(test, feature = "test-support")))]
9266 let _ = cpp;
9267 let mut index = SourceUsingIndex::default();
9268 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
9269 let mut stack = vec![root];
9270 while let Some(node) = stack.pop() {
9271 let target = match node.kind() {
9272 "using_directive" | "using_declaration" => {
9273 if let Some(namespace_node) = using_namespace_directive_name_node(node) {
9274 let mut namespace_components = Vec::new();
9275 append_cpp_name_components(namespace_node, source, &mut namespace_components)
9276 .map(|_| EffectiveUsingTarget::Namespace {
9277 namespace_components,
9278 global: is_globally_qualified_cpp_name(namespace_node),
9279 })
9280 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
9281 let mut target_components = Vec::new();
9282 (append_cpp_name_components(type_node, source, &mut target_components)
9283 .is_some()
9284 && target_components.len() >= 2)
9285 .then(|| EffectiveUsingTarget::Ordinary {
9286 name: target_components
9287 .last()
9288 .expect("ordinary using has a terminal component")
9289 .clone(),
9290 target_components,
9291 global: is_globally_qualified_cpp_name(type_node),
9292 })
9293 } else {
9294 None
9295 }
9296 }
9297 "declaration" => recovered_macro_using_declaration_type_node(node, source).and_then(
9298 |(type_node, global)| {
9299 let mut target_components = Vec::new();
9300 (append_cpp_name_components(type_node, source, &mut target_components)
9301 .is_some()
9302 && target_components.len() >= 2)
9303 .then(|| EffectiveUsingTarget::Ordinary {
9304 name: target_components
9305 .last()
9306 .expect("recovered ordinary using has a terminal component")
9307 .clone(),
9308 target_components,
9309 global,
9310 })
9311 },
9312 ),
9313 _ => None,
9314 };
9315 if let Some(target) = target {
9316 #[cfg(any(test, feature = "test-support"))]
9322 cpp.record_using_guard_context_inspection_for_test();
9323 let required_guards = if callable_preprocessor_context_is_visible(node, source) {
9324 Some(HashSet::default())
9325 } else {
9326 preprocessor_guard_environment(node, source)
9327 };
9328 let Some(required_guards) = required_guards else {
9329 let mut cursor = node.walk();
9330 stack.extend(node.children(&mut cursor));
9331 continue;
9332 };
9333 if let Some((scope_start, scope_end, scope_depth, block_scope)) =
9334 ordinary_using_scope(node)
9335 {
9336 let declaration_namespace = enclosing_namespace_components(node, source);
9337 let declaration_namespace = if declaration_namespace.is_empty() {
9338 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
9339 .unwrap_or(declaration_namespace)
9340 } else {
9341 declaration_namespace
9342 };
9343 let namespace_scope = using_named_scope(node, source);
9344 let lexical_depth = declaration_namespace.len();
9345 let binding = OrdinaryTypeImport {
9346 target,
9347 source: source_file.clone(),
9348 declaration_byte: node.end_byte(),
9349 scope_start,
9350 scope_end,
9351 scope_depth,
9352 block_scope,
9353 lexical_depth,
9354 declaration_namespace,
9355 namespace_scope,
9356 resolved_target_components: None,
9357 required_guards,
9358 };
9359 match &binding.target {
9360 EffectiveUsingTarget::Ordinary { name, .. } => index
9361 .ordinary_by_name
9362 .entry(name.clone())
9363 .or_default()
9364 .push(binding),
9365 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
9366 }
9367 }
9368 }
9369 let mut cursor = node.walk();
9370 stack.extend(node.children(&mut cursor));
9371 }
9372 index
9373}
9374
9375struct OrphanedNamespaceEnvelope {
9376 body_end: usize,
9377 components: Vec<String>,
9378 class_names: HashSet<String>,
9379}
9380
9381fn collect_orphaned_namespace_envelopes(
9388 root: Node<'_>,
9389 source: &str,
9390) -> Vec<OrphanedNamespaceEnvelope> {
9391 let mut envelopes = Vec::new();
9392 let mut stack = vec![root];
9393 while let Some(current) = stack.pop() {
9394 if current.kind() == "namespace_definition"
9395 && let Some(body) = current.child_by_field_name("body")
9396 && current.end_byte() == body.end_byte()
9397 && let Some(name) = current.child_by_field_name("name")
9398 {
9399 let mut components = enclosing_namespace_components(current, source);
9400 if append_cpp_name_components(name, source, &mut components).is_some()
9401 && !components.is_empty()
9402 {
9403 let mut class_names = HashSet::default();
9404 let mut body_stack = vec![body];
9405 while let Some(node) = body_stack.pop() {
9406 if let Some(name) = orphaned_class_definition_name(node, source) {
9407 class_names.insert(name);
9408 }
9409 let mut cursor = node.walk();
9410 if node.kind() == "ERROR" {
9411 body_stack.extend(node.children(&mut cursor));
9412 } else {
9413 body_stack.extend(node.named_children(&mut cursor));
9414 }
9415 }
9416 envelopes.push(OrphanedNamespaceEnvelope {
9417 body_end: body.end_byte(),
9418 components,
9419 class_names,
9420 });
9421 }
9422 }
9423 let mut cursor = current.walk();
9424 stack.extend(current.named_children(&mut cursor));
9425 }
9426 envelopes
9427}
9428
9429fn collect_orphaned_namespace_type_envelopes(
9434 root: Node<'_>,
9435 source: &str,
9436) -> Vec<OrphanedNamespaceEnvelope> {
9437 let mut envelopes = Vec::new();
9438 let mut stack = vec![root];
9439 while let Some(current) = stack.pop() {
9440 if current.kind() == "namespace_definition"
9441 && current.has_error()
9442 && let Some(body) = current.child_by_field_name("body")
9443 && current.end_byte() == body.end_byte()
9444 && let Some(name) = current.child_by_field_name("name")
9445 {
9446 let mut components = enclosing_namespace_components(current, source);
9447 if append_cpp_name_components(name, source, &mut components).is_some() {
9448 envelopes.push(OrphanedNamespaceEnvelope {
9449 body_end: body.end_byte(),
9450 components,
9451 class_names: HashSet::default(),
9452 });
9453 }
9454 }
9455 if !current.has_error() {
9456 continue;
9457 }
9458 let mut cursor = current.walk();
9459 stack.extend(
9460 current
9461 .named_children(&mut cursor)
9462 .filter(|child| child.has_error()),
9463 );
9464 }
9465 envelopes
9466}
9467
9468fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
9469 if matches!(
9470 node.kind(),
9471 "class_specifier" | "struct_specifier" | "union_specifier"
9472 ) {
9473 let body = node.child_by_field_name("body")?;
9474 let name = node.child_by_field_name("name")?;
9475 return (!name.is_missing() && !body.is_missing())
9476 .then(|| node_text(name, source).to_string());
9477 }
9478 if node.kind() != "ERROR" {
9479 return None;
9480 }
9481
9482 for index in 0..node.child_count() {
9488 let Some(keyword) = node.child(index) else {
9489 continue;
9490 };
9491 if !matches!(keyword.kind(), "class" | "struct" | "union") {
9492 continue;
9493 }
9494 let mut name = None;
9495 for next_index in (index + 1)..node.child_count() {
9496 let Some(next) = node.child(next_index) else {
9497 continue;
9498 };
9499 if next.kind() == ";" {
9500 break;
9501 }
9502 if next.kind() == "{" {
9503 return name
9504 .filter(|name_node: &Node<'_>| !name_node.is_missing())
9505 .map(|name_node| node_text(name_node, source).to_string());
9506 }
9507 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
9508 name = Some(next);
9509 }
9510 }
9511 }
9512 None
9513}
9514
9515fn recovered_orphaned_namespace_components(
9516 node: Node<'_>,
9517 source: &str,
9518 envelopes: &[OrphanedNamespaceEnvelope],
9519) -> Option<Vec<String>> {
9520 let owner_name = orphaned_using_owner_name(node, source)?;
9521 envelopes
9522 .iter()
9523 .filter(|envelope| {
9524 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
9525 })
9526 .max_by_key(|envelope| envelope.body_end)
9527 .map(|envelope| envelope.components.clone())
9528}
9529
9530fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
9531 let function = std::iter::successors(node.parent(), |current| current.parent())
9532 .find(|current| current.kind() == "function_definition")?;
9533 let owner = function_definition_owner_lookup_node(function)?;
9534 let scope = owner.child_by_field_name("scope")?;
9535 let mut components = Vec::new();
9536 append_cpp_name_components(scope, source, &mut components)?;
9537 if components.len() != 1 {
9544 return None;
9545 }
9546 components.pop()
9547}
9548
9549fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
9550 let mut project = ProjectUsingIndex::default();
9551 for source_file in visibility.all_visible_source_files() {
9552 let source_index = visibility.cpp().source_using_index(&source_file);
9556 for (name, bindings) in &source_index.ordinary_by_name {
9557 project
9558 .ordinary_by_name
9559 .entry(name.clone())
9560 .or_default()
9561 .extend(bindings.iter().cloned());
9562 }
9563 project
9564 .directives
9565 .extend(source_index.directives.iter().cloned());
9566 }
9567 project
9568}
9569
9570fn project_using_index<'a>(visibility: &'a VisibilityIndex<'_>) -> &'a ProjectUsingIndex {
9571 visibility.project_using_index(|| build_project_using_index(visibility))
9572}
9573
9574pub fn prewarm_project_using_index(visibility: &VisibilityIndex<'_>) {
9580 let _ = project_using_index(visibility);
9581}
9582
9583fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
9584 let (components, global) = match &binding.target {
9585 EffectiveUsingTarget::Ordinary {
9586 target_components,
9587 global,
9588 ..
9589 } => (target_components, *global),
9590 EffectiveUsingTarget::Namespace {
9591 namespace_components,
9592 global,
9593 } => (namespace_components, *global),
9594 };
9595 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
9596}
9597
9598fn using_binding_target_components_for_name(
9599 binding: &OrdinaryTypeImport,
9600 project: &ProjectUsingIndex,
9601 visibility: &VisibilityIndex<'_>,
9602 file: &ProjectFile,
9603 name: &str,
9604) -> Option<Vec<String>> {
9605 let cpp_source = CppGraphSource::from_source(visibility.cpp());
9608 let visible_candidates = visibility
9609 .visible_identifier_candidates(file, name)
9610 .filter(|candidate| {
9611 candidate.is_class()
9612 || is_type_alias(candidate)
9613 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
9614 })
9615 .collect::<Vec<_>>();
9616 if visible_candidates.is_empty() {
9617 return None;
9618 }
9619 match &binding.target {
9620 EffectiveUsingTarget::Ordinary {
9621 name: imported_name,
9622 ..
9623 } if imported_name == name => {
9624 effective_using_target_tiers(binding)
9625 .into_iter()
9626 .find(|qualified| {
9627 let qualified_name = qualified.join("::");
9628 visible_candidates
9629 .iter()
9630 .any(|candidate| cpp_name_for(candidate) == qualified_name)
9631 })
9632 }
9633 EffectiveUsingTarget::Namespace { .. } => {
9634 visibility.note_using_namespace_lookup_for_test();
9635 let target_tiers = effective_using_target_tiers(binding);
9636 let resolved = target_tiers
9637 .iter()
9638 .find(|namespace_components| {
9639 let namespace = namespace_components.join("::");
9640 visible_candidates.iter().any(|candidate| {
9641 visibility.note_using_name_candidate_inspection_for_test();
9642 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
9643 candidate_namespace == namespace
9644 || candidate_namespace.starts_with(&format!("{namespace}::"))
9645 })
9646 }) || project.directives.iter().any(|candidate| {
9647 candidate.namespace_scope.as_deref()
9648 == Some(namespace_components.as_slice())
9649 }) || project
9650 .ordinary_by_name
9651 .values()
9652 .flatten()
9653 .any(|candidate| {
9654 candidate.namespace_scope.as_deref()
9655 == Some(namespace_components.as_slice())
9656 })
9657 })
9658 .cloned();
9659 resolved.or_else(|| {
9660 (target_tiers.len() == 1)
9666 .then(|| target_tiers.into_iter().next())
9667 .flatten()
9668 })
9669 }
9670 EffectiveUsingTarget::Ordinary { .. } => None,
9671 }
9672}
9673
9674fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
9675 let start = activation.checked_sub(1)?;
9676 let mut node = root.descendant_for_byte_range(start, activation)?;
9677 while node.kind() != "preproc_include" {
9678 node = node.parent()?;
9679 }
9680 Some(node)
9681}
9682
9683fn project_using_bindings(
9684 binding: OrdinaryTypeImport,
9685 visibility: &VisibilityIndex<'_>,
9686 file: &ProjectFile,
9687 root: Node<'_>,
9688 source: &str,
9689) -> Vec<OrdinaryTypeImport> {
9690 if binding.source == *file {
9691 return vec![binding];
9692 }
9693 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
9694 return Vec::new();
9695 }
9696 visibility.note_using_donor_activation_for_test();
9697 let Some(prepared) = visibility.cpp().prepared_syntax(file) else {
9698 return Vec::new();
9699 };
9700 let projections = visibility
9701 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
9702 .map_or_else(
9703 || {
9704 visibility.conditional_include_projections_for_source(
9705 file,
9706 prepared.as_ref(),
9707 &binding.source,
9708 )
9709 },
9710 |activation_byte| {
9711 Arc::from([ConditionalIncludeProjection {
9712 activation_byte,
9713 required_guards: HashSet::default(),
9714 }])
9715 },
9716 );
9717 projections
9718 .iter()
9719 .cloned()
9720 .filter_map(|projection| {
9721 let required_guards =
9722 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
9723 let mut projected = binding.clone();
9724 projected.required_guards = required_guards;
9725 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
9726 })
9727 .collect()
9728}
9729
9730fn project_using_binding_at_activation(
9731 mut binding: OrdinaryTypeImport,
9732 activation: usize,
9733 root: Node<'_>,
9734 source: &str,
9735) -> Option<OrdinaryTypeImport> {
9736 let include = include_node_for_activation(root, activation)?;
9737 let include_namespace = enclosing_namespace_components(include, source);
9738 let mut declaration_namespace = include_namespace.clone();
9739 declaration_namespace.extend(binding.declaration_namespace);
9740 binding.declaration_namespace = declaration_namespace;
9741 binding.declaration_byte = activation;
9742 if let Some(prefix) = using_named_scope(include, source) {
9743 let mut projected = prefix;
9744 projected.extend(binding.namespace_scope.take().unwrap_or_default());
9745 binding.scope_depth = projected.len();
9746 binding.block_scope = false;
9747 binding.lexical_depth = projected.len();
9748 binding.namespace_scope = Some(projected);
9749 binding.scope_start = 0;
9750 binding.scope_end = usize::MAX;
9751 Some(binding)
9752 } else if let Some((start, end, depth, block_scope)) = ordinary_using_scope(include) {
9753 binding.namespace_scope = None;
9754 binding.scope_start = start;
9755 binding.scope_end = end;
9756 binding.scope_depth = depth;
9757 binding.block_scope = block_scope;
9758 binding.lexical_depth = include_namespace.len();
9759 Some(binding)
9760 } else {
9761 None
9762 }
9763}
9764
9765pub fn effective_using_bindings_for_name(
9771 visibility: &VisibilityIndex<'_>,
9772 imports: &OrdinaryTypeImportCell,
9773 file: &ProjectFile,
9774 node: Node<'_>,
9775 source: &str,
9776 name: &str,
9777) -> Arc<[OrdinaryTypeImport]> {
9778 imports
9779 .projection_cell(name)
9780 .get_or_init(|| {
9781 let project = project_using_index(visibility);
9782 let name_bindings = project.ordinary_by_name.get(name);
9783 if name_bindings.is_none() && project.directives.is_empty() {
9784 return Arc::from(Vec::new());
9785 }
9786 let root = root_node(node);
9787 let mut projected = Vec::new();
9788 for binding in name_bindings
9789 .into_iter()
9790 .flatten()
9791 .chain(project.directives.iter())
9792 {
9793 if !visibility.source_is_visible(file, &binding.source) {
9794 continue;
9795 }
9796 let target_components = using_binding_target_components_for_name(
9797 binding, project, visibility, file, name,
9798 )
9799 .or_else(|| match &binding.target {
9800 EffectiveUsingTarget::Ordinary {
9801 name: imported_name,
9802 target_components,
9803 ..
9804 } if imported_name == name => Some(target_components.clone()),
9805 EffectiveUsingTarget::Ordinary { .. }
9806 | EffectiveUsingTarget::Namespace { .. } => None,
9807 });
9808 let Some(target_components) = target_components else {
9809 continue;
9810 };
9811 let mut binding = binding.clone();
9812 binding.resolved_target_components = Some(target_components);
9813 projected.extend(project_using_bindings(
9814 binding, visibility, file, root, source,
9815 ));
9816 }
9817 Arc::from(projected)
9818 })
9819 .clone()
9820}
9821
9822pub fn initialized_ordinary_type_imports(
9823 root: Node<'_>,
9824 analyzer: &CppGraphSource<'_>,
9825 visibility: &VisibilityIndex<'_>,
9826 file: &ProjectFile,
9827 source: &str,
9828) -> OrdinaryTypeImportCell {
9829 let cell = visibility.ordinary_type_import_cell(file);
9830 let _ = (root, analyzer, source);
9831 cell
9832}
9833
9834fn root_node(mut node: Node<'_>) -> Node<'_> {
9835 while let Some(parent) = node.parent() {
9836 node = parent;
9837 }
9838 node
9839}
9840
9841fn effective_using_binding_active(
9846 binding: &OrdinaryTypeImport,
9847 node: Node<'_>,
9848 lexical_scope: &[String],
9849 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9850 visibility: &VisibilityIndex<'_>,
9851 file: &ProjectFile,
9852) -> bool {
9853 effective_using_binding_guards_active(
9854 binding,
9855 node.start_byte(),
9856 reference_guards,
9857 visibility,
9858 file,
9859 ) && binding.namespace_scope.as_ref().map_or_else(
9860 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
9861 |namespace| lexical_scope.starts_with(namespace),
9862 )
9863}
9864
9865fn effective_using_binding_guards_active(
9866 binding: &OrdinaryTypeImport,
9867 reference_byte: usize,
9868 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9869 visibility: &VisibilityIndex<'_>,
9870 file: &ProjectFile,
9871) -> bool {
9872 binding.declaration_byte <= reference_byte
9873 && reference_guards.is_some_and(|active| binding.required_guards.is_subset(active))
9874 && visibility.preprocessor_guards_stable_between(
9875 file,
9876 0,
9877 reference_byte,
9878 &binding.required_guards,
9879 )
9880}
9881
9882fn effective_using_binding_guards_compatible(
9883 binding: &OrdinaryTypeImport,
9884 reference_byte: usize,
9885 reference_guards: Option<&HashSet<PreprocessorGuard>>,
9886 visibility: &VisibilityIndex<'_>,
9887 file: &ProjectFile,
9888) -> bool {
9889 binding.source != *file
9890 && !binding.required_guards.is_empty()
9891 && binding.declaration_byte <= reference_byte
9892 && reference_guards.is_some_and(|active| {
9893 !binding.required_guards.is_subset(active)
9894 && merge_preprocessor_guards(&binding.required_guards, active).is_some()
9895 })
9896 && visibility.preprocessor_guards_stable_between(
9897 file,
9898 0,
9899 reference_byte,
9900 &binding.required_guards,
9901 )
9902}
9903
9904#[allow(clippy::too_many_arguments)]
9905fn binding_type_candidates(
9906 binding: &OrdinaryTypeImport,
9907 active_bindings: &[&OrdinaryTypeImport],
9908 analyzer: &CppGraphSource<'_>,
9909 visibility: &VisibilityIndex<'_>,
9910 file: &ProjectFile,
9911 name: &str,
9912 direct_target: Option<&CodeUnit>,
9913 reference_byte: usize,
9914) -> Vec<(CodeUnit, Vec<String>)> {
9915 let Some(qualified) = binding.resolved_target_components.clone() else {
9916 return Vec::new();
9917 };
9918 let mut targets = Vec::new();
9919 match binding.target {
9920 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
9921 EffectiveUsingTarget::Namespace { .. } => {
9922 let mut stack = vec![qualified];
9923 let mut visited = HashSet::default();
9924 while let Some(namespace) = stack.pop() {
9925 if !visited.insert(namespace.clone()) {
9926 continue;
9927 }
9928 let mut target = namespace.clone();
9929 target.push(name.to_string());
9930 targets.push(target);
9931 stack.extend(active_bindings.iter().filter_map(|candidate| {
9932 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
9933 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
9934 .then(|| candidate.resolved_target_components.clone())
9935 .flatten()
9936 }));
9937 }
9938 }
9939 }
9940 targets
9941 .into_iter()
9942 .flat_map(|target| {
9943 let mut candidates = visibility
9944 .visible_identifier_candidates(file, name)
9945 .filter(|candidate| {
9946 (candidate.is_class() || is_type_alias(candidate))
9947 && type_candidate_matches_lookup_components(
9948 analyzer,
9949 visibility,
9950 file,
9951 candidate,
9952 reference_byte,
9953 &target,
9954 )
9955 })
9956 .cloned()
9957 .collect::<Vec<_>>();
9958 if candidates.is_empty()
9959 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9960 && let Some(target_unit) = direct_target
9961 {
9962 let expanded_target_name = macro_expanded_cpp_name_components(
9963 visibility,
9964 file,
9965 target_unit,
9966 reference_byte,
9967 );
9968 if (target_unit.is_class() || is_type_alias(target_unit))
9969 && expanded_target_name == target
9970 && visibility.external_type_candidate_visible_at(
9971 file,
9972 target_unit,
9973 reference_byte,
9974 )
9975 {
9976 candidates.push(target_unit.clone());
9977 }
9978 }
9979 if candidates.is_empty()
9980 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
9981 && let Some(target_unit) = direct_target
9982 {
9983 let visible_types = visibility
9984 .visible_identifier_candidates(file, name)
9985 .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
9986 .collect::<Vec<_>>();
9987 let uniquely_names_target = !visible_types.is_empty()
9988 && visible_types
9989 .iter()
9990 .all(|candidate| same_visible_symbol(candidate, target_unit));
9991 if uniquely_names_target {
9992 candidates.extend(visible_types.into_iter().cloned());
9993 }
9994 }
9995 candidates
9996 .into_iter()
9997 .map(move |candidate| (candidate, target.clone()))
9998 })
9999 .collect()
10000}
10001
10002fn macro_expanded_cpp_name_components(
10003 visibility: &VisibilityIndex<'_>,
10004 file: &ProjectFile,
10005 unit: &CodeUnit,
10006 reference_byte: usize,
10007) -> Vec<String> {
10008 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10009 brokk_bifrost_core::analyzer::Language::Cpp,
10010 &cpp_name_for(unit),
10011 )
10012 .into_iter()
10013 .flat_map(|component| {
10014 macro_expanded_cpp_name_component(visibility, file, component, reference_byte)
10015 })
10016 .collect()
10017}
10018
10019fn macro_expanded_cpp_name_component(
10020 visibility: &VisibilityIndex<'_>,
10021 file: &ProjectFile,
10022 component: String,
10023 reference_byte: usize,
10024) -> Vec<String> {
10025 let Some(replacement) =
10026 visibility.object_macro_replacement_at(file, &component, reference_byte)
10027 else {
10028 return vec![component];
10029 };
10030 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10031 brokk_bifrost_core::analyzer::Language::Cpp,
10032 &replacement,
10033 );
10034 if expanded.is_empty() {
10035 vec![component]
10036 } else {
10037 expanded
10038 }
10039}
10040
10041fn type_candidate_matches_lookup_components(
10049 analyzer: &CppGraphSource<'_>,
10050 visibility: &VisibilityIndex<'_>,
10051 file: &ProjectFile,
10052 candidate: &CodeUnit,
10053 reference_byte: usize,
10054 target: &[String],
10055) -> bool {
10056 let expanded = macro_expanded_cpp_name_components(visibility, file, candidate, reference_byte);
10057 if expanded == target {
10058 return true;
10059 }
10060 let Some(cpp) = analyzer.cpp else {
10061 return false;
10062 };
10063 let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
10064 return false;
10065 };
10066 let root = prepared.tree().root_node();
10067 for range in analyzer.ranges(candidate) {
10068 let Some(mut current) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
10069 else {
10070 continue;
10071 };
10072 let mut namespaces = Vec::<(Vec<String>, bool)>::new();
10073 loop {
10074 if current.kind() == "namespace_definition"
10075 && let Some(name) = current.child_by_field_name("name")
10076 {
10077 let mut components = Vec::new();
10078 if append_cpp_name_components(name, prepared.source(), &mut components).is_some()
10079 && !components.is_empty()
10080 {
10081 let inline = (0..current.child_count())
10082 .filter_map(|index| current.child(index))
10083 .any(|child| !child.is_named() && child.kind() == "inline");
10084 namespaces.push((components, inline));
10085 }
10086 }
10087 let Some(parent) = current.parent() else {
10088 break;
10089 };
10090 current = parent;
10091 }
10092 namespaces.reverse();
10093 let mut namespace_components = Vec::new();
10094 let mut inline_indexes = HashSet::default();
10095 for (components, inline) in namespaces {
10096 for component in components {
10097 let expanded_component =
10098 macro_expanded_cpp_name_component(visibility, file, component, reference_byte);
10099 if inline {
10100 inline_indexes.extend(
10101 namespace_components.len()
10102 ..namespace_components.len() + expanded_component.len(),
10103 );
10104 }
10105 namespace_components.extend(expanded_component);
10106 }
10107 }
10108 if inline_indexes.is_empty() || !expanded.starts_with(&namespace_components) {
10109 continue;
10110 }
10111 let mut reachable = vec![false; target.len() + 1];
10115 reachable[0] = true;
10116 for (index, component) in expanded.iter().enumerate() {
10117 let mut next = vec![false; target.len() + 1];
10118 for (target_index, reached) in reachable.iter().copied().enumerate() {
10119 if !reached {
10120 continue;
10121 }
10122 if inline_indexes.contains(&index) {
10123 next[target_index] = true;
10124 }
10125 if target
10126 .get(target_index)
10127 .is_some_and(|target_component| target_component == component)
10128 {
10129 next[target_index + 1] = true;
10130 }
10131 }
10132 reachable = next;
10133 }
10134 if reachable[target.len()] {
10135 return true;
10136 }
10137 }
10138 false
10139}
10140
10141#[allow(clippy::too_many_arguments)]
10142fn resolved_type_import(
10143 candidates: Vec<(CodeUnit, Vec<String>)>,
10144 lexical_depth: usize,
10145 is_direct: bool,
10146 analyzer: &CppGraphSource<'_>,
10147 visibility: &VisibilityIndex<'_>,
10148 file: &ProjectFile,
10149 direct_target: Option<&CodeUnit>,
10150) -> OrdinaryTypeImportResolution {
10151 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
10152 for candidate in candidates {
10153 if !logical
10154 .iter()
10155 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
10156 {
10157 logical.push(candidate);
10158 }
10159 }
10160 let selected = match logical.as_slice() {
10161 [] => return OrdinaryTypeImportResolution::Missing,
10162 [only] => only,
10163 several => {
10168 let units = several
10169 .iter()
10170 .map(|(unit, _)| unit)
10171 .collect::<Vec<&CodeUnit>>();
10172 let Some(spelling) = direct_target.and_then(|target| {
10173 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
10174 }) else {
10175 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
10176 };
10177 several
10178 .iter()
10179 .find(|(unit, _)| same_symbol(unit, spelling))
10180 .expect("the selected spelling is one of the imported candidates")
10181 }
10182 };
10183 OrdinaryTypeImportResolution::Resolved {
10184 target: selected.0.clone(),
10185 target_components: selected.1.clone(),
10186 lexical_depth,
10187 is_direct,
10188 }
10189}
10190
10191#[allow(clippy::too_many_arguments)]
10192fn ordinary_type_import_resolution(
10193 node: Node<'_>,
10194 components: &[String],
10195 global: bool,
10196 analyzer: &CppGraphSource<'_>,
10197 visibility: &VisibilityIndex<'_>,
10198 imports: &OrdinaryTypeImportCell,
10199 file: &ProjectFile,
10200 source: &str,
10201 lexical_scope: &[String],
10202 direct_target: Option<&CodeUnit>,
10203) -> OrdinaryTypeImportResolution {
10204 if global || components.len() != 1 {
10205 return OrdinaryTypeImportResolution::Missing;
10206 }
10207 let name = &components[0];
10208 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
10209 if bindings.is_empty() {
10213 return OrdinaryTypeImportResolution::Missing;
10214 }
10215 let reference_guards = preprocessor_guard_environment(node, source);
10216 let active = bindings
10217 .iter()
10218 .filter(|binding| {
10219 effective_using_binding_active(
10220 binding,
10221 node,
10222 lexical_scope,
10223 reference_guards.as_ref(),
10224 visibility,
10225 file,
10226 )
10227 })
10228 .collect::<Vec<_>>();
10229 let transitive = bindings
10230 .iter()
10231 .filter(|binding| {
10232 effective_using_binding_guards_active(
10233 binding,
10234 node.start_byte(),
10235 reference_guards.as_ref(),
10236 visibility,
10237 file,
10238 ) && (binding.namespace_scope.is_some()
10239 || (binding.scope_start <= node.start_byte()
10240 && node.end_byte() <= binding.scope_end))
10241 })
10242 .collect::<Vec<_>>();
10243 ordinary_type_import_resolution_for_bindings(
10244 node,
10245 name,
10246 analyzer,
10247 visibility,
10248 file,
10249 lexical_scope,
10250 direct_target,
10251 &active,
10252 &transitive,
10253 )
10254}
10255
10256#[allow(clippy::too_many_arguments)]
10257fn compatible_foreign_type_import_resolution(
10258 node: Node<'_>,
10259 components: &[String],
10260 global: bool,
10261 analyzer: &CppGraphSource<'_>,
10262 visibility: &VisibilityIndex<'_>,
10263 imports: &OrdinaryTypeImportCell,
10264 file: &ProjectFile,
10265 source: &str,
10266 lexical_scope: &[String],
10267 direct_target: &CodeUnit,
10268) -> OrdinaryTypeImportResolution {
10269 if global || components.len() != 1 {
10270 return OrdinaryTypeImportResolution::Missing;
10271 }
10272 let name = &components[0];
10273 let bindings = effective_using_bindings_for_name(visibility, imports, file, node, source, name);
10274 if bindings.is_empty() {
10275 return OrdinaryTypeImportResolution::Missing;
10276 }
10277 let reference_guards = preprocessor_guard_environment(node, source);
10278 let compatible = bindings
10279 .iter()
10280 .filter(|binding| {
10281 effective_using_binding_guards_compatible(
10282 binding,
10283 node.start_byte(),
10284 reference_guards.as_ref(),
10285 visibility,
10286 file,
10287 ) && binding.namespace_scope.as_ref().map_or_else(
10288 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
10289 |namespace| lexical_scope.starts_with(namespace),
10290 )
10291 })
10292 .collect::<Vec<_>>();
10293 let transitive = bindings
10294 .iter()
10295 .filter(|binding| {
10296 effective_using_binding_guards_compatible(
10297 binding,
10298 node.start_byte(),
10299 reference_guards.as_ref(),
10300 visibility,
10301 file,
10302 ) && (binding.namespace_scope.is_some()
10303 || (binding.scope_start <= node.start_byte()
10304 && node.end_byte() <= binding.scope_end))
10305 })
10306 .collect::<Vec<_>>();
10307 ordinary_type_import_resolution_for_bindings(
10308 node,
10309 name,
10310 analyzer,
10311 visibility,
10312 file,
10313 lexical_scope,
10314 Some(direct_target),
10315 &compatible,
10316 &transitive,
10317 )
10318}
10319
10320#[allow(clippy::too_many_arguments)]
10321fn ordinary_type_import_resolution_for_bindings(
10322 node: Node<'_>,
10323 name: &str,
10324 analyzer: &CppGraphSource<'_>,
10325 visibility: &VisibilityIndex<'_>,
10326 file: &ProjectFile,
10327 lexical_scope: &[String],
10328 direct_target: Option<&CodeUnit>,
10329 active: &[&OrdinaryTypeImport],
10330 transitive: &[&OrdinaryTypeImport],
10331) -> OrdinaryTypeImportResolution {
10332 let mut concrete_depths = active
10333 .iter()
10334 .filter(|binding| binding.namespace_scope.is_none())
10335 .map(|binding| binding.scope_depth)
10336 .collect::<Vec<_>>();
10337 concrete_depths.sort_unstable();
10338 concrete_depths.dedup();
10339 for depth in concrete_depths.into_iter().rev() {
10340 let at_tier = active
10341 .iter()
10342 .copied()
10343 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
10344 let direct = at_tier
10345 .clone()
10346 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
10347 .flat_map(|binding| {
10348 binding_type_candidates(
10349 binding,
10350 transitive,
10351 analyzer,
10352 visibility,
10353 file,
10354 name,
10355 direct_target,
10356 node.start_byte(),
10357 )
10358 })
10359 .collect::<Vec<_>>();
10360 if !direct.is_empty() {
10361 return resolved_type_import(
10362 direct,
10363 lexical_scope.len(),
10364 true,
10365 analyzer,
10366 visibility,
10367 file,
10368 direct_target,
10369 );
10370 }
10371 let directives = at_tier
10372 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
10373 .flat_map(|binding| {
10374 binding_type_candidates(
10375 binding,
10376 transitive,
10377 analyzer,
10378 visibility,
10379 file,
10380 name,
10381 direct_target,
10382 node.start_byte(),
10383 )
10384 })
10385 .collect::<Vec<_>>();
10386 if !directives.is_empty() {
10387 return resolved_type_import(
10388 directives,
10389 lexical_scope.len(),
10390 false,
10391 analyzer,
10392 visibility,
10393 file,
10394 direct_target,
10395 );
10396 }
10397 }
10398 for prefix_len in (0..=lexical_scope.len()).rev() {
10399 let tier = &lexical_scope[..prefix_len];
10400 let at_tier = active
10401 .iter()
10402 .copied()
10403 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
10404 let direct = at_tier
10405 .clone()
10406 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
10407 .flat_map(|binding| {
10408 binding_type_candidates(
10409 binding,
10410 transitive,
10411 analyzer,
10412 visibility,
10413 file,
10414 name,
10415 direct_target,
10416 node.start_byte(),
10417 )
10418 })
10419 .collect::<Vec<_>>();
10420 if !direct.is_empty() {
10421 return resolved_type_import(
10422 direct,
10423 prefix_len,
10424 true,
10425 analyzer,
10426 visibility,
10427 file,
10428 direct_target,
10429 );
10430 }
10431 let directives = at_tier
10432 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
10433 .flat_map(|binding| {
10434 binding_type_candidates(
10435 binding,
10436 transitive,
10437 analyzer,
10438 visibility,
10439 file,
10440 name,
10441 direct_target,
10442 node.start_byte(),
10443 )
10444 })
10445 .collect::<Vec<_>>();
10446 if !directives.is_empty() {
10447 return resolved_type_import(
10448 directives,
10449 prefix_len,
10450 false,
10451 analyzer,
10452 visibility,
10453 file,
10454 direct_target,
10455 );
10456 }
10457 }
10458 OrdinaryTypeImportResolution::Missing
10459}
10460
10461#[allow(clippy::too_many_arguments)]
10462pub fn resolve_type_components_lexically_at(
10463 node: Node<'_>,
10464 components: &[String],
10465 global: bool,
10466 analyzer: &CppGraphSource<'_>,
10467 visibility: &VisibilityIndex<'_>,
10468 ordinary_type_imports: &OrdinaryTypeImportCell,
10469 file: &ProjectFile,
10470 source: &str,
10471) -> LexicalTypeResolution {
10472 resolve_type_components_lexically_at_inner(
10473 node,
10474 components,
10475 global,
10476 analyzer,
10477 visibility,
10478 ordinary_type_imports,
10479 file,
10480 source,
10481 None,
10482 false,
10483 false,
10484 None,
10485 )
10486}
10487
10488#[allow(clippy::too_many_arguments)]
10496pub fn resolve_type_components_lexically_at_preserving_alias(
10497 node: Node<'_>,
10498 components: &[String],
10499 global: bool,
10500 analyzer: &CppGraphSource<'_>,
10501 visibility: &VisibilityIndex<'_>,
10502 file: &ProjectFile,
10503 source: &str,
10504) -> LexicalTypeResolution {
10505 let ordinary_type_imports =
10506 initialized_ordinary_type_imports(root_node(node), analyzer, visibility, file, source);
10507 resolve_type_components_lexically_at_inner(
10508 node,
10509 components,
10510 global,
10511 analyzer,
10512 visibility,
10513 &ordinary_type_imports,
10514 file,
10515 source,
10516 None,
10517 false,
10518 true,
10519 None,
10520 )
10521}
10522
10523#[allow(clippy::too_many_arguments)]
10524fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
10525 node: Node<'_>,
10526 components: &[String],
10527 global: bool,
10528 analyzer: &CppGraphSource<'_>,
10529 visibility: &VisibilityIndex<'_>,
10530 ordinary_type_imports: &OrdinaryTypeImportCell,
10531 file: &ProjectFile,
10532 source: &str,
10533 scope_cache: Option<&LexicalScopeCache>,
10534) -> LexicalTypeResolution {
10535 resolve_type_components_lexically_at_inner(
10536 node,
10537 components,
10538 global,
10539 analyzer,
10540 visibility,
10541 ordinary_type_imports,
10542 file,
10543 source,
10544 None,
10545 false,
10546 true,
10547 scope_cache,
10548 )
10549}
10550
10551#[allow(clippy::too_many_arguments)]
10552fn resolve_type_components_lexically_at_for_target_with_scope_cache(
10553 node: Node<'_>,
10554 components: &[String],
10555 global: bool,
10556 analyzer: &CppGraphSource<'_>,
10557 visibility: &VisibilityIndex<'_>,
10558 ordinary_type_imports: &OrdinaryTypeImportCell,
10559 file: &ProjectFile,
10560 source: &str,
10561 target: &CodeUnit,
10562 apply_structured_prefilter: bool,
10563 scope_cache: Option<&LexicalScopeCache>,
10564) -> LexicalTypeResolution {
10565 resolve_type_components_lexically_at_inner(
10566 node,
10567 components,
10568 global,
10569 analyzer,
10570 visibility,
10571 ordinary_type_imports,
10572 file,
10573 source,
10574 Some(target),
10575 apply_structured_prefilter,
10576 false,
10577 scope_cache,
10578 )
10579}
10580
10581#[allow(clippy::too_many_arguments)]
10582fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
10583 node: Node<'_>,
10584 components: &[String],
10585 global: bool,
10586 analyzer: &CppGraphSource<'_>,
10587 visibility: &VisibilityIndex<'_>,
10588 ordinary_type_imports: &OrdinaryTypeImportCell,
10589 file: &ProjectFile,
10590 source: &str,
10591 recovered_scope: &[String],
10592) -> LexicalTypeResolution {
10593 resolve_type_components_in_authoritative_scope(
10594 node,
10595 components,
10596 global,
10597 analyzer,
10598 visibility,
10599 ordinary_type_imports,
10600 file,
10601 source,
10602 None,
10603 false,
10604 true,
10605 recovered_scope.to_vec(),
10606 )
10607}
10608
10609#[allow(clippy::too_many_arguments)]
10610fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
10611 node: Node<'_>,
10612 components: &[String],
10613 global: bool,
10614 analyzer: &CppGraphSource<'_>,
10615 visibility: &VisibilityIndex<'_>,
10616 ordinary_type_imports: &OrdinaryTypeImportCell,
10617 file: &ProjectFile,
10618 source: &str,
10619 target: &CodeUnit,
10620 apply_structured_prefilter: bool,
10621 recovered_scope: &[String],
10622) -> LexicalTypeResolution {
10623 resolve_type_components_in_authoritative_scope(
10624 node,
10625 components,
10626 global,
10627 analyzer,
10628 visibility,
10629 ordinary_type_imports,
10630 file,
10631 source,
10632 Some(target),
10633 apply_structured_prefilter,
10634 false,
10635 recovered_scope.to_vec(),
10636 )
10637}
10638
10639#[allow(clippy::too_many_arguments)]
10640fn resolve_type_components_lexically_at_inner(
10641 node: Node<'_>,
10642 components: &[String],
10643 global: bool,
10644 analyzer: &CppGraphSource<'_>,
10645 visibility: &VisibilityIndex<'_>,
10646 ordinary_type_imports: &OrdinaryTypeImportCell,
10647 file: &ProjectFile,
10648 source: &str,
10649 direct_target: Option<&CodeUnit>,
10650 apply_structured_prefilter: bool,
10651 preserve_alias: bool,
10652 scope_cache: Option<&LexicalScopeCache>,
10653) -> LexicalTypeResolution {
10654 let lexical_scope = if global {
10655 Vec::new()
10656 } else {
10657 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
10658 node,
10659 analyzer,
10660 visibility,
10661 file,
10662 source,
10663 true,
10664 recovered_macro_decorated_declarator_type(node)
10665 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
10666 scope_cache,
10667 ) {
10668 LexicalScopeResolution::Resolved(scope) => scope,
10669 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
10670 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
10671 }
10672 };
10673 resolve_type_components_lexically_at_scoped(
10674 node,
10675 components,
10676 global,
10677 analyzer,
10678 visibility,
10679 ordinary_type_imports,
10680 file,
10681 source,
10682 direct_target,
10683 apply_structured_prefilter,
10684 preserve_alias,
10685 lexical_scope,
10686 )
10687}
10688
10689#[allow(clippy::too_many_arguments)]
10690fn resolve_type_components_lexically_at_scoped(
10691 node: Node<'_>,
10692 components: &[String],
10693 global: bool,
10694 analyzer: &CppGraphSource<'_>,
10695 visibility: &VisibilityIndex<'_>,
10696 ordinary_type_imports: &OrdinaryTypeImportCell,
10697 file: &ProjectFile,
10698 source: &str,
10699 direct_target: Option<&CodeUnit>,
10700 apply_structured_prefilter: bool,
10701 preserve_alias: bool,
10702 mut lexical_scope: Vec<String>,
10703) -> LexicalTypeResolution {
10704 if !global
10705 && components.len() == 1
10706 && let Some(target) = direct_target
10709 && lexical_scope
10710 .last()
10711 .is_none_or(|last| last != &components[0])
10712 && has_malformed_wrapper_function_definition_ancestor(node)
10717 && let Some(indexed_namespace) =
10718 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
10719 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
10720 {
10721 lexical_scope = indexed_namespace;
10722 }
10723 resolve_type_components_in_authoritative_scope(
10724 node,
10725 components,
10726 global,
10727 analyzer,
10728 visibility,
10729 ordinary_type_imports,
10730 file,
10731 source,
10732 direct_target,
10733 apply_structured_prefilter,
10734 preserve_alias,
10735 lexical_scope,
10736 )
10737}
10738
10739#[allow(clippy::too_many_arguments)]
10745fn resolve_type_components_in_authoritative_scope(
10746 node: Node<'_>,
10747 components: &[String],
10748 global: bool,
10749 analyzer: &CppGraphSource<'_>,
10750 visibility: &VisibilityIndex<'_>,
10751 ordinary_type_imports: &OrdinaryTypeImportCell,
10752 file: &ProjectFile,
10753 source: &str,
10754 direct_target: Option<&CodeUnit>,
10755 apply_structured_prefilter: bool,
10756 preserve_alias: bool,
10757 lexical_scope: Vec<String>,
10758) -> LexicalTypeResolution {
10759 if apply_structured_prefilter
10760 && direct_target.is_some()
10761 && !preserve_alias
10762 && !global
10763 && components.len() == 1
10764 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
10765 {
10766 return LexicalTypeResolution::Missing;
10767 }
10768 if !global
10774 && components.len() == 1
10775 && recovered_macro_decorated_type_node(node).is_some()
10776 && let Some(resolution) = recovered_same_file_type_alias_resolution(
10777 node,
10778 components,
10779 analyzer,
10780 visibility,
10781 file,
10782 direct_target,
10783 &lexical_scope,
10784 )
10785 {
10786 return resolution;
10787 }
10788 if apply_structured_prefilter
10789 && let Some(target) = direct_target
10790 && !preserve_alias
10791 && !visibility.structured_type_reference_may_resolve_to_target(
10792 analyzer,
10793 file,
10794 components,
10795 global,
10796 &lexical_scope,
10797 target,
10798 )
10799 {
10800 return LexicalTypeResolution::Missing;
10801 }
10802 let normal = if preserve_alias {
10803 visibility.resolve_type_components_lexically_for_forward(
10804 analyzer,
10805 file,
10806 components,
10807 global,
10808 &lexical_scope,
10809 )
10810 } else {
10811 direct_target.map_or_else(
10812 || {
10813 visibility.resolve_type_components_lexically(
10814 analyzer,
10815 file,
10816 components,
10817 global,
10818 &lexical_scope,
10819 )
10820 },
10821 |target| {
10822 visibility.resolve_type_components_lexically_for_target(
10823 analyzer,
10824 file,
10825 components,
10826 global,
10827 &lexical_scope,
10828 target,
10829 )
10830 },
10831 )
10832 };
10833 let normal = match normal {
10834 LexicalTypeResolution::Resolved { ref unit, .. }
10835 if !visibility
10836 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
10837 {
10838 LexicalTypeResolution::Missing
10839 }
10840 resolution => resolution,
10841 };
10842 let normal_depth = match &normal {
10843 LexicalTypeResolution::Resolved { components, .. } => {
10844 Some(components.len().saturating_sub(1))
10845 }
10846 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
10847 };
10848 match ordinary_type_import_resolution(
10854 node,
10855 components,
10856 global,
10857 analyzer,
10858 visibility,
10859 ordinary_type_imports,
10860 file,
10861 source,
10862 &lexical_scope,
10863 direct_target,
10864 ) {
10865 OrdinaryTypeImportResolution::Missing => normal,
10866 OrdinaryTypeImportResolution::Resolved {
10867 lexical_depth,
10868 is_direct,
10869 ..
10870 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
10871 || normal_depth.is_some_and(|depth| {
10872 depth > lexical_depth || (!is_direct && depth == lexical_depth)
10873 }) =>
10874 {
10875 normal
10876 }
10877 OrdinaryTypeImportResolution::Resolved {
10878 target,
10879 target_components,
10880 ..
10881 } => visibility.resolve_imported_type_candidate(
10882 analyzer,
10883 file,
10884 &target,
10885 &target_components,
10886 direct_target,
10887 preserve_alias,
10888 ),
10889 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
10890 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
10891 {
10892 normal
10893 }
10894 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
10895 }
10896}
10897
10898fn recovered_same_file_type_alias_resolution(
10899 node: Node<'_>,
10900 components: &[String],
10901 analyzer: &CppGraphSource<'_>,
10902 visibility: &VisibilityIndex<'_>,
10903 file: &ProjectFile,
10904 direct_target: Option<&CodeUnit>,
10905 lexical_scope: &[String],
10906) -> Option<LexicalTypeResolution> {
10907 debug_assert_eq!(components.len(), 1);
10908 debug_assert!(recovered_macro_decorated_type_node(node).is_some());
10909 let alias_provider = analyzer.type_alias_provider()?;
10910 for qualified in lexical_component_tiers(components, false, lexical_scope) {
10911 let candidates = visibility
10912 .visible_identifier_candidates(file, &components[0])
10913 .filter(|candidate| {
10914 candidate.source() == file
10915 && canonical_cpp_scope_components(candidate) == qualified
10916 && visibility
10917 .external_type_candidate_visible_in_context(analyzer, file, candidate, node)
10918 })
10919 .collect::<Vec<_>>();
10920 if candidates.is_empty() {
10921 continue;
10922 }
10923 if candidates
10924 .iter()
10925 .any(|candidate| !alias_provider.is_type_alias(candidate))
10926 {
10927 return None;
10928 }
10929 let unit = if let Some(target) = direct_target {
10930 visibility.unique_type_candidate_preserving_target(
10931 analyzer,
10932 file,
10933 &candidates,
10934 target,
10935 )?
10936 } else {
10937 let first = candidates[0];
10938 if candidates
10939 .iter()
10940 .any(|candidate| !same_visible_symbol(candidate, first))
10941 {
10942 return None;
10943 }
10944 first.clone()
10945 };
10946 return Some(LexicalTypeResolution::Resolved {
10947 unit,
10948 components: qualified,
10949 candidates: candidates.into_iter().cloned().collect(),
10950 });
10951 }
10952 None
10953}
10954
10955fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10956 matches!(
10957 structured_owner_context_resolution(node, ctx),
10958 StructuredOwnerContextResolution::SelfTarget
10959 | StructuredOwnerContextResolution::InheritedTarget
10960 )
10961}
10962
10963fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
10967 let Some(call) = node.parent().filter(|parent| {
10968 parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node)
10969 }) else {
10970 return matches!(
10971 structured_owner_context_resolution(node, ctx),
10972 StructuredOwnerContextResolution::InheritedTarget
10973 );
10974 };
10975 let Some(target_owner) = ctx.spec.owner.as_ref() else {
10976 return false;
10977 };
10978 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
10979 return false;
10980 };
10981 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
10982 return false;
10983 }
10984 let Some(arity) = ctx
10985 .visibility
10986 .call_arity_evidence(ctx.file, call, ctx.source)
10987 .exact()
10988 else {
10989 return false;
10990 };
10991 matches!(
10992 resolve_declaring_callable_owner(
10993 &ctx.analyzer,
10994 ctx.visibility,
10995 ctx.file,
10996 cached_declaring_member_owner(&enclosing_owner, ctx),
10997 &ctx.spec.member_name,
10998 arity,
10999 ),
11000 EnclosingMemberOwnerResolution::Owner(owner)
11001 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx)
11002 )
11003}
11004
11005fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11006 matches!(
11007 structured_owner_context_resolution(node, ctx),
11008 StructuredOwnerContextResolution::NonTarget
11009 )
11010}
11011
11012fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
11013 let Some(target_owner) = ctx.spec.owner.as_ref() else {
11014 return false;
11015 };
11016 let mut current = node.parent();
11017 while let Some(parent) = current {
11018 if parent.kind() == "function_definition" {
11019 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
11020 return false;
11021 };
11022 if let Some(owners) = out_of_line_member_definition_owner(
11023 &ctx.analyzer,
11024 ctx.visibility,
11025 ctx.file,
11026 ctx.source,
11027 owner_lookup,
11028 ) && let Some((_, owner)) = owners.innermost()
11029 {
11030 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
11031 }
11032 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
11033 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
11034 }
11035 return false;
11036 }
11037 current = parent.parent();
11038 }
11039 false
11040}
11041
11042#[derive(Clone, Copy)]
11043enum StructuredOwnerContextResolution {
11044 SelfTarget,
11047 InheritedTarget,
11052 NonTarget,
11053 Ambiguous,
11054 Missing,
11055}
11056
11057fn structured_owner_context_resolution(
11058 node: Node<'_>,
11059 ctx: &ScanCtx<'_>,
11060) -> StructuredOwnerContextResolution {
11061 let Some(target_owner) = ctx.spec.owner.as_ref() else {
11062 return StructuredOwnerContextResolution::Missing;
11063 };
11064 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
11065 return StructuredOwnerContextResolution::Missing;
11066 };
11067 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
11068 return StructuredOwnerContextResolution::SelfTarget;
11069 }
11070 let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
11073 match member_owner {
11074 EnclosingMemberOwnerResolution::Owner(owner)
11075 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
11076 {
11077 StructuredOwnerContextResolution::InheritedTarget
11078 }
11079 EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
11080 EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
11081 EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
11082 }
11083}
11084
11085fn cached_declaring_member_owner(
11086 receiver_owner: &CodeUnit,
11087 ctx: &ScanCtx<'_>,
11088) -> EnclosingMemberOwnerResolution {
11089 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
11090 return cached;
11091 }
11092 let resolved = resolve_declaring_member_owner(
11093 &ctx.analyzer,
11094 ctx.visibility,
11095 ctx.file,
11096 receiver_owner,
11097 &ctx.spec.member_name,
11098 );
11099 let resolved = if matches!(resolved, EnclosingMemberOwnerResolution::Missing) {
11100 indexed_declaring_owner_for_recovered_member(receiver_owner, ctx)
11101 } else {
11102 resolved
11103 };
11104 ctx.member_owner_cache
11105 .borrow_mut()
11106 .insert(receiver_owner.clone(), resolved.clone());
11107 resolved
11108}
11109
11110fn indexed_declaring_owner_for_recovered_member(
11117 receiver_owner: &CodeUnit,
11118 ctx: &ScanCtx<'_>,
11119) -> EnclosingMemberOwnerResolution {
11120 let Some(spec_owner) = ctx.spec.owner.as_ref() else {
11121 return EnclosingMemberOwnerResolution::Missing;
11122 };
11123 if ctx.spec.kind != TargetKind::Method || ctx.spec.target.source() == spec_owner.source() {
11124 return EnclosingMemberOwnerResolution::Missing;
11125 }
11126 let Some(hierarchy) = ctx.analyzer.type_hierarchy_provider() else {
11127 return EnclosingMemberOwnerResolution::Missing;
11128 };
11129 let Some(receiver_owner) =
11130 ctx.visibility
11131 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, receiver_owner)
11132 else {
11133 return EnclosingMemberOwnerResolution::Ambiguous;
11134 };
11135 let Some(target_owner) = ctx.spec.owner.as_ref().and_then(|owner| {
11136 ctx.visibility
11137 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, owner)
11138 }) else {
11139 return EnclosingMemberOwnerResolution::Missing;
11140 };
11141
11142 let owner_declares_member = |owner: &CodeUnit| {
11143 if same_visible_symbol(owner, &target_owner) {
11144 return true;
11145 }
11146 let mut member_fq = owner.fq().clone();
11147 member_fq.push(
11148 ctx.spec
11149 .target
11150 .fq()
11151 .last()
11152 .expect("a method target has a terminal member segment"),
11153 );
11154 ctx.analyzer
11155 .definitions(&member_fq.display(segment_interner()))
11156 .any(|child| child.is_function())
11157 };
11158 if owner_declares_member(&receiver_owner) {
11159 return EnclosingMemberOwnerResolution::Owner(receiver_owner);
11160 }
11161
11162 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
11163 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
11164 let mut declaring_owner = None;
11165 while let Some(raw_owner) = stack.pop() {
11166 let Some(owner) =
11167 ctx.visibility
11168 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, &raw_owner)
11169 else {
11170 return EnclosingMemberOwnerResolution::Ambiguous;
11171 };
11172 let propagated = propagated_counts.entry(owner.clone()).or_default();
11173 if *propagated == 2 {
11174 continue;
11175 }
11176 *propagated += 1;
11177 if owner_declares_member(&owner) {
11178 if declaring_owner.is_some() {
11179 return EnclosingMemberOwnerResolution::Ambiguous;
11180 }
11181 declaring_owner = Some(owner);
11182 continue;
11183 }
11184 stack.extend(hierarchy.get_direct_ancestors(&owner));
11185 }
11186 declaring_owner
11187 .map(EnclosingMemberOwnerResolution::Owner)
11188 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
11189}
11190
11191fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
11192 if (has_recovered_class_shape_ancestor(node)
11197 || has_malformed_wrapper_function_definition_ancestor(node))
11198 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
11199 {
11200 return Some(owner);
11201 }
11202 let mut current = node.parent();
11203 while let Some(parent) = current {
11204 if parent.kind() == "function_definition" {
11205 let owner_lookup = function_definition_owner_lookup_node(parent);
11206 if let Some(owner_lookup) = owner_lookup
11207 && let Some(owners) = out_of_line_member_definition_owner(
11208 &ctx.analyzer,
11209 ctx.visibility,
11210 ctx.file,
11211 ctx.source,
11212 owner_lookup,
11213 )
11214 && let Some((_, owner)) = owners.innermost()
11215 {
11216 return Some(owner.clone());
11217 }
11218 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
11219 return Some(owner);
11220 }
11221 if let Some(owner) = enclosing_context(parent, ctx)
11222 .owner
11223 .filter(|owner| owner.is_class())
11224 {
11225 return Some(owner);
11226 }
11227 if let Some(owner_lookup) = owner_lookup
11228 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
11229 {
11230 return Some(owner);
11231 }
11232 break;
11233 }
11234 current = parent.parent();
11235 }
11236 enclosing_context(node, ctx)
11237 .owner
11238 .filter(|owner| owner.is_class())
11239}
11240
11241fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
11242 let target_owner = ctx.spec.owner.as_ref()?;
11243 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
11244 let owner_name = owner_components.last()?;
11245 let mut candidates = Vec::new();
11246 for candidate in ctx
11247 .visibility
11248 .visible_identifier_candidates(ctx.file, owner_name)
11249 .filter(|candidate| candidate.is_class())
11250 {
11251 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11252 brokk_bifrost_core::analyzer::Language::Cpp,
11253 &cpp_name_for(candidate),
11254 );
11255 if !components.ends_with(&owner_components)
11256 || candidates
11257 .iter()
11258 .any(|existing| same_logical_symbol(existing, candidate))
11259 {
11260 continue;
11261 }
11262 candidates.push(candidate.clone());
11263 }
11264 let [candidate] = candidates.as_slice() else {
11265 return None;
11266 };
11267 (same_logical_symbol(candidate, target_owner)
11268 && target_group_contains_owner_peer(candidate, ctx))
11269 .then(|| candidate.clone())
11270}