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