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