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