1use crate::call_match::{
2 CppArgType, cpp_filter_candidates_by_args, cpp_literal_arg_type, cpp_signature_param_types,
3 cpp_type_text_pointer_depth, normalize_cpp_type_name,
4};
5use crate::declarations::{
6 CppSentinelRecoveredClass, cpp_export_macro_token, cpp_sentinel_recovered_scope_for_node,
7 node_text, recovered_macro_return_type_node,
8};
9use crate::graph::CppGraphSource;
10use crate::graph::callable_definitions_share_identity_evidence as cpp_callable_definitions_share_identity_evidence;
11use crate::graph::hits::{
12 enclosing_context, is_member_field_own_declarator, push_definition_hit, push_hit,
13 push_recursive_reference_hit, push_self_receiver_hit, push_type_hit,
14 push_unproven_definition_hit, push_unproven_hit,
15};
16use crate::graph::resolver::*;
17use crate::graph::syntax::explicit_qualified_callable_value;
18use crate::graph_support::CppSource;
19use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
20use brokk_bifrost_core::analyzer::usages::common::same_node;
21use brokk_bifrost_core::analyzer::usages::inverted_edges::ClassRangeIndex;
22use brokk_bifrost_core::analyzer::usages::local_inference::{
23 LocalInferenceConfig, LocalInferenceEngine,
24};
25use brokk_bifrost_core::analyzer::usages::model::UsageHit;
26use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
27use brokk_bifrost_core::hash::{HashMap, HashSet};
28#[cfg(any(test, feature = "test-support"))]
29use std::cell::Cell;
30use std::cell::RefCell;
31use std::collections::BTreeSet;
32use std::sync::Arc;
33use tree_sitter::Node;
34
35#[cfg(any(test, feature = "test-support"))]
36thread_local! {
37 pub static LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
38}
39
40pub struct ScanState<'a> {
41 pub max_usages: usize,
42 pub hits: &'a mut BTreeSet<UsageHit>,
43 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
44 pub raw_match_count: &'a mut usize,
45 pub limit_exceeded: &'a mut bool,
46}
47
48pub struct ScanCtx<'a> {
49 pub analyzer: CppGraphSource<'a>,
50 pub visibility: &'a VisibilityIndex<'a>,
51 pub file: &'a ProjectFile,
52 pub source: &'a str,
53 ordinary_type_imports: OrdinaryTypeImportCell,
54 recovered_sentinel_classes: &'a [CppSentinelRecoveredClass],
55 class_ranges: Option<&'a ClassRangeIndex>,
56 pub line_starts: &'a [usize],
57 pub spec: &'a TargetSpec,
58 pub target_group: &'a HashSet<CodeUnit>,
59 pub has_physically_visible_type_target: bool,
60 type_reference_component_names: HashSet<String>,
61 pub target_declaration_ranges: Vec<Range>,
62 orphaned_namespaces: Vec<OrphanedNamespaceEnvelope>,
63 pub bindings: LocalInferenceEngine<CppScanBinding>,
64 local_shadows: LocalInferenceEngine<()>,
65 using_enum_owners: ScopedUsingEnumOwners,
66 semantic_using_enum_owners: SemanticUsingEnumOwners,
67 needs_using_enum_member_resolution: bool,
68 pub hits: &'a mut BTreeSet<UsageHit>,
69 pub unproven_hits: &'a mut BTreeSet<UsageHit>,
70 pub raw_match_count: &'a mut usize,
71 pub max_usages: usize,
72 pub limit_exceeded: &'a mut bool,
73 pub enclosing_cache: RefCell<HashMap<(usize, usize), EnclosingContext>>,
74 pub enclosing_owner_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
75 lexical_scope_cache: LexicalScopeCache,
76 lexical_free_function_cache: RefCell<HashMap<(String, String), bool>>,
77 member_owner_cache: RefCell<HashMap<CodeUnit, EnclosingMemberOwnerResolution>>,
78 global_field_internal_linkage_cache: RefCell<HashMap<CodeUnit, bool>>,
79 receiver_canonical_type_cache: RefCell<HashMap<CodeUnit, Option<CodeUnit>>>,
80}
81
82impl ScanCtx<'_> {
83 fn recovered_sentinel_scope(&self, node: Node<'_>) -> Option<Vec<String>> {
84 cpp_sentinel_recovered_scope_for_node(node, self.source, self.recovered_sentinel_classes)
85 }
86}
87
88#[derive(Clone, Default)]
89pub struct EnclosingContext {
90 pub enclosing: Option<CodeUnit>,
91 pub owner: Option<CodeUnit>,
92}
93
94pub fn prepare_file(cpp: &dyn CppSource, file: &ProjectFile) -> Option<Arc<PreparedSyntaxTree>> {
95 cpp.prepared_syntax(file)
96}
97
98#[allow(clippy::too_many_arguments)]
99pub fn scan_prepared_file(
100 analyzer: &CppGraphSource<'_>,
101 visibility: &VisibilityIndex<'_>,
102 file: &ProjectFile,
103 prepared: &PreparedSyntaxTree,
104 recovered_sentinel_classes: &[CppSentinelRecoveredClass],
105 class_ranges: Option<&ClassRangeIndex>,
106 spec: &TargetSpec,
107 target_group: &HashSet<CodeUnit>,
108 state: &mut ScanState<'_>,
109) {
110 if *state.limit_exceeded {
111 return;
112 }
113 let needs_using_enum_member_resolution = spec.enum_owner_kind == EnumOwnerKind::Scoped;
114 let has_physically_visible_type_target = spec.kind == TargetKind::Type
115 && target_group.iter().any(|target| {
116 same_logical_symbol(target, &spec.target)
117 && visibility.is_physically_visible(file, target)
118 });
119 if spec.kind == TargetKind::Type
120 && !has_physically_visible_type_target
121 && visibility
122 .visible_identifier_candidates(file, spec.target.identifier())
123 .any(|candidate| {
124 candidate != &spec.target
125 && !target_group.contains(candidate)
126 && same_logical_symbol(candidate, &spec.target)
127 && visibility.is_physically_visible(file, candidate)
128 })
129 {
130 return;
131 }
132 let target_declaration_ranges = if spec.kind == TargetKind::Type {
133 target_group
134 .iter()
135 .filter(|target| target.source() == file && same_logical_symbol(target, &spec.target))
136 .flat_map(|target| analyzer.ranges(target))
137 .collect()
138 } else if spec.target.source() == file {
139 analyzer.ranges(&spec.target)
140 } else {
141 Vec::new()
142 };
143 let ordinary_type_imports = initialized_ordinary_type_imports(
144 prepared.tree().root_node(),
145 analyzer,
146 visibility,
147 file,
148 prepared.source(),
149 );
150 let type_reference_component_names = if spec.kind == TargetKind::Type {
151 visibility.visible_type_reference_component_names_for_target(analyzer, file, &spec.target)
152 } else {
153 HashSet::default()
154 };
155 let orphaned_namespaces = if spec.kind == TargetKind::Type
156 && prepared.tree().root_node().has_error()
157 {
158 collect_orphaned_namespace_type_envelopes(prepared.tree().root_node(), prepared.source())
159 } else {
160 Vec::new()
161 };
162 let mut ctx = ScanCtx {
163 analyzer: *analyzer,
164 visibility,
165 file,
166 source: prepared.source(),
167 ordinary_type_imports,
168 recovered_sentinel_classes,
169 class_ranges,
170 line_starts: prepared.line_starts(),
171 spec,
172 target_group,
173 has_physically_visible_type_target,
174 type_reference_component_names,
175 target_declaration_ranges,
176 orphaned_namespaces,
177 bindings: LocalInferenceEngine::new(LocalInferenceConfig::default()),
178 local_shadows: LocalInferenceEngine::new(LocalInferenceConfig::default()),
179 using_enum_owners: ScopedUsingEnumOwners::new(),
180 semantic_using_enum_owners: SemanticUsingEnumOwners::new(),
181 needs_using_enum_member_resolution,
182 hits: state.hits,
183 unproven_hits: state.unproven_hits,
184 raw_match_count: state.raw_match_count,
185 max_usages: state.max_usages,
186 limit_exceeded: state.limit_exceeded,
187 enclosing_cache: RefCell::new(HashMap::default()),
188 enclosing_owner_cache: RefCell::new(HashMap::default()),
189 lexical_scope_cache: RefCell::new(HashMap::default()),
190 lexical_free_function_cache: RefCell::new(HashMap::default()),
191 member_owner_cache: RefCell::new(HashMap::default()),
192 global_field_internal_linkage_cache: RefCell::new(HashMap::default()),
193 receiver_canonical_type_cache: RefCell::new(HashMap::default()),
194 };
195 if needs_using_enum_member_resolution {
196 collect_semantic_using_enums(prepared.tree().root_node(), &mut ctx);
197 }
198 scan_node(prepared.tree().root_node(), &mut ctx);
199}
200
201enum UsingEnumDeclarationScope {
202 Block,
203 Class(CodeUnit),
204 Namespace(Vec<String>),
205 UnsupportedClass,
206}
207
208fn using_enum_declaration_scope(node: Node<'_>, ctx: &ScanCtx<'_>) -> UsingEnumDeclarationScope {
209 let mut current = node.parent();
210 while let Some(parent) = current {
211 if matches!(
212 parent.kind(),
213 "compound_statement"
214 | "function_definition"
215 | "lambda_expression"
216 | "for_statement"
217 | "while_statement"
218 | "if_statement"
219 ) {
220 return UsingEnumDeclarationScope::Block;
221 }
222 if matches!(
223 parent.kind(),
224 "class_specifier" | "struct_specifier" | "union_specifier"
225 ) {
226 let resolution = enclosing_lexical_scope_components(
227 node,
228 &ctx.analyzer,
229 ctx.visibility,
230 ctx.file,
231 ctx.source,
232 );
233 if let LexicalScopeResolution::Resolved(components) = resolution
234 && let LexicalTypeResolution::Resolved { unit, .. } =
235 ctx.visibility.resolve_type_components_lexically(
236 &ctx.analyzer,
237 ctx.file,
238 &components,
239 true,
240 &[],
241 )
242 {
243 return UsingEnumDeclarationScope::Class(unit);
244 }
245 return UsingEnumDeclarationScope::UnsupportedClass;
246 }
247 current = parent.parent();
248 }
249 UsingEnumDeclarationScope::Namespace(enclosing_namespace_components(node, ctx.source))
250}
251
252fn collect_semantic_using_enums(root: Node<'_>, ctx: &mut ScanCtx<'_>) {
253 let mut stack = vec![root];
254 while let Some(node) = stack.pop() {
255 if node.kind() == "using_declaration"
256 && let LexicalTypeResolution::Resolved { unit, .. } =
257 resolve_using_enum_declaration_owner(
258 node,
259 &ctx.analyzer,
260 ctx.visibility,
261 &ctx.ordinary_type_imports,
262 ctx.file,
263 ctx.source,
264 )
265 {
266 match using_enum_declaration_scope(node, ctx) {
267 UsingEnumDeclarationScope::Block => {}
268 UsingEnumDeclarationScope::Class(class) => {
269 ctx.semantic_using_enum_owners.import_class(class, unit);
270 }
271 UsingEnumDeclarationScope::Namespace(namespace) => {
272 ctx.semantic_using_enum_owners.import_namespace(
273 namespace,
274 node.start_byte(),
275 unit,
276 );
277 }
278 UsingEnumDeclarationScope::UnsupportedClass => {}
279 }
280 }
281 for index in (0..node.named_child_count()).rev() {
282 if let Some(child) = node.named_child(index) {
283 stack.push(child);
284 }
285 }
286 }
287}
288
289fn scan_node(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
290 if *ctx.limit_exceeded {
291 return;
292 }
293 let enters_scope = matches!(
294 node.kind(),
295 "compound_statement"
296 | "function_definition"
297 | "lambda_expression"
298 | "for_statement"
299 | "for_range_loop"
300 | "while_statement"
301 | "if_statement"
302 );
303 let enters_using_enum_scope = ctx.needs_using_enum_member_resolution
304 && (enters_scope
305 || matches!(
306 node.kind(),
307 "namespace_definition" | "class_specifier" | "struct_specifier" | "union_specifier"
308 ));
309 if enters_scope {
310 ctx.bindings.enter_scope();
311 ctx.local_shadows.enter_scope();
312 }
313 if enters_using_enum_scope {
314 ctx.using_enum_owners.enter_scope();
315 }
316
317 seed_declarations(node, ctx);
318 maybe_record_hit(node, ctx);
319
320 let mut cursor = node.walk();
321 for child in node.named_children(&mut cursor) {
322 scan_node(child, ctx);
323 if *ctx.limit_exceeded {
324 break;
325 }
326 }
327
328 if enters_scope {
329 ctx.bindings.exit_scope();
330 ctx.local_shadows.exit_scope();
331 }
332 if enters_using_enum_scope {
333 ctx.using_enum_owners.exit_scope();
334 }
335}
336
337fn seed_declarations(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
338 if crate::declarations::is_direct_recovered_exported_class_field_declaration(node, ctx.source)
339 || (ctx.spec.kind != TargetKind::Type
340 && indexed_recovered_class_field_declaration(node, ctx))
341 {
342 return;
343 }
344 match node.kind() {
345 "parameter_declaration" | "optional_parameter_declaration" => seed_typed_binding(node, ctx),
346 "declaration" | "field_declaration" => seed_variable_declaration(node, ctx),
347 "for_range_loop" => seed_range_binding(node, ctx),
348 "using_declaration" => seed_using_enum(node, ctx),
349 _ => {}
350 }
351}
352
353fn indexed_recovered_class_field_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
354 if node.kind() != "declaration"
355 || !has_function_scope_ancestor(node)
356 || has_ancestor_kind(node, "lambda_expression")
357 || !(has_recovered_class_shape_ancestor(node)
358 || has_malformed_wrapper_function_definition_ancestor(node))
359 {
360 return false;
361 }
362 let context = enclosing_context(node, ctx);
363 context.enclosing.as_ref().is_some_and(CodeUnit::is_field)
364 && context.owner.as_ref().is_some_and(CodeUnit::is_class)
365}
366
367fn seed_using_enum(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
368 if !ctx.needs_using_enum_member_resolution {
369 return;
370 }
371 if let LexicalTypeResolution::Resolved { unit, .. } = resolve_using_enum_declaration_owner(
372 node,
373 &ctx.analyzer,
374 ctx.visibility,
375 &ctx.ordinary_type_imports,
376 ctx.file,
377 ctx.source,
378 ) && matches!(
379 using_enum_declaration_scope(node, ctx),
380 UsingEnumDeclarationScope::Block
381 ) {
382 ctx.using_enum_owners.import(unit);
383 }
384}
385
386fn seed_variable_declaration(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
387 if node.kind() == "field_declaration" {
392 return;
393 }
394 let type_node = node
395 .child_by_field_name("type")
396 .or_else(|| first_type_child(node));
397 let type_text = type_node.map(|node| node_text(node, ctx.source).to_string());
398 let mut cursor = node.walk();
399 for child in node.named_children(&mut cursor) {
400 let declarator = if child.kind() == "init_declarator" {
401 child.child_by_field_name("declarator")
402 } else if is_declarator_node(child) {
403 Some(child)
404 } else {
405 None
406 };
407 let Some(declarator) = declarator else {
408 continue;
409 };
410 let Some(name) = extract_variable_name(declarator, ctx.source) else {
411 continue;
412 };
413 if declarator.kind() == "function_declarator"
414 && !constructor_style_local_declaration(
415 ctx.visibility,
416 ctx.file,
417 ctx.source,
418 declarator,
419 type_text.as_deref(),
420 &ctx.bindings,
421 )
422 {
423 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
424 ctx.local_shadows.declare_shadow(name);
425 }
426 continue;
427 }
428 if node.kind() == "declaration" && has_function_scope_ancestor(node) {
429 ctx.local_shadows.declare_shadow(name.clone());
430 }
431 if ctx.spec.kind == TargetKind::Type {
432 ctx.bindings.declare_shadow(name);
433 continue;
434 }
435 let value = child.child_by_field_name("value");
436 seed_binding_from_type_or_value(&name, type_node, value, ctx);
437 }
438}
439
440fn seed_typed_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
441 if !parameter_belongs_to_callable_scope(node) {
442 return;
443 }
444 let Some(declarator) = node.child_by_field_name("declarator") else {
445 return;
446 };
447 let Some(name) = extract_variable_name(declarator, ctx.source) else {
448 return;
449 };
450 if has_function_scope_ancestor(node) {
451 ctx.local_shadows.declare_shadow(name.clone());
452 }
453 if ctx.spec.kind == TargetKind::Type {
454 ctx.bindings.declare_shadow(name);
455 return;
456 }
457 let type_node = node
458 .child_by_field_name("type")
459 .or_else(|| first_type_child(node));
460 seed_binding_from_type_or_value(&name, type_node, None, ctx);
461}
462
463fn seed_range_binding(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
464 let Some(declarator) = node.child_by_field_name("declarator") else {
465 return;
466 };
467 let Some(name) = extract_variable_name(declarator, ctx.source) else {
468 return;
469 };
470 if has_function_scope_ancestor(node) {
471 ctx.local_shadows.declare_shadow(name.clone());
472 }
473 if ctx.spec.kind == TargetKind::Type {
474 ctx.bindings.declare_shadow(name);
475 return;
476 }
477 let type_node = node
478 .child_by_field_name("type")
479 .or_else(|| first_type_child(node));
480 seed_binding_from_type_or_value(&name, type_node, None, ctx);
481}
482
483fn has_function_scope_ancestor(node: Node<'_>) -> bool {
484 let mut current = node.parent();
485 while let Some(parent) = current {
486 if parent.kind() == "namespace_definition" {
494 return false;
495 }
496 if parent.kind() == "function_definition" {
497 return !is_malformed_wrapper_function_definition(parent);
504 }
505 if parent.kind() == "lambda_expression" {
506 return true;
507 }
508 current = parent.parent();
509 }
510 false
511}
512
513fn seed_binding_from_type_or_value(
514 name: &str,
515 type_node: Option<Node<'_>>,
516 value: Option<Node<'_>>,
517 ctx: &mut ScanCtx<'_>,
518) {
519 if name.is_empty() {
520 return;
521 }
522 let resolved = type_node
523 .filter(|node| normalize_type_text(node_text(*node, ctx.source)) != "auto")
524 .map(|node| {
525 let text = node_text(node, ctx.source);
526 let name = normalize_cpp_type_name(text);
527 let lexical_scope = ctx.recovered_sentinel_scope(node).or_else(|| {
532 if cpp_template_reference_arguments(node, ctx.source).is_some() {
533 return None;
534 }
535 match enclosing_lexical_scope_components(
536 node,
537 &ctx.analyzer,
538 ctx.visibility,
539 ctx.file,
540 ctx.source,
541 ) {
542 LexicalScopeResolution::Resolved(scope) => Some(scope),
543 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => None,
544 }
545 });
546 let unit = lexical_scope
547 .as_deref()
548 .and_then(|scope| resolve_seed_type_node_lexically(node, ctx, scope))
549 .or_else(|| {
550 match ctx
551 .visibility
552 .resolve_type_node_result(ctx.file, node, ctx.source)
553 {
554 Ok(Some(unit)) => Some(unit),
555 Ok(None) => ctx
556 .visibility
557 .canonical_type_for_reference(ctx.file, &name)
558 .or_else(|| ctx.visibility.resolve_type(ctx.file, &name)),
559 Err(_) => None,
560 }
561 });
562 CppScanBinding::from_type_name(name.clone(), unit, cpp_type_text_pointer_depth(text))
563 })
564 .or_else(|| value.and_then(|value| infer_type_from_value(value, ctx)));
565
566 if let Some(resolved) = resolved {
567 ctx.bindings.seed_symbol(name.to_string(), resolved);
568 } else if let Some(value) = value
569 && value.kind() == "identifier"
570 {
571 ctx.bindings
572 .alias_symbol(name.to_string(), node_text(value, ctx.source));
573 } else {
574 ctx.bindings.declare_shadow(name.to_string());
575 }
576}
577
578fn resolve_seed_type_node_lexically(
579 node: Node<'_>,
580 ctx: &ScanCtx<'_>,
581 scope: &[String],
582) -> Option<CodeUnit> {
583 let (components, global) = type_reference_components(node, ctx.source)?;
584 match ctx.visibility.resolve_type_components_lexically(
585 &ctx.analyzer,
586 ctx.file,
587 &components,
588 global,
589 scope,
590 ) {
591 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
592 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
593 }
594}
595
596const MAX_RECEIVER_CALL_RESOLUTION_DEPTH: usize = 32;
597
598fn infer_type_from_value(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppScanBinding> {
599 infer_type_from_value_with_budget(node, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
600}
601
602fn infer_type_from_value_with_budget(
603 node: Node<'_>,
604 ctx: &ScanCtx<'_>,
605 remaining_call_depth: usize,
606) -> Option<CppScanBinding> {
607 match node.kind() {
608 "new_expression" | "call_expression" if remaining_call_depth == 0 => {
609 infer_cpp_initializer_binding(
610 &ctx.analyzer,
611 ctx.visibility,
612 ctx.file,
613 ctx.source,
614 node,
615 None,
616 )
617 }
618 "new_expression" | "call_expression" => infer_cpp_initializer_binding(
619 &ctx.analyzer,
620 ctx.visibility,
621 ctx.file,
622 ctx.source,
623 node,
624 Some(&|receiver, source| {
625 receiver_type_units_with_budget(receiver, source, ctx, remaining_call_depth - 1)
626 }),
627 ),
628 "initializer_list" => None,
629 "identifier" => {
630 let resolved = ctx.bindings.resolve_symbol(node_text(node, ctx.source));
631 resolved
632 .as_precise()?
633 .iter()
634 .find(|binding| binding.unit.as_ref().is_some_and(CodeUnit::is_class))
635 .cloned()
636 }
637 _ => {
638 let text = node_text(node, ctx.source);
639 let name = normalize_cpp_type_name(text);
640 ctx.visibility
641 .resolve_type(ctx.file, &name)
642 .map(|unit| CppScanBinding::from_unit(unit, 0))
643 }
644 }
645}
646
647fn maybe_record_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
648 match ctx.spec.kind {
649 TargetKind::Type => maybe_record_type_hit(node, ctx),
650 TargetKind::Constructor => maybe_record_constructor_hit(node, ctx),
651 TargetKind::FreeFunction => maybe_record_free_function_hit(node, ctx),
652 TargetKind::Method => maybe_record_method_hit(node, ctx),
653 TargetKind::GlobalField => maybe_record_global_field_hit(node, ctx),
654 TargetKind::MemberField => maybe_record_member_field_hit(node, ctx),
655 TargetKind::Macro => maybe_record_macro_hit(node, ctx),
656 }
657}
658
659fn maybe_record_macro_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
660 if !matches!(
661 node.kind(),
662 "identifier"
663 | "field_identifier"
664 | "type_identifier"
665 | "namespace_identifier"
666 | "preproc_arg"
667 ) || node_text(node, ctx.source) != ctx.spec.member_name
668 || node.parent().is_some_and(|parent| {
669 matches!(parent.kind(), "preproc_def" | "preproc_function_def")
670 && parent
671 .child_by_field_name("name")
672 .is_some_and(|name| same_node(name, node))
673 })
674 {
675 return;
676 }
677 if ctx.visibility.macro_binding_matches_target_at(
678 &ctx.analyzer,
679 ctx.file,
680 &ctx.spec.member_name,
681 node.start_byte(),
682 &ctx.spec.target,
683 ) {
684 *ctx.raw_match_count += 1;
685 push_hit(node, ctx);
686 } else if ctx.visibility.macro_name_may_be_bound_at(
687 ctx.file,
688 &ctx.spec.member_name,
689 node.start_byte(),
690 ) && ctx
691 .visibility
692 .macro_target_is_visible_candidate(ctx.file, &ctx.spec.target)
693 {
694 *ctx.raw_match_count += 1;
695 push_unproven_hit(node, ctx);
696 }
697}
698
699fn maybe_record_type_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
700 if let Some(return_type) = recovered_macro_return_type_node(node, ctx.source) {
701 maybe_record_recovered_macro_return_type_hit(return_type, ctx);
702 return;
703 }
704 if let Some((owner, _member_pointer)) = member_pointer_owner_components(node, ctx.source) {
705 if ctx
712 .analyzer
713 .type_alias_provider()
714 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
715 && canonical_cpp_scope_components(&ctx.spec.target) == owner.names
716 && let Some(terminal) = owner.nodes.last().copied()
717 {
718 if !member_pointer_alias_owner_prefix_matches(node, &owner, ctx) {
719 return;
720 }
721 if ctx.visibility.external_type_candidate_visible_in_context(
722 &ctx.analyzer,
723 ctx.file,
724 &ctx.spec.target,
725 terminal,
726 ) || ctx
727 .visibility
728 .dependent_member_pointer_alias_visible_in_context(
729 &ctx.analyzer,
730 ctx.file,
731 &ctx.spec.target,
732 &owner.names,
733 terminal,
734 )
735 {
736 *ctx.raw_match_count += 1;
737 push_type_hit(terminal, ctx);
738 }
739 return;
740 }
741 if let Some(scopes) = static_qualifier_type_scopes_for_components(node, owner, ctx) {
742 *ctx.raw_match_count += 1;
743 for scope in scopes {
744 push_type_hit(scope, ctx);
745 }
746 return;
747 }
748 return;
749 }
750 if node.kind() == "call_expression" {
751 maybe_record_direct_temporary_type_hit(node, ctx);
752 return;
753 }
754 if node.parent().is_some_and(|parent| {
755 parent.kind() == "operator_cast"
756 && parent
757 .child_by_field_name("type")
758 .is_some_and(|target| same_node(target, node))
759 }) {
760 return;
761 }
762 if let Some(hit) = target_guided_static_cast_alias_type_descriptor(node, ctx) {
763 *ctx.raw_match_count += 1;
764 push_type_hit(hit, ctx);
765 return;
766 }
767 if matches!(node.kind(), "identifier" | "template_function")
768 && call_for_function_node(node).is_some()
769 {
770 return;
771 }
772 if node.kind() == "using_declaration" {
773 let (resolution, type_node) =
774 if let Some(type_node) = using_enum_declaration_type_node(node) {
775 (
776 resolve_using_enum_declaration_owner(
777 node,
778 &ctx.analyzer,
779 ctx.visibility,
780 &ctx.ordinary_type_imports,
781 ctx.file,
782 ctx.source,
783 ),
784 type_node,
785 )
786 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
787 (
788 resolve_ordinary_using_declaration_owner(
789 node,
790 &ctx.analyzer,
791 ctx.visibility,
792 ctx.file,
793 ctx.source,
794 ),
795 type_node,
796 )
797 } else {
798 return;
799 };
800 if let LexicalTypeResolution::Resolved { unit, .. } = resolution
801 && same_visible_symbol(&unit, &ctx.spec.target)
802 {
803 *ctx.raw_match_count += 1;
804 push_type_hit(type_node, ctx);
805 }
806 return;
807 }
808 if let Some((type_node, _)) = recovered_macro_decorated_type_node(node) {
809 let mut matching = Vec::new();
817 for candidate in [node, type_node] {
818 if matching
819 .iter()
820 .any(|existing| same_node(*existing, candidate))
821 {
822 continue;
823 }
824 if let LexicalTypeResolution::Resolved {
825 unit, candidates, ..
826 } = resolve_type_node_lexically_for_target(
827 candidate,
828 &ctx.analyzer,
829 ctx.visibility,
830 &ctx.ordinary_type_imports,
831 ctx.file,
832 ctx.source,
833 &ctx.spec.target,
834 Some(&ctx.lexical_scope_cache),
835 ctx.recovered_sentinel_scope(candidate).as_deref(),
836 ) && type_resolution_matches_target(candidate, &unit, &candidates, ctx)
837 {
838 matching.push(candidate);
839 }
840 }
841 match matching.as_slice() {
842 [candidate] if !same_node(*candidate, node) => {
843 maybe_record_type_hit(*candidate, ctx);
844 return;
845 }
846 [candidate] if same_node(*candidate, node) => {}
847 [] => {}
848 _ => return,
849 }
850 }
851 let recovered_type = recovered_macro_decorated_declarator_type(node).is_some();
852 if !recovered_type
853 && !matches!(
854 node.kind(),
855 "type_identifier" | "qualified_identifier" | "scoped_type_identifier" | "template_type"
856 )
857 {
858 return;
859 }
860 if type_reference_components(node, ctx.source).is_some_and(|(components, global)| {
861 components.len() == 1 && !global && local_type_name_shadows(node, ctx)
862 }) {
863 return;
864 }
865 if !recovered_type
866 && matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
867 && is_declaration_name(node)
868 && let Some(owners) = out_of_line_member_definition_owner(
869 &ctx.analyzer,
870 ctx.visibility,
871 ctx.file,
872 ctx.source,
873 node,
874 )
875 {
876 let terminal_destructor = out_of_line_destructor_type_reference(node);
877 let innermost = owners.innermost().map(|(_, owner)| owner.clone());
878 *ctx.raw_match_count += 1;
879 let mut matched_owner = false;
880 for (owner_node, owner) in owners.owners {
881 if same_visible_symbol(&owner, &ctx.spec.target) {
882 matched_owner = true;
883 push_guarded_owner_hit(owner_node, &owner, node, ctx);
884 }
885 }
886 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
887 for scope in scopes {
888 push_hit(scope, ctx);
889 }
890 } else if !matched_owner
891 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
892 {
893 push_unproven_hit(scope, ctx);
894 }
895 if let Some(terminal_destructor) = terminal_destructor
896 && innermost
897 .as_ref()
898 .is_some_and(|owner| same_visible_symbol(owner, &ctx.spec.target))
899 {
900 push_hit(terminal_destructor, ctx);
901 }
902 return;
903 }
904 if ctx.visibility.is_template_specialization(&ctx.spec.target)
910 && node.kind() == "qualified_identifier"
911 && node
912 .child_by_field_name("name")
913 .is_some_and(|name| name.kind() == "template_type")
914 {
915 return;
916 }
917 if let Some(scope) = target_guided_dependent_alias_qualifier_scope(node, ctx) {
918 *ctx.raw_match_count += 1;
919 push_unproven_hit(scope, ctx);
920 return;
921 }
922 if !recovered_type && is_nested_type_node(node) {
923 let nested_template = if node.kind() == "template_type" {
930 Some(node)
931 } else {
932 node.parent().filter(|parent| {
933 parent.kind() == "template_type" && parent.child_by_field_name("name") == Some(node)
934 })
935 };
936 let nested_alias_qualifier = nested_template.is_some_and(|template| {
943 let enclosing_qualified_type_owns_range = template.parent().is_some_and(|parent| {
944 matches!(
945 parent.kind(),
946 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
947 ) && parent.child_by_field_name("name") == Some(template)
948 });
949 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
950 return false;
951 };
952 let Some(name) = template_reference_name_node(template) else {
953 return false;
954 };
955 let alias_candidates = ctx
956 .visibility
957 .visible_identifier_candidates(ctx.file, node_text(name, ctx.source))
958 .filter(|candidate| alias_provider.is_type_alias(candidate))
959 .cloned()
960 .collect::<Vec<_>>();
961 let direct_target_alias_visible = !alias_candidates
962 .iter()
963 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
964 || ctx.visibility.external_type_candidate_visible_in_context(
965 &ctx.analyzer,
966 ctx.file,
967 &ctx.spec.target,
968 template,
969 );
970 !enclosing_qualified_type_owns_range
971 && direct_target_alias_visible
972 && template_reference_candidates_select_target(
973 template,
974 &alias_candidates,
975 &ctx.analyzer,
976 ctx.visibility,
977 ctx.file,
978 ctx.source,
979 &ctx.spec.target,
980 )
981 });
982 if !ctx.visibility.is_template_specialization(&ctx.spec.target) && !nested_alias_qualifier {
983 return;
984 }
985 if nested_alias_qualifier {
986 let template = nested_template.expect("a nested alias qualifier has a template node");
987 *ctx.raw_match_count += 1;
988 let hit =
989 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
990 type_reference_hit_node(template, ctx.file, ctx.source, &ctx.bindings)
991 });
992 push_type_hit(hit, ctx);
993 return;
994 }
995 if ctx.visibility.is_template_specialization(&ctx.spec.target)
1002 && let Some(template) = nested_template
1003 && template_type_component_preserves_target(
1004 template,
1005 &ctx.visibility
1006 .visible_identifier_candidates(ctx.file, node_text(template, ctx.source))
1007 .cloned()
1008 .collect::<Vec<_>>(),
1009 ctx,
1010 )
1011 {
1012 *ctx.raw_match_count += 1;
1013 let hit = template
1014 .child_by_field_name("name")
1015 .filter(|name| name.kind() == "type_identifier")
1016 .unwrap_or(template);
1017 push_type_hit(hit, ctx);
1018 return;
1019 }
1020 if let Some(template) = nested_template
1021 && let Some(_resolution) = resolve_nested_template_type_for_target(template, ctx)
1022 {
1023 *ctx.raw_match_count += 1;
1024 let hit =
1025 target_guided_missing_alias_rhs_type_leaf(template, ctx).unwrap_or_else(|| {
1026 if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1027 template
1028 .child_by_field_name("name")
1029 .filter(|name| name.kind() == "type_identifier")
1030 .unwrap_or(template)
1031 } else {
1032 type_reference_hit_node(template, ctx.file, ctx.source, &ctx.bindings)
1033 }
1034 });
1035 push_type_hit(hit, ctx);
1036 }
1037 return;
1038 }
1039 if !recovered_type && !type_reference_components_may_name_target(node, ctx) {
1040 return;
1041 }
1042 if !recovered_type && let Some(call) = call_for_function_node(node) {
1043 let direct_target = resolve_qualified_call_target(
1044 call,
1045 node,
1046 &ctx.analyzer,
1047 ctx.visibility,
1048 &ctx.ordinary_type_imports,
1049 ctx.file,
1050 ctx.source,
1051 );
1052 if matches!(direct_target, BareCallTargetResolution::Type(_))
1053 && let LexicalTypeResolution::Resolved {
1054 unit, candidates, ..
1055 } = resolve_type_node_lexically_for_target(
1056 node,
1057 &ctx.analyzer,
1058 ctx.visibility,
1059 &ctx.ordinary_type_imports,
1060 ctx.file,
1061 ctx.source,
1062 &ctx.spec.target,
1063 Some(&ctx.lexical_scope_cache),
1064 ctx.recovered_sentinel_scope(node).as_deref(),
1065 )
1066 && type_resolution_matches_target(node, &unit, &candidates, ctx)
1067 {
1068 *ctx.raw_match_count += 1;
1069 push_type_hit(
1070 type_reference_hit_node(node, ctx.file, ctx.source, &ctx.bindings),
1071 ctx,
1072 );
1073 } else if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1074 *ctx.raw_match_count += 1;
1075 for scope in scopes {
1076 push_type_hit(scope, ctx);
1077 }
1078 } else if let Some(scope) = target_guided_unproven_qualified_call_owner_scope(node, ctx) {
1079 *ctx.raw_match_count += 1;
1080 push_unproven_hit(scope, ctx);
1081 }
1082 return;
1083 }
1084 if let Some((hit, proven)) = target_guided_alias_template_reference(node, ctx) {
1085 *ctx.raw_match_count += 1;
1086 if proven {
1087 push_type_hit(hit, ctx);
1088 } else {
1089 push_unproven_hit(hit, ctx);
1090 }
1091 return;
1092 }
1093 if !recovered_type && is_declaration_name(node) {
1094 let mut matched_owner = false;
1095 if let Some(owners) = out_of_line_member_definition_owner(
1096 &ctx.analyzer,
1097 ctx.visibility,
1098 ctx.file,
1099 ctx.source,
1100 node,
1101 ) {
1102 for (owner_node, owner) in owners.owners {
1103 if same_visible_symbol(&owner, &ctx.spec.target) {
1104 matched_owner = true;
1105 *ctx.raw_match_count += 1;
1106 push_guarded_owner_hit(owner_node, &owner, node, ctx);
1107 }
1108 }
1109 }
1110 if !matched_owner && let Some(scopes) = target_guided_qualifier_type_scopes(node, ctx) {
1111 *ctx.raw_match_count += 1;
1112 for scope in scopes {
1113 push_hit(scope, ctx);
1114 }
1115 } else if !matched_owner
1116 && let Some(scope) = target_guided_unproven_out_of_line_owner(node, ctx)
1117 {
1118 *ctx.raw_match_count += 1;
1119 push_unproven_hit(scope, ctx);
1120 }
1121 return;
1122 }
1123 let hit_node = node;
1124 let text = node_text(hit_node, ctx.source);
1125 let type_resolution = if hit_node.kind() == "template_type"
1126 && ctx.visibility.is_template_specialization(&ctx.spec.target)
1127 {
1128 resolve_nested_template_type_for_target(hit_node, ctx).unwrap_or_else(|| {
1129 resolve_type_node_lexically_for_target(
1130 hit_node,
1131 &ctx.analyzer,
1132 ctx.visibility,
1133 &ctx.ordinary_type_imports,
1134 ctx.file,
1135 ctx.source,
1136 &ctx.spec.target,
1137 Some(&ctx.lexical_scope_cache),
1138 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1139 )
1140 })
1141 } else {
1142 resolve_type_node_lexically_for_target(
1143 hit_node,
1144 &ctx.analyzer,
1145 ctx.visibility,
1146 &ctx.ordinary_type_imports,
1147 ctx.file,
1148 ctx.source,
1149 &ctx.spec.target,
1150 Some(&ctx.lexical_scope_cache),
1151 ctx.recovered_sentinel_scope(hit_node).as_deref(),
1152 )
1153 };
1154 match type_resolution {
1155 LexicalTypeResolution::Resolved {
1156 unit, candidates, ..
1157 } if type_resolution_matches_target(node, &unit, &candidates, ctx) => {
1158 *ctx.raw_match_count += 1;
1159 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1160 for scope in scopes {
1161 push_type_hit(scope, ctx);
1162 }
1163 } else {
1164 let hit_node = if ctx.visibility.is_template_specialization(&ctx.spec.target) {
1165 hit_node
1166 .child_by_field_name("name")
1167 .filter(|name| name.kind() == "type_identifier")
1168 .unwrap_or(hit_node)
1169 } else if ctx
1170 .analyzer
1171 .type_alias_provider()
1172 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1173 && ctx
1174 .analyzer
1175 .parent_of(&ctx.spec.target)
1176 .is_some_and(|owner| owner.is_class())
1177 && ctx
1178 .visibility
1179 .is_exhaustive_same_fqn_type_declaration_family(
1180 &ctx.analyzer,
1181 ctx.file,
1182 &ctx.spec.target,
1183 )
1184 && matches!(
1185 hit_node.kind(),
1186 "qualified_identifier" | "scoped_type_identifier"
1187 )
1188 {
1189 hit_node.child_by_field_name("name").unwrap_or(hit_node)
1190 } else if qualified_type_scope_contains_template(hit_node) {
1191 function_terminal_node(hit_node)
1192 } else {
1193 hit_node
1194 };
1195 push_type_hit(
1196 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1197 ctx,
1198 );
1199 }
1200 return;
1201 }
1202 LexicalTypeResolution::Resolved {
1203 unit: _,
1204 candidates,
1205 ..
1206 } => {
1207 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1208 *ctx.raw_match_count += 1;
1209 for scope in scopes {
1210 push_type_hit(scope, ctx);
1211 }
1212 } else if let Some(hit) =
1213 target_guided_unproven_alias_type_reference(node, &candidates, ctx)
1214 {
1215 *ctx.raw_match_count += 1;
1216 push_unproven_hit(hit, ctx);
1217 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1218 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1219 {
1220 *ctx.raw_match_count += 1;
1221 push_type_hit(leaf, ctx);
1222 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1223 *ctx.raw_match_count += 1;
1224 push_unproven_hit(leaf, ctx);
1225 }
1226 return;
1227 }
1228 LexicalTypeResolution::Ambiguous => {
1229 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1230 *ctx.raw_match_count += 1;
1231 for scope in scopes {
1232 push_type_hit(scope, ctx);
1233 }
1234 } else if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1235 *ctx.raw_match_count += 1;
1236 push_unproven_hit(leaf, ctx);
1237 } else if let Some(leaf) = target_guided_missing_alias_rhs_type_leaf(node, ctx)
1238 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
1239 .or_else(|| target_guided_ambiguous_owned_alias_type_leaf(node, ctx))
1240 {
1241 *ctx.raw_match_count += 1;
1242 push_type_hit(leaf, ctx);
1243 }
1244 return;
1245 }
1246 LexicalTypeResolution::Missing => {
1247 if let Some(leaf) = target_guided_dependent_class_alias_leaf(node, ctx) {
1248 *ctx.raw_match_count += 1;
1249 push_unproven_hit(leaf, ctx);
1250 return;
1251 }
1252 if let Some(leaf) = target_guided_missing_type_leaf(node, ctx) {
1253 *ctx.raw_match_count += 1;
1254 push_type_hit(leaf, ctx);
1255 return;
1256 }
1257 let raw_resolution = resolve_type_node_lexically_for_target_without_visibility(
1258 hit_node,
1259 &ctx.analyzer,
1260 ctx.visibility,
1261 ctx.file,
1262 ctx.source,
1263 &ctx.spec.target,
1264 );
1265 let raw_matches = matches!(
1266 raw_resolution,
1267 LexicalTypeResolution::Resolved {
1268 ref unit,
1269 ref candidates,
1270 ..
1271 } if type_resolution_identifies_unit_target(
1272 hit_node,
1273 unit,
1274 candidates,
1275 &ctx.spec.target,
1276 ctx,
1277 )
1278 );
1279 if raw_matches
1280 || type_node_has_exact_target_identity_without_visibility(
1281 hit_node,
1282 &ctx.analyzer,
1283 ctx.visibility,
1284 ctx.file,
1285 ctx.source,
1286 &ctx.spec.target,
1287 )
1288 {
1289 *ctx.raw_match_count += 1;
1290 push_unproven_hit(
1291 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1292 ctx,
1293 );
1294 return;
1295 }
1296 }
1297 }
1298 if ctx
1299 .visibility
1300 .parser_alias_resolves_to_type(&ctx.analyzer, ctx.file, text, &ctx.spec.target)
1301 {
1302 *ctx.raw_match_count += 1;
1303 push_type_hit(
1304 type_reference_hit_node(hit_node, ctx.file, ctx.source, &ctx.bindings),
1305 ctx,
1306 );
1307 return;
1308 }
1309 if let Some(scopes) = static_qualifier_type_scopes(node, ctx) {
1310 *ctx.raw_match_count += 1;
1311 for scope in scopes {
1312 push_type_hit(scope, ctx);
1313 }
1314 return;
1315 }
1316 if !name_mentions(text, &ctx.spec.member_name) {
1317 return;
1318 }
1319 *ctx.raw_match_count += 1;
1320 if !ctx.visibility.external_type_candidate_visible_in_context(
1321 &ctx.analyzer,
1322 ctx.file,
1323 &ctx.spec.target,
1324 hit_node,
1325 ) {
1326 if let Some(scope) = static_qualifier_name_scope(node, ctx) {
1327 push_unproven_hit(scope, ctx);
1328 } else {
1329 push_unproven_hit(hit_node, ctx);
1330 }
1331 }
1332}
1333
1334fn push_guarded_owner_hit(
1335 owner_node: Node<'_>,
1336 owner: &CodeUnit,
1337 reference: Node<'_>,
1338 ctx: &mut ScanCtx<'_>,
1339) {
1340 if ctx
1341 .visibility
1342 .external_type_candidate_guard_compatible_in_context(
1343 &ctx.analyzer,
1344 ctx.file,
1345 owner,
1346 reference,
1347 )
1348 {
1349 push_hit(owner_node, ctx);
1350 } else {
1351 push_unproven_hit(owner_node, ctx);
1352 }
1353}
1354
1355fn target_guided_alias_template_reference<'tree>(
1358 node: Node<'tree>,
1359 ctx: &ScanCtx<'_>,
1360) -> Option<(Node<'tree>, bool)> {
1361 let alias_provider = ctx.analyzer.type_alias_provider()?;
1362 if !matches!(
1363 node.kind(),
1364 "qualified_identifier" | "scoped_type_identifier"
1365 ) || !alias_provider.is_type_alias(&ctx.spec.target)
1366 {
1367 return None;
1368 }
1369 cpp_template_reference_arguments(node, ctx.source)?;
1370 let (components, global) = type_reference_components(node, ctx.source)?;
1371 if components.last().map(String::as_str) != Some(ctx.spec.target.identifier()) {
1372 return None;
1373 }
1374 let target = physically_visible_type_target(ctx)?;
1375 let target_components = canonical_cpp_scope_components(target);
1376 let parser_namespace = enclosing_namespace_components(node, ctx.source);
1377 let path_matches = if global {
1378 components == target_components
1379 || (!parser_namespace.is_empty()
1380 && target_components.starts_with(&parser_namespace)
1381 && target_components[parser_namespace.len()..] == components)
1382 } else {
1383 let lexical_scope = match enclosing_lexical_scope_components(
1384 node,
1385 &ctx.analyzer,
1386 ctx.visibility,
1387 ctx.file,
1388 ctx.source,
1389 ) {
1390 LexicalScopeResolution::Resolved(scope) => scope,
1391 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => parser_namespace,
1392 };
1393 lexical_component_tiers(&components, false, &lexical_scope)
1394 .any(|scope| scope == target_components)
1395 };
1396 let scoped_candidates = ctx
1397 .visibility
1398 .visible_identifier_candidates(ctx.file, target.identifier())
1399 .filter(|candidate| canonical_cpp_scope_components(candidate) == target_components)
1400 .collect::<Vec<_>>();
1401 if !path_matches
1402 || scoped_candidates.is_empty()
1403 || scoped_candidates
1404 .iter()
1405 .any(|candidate| !same_visible_symbol(candidate, target))
1406 || !ctx.visibility.structured_alias_primary_preserves_target(
1407 &ctx.analyzer,
1408 ctx.file,
1409 target,
1410 target,
1411 )
1412 {
1413 return None;
1414 }
1415 let proven = ctx.visibility.external_type_candidate_visible_in_context(
1416 &ctx.analyzer,
1417 ctx.file,
1418 target,
1419 node,
1420 );
1421 Some((node, proven))
1422}
1423
1424fn maybe_record_recovered_macro_return_type_hit(return_type: Node<'_>, ctx: &mut ScanCtx<'_>) {
1430 let name = node_text(return_type, ctx.source);
1431 if name != ctx.spec.target.identifier() || ctx.local_shadows.is_shadowed(name) {
1432 return;
1433 }
1434 if physically_visible_type_target(ctx).is_some()
1435 && type_alias_owner_encloses_structured_reference(return_type, ctx)
1436 && !nearer_type_name_shadows_structured_reference(return_type, ctx)
1437 && ctx.visibility.external_type_candidate_visible_in_context(
1438 &ctx.analyzer,
1439 ctx.file,
1440 &ctx.spec.target,
1441 return_type,
1442 )
1443 {
1444 *ctx.raw_match_count += 1;
1445 push_type_hit(return_type, ctx);
1446 return;
1447 }
1448 let Some(scope) = ctx
1449 .recovered_sentinel_scope(return_type)
1450 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, return_type))
1451 else {
1452 return;
1453 };
1454 let components = [name.to_string()];
1455 let resolution = ctx.visibility.resolve_type_components_lexically(
1456 &ctx.analyzer,
1457 ctx.file,
1458 &components,
1459 false,
1460 &scope,
1461 );
1462 if let LexicalTypeResolution::Resolved {
1463 unit, candidates, ..
1464 } = resolution
1465 && (same_visible_symbol(&unit, &ctx.spec.target)
1466 || candidates
1467 .iter()
1468 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
1469 {
1470 *ctx.raw_match_count += 1;
1471 push_type_hit(return_type, ctx);
1472 }
1473}
1474
1475fn member_pointer_owner_components<'tree>(
1480 node: Node<'tree>,
1481 source: &str,
1482) -> Option<(QualifiedOwnerComponents<'tree>, Node<'tree>)> {
1483 if node.kind() != "qualified_identifier" {
1484 return None;
1485 }
1486 let declarator = node.child_by_field_name("name")?;
1487 if !matches!(
1488 declarator.kind(),
1489 "pointer_type_declarator" | "abstract_pointer_declarator"
1490 ) {
1491 return None;
1492 }
1493 let scope = node.child_by_field_name("scope")?;
1494 let mut saw_scope = false;
1501 let has_scope_separator = (0..node.child_count()).any(|index| {
1502 let Some(child) = node.child(index) else {
1503 return false;
1504 };
1505 if same_node(child, scope) {
1506 saw_scope = true;
1507 return false;
1508 }
1509 saw_scope && !same_node(child, declarator) && child.kind() == "::" && !child.is_missing()
1510 });
1511 if !has_scope_separator {
1512 return None;
1513 }
1514 let mut nodes = cpp_name_component_nodes(scope)?;
1515 let mut outer = node;
1516 while let Some(parent) = outer.parent()
1517 && parent.kind() == "qualified_identifier"
1518 && parent.child_by_field_name("name") == Some(outer)
1519 {
1520 let mut prefix = cpp_name_component_nodes(parent.child_by_field_name("scope")?)?;
1521 prefix.append(&mut nodes);
1522 nodes = prefix;
1523 outer = parent;
1524 }
1525 let names = nodes
1526 .iter()
1527 .map(|component| node_text(*component, source).to_string())
1528 .collect();
1529 Some((
1530 QualifiedOwnerComponents {
1531 nodes,
1532 names,
1533 global: is_globally_qualified_cpp_name(outer),
1534 },
1535 outer,
1536 ))
1537}
1538
1539fn member_pointer_alias_owner_prefix_matches(
1540 node: Node<'_>,
1541 owner: &QualifiedOwnerComponents<'_>,
1542 ctx: &ScanCtx<'_>,
1543) -> bool {
1544 let Some((_, owner_prefix)) = owner.names.split_last() else {
1545 return false;
1546 };
1547 let Some(parent) = type_owner_of(&ctx.analyzer, &ctx.spec.target) else {
1548 return false;
1549 };
1550 let recovered_scope = ctx.recovered_sentinel_scope(node);
1551 let resolution = if let Some(recovered_scope) = recovered_scope {
1552 resolve_type_components_lexically_at_for_target_with_recovered_scope(
1553 node,
1554 owner_prefix,
1555 owner.global,
1556 &ctx.analyzer,
1557 ctx.visibility,
1558 &ctx.ordinary_type_imports,
1559 ctx.file,
1560 ctx.source,
1561 &parent,
1562 false,
1563 &recovered_scope,
1564 )
1565 } else {
1566 resolve_type_components_lexically_at_for_target_with_scope_cache(
1567 node,
1568 owner_prefix,
1569 owner.global,
1570 &ctx.analyzer,
1571 ctx.visibility,
1572 &ctx.ordinary_type_imports,
1573 ctx.file,
1574 ctx.source,
1575 &parent,
1576 false,
1577 Some(&ctx.lexical_scope_cache),
1578 )
1579 };
1580 let LexicalTypeResolution::Resolved {
1581 unit, candidates, ..
1582 } = resolution
1583 else {
1584 return false;
1585 };
1586 same_member_pointer_owner_identity(&unit, &parent)
1587 || candidates
1588 .iter()
1589 .any(|candidate| same_member_pointer_owner_identity(candidate, &parent))
1590}
1591
1592fn same_member_pointer_owner_identity(left: &CodeUnit, right: &CodeUnit) -> bool {
1593 same_visible_symbol(left, right)
1594 || (left.kind() == right.kind()
1595 && left.fq_name() == right.fq_name()
1596 && left.source() == right.source())
1597}
1598
1599fn resolve_nested_template_type_for_target(
1606 node: Node<'_>,
1607 ctx: &ScanCtx<'_>,
1608) -> Option<LexicalTypeResolution> {
1609 let reference_node = node
1610 .parent()
1611 .filter(|parent| {
1612 parent.kind() == "qualified_identifier"
1613 && parent.child_by_field_name("name") == Some(node)
1614 })
1615 .unwrap_or(node);
1616 let target_resolution = resolve_type_node_lexically_for_target(
1617 reference_node,
1618 &ctx.analyzer,
1619 ctx.visibility,
1620 &ctx.ordinary_type_imports,
1621 ctx.file,
1622 ctx.source,
1623 &ctx.spec.target,
1624 Some(&ctx.lexical_scope_cache),
1625 ctx.recovered_sentinel_scope(reference_node).as_deref(),
1626 );
1627 if let LexicalTypeResolution::Resolved {
1628 unit, candidates, ..
1629 } = target_resolution
1630 && template_reference_candidates_select_target(
1631 reference_node,
1632 &candidates,
1633 &ctx.analyzer,
1634 ctx.visibility,
1635 ctx.file,
1636 ctx.source,
1637 &ctx.spec.target,
1638 )
1639 {
1640 return Some(LexicalTypeResolution::Resolved {
1641 unit,
1642 components: Vec::new(),
1643 candidates,
1644 });
1645 }
1646
1647 let normal_resolution = resolve_type_node_lexically(
1648 reference_node,
1649 &ctx.analyzer,
1650 ctx.visibility,
1651 &ctx.ordinary_type_imports,
1652 ctx.file,
1653 ctx.source,
1654 );
1655 let LexicalTypeResolution::Resolved {
1656 unit,
1657 components,
1658 candidates,
1659 } = normal_resolution
1660 else {
1661 return None;
1662 };
1663 let arguments = cpp_template_reference_arguments(reference_node, ctx.source)?;
1664 let specialized = ctx
1665 .visibility
1666 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
1667 .ok()
1668 .unwrap_or(unit);
1669 (same_visible_symbol(&specialized, &ctx.spec.target)
1670 || template_type_component_preserves_target(reference_node, &candidates, ctx))
1671 .then_some(LexicalTypeResolution::Resolved {
1672 unit: specialized,
1673 components,
1674 candidates,
1675 })
1676}
1677
1678fn type_reference_components_may_name_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
1679 let Some((components, _)) = type_reference_components(node, ctx.source) else {
1680 return false;
1681 };
1682 components.iter().any(|component| {
1683 ctx.type_reference_component_names.contains(component)
1684 || (component == ctx.spec.target.identifier()
1685 && matches!(
1686 node.kind(),
1687 "qualified_identifier" | "scoped_type_identifier"
1688 )
1689 && cpp_template_reference_arguments(node, ctx.source).is_some()
1690 && ctx
1691 .analyzer
1692 .type_alias_provider()
1693 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1694 && physically_visible_type_target(ctx).is_some())
1695 })
1696}
1697
1698fn qualified_type_scope_contains_template(node: Node<'_>) -> bool {
1699 let Some(scope) = node.child_by_field_name("scope") else {
1700 return false;
1701 };
1702 let mut pending = vec![scope];
1703 while let Some(candidate) = pending.pop() {
1704 if candidate.kind() == "template_type" {
1705 return true;
1706 }
1707 if matches!(
1708 candidate.kind(),
1709 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1710 ) {
1711 if let Some(scope) = candidate.child_by_field_name("scope") {
1712 pending.push(scope);
1713 }
1714 if let Some(name) = candidate.child_by_field_name("name") {
1715 pending.push(name);
1716 }
1717 }
1718 }
1719 false
1720}
1721
1722fn call_for_function_node(node: Node<'_>) -> Option<Node<'_>> {
1723 let parent = node.parent()?;
1724 (parent.kind() == "call_expression" && parent.child_by_field_name("function") == Some(node))
1725 .then_some(parent)
1726}
1727
1728fn physically_visible_type_target<'a>(ctx: &'a ScanCtx<'_>) -> Option<&'a CodeUnit> {
1729 ctx.target_group.iter().find(|target| {
1730 same_logical_symbol(target, &ctx.spec.target)
1731 && ctx.visibility.is_physically_visible(ctx.file, target)
1732 })
1733}
1734
1735fn target_guided_missing_direct_temporary_type<'tree>(
1736 function: Node<'tree>,
1737 ctx: &ScanCtx<'_>,
1738) -> Option<Node<'tree>> {
1739 let target = physically_visible_type_target(ctx)?;
1740 let component_nodes = cpp_name_component_nodes(function)?;
1741 let terminal = component_nodes.last().copied()?;
1742 if node_text(terminal, ctx.source) != target.identifier() {
1743 return None;
1744 }
1745 let components = component_nodes
1746 .iter()
1747 .map(|component| node_text(*component, ctx.source).to_string())
1748 .collect::<Vec<_>>();
1749 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, function)?;
1750 indexed_scope_matches_target_name(
1751 &indexed_scope,
1752 &components,
1753 is_globally_qualified_cpp_name(function),
1754 target,
1755 )
1756 .then_some(terminal)
1757}
1758
1759fn maybe_record_direct_temporary_type_hit(call: Node<'_>, ctx: &mut ScanCtx<'_>) {
1760 let Some(function) = call.child_by_field_name("function") else {
1761 return;
1762 };
1763 if !matches!(
1764 function.kind(),
1765 "identifier"
1766 | "type_identifier"
1767 | "template_function"
1768 | "template_type"
1769 | "qualified_identifier"
1770 | "scoped_identifier"
1771 | "scoped_type_identifier"
1772 ) {
1773 return;
1774 }
1775 if !type_reference_components_may_name_target(function, ctx) {
1776 return;
1777 }
1778 if let Some(scopes) = static_qualifier_type_scopes(function, ctx) {
1779 *ctx.raw_match_count += 1;
1780 for scope in scopes {
1781 push_type_hit(scope, ctx);
1782 }
1783 return;
1784 }
1785 let terminal = function_terminal_node(function);
1786 let name = node_text(terminal, ctx.source);
1787 if name.is_empty() || ctx.local_shadows.is_shadowed(name) {
1788 return;
1789 }
1790 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx) {
1791 match resolve_declaring_member_owner(
1792 &ctx.analyzer,
1793 ctx.visibility,
1794 ctx.file,
1795 &enclosing_owner,
1796 name,
1797 ) {
1798 EnclosingMemberOwnerResolution::Owner(owner)
1799 if matches!(
1800 ctx.visibility
1801 .visible_member_for_owner_name(ctx.file, &owner, name,),
1802 VisibleMemberResolution::Callable(_) | VisibleMemberResolution::AmbiguousKind
1803 ) =>
1804 {
1805 return;
1806 }
1807 EnclosingMemberOwnerResolution::Ambiguous => return,
1808 EnclosingMemberOwnerResolution::Owner(_) | EnclosingMemberOwnerResolution::Missing => {}
1809 }
1810 }
1811
1812 if ctx
1819 .analyzer
1820 .type_alias_provider()
1821 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
1822 && name == ctx.spec.target.identifier()
1823 && physically_visible_type_target(ctx).is_some()
1824 && !local_type_name_shadows(function, ctx)
1825 && type_alias_owner_matches_structured_reference(function, ctx)
1826 && ctx.visibility.external_type_candidate_visible_in_context(
1827 &ctx.analyzer,
1828 ctx.file,
1829 &ctx.spec.target,
1830 function,
1831 )
1832 {
1833 *ctx.raw_match_count += 1;
1834 push_type_hit(terminal, ctx);
1835 return;
1836 }
1837
1838 let call_resolution = resolve_qualified_call_target(
1839 call,
1840 function,
1841 &ctx.analyzer,
1842 ctx.visibility,
1843 &ctx.ordinary_type_imports,
1844 ctx.file,
1845 ctx.source,
1846 );
1847 match call_resolution {
1848 BareCallTargetResolution::Type(unit) => {
1849 if same_visible_symbol(&unit, &ctx.spec.target) {
1850 *ctx.raw_match_count += 1;
1851 let hit_node = if matches!(
1852 function.kind(),
1853 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1854 ) {
1855 function_terminal_node(function)
1856 } else {
1857 function
1858 };
1859 push_type_hit(hit_node, ctx);
1860 return;
1861 }
1862 }
1863 BareCallTargetResolution::FreeFunctions(units)
1864 if units.iter().all(|unit| {
1865 unit.fq_name() == ctx.spec.target.fq_name()
1866 && ctx
1867 .visibility
1868 .callable_is_constructor_declaration(&ctx.analyzer, unit)
1869 }) => {}
1870 BareCallTargetResolution::Ambiguous => {
1871 push_unproven_hit(function, ctx);
1872 return;
1873 }
1874 BareCallTargetResolution::FreeFunctions(_)
1875 | BareCallTargetResolution::UnprovenFreeFunctions(_)
1876 | BareCallTargetResolution::CallableShadow => return,
1877 BareCallTargetResolution::Missing => {}
1882 }
1883 let target_resolution = resolve_type_node_lexically_for_target(
1884 function,
1885 &ctx.analyzer,
1886 ctx.visibility,
1887 &ctx.ordinary_type_imports,
1888 ctx.file,
1889 ctx.source,
1890 &ctx.spec.target,
1891 Some(&ctx.lexical_scope_cache),
1892 ctx.recovered_sentinel_scope(function).as_deref(),
1893 );
1894 match target_resolution {
1895 LexicalTypeResolution::Resolved {
1896 unit, candidates, ..
1897 } if same_visible_symbol(&unit, &ctx.spec.target)
1898 || candidates
1899 .iter()
1900 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)) =>
1901 {
1902 *ctx.raw_match_count += 1;
1903 let hit_node = if matches!(
1904 function.kind(),
1905 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1906 ) {
1907 function_terminal_node(function)
1908 } else {
1909 function
1910 };
1911 push_type_hit(hit_node, ctx);
1912 }
1913 LexicalTypeResolution::Missing => {
1914 if let Some(hit) = target_guided_missing_direct_temporary_type(function, ctx) {
1915 *ctx.raw_match_count += 1;
1916 push_type_hit(hit, ctx);
1917 }
1918 }
1919 LexicalTypeResolution::Resolved { .. } | LexicalTypeResolution::Ambiguous => {}
1920 }
1921}
1922
1923pub enum BareCallTargetResolution {
1924 Type(CodeUnit),
1925 FreeFunctions(Vec<CodeUnit>),
1926 UnprovenFreeFunctions(Vec<CodeUnit>),
1927 CallableShadow,
1928 Ambiguous,
1929 Missing,
1930}
1931
1932#[allow(clippy::too_many_arguments)]
1933fn resolve_qualified_call_target(
1934 call: Node<'_>,
1935 function: Node<'_>,
1936 analyzer: &CppGraphSource<'_>,
1937 visibility: &VisibilityIndex,
1938 ordinary_type_imports: &OrdinaryTypeImportCell,
1939 file: &ProjectFile,
1940 source: &str,
1941) -> BareCallTargetResolution {
1942 if matches!(function.kind(), "identifier" | "template_function") {
1943 return resolve_bare_call_target(
1944 call,
1945 function,
1946 analyzer,
1947 visibility,
1948 ordinary_type_imports,
1949 file,
1950 source,
1951 );
1952 }
1953 if !matches!(
1954 function.kind(),
1955 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
1956 ) {
1957 return BareCallTargetResolution::Missing;
1958 }
1959 let terminal = function_terminal_node(function);
1960 let name = node_text(terminal, source);
1961 let Some((mut components, _)) = qualified_callable_owner_components(function, source) else {
1962 return BareCallTargetResolution::Missing;
1963 };
1964 components.push(name.to_string());
1965 let qualified_name = components.join("::");
1966 let type_resolution = resolve_type_node_lexically(
1967 function,
1968 analyzer,
1969 visibility,
1970 ordinary_type_imports,
1971 file,
1972 source,
1973 );
1974 let same_name_resolves_to_type = matches!(
1975 &type_resolution,
1976 LexicalTypeResolution::Resolved {
1977 unit,
1978 candidates,
1979 ..
1980 } if cpp_name_for(unit) == qualified_name
1981 || candidates
1982 .iter()
1983 .any(|candidate| cpp_name_for(candidate) == qualified_name)
1984 );
1985 let candidates = visibility
1986 .visible_identifier_candidates(file, name)
1987 .filter(|candidate| {
1988 candidate.is_function()
1989 && type_owner_of(analyzer, candidate).is_none()
1990 && !(same_name_resolves_to_type
1991 && visibility.callable_is_constructor_declaration(analyzer, candidate))
1992 && cpp_name_for(candidate) == qualified_name
1993 && visibility.declaration_visible_at(analyzer, file, candidate, call.start_byte())
1994 })
1995 .cloned()
1996 .collect::<Vec<_>>();
1997 if !candidates.is_empty() {
1998 return resolve_callable_candidates(
1999 candidates,
2000 visibility.call_arity_evidence(file, call, source).exact(),
2001 call.start_byte(),
2002 analyzer,
2003 visibility,
2004 file,
2005 );
2006 }
2007 match type_resolution {
2008 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2009 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2010 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2011 }
2012}
2013
2014fn binding_free_function_candidates(
2015 binding: &OrdinaryTypeImport,
2016 active_bindings: &[&OrdinaryTypeImport],
2017 analyzer: &CppGraphSource<'_>,
2018 visibility: &VisibilityIndex<'_>,
2019 file: &ProjectFile,
2020 name: &str,
2021 reference_byte: usize,
2022) -> Vec<CodeUnit> {
2023 let Some(qualified) = binding.resolved_target_components.as_ref() else {
2024 return Vec::new();
2025 };
2026 let mut targets = Vec::new();
2027 match binding.target {
2028 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified.clone()),
2029 EffectiveUsingTarget::Namespace { .. } => {
2030 let mut stack = vec![qualified.clone()];
2031 let mut visited = HashSet::default();
2032 while let Some(namespace) = stack.pop() {
2033 if !visited.insert(namespace.clone()) {
2034 continue;
2035 }
2036 let mut target = namespace.clone();
2037 target.push(name.to_string());
2038 targets.push(target);
2039 stack.extend(active_bindings.iter().filter_map(|candidate| {
2040 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
2041 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
2042 .then(|| candidate.resolved_target_components.clone())
2043 .flatten()
2044 }));
2045 }
2046 }
2047 }
2048 targets
2049 .into_iter()
2050 .flat_map(|target| {
2051 let qualified_name = target.join("::");
2052 visibility
2053 .visible_identifier_candidates(file, name)
2054 .filter(move |candidate| {
2055 candidate.is_function()
2056 && type_owner_of(analyzer, candidate).is_none()
2057 && cpp_name_for(candidate) == qualified_name
2058 && visibility.declaration_visible_at(
2059 analyzer,
2060 file,
2061 candidate,
2062 reference_byte,
2063 )
2064 })
2065 .cloned()
2066 })
2067 .collect()
2068}
2069
2070fn dedupe_callable_candidates(candidates: &mut Vec<CodeUnit>) {
2071 let mut deduped = Vec::with_capacity(candidates.len());
2072 for candidate in candidates.drain(..) {
2073 if !deduped
2074 .iter()
2075 .any(|existing| same_logical_symbol(existing, &candidate))
2076 {
2077 deduped.push(candidate);
2078 }
2079 }
2080 *candidates = deduped;
2081}
2082
2083fn resolve_callable_candidates(
2084 candidates: Vec<CodeUnit>,
2085 call_arity: Option<usize>,
2086 reference_byte: usize,
2087 analyzer: &CppGraphSource<'_>,
2088 visibility: &VisibilityIndex<'_>,
2089 file: &ProjectFile,
2090) -> BareCallTargetResolution {
2091 let mut candidates = candidates;
2092 dedupe_callable_candidates(&mut candidates);
2093 if candidates.is_empty() {
2094 return BareCallTargetResolution::Missing;
2095 }
2096 let Some(call_arity) = call_arity else {
2097 if candidates.len() == 1 {
2104 return BareCallTargetResolution::FreeFunctions(candidates);
2105 }
2106 return BareCallTargetResolution::UnprovenFreeFunctions(candidates);
2107 };
2108 let applicable = candidates
2109 .into_iter()
2110 .filter(|candidate| {
2111 visibility
2112 .callable_arity_at_reference(analyzer, file, candidate, reference_byte)
2113 .is_some_and(|arity| arity.accepts(call_arity))
2114 })
2115 .collect::<Vec<_>>();
2116 if applicable.is_empty() {
2117 BareCallTargetResolution::CallableShadow
2118 } else {
2119 BareCallTargetResolution::FreeFunctions(applicable)
2120 }
2121}
2122
2123fn resolve_direct_type_candidates(
2124 candidates: Vec<(CodeUnit, Vec<String>)>,
2125 analyzer: &CppGraphSource<'_>,
2126 visibility: &VisibilityIndex<'_>,
2127 file: &ProjectFile,
2128) -> BareCallTargetResolution {
2129 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
2130 for candidate in candidates {
2131 if !logical
2132 .iter()
2133 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
2134 {
2135 logical.push(candidate);
2136 }
2137 }
2138 let [(target, components)] = logical.as_slice() else {
2139 return if logical.is_empty() {
2140 BareCallTargetResolution::Missing
2141 } else {
2142 BareCallTargetResolution::Ambiguous
2143 };
2144 };
2145 match visibility
2146 .resolve_imported_type_candidate(analyzer, file, target, components, None, false)
2147 {
2148 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2149 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2150 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2151 }
2152}
2153
2154#[allow(clippy::too_many_arguments)]
2155pub fn resolve_bare_call_target(
2156 call: Node<'_>,
2157 function: Node<'_>,
2158 analyzer: &CppGraphSource<'_>,
2159 visibility: &VisibilityIndex<'_>,
2160 ordinary_type_imports: &OrdinaryTypeImportCell,
2161 file: &ProjectFile,
2162 source: &str,
2163) -> BareCallTargetResolution {
2164 if !matches!(function.kind(), "identifier" | "template_function") {
2165 return BareCallTargetResolution::Missing;
2166 }
2167 let terminal = function_terminal_node(function);
2168 let name = node_text(terminal, source);
2169 if name.is_empty() {
2170 return BareCallTargetResolution::Missing;
2171 }
2172 let call_arity = visibility.call_arity_evidence(file, call, source).exact();
2173 let lexical_scope =
2174 match enclosing_lexical_scope_components(function, analyzer, visibility, file, source) {
2175 LexicalScopeResolution::Resolved(scope) => scope,
2176 LexicalScopeResolution::Ambiguous => return BareCallTargetResolution::Ambiguous,
2177 LexicalScopeResolution::Missing => return BareCallTargetResolution::Missing,
2178 };
2179 let type_resolution = resolve_type_node_lexically(
2180 function,
2181 analyzer,
2182 visibility,
2183 ordinary_type_imports,
2184 file,
2185 source,
2186 );
2187 let type_components = match &type_resolution {
2188 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2189 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2190 };
2191 let direct_type_resolution = visibility.resolve_type_components_lexically(
2192 analyzer,
2193 file,
2194 &[name.to_string()],
2195 false,
2196 &lexical_scope,
2197 );
2198 let direct_type_components = match &direct_type_resolution {
2199 LexicalTypeResolution::Resolved { components, .. } => Some(components.as_slice()),
2200 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
2201 };
2202 let bindings = effective_using_bindings_for_name(
2203 visibility,
2204 ordinary_type_imports,
2205 file,
2206 root_node(function),
2207 source,
2208 name,
2209 );
2210 let active_bindings = bindings
2211 .iter()
2212 .filter(|binding| {
2213 effective_using_binding_active(
2214 binding,
2215 function,
2216 &lexical_scope,
2217 source,
2218 visibility,
2219 file,
2220 )
2221 })
2222 .collect::<Vec<_>>();
2223 let function_guards = preprocessor_guard_environment(function, source);
2224 let transitive_bindings = bindings
2225 .iter()
2226 .filter(|binding| {
2227 binding.declaration_byte <= function.start_byte()
2228 && function_guards
2229 .as_ref()
2230 .is_some_and(|active| binding.required_guards.is_subset(active))
2231 && visibility.preprocessor_guards_stable_between(
2232 file,
2233 0,
2234 function.start_byte(),
2235 &binding.required_guards,
2236 )
2237 && (binding.namespace_scope.is_some()
2238 || (binding.scope_start <= function.start_byte()
2239 && function.end_byte() <= binding.scope_end))
2240 })
2241 .collect::<Vec<_>>();
2242 let mut concrete_depths = active_bindings
2243 .iter()
2244 .filter(|binding| binding.namespace_scope.is_none())
2245 .map(|binding| binding.scope_depth)
2246 .collect::<Vec<_>>();
2247 concrete_depths.sort_unstable();
2248 concrete_depths.dedup();
2249 for depth in concrete_depths.into_iter().rev() {
2250 let at_tier = active_bindings
2251 .iter()
2252 .copied()
2253 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
2254 let direct = at_tier
2255 .clone()
2256 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2257 .flat_map(|binding| {
2258 binding_free_function_candidates(
2259 binding,
2260 &transitive_bindings,
2261 analyzer,
2262 visibility,
2263 file,
2264 name,
2265 call.start_byte(),
2266 )
2267 })
2268 .collect::<Vec<_>>();
2269 if !direct.is_empty() {
2270 return resolve_callable_candidates(
2271 direct,
2272 call_arity,
2273 call.start_byte(),
2274 analyzer,
2275 visibility,
2276 file,
2277 );
2278 }
2279 let direct_types = at_tier
2280 .clone()
2281 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2282 .flat_map(|binding| {
2283 binding_type_candidates(
2284 binding,
2285 &transitive_bindings,
2286 visibility,
2287 file,
2288 name,
2289 None,
2290 call.start_byte(),
2291 )
2292 })
2293 .collect::<Vec<_>>();
2294 if !direct_types.is_empty() {
2295 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2300 }
2301 let directives = at_tier
2302 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2303 .flat_map(|binding| {
2304 binding_free_function_candidates(
2305 binding,
2306 &transitive_bindings,
2307 analyzer,
2308 visibility,
2309 file,
2310 name,
2311 call.start_byte(),
2312 )
2313 })
2314 .collect::<Vec<_>>();
2315 if !directives.is_empty() {
2316 return resolve_callable_candidates(
2317 directives,
2318 call_arity,
2319 call.start_byte(),
2320 analyzer,
2321 visibility,
2322 file,
2323 );
2324 }
2325 }
2326 for prefix_len in (0..=lexical_scope.len()).rev() {
2327 let mut qualified = lexical_scope[..prefix_len].to_vec();
2328 qualified.push(name.to_string());
2329 let same_name_resolves_to_type = direct_type_components
2330 .is_some_and(|components| components == qualified.as_slice())
2331 || type_components.is_some_and(|components| components == qualified.as_slice());
2332 let mut direct = visibility
2333 .visible_identifier_candidates(file, name)
2334 .filter(|candidate| {
2335 candidate.is_function()
2336 && type_owner_of(analyzer, candidate).is_none()
2337 && !(same_name_resolves_to_type
2338 && visibility.callable_is_constructor_declaration(analyzer, candidate))
2339 && cpp_name_for(candidate) == qualified.join("::")
2340 && visibility.declaration_visible_at(
2341 analyzer,
2342 file,
2343 candidate,
2344 call.start_byte(),
2345 )
2346 })
2347 .cloned()
2348 .collect::<Vec<_>>();
2349 let at_tier = active_bindings.iter().copied().filter(|binding| {
2350 binding.namespace_scope.as_deref() == Some(&lexical_scope[..prefix_len])
2351 });
2352 direct.extend(
2353 at_tier
2354 .clone()
2355 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2356 .flat_map(|binding| {
2357 binding_free_function_candidates(
2358 binding,
2359 &transitive_bindings,
2360 analyzer,
2361 visibility,
2362 file,
2363 name,
2364 call.start_byte(),
2365 )
2366 }),
2367 );
2368 if !direct.is_empty() {
2369 return resolve_callable_candidates(
2370 direct,
2371 call_arity,
2372 call.start_byte(),
2373 analyzer,
2374 visibility,
2375 file,
2376 );
2377 }
2378 let mut direct_types = at_tier
2379 .clone()
2380 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
2381 .flat_map(|binding| {
2382 binding_type_candidates(
2383 binding,
2384 &transitive_bindings,
2385 visibility,
2386 file,
2387 name,
2388 None,
2389 call.start_byte(),
2390 )
2391 })
2392 .collect::<Vec<_>>();
2393 if direct_type_components.is_some_and(|components| components == qualified.as_slice())
2394 && let LexicalTypeResolution::Resolved {
2395 unit, components, ..
2396 } = &direct_type_resolution
2397 {
2398 direct_types.push((unit.clone(), components.clone()));
2399 }
2400 if !direct_types.is_empty() {
2401 return resolve_direct_type_candidates(direct_types, analyzer, visibility, file);
2406 }
2407 let directives = at_tier
2408 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
2409 .flat_map(|binding| {
2410 binding_free_function_candidates(
2411 binding,
2412 &transitive_bindings,
2413 analyzer,
2414 visibility,
2415 file,
2416 name,
2417 call.start_byte(),
2418 )
2419 })
2420 .collect::<Vec<_>>();
2421 if !directives.is_empty() {
2422 return resolve_callable_candidates(
2423 directives,
2424 call_arity,
2425 call.start_byte(),
2426 analyzer,
2427 visibility,
2428 file,
2429 );
2430 }
2431 if type_components.is_some_and(|components| components == qualified.as_slice()) {
2432 return match type_resolution {
2436 LexicalTypeResolution::Resolved { unit, .. } => {
2437 BareCallTargetResolution::Type(unit)
2438 }
2439 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2440 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2441 };
2442 }
2443 }
2444 match type_resolution {
2453 LexicalTypeResolution::Resolved { unit, .. } => BareCallTargetResolution::Type(unit),
2454 LexicalTypeResolution::Ambiguous => BareCallTargetResolution::Ambiguous,
2455 LexicalTypeResolution::Missing => BareCallTargetResolution::Missing,
2456 }
2457}
2458
2459fn static_qualifier_type_scopes<'tree>(
2460 node: Node<'tree>,
2461 ctx: &ScanCtx<'_>,
2462) -> Option<Vec<Node<'tree>>> {
2463 if !matches!(
2464 node.kind(),
2465 "qualified_identifier" | "scoped_type_identifier"
2466 ) {
2467 return None;
2468 }
2469 debug_assert!(!is_nested_type_node(node));
2472 let qualified = qualified_owner_components(node, ctx.source)?;
2473 static_qualifier_type_scopes_for_components(node, qualified, ctx)
2474}
2475
2476fn static_qualifier_type_scopes_for_components<'tree>(
2477 node: Node<'tree>,
2478 qualified: QualifiedOwnerComponents<'tree>,
2479 ctx: &ScanCtx<'_>,
2480) -> Option<Vec<Node<'tree>>> {
2481 if !qualified.global
2482 && qualified.names.first().is_some_and(|name| {
2483 name == ctx.spec.target.identifier()
2484 && qualified
2485 .nodes
2486 .first()
2487 .is_some_and(|owner| local_type_name_shadows(*owner, ctx))
2488 })
2489 {
2490 return None;
2491 }
2492 let mut matches = Vec::new();
2493 let mut inherited_injected_name_is_shadowed = false;
2494 for component_count in 1..=qualified.names.len() {
2495 let resolution = resolve_type_components_lexically_at_for_target_with_scope_cache(
2496 node,
2497 &qualified.names[..component_count],
2498 qualified.global,
2499 &ctx.analyzer,
2500 ctx.visibility,
2501 &ctx.ordinary_type_imports,
2502 ctx.file,
2503 ctx.source,
2504 &ctx.spec.target,
2505 false,
2506 Some(&ctx.lexical_scope_cache),
2507 );
2508 match resolution {
2509 LexicalTypeResolution::Resolved {
2510 unit, candidates, ..
2511 } if (!ctx
2512 .analyzer
2513 .type_alias_provider()
2514 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
2515 || ctx.visibility.external_type_candidate_visible_in_context(
2516 &ctx.analyzer,
2517 ctx.file,
2518 &ctx.spec.target,
2519 node,
2520 ))
2521 && (same_visible_symbol(&unit, &ctx.spec.target)
2522 || candidates
2523 .iter()
2524 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target)))
2525 && target_alias_candidates_visible(&candidates, node, ctx) =>
2526 {
2527 let matched =
2528 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node);
2529 if !template_type_component_preserves_target(matched, &candidates, ctx) {
2530 continue;
2531 }
2532 if !matches.iter().any(|existing: &Node<'_>| {
2533 existing.start_byte() == matched.start_byte()
2534 && existing.end_byte() == matched.end_byte()
2535 }) {
2536 matches.push(matched);
2537 }
2538 }
2539 LexicalTypeResolution::Ambiguous => {
2545 return (!inherited_injected_name_is_shadowed)
2546 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
2547 .flatten()
2548 .map(|scope| vec![scope])
2549 .or_else(|| target_guided_qualifier_type_scopes(node, ctx));
2550 }
2551 LexicalTypeResolution::Resolved { .. } => {
2552 if let Some(matched) =
2553 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
2554 {
2555 matches.push(matched);
2556 }
2557 inherited_injected_name_is_shadowed |= component_count == 1;
2558 }
2559 LexicalTypeResolution::Missing => {
2560 if let Some(matched) =
2561 target_guided_nested_alias_type_scope(node, &qualified, component_count, ctx)
2562 {
2563 matches.push(matched);
2564 }
2565 }
2566 }
2567 }
2568 if matches.is_empty() {
2569 (!inherited_injected_name_is_shadowed)
2570 .then(|| inherited_injected_class_qualifier_scope(node, ctx))
2571 .flatten()
2572 .map(|scope| vec![scope])
2573 .or_else(|| target_guided_qualifier_type_scopes(node, ctx))
2574 } else {
2575 Some(matches)
2576 }
2577}
2578
2579fn target_guided_unproven_qualified_call_owner_scope<'tree>(
2582 node: Node<'tree>,
2583 ctx: &ScanCtx<'_>,
2584) -> Option<Node<'tree>> {
2585 let target = physically_visible_type_target(ctx)?;
2586 if !target.is_class() {
2587 return None;
2588 }
2589 let qualified = qualified_owner_components(node, ctx.source)?;
2590 let lexical_scope = match enclosing_lexical_scope_components(
2591 node,
2592 &ctx.analyzer,
2593 ctx.visibility,
2594 ctx.file,
2595 ctx.source,
2596 ) {
2597 LexicalScopeResolution::Resolved(scope) => scope,
2598 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
2599 enclosing_namespace_components(node, ctx.source)
2600 }
2601 };
2602 let LexicalTypeResolution::Resolved {
2603 unit, candidates, ..
2604 } = ctx.visibility.resolve_type_components_lexically_for_target(
2605 &ctx.analyzer,
2606 ctx.file,
2607 &qualified.names,
2608 qualified.global,
2609 &lexical_scope,
2610 target,
2611 )
2612 else {
2613 return None;
2614 };
2615 (same_visible_symbol(&unit, target)
2616 || candidates
2617 .iter()
2618 .any(|candidate| same_visible_symbol(candidate, target)))
2619 .then(|| qualified.nodes.last().copied())
2620 .flatten()
2621}
2622
2623fn target_guided_nested_alias_type_scope<'tree>(
2629 node: Node<'tree>,
2630 qualified: &QualifiedOwnerComponents<'tree>,
2631 component_count: usize,
2632 ctx: &ScanCtx<'_>,
2633) -> Option<Node<'tree>> {
2634 if component_count < 2 {
2635 return None;
2636 }
2637 let (owner_components, member_name) =
2638 qualified.names[..component_count].split_at(component_count - 1);
2639 let LexicalTypeResolution::Resolved { unit: owner, .. } = resolve_type_components_lexically_at(
2640 node,
2641 owner_components,
2642 qualified.global,
2643 &ctx.analyzer,
2644 ctx.visibility,
2645 &ctx.ordinary_type_imports,
2646 ctx.file,
2647 ctx.source,
2648 ) else {
2649 return None;
2650 };
2651 let member_name = member_name.first()?;
2652 let alias_provider = ctx.analyzer.type_alias_provider()?;
2653 ctx.visibility
2654 .visible_members_for_owner_name(ctx.file, &owner, member_name)
2655 .into_iter()
2656 .filter(|member| alias_provider.is_type_alias(member))
2657 .find(|member| {
2658 let member_visible = ctx.visibility.external_type_candidate_visible_in_context(
2659 &ctx.analyzer,
2660 ctx.file,
2661 member,
2662 node,
2663 ) || ctx
2664 .visibility
2665 .external_type_candidate_guard_compatible_in_context(
2666 &ctx.analyzer,
2667 ctx.file,
2668 member,
2669 node,
2670 );
2671 if !member_visible {
2672 return false;
2673 }
2674 same_visible_symbol(member, &ctx.spec.target)
2675 || same_visible_symbol(&canonical_alias_target(member, ctx), &ctx.spec.target)
2676 })
2677 .map(|_| qualified_type_component_hit_node(qualified.nodes[component_count - 1], node))
2678}
2679
2680fn canonical_alias_target(candidate: &CodeUnit, ctx: &ScanCtx<'_>) -> CodeUnit {
2681 if ctx.visibility.structured_class_alias_resolves_to_target(
2682 &ctx.analyzer,
2683 ctx.file,
2684 candidate,
2685 &ctx.spec.target,
2686 ) {
2687 return ctx.spec.target.clone();
2688 }
2689 let structured = ctx
2690 .visibility
2691 .canonical_type_unit(&ctx.analyzer, ctx.file, candidate);
2692 if let Some(canonical) = structured
2693 .as_ref()
2694 .filter(|canonical| !same_visible_symbol(canonical, candidate))
2695 {
2696 return canonical.clone();
2697 }
2698 structured.unwrap_or_else(|| candidate.clone())
2699}
2700
2701fn target_guided_dependent_alias_qualifier_scope<'tree>(
2705 node: Node<'tree>,
2706 ctx: &ScanCtx<'_>,
2707) -> Option<Node<'tree>> {
2708 if !matches!(
2709 node.kind(),
2710 "qualified_identifier" | "scoped_type_identifier"
2711 ) {
2712 return None;
2713 }
2714 let target = physically_visible_type_target(ctx)?;
2715 let alias_provider = ctx.analyzer.type_alias_provider()?;
2716 if !target.is_class() || alias_provider.is_type_alias(target) {
2717 return None;
2718 }
2719 let nodes = cpp_name_component_nodes(node)?;
2720 let names = nodes
2721 .iter()
2722 .map(|component| node_text(*component, ctx.source).to_string())
2723 .collect::<Vec<_>>();
2724 let global = is_globally_qualified_cpp_name(node);
2725 let lexical_scope = match enclosing_lexical_scope_components(
2726 node,
2727 &ctx.analyzer,
2728 ctx.visibility,
2729 ctx.file,
2730 ctx.source,
2731 ) {
2732 LexicalScopeResolution::Resolved(scope) => scope,
2733 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
2734 enclosing_namespace_components(node, ctx.source)
2735 }
2736 };
2737 for component_count in 1..=names.len() {
2738 let components = &names[..component_count];
2739 let name = components.last()?;
2740 let candidates = ctx
2741 .visibility
2742 .visible_identifier_candidates(ctx.file, name)
2743 .filter(|candidate| alias_provider.is_type_alias(candidate))
2744 .filter(|candidate| {
2745 ctx.visibility.is_physically_visible(ctx.file, candidate)
2746 && ctx
2747 .visibility
2748 .external_type_candidate_guard_compatible_in_context(
2749 &ctx.analyzer,
2750 ctx.file,
2751 candidate,
2752 node,
2753 )
2754 })
2755 .filter(|candidate| {
2756 let candidate_components = canonical_cpp_scope_components(candidate);
2757 lexical_component_tiers(components, global, &lexical_scope)
2758 .any(|tier| tier == candidate_components)
2759 || (component_count == 1
2760 && member_alias_owner_matches_reference_for(
2761 candidate,
2762 nodes[component_count - 1],
2763 ctx,
2764 ))
2765 })
2766 .filter(|candidate| {
2767 ctx.visibility.structured_class_alias_path_preserves_target(
2768 &ctx.analyzer,
2769 ctx.file,
2770 candidate,
2771 target,
2772 )
2773 });
2774 let mut aliases: Vec<&CodeUnit> = Vec::new();
2775 for candidate in candidates {
2776 if !aliases
2777 .iter()
2778 .any(|existing| same_logical_symbol(existing, candidate))
2779 {
2780 aliases.push(candidate);
2781 }
2782 }
2783 if aliases.len() == 1 {
2784 return nodes.get(component_count - 1).copied();
2785 }
2786 if aliases.len() > 1 {
2787 return None;
2788 }
2789 }
2790 None
2791}
2792
2793fn target_guided_dependent_class_alias_leaf<'tree>(
2796 node: Node<'tree>,
2797 ctx: &ScanCtx<'_>,
2798) -> Option<Node<'tree>> {
2799 if node.kind() != "type_identifier"
2800 || is_declaration_name(node)
2801 || local_type_name_shadows(node, ctx)
2802 {
2803 return None;
2804 }
2805 let target = physically_visible_type_target(ctx)?;
2806 let alias_provider = ctx.analyzer.type_alias_provider()?;
2807 if !target.is_class() || alias_provider.is_type_alias(target) {
2808 return None;
2809 }
2810 let name = node_text(node, ctx.source);
2811 let aliases = ctx
2812 .visibility
2813 .visible_identifier_candidates(ctx.file, name)
2814 .filter(|candidate| alias_provider.is_type_alias(candidate))
2815 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
2816 .filter(|candidate| {
2817 ctx.visibility.is_physically_visible(ctx.file, candidate)
2818 && ctx
2819 .visibility
2820 .external_type_candidate_guard_compatible_in_context(
2821 &ctx.analyzer,
2822 ctx.file,
2823 candidate,
2824 node,
2825 )
2826 })
2827 .filter(|candidate| {
2828 ctx.visibility.structured_class_alias_path_preserves_target(
2829 &ctx.analyzer,
2830 ctx.file,
2831 candidate,
2832 target,
2833 )
2834 })
2835 .collect::<Vec<_>>();
2836 matches!(aliases.as_slice(), [_]).then_some(node)
2837}
2838
2839fn target_guided_unproven_alias_type_reference<'tree>(
2842 node: Node<'tree>,
2843 candidates: &[CodeUnit],
2844 ctx: &ScanCtx<'_>,
2845) -> Option<Node<'tree>> {
2846 if cpp_template_reference_arguments(node, ctx.source).is_some() {
2847 return None;
2848 }
2849 let target = physically_visible_type_target(ctx)?;
2850 if !target.is_class() {
2851 return None;
2852 }
2853 let alias_provider = ctx.analyzer.type_alias_provider()?;
2854 let (components, _) = type_reference_components(node, ctx.source)?;
2855 let hit = function_terminal_node(node);
2856 candidates
2857 .iter()
2858 .filter(|candidate| {
2859 alias_provider.is_type_alias(candidate)
2860 && ctx.visibility.is_physically_visible(ctx.file, candidate)
2861 && canonical_cpp_scope_components(candidate) == components
2862 })
2863 .find(|candidate| {
2864 same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
2865 || ctx.visibility.structured_alias_primary_preserves_target(
2866 &ctx.analyzer,
2867 ctx.file,
2868 candidate,
2869 target,
2870 )
2871 })
2872 .map(|_| hit)
2873}
2874
2875fn target_alias_candidates_visible(
2876 candidates: &[CodeUnit],
2877 reference: Node<'_>,
2878 ctx: &ScanCtx<'_>,
2879) -> bool {
2880 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
2881 return true;
2882 };
2883 if candidates.iter().any(|candidate| {
2884 !alias_provider.is_type_alias(candidate)
2885 && ctx.visibility.same_template_member_identity(
2886 &ctx.analyzer,
2887 candidate,
2888 &ctx.spec.target,
2889 )
2890 }) {
2891 return true;
2892 }
2893 let target_aliases = candidates
2894 .iter()
2895 .filter(|candidate| {
2896 alias_provider.is_type_alias(candidate)
2897 && same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
2898 })
2899 .collect::<Vec<_>>();
2900 target_aliases.is_empty()
2901 || target_aliases
2902 .iter()
2903 .any(|candidate| type_candidate_visible_at_reference(candidate, reference, ctx))
2904}
2905
2906fn type_candidate_visible_at_reference(
2907 candidate: &CodeUnit,
2908 reference: Node<'_>,
2909 ctx: &ScanCtx<'_>,
2910) -> bool {
2911 let class_owned_alias = ctx
2912 .analyzer
2913 .type_alias_provider()
2914 .is_some_and(|provider| provider.is_type_alias(candidate))
2915 && ctx
2916 .analyzer
2917 .parent_of(candidate)
2918 .is_some_and(|owner| owner.is_class());
2919 if class_owned_alias {
2920 let conditional_family = ctx
2921 .visibility
2922 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, candidate);
2923 let owner_match = qualified_reference_selects_type_candidate(candidate, reference, ctx)
2924 || unqualified_reference_selects_inherited_alias(candidate, reference, ctx)
2925 || member_alias_owner_matches_reference_for(candidate, reference, ctx);
2926 let guard_match = ctx
2927 .visibility
2928 .external_type_candidate_guard_compatible_in_context(
2929 &ctx.analyzer,
2930 ctx.file,
2931 candidate,
2932 reference,
2933 );
2934 let general_match = conditional_family
2935 && ctx.visibility.external_type_candidate_visible_in_context(
2936 &ctx.analyzer,
2937 ctx.file,
2938 candidate,
2939 reference,
2940 );
2941 return owner_match && (guard_match || general_match);
2942 }
2943 ctx.visibility.external_type_candidate_visible_in_context(
2944 &ctx.analyzer,
2945 ctx.file,
2946 candidate,
2947 reference,
2948 )
2949}
2950
2951fn unqualified_reference_selects_inherited_alias(
2952 candidate: &CodeUnit,
2953 reference: Node<'_>,
2954 ctx: &ScanCtx<'_>,
2955) -> bool {
2956 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2957 return false;
2958 };
2959 if global || components.len() != 1 {
2960 return false;
2961 }
2962 matches!(
2963 resolve_type_node_lexically_for_target(
2964 reference,
2965 &ctx.analyzer,
2966 ctx.visibility,
2967 &ctx.ordinary_type_imports,
2968 ctx.file,
2969 ctx.source,
2970 candidate,
2971 Some(&ctx.lexical_scope_cache),
2972 ctx.recovered_sentinel_scope(reference).as_deref(),
2973 ),
2974 LexicalTypeResolution::Resolved {
2975 ref unit,
2976 ref candidates,
2977 ..
2978 } if ctx
2979 .visibility
2980 .same_template_member_identity(&ctx.analyzer, unit, candidate)
2981 || candidates.iter().any(|resolved| {
2982 ctx.visibility.same_template_member_identity(
2983 &ctx.analyzer,
2984 resolved,
2985 candidate,
2986 )
2987 })
2988 )
2989}
2990
2991fn qualified_reference_selects_type_candidate(
2992 candidate: &CodeUnit,
2993 reference: Node<'_>,
2994 ctx: &ScanCtx<'_>,
2995) -> bool {
2996 let Some((components, global)) = type_reference_components(reference, ctx.source) else {
2997 return false;
2998 };
2999 if components.len() < 2 {
3000 return false;
3001 }
3002 let candidate_components = canonical_cpp_scope_components(candidate);
3003 let lexical_scope = ctx.recovered_sentinel_scope(reference).unwrap_or_else(|| {
3004 match enclosing_lexical_scope_components(
3005 reference,
3006 &ctx.analyzer,
3007 ctx.visibility,
3008 ctx.file,
3009 ctx.source,
3010 ) {
3011 LexicalScopeResolution::Resolved(scope) => scope,
3012 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3013 enclosing_namespace_components(reference, ctx.source)
3014 }
3015 }
3016 });
3017 lexical_component_tiers(&components, global, &lexical_scope)
3018 .any(|qualified| qualified == candidate_components)
3019}
3020
3021fn qualified_type_component_hit_node<'tree>(
3022 component: Node<'tree>,
3023 qualified: Node<'tree>,
3024) -> Node<'tree> {
3025 let mut current = component;
3026 while let Some(parent) = current.parent() {
3027 let is_type_name = matches!(
3028 parent.kind(),
3029 "template_type"
3030 | "qualified_identifier"
3031 | "scoped_identifier"
3032 | "scoped_type_identifier"
3033 ) && parent
3034 .child_by_field_name("name")
3035 .is_some_and(|name| same_node(name, current));
3036 if !is_type_name {
3037 break;
3038 }
3039 current = parent;
3040 if same_node(parent, qualified) {
3041 break;
3042 }
3043 }
3044 current
3045}
3046
3047fn template_type_component_preserves_target(
3048 node: Node<'_>,
3049 candidates: &[CodeUnit],
3050 ctx: &ScanCtx<'_>,
3051) -> bool {
3052 template_reference_candidates_select_target(
3053 node,
3054 candidates,
3055 &ctx.analyzer,
3056 ctx.visibility,
3057 ctx.file,
3058 ctx.source,
3059 &ctx.spec.target,
3060 )
3061}
3062
3063fn template_reference_candidates_select_target(
3064 node: Node<'_>,
3065 candidates: &[CodeUnit],
3066 analyzer: &CppGraphSource<'_>,
3067 visibility: &VisibilityIndex<'_>,
3068 file: &ProjectFile,
3069 source: &str,
3070 target: &CodeUnit,
3071) -> bool {
3072 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3073 return !visibility.is_template_specialization(target);
3074 };
3075 let direct_template_name =
3076 template_reference_name_node(node).map(|name| node_text(name, source));
3077 let named_alias_selects_target = direct_template_name.is_some_and(|name| {
3078 analyzer.type_alias_provider().is_some_and(|provider| {
3079 visibility
3080 .visible_identifier_candidates(file, name)
3081 .filter(|candidate| provider.is_type_alias(candidate))
3082 .any(|candidate| {
3083 visibility.template_alias_arguments_preserve_target(
3084 analyzer, file, candidate, &arguments, target,
3085 )
3086 })
3087 })
3088 });
3089 named_alias_selects_target
3090 || candidates.iter().any(|candidate| {
3091 (same_visible_symbol(candidate, target)
3092 && visibility.is_primary_template(target)
3093 && direct_template_name == Some(candidate.identifier()))
3094 || visibility.template_alias_arguments_preserve_target(
3095 analyzer, file, candidate, &arguments, target,
3096 )
3097 || visibility
3098 .resolve_template_arguments(file, candidate.clone(), &arguments)
3099 .is_ok_and(|resolved| same_visible_symbol(&resolved, target))
3100 })
3101}
3102
3103fn template_reference_name_node(node: Node<'_>) -> Option<Node<'_>> {
3104 let template = if node.kind() == "template_type" {
3105 node
3106 } else {
3107 node.child_by_field_name("name")
3108 .filter(|name| name.kind() == "template_type")?
3109 };
3110 template.child_by_field_name("name")
3111}
3112
3113fn type_resolution_matches_target(
3114 node: Node<'_>,
3115 unit: &CodeUnit,
3116 candidates: &[CodeUnit],
3117 ctx: &ScanCtx<'_>,
3118) -> bool {
3119 type_resolution_matches_unit_target(node, unit, candidates, &ctx.spec.target, ctx)
3120}
3121
3122fn type_resolution_matches_unit_target(
3123 node: Node<'_>,
3124 unit: &CodeUnit,
3125 candidates: &[CodeUnit],
3126 target: &CodeUnit,
3127 ctx: &ScanCtx<'_>,
3128) -> bool {
3129 target_alias_candidates_visible(candidates, node, ctx)
3130 && type_resolution_identifies_unit_target(node, unit, candidates, target, ctx)
3131}
3132
3133fn type_resolution_identifies_unit_target(
3141 node: Node<'_>,
3142 unit: &CodeUnit,
3143 candidates: &[CodeUnit],
3144 target: &CodeUnit,
3145 ctx: &ScanCtx<'_>,
3146) -> bool {
3147 if !template_alias_owner_matches_reference(node, target, ctx) {
3148 return false;
3149 }
3150 if ctx.visibility.is_template_specialization(target)
3151 && cpp_template_reference_arguments(node, ctx.source).is_some()
3152 {
3153 let selected_unit =
3154 cpp_template_reference_arguments(node, ctx.source).and_then(|arguments| {
3155 ctx.visibility
3156 .resolve_template_arguments(ctx.file, unit.clone(), &arguments)
3157 .ok()
3158 });
3159 return selected_unit
3160 .as_ref()
3161 .is_some_and(|selected| same_visible_symbol(selected, target))
3162 || template_reference_candidates_select_target(
3163 node,
3164 candidates,
3165 &ctx.analyzer,
3166 ctx.visibility,
3167 ctx.file,
3168 ctx.source,
3169 target,
3170 );
3171 }
3172 ctx.visibility
3173 .same_template_member_identity(&ctx.analyzer, unit, target)
3174 || ctx.visibility.structured_class_alias_resolves_to_target(
3175 &ctx.analyzer,
3176 ctx.file,
3177 unit,
3178 target,
3179 )
3180 || candidates.iter().any(|candidate| {
3181 ctx.visibility
3182 .same_template_member_identity(&ctx.analyzer, candidate, target)
3183 || ctx.visibility.structured_class_alias_resolves_to_target(
3184 &ctx.analyzer,
3185 ctx.file,
3186 candidate,
3187 target,
3188 )
3189 })
3190}
3191
3192fn template_alias_owner_matches_reference(
3198 node: Node<'_>,
3199 target: &CodeUnit,
3200 ctx: &ScanCtx<'_>,
3201) -> bool {
3202 if !ctx
3203 .analyzer
3204 .type_alias_provider()
3205 .is_some_and(|provider| provider.is_type_alias(target))
3206 {
3207 return true;
3208 }
3209 let Some(target_owner) = ctx.analyzer.parent_of(target) else {
3210 return true;
3211 };
3212 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
3213 return true;
3214 };
3215 if !ctx.visibility.is_template_specialization(&target_owner)
3216 && !ctx.visibility.is_template_specialization(&reference_owner)
3217 {
3218 return true;
3219 }
3220 same_visible_symbol(&target_owner, &reference_owner)
3221}
3222
3223fn inherited_injected_class_qualifier_scope<'tree>(
3224 node: Node<'tree>,
3225 ctx: &ScanCtx<'_>,
3226) -> Option<Node<'tree>> {
3227 let qualified = qualified_owner_components(node, ctx.source)?;
3228 if qualified.global || qualified.names.is_empty() {
3229 return None;
3230 }
3231 let injected_name = &qualified.names[0];
3232 if !ctx.spec.target.is_class()
3233 || ctx.spec.target.identifier() != injected_name
3234 || physically_visible_type_target(ctx).is_none()
3235 {
3236 return None;
3237 }
3238 let enclosing_owner = structured_enclosing_owner(node, ctx)?;
3239 let hierarchy = ctx.analyzer.type_hierarchy_provider()?;
3240 let mut frontier = hierarchy.get_direct_ancestors(&enclosing_owner);
3241 let mut visited = HashSet::default();
3242 while !frontier.is_empty() {
3243 let mut level_matches = Vec::new();
3244 let mut next_frontier = Vec::new();
3245 for raw_owner in frontier {
3246 let owner = ctx.visibility.canonical_visible_full_type_unit(
3247 &ctx.analyzer,
3248 ctx.file,
3249 &raw_owner,
3250 )?;
3251 if !visited.insert(owner.clone()) {
3252 continue;
3253 }
3254 if owner.identifier() == injected_name
3255 && !level_matches
3256 .iter()
3257 .any(|existing| same_logical_symbol(existing, &owner))
3258 {
3259 level_matches.push(owner.clone());
3260 }
3261 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
3262 }
3263 if let Some(first) = level_matches.first() {
3264 if level_matches
3265 .iter()
3266 .all(|candidate| same_logical_symbol(candidate, first))
3267 && same_visible_symbol(first, &ctx.spec.target)
3268 {
3269 return qualified.nodes.first().copied();
3270 }
3271 return None;
3274 }
3275 frontier = next_frontier;
3276 }
3277 None
3278}
3279
3280fn target_guided_qualifier_type_scopes<'tree>(
3283 node: Node<'tree>,
3284 ctx: &ScanCtx<'_>,
3285) -> Option<Vec<Node<'tree>>> {
3286 if !matches!(
3287 node.kind(),
3288 "qualified_identifier" | "scoped_type_identifier"
3289 ) {
3290 return None;
3291 }
3292 let target = physically_visible_type_target(ctx)?;
3293 let qualified = qualified_owner_components(node, ctx.source)?;
3294 let lexical_scope = match enclosing_lexical_scope_components(
3303 node,
3304 &ctx.analyzer,
3305 ctx.visibility,
3306 ctx.file,
3307 ctx.source,
3308 ) {
3309 LexicalScopeResolution::Resolved(scope) => scope,
3310 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => {
3311 enclosing_namespace_components(node, ctx.source)
3312 }
3313 };
3314 let indexed_owner_scope =
3315 indexed_enclosing_owner_scope(&ctx.analyzer, ctx.visibility, ctx.file, node);
3316 let recovered_owner_scope = ctx.recovered_sentinel_scope(node);
3317 let mut matches = Vec::new();
3318 for component_count in 1..=qualified.names.len() {
3319 let components = &qualified.names[..component_count];
3320 let lexical_tiers = lexical_component_tiers(components, qualified.global, &lexical_scope)
3321 .collect::<Vec<_>>();
3322 let name = components.last()?;
3323 let mut candidates = Vec::new();
3324 let mut exact_candidates = Vec::new();
3325 for candidate in ctx
3326 .visibility
3327 .visible_identifier_candidates(ctx.file, name)
3328 .filter(|candidate| candidate.is_class())
3329 .filter(|candidate| type_candidate_visible_at_reference(candidate, node, ctx))
3330 {
3331 let candidate_components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3332 brokk_bifrost_core::analyzer::Language::Cpp,
3333 &cpp_name_for(candidate),
3334 );
3335 if !candidate_components.ends_with(components)
3336 || candidates
3337 .iter()
3338 .any(|existing| same_logical_symbol(existing, candidate))
3339 {
3340 continue;
3341 }
3342 let exact_lexical_scope = lexical_tiers
3343 .iter()
3344 .any(|expected| expected == &candidate_components);
3345 let candidate_owner = &candidate_components[..candidate_components.len() - 1];
3346 let structured_owner_match = indexed_owner_scope
3347 .as_ref()
3348 .is_some_and(|owner| owner.starts_with(candidate_owner))
3349 || recovered_owner_scope
3350 .as_ref()
3351 .is_some_and(|owner| owner.starts_with(candidate_owner));
3352 let class_alias_owner_match = ctx
3353 .analyzer
3354 .type_alias_provider()
3355 .is_some_and(|provider| provider.is_type_alias(candidate))
3356 && member_alias_owner_matches_reference_for(candidate, node, ctx);
3357 let macro_namespace_owner_match = is_declaration_name(node)
3358 && macro_namespace_scope_matches(candidate_owner, node, ctx);
3359 if components.len() == 1
3360 && candidate_components != components
3361 && !exact_lexical_scope
3362 && !structured_owner_match
3363 && !class_alias_owner_match
3364 && !macro_namespace_owner_match
3365 {
3366 continue;
3367 }
3368 candidates.push(candidate.clone());
3369 if exact_lexical_scope {
3370 exact_candidates.push(candidate.clone());
3371 }
3372 }
3373 if !exact_candidates.is_empty() {
3374 candidates = exact_candidates;
3375 }
3376 let canonical_alias_target_matches = matches!(
3381 candidates.as_slice(),
3382 [candidate]
3383 if ctx
3384 .analyzer
3385 .type_alias_provider()
3386 .is_some_and(|provider| provider.is_type_alias(candidate))
3387 && type_candidate_visible_at_reference(candidate, node, ctx)
3388 && same_visible_symbol(&canonical_alias_target(candidate, ctx), target)
3389 && (brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3390 brokk_bifrost_core::analyzer::Language::Cpp,
3391 &cpp_name_for(candidate),
3392 ) == components
3393 || member_alias_owner_matches_reference_for(candidate, node, ctx))
3394 );
3395 let direct_alias_target = ctx
3396 .analyzer
3397 .type_alias_provider()
3398 .is_some_and(|provider| provider.is_type_alias(target))
3399 && candidates
3400 .iter()
3401 .any(|candidate| same_symbol(candidate, target));
3402 let unique_target = matches!(
3403 candidates.as_slice(),
3404 [candidate] if same_visible_symbol(candidate, target)
3405 );
3406 if direct_alias_target || unique_target || canonical_alias_target_matches {
3407 let matched = if ctx
3408 .analyzer
3409 .type_alias_provider()
3410 .is_some_and(|provider| provider.is_type_alias(target))
3411 && ctx
3412 .analyzer
3413 .parent_of(target)
3414 .is_some_and(|owner| owner.is_class())
3415 && ctx
3416 .visibility
3417 .is_exhaustive_same_fqn_type_declaration_family(&ctx.analyzer, ctx.file, target)
3418 {
3419 qualified.nodes[component_count - 1]
3423 } else {
3424 qualified_type_component_hit_node(qualified.nodes[component_count - 1], node)
3425 };
3426 if template_type_component_preserves_target(matched, &candidates, ctx) {
3427 matches.push(matched);
3428 }
3429 }
3430 }
3431 (!matches.is_empty()).then_some(matches)
3432}
3433
3434fn target_guided_unproven_out_of_line_owner<'tree>(
3437 node: Node<'tree>,
3438 ctx: &ScanCtx<'_>,
3439) -> Option<Node<'tree>> {
3440 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
3441 || !is_declaration_name(node)
3442 {
3443 return None;
3444 }
3445 let target = physically_visible_type_target(ctx)?;
3446 if !target.is_class() {
3447 return None;
3448 }
3449 let qualified = qualified_owner_components(node, ctx.source)?;
3450 let target_components = canonical_cpp_scope_components(target);
3451 let mut scope = ctx
3452 .recovered_sentinel_scope(node)
3453 .or_else(|| {
3454 let parser_scope = enclosing_namespace_components(node, ctx.source);
3455 (!parser_scope.is_empty()).then_some(parser_scope)
3456 })
3457 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
3458 let target_namespace = &target_components[..target_components.len().saturating_sub(1)];
3459 if has_malformed_wrapper_function_definition_ancestor(node)
3460 && target_namespace.starts_with(&scope)
3461 && target_namespace.len() > scope.len()
3462 {
3463 scope = target_namespace.to_vec();
3464 }
3465 if !lexical_component_tiers(&qualified.names, qualified.global, &scope)
3466 .any(|components| components == target_components)
3467 {
3468 return None;
3469 }
3470 let owner_name = qualified.names.last()?;
3471 let candidates = ctx
3472 .visibility
3473 .visible_identifier_candidates(ctx.file, owner_name)
3474 .filter(|candidate| {
3475 candidate.is_class() && canonical_cpp_scope_components(candidate) == target_components
3476 })
3477 .collect::<Vec<_>>();
3478 if candidates.is_empty()
3479 || candidates
3480 .iter()
3481 .any(|candidate| !same_visible_symbol(candidate, target))
3482 {
3483 return None;
3484 }
3485 qualified.nodes.last().copied()
3486}
3487
3488fn macro_namespace_scope_matches(
3489 candidate_owner: &[String],
3490 node: Node<'_>,
3491 ctx: &ScanCtx<'_>,
3492) -> bool {
3493 let namespace = enclosing_namespace_components(node, ctx.source);
3494 if namespace.is_empty() || candidate_owner.is_empty() {
3495 return false;
3496 }
3497 let mut expanded_owner = Vec::new();
3498 for component in candidate_owner {
3499 if let Some(replacement) =
3500 ctx.visibility
3501 .object_macro_replacement_at(ctx.file, component, node.start_byte())
3502 {
3503 let replacement_components =
3504 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3505 brokk_bifrost_core::analyzer::Language::Cpp,
3506 &replacement,
3507 );
3508 if replacement_components.is_empty() {
3509 return false;
3510 }
3511 expanded_owner.extend(replacement_components);
3512 } else {
3513 expanded_owner.push(component.clone());
3514 }
3515 }
3516 expanded_owner == namespace
3517}
3518
3519fn target_guided_missing_type_leaf<'tree>(
3520 node: Node<'tree>,
3521 ctx: &ScanCtx<'_>,
3522) -> Option<Node<'tree>> {
3523 physically_visible_type_target(ctx)?;
3524 target_guided_missing_dependent_nested_type_leaf(node, ctx)
3525 .or_else(|| target_guided_missing_declaration_type_leaf(node, ctx))
3526 .or_else(|| target_guided_missing_alias_rhs_type_leaf(node, ctx))
3527 .or_else(|| target_guided_missing_class_alias_target_type_leaf(node, ctx))
3528 .or_else(|| target_guided_missing_member_alias_type_leaf(node, ctx))
3529 .or_else(|| target_guided_missing_template_argument_type_leaf(node, ctx))
3530 .or_else(|| target_guided_missing_orphaned_namespace_type_leaf(node, ctx))
3531}
3532
3533fn target_guided_missing_class_alias_target_type_leaf<'tree>(
3537 node: Node<'tree>,
3538 ctx: &ScanCtx<'_>,
3539) -> Option<Node<'tree>> {
3540 if node.kind() != "type_identifier"
3541 || is_declaration_name(node)
3542 || local_type_name_shadows(node, ctx)
3543 {
3544 return None;
3545 }
3546 let alias_provider = ctx.analyzer.type_alias_provider()?;
3547 let name = node_text(node, ctx.source);
3548 let aliases = ctx
3549 .visibility
3550 .visible_identifier_candidates(ctx.file, name)
3551 .filter(|candidate| alias_provider.is_type_alias(candidate))
3552 .filter(|candidate| {
3553 ctx.analyzer
3554 .parent_of(candidate)
3555 .is_some_and(|owner| owner.is_class())
3556 })
3557 .filter(|candidate| member_alias_owner_matches_reference_for(candidate, node, ctx))
3558 .filter(|candidate| {
3559 ctx.visibility
3560 .external_type_candidate_guard_compatible_in_context(
3561 &ctx.analyzer,
3562 ctx.file,
3563 candidate,
3564 node,
3565 )
3566 })
3567 .collect::<Vec<_>>();
3568 (!aliases.is_empty()
3569 && aliases.iter().all(|candidate| {
3570 same_visible_symbol(&canonical_alias_target(candidate, ctx), &ctx.spec.target)
3571 }))
3572 .then_some(node)
3573}
3574
3575fn target_guided_ambiguous_owned_alias_type_leaf<'tree>(
3579 node: Node<'tree>,
3580 ctx: &ScanCtx<'_>,
3581) -> Option<Node<'tree>> {
3582 let parameter = nearest_declaration_type_context(node).is_some_and(|declaration| {
3583 matches!(
3584 declaration.kind(),
3585 "parameter_declaration" | "optional_parameter_declaration"
3586 )
3587 });
3588 let placement_new_type = node.parent().is_some_and(|parent| {
3589 parent.kind() == "new_expression" && parent.child_by_field_name("type") == Some(node)
3590 });
3591 if !parameter && !placement_new_type {
3592 return None;
3593 }
3594 if !ctx
3595 .analyzer
3596 .type_alias_provider()
3597 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3598 || !type_alias_owner_matches_structured_reference(node, ctx)
3599 {
3600 return None;
3601 }
3602 target_guided_missing_declaration_type_leaf(node, ctx)
3603}
3604
3605fn target_guided_missing_dependent_nested_type_leaf<'tree>(
3612 node: Node<'tree>,
3613 ctx: &ScanCtx<'_>,
3614) -> Option<Node<'tree>> {
3615 if !matches!(
3616 node.kind(),
3617 "qualified_identifier" | "scoped_type_identifier"
3618 ) || !qualified_type_scope_contains_template(node)
3619 {
3620 return None;
3621 }
3622 let name = node
3623 .child_by_field_name("name")
3624 .filter(|name| name.kind() == "type_identifier")?;
3625 if node_text(name, ctx.source) != ctx.spec.target.identifier() {
3626 return None;
3627 }
3628 let owner_target = ctx.analyzer.parent_of(&ctx.spec.target)?;
3629 if !owner_target.is_class() {
3630 return None;
3631 }
3632 let owner = node.child_by_field_name("scope")?;
3633 let owner_resolution = resolve_type_node_lexically_for_target(
3634 owner,
3635 &ctx.analyzer,
3636 ctx.visibility,
3637 &ctx.ordinary_type_imports,
3638 ctx.file,
3639 ctx.source,
3640 &owner_target,
3641 Some(&ctx.lexical_scope_cache),
3642 ctx.recovered_sentinel_scope(owner).as_deref(),
3643 );
3644 if matches!(
3645 owner_resolution,
3646 LexicalTypeResolution::Resolved {
3647 ref unit,
3648 ref candidates,
3649 ..
3650 } if type_resolution_matches_unit_target(
3651 owner,
3652 unit,
3653 candidates,
3654 &owner_target,
3655 ctx,
3656 )
3657 ) {
3658 return Some(name);
3659 }
3660
3661 let qualified = qualified_owner_components(node, ctx.source)?;
3662 let parser_namespace = enclosing_namespace_components(node, ctx.source);
3663 let orphaned_namespace = ctx
3664 .orphaned_namespaces
3665 .iter()
3666 .filter(|envelope| envelope.error_marked && envelope.body_end < node.start_byte())
3667 .max_by_key(|envelope| envelope.body_end)
3668 .map(|envelope| envelope.components.clone());
3669 let indexed_scope = ctx
3670 .recovered_sentinel_scope(node)
3671 .or_else(|| (!parser_namespace.is_empty()).then_some(parser_namespace))
3672 .or(orphaned_namespace)
3673 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node))?;
3674 let owner_components = canonical_cpp_scope_components(&owner_target);
3675 if !lexical_component_tiers(&qualified.names, qualified.global, &indexed_scope)
3676 .any(|components| components == owner_components)
3677 {
3678 return None;
3679 }
3680 let scoped_candidates = visible_type_identifier_candidates(ctx, owner_target.identifier())
3681 .into_iter()
3682 .filter(|candidate| canonical_cpp_scope_components(candidate) == owner_components)
3683 .collect::<Vec<_>>();
3684 (!scoped_candidates.is_empty()
3685 && scoped_candidates
3686 .iter()
3687 .all(|candidate| same_visible_symbol(candidate, &owner_target)))
3688 .then_some(name)
3689}
3690
3691fn target_guided_missing_orphaned_namespace_type_leaf<'tree>(
3706 node: Node<'tree>,
3707 ctx: &ScanCtx<'_>,
3708) -> Option<Node<'tree>> {
3709 let target = &ctx.spec.target;
3710 let name = node_text(node, ctx.source);
3711 let (components, global) = type_reference_components(node, ctx.source)?;
3712 if global || components.len() != 1 || components[0] != name {
3713 return None;
3714 }
3715 if !target.is_class()
3716 || ctx
3717 .analyzer
3718 .type_alias_provider()
3719 .is_some_and(|provider| provider.is_type_alias(target))
3720 || is_declaration_name(node)
3721 || name != target.identifier()
3722 || ctx.local_shadows.is_shadowed(name)
3723 || local_type_name_shadows(node, ctx)
3724 || !ctx.visibility.is_physically_visible(ctx.file, target)
3725 || !ctx.visibility.external_type_candidate_visible_in_context(
3726 &ctx.analyzer,
3727 ctx.file,
3728 target,
3729 node,
3730 )
3731 {
3732 return None;
3733 }
3734
3735 let target_components = canonical_cpp_scope_components(target);
3736 let target_package_len = target_components.len().checked_sub(1)?;
3737 let target_package = &target_components[..target_package_len];
3738 let surviving_namespace = enclosing_namespace_components(node, ctx.source);
3739 if !target_package.starts_with(&surviving_namespace) {
3740 return None;
3741 }
3742 let envelope = ctx
3743 .orphaned_namespaces
3744 .iter()
3745 .filter(|envelope| envelope.error_marked && envelope.body_end < node.start_byte())
3746 .max_by_key(|envelope| envelope.body_end)?;
3747 if envelope.components.as_slice() != target_package {
3748 return None;
3749 }
3750
3751 if target.source() == ctx.file
3752 && !ctx.analyzer.ranges(target).iter().any(|range| {
3753 range.start_byte < envelope.body_end && range.end_byte <= envelope.body_end
3754 })
3755 {
3756 return None;
3757 }
3758
3759 let candidates = visible_type_identifier_candidates(ctx, name);
3760 if candidates.is_empty()
3761 || candidates
3762 .iter()
3763 .any(|candidate| !same_visible_symbol(candidate, target))
3764 {
3765 return None;
3766 }
3767 Some(node)
3768}
3769
3770fn target_guided_missing_member_alias_type_leaf<'tree>(
3776 node: Node<'tree>,
3777 ctx: &ScanCtx<'_>,
3778) -> Option<Node<'tree>> {
3779 if !is_template_argument_type_leaf(node)
3780 || is_declaration_name(node)
3781 || ctx
3782 .target_declaration_ranges
3783 .iter()
3784 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte)
3785 || node_text(node, ctx.source) != ctx.spec.target.identifier()
3786 || local_type_name_shadows(node, ctx)
3787 || !ctx
3788 .analyzer
3789 .type_alias_provider()
3790 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
3791 {
3792 return None;
3793 }
3794 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node);
3795 let owner_scope_matches = member_alias_owner_matches_reference(node, ctx);
3796 if !owner_scope_matches
3797 && !indexed_scope.is_some_and(|scope| {
3798 indexed_scope_matches_target_name(
3799 &scope,
3800 &[ctx.spec.target.identifier().to_string()],
3801 false,
3802 &ctx.spec.target,
3803 )
3804 })
3805 {
3806 return None;
3807 }
3808 if !(ctx.visibility.external_type_candidate_visible_in_context(
3815 &ctx.analyzer,
3816 ctx.file,
3817 &ctx.spec.target,
3818 node,
3819 ) || owner_scope_matches && member_alias_complete_class_context(node, ctx))
3820 {
3821 return None;
3822 }
3823 let target_visible = ctx
3824 .visibility
3825 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
3826 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
3827 target_visible.then_some(node)
3828}
3829
3830fn target_guided_missing_template_argument_type_leaf<'tree>(
3836 node: Node<'tree>,
3837 ctx: &ScanCtx<'_>,
3838) -> Option<Node<'tree>> {
3839 let target = &ctx.spec.target;
3840 let name = node_text(node, ctx.source);
3841 if !target.is_class()
3842 || !is_template_argument_type_leaf(node)
3843 || is_declaration_name(node)
3844 || name != target.identifier()
3845 || ctx.local_shadows.is_shadowed(name)
3846 || local_type_name_shadows(node, ctx)
3847 || !ctx.visibility.is_physically_visible(ctx.file, target)
3848 || ctx
3849 .analyzer
3850 .type_alias_provider()
3851 .is_some_and(|provider| provider.is_type_alias(target))
3852 {
3853 return None;
3854 }
3855
3856 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
3857 let target_components = canonical_cpp_scope_components(target);
3858 if target_components.last().map(String::as_str) != Some(name)
3859 || !lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
3860 .any(|components| components == target_components)
3861 {
3862 return None;
3863 }
3864
3865 let candidates = visible_type_identifier_candidates(ctx, name);
3869 if candidates.is_empty()
3870 || candidates.iter().any(|candidate| {
3871 !candidate.is_class()
3872 || ctx
3873 .analyzer
3874 .type_alias_provider()
3875 .is_some_and(|provider| provider.is_type_alias(candidate))
3876 || (!same_visible_symbol(candidate, target)
3877 && lexical_component_tiers(&[name.to_string()], false, &indexed_scope)
3878 .any(|components| components == canonical_cpp_scope_components(candidate)))
3879 })
3880 || !candidates
3881 .iter()
3882 .any(|candidate| same_visible_symbol(candidate, target))
3883 {
3884 return None;
3885 }
3886
3887 ctx.visibility
3890 .external_type_candidate_visible_in_context(&ctx.analyzer, ctx.file, target, node)
3891 .then_some(node)
3892}
3893
3894fn member_alias_owner_matches_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
3899 member_alias_owner_matches_reference_for(&ctx.spec.target, node, ctx)
3900}
3901
3902fn member_alias_owner_matches_reference_for(
3903 target: &CodeUnit,
3904 node: Node<'_>,
3905 ctx: &ScanCtx<'_>,
3906) -> bool {
3907 let Some(owner) = ctx.analyzer.parent_of(target) else {
3908 return false;
3909 };
3910 if !owner.is_class() {
3911 return false;
3912 }
3913 let reference_owner = ctx
3914 .class_ranges
3915 .and_then(|class_ranges| class_ranges.enclosing_unit(node.start_byte()).cloned())
3916 .or_else(|| structured_enclosing_owner(node, ctx));
3917 if reference_owner.as_ref().is_some_and(|reference_owner| {
3918 ctx.visibility
3919 .same_template_owner_identity(&owner, reference_owner)
3920 }) {
3921 return true;
3922 }
3923 if reference_owner.is_some_and(|reference_owner| {
3924 matches!(
3925 resolve_declaring_member_owner(
3926 &ctx.analyzer,
3927 ctx.visibility,
3928 ctx.file,
3929 &reference_owner,
3930 target.identifier(),
3931 ),
3932 EnclosingMemberOwnerResolution::Owner(declaring_owner)
3933 if ctx
3934 .visibility
3935 .same_template_owner_identity(&owner, &declaring_owner)
3936 )
3937 }) {
3938 return true;
3939 }
3940 let range = Range {
3941 start_byte: node.start_byte(),
3942 end_byte: node.end_byte(),
3943 start_line: node.start_position().row + 1,
3944 end_line: node.end_position().row + 1,
3945 };
3946 let mut indexed_enclosing = ctx.analyzer.enclosing_code_unit(ctx.file, &range);
3947 while let Some(candidate) = indexed_enclosing {
3948 if candidate.is_class()
3949 && ctx
3950 .visibility
3951 .same_template_owner_identity(&owner, &candidate)
3952 {
3953 return true;
3954 }
3955 indexed_enclosing = ctx.analyzer.parent_of(&candidate);
3956 }
3957 if let Some(reference_body) = malformed_recovered_class_body(node) {
3958 let mut root = node;
3959 while let Some(parent) = root.parent() {
3960 root = parent;
3961 }
3962 if ctx.analyzer.ranges(target).iter().any(|range| {
3963 root.descendant_for_byte_range(range.start_byte, range.end_byte)
3964 .and_then(malformed_recovered_class_body)
3965 .is_some_and(|declaration_body| same_node(declaration_body, reference_body))
3966 }) {
3967 return true;
3968 }
3969 }
3970 if structured_enclosing_owner(node, ctx)
3971 .is_some_and(|reference_owner| same_logical_symbol(&owner, &reference_owner))
3972 {
3973 return true;
3974 }
3975 let owner_components = canonical_cpp_scope_components(&owner);
3976 if ctx
3977 .recovered_sentinel_scope(node)
3978 .is_some_and(|scope| scope == owner_components)
3979 {
3980 return true;
3981 }
3982 let Some(reference_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
3983 else {
3984 return false;
3985 };
3986 !owner_components.is_empty() && reference_scope == owner_components
3987}
3988
3989fn malformed_recovered_class_body(mut node: Node<'_>) -> Option<Node<'_>> {
3990 loop {
3991 if node.kind() == "compound_statement"
3992 && node
3993 .parent()
3994 .is_some_and(|parent| parent.kind() == "declaration_list")
3995 && node.prev_named_sibling().is_some_and(|header| {
3996 header.kind() == "ERROR"
3997 && header.end_byte() <= node.start_byte()
3998 && error_contains_class_header(header)
3999 })
4000 {
4001 return Some(node);
4002 }
4003 node = node.parent()?;
4004 }
4005}
4006
4007fn error_contains_class_header(node: Node<'_>) -> bool {
4008 let mut pending = vec![(node, 0usize)];
4009 while let Some((current, depth)) = pending.pop() {
4010 if matches!(current.kind(), "class" | "struct" | "union") {
4011 let mut sibling = current.next_sibling();
4012 let mut saw_name = false;
4013 while let Some(candidate) = sibling {
4014 match candidate.kind() {
4015 "comment" => {}
4016 "{" | "base_class_clause" | ":" => return saw_name,
4017 "identifier" | "type_identifier" if !saw_name => saw_name = true,
4018 _ if !candidate.is_named() => {}
4019 _ => break,
4020 }
4021 sibling = candidate.next_sibling();
4022 }
4023 }
4024 if depth >= 1 {
4025 continue;
4026 }
4027 let mut cursor = current.walk();
4028 pending.extend(
4029 current
4030 .children(&mut cursor)
4031 .filter(|child| child.kind() != "compound_statement")
4032 .map(|child| (child, depth + 1)),
4033 );
4034 }
4035 false
4036}
4037
4038fn member_alias_complete_class_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4039 has_ancestor_kind(node, "compound_statement")
4040 && ctx
4041 .visibility
4042 .external_type_candidate_guard_compatible_in_context(
4043 &ctx.analyzer,
4044 ctx.file,
4045 &ctx.spec.target,
4046 node,
4047 )
4048}
4049
4050fn type_alias_owner_matches_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4051 ctx.analyzer
4052 .type_alias_provider()
4053 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4054 && member_alias_owner_matches_reference(node, ctx)
4055}
4056
4057fn type_alias_owner_encloses_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4061 if !ctx
4062 .analyzer
4063 .type_alias_provider()
4064 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4065 {
4066 return false;
4067 }
4068 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4069 return false;
4070 };
4071 let mut reference_owner = structured_enclosing_owner(node, ctx);
4072 while let Some(owner) = reference_owner {
4073 if same_logical_symbol(&target_owner, &owner) {
4074 return true;
4075 }
4076 reference_owner = ctx.analyzer.parent_of(&owner);
4077 }
4078 false
4079}
4080
4081fn nearer_type_name_shadows_structured_reference(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4086 let Some(target_owner) = ctx.analyzer.parent_of(&ctx.spec.target) else {
4087 return false;
4088 };
4089 let Some(alias_provider) = ctx.analyzer.type_alias_provider() else {
4090 return false;
4091 };
4092 let Some(reference_owner) = structured_enclosing_owner(node, ctx) else {
4093 return false;
4094 };
4095 let candidates = ctx
4096 .visibility
4097 .visible_identifier_candidates(ctx.file, ctx.spec.target.identifier())
4098 .filter(|candidate| {
4099 candidate.is_class()
4100 && alias_provider.is_type_alias(candidate)
4101 && !same_visible_symbol(candidate, &ctx.spec.target)
4102 })
4103 .cloned()
4104 .collect::<Vec<_>>();
4105
4106 let mut owner = Some(reference_owner);
4107 while let Some(owner_unit) = owner {
4108 if same_logical_symbol(&target_owner, &owner_unit) {
4109 return false;
4110 }
4111 if candidates.iter().any(|candidate| {
4112 ctx.analyzer
4113 .parent_of(candidate)
4114 .is_some_and(|candidate_owner| {
4115 candidate_owner.is_class() && same_logical_symbol(&candidate_owner, &owner_unit)
4116 })
4117 && ctx
4118 .visibility
4119 .external_type_candidate_guard_compatible_in_context(
4120 &ctx.analyzer,
4121 ctx.file,
4122 candidate,
4123 node,
4124 )
4125 }) {
4126 return true;
4127 }
4128 owner = ctx.analyzer.parent_of(&owner_unit);
4129 }
4130 false
4131}
4132
4133fn local_type_name_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
4134 let Some(callable) = nearest_callable_scope(node) else {
4135 return false;
4136 };
4137 let mut root_callable = callable;
4138 let mut ancestor = callable.parent();
4139 while let Some(current) = ancestor {
4140 if matches!(current.kind(), "function_definition" | "lambda_expression") {
4141 root_callable = current;
4142 }
4143 ancestor = current.parent();
4144 }
4145
4146 let mut stack = vec![root_callable];
4147 while let Some(current) = stack.pop() {
4148 if current.start_byte() >= node.start_byte() {
4149 continue;
4150 }
4151 if let Some(name) = local_type_name_declaration_node(current)
4152 && node_text(name, ctx.source) == ctx.spec.target.identifier()
4153 && nearest_callable_scope(current).is_some_and(|owner| {
4154 !is_malformed_wrapper_function_definition(owner)
4155 && owner.start_byte() <= callable.start_byte()
4156 && callable.end_byte() <= owner.end_byte()
4157 })
4158 && local_alias_scope_contains_node(current, node)
4159 {
4160 return true;
4161 }
4162 let mut cursor = current.walk();
4163 stack.extend(current.named_children(&mut cursor));
4164 }
4165 false
4166}
4167
4168fn local_type_name_declaration_node(node: Node<'_>) -> Option<Node<'_>> {
4169 local_type_alias_name_node(node).or_else(|| match node.kind() {
4170 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => node
4171 .child_by_field_name("name")
4172 .filter(|name| is_declaration_name(*name)),
4173 _ => None,
4174 })
4175}
4176
4177fn nearest_callable_scope(mut node: Node<'_>) -> Option<Node<'_>> {
4178 loop {
4179 if matches!(node.kind(), "function_definition" | "lambda_expression") {
4180 return Some(node);
4181 }
4182 node = node.parent()?;
4183 }
4184}
4185
4186fn local_type_alias_name_node(node: Node<'_>) -> Option<Node<'_>> {
4187 match node.kind() {
4188 "alias_declaration" => node.child_by_field_name("name"),
4189 "type_definition" => node
4190 .child_by_field_name("declarator")
4191 .and_then(declarator_name_node),
4192 _ => None,
4193 }
4194}
4195
4196fn local_alias_scope_contains_node(alias: Node<'_>, node: Node<'_>) -> bool {
4197 let mut current = alias.parent();
4198 while let Some(parent) = current {
4199 if matches!(
4200 parent.kind(),
4201 "class_specifier" | "struct_specifier" | "union_specifier"
4202 ) {
4203 return false;
4204 }
4205 if parent.kind() == "compound_statement" {
4206 return parent.start_byte() <= node.start_byte()
4207 && node.end_byte() <= parent.end_byte();
4208 }
4209 if matches!(parent.kind(), "function_definition" | "lambda_expression") {
4210 let Some(body) = parent.child_by_field_name("body") else {
4211 return false;
4212 };
4213 return node_is_within(body, alias) && node_is_within(body, node);
4214 }
4215 current = parent.parent();
4216 }
4217 false
4218}
4219
4220fn target_guided_missing_declaration_type_leaf<'tree>(
4221 node: Node<'tree>,
4222 ctx: &ScanCtx<'_>,
4223) -> Option<Node<'tree>> {
4224 if is_declaration_name(node) {
4225 return None;
4226 }
4227 let component_nodes = cpp_name_component_nodes(node)?;
4228 let name_node = component_nodes.last().copied()?;
4229 let name = node_text(name_node, ctx.source);
4230 if name != ctx.spec.target.identifier() {
4231 return None;
4232 }
4233 let inside_target_declaration = ctx
4234 .target_declaration_ranges
4235 .iter()
4236 .any(|range| range.start_byte <= node.start_byte() && node.end_byte() <= range.end_byte);
4237 if !inside_target_declaration
4238 && !ctx.visibility.external_type_candidate_visible_in_context(
4239 &ctx.analyzer,
4240 ctx.file,
4241 &ctx.spec.target,
4242 node,
4243 )
4244 {
4245 return None;
4246 }
4247 let components = component_nodes
4248 .iter()
4249 .map(|component| node_text(*component, ctx.source).to_string())
4250 .collect::<Vec<_>>();
4251 let local_alias_shadow = local_type_name_shadows(node, ctx);
4252 let structured_alias_owner = type_alias_owner_matches_structured_reference(node, ctx);
4253 let indexed_alias_owner = ctx
4254 .analyzer
4255 .type_alias_provider()
4256 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target))
4257 && member_alias_owner_matches_reference(node, ctx);
4258 let target_alias_self_reference = inside_target_declaration
4259 && ctx
4260 .analyzer
4261 .type_alias_provider()
4262 .is_some_and(|provider| provider.is_type_alias(&ctx.spec.target));
4263 let member_alias_visible = ctx.visibility.external_type_candidate_visible_in_context(
4264 &ctx.analyzer,
4265 ctx.file,
4266 &ctx.spec.target,
4267 node,
4268 ) || member_alias_complete_class_context(node, ctx);
4269 if !target_alias_self_reference
4270 && !local_alias_shadow
4271 && member_alias_visible
4272 && (structured_alias_owner || indexed_alias_owner)
4273 {
4274 return Some(node);
4275 }
4276 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4277 let declaration = nearest_declaration_type_context(node)?;
4278 let exact_scope_match = indexed_scope_matches_target_name(
4279 &indexed_scope,
4280 &components,
4281 is_globally_qualified_cpp_name(node),
4282 &ctx.spec.target,
4283 );
4284 let candidates = visible_type_identifier_candidates(ctx, name);
4285 let unique_visible_target = !candidates.is_empty()
4286 && candidates
4287 .iter()
4288 .all(|candidate| same_visible_symbol(candidate, &ctx.spec.target));
4289 if matches!(declaration.kind(), "field_declaration" | "declaration") {
4290 let parser_lost_declaration_scope =
4291 target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target)
4292 && unique_visible_target;
4293 return (exact_scope_match || parser_lost_declaration_scope).then_some(node);
4294 }
4295 let lost_namespace_parameter_context =
4296 matches!(
4297 declaration.kind(),
4298 "parameter_declaration" | "optional_parameter_declaration"
4299 ) && target_guided_scope_lost_namespace(&indexed_scope, &ctx.spec.target);
4300 if exact_scope_match || (lost_namespace_parameter_context && unique_visible_target) {
4301 return Some(node);
4302 }
4303 None
4304}
4305
4306fn target_guided_missing_alias_rhs_type_leaf<'tree>(
4307 node: Node<'tree>,
4308 ctx: &ScanCtx<'_>,
4309) -> Option<Node<'tree>> {
4310 let mut stack = vec![node];
4311 while let Some(candidate) = stack.pop() {
4312 if candidate.kind() == "type_identifier"
4313 && !is_declaration_name(candidate)
4314 && matches!(
4315 candidate.parent().map(|parent| parent.kind()),
4316 Some("template_type")
4317 )
4318 {
4319 let mut current = candidate.parent();
4320 let mut saw_qualified = false;
4321 let mut saw_dependent = false;
4322 let mut saw_type_descriptor = false;
4323 let mut saw_alias_declaration = false;
4324 while let Some(ancestor) = current {
4325 match ancestor.kind() {
4326 "qualified_identifier" | "scoped_type_identifier" => saw_qualified = true,
4327 "dependent_type" => saw_dependent = true,
4328 "type_descriptor" => saw_type_descriptor = true,
4329 "alias_declaration" => {
4330 saw_alias_declaration = true;
4331 break;
4332 }
4333 "template_type"
4334 | "template_argument_list"
4335 | "typename"
4336 | "template_declaration" => {}
4337 _ => {}
4338 }
4339 current = ancestor.parent();
4340 }
4341 let name = node_text(candidate, ctx.source);
4342 let visible_candidates = visible_type_identifier_candidates(ctx, name);
4343 let canonical_alias_target = visible_candidates
4344 .iter()
4345 .filter_map(|alias| ctx.visibility.alias_target(alias))
4346 .any(|target| same_visible_symbol(&target, &ctx.spec.target));
4347 let alias_resolves = ctx.visibility.parser_alias_resolves_to_type(
4348 &ctx.analyzer,
4349 ctx.file,
4350 name,
4351 &ctx.spec.target,
4352 ) || canonical_alias_target;
4353 if saw_qualified
4354 && saw_dependent
4355 && saw_type_descriptor
4356 && saw_alias_declaration
4357 && alias_resolves
4358 && ctx.visibility.external_type_candidate_visible_in_context(
4359 &ctx.analyzer,
4360 ctx.file,
4361 &ctx.spec.target,
4362 candidate,
4363 )
4364 {
4365 return Some(candidate);
4366 }
4367 }
4368 for index in (0..candidate.named_child_count()).rev() {
4369 if let Some(child) = candidate.named_child(index) {
4370 stack.push(child);
4371 }
4372 }
4373 }
4374 None
4375}
4376
4377fn nearest_declaration_type_context(node: Node<'_>) -> Option<Node<'_>> {
4378 let mut current = Some(node);
4379 while let Some(ancestor) = current {
4380 if matches!(
4381 ancestor.kind(),
4382 "field_declaration"
4383 | "parameter_declaration"
4384 | "optional_parameter_declaration"
4385 | "declaration"
4386 | "type_descriptor"
4387 ) {
4388 let contains_type = ancestor
4389 .child_by_field_name("type")
4390 .is_some_and(|type_node| {
4391 type_node.start_byte() <= node.start_byte()
4392 && node.end_byte() <= type_node.end_byte()
4393 });
4394 if contains_type
4395 && !(ancestor.kind() == "type_descriptor" && is_template_argument_type_leaf(node))
4396 {
4397 return Some(ancestor);
4398 }
4399 if ancestor.kind() == "type_descriptor"
4400 && ancestor.parent().is_some_and(|parent| {
4401 matches!(
4402 parent.kind(),
4403 "cast_expression"
4404 | "new_expression"
4405 | "sizeof_expression"
4406 | "alignof_expression"
4407 | "typeid_expression"
4408 )
4409 })
4410 {
4411 return Some(ancestor);
4412 }
4413 }
4414 if matches!(
4415 ancestor.kind(),
4416 "compound_statement"
4417 | "translation_unit"
4418 | "namespace_definition"
4419 | "alias_declaration"
4420 | "type_definition"
4421 | "base_class_clause"
4422 ) {
4423 return None;
4424 }
4425 current = ancestor.parent();
4426 }
4427 None
4428}
4429
4430fn visible_type_identifier_candidates(ctx: &ScanCtx<'_>, name: &str) -> Vec<CodeUnit> {
4431 let mut candidates = Vec::new();
4432 for candidate in ctx
4433 .visibility
4434 .visible_identifier_candidates(ctx.file, name)
4435 .filter(|candidate| {
4436 candidate.is_class()
4437 || ctx
4438 .analyzer
4439 .type_alias_provider()
4440 .is_some_and(|provider| provider.is_type_alias(candidate))
4441 })
4442 {
4443 if !candidates
4444 .iter()
4445 .any(|existing| same_logical_symbol(existing, candidate))
4446 {
4447 candidates.push(candidate.clone());
4448 }
4449 }
4450 candidates
4451}
4452
4453fn target_guided_static_cast_alias_type_descriptor<'tree>(
4458 node: Node<'tree>,
4459 ctx: &ScanCtx<'_>,
4460) -> Option<Node<'tree>> {
4461 if node.kind() != "type_descriptor" {
4462 return None;
4463 }
4464 let argument_list = node.parent().filter(|parent| {
4465 parent.kind() == "template_argument_list"
4466 && parent.named_child_count() == 1
4467 && parent.named_child(0) == Some(node)
4468 })?;
4469 let template = argument_list.parent().filter(|parent| {
4470 parent.kind() == "template_function"
4471 && parent.child_by_field_name("arguments") == Some(argument_list)
4472 })?;
4473 let name = template.child_by_field_name("name")?;
4474 if name.kind() != "identifier" || node_text(name, ctx.source) != "static_cast" {
4475 return None;
4476 }
4477 let target = &ctx.spec.target;
4478 if node_text(node, ctx.source) != target.identifier()
4479 || !ctx
4480 .analyzer
4481 .type_alias_provider()
4482 .is_some_and(|provider| provider.is_type_alias(target))
4483 || !ctx.visibility.is_physically_visible(ctx.file, target)
4484 || !ctx.visibility.external_type_candidate_visible_in_context(
4485 &ctx.analyzer,
4486 ctx.file,
4487 target,
4488 node,
4489 )
4490 {
4491 return None;
4492 }
4493
4494 let indexed_scope = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)?;
4495 let target_scope = canonical_cpp_scope_components(target);
4496 let name_components = [target.identifier().to_string()];
4497 if !lexical_component_tiers(&name_components, false, &indexed_scope)
4498 .any(|components| components == target_scope)
4499 {
4500 return None;
4501 }
4502
4503 let candidates = visible_type_identifier_candidates(ctx, target.identifier());
4504 if !candidates
4505 .iter()
4506 .any(|candidate| same_visible_symbol(candidate, target))
4507 {
4508 return None;
4509 }
4510 if candidates.iter().any(|candidate| {
4511 !same_visible_symbol(candidate, target)
4512 && lexical_component_tiers(&name_components, false, &indexed_scope)
4513 .any(|components| components == canonical_cpp_scope_components(candidate))
4514 }) {
4515 return None;
4516 }
4517 Some(node)
4518}
4519
4520fn indexed_scope_matches_target_name(
4521 indexed_scope: &[String],
4522 components: &[String],
4523 global: bool,
4524 target: &CodeUnit,
4525) -> bool {
4526 let target_name = cpp_name_for(target);
4527 lexical_component_tiers(components, global, indexed_scope)
4528 .any(|qualified| qualified.join("::") == target_name)
4529}
4530
4531fn target_guided_scope_lost_namespace(indexed_scope: &[String], target: &CodeUnit) -> bool {
4532 if target.package_name().is_empty() {
4533 return false;
4534 }
4535 if indexed_scope.len() <= 1 {
4536 return true;
4537 }
4538 let mut target_scope = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4539 brokk_bifrost_core::analyzer::Language::Cpp,
4540 &cpp_name_for(target),
4541 );
4542 target_scope.pop();
4543 (1..indexed_scope.len())
4544 .rev()
4545 .any(|prefix_len| target_scope.ends_with(&indexed_scope[..prefix_len]))
4546}
4547
4548fn indexed_enclosing_lexical_scope(
4549 analyzer: &CppGraphSource<'_>,
4550 file: &ProjectFile,
4551 node: Node<'_>,
4552) -> Option<Vec<String>> {
4553 let range = Range {
4554 start_byte: node.start_byte(),
4555 end_byte: node.end_byte(),
4556 start_line: node.start_position().row,
4557 end_line: node.end_position().row,
4558 };
4559 let enclosing = analyzer.enclosing_code_unit(file, &range)?;
4560 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4561 brokk_bifrost_core::analyzer::Language::Cpp,
4562 &cpp_name_for(&enclosing),
4563 );
4564 if !enclosing.is_class() && !enclosing.is_module() {
4565 components.pop();
4566 }
4567 Some(components)
4568}
4569
4570fn static_qualifier_name_scope<'tree>(node: Node<'tree>, ctx: &ScanCtx<'_>) -> Option<Node<'tree>> {
4571 if node.kind() != "qualified_identifier" {
4572 return None;
4573 }
4574 let mut stack = vec![node];
4575 while let Some(current) = stack.pop() {
4576 if current.kind() != "qualified_identifier" {
4577 continue;
4578 }
4579 if let Some(scope) = current.child_by_field_name("scope") {
4580 let text = qualified_scope_text(scope, ctx.source);
4581 if name_mentions(&text, &ctx.spec.member_name) {
4582 return Some(scope);
4583 }
4584 }
4585 let mut cursor = current.walk();
4586 for child in current.named_children(&mut cursor) {
4587 if child.kind() == "qualified_identifier" {
4588 stack.push(child);
4589 }
4590 }
4591 }
4592 None
4593}
4594
4595fn qualified_scope_text(scope: Node<'_>, source: &str) -> String {
4596 let mut parts = vec![node_text(scope, source).to_string()];
4597 let mut current = scope.parent();
4598 while let Some(qualified) = current {
4599 let Some(parent) = qualified.parent() else {
4600 break;
4601 };
4602 if parent.kind() != "qualified_identifier"
4603 || parent.child_by_field_name("name") != Some(qualified)
4604 {
4605 break;
4606 }
4607 if let Some(outer_scope) = parent.child_by_field_name("scope") {
4608 parts.push(node_text(outer_scope, source).to_string());
4609 }
4610 current = Some(parent);
4611 }
4612 parts.reverse();
4613 parts.join("::")
4614}
4615
4616fn maybe_record_constructor_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4617 if node.kind() == "function_definition" {
4618 return;
4619 }
4620 if !matches!(
4621 node.kind(),
4622 "call_expression"
4623 | "new_expression"
4624 | "compound_literal_expression"
4625 | "declaration"
4626 | "field_initializer"
4627 ) {
4628 return;
4629 }
4630 let Some(owner) = ctx.spec.owner.as_ref() else {
4631 return;
4632 };
4633 if node.kind() == "field_initializer" {
4634 if !field_initializer_constructs_target(node, ctx, owner) {
4635 return;
4636 }
4637 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4638 match ctx
4639 .visibility
4640 .call_arity_evidence(ctx.file, node, ctx.source)
4641 .accepts(expected)
4642 {
4643 Some(true) => {}
4644 Some(false) => return,
4645 None => {
4646 push_unproven_hit(node, ctx);
4647 return;
4648 }
4649 }
4650 }
4651 push_hit(node, ctx);
4652 return;
4653 }
4654 if node.kind() == "declaration" {
4655 if declaration_is_object_construction_candidate(node, ctx)
4656 && declaration_mentions_type(node, ctx, owner)
4657 && ctx
4658 .spec
4659 .callable_arity_at(node.start_byte())
4660 .is_none_or(|expected| expected.accepts(declaration_constructor_arity(node, ctx)))
4661 {
4662 push_hit(node, ctx);
4663 }
4664 return;
4665 }
4666 let Some(type_node) = constructor_type_node(node) else {
4667 return;
4668 };
4669 let hit_node = function_terminal_node(type_node);
4670 let text = node_text(type_node, ctx.source);
4671 if !name_mentions(text, &ctx.spec.member_name) {
4672 return;
4673 }
4674 *ctx.raw_match_count += 1;
4675 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4676 match ctx
4677 .visibility
4678 .call_arity_evidence(ctx.file, node, ctx.source)
4679 .accepts(expected)
4680 {
4681 Some(true) => {}
4682 Some(false) => return,
4683 None => {
4684 push_unproven_hit(hit_node, ctx);
4685 return;
4686 }
4687 }
4688 }
4689 let structured_resolution = resolve_type_node_lexically_for_target(
4690 type_node,
4691 &ctx.analyzer,
4692 ctx.visibility,
4693 &ctx.ordinary_type_imports,
4694 ctx.file,
4695 ctx.source,
4696 owner,
4697 Some(&ctx.lexical_scope_cache),
4698 ctx.recovered_sentinel_scope(type_node).as_deref(),
4699 );
4700 let structurally_resolves = matches!(
4701 &structured_resolution,
4702 LexicalTypeResolution::Resolved {
4703 unit, candidates, ..
4704 } if same_visible_symbol(unit, owner)
4705 || candidates
4706 .iter()
4707 .any(|candidate| same_visible_symbol(candidate, owner))
4708 );
4709 if structurally_resolves
4710 || matches!(structured_resolution, LexicalTypeResolution::Missing)
4711 && ctx
4712 .visibility
4713 .resolves_to_type(&ctx.analyzer, ctx.file, text, owner)
4714 {
4715 push_hit(hit_node, ctx);
4716 } else {
4717 push_unproven_hit(hit_node, ctx);
4718 }
4719}
4720
4721fn maybe_record_free_function_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4722 if node.kind() == "function_definition" {
4723 maybe_record_free_function_definition_hit(node, ctx);
4724 return;
4725 }
4726 if node.kind() == "identifier" {
4727 maybe_record_free_function_value_reference(node, ctx);
4728 return;
4729 }
4730 if node.kind() != "call_expression" {
4731 return;
4732 }
4733 let Some(function) = node
4734 .child_by_field_name("function")
4735 .or_else(|| node.named_child(0))
4736 else {
4737 return;
4738 };
4739 let text = node_text(function, ctx.source);
4740 if !name_matches_callable(text, &ctx.spec.member_name) {
4741 return;
4742 }
4743 *ctx.raw_match_count += 1;
4744 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
4745 match ctx
4746 .visibility
4747 .call_arity_evidence(ctx.file, node, ctx.source)
4748 .accepts(expected)
4749 {
4750 Some(true) => {}
4751 Some(false) => return,
4752 None => {
4753 if !bare_name_binds_only_target(node, function, text, ctx) {
4760 push_unproven_hit(function_terminal_node(function), ctx);
4761 return;
4762 }
4763 }
4764 }
4765 }
4766 if matches!(function.kind(), "identifier" | "template_function") {
4767 let terminal = function_terminal_node(function);
4768 let name = node_text(terminal, ctx.source);
4769 if ctx.local_shadows.is_shadowed(name) {
4770 return;
4771 }
4772 if let Some(enclosing_owner) = structured_enclosing_owner(function, ctx)
4773 && !matches!(
4774 resolve_declaring_member_owner(
4775 &ctx.analyzer,
4776 ctx.visibility,
4777 ctx.file,
4778 &enclosing_owner,
4779 name,
4780 ),
4781 EnclosingMemberOwnerResolution::Missing
4782 )
4783 {
4784 return;
4785 }
4786 match resolve_bare_call_target(
4787 node,
4788 function,
4789 &ctx.analyzer,
4790 ctx.visibility,
4791 &ctx.ordinary_type_imports,
4792 ctx.file,
4793 ctx.source,
4794 ) {
4795 BareCallTargetResolution::FreeFunctions(units)
4796 if units
4797 .iter()
4798 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
4799 {
4800 if free_function_call_may_target(node, text, ctx) {
4801 let recursive = enclosing_context(terminal, ctx)
4802 .enclosing
4803 .as_ref()
4804 .is_some_and(|enclosing| same_logical_symbol(enclosing, &ctx.spec.target));
4805 if recursive {
4806 push_recursive_reference_hit(terminal, ctx);
4807 } else {
4808 push_hit(terminal, ctx);
4809 }
4810 }
4811 }
4812 BareCallTargetResolution::UnprovenFreeFunctions(units)
4813 if units
4814 .iter()
4815 .any(|unit| same_visible_symbol(unit, &ctx.spec.target)) =>
4816 {
4817 push_unproven_hit(terminal, ctx);
4818 }
4819 BareCallTargetResolution::FreeFunctions(_)
4820 | BareCallTargetResolution::UnprovenFreeFunctions(_)
4821 | BareCallTargetResolution::Type(_)
4822 | BareCallTargetResolution::CallableShadow => {}
4823 BareCallTargetResolution::Ambiguous | BareCallTargetResolution::Missing => {
4824 push_unproven_hit(terminal, ctx);
4825 }
4826 }
4827 return;
4828 }
4829 if !free_function_call_may_target(node, text, ctx) {
4830 return;
4831 }
4832 if ctx.visibility.contains_named_symbol(
4833 ctx.file,
4834 text,
4835 TargetKind::FreeFunction,
4836 &ctx.spec.target,
4837 ) {
4838 push_hit(function_terminal_node(function), ctx);
4839 } else if ctx.visibility.resolve_known_non_target(
4840 ctx.file,
4841 text,
4842 TargetKind::FreeFunction,
4843 &ctx.spec.target,
4844 ) {
4845 } else {
4848 push_unproven_hit(function_terminal_node(function), ctx);
4849 }
4850}
4851
4852fn bare_name_binds_only_target(
4861 call: Node<'_>,
4862 function: Node<'_>,
4863 text: &str,
4864 ctx: &ScanCtx<'_>,
4865) -> bool {
4866 if !matches!(function.kind(), "identifier" | "template_function") {
4867 return false;
4868 }
4869 let mut candidates = ctx
4870 .visibility
4871 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
4872 candidates.retain(|candidate| {
4873 ctx.visibility
4874 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
4875 });
4876 dedupe_callable_candidates(&mut candidates);
4877 matches!(candidates.as_slice(), [only] if same_visible_symbol(only, &ctx.spec.target))
4878}
4879
4880fn free_function_call_may_target(call: Node<'_>, text: &str, ctx: &ScanCtx<'_>) -> bool {
4881 if ctx.spec.param_types.is_none() {
4882 return true;
4883 }
4884 let mut candidates = ctx
4885 .visibility
4886 .named_candidates(ctx.file, text, TargetKind::FreeFunction);
4887 candidates.retain(|candidate| {
4888 ctx.visibility
4889 .declaration_visible_at(&ctx.analyzer, ctx.file, candidate, call.start_byte())
4890 });
4891 let Some(arity) = ctx
4892 .visibility
4893 .call_arity_evidence(ctx.file, call, ctx.source)
4894 .exact()
4895 else {
4896 return true;
4897 };
4898 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
4899 if candidates.is_empty()
4900 || !candidates
4901 .iter()
4902 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
4903 {
4904 return true;
4905 }
4906 let arg_types = call_argument_types(call, ctx);
4907 let filtered = cpp_filter_candidates_by_args(
4908 candidates,
4909 &arg_types,
4910 &|name| ctx.visibility.resolve_type(ctx.file, name),
4911 &|left, right| same_visible_symbol(left, right),
4912 );
4913 filtered
4914 .iter()
4915 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
4916}
4917
4918fn call_argument_types(call: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<Option<CppArgType>> {
4919 let Some(args) = call
4920 .child_by_field_name("arguments")
4921 .or_else(|| call.child_by_field_name("parameters"))
4922 .or_else(|| call.child_by_field_name("value"))
4923 else {
4924 return Vec::new();
4925 };
4926 argument_children(args)
4927 .map(|arg| expression_arg_type(arg, ctx))
4928 .collect()
4929}
4930
4931fn expression_arg_type(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CppArgType> {
4932 match node.kind() {
4933 "number_literal" | "true" | "false" | "char_literal" | "string_literal"
4934 | "unary_expression" => cpp_literal_arg_type(node, ctx.source).map(|mut literal| {
4935 literal.unit = ctx.visibility.resolve_type(ctx.file, &literal.name);
4936 literal
4937 }),
4938 "identifier" => ctx
4939 .bindings
4940 .resolve_symbol(node_text(node, ctx.source))
4941 .as_precise()
4942 .and_then(|bindings| bindings.iter().find_map(CppScanBinding::as_arg_type)),
4943 "parenthesized_expression" => node
4944 .child_by_field_name("argument")
4945 .or_else(|| node.named_child(0))
4946 .and_then(|inner| expression_arg_type(inner, ctx)),
4947 "pointer_expression" => {
4948 let delta = match node.child_by_field_name("operator")?.kind() {
4949 "&" => 1,
4950 "*" => -1,
4951 _ => return None,
4952 };
4953 let inner = node
4954 .child_by_field_name("argument")
4955 .or_else(|| node.named_child(0))?;
4956 let mut arg_type = expression_arg_type(inner, ctx)?;
4957 arg_type.indirection += delta;
4958 Some(arg_type)
4959 }
4960 _ => None,
4961 }
4962}
4963
4964fn maybe_record_free_function_value_reference(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4969 let text = node_text(node, ctx.source);
4970 if !name_matches_callable(text, &ctx.spec.member_name) {
4971 return;
4972 }
4973 if is_declaration_name(node) || is_call_callee_node(node) {
4974 return;
4975 }
4976 *ctx.raw_match_count += 1;
4977 if ctx.visibility.contains_named_symbol(
4978 ctx.file,
4979 text,
4980 TargetKind::FreeFunction,
4981 &ctx.spec.target,
4982 ) {
4983 push_hit(node, ctx);
4984 } else if ctx.visibility.resolve_known_non_target(
4985 ctx.file,
4986 text,
4987 TargetKind::FreeFunction,
4988 &ctx.spec.target,
4989 ) {
4990 } else {
4992 push_unproven_hit(node, ctx);
4993 }
4994}
4995
4996fn maybe_record_free_function_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
4997 let Some(function) = function_definition_name_node(node) else {
4998 return;
4999 };
5000 let text = node_text(function, ctx.source);
5001 if !name_matches_callable(text, &ctx.spec.member_name) {
5002 return;
5003 }
5004 *ctx.raw_match_count += 1;
5005 if !function_definition_signature_matches_target(node, ctx) {
5006 return;
5007 }
5008 if definition_name_candidates(function, ctx)
5009 .iter()
5010 .any(|name| {
5011 ctx.visibility.contains_named_symbol(
5012 ctx.file,
5013 name,
5014 TargetKind::FreeFunction,
5015 &ctx.spec.target,
5016 )
5017 })
5018 {
5019 push_definition_hit(function, ctx);
5020 } else if definition_name_candidates(function, ctx)
5021 .iter()
5022 .any(|name| {
5023 ctx.visibility.resolve_known_non_target(
5024 ctx.file,
5025 name,
5026 TargetKind::FreeFunction,
5027 &ctx.spec.target,
5028 )
5029 })
5030 {
5031 } else {
5033 push_unproven_definition_hit(function, ctx);
5034 }
5035}
5036
5037fn maybe_record_method_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5038 if node.kind() == "using_declaration" {
5039 maybe_record_using_member_hit(node, ctx);
5040 return;
5041 }
5042 if let Some(member) = recovered_direct_initializer_qualified_callable(node) {
5043 maybe_record_qualified_method_value_hit(node, member, ctx);
5044 return;
5045 }
5046 if let Some(value) = explicit_qualified_callable_value(node) {
5047 maybe_record_qualified_method_value_hit(value.qualified, value.member, ctx);
5048 return;
5049 }
5050 if node.kind() == "function_definition" {
5051 maybe_record_method_definition_hit(node, ctx);
5052 return;
5053 }
5054 if node.kind() != "call_expression" {
5055 return;
5056 }
5057 if let Some((receiver, operator)) = explicit_operator_call(node) {
5058 let text = node_text(operator, ctx.source);
5059 if !name_matches_callable(text, &ctx.spec.member_name) {
5060 return;
5061 }
5062 *ctx.raw_match_count += 1;
5063 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5064 match ctx
5065 .visibility
5066 .call_arity_evidence(ctx.file, node, ctx.source)
5067 .accepts(expected)
5068 {
5069 Some(true) => {}
5070 Some(false) => return,
5071 None => {
5072 push_unproven_hit(operator, ctx);
5073 return;
5074 }
5075 }
5076 }
5077 match explicit_receiver_target_resolution(receiver, ctx) {
5078 MethodReceiverTargetResolution::Target if receiver_is_self_like(receiver) => {
5079 push_self_receiver_hit(operator, ctx);
5080 }
5081 MethodReceiverTargetResolution::Target => push_hit(operator, ctx),
5082 MethodReceiverTargetResolution::Missing => push_unproven_hit(operator, ctx),
5083 MethodReceiverTargetResolution::NonTarget
5084 | MethodReceiverTargetResolution::Ambiguous => {}
5085 }
5086 return;
5087 }
5088 let Some(function) = node
5089 .child_by_field_name("function")
5090 .or_else(|| node.named_child(0))
5091 else {
5092 return;
5093 };
5094 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
5095 return;
5096 }
5097 if function.kind() == "identifier"
5098 && ctx
5099 .local_shadows
5100 .is_shadowed(node_text(function, ctx.source))
5101 {
5102 return;
5103 }
5104 *ctx.raw_match_count += 1;
5105 if let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) {
5106 match ctx
5107 .visibility
5108 .call_arity_evidence(ctx.file, node, ctx.source)
5109 .accepts(expected)
5110 {
5111 Some(true) => {}
5112 Some(false) => return,
5113 None => {
5114 push_unproven_hit(function_terminal_node(function), ctx);
5115 return;
5116 }
5117 }
5118 }
5119 if !method_call_may_target(node, ctx) {
5120 return;
5121 }
5122 if is_structurally_qualified(function) {
5123 match qualified_owner_resolution(function, ctx) {
5124 QualifiedOwnerResolution::Target => {
5125 push_hit(function_terminal_node(function), ctx);
5126 }
5127 QualifiedOwnerResolution::NonTarget => {}
5128 QualifiedOwnerResolution::Unresolved => {
5129 push_unproven_hit(function_terminal_node(function), ctx);
5130 }
5131 }
5132 return;
5133 }
5134 match call_function_target_resolution(function, ctx) {
5135 MethodReceiverTargetResolution::Target
5136 if call_function_has_direct_self_receiver(function) =>
5137 {
5138 push_self_receiver_hit(function_terminal_node(function), ctx);
5139 }
5140 MethodReceiverTargetResolution::Target => {
5141 push_hit(function_terminal_node(function), ctx);
5142 }
5143 MethodReceiverTargetResolution::NonTarget | MethodReceiverTargetResolution::Ambiguous => {}
5144 MethodReceiverTargetResolution::Missing
5150 if inherited_target_owner_context(function, ctx) =>
5151 {
5152 push_hit(function_terminal_node(function), ctx);
5153 }
5154 MethodReceiverTargetResolution::Missing
5155 if same_owner_context(function, ctx)
5156 || out_of_line_target_owner_context(function, ctx) =>
5157 {
5158 push_self_receiver_hit(function_terminal_node(function), ctx);
5159 }
5160 MethodReceiverTargetResolution::Missing
5161 if function.kind() == "identifier"
5162 && resolves_to_lexical_free_function(function, ctx) =>
5163 {
5164 }
5167 MethodReceiverTargetResolution::Missing
5168 if !receiver_has_known_non_target(function, ctx)
5169 && !known_non_target_owner_context(function, ctx) =>
5170 {
5171 push_unproven_hit(function_terminal_node(function), ctx);
5172 }
5173 MethodReceiverTargetResolution::Missing => {}
5174 }
5175}
5176
5177fn recovered_direct_initializer_qualified_callable(node: Node<'_>) -> Option<Node<'_>> {
5178 if node.kind() != "qualified_identifier" {
5179 return None;
5180 }
5181 let parameter = node
5182 .parent()
5183 .filter(|parent| parent.kind() == "parameter_declaration")?;
5184 let parameter_declarator = parameter.child_by_field_name("declarator")?;
5185 if parameter.child_by_field_name("type") != Some(node)
5190 || parameter_declarator.kind() != "abstract_function_declarator"
5191 {
5192 return None;
5193 }
5194 let parameter_list = parameter
5195 .parent()
5196 .filter(|parent| parent.kind() == "parameter_list")?;
5197 if parameter_list.named_child_count() != 1 {
5198 return None;
5199 }
5200 let function_declarator = parameter_list
5201 .parent()
5202 .filter(|parent| parent.kind() == "function_declarator")?;
5203 if function_declarator
5204 .child_by_field_name("declarator")
5205 .is_none_or(|declarator| declarator.kind() != "identifier")
5206 || function_declarator
5207 .parent()
5208 .is_none_or(|parent| parent.kind() != "declaration")
5209 {
5210 return None;
5211 }
5212 node.child_by_field_name("name")
5213}
5214
5215fn maybe_record_using_member_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5216 let Some(imported) = ordinary_using_declaration_type_node(node) else {
5217 return;
5218 };
5219 if !callable_node_matches(imported, &ctx.spec.member_name, ctx.source) {
5220 return;
5221 }
5222 let Some(target_owner) = ctx.spec.owner.as_ref() else {
5223 return;
5224 };
5225 *ctx.raw_match_count += 1;
5226 let owner_resolution = qualified_owner_components(imported, ctx.source)
5227 .map(|qualified| {
5228 let lexical_scope = match enclosing_lexical_scope_components(
5229 imported,
5230 &ctx.analyzer,
5231 ctx.visibility,
5232 ctx.file,
5233 ctx.source,
5234 ) {
5235 LexicalScopeResolution::Resolved(scope) => scope,
5236 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
5237 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
5238 };
5239 ctx.visibility.resolve_type_components_lexically(
5240 &ctx.analyzer,
5241 ctx.file,
5242 &qualified.names,
5243 qualified.global,
5244 &lexical_scope,
5245 )
5246 })
5247 .unwrap_or(LexicalTypeResolution::Missing);
5248 let matches_target_owner = matches!(
5249 owner_resolution,
5250 LexicalTypeResolution::Resolved {
5251 ref unit,
5252 ref candidates,
5253 ..
5254 } if same_visible_symbol(unit, target_owner)
5255 || candidates
5256 .iter()
5257 .any(|candidate| same_visible_symbol(candidate, target_owner))
5258 );
5259 if !matches_target_owner {
5260 match owner_resolution {
5261 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
5262 push_unproven_hit(imported, ctx);
5263 }
5264 LexicalTypeResolution::Resolved { .. } => {}
5265 }
5266 return;
5267 }
5268 match ctx.visibility.visible_member_for_owner_name(
5269 ctx.file,
5270 target_owner,
5271 &ctx.spec.member_name,
5272 ) {
5273 VisibleMemberResolution::Callable(candidates)
5274 if candidates.iter().all(|candidate| {
5275 ctx.target_group.contains(candidate)
5276 || ctx
5277 .target_group
5278 .iter()
5279 .any(|target| same_visible_symbol(candidate, target))
5280 }) =>
5281 {
5282 push_hit(imported, ctx);
5283 }
5284 VisibleMemberResolution::NonCallable => {}
5285 VisibleMemberResolution::Callable(_)
5286 | VisibleMemberResolution::AmbiguousKind
5287 | VisibleMemberResolution::Missing => {
5288 push_unproven_hit(imported, ctx);
5289 }
5290 }
5291}
5292
5293fn resolves_to_lexical_free_function(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5294 let name = node_text(node, ctx.source);
5295 let namespace = enclosing_namespace_components(node, ctx.source).join(".");
5296 let key = (namespace.clone(), name.to_string());
5297 if let Some(resolved) = ctx.lexical_free_function_cache.borrow().get(&key).copied() {
5298 return resolved;
5299 }
5300 let resolved = ctx
5301 .visibility
5302 .visible_identifier_candidates(ctx.file, name)
5303 .any(|unit| {
5304 unit.is_function()
5305 && type_owner_of(&ctx.analyzer, unit).is_none()
5306 && unit.package_name() == namespace
5307 });
5308 ctx.lexical_free_function_cache
5309 .borrow_mut()
5310 .insert(key, resolved);
5311 resolved
5312}
5313
5314fn maybe_record_qualified_method_value_hit(
5315 qualified: Node<'_>,
5316 member: Node<'_>,
5317 ctx: &mut ScanCtx<'_>,
5318) {
5319 if !name_matches_callable(node_text(member, ctx.source), &ctx.spec.member_name) {
5320 return;
5321 }
5322 *ctx.raw_match_count += 1;
5323 let resolution =
5324 qualified_callable_value_resolution(qualified, node_text(member, ctx.source), ctx);
5325 match resolution {
5326 LexicalCallableValueResolution::Type(resolved_owner) => {
5327 let Some(owner) = ctx.spec.owner.as_ref() else {
5328 push_unproven_hit(member, ctx);
5329 return;
5330 };
5331 if !receiver_owner_matches_target(&resolved_owner, owner, member.start_byte(), ctx) {
5332 if same_visible_symbol(&resolved_owner, owner) {
5333 push_unproven_hit(member, ctx);
5334 }
5335 return;
5336 }
5337 match ctx.visibility.visible_member_for_owner_name(
5338 ctx.file,
5339 owner,
5340 &ctx.spec.member_name,
5341 ) {
5342 VisibleMemberResolution::Callable(candidates)
5343 if candidates.iter().all(|candidate| {
5344 ctx.target_group.contains(candidate)
5345 || ctx
5346 .target_group
5347 .iter()
5348 .any(|target| same_visible_symbol(candidate, target))
5349 }) =>
5350 {
5351 push_hit(member, ctx);
5354 }
5355 VisibleMemberResolution::NonCallable => {}
5356 VisibleMemberResolution::Callable(_)
5357 | VisibleMemberResolution::AmbiguousKind
5358 | VisibleMemberResolution::Missing => {
5359 push_unproven_hit(member, ctx);
5360 }
5361 }
5362 }
5363 LexicalCallableValueResolution::FreeFunction(_) => {}
5364 LexicalCallableValueResolution::Ambiguous | LexicalCallableValueResolution::Missing => {
5365 push_unproven_hit(member, ctx);
5366 }
5367 }
5368}
5369
5370fn qualified_callable_value_resolution(
5371 qualified: Node<'_>,
5372 member_name: &str,
5373 ctx: &ScanCtx<'_>,
5374) -> LexicalCallableValueResolution {
5375 let Some((owner_components, global)) =
5376 qualified_callable_owner_components(qualified, ctx.source)
5377 else {
5378 return LexicalCallableValueResolution::Missing;
5379 };
5380 let lexical_scope = if global {
5381 Vec::new()
5382 } else {
5383 match enclosing_lexical_scope_components(
5384 qualified,
5385 &ctx.analyzer,
5386 ctx.visibility,
5387 ctx.file,
5388 ctx.source,
5389 ) {
5390 LexicalScopeResolution::Resolved(scope) => scope,
5391 LexicalScopeResolution::Ambiguous => {
5392 return LexicalCallableValueResolution::Ambiguous;
5393 }
5394 LexicalScopeResolution::Missing => return LexicalCallableValueResolution::Missing,
5395 }
5396 };
5397 if let Some(target_owner) = ctx.spec.owner.as_ref()
5398 && let LexicalTypeResolution::Resolved { unit, .. } =
5399 resolve_type_components_lexically_at_for_target_with_scope_cache(
5400 qualified,
5401 &owner_components,
5402 global,
5403 &ctx.analyzer,
5404 ctx.visibility,
5405 &ctx.ordinary_type_imports,
5406 ctx.file,
5407 ctx.source,
5408 target_owner,
5409 false,
5410 Some(&ctx.lexical_scope_cache),
5411 )
5412 {
5413 return LexicalCallableValueResolution::Type(unit);
5414 }
5415 ctx.visibility.resolve_callable_value_components_lexically(
5416 &ctx.analyzer,
5417 ctx.file,
5418 &owner_components,
5419 member_name,
5420 global,
5421 &lexical_scope,
5422 )
5423}
5424
5425fn method_call_may_target(call: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5426 let Some(owner) = ctx.spec.owner.as_ref() else {
5427 return true;
5428 };
5429 if ctx.spec.param_types.is_none() {
5430 return true;
5431 }
5432 let mut candidates = ctx
5433 .visibility
5434 .visible_members_for_owner_name(ctx.file, owner, &ctx.spec.member_name)
5435 .into_iter()
5436 .filter(|unit| unit.is_function())
5437 .cloned()
5438 .collect::<Vec<_>>();
5439 let Some(arity) = ctx
5440 .visibility
5441 .call_arity_evidence(ctx.file, call, ctx.source)
5442 .exact()
5443 else {
5444 return true;
5445 };
5446 candidates.retain(|unit| cpp_callable_arity(&ctx.analyzer, unit).accepts(arity));
5447 if candidates.is_empty()
5448 || !candidates
5449 .iter()
5450 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5451 {
5452 return true;
5453 }
5454 let arg_types = call_argument_types(call, ctx);
5455 let filtered = cpp_filter_candidates_by_args(
5456 candidates,
5457 &arg_types,
5458 &|name| ctx.visibility.resolve_type(ctx.file, name),
5459 &|left, right| same_visible_symbol(left, right),
5460 );
5461 filtered
5462 .iter()
5463 .any(|candidate| same_visible_symbol(candidate, &ctx.spec.target))
5464}
5465
5466fn maybe_record_method_definition_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5467 let Some(function) = function_definition_name_node(node) else {
5468 return;
5469 };
5470 if !callable_node_matches(function, &ctx.spec.member_name, ctx.source) {
5471 return;
5472 }
5473 *ctx.raw_match_count += 1;
5474 if !function_definition_signature_matches_target(node, ctx) {
5475 return;
5476 }
5477 if node_inside_target_declaration(function, ctx) {
5478 return;
5479 }
5480 if is_structurally_qualified(function) {
5481 match qualified_owner_resolution(function, ctx) {
5482 QualifiedOwnerResolution::Target => push_definition_hit(function, ctx),
5483 QualifiedOwnerResolution::NonTarget => {}
5484 QualifiedOwnerResolution::Unresolved => push_unproven_definition_hit(function, ctx),
5485 }
5486 return;
5487 }
5488 if definition_name_candidates(function, ctx)
5489 .iter()
5490 .any(|name| {
5491 name.contains("::")
5492 && ctx.visibility.contains_named_symbol(
5493 ctx.file,
5494 name,
5495 TargetKind::Method,
5496 &ctx.spec.target,
5497 )
5498 })
5499 {
5500 push_definition_hit(function, ctx);
5501 } else if definition_name_candidates(function, ctx)
5502 .iter()
5503 .any(|name| {
5504 ctx.visibility.resolve_known_non_target(
5505 ctx.file,
5506 name,
5507 TargetKind::Method,
5508 &ctx.spec.target,
5509 )
5510 })
5511 || known_non_target_owner_context(function, ctx)
5512 {
5513 } else {
5515 push_unproven_definition_hit(function, ctx);
5516 }
5517}
5518
5519fn node_inside_target_declaration(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5520 ctx.target_declaration_ranges
5521 .iter()
5522 .any(|range| node.start_byte() >= range.start_byte && node.end_byte() <= range.end_byte)
5523}
5524
5525fn explicit_operator_call(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
5526 let mut receiver = None;
5527 let mut cursor = node.walk();
5528 for child in node.named_children(&mut cursor) {
5529 if child.kind() == "argument_list" {
5530 continue;
5531 }
5532 if let Some(operator) = first_descendant_of_kind(child, "operator_name") {
5533 return receiver.map(|receiver| (receiver, operator));
5534 }
5535 if receiver.is_none() {
5536 receiver = Some(child);
5537 }
5538 }
5539 None
5540}
5541
5542fn function_definition_name_node(node: Node<'_>) -> Option<Node<'_>> {
5543 if node.kind() != "function_definition" {
5544 return None;
5545 }
5546 node.child_by_field_name("declarator")
5547 .and_then(declarator_name_node)
5548}
5549
5550fn function_definition_owner_lookup_node(node: Node<'_>) -> Option<Node<'_>> {
5551 function_definition_name_node(node)
5552}
5553
5554fn function_definition_signature_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5555 let definition = node_text(node, ctx.source);
5556 let Some(expected) = ctx.spec.callable_arity_at(node.start_byte()) else {
5557 return true;
5558 };
5559 if !expected.accepts(signature_arity(Some(definition))) {
5560 return false;
5561 }
5562 let Some(target_signature) = ctx.spec.target.signature() else {
5563 return true;
5564 };
5565 cpp_signature_param_types(definition) == cpp_signature_param_types(target_signature)
5566}
5567
5568fn callable_node_matches(node: Node<'_>, expected: &str, source: &str) -> bool {
5569 name_matches_callable(node_text(function_terminal_node(node), source), expected)
5570}
5571
5572fn definition_name_candidates(function: Node<'_>, ctx: &ScanCtx<'_>) -> Vec<String> {
5573 let raw = normalize_cpp_reference_text(node_text(function, ctx.source));
5574 if raw.is_empty() {
5575 return Vec::new();
5576 }
5577 let Some(namespace) = enclosing_namespace_context(function, ctx.source) else {
5578 return vec![raw];
5579 };
5580 if !raw.contains("::") {
5581 return vec![format!("{namespace}::{raw}")];
5582 }
5583 if raw
5589 .split("::")
5590 .next()
5591 .is_some_and(|head| head != namespace && !namespace.ends_with(&format!("::{head}")))
5592 {
5593 vec![format!("{namespace}::{raw}"), raw]
5594 } else {
5595 vec![raw]
5596 }
5597}
5598
5599fn first_descendant_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
5600 if node.kind() == kind {
5601 return Some(node);
5602 }
5603 let mut cursor = node.walk();
5604 for child in node.named_children(&mut cursor) {
5605 if let Some(found) = first_descendant_of_kind(child, kind) {
5606 return Some(found);
5607 }
5608 }
5609 None
5610}
5611
5612fn maybe_record_global_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5613 if matches!(node.kind(), "identifier" | "field_identifier")
5614 && designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node).is_some()
5615 {
5616 return;
5617 }
5618 if !matches!(
5619 node.kind(),
5620 "identifier" | "field_identifier" | "qualified_identifier"
5621 ) || !name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
5622 || is_declaration_name(node)
5623 || is_member_field_own_declarator(node, ctx)
5624 || is_selected_field_expression_member_descendant(node)
5625 || is_nested_in_qualified_identifier(node)
5626 {
5627 return;
5628 }
5629 *ctx.raw_match_count += 1;
5630 if global_field_resolves_to_target(node, ctx) {
5631 push_hit(node, ctx);
5632 } else if global_field_is_known_non_target(node, ctx) {
5633 } else {
5634 push_unproven_hit(node, ctx);
5635 }
5636}
5637
5638fn is_selected_field_expression_member_descendant(mut node: Node<'_>) -> bool {
5644 while let Some(parent) = node.parent() {
5645 if parent.kind() == "field_expression" {
5646 if parent
5647 .child_by_field_name("field")
5648 .is_some_and(|field| node_is_within(field, node))
5649 {
5650 return true;
5651 }
5652 let receiver = parent
5653 .child_by_field_name("argument")
5654 .or_else(|| parent.child_by_field_name("object"))
5655 .or_else(|| parent.named_child(0));
5656 if !receiver.is_some_and(|receiver| node_is_within(receiver, node)) {
5657 return true;
5660 }
5661 }
5662 node = parent;
5663 }
5664 false
5665}
5666
5667fn node_is_within(parent: Node<'_>, child: Node<'_>) -> bool {
5668 parent.start_byte() <= child.start_byte() && child.end_byte() <= parent.end_byte()
5669}
5670
5671fn global_field_resolves_to_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5672 let text = node_text(node, ctx.source);
5673 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
5674 return false;
5675 }
5676 if text.contains("::") {
5677 return ctx.visibility.contains_named_symbol(
5678 ctx.file,
5679 text,
5680 TargetKind::GlobalField,
5681 &ctx.spec.target,
5682 );
5683 }
5684 if let Some(namespace) = enclosing_namespace_context(node, ctx.source)
5685 && cpp_namespace_for(&ctx.spec.target).as_deref() == Some(namespace.as_str())
5686 {
5687 return ctx.visibility.contains_named_symbol(
5688 ctx.file,
5689 text,
5690 TargetKind::GlobalField,
5691 &ctx.spec.target,
5692 );
5693 }
5694 if let Some(indexed_scope) = indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, node)
5695 && cpp_namespace_for(&ctx.spec.target).is_some_and(|namespace| {
5696 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5697 brokk_bifrost_core::analyzer::Language::Cpp,
5698 &namespace,
5699 ) == indexed_scope
5700 })
5701 {
5702 return ctx.visibility.contains_named_symbol(
5703 ctx.file,
5704 text,
5705 TargetKind::GlobalField,
5706 &ctx.spec.target,
5707 );
5708 }
5709 bare_global_field_uniquely_resolves_to_target(text, ctx)
5710}
5711
5712fn bare_global_field_uniquely_resolves_to_target(text: &str, ctx: &ScanCtx<'_>) -> bool {
5713 let mut matched_target = false;
5714 for unit in ctx.visibility.visible_identifier_candidates(ctx.file, text) {
5715 if !has_persisted_global_field_identity(unit)
5716 || !name_matches_terminal(unit.identifier(), &ctx.spec.member_name)
5717 {
5718 continue;
5719 }
5720 if !name_matches_terminal(cpp_name_for(unit).as_str(), text) {
5721 continue;
5722 }
5723 if same_visible_global_field_symbol(
5724 &ctx.analyzer,
5725 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
5726 unit,
5727 &ctx.spec.target,
5728 ) {
5729 matched_target = true;
5730 } else {
5731 return false;
5732 }
5733 }
5734 matched_target
5735}
5736
5737fn has_persisted_global_field_identity(unit: &CodeUnit) -> bool {
5738 unit.is_field() && !unit.short_name().contains('.')
5743}
5744
5745fn global_field_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5746 let text = node_text(node, ctx.source);
5747 if !text.contains("::") && ctx.local_shadows.is_shadowed(text) {
5748 return true;
5749 }
5750 if text.contains("::") {
5751 return ctx.visibility.resolve_known_non_target(
5752 ctx.file,
5753 text,
5754 TargetKind::GlobalField,
5755 &ctx.spec.target,
5756 );
5757 }
5758 let Some(namespace) = enclosing_namespace_context(node, ctx.source) else {
5759 return false;
5760 };
5761 cpp_namespace_for(&ctx.spec.target).as_deref() != Some(namespace.as_str())
5762 && ctx
5763 .visibility
5764 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
5765 .any(|unit| {
5766 has_persisted_global_field_identity(unit)
5767 && unit.identifier() == ctx.spec.member_name
5768 && cpp_namespace_for(unit).as_deref() == Some(namespace.as_str())
5769 && !same_visible_global_field_symbol(
5770 &ctx.analyzer,
5771 &mut ctx.global_field_internal_linkage_cache.borrow_mut(),
5772 unit,
5773 &ctx.spec.target,
5774 )
5775 })
5776}
5777
5778fn maybe_record_member_field_hit(node: Node<'_>, ctx: &mut ScanCtx<'_>) {
5779 if node.kind() == "field_expression" {
5780 let Some(field) = node.child_by_field_name("field") else {
5781 return;
5782 };
5783 if node_text(field, ctx.source) != ctx.spec.member_name {
5784 return;
5785 }
5786 *ctx.raw_match_count += 1;
5787 let receiver = node
5788 .child_by_field_name("argument")
5789 .or_else(|| node.child_by_field_name("object"));
5790 match receiver.map(|receiver| explicit_receiver_target_resolution(receiver, ctx)) {
5791 Some(MethodReceiverTargetResolution::Target) => push_hit(field, ctx),
5792 Some(MethodReceiverTargetResolution::Missing) | None => push_unproven_hit(field, ctx),
5793 Some(
5794 MethodReceiverTargetResolution::NonTarget
5795 | MethodReceiverTargetResolution::Ambiguous,
5796 ) => {}
5797 }
5798 return;
5799 }
5800
5801 if matches!(node.kind(), "identifier" | "field_identifier")
5802 && name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
5803 && let Some(designator_owner) =
5804 designated_initializer_owner(ctx.visibility, ctx.file, ctx.source, node)
5805 {
5806 *ctx.raw_match_count += 1;
5807 match designator_owner {
5808 DesignatedInitializerOwner::Resolved(owner)
5809 if ctx
5810 .spec
5811 .owner
5812 .as_ref()
5813 .is_some_and(|target_owner| same_visible_symbol(&owner, target_owner)) =>
5814 {
5815 push_hit(node, ctx);
5816 }
5817 DesignatedInitializerOwner::Unresolved => push_unproven_hit(node, ctx),
5818 DesignatedInitializerOwner::Resolved(_) => {}
5819 }
5820 return;
5821 }
5822
5823 let qualified_member_name_matches =
5824 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
5825 && cpp_name_component_nodes(node)
5826 .and_then(|components| components.last().copied())
5827 .is_some_and(|terminal| node_text(terminal, ctx.source) == ctx.spec.member_name);
5828 if !matches!(
5829 node.kind(),
5830 "identifier" | "field_identifier" | "qualified_identifier" | "scoped_identifier"
5831 ) || (!name_matches_terminal(node_text(node, ctx.source), &ctx.spec.member_name)
5832 && !qualified_member_name_matches)
5833 || is_declaration_name(node)
5834 || is_member_field_own_declarator(node, ctx)
5835 || is_selected_field_expression_member_descendant(node)
5836 || is_nested_in_qualified_identifier(node)
5837 {
5838 return;
5839 }
5840 *ctx.raw_match_count += 1;
5841 if is_structurally_qualified(node) {
5842 match qualified_owner_resolution(node, ctx) {
5843 QualifiedOwnerResolution::Target => push_hit(node, ctx),
5844 QualifiedOwnerResolution::NonTarget => {}
5845 QualifiedOwnerResolution::Unresolved => push_unproven_hit(node, ctx),
5846 }
5847 return;
5848 }
5849 let text = node_text(node, ctx.source);
5850 if ctx.local_shadows.is_shadowed(text) {
5851 return;
5852 }
5853 let unscoped_enum_match = ctx.spec.enum_owner_kind == EnumOwnerKind::Unscoped
5854 && ctx.visibility.is_visible(ctx.file, &ctx.spec.target);
5855 let owner_context = structured_owner_context_resolution(node, ctx);
5856 if matches!(
5857 owner_context,
5858 StructuredOwnerContextResolution::SelfTarget
5859 | StructuredOwnerContextResolution::InheritedTarget
5860 ) || unscoped_enum_match
5861 {
5862 push_hit(node, ctx);
5863 } else if let Some(target_owner) = (ctx.spec.enum_owner_kind == EnumOwnerKind::Scoped)
5864 .then_some(ctx.spec.owner.as_ref())
5865 .flatten()
5866 {
5867 let resolution =
5868 match resolve_active_using_enum_member(node, ctx) {
5869 ActiveUsingEnumMemberResolution::Block(resolution) => resolution,
5870 ActiveUsingEnumMemberResolution::Class(resolution) => {
5871 if direct_class_member_shadows(node, ctx) {
5872 return;
5873 }
5874 resolution
5875 }
5876 ActiveUsingEnumMemberResolution::Namespace(resolution) => {
5877 if let Some(owner) = structured_enclosing_owner(node, ctx) {
5878 if direct_class_member_shadows(node, ctx) {
5879 return;
5880 }
5881 let complete_same_file_leaf =
5882 owner.source() == ctx.file
5883 && ctx.analyzer.type_hierarchy_provider().is_some_and(
5884 |hierarchy| hierarchy.get_direct_ancestors(&owner).is_empty(),
5885 );
5886 if !complete_same_file_leaf {
5887 push_unproven_hit(node, ctx);
5888 return;
5889 }
5890 }
5891 match owner_context {
5892 StructuredOwnerContextResolution::SelfTarget
5893 | StructuredOwnerContextResolution::InheritedTarget
5894 | StructuredOwnerContextResolution::NonTarget => return,
5895 StructuredOwnerContextResolution::Ambiguous => {
5896 push_unproven_hit(node, ctx);
5897 return;
5898 }
5899 StructuredOwnerContextResolution::Missing => {}
5900 }
5901 if namespace_value_shadows(node, ctx) {
5902 return;
5903 }
5904 resolution
5905 }
5906 ActiveUsingEnumMemberResolution::Missing => {
5907 if direct_class_member_shadows(node, ctx)
5908 || (structured_enclosing_owner(node, ctx).is_none()
5909 && namespace_value_shadows(node, ctx))
5910 {
5911 return;
5912 }
5913 UsingEnumMemberResolution::Missing
5914 }
5915 };
5916 match resolution {
5917 UsingEnumMemberResolution::Resolved { owner, member }
5918 if same_visible_symbol(&owner, target_owner)
5919 && same_visible_symbol(&member, &ctx.spec.target) =>
5920 {
5921 push_hit(node, ctx);
5922 }
5923 UsingEnumMemberResolution::Resolved { .. } => {}
5924 UsingEnumMemberResolution::Ambiguous | UsingEnumMemberResolution::Missing => {
5925 push_unproven_hit(node, ctx)
5926 }
5927 }
5928 } else if !matches!(owner_context, StructuredOwnerContextResolution::NonTarget) {
5929 push_unproven_hit(node, ctx);
5930 }
5931}
5932
5933enum ActiveUsingEnumMemberResolution {
5934 Block(UsingEnumMemberResolution),
5935 Class(UsingEnumMemberResolution),
5936 Namespace(UsingEnumMemberResolution),
5937 Missing,
5938}
5939
5940fn direct_class_member_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5941 structured_enclosing_owner(node, ctx).is_some_and(|owner| {
5942 ctx.visibility
5943 .visible_members_for_owner_name(ctx.file, &owner, &ctx.spec.member_name)
5944 .into_iter()
5945 .next()
5946 .is_some()
5947 })
5948}
5949
5950fn resolve_active_using_enum_member(
5951 node: Node<'_>,
5952 ctx: &ScanCtx<'_>,
5953) -> ActiveUsingEnumMemberResolution {
5954 let block =
5955 ctx.using_enum_owners
5956 .resolve_member(ctx.visibility, ctx.file, &ctx.spec.member_name);
5957 if !matches!(block, UsingEnumMemberResolution::Missing) {
5958 return ActiveUsingEnumMemberResolution::Block(block);
5959 }
5960 let class = structured_enclosing_owner(node, ctx);
5961 let namespace = enclosing_namespace_components(node, ctx.source);
5962 match ctx.semantic_using_enum_owners.resolve_member(
5963 ctx.visibility,
5964 ctx.file,
5965 class.as_ref(),
5966 &namespace,
5967 node.start_byte(),
5968 &ctx.spec.member_name,
5969 ) {
5970 SemanticUsingEnumMemberResolution::Class(resolution) => {
5971 ActiveUsingEnumMemberResolution::Class(resolution)
5972 }
5973 SemanticUsingEnumMemberResolution::Namespace(resolution) => {
5974 ActiveUsingEnumMemberResolution::Namespace(resolution)
5975 }
5976 SemanticUsingEnumMemberResolution::Missing => ActiveUsingEnumMemberResolution::Missing,
5977 }
5978}
5979
5980fn namespace_value_shadows(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
5981 let namespace = enclosing_namespace_components(node, ctx.source).join("::");
5982 !matches!(
5983 resolve_namespace_value(
5984 &ctx.analyzer,
5985 ctx.visibility,
5986 ctx.file,
5987 &namespace,
5988 &ctx.spec.member_name,
5989 node.start_byte(),
5990 ),
5991 NamespaceValueResolution::Missing
5992 )
5993}
5994
5995fn is_nested_in_qualified_identifier(node: Node<'_>) -> bool {
5996 if node.kind() == "qualified_identifier" {
5997 return false;
5998 }
5999 let mut current = node.parent();
6000 while let Some(parent) = current {
6001 if parent.kind() == "ERROR" {
6009 return false;
6010 }
6011 if parent.kind() == "qualified_identifier" {
6012 return true;
6013 }
6014 current = parent.parent();
6015 }
6016 false
6017}
6018
6019fn receiver_type_units(node: Node<'_>, source: &str, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6020 receiver_type_units_with_budget(node, source, ctx, MAX_RECEIVER_CALL_RESOLUTION_DEPTH)
6021}
6022
6023fn receiver_type_units_with_budget(
6024 node: Node<'_>,
6025 source: &str,
6026 ctx: &ScanCtx<'_>,
6027 remaining_call_depth: usize,
6028) -> Vec<CodeUnit> {
6029 let mut current = node;
6030 let mut member_chain = Vec::new();
6031 let mut base_units = loop {
6032 match current.kind() {
6033 "field_expression" => {
6034 let Some(member) = current.child_by_field_name("field") else {
6035 return Vec::new();
6036 };
6037 let Some(receiver) = current
6038 .child_by_field_name("argument")
6039 .or_else(|| current.child_by_field_name("object"))
6040 .or_else(|| current.named_child(0))
6041 else {
6042 return Vec::new();
6043 };
6044 member_chain.push(node_text(member, source));
6045 current = receiver;
6046 }
6047 "pointer_expression" | "parenthesized_expression" | "subscript_expression" => {
6048 let Some(inner) = current
6049 .child_by_field_name("argument")
6050 .or_else(|| current.named_child(0))
6051 else {
6052 return Vec::new();
6053 };
6054 current = inner;
6055 }
6056 "identifier" | "field_identifier" => {
6063 let name = node_text(current, source);
6064 let local = ctx.bindings.resolve_symbol(name);
6065 if let Some(bindings) = local.as_precise() {
6066 break receiver_units_from_bindings(current, bindings, ctx);
6067 }
6068 if ctx.bindings.is_shadowed(name) {
6069 return Vec::new();
6070 }
6071 let owner = structured_enclosing_owner(current, ctx)
6072 .filter(CodeUnit::is_class)
6073 .or_else(|| {
6074 enclosing_context(current, ctx)
6075 .owner
6076 .filter(CodeUnit::is_class)
6077 });
6078 if let Some(owner) = owner {
6079 let implicit_fields = ctx
6080 .visibility
6081 .visible_members_for_owner_name(ctx.file, &owner, name)
6082 .into_iter()
6083 .filter(|unit| unit.is_field())
6084 .collect::<Vec<_>>();
6085 if !implicit_fields.is_empty() {
6086 break receiver_units_from_declared_fields(implicit_fields, current, ctx);
6087 }
6088 }
6089 let global_fields = ctx
6090 .visibility
6091 .visible_identifier_candidates(ctx.file, name)
6092 .filter(|unit| {
6093 has_persisted_global_field_identity(unit) && unit.identifier() == name
6094 })
6095 .collect::<Vec<_>>();
6096 if global_fields.is_empty() {
6097 break ctx
6098 .visibility
6099 .resolve_type(ctx.file, name)
6100 .into_iter()
6101 .collect();
6102 }
6103 if let Some(first) = global_fields.first()
6104 && global_fields
6105 .iter()
6106 .skip(1)
6107 .any(|field| !same_visible_symbol(first, field))
6108 {
6109 return Vec::new();
6110 }
6111 break receiver_units_from_declared_fields(global_fields, current, ctx);
6112 }
6113 "call_expression" | "new_expression" => {
6114 break infer_type_from_value_with_budget(current, ctx, remaining_call_depth)
6115 .and_then(|binding| binding.unit)
6116 .into_iter()
6117 .collect();
6118 }
6119 "this" => {
6120 break enclosing_context(current, ctx).owner.into_iter().collect();
6121 }
6122 "qualified_identifier" | "scoped_identifier" => {
6123 let reference = node_text(current, source);
6124 let fields = ctx
6125 .visibility
6126 .named_candidates(ctx.file, reference, TargetKind::GlobalField)
6127 .into_iter()
6128 .filter(has_persisted_global_field_identity)
6129 .collect::<Vec<_>>();
6130 if fields.is_empty() {
6131 break ctx
6132 .visibility
6133 .resolve_type(ctx.file, reference)
6134 .into_iter()
6135 .collect();
6136 }
6137 break receiver_units_from_declared_fields(fields.iter().collect(), current, ctx);
6138 }
6139 _ => {
6140 break ctx
6141 .visibility
6142 .resolve_type(ctx.file, node_text(current, source))
6143 .into_iter()
6144 .collect();
6145 }
6146 }
6147 };
6148
6149 base_units = canonical_receiver_units(base_units, ctx);
6150 if base_units.is_empty() {
6151 return Vec::new();
6152 }
6153
6154 while let Some(member_name) = member_chain.pop() {
6155 let mut next_units = Vec::new();
6156 for owner in &base_units {
6157 let fields =
6158 ctx.visibility
6159 .visible_members_for_owner_name(ctx.file, owner, member_name);
6160 for field in fields.into_iter().filter(|unit| unit.is_field()) {
6161 let Some(unit) =
6162 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
6163 .and_then(|binding| binding.unit)
6164 .or_else(|| recovered_receiver_field_type(current, field, ctx))
6165 else {
6166 continue;
6167 };
6168 if !next_units
6169 .iter()
6170 .any(|existing| same_visible_symbol(existing, &unit))
6171 {
6172 next_units.push(unit);
6173 }
6174 }
6175 }
6176 if next_units.is_empty() {
6177 return Vec::new();
6178 }
6179 base_units = unanimous_receiver_units(next_units);
6180 if base_units.is_empty() {
6181 return Vec::new();
6182 }
6183 }
6184 base_units
6185}
6186
6187fn receiver_units_from_bindings(
6188 node: Node<'_>,
6189 bindings: &HashSet<CppScanBinding>,
6190 ctx: &ScanCtx<'_>,
6191) -> Vec<CodeUnit> {
6192 let mut units = Vec::new();
6193 for binding in bindings {
6194 let raw_unit = if let Some(unit) = &binding.unit {
6195 unit.clone()
6196 } else {
6197 let Some(type_name) = binding.type_name.as_deref() else {
6198 return Vec::new();
6199 };
6200 let Some(unit) = receiver_type_name_unit(node, type_name, ctx) else {
6201 return Vec::new();
6202 };
6203 unit
6204 };
6205 if let Some(unit) = canonical_receiver_unit(&raw_unit, ctx) {
6206 units.push(unit);
6207 continue;
6208 }
6209 if let Some(unit) = recovered_receiver_alias_target(node, &raw_unit, ctx) {
6210 units.push(unit);
6211 continue;
6212 }
6213 return Vec::new();
6214 }
6215 unanimous_receiver_units(units)
6216}
6217
6218fn recovered_receiver_alias_target(
6223 reference: Node<'_>,
6224 alias: &CodeUnit,
6225 ctx: &ScanCtx<'_>,
6226) -> Option<CodeUnit> {
6227 if !ctx
6228 .analyzer
6229 .type_alias_provider()
6230 .is_some_and(|provider| provider.is_type_alias(alias))
6231 {
6232 return None;
6233 }
6234 let target = ctx.spec.owner.as_ref()?.clone();
6235 if !target.is_class() || alias.source() != ctx.file {
6236 return None;
6237 }
6238 let range = ctx
6239 .analyzer
6240 .ranges(alias)
6241 .into_iter()
6242 .find(|range| range.start_byte < range.end_byte)?;
6243 let mut node =
6244 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
6245 while node.kind() != "alias_declaration" {
6246 node = node.parent()?;
6247 }
6248 let type_descriptor = node.child_by_field_name("type")?;
6249 let type_node = first_type_child(type_descriptor).unwrap_or(type_descriptor);
6250 let resolution = resolve_type_node_lexically_for_target(
6251 type_node,
6252 &ctx.analyzer,
6253 ctx.visibility,
6254 &ctx.ordinary_type_imports,
6255 ctx.file,
6256 ctx.source,
6257 &target,
6258 Some(&ctx.lexical_scope_cache),
6259 ctx.recovered_sentinel_scope(type_node).as_deref(),
6260 );
6261 if let LexicalTypeResolution::Resolved {
6262 unit, candidates, ..
6263 } = resolution
6264 && (same_visible_symbol(&unit, &target)
6265 || candidates
6266 .iter()
6267 .any(|candidate| same_visible_symbol(candidate, &target)))
6268 {
6269 return Some(target);
6270 }
6271 let (components, global) = type_reference_components(type_node, ctx.source)?;
6272 let scope = ctx
6273 .recovered_sentinel_scope(type_node)
6274 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
6275 let path_matches = indexed_scope_matches_target_name(&scope, &components, global, &target);
6276 let visible = ctx.visibility.external_type_candidate_visible_in_context(
6277 &ctx.analyzer,
6278 ctx.file,
6279 &target,
6280 type_node,
6281 );
6282 (path_matches && visible).then_some(target)
6283}
6284
6285fn receiver_type_name_unit(node: Node<'_>, type_name: &str, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
6286 let normalized = normalize_cpp_type_name(type_name);
6287 if normalized.is_empty() {
6288 return None;
6289 }
6290
6291 if let Some(alias_type) = local_receiver_alias_type_node(node, &normalized, ctx) {
6296 let alias_type = receiver_type_node_base(alias_type);
6297 match ctx
6298 .visibility
6299 .resolve_type_node_result(ctx.file, alias_type, ctx.source)
6300 {
6301 Ok(Some(unit)) => return Some(unit),
6302 Err(_) => return None,
6303 Ok(None) => {}
6304 }
6305 if let Some(unit) = resolve_receiver_type_node_lexically(alias_type, ctx) {
6306 return Some(unit);
6307 }
6308 }
6309
6310 match resolve_receiver_type_name_lexically(node, &normalized, ctx) {
6311 LexicalTypeResolution::Resolved { unit, .. } => return Some(unit),
6312 LexicalTypeResolution::Ambiguous => return None,
6313 LexicalTypeResolution::Missing => {}
6314 }
6315 let candidates = ctx
6316 .visibility
6317 .type_name_candidates(ctx.file, &normalized)
6318 .into_iter()
6319 .filter_map(|candidate| canonical_receiver_unit(candidate, ctx))
6320 .collect();
6321 unanimous_receiver_units(candidates).into_iter().next()
6322}
6323
6324fn resolve_receiver_type_node_lexically(
6325 type_node: Node<'_>,
6326 ctx: &ScanCtx<'_>,
6327) -> Option<CodeUnit> {
6328 let type_node = receiver_type_node_base(type_node);
6329 let components = cpp_type_name_components(type_node, ctx.source)?;
6330 let lexical_scope = match enclosing_lexical_scope_components(
6331 type_node,
6332 &ctx.analyzer,
6333 ctx.visibility,
6334 ctx.file,
6335 ctx.source,
6336 ) {
6337 LexicalScopeResolution::Resolved(scope) => scope,
6338 LexicalScopeResolution::Ambiguous | LexicalScopeResolution::Missing => return None,
6339 };
6340 match ctx.visibility.resolve_type_components_lexically(
6341 &ctx.analyzer,
6342 ctx.file,
6343 &components,
6344 is_globally_qualified_cpp_name(type_node),
6345 &lexical_scope,
6346 ) {
6347 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6348 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6349 }
6350}
6351
6352fn receiver_type_node_base(mut node: Node<'_>) -> Node<'_> {
6353 while node.kind() == "type_descriptor" {
6354 let Some(inner) = node.child_by_field_name("type") else {
6355 break;
6356 };
6357 node = inner;
6358 }
6359 node
6360}
6361
6362fn resolve_receiver_type_name_lexically(
6363 node: Node<'_>,
6364 normalized: &str,
6365 ctx: &ScanCtx<'_>,
6366) -> LexicalTypeResolution {
6367 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6368 brokk_bifrost_core::analyzer::Language::Cpp,
6369 normalized,
6370 );
6371 if components.is_empty() {
6372 return LexicalTypeResolution::Missing;
6373 }
6374 let lexical_scope = match enclosing_lexical_scope_components(
6375 node,
6376 &ctx.analyzer,
6377 ctx.visibility,
6378 ctx.file,
6379 ctx.source,
6380 ) {
6381 LexicalScopeResolution::Resolved(scope) => scope,
6382 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
6383 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
6384 };
6385 ctx.visibility.resolve_type_components_lexically(
6386 &ctx.analyzer,
6387 ctx.file,
6388 &components,
6389 normalized.starts_with("::"),
6390 &lexical_scope,
6391 )
6392}
6393
6394fn local_receiver_alias_type_node<'tree>(
6395 node: Node<'tree>,
6396 name: &str,
6397 ctx: &ScanCtx<'_>,
6398) -> Option<Node<'tree>> {
6399 let callable = nearest_callable_scope(node)?;
6400 let mut root_callable = callable;
6401 let mut ancestor = callable.parent();
6402 while let Some(current) = ancestor {
6403 if matches!(current.kind(), "function_definition" | "lambda_expression") {
6404 root_callable = current;
6405 }
6406 ancestor = current.parent();
6407 }
6408
6409 let mut stack = vec![root_callable];
6410 let mut best = None;
6411 while let Some(current) = stack.pop() {
6412 if current.start_byte() >= node.start_byte() {
6413 continue;
6414 }
6415 if local_type_alias_name_node(current)
6416 .is_some_and(|alias_name| node_text(alias_name, ctx.source) == name)
6417 && local_alias_scope_contains_node(current, node)
6418 {
6419 let replace = best
6420 .is_none_or(|existing: Node<'tree>| existing.start_byte() < current.start_byte());
6421 if replace {
6422 best = current.child_by_field_name("type");
6423 }
6424 }
6425 let mut cursor = current.walk();
6426 stack.extend(current.named_children(&mut cursor));
6427 }
6428 best
6429}
6430
6431fn canonical_receiver_units(units: Vec<CodeUnit>, ctx: &ScanCtx<'_>) -> Vec<CodeUnit> {
6432 let mut canonical = Vec::with_capacity(units.len());
6433 for unit in units {
6434 let Some(unit) = canonical_receiver_unit(&unit, ctx) else {
6435 return Vec::new();
6436 };
6437 canonical.push(unit);
6438 }
6439 unanimous_receiver_units(canonical)
6440}
6441
6442fn canonical_receiver_unit(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
6443 if let Some(cached) = ctx.receiver_canonical_type_cache.borrow().get(unit) {
6444 return cached.clone();
6445 }
6446 let canonical = ctx
6447 .visibility
6448 .canonical_visible_full_type_unit(&ctx.analyzer, ctx.file, unit);
6449 ctx.receiver_canonical_type_cache
6450 .borrow_mut()
6451 .insert(unit.clone(), canonical.clone());
6452 canonical
6453}
6454
6455fn receiver_units_from_declared_fields(
6456 fields: Vec<&CodeUnit>,
6457 reference: Node<'_>,
6458 ctx: &ScanCtx<'_>,
6459) -> Vec<CodeUnit> {
6460 let Some(first) = fields.first() else {
6461 return Vec::new();
6462 };
6463 if fields
6464 .iter()
6465 .skip(1)
6466 .any(|field| !same_visible_symbol(first, field))
6467 {
6468 return Vec::new();
6469 }
6470 unanimous_receiver_units(
6471 fields
6472 .into_iter()
6473 .filter_map(|field| {
6474 field_declared_binding(&ctx.analyzer, ctx.visibility, ctx.file, field)
6475 .and_then(|binding| binding.unit)
6476 .or_else(|| recovered_receiver_field_type(reference, field, ctx))
6477 })
6478 .collect(),
6479 )
6480}
6481
6482fn recovered_receiver_field_type(
6487 reference: Node<'_>,
6488 field: &CodeUnit,
6489 ctx: &ScanCtx<'_>,
6490) -> Option<CodeUnit> {
6491 let target = ctx.spec.owner.as_ref()?.clone();
6492 if !target.is_class() || field.source() != ctx.file {
6493 return None;
6494 }
6495 let range = ctx
6496 .analyzer
6497 .ranges(field)
6498 .into_iter()
6499 .find(|range| range.start_byte < range.end_byte)?;
6500 let mut declaration =
6501 root_node(reference).descendant_for_byte_range(range.start_byte, range.end_byte)?;
6502 while !matches!(declaration.kind(), "declaration" | "field_declaration") {
6503 declaration = declaration.parent()?;
6504 }
6505 let type_node = first_type_child(declaration)?;
6506 let resolution = resolve_type_node_lexically_for_target(
6507 type_node,
6508 &ctx.analyzer,
6509 ctx.visibility,
6510 &ctx.ordinary_type_imports,
6511 ctx.file,
6512 ctx.source,
6513 &target,
6514 Some(&ctx.lexical_scope_cache),
6515 ctx.recovered_sentinel_scope(type_node).as_deref(),
6516 );
6517 if let LexicalTypeResolution::Resolved {
6518 unit, candidates, ..
6519 } = resolution
6520 && (same_visible_symbol(&unit, &target)
6521 || candidates
6522 .iter()
6523 .any(|candidate| same_visible_symbol(candidate, &target)))
6524 {
6525 return Some(target);
6526 }
6527 let type_node = receiver_type_node_base(type_node);
6528 let (components, global) = type_reference_components(type_node, ctx.source)?;
6529 let scope = ctx
6530 .recovered_sentinel_scope(type_node)
6531 .or_else(|| indexed_enclosing_lexical_scope(&ctx.analyzer, ctx.file, type_node))?;
6532 (indexed_scope_matches_target_name(&scope, &components, global, &target)
6533 && ctx.visibility.external_type_candidate_visible_in_context(
6534 &ctx.analyzer,
6535 ctx.file,
6536 &target,
6537 type_node,
6538 ))
6539 .then_some(target)
6540}
6541
6542fn unanimous_receiver_units(units: Vec<CodeUnit>) -> Vec<CodeUnit> {
6543 let mut unique = Vec::new();
6544 for unit in units {
6545 if !unique
6546 .iter()
6547 .any(|existing| same_visible_symbol(existing, &unit))
6548 {
6549 unique.push(unit);
6550 if unique.len() > 1 {
6551 return Vec::new();
6552 }
6553 }
6554 }
6555 unique
6556}
6557
6558fn receiver_matches_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6559 let Some(owner) = ctx.spec.owner.as_ref() else {
6560 return false;
6561 };
6562 match node.kind() {
6563 "field_expression" => node
6564 .child_by_field_name("argument")
6565 .or_else(|| node.child_by_field_name("object"))
6566 .is_some_and(|receiver| {
6567 receiver_is_self_like(receiver) && same_owner_context(receiver, ctx)
6568 || receiver_type_units(receiver, ctx.source, ctx)
6569 .iter()
6570 .any(|target| {
6571 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
6572 })
6573 }),
6574 "call_expression" => node
6575 .child_by_field_name("function")
6576 .is_some_and(|function| receiver_matches_target(function, ctx)),
6577 "pointer_expression" | "parenthesized_expression" => node
6578 .child_by_field_name("argument")
6579 .or_else(|| node.named_child(0))
6580 .is_some_and(|child| receiver_matches_target(child, ctx)),
6581 "identifier" => ctx
6582 .bindings
6583 .resolve_symbol(node_text(node, ctx.source))
6584 .as_precise()
6585 .is_some_and(|targets| {
6586 targets
6587 .iter()
6588 .filter_map(|target| target.unit.as_ref())
6589 .any(|target| {
6590 receiver_owner_matches_target(target, owner, node.start_byte(), ctx)
6591 })
6592 }),
6593 "this" => same_owner_context(node, ctx),
6594 _ => qualified_owner_matches(node, ctx),
6595 }
6596}
6597
6598fn declaring_owner_for_explicit_receiver(
6599 receiver: Node<'_>,
6600 ctx: &ScanCtx<'_>,
6601) -> EnclosingMemberOwnerResolution {
6602 if receiver_is_self_like(receiver) {
6603 return EnclosingMemberOwnerResolution::Missing;
6604 }
6605 let receiver_units = receiver_type_units(receiver, ctx.source, ctx);
6606 let mut declaring_owner = None;
6607 for receiver_owner in receiver_units {
6608 if ctx.spec.owner.as_ref().is_some_and(|target_owner| {
6609 receiver_owner_matches_target(&receiver_owner, target_owner, receiver.start_byte(), ctx)
6610 }) {
6611 if declaring_owner
6612 .as_ref()
6613 .is_some_and(|existing| !same_visible_symbol(existing, &receiver_owner))
6614 {
6615 return EnclosingMemberOwnerResolution::Ambiguous;
6616 }
6617 declaring_owner = Some(receiver_owner);
6618 continue;
6619 }
6620 match cached_declaring_member_owner(&receiver_owner, ctx) {
6621 EnclosingMemberOwnerResolution::Owner(owner) => {
6622 if declaring_owner
6623 .as_ref()
6624 .is_some_and(|existing| !same_visible_symbol(existing, &owner))
6625 {
6626 return EnclosingMemberOwnerResolution::Ambiguous;
6627 }
6628 declaring_owner = Some(owner);
6629 }
6630 EnclosingMemberOwnerResolution::Ambiguous => {
6631 return EnclosingMemberOwnerResolution::Ambiguous;
6632 }
6633 EnclosingMemberOwnerResolution::Missing => {}
6634 }
6635 }
6636 declaring_owner
6637 .map(EnclosingMemberOwnerResolution::Owner)
6638 .unwrap_or(EnclosingMemberOwnerResolution::Missing)
6639}
6640
6641fn declaring_owner_from_call_function(
6642 function: Node<'_>,
6643 ctx: &ScanCtx<'_>,
6644) -> Option<EnclosingMemberOwnerResolution> {
6645 match function.kind() {
6646 "field_expression" => function
6647 .child_by_field_name("argument")
6648 .or_else(|| function.child_by_field_name("object"))
6649 .map(|receiver| declaring_owner_for_explicit_receiver(receiver, ctx))
6650 .or(Some(EnclosingMemberOwnerResolution::Missing)),
6651 "call_expression" => function
6652 .child_by_field_name("function")
6653 .and_then(|inner| declaring_owner_from_call_function(inner, ctx)),
6654 _ => None,
6655 }
6656}
6657
6658enum MethodReceiverTargetResolution {
6659 Target,
6660 NonTarget,
6661 Ambiguous,
6662 Missing,
6663}
6664
6665fn method_receiver_target_resolution(
6666 node: Node<'_>,
6667 declaring_owner: EnclosingMemberOwnerResolution,
6668 ctx: &ScanCtx<'_>,
6669) -> MethodReceiverTargetResolution {
6670 let Some(target_owner) = ctx.spec.owner.as_ref() else {
6671 return MethodReceiverTargetResolution::Missing;
6672 };
6673 match declaring_owner {
6674 EnclosingMemberOwnerResolution::Owner(owner)
6675 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
6676 {
6677 MethodReceiverTargetResolution::Target
6678 }
6679 EnclosingMemberOwnerResolution::Owner(owner)
6680 if receiver_owner_is_known_non_target(&owner, target_owner, node.start_byte(), ctx) =>
6681 {
6682 MethodReceiverTargetResolution::NonTarget
6683 }
6684 EnclosingMemberOwnerResolution::Owner(_) => MethodReceiverTargetResolution::Missing,
6685 EnclosingMemberOwnerResolution::Ambiguous => MethodReceiverTargetResolution::Ambiguous,
6686 EnclosingMemberOwnerResolution::Missing if receiver_matches_target(node, ctx) => {
6687 MethodReceiverTargetResolution::Target
6688 }
6689 EnclosingMemberOwnerResolution::Missing if receiver_has_known_non_target(node, ctx) => {
6690 MethodReceiverTargetResolution::NonTarget
6691 }
6692 EnclosingMemberOwnerResolution::Missing => MethodReceiverTargetResolution::Missing,
6693 }
6694}
6695
6696fn explicit_receiver_target_resolution(
6697 receiver: Node<'_>,
6698 ctx: &ScanCtx<'_>,
6699) -> MethodReceiverTargetResolution {
6700 method_receiver_target_resolution(
6701 receiver,
6702 declaring_owner_for_explicit_receiver(receiver, ctx),
6703 ctx,
6704 )
6705}
6706
6707fn call_function_target_resolution(
6708 function: Node<'_>,
6709 ctx: &ScanCtx<'_>,
6710) -> MethodReceiverTargetResolution {
6711 let Some(declaring_owner) = declaring_owner_from_call_function(function, ctx) else {
6712 return MethodReceiverTargetResolution::Missing;
6716 };
6717 method_receiver_target_resolution(function, declaring_owner, ctx)
6718}
6719
6720fn receiver_owner_matches_target(
6721 receiver_owner: &CodeUnit,
6722 target_owner: &CodeUnit,
6723 reference_byte: usize,
6724 ctx: &ScanCtx<'_>,
6725) -> bool {
6726 same_symbol(receiver_owner, target_owner)
6727 || same_logical_symbol(receiver_owner, target_owner)
6728 && (ctx.visibility.is_physically_visible(ctx.file, target_owner)
6729 || (ctx.spec.owner_is_forward_declaration
6730 && ctx
6731 .visibility
6732 .is_physically_visible(ctx.file, receiver_owner))
6733 || visible_target_peer_matches_owner(receiver_owner, reference_byte, ctx)
6734 || target_group_contains_owner_peer(receiver_owner, ctx))
6735}
6736
6737fn receiver_owner_is_known_non_target(
6738 receiver_owner: &CodeUnit,
6739 target_owner: &CodeUnit,
6740 reference_byte: usize,
6741 ctx: &ScanCtx<'_>,
6742) -> bool {
6743 if receiver_owner_matches_target(receiver_owner, target_owner, reference_byte, ctx) {
6744 return false;
6745 }
6746 if !same_logical_symbol(receiver_owner, target_owner) {
6747 return true;
6748 }
6749 !ctx.target_group.iter().any(|target| {
6750 same_logical_symbol(target, &ctx.spec.target) && target.source() == target_owner.source()
6751 })
6752}
6753
6754fn target_group_contains_owner_peer(owner: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
6755 ctx.visibility
6756 .external_type_declaration_visible_at(ctx.file, owner, usize::MAX)
6757 && ctx.target_group.iter().any(|target| {
6758 type_owner_of(&ctx.analyzer, target)
6759 .as_ref()
6760 .is_some_and(|target_owner| {
6761 same_symbol(target_owner, owner)
6762 || (same_logical_symbol(target_owner, owner)
6763 && target_owner.source() == owner.source())
6764 })
6765 })
6766}
6767
6768fn visible_target_peer_matches_owner(
6769 owner: &CodeUnit,
6770 reference_byte: usize,
6771 ctx: &ScanCtx<'_>,
6772) -> bool {
6773 ctx.visibility
6774 .external_type_declaration_visible_at(ctx.file, owner, reference_byte)
6775 && ctx
6776 .visibility
6777 .visible_identifier_candidates(ctx.file, &ctx.spec.member_name)
6778 .any(|candidate| {
6779 cpp_callable_definitions_share_identity_evidence(
6780 &ctx.analyzer,
6781 candidate,
6782 &ctx.spec.target,
6783 ) && ctx.visibility.declaration_visible_at(
6784 &ctx.analyzer,
6785 ctx.file,
6786 candidate,
6787 reference_byte,
6788 ) && type_owner_of(&ctx.analyzer, candidate)
6789 .as_ref()
6790 .is_some_and(|candidate_owner| {
6791 same_symbol(candidate_owner, owner)
6792 || (same_logical_symbol(candidate_owner, owner)
6793 && candidate_owner.source() == owner.source())
6794 })
6795 })
6796}
6797
6798fn receiver_is_self_like(node: Node<'_>) -> bool {
6799 match node.kind() {
6800 "this" => true,
6801 "pointer_expression" | "parenthesized_expression" => node
6802 .child_by_field_name("argument")
6803 .or_else(|| node.named_child(0))
6804 .is_some_and(receiver_is_self_like),
6805 _ => false,
6806 }
6807}
6808
6809fn call_function_has_direct_self_receiver(function: Node<'_>) -> bool {
6810 match function.kind() {
6811 "field_expression" => function
6812 .child_by_field_name("argument")
6813 .or_else(|| function.child_by_field_name("object"))
6814 .is_some_and(receiver_is_self_like),
6815 _ => receiver_is_self_like(function),
6816 }
6817}
6818
6819fn receiver_has_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6820 let Some(owner) = ctx.spec.owner.as_ref() else {
6821 return false;
6822 };
6823 match node.kind() {
6824 "field_expression" => node
6825 .child_by_field_name("argument")
6826 .or_else(|| node.child_by_field_name("object"))
6827 .is_some_and(|receiver| {
6828 let units = receiver_type_units(receiver, ctx.source, ctx);
6829 !units.is_empty()
6830 && units.iter().all(|target| {
6831 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
6832 })
6833 }),
6834 "call_expression" => node
6835 .child_by_field_name("function")
6836 .is_some_and(|function| receiver_has_known_non_target(function, ctx)),
6837 "pointer_expression" | "parenthesized_expression" => node
6838 .child_by_field_name("argument")
6839 .or_else(|| node.named_child(0))
6840 .is_some_and(|child| receiver_has_known_non_target(child, ctx)),
6841 "identifier" => ctx
6842 .bindings
6843 .resolve_symbol(node_text(node, ctx.source))
6844 .as_precise()
6845 .is_some_and(|targets| {
6846 let units = targets
6847 .iter()
6848 .filter_map(|target| target.unit.as_ref())
6849 .collect::<Vec<_>>();
6850 !units.is_empty()
6851 && units.iter().all(|target| {
6852 receiver_owner_is_known_non_target(target, owner, node.start_byte(), ctx)
6853 })
6854 }),
6855 "this" => known_non_target_owner_context(node, ctx),
6856 "qualified_identifier" | "scoped_identifier" | "field_identifier" => {
6857 qualified_owner_is_known_non_target(node, ctx)
6858 }
6859 _ => false,
6860 }
6861}
6862
6863#[derive(Clone, Copy, PartialEq, Eq)]
6864enum QualifiedOwnerResolution {
6865 Target,
6866 NonTarget,
6867 Unresolved,
6868}
6869
6870#[derive(Clone)]
6871pub enum LexicalScopeResolution {
6872 Resolved(Vec<String>),
6873 Ambiguous,
6874 Missing,
6875}
6876
6877type LexicalScopeCache = RefCell<HashMap<(usize, usize, bool, bool), LexicalScopeResolution>>;
6878
6879fn qualified_owner_matches(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6880 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::Target
6881}
6882
6883fn qualified_owner_is_known_non_target(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
6884 qualified_owner_resolution(node, ctx) == QualifiedOwnerResolution::NonTarget
6885}
6886
6887fn is_structurally_qualified(node: Node<'_>) -> bool {
6888 matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
6889}
6890
6891fn qualified_owner_resolution(node: Node<'_>, ctx: &ScanCtx<'_>) -> QualifiedOwnerResolution {
6892 let Some(target_owner) = ctx.spec.owner.as_ref() else {
6893 return QualifiedOwnerResolution::Unresolved;
6894 };
6895 let Some((components, global)) = qualified_callable_owner_components(node, ctx.source) else {
6896 return QualifiedOwnerResolution::Unresolved;
6897 };
6898 if !global
6899 && !matches!(
6900 enclosing_lexical_scope_components(
6901 node,
6902 &ctx.analyzer,
6903 ctx.visibility,
6904 ctx.file,
6905 ctx.source,
6906 ),
6907 LexicalScopeResolution::Resolved(_)
6908 )
6909 {
6910 return QualifiedOwnerResolution::Unresolved;
6911 }
6912 match resolve_type_components_lexically_at_for_target_with_scope_cache(
6913 node,
6914 &components,
6915 global,
6916 &ctx.analyzer,
6917 ctx.visibility,
6918 &ctx.ordinary_type_imports,
6919 ctx.file,
6920 ctx.source,
6921 target_owner,
6922 false,
6923 Some(&ctx.lexical_scope_cache),
6924 ) {
6925 LexicalTypeResolution::Resolved { unit: owner, .. }
6926 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
6927 {
6928 QualifiedOwnerResolution::Target
6929 }
6930 LexicalTypeResolution::Resolved { unit: owner, .. }
6931 if same_visible_symbol(&owner, target_owner) =>
6932 {
6933 QualifiedOwnerResolution::Unresolved
6934 }
6935 LexicalTypeResolution::Resolved { .. } => QualifiedOwnerResolution::NonTarget,
6936 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
6937 QualifiedOwnerResolution::Unresolved
6938 }
6939 }
6940}
6941
6942fn qualified_callable_owner_components(
6943 node: Node<'_>,
6944 source: &str,
6945) -> Option<(Vec<String>, bool)> {
6946 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier") {
6947 return None;
6948 }
6949 let global = is_globally_qualified_cpp_name(node);
6950 let mut components = Vec::new();
6951 append_cpp_name_components(node, source, &mut components)?;
6952 components.pop()?;
6953 (!components.is_empty()).then_some((components, global))
6954}
6955
6956fn type_reference_components(node: Node<'_>, source: &str) -> Option<(Vec<String>, bool)> {
6957 if !matches!(
6958 node.kind(),
6959 "identifier"
6960 | "type_identifier"
6961 | "namespace_identifier"
6962 | "qualified_identifier"
6963 | "scoped_identifier"
6964 | "scoped_type_identifier"
6965 | "template_type"
6966 | "template_function"
6967 ) {
6968 return None;
6969 }
6970 let mut components = Vec::new();
6971 append_cpp_name_components(node, source, &mut components)?;
6972 (!components.is_empty()).then_some((components, is_globally_qualified_cpp_name(node)))
6973}
6974
6975pub fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Vec<String> {
6976 let mut namespaces = Vec::new();
6977 let mut current = node.parent();
6978 while let Some(parent) = current {
6979 if parent.kind() == "namespace_definition"
6980 && let Some(name) = parent.child_by_field_name("name")
6981 {
6982 let mut components = Vec::new();
6983 if append_cpp_name_components(name, source, &mut components).is_some() {
6984 namespaces.push(components);
6985 }
6986 }
6987 current = parent.parent();
6988 }
6989 namespaces.reverse();
6990 namespaces.into_iter().flatten().collect()
6991}
6992
6993pub fn enclosing_lexical_scope_components(
6994 node: Node<'_>,
6995 analyzer: &CppGraphSource<'_>,
6996 visibility: &VisibilityIndex<'_>,
6997 file: &ProjectFile,
6998 source: &str,
6999) -> LexicalScopeResolution {
7000 enclosing_lexical_scope_components_with_unresolved_owner(
7001 node, analyzer, visibility, file, source, false, false,
7002 )
7003}
7004
7005#[allow(clippy::too_many_arguments)]
7006fn cached_enclosing_lexical_scope_components_with_unresolved_owner(
7007 node: Node<'_>,
7008 analyzer: &CppGraphSource<'_>,
7009 visibility: &VisibilityIndex<'_>,
7010 file: &ProjectFile,
7011 source: &str,
7012 allow_structured_unresolved_owner: bool,
7013 ignore_function_owner: bool,
7014 cache: Option<&LexicalScopeCache>,
7015) -> LexicalScopeResolution {
7016 let Some(cache) = cache else {
7017 return enclosing_lexical_scope_components_with_unresolved_owner(
7018 node,
7019 analyzer,
7020 visibility,
7021 file,
7022 source,
7023 allow_structured_unresolved_owner,
7024 ignore_function_owner,
7025 );
7026 };
7027 let (anchor_start, anchor_end) = lexical_scope_cache_anchor(node);
7028 let key = (
7029 anchor_start,
7030 anchor_end,
7031 allow_structured_unresolved_owner,
7032 ignore_function_owner,
7033 );
7034 if let Some(cached) = cache.borrow().get(&key).cloned() {
7035 return cached;
7036 }
7037 let resolved = enclosing_lexical_scope_components_with_unresolved_owner(
7038 node,
7039 analyzer,
7040 visibility,
7041 file,
7042 source,
7043 allow_structured_unresolved_owner,
7044 ignore_function_owner,
7045 );
7046 cache.borrow_mut().insert(key, resolved.clone());
7047 resolved
7048}
7049
7050fn lexical_scope_cache_anchor(node: Node<'_>) -> (usize, usize) {
7051 let mut current = node;
7052 loop {
7053 if matches!(
7054 current.kind(),
7055 "function_definition"
7056 | "class_specifier"
7057 | "struct_specifier"
7058 | "union_specifier"
7059 | "namespace_definition"
7060 | "translation_unit"
7061 ) {
7062 return (current.start_byte(), current.end_byte());
7063 }
7064 let Some(parent) = current.parent() else {
7065 return (current.start_byte(), current.end_byte());
7066 };
7067 current = parent;
7068 }
7069}
7070
7071fn enclosing_lexical_scope_components_with_unresolved_owner(
7072 node: Node<'_>,
7073 analyzer: &CppGraphSource<'_>,
7074 visibility: &VisibilityIndex<'_>,
7075 file: &ProjectFile,
7076 source: &str,
7077 allow_structured_unresolved_owner: bool,
7078 ignore_function_owner: bool,
7079) -> LexicalScopeResolution {
7080 #[cfg(any(test, feature = "test-support"))]
7081 LEXICAL_SCOPE_RECONSTRUCTIONS_FOR_TEST.with(|count| count.set(count.get() + 1));
7082 let namespace = enclosing_namespace_components(node, source);
7083 let mut scope = namespace.clone();
7084 let mut classes = Vec::new();
7085 let mut function_definition = None;
7086 let mut current = node.parent();
7087 while let Some(parent) = current {
7088 if matches!(
7089 parent.kind(),
7090 "class_specifier" | "struct_specifier" | "union_specifier"
7091 ) && let Some(name) = parent.child_by_field_name("name")
7092 {
7093 let mut components = Vec::new();
7094 if append_cpp_name_components(name, source, &mut components).is_some() {
7095 classes.push(components);
7096 }
7097 }
7098 if function_definition.is_none() && parent.kind() == "function_definition" {
7099 function_definition = Some(parent);
7100 }
7101 current = parent.parent();
7102 }
7103 let displaced_class_scope = has_recovered_class_shape_ancestor(node)
7104 || has_malformed_wrapper_function_definition_ancestor(node);
7105 let has_qualified_function_owner = function_definition
7106 .and_then(function_definition_owner_lookup_node)
7107 .is_some_and(|owner| {
7108 is_structurally_qualified(owner) && !is_macro_decorated_function_owner(owner)
7109 });
7110 let indexed_scope = displaced_class_scope
7111 .then(|| {
7112 indexed_structural_class_scope(visibility, file, node, source)
7113 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7114 })
7115 .flatten()
7116 .or_else(|| {
7117 (has_qualified_function_owner && function_definition.is_some())
7124 .then(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7125 .flatten()
7126 .filter(|indexed| {
7127 qualified_owner_scope_is_recoverable(
7128 indexed,
7129 &namespace,
7130 &classes,
7131 function_definition
7132 .and_then(function_definition_owner_lookup_node)
7133 .and_then(|owner| qualified_callable_owner_components(owner, source))
7134 .map(|(components, _)| components),
7135 )
7136 })
7137 })
7138 .or_else(|| {
7139 indexed_structural_class_scope(visibility, file, node, source)
7145 .or_else(|| indexed_enclosing_owner_scope(analyzer, visibility, file, node))
7146 .filter(|indexed| {
7147 qualified_owner_scope_is_recoverable(indexed, &namespace, &classes, None)
7148 })
7149 })
7150 .or_else(|| {
7151 (classes.is_empty() && function_definition.is_some() && !has_qualified_function_owner)
7155 .then(|| indexed_enclosing_lexical_scope(analyzer, file, node))
7156 .flatten()
7157 .filter(|indexed| indexed.len() > namespace.len())
7158 });
7159 if let Some(indexed_scope) = indexed_scope.as_ref() {
7160 scope = indexed_scope.clone();
7165 classes.clear();
7166 }
7167
7168 if !ignore_function_owner
7169 && has_qualified_function_owner
7170 && let Some(function) = function_definition.and_then(function_definition_owner_lookup_node)
7171 {
7172 let Some((owner, global)) = qualified_callable_owner_components(function, source) else {
7173 return LexicalScopeResolution::Missing;
7174 };
7175 match visibility.resolve_type_components_lexically(analyzer, file, &owner, global, &scope) {
7176 LexicalTypeResolution::Resolved { components, .. } => scope = components,
7177 LexicalTypeResolution::Ambiguous => return LexicalScopeResolution::Ambiguous,
7178 LexicalTypeResolution::Missing if allow_structured_unresolved_owner => {
7179 if let Some(indexed) = indexed_scope.as_ref().filter(|indexed| {
7180 qualified_owner_scope_is_recoverable(
7181 indexed,
7182 &namespace,
7183 &classes,
7184 Some(owner.clone()),
7185 )
7186 }) {
7187 scope = indexed.clone();
7188 } else {
7189 scope = if global || owner.starts_with(&namespace) {
7190 owner
7191 } else {
7192 let mut relative = namespace;
7193 relative.extend(owner);
7194 relative
7195 };
7196 }
7197 }
7198 LexicalTypeResolution::Missing => {
7199 match indexed_enclosing_owner_scope(analyzer, visibility, file, node) {
7209 Some(indexed) => scope = indexed,
7210 None => return LexicalScopeResolution::Missing,
7211 }
7212 }
7213 }
7214 }
7215
7216 classes.reverse();
7217 scope.extend(classes.into_iter().flatten());
7218 LexicalScopeResolution::Resolved(scope)
7219}
7220
7221fn has_recovered_class_shape_ancestor(node: Node<'_>) -> bool {
7222 let mut current = node.parent();
7223 while let Some(parent) = current {
7224 if parent.kind() == "function_definition"
7225 && parent.child_by_field_name("type").is_some_and(|type_node| {
7226 matches!(
7227 type_node.kind(),
7228 "class_specifier" | "struct_specifier" | "union_specifier"
7229 )
7230 })
7231 {
7232 return true;
7233 }
7234 current = parent.parent();
7235 }
7236 false
7237}
7238
7239fn has_malformed_wrapper_function_definition_ancestor(node: Node<'_>) -> bool {
7240 let mut current = node.parent();
7241 while let Some(parent) = current {
7242 if parent.kind() == "function_definition"
7243 && is_malformed_wrapper_function_definition(parent)
7244 {
7245 return true;
7246 }
7247 current = parent.parent();
7248 }
7249 false
7250}
7251
7252fn is_malformed_wrapper_function_definition(node: Node<'_>) -> bool {
7253 node.has_error()
7254 && node
7255 .child_by_field_name("declarator")
7256 .is_some_and(|declarator| {
7257 declarator.kind() != "function_declarator"
7258 && first_descendant_of_kind(declarator, "function_declarator").is_none()
7259 })
7260}
7261
7262fn is_macro_decorated_function_owner(node: Node<'_>) -> bool {
7268 node.child_by_field_name("scope")
7269 .and_then(|scope| recovered_macro_decorated_type_node(scope))
7270 .is_some()
7271}
7272
7273fn indexed_structural_class_scope(
7274 visibility: &VisibilityIndex<'_>,
7275 file: &ProjectFile,
7276 node: Node<'_>,
7277 source: &str,
7278) -> Option<Vec<String>> {
7279 let mut current = node.parent();
7280 while let Some(parent) = current {
7281 if matches!(
7282 parent.kind(),
7283 "class_specifier" | "struct_specifier" | "union_specifier"
7284 ) {
7285 return visibility.indexed_structural_class_scope(file, parent, source);
7286 }
7287 current = parent.parent();
7288 }
7289 None
7290}
7291
7292fn qualified_owner_scope_is_recoverable(
7303 indexed: &[String],
7304 namespace: &[String],
7305 classes: &[Vec<String>],
7306 qualified_owner: Option<Vec<String>>,
7307) -> bool {
7308 if let Some(owner) = qualified_owner {
7309 if indexed.len() <= owner.len() || !indexed.ends_with(&owner) {
7310 return false;
7311 }
7312 if namespace.is_empty() {
7318 return true;
7319 }
7320 if indexed.len() <= namespace.len() {
7321 return false;
7322 }
7323 let mut prefix = indexed.iter();
7324 return namespace
7325 .iter()
7326 .all(|component| prefix.any(|candidate| candidate == component));
7327 }
7328 let class_components = classes.iter().flatten().cloned().collect::<Vec<_>>();
7329 if !class_components.is_empty() {
7330 return indexed.len() > class_components.len() && indexed.ends_with(&class_components);
7331 }
7332 if namespace.is_empty() || indexed.len() <= namespace.len() {
7333 return false;
7334 }
7335 let mut prefix = indexed.iter();
7336 namespace
7337 .iter()
7338 .all(|component| prefix.any(|candidate| candidate == component))
7339}
7340
7341fn is_indexed_class_owner(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
7344 unit.is_class()
7345 && !analyzer
7346 .type_alias_provider()
7347 .is_some_and(|provider| provider.is_type_alias(unit))
7348}
7349
7350fn indexed_enclosing_owner_scope(
7364 analyzer: &CppGraphSource<'_>,
7365 visibility: &VisibilityIndex<'_>,
7366 file: &ProjectFile,
7367 node: Node<'_>,
7368) -> Option<Vec<String>> {
7369 visibility.indexed_enclosing_owner_scope(analyzer, file, node)
7370}
7371
7372fn cached_indexed_enclosing_class_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
7373 let start = enclosing_context(node, ctx).enclosing?;
7374 brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(start, |unit| {
7375 ctx.analyzer.parent_of(unit)
7376 })
7377 .find(|unit| is_indexed_class_owner(&ctx.analyzer, unit))
7378}
7379
7380pub fn resolve_type_node_lexically(
7381 node: Node<'_>,
7382 analyzer: &CppGraphSource<'_>,
7383 visibility: &VisibilityIndex<'_>,
7384 ordinary_type_imports: &OrdinaryTypeImportCell,
7385 file: &ProjectFile,
7386 source: &str,
7387) -> LexicalTypeResolution {
7388 let Some((components, global)) = type_reference_components(node, source) else {
7389 return LexicalTypeResolution::Missing;
7390 };
7391 let resolution = resolve_type_components_lexically_at(
7392 node,
7393 &components,
7394 global,
7395 analyzer,
7396 visibility,
7397 ordinary_type_imports,
7398 file,
7399 source,
7400 );
7401 if !is_template_argument_type_leaf(node) {
7402 return resolution;
7403 }
7404
7405 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
7412 return resolution;
7413 };
7414 let namespace_scope = enclosing_namespace_components(node, source);
7415 if indexed_scope.len() <= namespace_scope.len() {
7416 return resolution;
7417 }
7418 let indexed = visibility.resolve_type_components_lexically(
7419 analyzer,
7420 file,
7421 &components,
7422 global,
7423 &indexed_scope,
7424 );
7425 match indexed {
7426 LexicalTypeResolution::Resolved { ref unit, .. }
7427 if !visibility
7428 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
7429 {
7430 resolution
7431 }
7432 LexicalTypeResolution::Resolved { .. } => indexed,
7433 _ => resolution,
7434 }
7435}
7436
7437fn is_template_argument_type_leaf(node: Node<'_>) -> bool {
7438 let Some(type_descriptor) = node.parent() else {
7439 return false;
7440 };
7441 if type_descriptor.kind() != "type_descriptor"
7442 || type_descriptor.child_by_field_name("type") != Some(node)
7443 {
7444 return false;
7445 }
7446 let Some(arguments) = type_descriptor.parent() else {
7447 return false;
7448 };
7449 if arguments.kind() != "template_argument_list" {
7450 return false;
7451 }
7452 arguments.parent().is_some_and(|parent| {
7453 matches!(parent.kind(), "template_type" | "template_function")
7454 && parent.child_by_field_name("arguments") == Some(arguments)
7455 })
7456}
7457
7458#[allow(clippy::too_many_arguments)]
7459pub fn resolve_type_node_lexically_for_target(
7460 node: Node<'_>,
7461 analyzer: &CppGraphSource<'_>,
7462 visibility: &VisibilityIndex<'_>,
7463 ordinary_type_imports: &OrdinaryTypeImportCell,
7464 file: &ProjectFile,
7465 source: &str,
7466 target: &CodeUnit,
7467 scope_cache: Option<&LexicalScopeCache>,
7468 recovered_scope: Option<&[String]>,
7469) -> LexicalTypeResolution {
7470 let Some((reference_components, global)) = type_reference_components(node, source) else {
7471 return LexicalTypeResolution::Missing;
7472 };
7473 let terminal = reference_components
7474 .last()
7475 .expect("type reference components are non-empty");
7476 if !visibility.coarse_unqualified_type_reference_may_resolve(file, terminal) {
7477 return LexicalTypeResolution::Missing;
7478 }
7479 let template_arguments = cpp_template_reference_arguments(node, source);
7480 let selects_concrete_specialization =
7481 template_arguments.is_some() && visibility.is_template_specialization(target);
7482 if !selects_concrete_specialization
7483 && !visibility.structured_type_reference_may_resolve_to_target(
7484 analyzer,
7485 file,
7486 std::slice::from_ref(terminal),
7487 false,
7488 &[],
7489 target,
7490 )
7491 {
7492 return LexicalTypeResolution::Missing;
7493 }
7494 if let Some(arguments) = template_arguments.as_ref() {
7495 let alias_resolution = if let Some(recovered_scope) = recovered_scope {
7496 resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
7497 node,
7498 &reference_components,
7499 global,
7500 analyzer,
7501 visibility,
7502 ordinary_type_imports,
7503 file,
7504 source,
7505 recovered_scope,
7506 )
7507 } else {
7508 resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
7509 node,
7510 &reference_components,
7511 global,
7512 analyzer,
7513 visibility,
7514 ordinary_type_imports,
7515 file,
7516 source,
7517 scope_cache,
7518 )
7519 };
7520 return match alias_resolution {
7521 LexicalTypeResolution::Resolved {
7522 unit,
7523 components,
7524 candidates,
7525 } if visibility.template_alias_arguments_preserve_target(
7526 analyzer, file, &unit, arguments, target,
7527 ) =>
7528 {
7529 LexicalTypeResolution::Resolved {
7530 unit: target.clone(),
7531 components,
7532 candidates,
7533 }
7534 }
7535 LexicalTypeResolution::Resolved {
7536 unit,
7537 components,
7538 candidates,
7539 } => match visibility.resolve_template_arguments(file, unit.clone(), arguments) {
7540 Ok(resolved_unit) => {
7541 let target_guided = (!same_visible_symbol(&resolved_unit, target))
7542 .then(|| {
7543 target_guided_malformed_template_alias_resolution(
7544 node,
7545 analyzer,
7546 visibility,
7547 file,
7548 arguments,
7549 &reference_components,
7550 target,
7551 )
7552 })
7553 .flatten();
7554 target_guided.unwrap_or(LexicalTypeResolution::Resolved {
7555 unit: resolved_unit,
7556 components,
7557 candidates,
7558 })
7559 }
7560 Err(_) => LexicalTypeResolution::Ambiguous,
7561 },
7562 LexicalTypeResolution::Missing => {
7563 let target_preserving = if let Some(recovered_scope) = recovered_scope {
7564 resolve_type_components_lexically_at_for_target_with_recovered_scope(
7565 node,
7566 &reference_components,
7567 global,
7568 analyzer,
7569 visibility,
7570 ordinary_type_imports,
7571 file,
7572 source,
7573 target,
7574 true,
7575 recovered_scope,
7576 )
7577 } else {
7578 resolve_type_components_lexically_at_for_target_with_scope_cache(
7579 node,
7580 &reference_components,
7581 global,
7582 analyzer,
7583 visibility,
7584 ordinary_type_imports,
7585 file,
7586 source,
7587 target,
7588 true,
7589 scope_cache,
7590 )
7591 };
7592 match target_preserving {
7593 LexicalTypeResolution::Resolved {
7594 unit: _,
7595 components,
7596 candidates,
7597 } if template_reference_candidates_select_target(
7598 node,
7599 &candidates,
7600 analyzer,
7601 visibility,
7602 file,
7603 source,
7604 target,
7605 ) =>
7606 {
7607 LexicalTypeResolution::Resolved {
7608 unit: target.clone(),
7609 components,
7610 candidates,
7611 }
7612 }
7613 _ => target_guided_malformed_template_alias_resolution(
7614 node,
7615 analyzer,
7616 visibility,
7617 file,
7618 arguments,
7619 &reference_components,
7620 target,
7621 )
7622 .unwrap_or(LexicalTypeResolution::Missing),
7623 }
7624 }
7625 LexicalTypeResolution::Ambiguous => LexicalTypeResolution::Ambiguous,
7626 };
7627 }
7628 let resolution = if let Some(recovered_scope) = recovered_scope {
7629 resolve_type_components_lexically_at_for_target_with_recovered_scope(
7630 node,
7631 &reference_components,
7632 global,
7633 analyzer,
7634 visibility,
7635 ordinary_type_imports,
7636 file,
7637 source,
7638 target,
7639 true,
7640 recovered_scope,
7641 )
7642 } else {
7643 resolve_type_components_lexically_at_for_target_with_scope_cache(
7644 node,
7645 &reference_components,
7646 global,
7647 analyzer,
7648 visibility,
7649 ordinary_type_imports,
7650 file,
7651 source,
7652 target,
7653 true,
7654 scope_cache,
7655 )
7656 };
7657 if !is_template_argument_type_leaf(node) {
7658 return resolution;
7659 }
7660
7661 let Some(indexed_scope) = indexed_enclosing_lexical_scope(analyzer, file, node) else {
7669 return resolution;
7670 };
7671 let namespace_scope = enclosing_namespace_components(node, source);
7672 if indexed_scope.len() <= namespace_scope.len() {
7673 return resolution;
7674 }
7675 let indexed = visibility.resolve_type_components_lexically_for_target(
7676 analyzer,
7677 file,
7678 &reference_components,
7679 global,
7680 &indexed_scope,
7681 target,
7682 );
7683 match indexed {
7684 LexicalTypeResolution::Resolved {
7685 ref unit,
7686 ref candidates,
7687 ..
7688 } if (same_visible_symbol(unit, target)
7689 || candidates
7690 .iter()
7691 .any(|candidate| same_visible_symbol(candidate, target)))
7692 && visibility
7693 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
7694 {
7695 indexed
7696 }
7697 _ => resolution,
7698 }
7699}
7700
7701#[allow(clippy::too_many_arguments)]
7702fn target_guided_malformed_template_alias_resolution(
7703 node: Node<'_>,
7704 analyzer: &CppGraphSource<'_>,
7705 visibility: &VisibilityIndex<'_>,
7706 file: &ProjectFile,
7707 arguments: &[brokk_bifrost_core::analyzer::model::CppTemplateExpression],
7708 components: &[String],
7709 target: &CodeUnit,
7710) -> Option<LexicalTypeResolution> {
7711 if components.len() != 1 || !has_malformed_wrapper_function_definition_ancestor(node) {
7712 return None;
7713 }
7714
7715 let identifier = &components[0];
7716 let namespace =
7717 visibility.target_preserving_reference_namespace(analyzer, file, identifier, target)?;
7718 let namespace_name = namespace.join("::");
7719 let candidates = visibility
7720 .visible_identifier_candidates(file, identifier)
7721 .filter(|candidate| {
7722 cpp_namespace_for(candidate).unwrap_or_default() == namespace_name
7723 && visibility.type_candidate_may_be_visible_before_reference(
7724 analyzer,
7725 file,
7726 candidate,
7727 node.start_byte(),
7728 )
7729 })
7730 .cloned()
7731 .collect::<Vec<_>>();
7732 let first = candidates.first()?;
7733 if !candidates
7734 .iter()
7735 .all(|candidate| same_logical_symbol(first, candidate))
7736 || !candidates.iter().all(|candidate| {
7737 visibility.template_alias_arguments_preserve_target(
7738 analyzer, file, candidate, arguments, target,
7739 )
7740 })
7741 {
7742 return None;
7743 }
7744
7745 let mut resolved_components = namespace;
7746 resolved_components.push(identifier.clone());
7747 Some(LexicalTypeResolution::Resolved {
7748 unit: target.clone(),
7749 components: resolved_components,
7750 candidates,
7751 })
7752}
7753
7754fn resolve_type_node_lexically_for_target_without_visibility(
7755 node: Node<'_>,
7756 analyzer: &CppGraphSource<'_>,
7757 visibility: &VisibilityIndex<'_>,
7758 file: &ProjectFile,
7759 source: &str,
7760 target: &CodeUnit,
7761) -> LexicalTypeResolution {
7762 let Some((components, global)) = type_reference_components(node, source) else {
7763 return LexicalTypeResolution::Missing;
7764 };
7765 let lexical_scope = match enclosing_lexical_scope_components_with_unresolved_owner(
7766 node,
7767 analyzer,
7768 visibility,
7769 file,
7770 source,
7771 true,
7772 recovered_macro_decorated_declarator_type(node)
7773 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
7774 ) {
7775 LexicalScopeResolution::Resolved(scope) => scope,
7776 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7777 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7778 };
7779 visibility.resolve_type_components_lexically_for_target(
7780 analyzer,
7781 file,
7782 &components,
7783 global,
7784 &lexical_scope,
7785 target,
7786 )
7787}
7788
7789fn type_node_has_exact_target_identity_without_visibility(
7790 node: Node<'_>,
7791 analyzer: &CppGraphSource<'_>,
7792 visibility: &VisibilityIndex<'_>,
7793 file: &ProjectFile,
7794 source: &str,
7795 target: &CodeUnit,
7796) -> bool {
7797 let Some((components, global)) = type_reference_components(node, source) else {
7798 return false;
7799 };
7800 let LexicalScopeResolution::Resolved(lexical_scope) =
7801 enclosing_lexical_scope_components_with_unresolved_owner(
7802 node,
7803 analyzer,
7804 visibility,
7805 file,
7806 source,
7807 true,
7808 recovered_macro_decorated_declarator_type(node)
7809 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
7810 )
7811 else {
7812 return false;
7813 };
7814 let target_name = cpp_name_for(target);
7815 lexical_component_tiers(&components, global, &lexical_scope)
7816 .any(|qualified| qualified.join("::") == target_name)
7817}
7818
7819pub fn resolve_using_enum_declaration_owner(
7820 node: Node<'_>,
7821 analyzer: &CppGraphSource<'_>,
7822 visibility: &VisibilityIndex<'_>,
7823 ordinary_type_imports: &OrdinaryTypeImportCell,
7824 file: &ProjectFile,
7825 source: &str,
7826) -> LexicalTypeResolution {
7827 let Some(type_node) = using_enum_declaration_type_node(node) else {
7828 return LexicalTypeResolution::Missing;
7829 };
7830 let mut components = Vec::new();
7831 if append_cpp_name_components(type_node, source, &mut components).is_none()
7832 || components.is_empty()
7833 {
7834 return LexicalTypeResolution::Missing;
7835 }
7836 resolve_type_components_lexically_at(
7837 type_node,
7838 &components,
7839 is_globally_qualified_cpp_name(type_node),
7840 analyzer,
7841 visibility,
7842 ordinary_type_imports,
7843 file,
7844 source,
7845 )
7846}
7847
7848pub fn resolve_ordinary_using_declaration_owner(
7849 node: Node<'_>,
7850 analyzer: &CppGraphSource<'_>,
7851 visibility: &VisibilityIndex<'_>,
7852 file: &ProjectFile,
7853 source: &str,
7854) -> LexicalTypeResolution {
7855 let Some(type_node) = ordinary_using_declaration_type_node(node) else {
7856 return LexicalTypeResolution::Missing;
7857 };
7858 let mut components = Vec::new();
7859 if append_cpp_name_components(type_node, source, &mut components).is_none()
7860 || components.len() < 2
7861 {
7862 return LexicalTypeResolution::Missing;
7863 }
7864 let lexical_scope =
7865 match enclosing_lexical_scope_components(type_node, analyzer, visibility, file, source) {
7866 LexicalScopeResolution::Resolved(scope) => scope,
7867 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
7868 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
7869 };
7870 visibility.resolve_type_components_lexically(
7871 analyzer,
7872 file,
7873 &components,
7874 is_globally_qualified_cpp_name(type_node),
7875 &lexical_scope,
7876 )
7877}
7878
7879pub fn using_enum_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
7880 (node.kind() == "using_declaration"
7881 && (0..node.child_count()).any(|index| {
7882 node.child(index)
7883 .is_some_and(|child| child.kind() == "enum")
7884 }))
7885 .then(|| node.named_child(0))
7886 .flatten()
7887}
7888
7889pub fn ordinary_using_declaration_type_node(node: Node<'_>) -> Option<Node<'_>> {
7890 (node.kind() == "using_declaration"
7891 && using_enum_declaration_type_node(node).is_none()
7892 && using_namespace_directive_name_node(node).is_none())
7893 .then(|| node.named_child(0))
7894 .flatten()
7895}
7896
7897fn recovered_macro_using_declaration_type_node<'tree>(
7904 node: Node<'tree>,
7905 source: &str,
7906) -> Option<(Node<'tree>, bool)> {
7907 if node.kind() != "declaration" {
7908 return None;
7909 }
7910 let macro_type = node.child_by_field_name("type")?;
7911 if macro_type.kind() != "type_identifier"
7912 || !cpp_export_macro_token(node_text(macro_type, source))
7913 {
7914 return None;
7915 }
7916 let declarator = node.child_by_field_name("declarator")?;
7917 if declarator.kind() != "qualified_identifier" {
7918 return None;
7919 }
7920 let scope = declarator.child_by_field_name("scope")?;
7921 if scope.kind() != "namespace_identifier" || node_text(scope, source) != "using" {
7922 return None;
7923 }
7924 let target = declarator.child_by_field_name("name")?;
7925 let mut components = Vec::new();
7926 append_cpp_name_components(target, source, &mut components)?;
7927 (components.len() >= 2).then_some((target, is_globally_qualified_cpp_name(target)))
7928}
7929
7930fn using_namespace_directive_name_node(node: Node<'_>) -> Option<Node<'_>> {
7931 let is_directive = node.kind() == "using_directive"
7932 || (node.kind() == "using_declaration"
7933 && (0..node.child_count()).any(|index| {
7934 node.child(index)
7935 .is_some_and(|child| child.kind() == "namespace")
7936 }));
7937 if !is_directive {
7938 return None;
7939 }
7940 node.child_by_field_name("name")
7941 .or_else(|| node.named_child(node.named_child_count().checked_sub(1)?))
7942}
7943
7944fn using_named_scope(node: Node<'_>, source: &str) -> Option<Vec<String>> {
7945 let mut current = node.parent();
7946 while let Some(parent) = current {
7947 if matches!(
7948 parent.kind(),
7949 "compound_statement"
7950 | "function_definition"
7951 | "lambda_expression"
7952 | "for_statement"
7953 | "while_statement"
7954 | "if_statement"
7955 | "class_specifier"
7956 | "struct_specifier"
7957 | "union_specifier"
7958 ) {
7959 return None;
7960 }
7961 current = parent.parent();
7962 }
7963 Some(enclosing_namespace_components(node, source))
7964}
7965
7966fn ordinary_using_scope(node: Node<'_>) -> Option<(usize, usize, usize)> {
7967 let mut current = node.parent();
7968 while let Some(scope) = current {
7969 if matches!(
7970 scope.kind(),
7971 "compound_statement"
7972 | "declaration_list"
7973 | "field_declaration_list"
7974 | "translation_unit"
7975 ) {
7976 let mut depth = 0;
7977 let mut ancestor = scope.parent();
7978 while let Some(parent) = ancestor {
7979 depth += 1;
7980 ancestor = parent.parent();
7981 }
7982 return Some((scope.start_byte(), scope.end_byte(), depth));
7983 }
7984 current = scope.parent();
7985 }
7986 None
7987}
7988
7989fn collect_source_using_index(
7990 source_file: &ProjectFile,
7991 root: Node<'_>,
7992 source: &str,
7993) -> SourceUsingIndex {
7994 let mut index = SourceUsingIndex::default();
7995 let orphaned_namespaces = collect_orphaned_namespace_envelopes(root, source);
7996 let mut stack = vec![root];
7997 while let Some(node) = stack.pop() {
7998 let required_guards = if callable_preprocessor_context_is_visible(node, source) {
7999 Some(HashSet::default())
8000 } else {
8001 preprocessor_guard_environment(node, source)
8002 };
8003 let Some(required_guards) = required_guards else {
8004 let mut cursor = node.walk();
8005 stack.extend(node.children(&mut cursor));
8006 continue;
8007 };
8008 let target = if let Some(namespace_node) = using_namespace_directive_name_node(node) {
8009 let mut namespace_components = Vec::new();
8010 append_cpp_name_components(namespace_node, source, &mut namespace_components).map(
8011 |_| EffectiveUsingTarget::Namespace {
8012 namespace_components,
8013 global: is_globally_qualified_cpp_name(namespace_node),
8014 },
8015 )
8016 } else if let Some((type_node, global)) =
8017 recovered_macro_using_declaration_type_node(node, source)
8018 {
8019 let mut target_components = Vec::new();
8020 (append_cpp_name_components(type_node, source, &mut target_components).is_some()
8021 && target_components.len() >= 2)
8022 .then(|| EffectiveUsingTarget::Ordinary {
8023 name: target_components
8024 .last()
8025 .expect("recovered ordinary using has a terminal component")
8026 .clone(),
8027 target_components,
8028 global,
8029 })
8030 } else if let Some(type_node) = ordinary_using_declaration_type_node(node) {
8031 let mut target_components = Vec::new();
8032 (append_cpp_name_components(type_node, source, &mut target_components).is_some()
8033 && target_components.len() >= 2)
8034 .then(|| EffectiveUsingTarget::Ordinary {
8035 name: target_components
8036 .last()
8037 .expect("ordinary using has a terminal component")
8038 .clone(),
8039 target_components,
8040 global: is_globally_qualified_cpp_name(type_node),
8041 })
8042 } else {
8043 None
8044 };
8045 if let Some(target) = target
8046 && let Some((scope_start, scope_end, scope_depth)) = ordinary_using_scope(node)
8047 {
8048 let declaration_namespace = enclosing_namespace_components(node, source);
8049 let declaration_namespace = if declaration_namespace.is_empty() {
8050 recovered_orphaned_namespace_components(node, source, &orphaned_namespaces)
8051 .unwrap_or(declaration_namespace)
8052 } else {
8053 declaration_namespace
8054 };
8055 let namespace_scope = using_named_scope(node, source);
8056 let lexical_depth = declaration_namespace.len();
8057 let binding = OrdinaryTypeImport {
8058 target,
8059 source: source_file.clone(),
8060 declaration_byte: node.end_byte(),
8061 scope_start,
8062 scope_end,
8063 scope_depth,
8064 lexical_depth,
8065 declaration_namespace,
8066 namespace_scope,
8067 resolved_target_components: None,
8068 required_guards,
8069 };
8070 match &binding.target {
8071 EffectiveUsingTarget::Ordinary { name, .. } => index
8072 .ordinary_by_name
8073 .entry(name.clone())
8074 .or_default()
8075 .push(binding),
8076 EffectiveUsingTarget::Namespace { .. } => index.directives.push(binding),
8077 }
8078 }
8079 let mut cursor = node.walk();
8080 stack.extend(node.children(&mut cursor));
8081 }
8082 index
8083}
8084
8085struct OrphanedNamespaceEnvelope {
8086 body_end: usize,
8087 components: Vec<String>,
8088 class_names: HashSet<String>,
8089 error_marked: bool,
8090}
8091
8092fn collect_orphaned_namespace_envelopes(
8099 root: Node<'_>,
8100 source: &str,
8101) -> Vec<OrphanedNamespaceEnvelope> {
8102 let mut envelopes = Vec::new();
8103 let mut stack = vec![root];
8104 while let Some(current) = stack.pop() {
8105 if current.kind() == "namespace_definition"
8106 && let Some(body) = current.child_by_field_name("body")
8107 && current.end_byte() == body.end_byte()
8108 && let Some(name) = current.child_by_field_name("name")
8109 {
8110 let mut components = enclosing_namespace_components(current, source);
8111 if append_cpp_name_components(name, source, &mut components).is_some()
8112 && !components.is_empty()
8113 {
8114 let mut class_names = HashSet::default();
8115 let mut body_stack = vec![body];
8116 while let Some(node) = body_stack.pop() {
8117 if let Some(name) = orphaned_class_definition_name(node, source) {
8118 class_names.insert(name);
8119 }
8120 let mut cursor = node.walk();
8121 if node.kind() == "ERROR" {
8122 body_stack.extend(node.children(&mut cursor));
8123 } else {
8124 body_stack.extend(node.named_children(&mut cursor));
8125 }
8126 }
8127 envelopes.push(OrphanedNamespaceEnvelope {
8128 body_end: body.end_byte(),
8129 components,
8130 class_names,
8131 error_marked: current.has_error(),
8132 });
8133 }
8134 }
8135 let mut cursor = current.walk();
8136 stack.extend(current.named_children(&mut cursor));
8137 }
8138 envelopes
8139}
8140
8141fn collect_orphaned_namespace_type_envelopes(
8148 root: Node<'_>,
8149 source: &str,
8150) -> Vec<OrphanedNamespaceEnvelope> {
8151 let mut envelopes = Vec::new();
8152 let mut stack = vec![root];
8153 while let Some(current) = stack.pop() {
8154 if current.kind() == "namespace_definition"
8155 && current.has_error()
8156 && let Some(body) = current.child_by_field_name("body")
8157 && current.end_byte() == body.end_byte()
8158 && let Some(name) = current.child_by_field_name("name")
8159 {
8160 let mut components = enclosing_namespace_components(current, source);
8161 if append_cpp_name_components(name, source, &mut components).is_some() {
8162 envelopes.push(OrphanedNamespaceEnvelope {
8163 body_end: body.end_byte(),
8164 components,
8165 class_names: HashSet::default(),
8166 error_marked: true,
8167 });
8168 }
8169 }
8170 if !current.has_error() {
8171 continue;
8172 }
8173 let mut cursor = current.walk();
8174 stack.extend(
8175 current
8176 .named_children(&mut cursor)
8177 .filter(|child| child.has_error()),
8178 );
8179 }
8180 envelopes
8181}
8182
8183fn orphaned_class_definition_name(node: Node<'_>, source: &str) -> Option<String> {
8184 if matches!(
8185 node.kind(),
8186 "class_specifier" | "struct_specifier" | "union_specifier"
8187 ) {
8188 let body = node.child_by_field_name("body")?;
8189 let name = node.child_by_field_name("name")?;
8190 return (!name.is_missing() && !body.is_missing())
8191 .then(|| node_text(name, source).to_string());
8192 }
8193 if node.kind() != "ERROR" {
8194 return None;
8195 }
8196
8197 for index in 0..node.child_count() {
8203 let Some(keyword) = node.child(index) else {
8204 continue;
8205 };
8206 if !matches!(keyword.kind(), "class" | "struct" | "union") {
8207 continue;
8208 }
8209 let mut name = None;
8210 for next_index in (index + 1)..node.child_count() {
8211 let Some(next) = node.child(next_index) else {
8212 continue;
8213 };
8214 if next.kind() == ";" {
8215 break;
8216 }
8217 if next.kind() == "{" {
8218 return name
8219 .filter(|name_node: &Node<'_>| !name_node.is_missing())
8220 .map(|name_node| node_text(name_node, source).to_string());
8221 }
8222 if name.is_none() && matches!(next.kind(), "identifier" | "type_identifier") {
8223 name = Some(next);
8224 }
8225 }
8226 }
8227 None
8228}
8229
8230fn recovered_orphaned_namespace_components(
8231 node: Node<'_>,
8232 source: &str,
8233 envelopes: &[OrphanedNamespaceEnvelope],
8234) -> Option<Vec<String>> {
8235 let owner_name = orphaned_using_owner_name(node, source)?;
8236 envelopes
8237 .iter()
8238 .filter(|envelope| {
8239 envelope.body_end <= node.start_byte() && envelope.class_names.contains(&owner_name)
8240 })
8241 .max_by_key(|envelope| envelope.body_end)
8242 .map(|envelope| envelope.components.clone())
8243}
8244
8245fn orphaned_using_owner_name(node: Node<'_>, source: &str) -> Option<String> {
8246 let function = std::iter::successors(node.parent(), |current| current.parent())
8247 .find(|current| current.kind() == "function_definition")?;
8248 let owner = function_definition_owner_lookup_node(function)?;
8249 let scope = owner.child_by_field_name("scope")?;
8250 let mut components = Vec::new();
8251 append_cpp_name_components(scope, source, &mut components)?;
8252 if components.len() != 1 {
8259 return None;
8260 }
8261 components.pop()
8262}
8263
8264fn build_project_using_index(visibility: &VisibilityIndex<'_>) -> ProjectUsingIndex {
8265 let mut project = ProjectUsingIndex::default();
8266 for source_file in visibility.all_visible_source_files() {
8267 visibility.note_using_source_index_walk_for_test();
8268 let Some(prepared) = visibility.cpp().prepared_syntax(&source_file) else {
8269 continue;
8270 };
8271 let source_index = collect_source_using_index(
8272 &source_file,
8273 prepared.tree().root_node(),
8274 prepared.source(),
8275 );
8276 for (name, bindings) in source_index.ordinary_by_name {
8277 project
8278 .ordinary_by_name
8279 .entry(name)
8280 .or_default()
8281 .extend(bindings);
8282 }
8283 project.directives.extend(source_index.directives);
8284 }
8285 project
8286}
8287
8288fn effective_using_target_tiers(binding: &OrdinaryTypeImport) -> Vec<Vec<String>> {
8289 let (components, global) = match &binding.target {
8290 EffectiveUsingTarget::Ordinary {
8291 target_components,
8292 global,
8293 ..
8294 } => (target_components, *global),
8295 EffectiveUsingTarget::Namespace {
8296 namespace_components,
8297 global,
8298 } => (namespace_components, *global),
8299 };
8300 lexical_component_tiers(components, global, &binding.declaration_namespace).collect()
8301}
8302
8303fn using_binding_target_components_for_name(
8304 binding: &OrdinaryTypeImport,
8305 project: &ProjectUsingIndex,
8306 visibility: &VisibilityIndex<'_>,
8307 file: &ProjectFile,
8308 name: &str,
8309) -> Option<Vec<String>> {
8310 let cpp_source = CppGraphSource::from_source(visibility.cpp());
8313 let visible_candidates = visibility
8314 .visible_identifier_candidates(file, name)
8315 .filter(|candidate| {
8316 candidate.is_class()
8317 || is_type_alias(candidate)
8318 || (candidate.is_function() && type_owner_of(&cpp_source, candidate).is_none())
8319 })
8320 .collect::<Vec<_>>();
8321 if visible_candidates.is_empty() {
8322 return None;
8323 }
8324 match &binding.target {
8325 EffectiveUsingTarget::Ordinary {
8326 name: imported_name,
8327 ..
8328 } if imported_name == name => {
8329 effective_using_target_tiers(binding)
8330 .into_iter()
8331 .find(|qualified| {
8332 let qualified_name = qualified.join("::");
8333 visible_candidates
8334 .iter()
8335 .any(|candidate| cpp_name_for(candidate) == qualified_name)
8336 })
8337 }
8338 EffectiveUsingTarget::Namespace { .. } => {
8339 visibility.note_using_namespace_lookup_for_test();
8340 let target_tiers = effective_using_target_tiers(binding);
8341 let resolved = target_tiers
8342 .iter()
8343 .find(|namespace_components| {
8344 let namespace = namespace_components.join("::");
8345 visible_candidates.iter().any(|candidate| {
8346 visibility.note_using_name_candidate_inspection_for_test();
8347 cpp_namespace_for(candidate).is_some_and(|candidate_namespace| {
8348 candidate_namespace == namespace
8349 || candidate_namespace.starts_with(&format!("{namespace}::"))
8350 })
8351 }) || project.directives.iter().any(|candidate| {
8352 candidate.namespace_scope.as_deref()
8353 == Some(namespace_components.as_slice())
8354 }) || project
8355 .ordinary_by_name
8356 .values()
8357 .flatten()
8358 .any(|candidate| {
8359 candidate.namespace_scope.as_deref()
8360 == Some(namespace_components.as_slice())
8361 })
8362 })
8363 .cloned();
8364 resolved.or_else(|| {
8365 (target_tiers.len() == 1)
8371 .then(|| target_tiers.into_iter().next())
8372 .flatten()
8373 })
8374 }
8375 EffectiveUsingTarget::Ordinary { .. } => None,
8376 }
8377}
8378
8379fn include_node_for_activation(root: Node<'_>, activation: usize) -> Option<Node<'_>> {
8380 let start = activation.checked_sub(1)?;
8381 let mut node = root.descendant_for_byte_range(start, activation)?;
8382 while node.kind() != "preproc_include" {
8383 node = node.parent()?;
8384 }
8385 Some(node)
8386}
8387
8388fn project_using_bindings(
8389 binding: OrdinaryTypeImport,
8390 visibility: &VisibilityIndex<'_>,
8391 file: &ProjectFile,
8392 root: Node<'_>,
8393 source: &str,
8394) -> Vec<OrdinaryTypeImport> {
8395 if binding.source == *file {
8396 return vec![binding];
8397 }
8398 if !visibility.source_is_visible(file, &binding.source) || binding.namespace_scope.is_none() {
8399 return Vec::new();
8400 }
8401 visibility.note_using_donor_activation_for_test();
8402 let Some(prepared) = visibility.cpp().prepared_syntax(file) else {
8403 return Vec::new();
8404 };
8405 let projections = visibility
8406 .include_activation_for_source(visibility.cpp(), file, prepared.as_ref(), &binding.source)
8407 .map_or_else(
8408 || {
8409 visibility.conditional_include_projections_for_source(
8410 file,
8411 prepared.as_ref(),
8412 &binding.source,
8413 )
8414 },
8415 |activation_byte| {
8416 Arc::from([ConditionalIncludeProjection {
8417 activation_byte,
8418 required_guards: HashSet::default(),
8419 }])
8420 },
8421 );
8422 projections
8423 .iter()
8424 .cloned()
8425 .filter_map(|projection| {
8426 let required_guards =
8427 merge_preprocessor_guards(&binding.required_guards, &projection.required_guards)?;
8428 let mut projected = binding.clone();
8429 projected.required_guards = required_guards;
8430 project_using_binding_at_activation(projected, projection.activation_byte, root, source)
8431 })
8432 .collect()
8433}
8434
8435fn project_using_binding_at_activation(
8436 mut binding: OrdinaryTypeImport,
8437 activation: usize,
8438 root: Node<'_>,
8439 source: &str,
8440) -> Option<OrdinaryTypeImport> {
8441 let include = include_node_for_activation(root, activation)?;
8442 let include_namespace = enclosing_namespace_components(include, source);
8443 let mut declaration_namespace = include_namespace.clone();
8444 declaration_namespace.extend(binding.declaration_namespace);
8445 binding.declaration_namespace = declaration_namespace;
8446 binding.declaration_byte = activation;
8447 if let Some(prefix) = using_named_scope(include, source) {
8448 let mut projected = prefix;
8449 projected.extend(binding.namespace_scope.take().unwrap_or_default());
8450 binding.scope_depth = projected.len();
8451 binding.lexical_depth = projected.len();
8452 binding.namespace_scope = Some(projected);
8453 binding.scope_start = 0;
8454 binding.scope_end = usize::MAX;
8455 Some(binding)
8456 } else if let Some((start, end, depth)) = ordinary_using_scope(include) {
8457 binding.namespace_scope = None;
8458 binding.scope_start = start;
8459 binding.scope_end = end;
8460 binding.scope_depth = depth;
8461 binding.lexical_depth = include_namespace.len();
8462 Some(binding)
8463 } else {
8464 None
8465 }
8466}
8467
8468pub fn effective_using_bindings_for_name(
8469 visibility: &VisibilityIndex<'_>,
8470 imports: &OrdinaryTypeImportCell,
8471 file: &ProjectFile,
8472 root: Node<'_>,
8473 source: &str,
8474 name: &str,
8475) -> Arc<[OrdinaryTypeImport]> {
8476 imports
8477 .projection_cell(name)
8478 .get_or_init(|| {
8479 let project = visibility.project_using_index(|| build_project_using_index(visibility));
8480 let mut projected = Vec::new();
8481 for binding in project
8482 .ordinary_by_name
8483 .get(name)
8484 .into_iter()
8485 .flatten()
8486 .chain(project.directives.iter())
8487 {
8488 if !visibility.source_is_visible(file, &binding.source) {
8489 continue;
8490 }
8491 let Some(target_components) = using_binding_target_components_for_name(
8492 binding, project, visibility, file, name,
8493 ) else {
8494 continue;
8495 };
8496 let mut binding = binding.clone();
8497 binding.resolved_target_components = Some(target_components);
8498 projected.extend(project_using_bindings(
8499 binding, visibility, file, root, source,
8500 ));
8501 }
8502 Arc::from(projected)
8503 })
8504 .clone()
8505}
8506
8507pub fn initialized_ordinary_type_imports(
8508 root: Node<'_>,
8509 analyzer: &CppGraphSource<'_>,
8510 visibility: &VisibilityIndex<'_>,
8511 file: &ProjectFile,
8512 source: &str,
8513) -> OrdinaryTypeImportCell {
8514 let cell = visibility.ordinary_type_import_cell(file);
8515 let _ = (root, analyzer, source);
8516 cell
8517}
8518
8519fn root_node(mut node: Node<'_>) -> Node<'_> {
8520 while let Some(parent) = node.parent() {
8521 node = parent;
8522 }
8523 node
8524}
8525
8526fn effective_using_binding_active(
8527 binding: &OrdinaryTypeImport,
8528 node: Node<'_>,
8529 lexical_scope: &[String],
8530 source: &str,
8531 visibility: &VisibilityIndex<'_>,
8532 file: &ProjectFile,
8533) -> bool {
8534 binding.declaration_byte <= node.start_byte()
8535 && preprocessor_guard_environment(node, source)
8536 .is_some_and(|active| binding.required_guards.is_subset(&active))
8537 && visibility.preprocessor_guards_stable_between(
8538 file,
8539 0,
8540 node.start_byte(),
8541 &binding.required_guards,
8542 )
8543 && binding.namespace_scope.as_ref().map_or_else(
8544 || binding.scope_start <= node.start_byte() && node.end_byte() <= binding.scope_end,
8545 |namespace| lexical_scope.starts_with(namespace),
8546 )
8547}
8548
8549fn binding_type_candidates(
8550 binding: &OrdinaryTypeImport,
8551 active_bindings: &[&OrdinaryTypeImport],
8552 visibility: &VisibilityIndex<'_>,
8553 file: &ProjectFile,
8554 name: &str,
8555 direct_target: Option<&CodeUnit>,
8556 reference_byte: usize,
8557) -> Vec<(CodeUnit, Vec<String>)> {
8558 let Some(qualified) = binding.resolved_target_components.clone() else {
8559 return Vec::new();
8560 };
8561 let mut targets = Vec::new();
8562 match binding.target {
8563 EffectiveUsingTarget::Ordinary { .. } => targets.push(qualified),
8564 EffectiveUsingTarget::Namespace { .. } => {
8565 let mut stack = vec![qualified];
8566 let mut visited = HashSet::default();
8567 while let Some(namespace) = stack.pop() {
8568 if !visited.insert(namespace.clone()) {
8569 continue;
8570 }
8571 let mut target = namespace.clone();
8572 target.push(name.to_string());
8573 targets.push(target);
8574 stack.extend(active_bindings.iter().filter_map(|candidate| {
8575 (matches!(candidate.target, EffectiveUsingTarget::Namespace { .. })
8576 && candidate.namespace_scope.as_deref() == Some(namespace.as_slice()))
8577 .then(|| candidate.resolved_target_components.clone())
8578 .flatten()
8579 }));
8580 }
8581 }
8582 }
8583 targets
8584 .into_iter()
8585 .flat_map(|target| {
8586 let mut candidates = visibility
8587 .visible_identifier_candidates(file, name)
8588 .filter(|candidate| {
8589 (candidate.is_class() || is_type_alias(candidate))
8590 && macro_expanded_cpp_name_components(
8591 visibility,
8592 file,
8593 candidate,
8594 reference_byte,
8595 ) == target
8596 })
8597 .cloned()
8598 .collect::<Vec<_>>();
8599 if candidates.is_empty()
8600 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
8601 && let Some(target_unit) = direct_target
8602 {
8603 let expanded_target_name = macro_expanded_cpp_name_components(
8604 visibility,
8605 file,
8606 target_unit,
8607 reference_byte,
8608 );
8609 if (target_unit.is_class() || is_type_alias(target_unit))
8610 && expanded_target_name == target
8611 && visibility.external_type_candidate_visible_at(
8612 file,
8613 target_unit,
8614 reference_byte,
8615 )
8616 {
8617 candidates.push(target_unit.clone());
8618 }
8619 }
8620 if candidates.is_empty()
8621 && matches!(binding.target, EffectiveUsingTarget::Namespace { .. })
8622 && let Some(target_unit) = direct_target
8623 {
8624 let visible_types = visibility
8625 .visible_identifier_candidates(file, name)
8626 .filter(|candidate| candidate.is_class() || is_type_alias(candidate))
8627 .collect::<Vec<_>>();
8628 let uniquely_names_target = !visible_types.is_empty()
8629 && visible_types
8630 .iter()
8631 .all(|candidate| same_visible_symbol(candidate, target_unit));
8632 if uniquely_names_target {
8633 candidates.extend(visible_types.into_iter().cloned());
8634 }
8635 }
8636 candidates
8637 .into_iter()
8638 .map(move |candidate| (candidate, target.clone()))
8639 })
8640 .collect()
8641}
8642
8643fn macro_expanded_cpp_name_components(
8644 visibility: &VisibilityIndex<'_>,
8645 file: &ProjectFile,
8646 unit: &CodeUnit,
8647 reference_byte: usize,
8648) -> Vec<String> {
8649 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8650 brokk_bifrost_core::analyzer::Language::Cpp,
8651 &cpp_name_for(unit),
8652 )
8653 .into_iter()
8654 .flat_map(|component| {
8655 let Some(replacement) =
8656 visibility.object_macro_replacement_at(file, &component, reference_byte)
8657 else {
8658 return vec![component];
8659 };
8660 let expanded = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8661 brokk_bifrost_core::analyzer::Language::Cpp,
8662 &replacement,
8663 );
8664 if expanded.is_empty() {
8665 vec![component]
8666 } else {
8667 expanded
8668 }
8669 })
8670 .collect()
8671}
8672
8673#[allow(clippy::too_many_arguments)]
8674fn resolved_type_import(
8675 candidates: Vec<(CodeUnit, Vec<String>)>,
8676 lexical_depth: usize,
8677 is_direct: bool,
8678 analyzer: &CppGraphSource<'_>,
8679 visibility: &VisibilityIndex<'_>,
8680 file: &ProjectFile,
8681 direct_target: Option<&CodeUnit>,
8682) -> OrdinaryTypeImportResolution {
8683 let mut logical = Vec::<(CodeUnit, Vec<String>)>::new();
8684 for candidate in candidates {
8685 if !logical
8686 .iter()
8687 .any(|(existing, _)| same_logical_symbol(existing, &candidate.0))
8688 {
8689 logical.push(candidate);
8690 }
8691 }
8692 let selected = match logical.as_slice() {
8693 [] => return OrdinaryTypeImportResolution::Missing,
8694 [only] => only,
8695 several => {
8700 let units = several
8701 .iter()
8702 .map(|(unit, _)| unit)
8703 .collect::<Vec<&CodeUnit>>();
8704 let Some(spelling) = direct_target.and_then(|target| {
8705 visibility.same_fqn_type_spelling_for_target(analyzer, file, &units, target)
8706 }) else {
8707 return OrdinaryTypeImportResolution::Ambiguous { lexical_depth };
8708 };
8709 several
8710 .iter()
8711 .find(|(unit, _)| same_symbol(unit, spelling))
8712 .expect("the selected spelling is one of the imported candidates")
8713 }
8714 };
8715 OrdinaryTypeImportResolution::Resolved {
8716 target: selected.0.clone(),
8717 target_components: selected.1.clone(),
8718 lexical_depth,
8719 is_direct,
8720 }
8721}
8722
8723#[allow(clippy::too_many_arguments)]
8724fn ordinary_type_import_resolution(
8725 node: Node<'_>,
8726 components: &[String],
8727 global: bool,
8728 analyzer: &CppGraphSource<'_>,
8729 visibility: &VisibilityIndex<'_>,
8730 imports: &OrdinaryTypeImportCell,
8731 file: &ProjectFile,
8732 source: &str,
8733 lexical_scope: &[String],
8734 direct_target: Option<&CodeUnit>,
8735) -> OrdinaryTypeImportResolution {
8736 if global || components.len() != 1 {
8737 return OrdinaryTypeImportResolution::Missing;
8738 }
8739 let name = &components[0];
8740 let bindings =
8741 effective_using_bindings_for_name(visibility, imports, file, root_node(node), source, name);
8742 let active = bindings
8743 .iter()
8744 .filter(|binding| {
8745 effective_using_binding_active(binding, node, lexical_scope, source, visibility, file)
8746 })
8747 .collect::<Vec<_>>();
8748 let reference_guards = preprocessor_guard_environment(node, source);
8749 let transitive = bindings
8750 .iter()
8751 .filter(|binding| {
8752 binding.declaration_byte <= node.start_byte()
8753 && reference_guards
8754 .as_ref()
8755 .is_some_and(|active| binding.required_guards.is_subset(active))
8756 && visibility.preprocessor_guards_stable_between(
8757 file,
8758 0,
8759 node.start_byte(),
8760 &binding.required_guards,
8761 )
8762 && (binding.namespace_scope.is_some()
8763 || (binding.scope_start <= node.start_byte()
8764 && node.end_byte() <= binding.scope_end))
8765 })
8766 .collect::<Vec<_>>();
8767 let mut concrete_depths = active
8768 .iter()
8769 .filter(|binding| binding.namespace_scope.is_none())
8770 .map(|binding| binding.scope_depth)
8771 .collect::<Vec<_>>();
8772 concrete_depths.sort_unstable();
8773 concrete_depths.dedup();
8774 for depth in concrete_depths.into_iter().rev() {
8775 let at_tier = active
8776 .iter()
8777 .copied()
8778 .filter(|binding| binding.namespace_scope.is_none() && binding.scope_depth == depth);
8779 let direct = at_tier
8780 .clone()
8781 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
8782 .flat_map(|binding| {
8783 binding_type_candidates(
8784 binding,
8785 &transitive,
8786 visibility,
8787 file,
8788 name,
8789 direct_target,
8790 node.start_byte(),
8791 )
8792 })
8793 .collect::<Vec<_>>();
8794 if !direct.is_empty() {
8795 return resolved_type_import(
8796 direct,
8797 lexical_scope.len(),
8798 true,
8799 analyzer,
8800 visibility,
8801 file,
8802 direct_target,
8803 );
8804 }
8805 let directives = at_tier
8806 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
8807 .flat_map(|binding| {
8808 binding_type_candidates(
8809 binding,
8810 &transitive,
8811 visibility,
8812 file,
8813 name,
8814 direct_target,
8815 node.start_byte(),
8816 )
8817 })
8818 .collect::<Vec<_>>();
8819 if !directives.is_empty() {
8820 return resolved_type_import(
8821 directives,
8822 lexical_scope.len(),
8823 false,
8824 analyzer,
8825 visibility,
8826 file,
8827 direct_target,
8828 );
8829 }
8830 }
8831 for prefix_len in (0..=lexical_scope.len()).rev() {
8832 let tier = &lexical_scope[..prefix_len];
8833 let at_tier = active
8834 .iter()
8835 .copied()
8836 .filter(|binding| binding.namespace_scope.as_deref() == Some(tier));
8837 let direct = at_tier
8838 .clone()
8839 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Ordinary { .. }))
8840 .flat_map(|binding| {
8841 binding_type_candidates(
8842 binding,
8843 &transitive,
8844 visibility,
8845 file,
8846 name,
8847 direct_target,
8848 node.start_byte(),
8849 )
8850 })
8851 .collect::<Vec<_>>();
8852 if !direct.is_empty() {
8853 return resolved_type_import(
8854 direct,
8855 prefix_len,
8856 true,
8857 analyzer,
8858 visibility,
8859 file,
8860 direct_target,
8861 );
8862 }
8863 let directives = at_tier
8864 .filter(|binding| matches!(binding.target, EffectiveUsingTarget::Namespace { .. }))
8865 .flat_map(|binding| {
8866 binding_type_candidates(
8867 binding,
8868 &transitive,
8869 visibility,
8870 file,
8871 name,
8872 direct_target,
8873 node.start_byte(),
8874 )
8875 })
8876 .collect::<Vec<_>>();
8877 if !directives.is_empty() {
8878 return resolved_type_import(
8879 directives,
8880 prefix_len,
8881 false,
8882 analyzer,
8883 visibility,
8884 file,
8885 direct_target,
8886 );
8887 }
8888 }
8889 OrdinaryTypeImportResolution::Missing
8890}
8891
8892#[allow(clippy::too_many_arguments)]
8893pub fn resolve_type_components_lexically_at(
8894 node: Node<'_>,
8895 components: &[String],
8896 global: bool,
8897 analyzer: &CppGraphSource<'_>,
8898 visibility: &VisibilityIndex<'_>,
8899 ordinary_type_imports: &OrdinaryTypeImportCell,
8900 file: &ProjectFile,
8901 source: &str,
8902) -> LexicalTypeResolution {
8903 resolve_type_components_lexically_at_inner(
8904 node,
8905 components,
8906 global,
8907 analyzer,
8908 visibility,
8909 ordinary_type_imports,
8910 file,
8911 source,
8912 None,
8913 false,
8914 false,
8915 None,
8916 )
8917}
8918
8919#[allow(clippy::too_many_arguments)]
8920fn resolve_type_components_lexically_at_preserving_alias_with_scope_cache(
8921 node: Node<'_>,
8922 components: &[String],
8923 global: bool,
8924 analyzer: &CppGraphSource<'_>,
8925 visibility: &VisibilityIndex<'_>,
8926 ordinary_type_imports: &OrdinaryTypeImportCell,
8927 file: &ProjectFile,
8928 source: &str,
8929 scope_cache: Option<&LexicalScopeCache>,
8930) -> LexicalTypeResolution {
8931 resolve_type_components_lexically_at_inner(
8932 node,
8933 components,
8934 global,
8935 analyzer,
8936 visibility,
8937 ordinary_type_imports,
8938 file,
8939 source,
8940 None,
8941 false,
8942 true,
8943 scope_cache,
8944 )
8945}
8946
8947#[allow(clippy::too_many_arguments)]
8948fn resolve_type_components_lexically_at_for_target_with_scope_cache(
8949 node: Node<'_>,
8950 components: &[String],
8951 global: bool,
8952 analyzer: &CppGraphSource<'_>,
8953 visibility: &VisibilityIndex<'_>,
8954 ordinary_type_imports: &OrdinaryTypeImportCell,
8955 file: &ProjectFile,
8956 source: &str,
8957 target: &CodeUnit,
8958 apply_structured_prefilter: bool,
8959 scope_cache: Option<&LexicalScopeCache>,
8960) -> LexicalTypeResolution {
8961 resolve_type_components_lexically_at_inner(
8962 node,
8963 components,
8964 global,
8965 analyzer,
8966 visibility,
8967 ordinary_type_imports,
8968 file,
8969 source,
8970 Some(target),
8971 apply_structured_prefilter,
8972 false,
8973 scope_cache,
8974 )
8975}
8976
8977#[allow(clippy::too_many_arguments)]
8978fn resolve_type_components_lexically_at_preserving_alias_with_recovered_scope(
8979 node: Node<'_>,
8980 components: &[String],
8981 global: bool,
8982 analyzer: &CppGraphSource<'_>,
8983 visibility: &VisibilityIndex<'_>,
8984 ordinary_type_imports: &OrdinaryTypeImportCell,
8985 file: &ProjectFile,
8986 source: &str,
8987 recovered_scope: &[String],
8988) -> LexicalTypeResolution {
8989 resolve_type_components_lexically_at_scoped(
8990 node,
8991 components,
8992 global,
8993 analyzer,
8994 visibility,
8995 ordinary_type_imports,
8996 file,
8997 source,
8998 None,
8999 false,
9000 true,
9001 recovered_scope.to_vec(),
9002 )
9003}
9004
9005#[allow(clippy::too_many_arguments)]
9006fn resolve_type_components_lexically_at_for_target_with_recovered_scope(
9007 node: Node<'_>,
9008 components: &[String],
9009 global: bool,
9010 analyzer: &CppGraphSource<'_>,
9011 visibility: &VisibilityIndex<'_>,
9012 ordinary_type_imports: &OrdinaryTypeImportCell,
9013 file: &ProjectFile,
9014 source: &str,
9015 target: &CodeUnit,
9016 apply_structured_prefilter: bool,
9017 recovered_scope: &[String],
9018) -> LexicalTypeResolution {
9019 resolve_type_components_lexically_at_scoped(
9020 node,
9021 components,
9022 global,
9023 analyzer,
9024 visibility,
9025 ordinary_type_imports,
9026 file,
9027 source,
9028 Some(target),
9029 apply_structured_prefilter,
9030 false,
9031 recovered_scope.to_vec(),
9032 )
9033}
9034
9035#[allow(clippy::too_many_arguments)]
9036fn resolve_type_components_lexically_at_inner(
9037 node: Node<'_>,
9038 components: &[String],
9039 global: bool,
9040 analyzer: &CppGraphSource<'_>,
9041 visibility: &VisibilityIndex<'_>,
9042 ordinary_type_imports: &OrdinaryTypeImportCell,
9043 file: &ProjectFile,
9044 source: &str,
9045 direct_target: Option<&CodeUnit>,
9046 apply_structured_prefilter: bool,
9047 preserve_alias: bool,
9048 scope_cache: Option<&LexicalScopeCache>,
9049) -> LexicalTypeResolution {
9050 let lexical_scope = if global {
9051 Vec::new()
9052 } else {
9053 match cached_enclosing_lexical_scope_components_with_unresolved_owner(
9054 node,
9055 analyzer,
9056 visibility,
9057 file,
9058 source,
9059 true,
9060 recovered_macro_decorated_declarator_type(node)
9061 == Some(RecoveredDeclaratorTypeContext::FunctionDefinition),
9062 scope_cache,
9063 ) {
9064 LexicalScopeResolution::Resolved(scope) => scope,
9065 LexicalScopeResolution::Ambiguous => return LexicalTypeResolution::Ambiguous,
9066 LexicalScopeResolution::Missing => return LexicalTypeResolution::Missing,
9067 }
9068 };
9069 resolve_type_components_lexically_at_scoped(
9070 node,
9071 components,
9072 global,
9073 analyzer,
9074 visibility,
9075 ordinary_type_imports,
9076 file,
9077 source,
9078 direct_target,
9079 apply_structured_prefilter,
9080 preserve_alias,
9081 lexical_scope,
9082 )
9083}
9084
9085#[allow(clippy::too_many_arguments)]
9086fn resolve_type_components_lexically_at_scoped(
9087 node: Node<'_>,
9088 components: &[String],
9089 global: bool,
9090 analyzer: &CppGraphSource<'_>,
9091 visibility: &VisibilityIndex<'_>,
9092 ordinary_type_imports: &OrdinaryTypeImportCell,
9093 file: &ProjectFile,
9094 source: &str,
9095 direct_target: Option<&CodeUnit>,
9096 apply_structured_prefilter: bool,
9097 preserve_alias: bool,
9098 mut lexical_scope: Vec<String>,
9099) -> LexicalTypeResolution {
9100 if apply_structured_prefilter
9101 && direct_target.is_some()
9102 && !preserve_alias
9103 && !global
9104 && components.len() == 1
9105 && !visibility.coarse_unqualified_type_reference_may_resolve(file, &components[0])
9106 {
9107 return LexicalTypeResolution::Missing;
9108 }
9109 if !global
9110 && components.len() == 1
9111 && has_malformed_wrapper_function_definition_ancestor(node)
9118 && let Some(target) = direct_target
9119 && let Some(indexed_namespace) =
9120 visibility.target_preserving_reference_namespace(analyzer, file, &components[0], target)
9121 && (lexical_scope.is_empty() || !lexical_scope.starts_with(&indexed_namespace))
9122 && lexical_scope
9123 .last()
9124 .is_none_or(|last| last != &components[0])
9125 {
9126 lexical_scope = indexed_namespace;
9131 }
9132 if apply_structured_prefilter
9133 && let Some(target) = direct_target
9134 && !preserve_alias
9135 && !visibility.structured_type_reference_may_resolve_to_target(
9136 analyzer,
9137 file,
9138 components,
9139 global,
9140 &lexical_scope,
9141 target,
9142 )
9143 {
9144 return LexicalTypeResolution::Missing;
9145 }
9146 let normal = if preserve_alias {
9147 visibility.resolve_type_components_lexically_for_forward(
9148 analyzer,
9149 file,
9150 components,
9151 global,
9152 &lexical_scope,
9153 )
9154 } else {
9155 direct_target.map_or_else(
9156 || {
9157 visibility.resolve_type_components_lexically(
9158 analyzer,
9159 file,
9160 components,
9161 global,
9162 &lexical_scope,
9163 )
9164 },
9165 |target| {
9166 visibility.resolve_type_components_lexically_for_target(
9167 analyzer,
9168 file,
9169 components,
9170 global,
9171 &lexical_scope,
9172 target,
9173 )
9174 },
9175 )
9176 };
9177 let normal = match normal {
9178 LexicalTypeResolution::Resolved { ref unit, .. }
9179 if !visibility
9180 .external_type_candidate_visible_in_context(analyzer, file, unit, node) =>
9181 {
9182 LexicalTypeResolution::Missing
9183 }
9184 resolution => resolution,
9185 };
9186 let normal_depth = match &normal {
9187 LexicalTypeResolution::Resolved { components, .. } => {
9188 Some(components.len().saturating_sub(1))
9189 }
9190 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
9191 };
9192 match ordinary_type_import_resolution(
9198 node,
9199 components,
9200 global,
9201 analyzer,
9202 visibility,
9203 ordinary_type_imports,
9204 file,
9205 source,
9206 &lexical_scope,
9207 direct_target,
9208 ) {
9209 OrdinaryTypeImportResolution::Missing => normal,
9210 OrdinaryTypeImportResolution::Resolved {
9211 lexical_depth,
9212 is_direct,
9213 ..
9214 } if matches!(&normal, LexicalTypeResolution::Ambiguous)
9215 || normal_depth.is_some_and(|depth| {
9216 depth > lexical_depth || (!is_direct && depth == lexical_depth)
9217 }) =>
9218 {
9219 normal
9220 }
9221 OrdinaryTypeImportResolution::Resolved {
9222 target,
9223 target_components,
9224 ..
9225 } => visibility.resolve_imported_type_candidate(
9226 analyzer,
9227 file,
9228 &target,
9229 &target_components,
9230 direct_target,
9231 preserve_alias,
9232 ),
9233 OrdinaryTypeImportResolution::Ambiguous { lexical_depth }
9234 if normal_depth.is_some_and(|depth| depth > lexical_depth) =>
9235 {
9236 normal
9237 }
9238 OrdinaryTypeImportResolution::Ambiguous { .. } => LexicalTypeResolution::Ambiguous,
9239 }
9240}
9241
9242fn same_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9243 matches!(
9244 structured_owner_context_resolution(node, ctx),
9245 StructuredOwnerContextResolution::SelfTarget
9246 | StructuredOwnerContextResolution::InheritedTarget
9247 )
9248}
9249
9250fn inherited_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9254 matches!(
9255 structured_owner_context_resolution(node, ctx),
9256 StructuredOwnerContextResolution::InheritedTarget
9257 )
9258}
9259
9260fn known_non_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9261 matches!(
9262 structured_owner_context_resolution(node, ctx),
9263 StructuredOwnerContextResolution::NonTarget
9264 )
9265}
9266
9267fn out_of_line_target_owner_context(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9268 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9269 return false;
9270 };
9271 let mut current = node.parent();
9272 while let Some(parent) = current {
9273 if parent.kind() == "function_definition" {
9274 let Some(owner_lookup) = function_definition_owner_lookup_node(parent) else {
9275 return false;
9276 };
9277 if let Some(owners) = out_of_line_member_definition_owner(
9278 &ctx.analyzer,
9279 ctx.visibility,
9280 ctx.file,
9281 ctx.source,
9282 owner_lookup,
9283 ) && let Some((_, owner)) = owners.innermost()
9284 {
9285 return receiver_owner_matches_target(owner, target_owner, node.start_byte(), ctx);
9286 }
9287 if let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx) {
9288 return receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx);
9289 }
9290 return false;
9291 }
9292 current = parent.parent();
9293 }
9294 false
9295}
9296
9297#[derive(Clone, Copy)]
9298enum StructuredOwnerContextResolution {
9299 SelfTarget,
9302 InheritedTarget,
9307 NonTarget,
9308 Ambiguous,
9309 Missing,
9310}
9311
9312fn structured_owner_context_resolution(
9313 node: Node<'_>,
9314 ctx: &ScanCtx<'_>,
9315) -> StructuredOwnerContextResolution {
9316 let Some(target_owner) = ctx.spec.owner.as_ref() else {
9317 return StructuredOwnerContextResolution::Missing;
9318 };
9319 let Some(enclosing_owner) = structured_enclosing_owner(node, ctx) else {
9320 return StructuredOwnerContextResolution::Missing;
9321 };
9322 if receiver_owner_matches_target(&enclosing_owner, target_owner, node.start_byte(), ctx) {
9323 return StructuredOwnerContextResolution::SelfTarget;
9324 }
9325 let member_owner = cached_declaring_member_owner(&enclosing_owner, ctx);
9328 match member_owner {
9329 EnclosingMemberOwnerResolution::Owner(owner)
9330 if receiver_owner_matches_target(&owner, target_owner, node.start_byte(), ctx) =>
9331 {
9332 StructuredOwnerContextResolution::InheritedTarget
9333 }
9334 EnclosingMemberOwnerResolution::Owner(_) => StructuredOwnerContextResolution::NonTarget,
9335 EnclosingMemberOwnerResolution::Ambiguous => StructuredOwnerContextResolution::Ambiguous,
9336 EnclosingMemberOwnerResolution::Missing => StructuredOwnerContextResolution::Missing,
9337 }
9338}
9339
9340fn cached_declaring_member_owner(
9341 receiver_owner: &CodeUnit,
9342 ctx: &ScanCtx<'_>,
9343) -> EnclosingMemberOwnerResolution {
9344 if let Some(cached) = ctx.member_owner_cache.borrow().get(receiver_owner).cloned() {
9345 return cached;
9346 }
9347 let resolved = resolve_declaring_member_owner(
9348 &ctx.analyzer,
9349 ctx.visibility,
9350 ctx.file,
9351 receiver_owner,
9352 &ctx.spec.member_name,
9353 );
9354 ctx.member_owner_cache
9355 .borrow_mut()
9356 .insert(receiver_owner.clone(), resolved.clone());
9357 resolved
9358}
9359
9360fn structured_enclosing_owner(node: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9361 if (has_recovered_class_shape_ancestor(node)
9366 || has_malformed_wrapper_function_definition_ancestor(node))
9367 && let Some(owner) = cached_indexed_enclosing_class_owner(node, ctx)
9368 {
9369 return Some(owner);
9370 }
9371 let mut current = node.parent();
9372 while let Some(parent) = current {
9373 if parent.kind() == "function_definition" {
9374 let owner_lookup = function_definition_owner_lookup_node(parent);
9375 if let Some(owner_lookup) = owner_lookup
9376 && let Some(owners) = out_of_line_member_definition_owner(
9377 &ctx.analyzer,
9378 ctx.visibility,
9379 ctx.file,
9380 ctx.source,
9381 owner_lookup,
9382 )
9383 && let Some((_, owner)) = owners.innermost()
9384 {
9385 return Some(owner.clone());
9386 }
9387 if let Some(owner) = cached_indexed_enclosing_class_owner(parent, ctx) {
9388 return Some(owner);
9389 }
9390 if let Some(owner) = enclosing_context(parent, ctx)
9391 .owner
9392 .filter(|owner| owner.is_class())
9393 {
9394 return Some(owner);
9395 }
9396 if let Some(owner_lookup) = owner_lookup
9397 && let Some(owner) = target_guided_out_of_line_owner(owner_lookup, ctx)
9398 {
9399 return Some(owner);
9400 }
9401 break;
9402 }
9403 current = parent.parent();
9404 }
9405 enclosing_context(node, ctx)
9406 .owner
9407 .filter(|owner| owner.is_class())
9408}
9409
9410fn target_guided_out_of_line_owner(function: Node<'_>, ctx: &ScanCtx<'_>) -> Option<CodeUnit> {
9411 let target_owner = ctx.spec.owner.as_ref()?;
9412 let (owner_components, _) = qualified_callable_owner_components(function, ctx.source)?;
9413 let owner_name = owner_components.last()?;
9414 let mut candidates = Vec::new();
9415 for candidate in ctx
9416 .visibility
9417 .visible_identifier_candidates(ctx.file, owner_name)
9418 .filter(|candidate| candidate.is_class())
9419 {
9420 let components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9421 brokk_bifrost_core::analyzer::Language::Cpp,
9422 &cpp_name_for(candidate),
9423 );
9424 if !components.ends_with(&owner_components)
9425 || candidates
9426 .iter()
9427 .any(|existing| same_logical_symbol(existing, candidate))
9428 {
9429 continue;
9430 }
9431 candidates.push(candidate.clone());
9432 }
9433 let [candidate] = candidates.as_slice() else {
9434 return None;
9435 };
9436 (same_logical_symbol(candidate, target_owner)
9437 && target_group_contains_owner_peer(candidate, ctx))
9438 .then(|| candidate.clone())
9439}