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