1use crate::call_match::{
2 CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::declarations::{
5 cpp_export_macro_token, cpp_field_declaration_linkage, cpp_template_term, node_text,
6 normalize_cpp_whitespace, recovered_exported_class_has_body,
7};
8use crate::graph::CppGraphSource;
9use crate::graph::extractor::ScanCtx;
10use crate::graph_support::CppSource;
11use crate::imports::{
12 IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
13};
14use brokk_bifrost_core::analyzer::model::{
15 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
16 CppTemplateParameterMetadata, CppTemplateTerm,
17};
18use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
19use brokk_bifrost_core::analyzer::tree_walk::node_for_exact_range;
20use brokk_bifrost_core::analyzer::usages::common::same_node;
21use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
22use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
23use brokk_bifrost_core::cancellation::CancellationToken;
24use brokk_bifrost_core::hash::{HashMap, HashSet};
25use std::borrow::Cow;
26#[cfg(any(test, feature = "test-support"))]
27use std::cell::Cell;
28use std::collections::BTreeSet;
29use std::hash::Hash;
30#[cfg(any(test, feature = "test-support"))]
31use std::sync::atomic::{AtomicUsize, Ordering};
32use std::sync::{Arc, Mutex, OnceLock, RwLock};
33use std::thread::ThreadId;
34use tree_sitter::{Node, Parser, Tree};
35
36#[derive(Clone, Copy, PartialEq, Eq)]
37pub enum TargetKind {
38 Type,
39 Constructor,
40 FreeFunction,
41 Method,
42 GlobalField,
43 MemberField,
44}
45
46pub enum LexicalTypeResolution {
47 Resolved {
48 unit: CodeUnit,
49 components: Vec<String>,
50 candidates: Vec<CodeUnit>,
51 },
52 Ambiguous,
53 Missing,
54}
55
56#[derive(Clone, Copy)]
57enum TypeCandidateResolution<'a> {
58 Canonical,
59 PreserveAlias,
60 PreserveTarget(&'a CodeUnit),
61}
62
63pub enum LexicalCallableValueResolution {
64 Type(CodeUnit),
65 FreeFunction(CodeUnit),
66 Ambiguous,
67 Missing,
68}
69
70pub enum UsingEnumMemberResolution {
71 Resolved { owner: CodeUnit, member: CodeUnit },
72 Ambiguous,
73 Missing,
74}
75
76pub enum NamespaceValueResolution {
77 Resolved,
78 Ambiguous,
79 Missing,
80}
81
82pub fn resolve_namespace_value(
83 analyzer: &CppGraphSource<'_>,
84 visibility: &VisibilityIndex<'_>,
85 file: &ProjectFile,
86 namespace: &str,
87 name: &str,
88 before_byte: usize,
89) -> NamespaceValueResolution {
90 let mut matches = Vec::new();
91 for candidate in visibility.visible_identifier_candidates(file, name) {
92 if type_owner_of(analyzer, candidate).is_some()
93 || candidate.package_name() != namespace
94 || (candidate.source() == file
95 && !analyzer
96 .ranges(candidate)
97 .iter()
98 .any(|range| range.start_byte < before_byte))
99 || matches
100 .iter()
101 .any(|existing| same_visible_symbol(existing, candidate))
102 {
103 continue;
104 }
105 matches.push(candidate.clone());
106 if matches.len() > 1 {
107 return NamespaceValueResolution::Ambiguous;
108 }
109 }
110 matches
111 .pop()
112 .map(|_| NamespaceValueResolution::Resolved)
113 .unwrap_or(NamespaceValueResolution::Missing)
114}
115
116pub(crate) struct ScopedUsingEnumOwners {
117 scopes: Vec<Vec<CodeUnit>>,
118}
119
120pub(crate) struct SemanticUsingEnumOwners {
125 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
126 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
127}
128
129pub(crate) enum SemanticUsingEnumMemberResolution {
130 Class(UsingEnumMemberResolution),
131 Namespace(UsingEnumMemberResolution),
132 Missing,
133}
134
135impl SemanticUsingEnumOwners {
136 pub(crate) fn new() -> Self {
137 Self {
138 class_imports: HashMap::default(),
139 namespace_imports: HashMap::default(),
140 }
141 }
142
143 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
144 let imports = self.class_imports.entry(class).or_default();
145 if !imports
146 .iter()
147 .any(|existing| same_visible_symbol(existing, &enum_owner))
148 {
149 imports.push(enum_owner);
150 }
151 }
152
153 pub fn import_namespace(
154 &mut self,
155 namespace: Vec<String>,
156 declaration_byte: usize,
157 enum_owner: CodeUnit,
158 ) {
159 let imports = self.namespace_imports.entry(namespace).or_default();
160 if !imports
161 .iter()
162 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
163 {
164 imports.push((declaration_byte, enum_owner));
165 }
166 }
167
168 pub fn resolve_member(
169 &self,
170 visibility: &VisibilityIndex<'_>,
171 file: &ProjectFile,
172 class: Option<&CodeUnit>,
173 namespace: &[String],
174 before_byte: usize,
175 name: &str,
176 ) -> SemanticUsingEnumMemberResolution {
177 if let Some(class) = class
178 && let Some((_, imports)) = self
179 .class_imports
180 .iter()
181 .find(|(owner, _)| same_visible_symbol(owner, class))
182 {
183 let resolution =
184 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
185 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
186 return SemanticUsingEnumMemberResolution::Class(resolution);
187 }
188 }
189 for prefix_len in (0..=namespace.len()).rev() {
190 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
191 continue;
192 };
193 let owners = imports
194 .iter()
195 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
196 .map(|(_, owner)| owner);
197 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
198 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
199 return SemanticUsingEnumMemberResolution::Namespace(resolution);
200 }
201 }
202 SemanticUsingEnumMemberResolution::Missing
203 }
204}
205
206fn resolve_using_enum_member_for_owners<'a>(
207 visibility: &VisibilityIndex<'_>,
208 file: &ProjectFile,
209 owners: impl IntoIterator<Item = &'a CodeUnit>,
210 name: &str,
211) -> UsingEnumMemberResolution {
212 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
213 for owner in owners {
214 for member in visibility.visible_members_for_owner_name(file, owner, name) {
215 if !member.is_field()
216 || matches.iter().any(|(existing_owner, existing_member)| {
217 same_visible_symbol(existing_owner, owner)
218 && same_visible_symbol(existing_member, member)
219 })
220 {
221 continue;
222 }
223 matches.push((owner.clone(), member.clone()));
224 }
225 }
226 match matches.len() {
227 0 => UsingEnumMemberResolution::Missing,
228 1 => {
229 let (owner, member) = matches.pop().expect("one using-enum match");
230 UsingEnumMemberResolution::Resolved { owner, member }
231 }
232 _ => UsingEnumMemberResolution::Ambiguous,
233 }
234}
235
236impl ScopedUsingEnumOwners {
237 pub(crate) fn new() -> Self {
238 Self {
239 scopes: vec![Vec::new()],
240 }
241 }
242
243 pub fn enter_scope(&mut self) {
244 self.scopes.push(Vec::new());
245 }
246
247 pub fn exit_scope(&mut self) {
248 if self.scopes.len() > 1 {
249 self.scopes.pop();
250 }
251 }
252
253 pub fn import(&mut self, owner: CodeUnit) {
254 let scope = self
255 .scopes
256 .last_mut()
257 .expect("using-enum scope stack is never empty");
258 if !scope
259 .iter()
260 .any(|existing| same_visible_symbol(existing, &owner))
261 {
262 scope.push(owner);
263 }
264 }
265
266 pub fn resolve_member(
267 &self,
268 visibility: &VisibilityIndex<'_>,
269 file: &ProjectFile,
270 name: &str,
271 ) -> UsingEnumMemberResolution {
272 for scope in self.scopes.iter().rev() {
273 let resolution =
274 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
275 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
276 return resolution;
277 }
278 }
279 UsingEnumMemberResolution::Missing
280 }
281}
282
283#[derive(Clone)]
284pub struct TargetSpec {
285 pub target: CodeUnit,
286 pub kind: TargetKind,
287 pub owner: Option<CodeUnit>,
288 pub member_name: String,
289 pub callable_arity: Option<CallableArity>,
290 pub activated_callable_arities: Vec<ActivatedCallableArity>,
291 pub param_types: Option<Vec<String>>,
292 pub enum_owner_kind: EnumOwnerKind,
293 pub owner_is_forward_declaration: bool,
294}
295
296#[derive(Clone, Copy)]
297pub struct ActivatedCallableArity {
298 pub activation_byte: usize,
299 pub arity: CallableArity,
300}
301
302#[derive(Debug, PartialEq, Eq, Hash)]
303pub struct TypeScanKey {
304 target: LogicalSymbolKey,
305 member_name: String,
306}
307
308#[derive(Clone, Debug, PartialEq, Eq, Hash)]
309struct LogicalSymbolKey {
310 kind: CodeUnitType,
311 fq_name: String,
312 signature: Option<String>,
313}
314
315struct ResolvedTypeOwner {
316 unit: CodeUnit,
317 is_forward_declaration: bool,
318}
319
320#[derive(Clone, Copy, PartialEq, Eq)]
321pub enum EnumOwnerKind {
322 Scoped,
323 Unscoped,
324 NonEnum,
325}
326
327impl TargetSpec {
328 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
329 (self.kind == TargetKind::Type).then(|| TypeScanKey {
330 target: logical_symbol_key(&self.target),
331 member_name: self.member_name.clone(),
332 })
333 }
334
335 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
336 if target.is_class() {
337 return Some(Self::new(
338 target.clone(),
339 TargetKind::Type,
340 Some(target.clone()),
341 target.identifier().to_string(),
342 None,
343 None,
344 ));
345 }
346
347 if target.is_field() {
348 let owner = type_owner_of(analyzer, target);
354 let kind = if owner.is_some() {
355 TargetKind::MemberField
356 } else {
357 TargetKind::GlobalField
358 };
359 let enum_owner_kind = owner
360 .as_ref()
361 .map(|owner| classify_enum_owner(analyzer, owner))
362 .unwrap_or(EnumOwnerKind::NonEnum);
363 let mut spec = Self::new(
364 target.clone(),
365 kind,
366 owner,
367 target.identifier().to_string(),
368 None,
369 None,
370 );
371 spec.enum_owner_kind = enum_owner_kind;
372 return Some(spec);
373 }
374
375 if target.is_function() {
376 let owner_resolution = type_owner_resolution(analyzer, target)
379 .or_else(|| target_forward_owner_resolution(analyzer, target));
380 let owner_is_forward_declaration = owner_resolution
381 .as_ref()
382 .is_some_and(|owner| owner.is_forward_declaration);
383 let owner = owner_resolution.map(|owner| owner.unit);
384 let kind = if owner.as_ref().is_some_and(|owner| {
385 target.identifier() == owner.identifier()
386 || analyzer
387 .cpp
388 .and_then(|cpp| cpp.template_metadata(owner))
389 .is_some_and(|metadata| metadata.primary_name == target.identifier())
390 }) {
391 TargetKind::Constructor
392 } else if owner.is_some() {
393 TargetKind::Method
394 } else {
395 TargetKind::FreeFunction
396 };
397 let mut spec = Self::new(
398 target.clone(),
399 kind,
400 owner,
401 target.identifier().to_string(),
402 Some(cpp_callable_arity(analyzer, target)),
403 target.signature().and_then(cpp_signature_param_types),
404 );
405 spec.owner_is_forward_declaration = owner_is_forward_declaration;
406 return Some(spec);
407 }
408
409 None
410 }
411
412 pub fn with_visible_callable_arities<'a>(
413 &'a self,
414 analyzer: &CppGraphSource<'_>,
415 cpp: &dyn CppSource,
416 visibility: &VisibilityIndex<'_>,
417 file: &ProjectFile,
418 prepared: &PreparedSyntaxTree,
419 ) -> Cow<'a, Self> {
420 let macro_parameter_arity =
421 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
422 let activated_callable_arities =
423 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
424 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
425 return Cow::Borrowed(self);
426 }
427 let mut effective = self.clone();
428 if let Some(macro_parameter_arity) = macro_parameter_arity {
429 effective.callable_arity = Some(macro_parameter_arity);
430 }
431 effective.activated_callable_arities = activated_callable_arities;
432 Cow::Owned(effective)
433 }
434
435 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
436 let base = self.callable_arity?;
437 Some(
438 self.activated_callable_arities
439 .iter()
440 .filter(|candidate| candidate.activation_byte <= byte)
441 .fold(base, |arity, candidate| {
442 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
443 }),
444 )
445 }
446
447 pub fn new(
448 target: CodeUnit,
449 kind: TargetKind,
450 owner: Option<CodeUnit>,
451 member_name: String,
452 callable_arity: Option<CallableArity>,
453 param_types: Option<Vec<String>>,
454 ) -> Self {
455 Self {
456 target,
457 kind,
458 owner,
459 member_name,
460 callable_arity,
461 activated_callable_arities: Vec::new(),
462 param_types,
463 enum_owner_kind: EnumOwnerKind::NonEnum,
464 owner_is_forward_declaration: false,
465 }
466 }
467}
468
469fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
470 LogicalSymbolKey {
471 kind: unit.kind(),
472 fq_name: unit.fq_name(),
473 signature: unit.signature().map(str::to_string),
474 }
475}
476
477fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
478 let classify = |source: &str| {
479 let source = source.trim_start();
480 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
481 Some(EnumOwnerKind::Scoped)
482 } else if source.starts_with("enum ") {
483 Some(EnumOwnerKind::Unscoped)
484 } else {
485 None
486 }
487 };
488 owner
489 .signature()
490 .and_then(classify)
491 .or_else(|| {
492 analyzer
493 .get_source(owner, false)
494 .as_deref()
495 .and_then(classify)
496 })
497 .unwrap_or(EnumOwnerKind::NonEnum)
498}
499
500#[derive(Clone, PartialEq, Eq, Hash)]
501pub struct CppScanBinding {
502 pub unit: Option<CodeUnit>,
503 pub type_name: Option<String>,
504 pub indirection: i32,
505}
506
507impl CppScanBinding {
508 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
509 Self {
510 type_name: Some(cpp_name_for(&unit)),
511 unit: Some(unit),
512 indirection,
513 }
514 }
515
516 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
517 Self {
518 type_name: Some(type_name),
519 unit,
520 indirection,
521 }
522 }
523
524 pub fn as_arg_type(&self) -> Option<CppArgType> {
525 let name = self
526 .type_name
527 .clone()
528 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
529 Some(CppArgType {
530 name,
531 unit: self.unit.clone(),
532 indirection: self.indirection,
533 pointee_const: false,
534 })
535 }
536}
537
538type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
539type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
540pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
541pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
542type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
543pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
544type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
545
546#[derive(Clone, Default)]
547pub struct MacroEnvironment {
548 bindings: HashMap<String, MacroBinding>,
549 unknown_names: bool,
550 applied_pragma_once_files: HashSet<ProjectFile>,
551 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
552}
553
554#[derive(Default)]
555pub struct MacroEnvironmentCursor {
556 frontier: usize,
557 environment: Arc<MacroEnvironment>,
558}
559
560impl MacroEnvironment {
561 fn binding(&self, name: &str) -> Option<&MacroBinding> {
562 self.bindings.get(name)
563 }
564
565 fn may_bind(&self, name: &str) -> bool {
566 self.bindings.contains_key(name) || self.unknown_names
567 }
568
569 fn insert(&mut self, name: String, binding: MacroBinding) {
570 self.bindings.insert(name, binding);
571 }
572
573 fn remove(&mut self, name: &str) {
574 self.bindings.remove(name);
575 }
576
577 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
578 for binding in self.bindings.values_mut() {
579 *binding = MacroBinding::ambiguous(source, byte);
580 }
581 self.unknown_names = true;
582 }
583}
584
585#[derive(Clone)]
586pub enum EffectiveUsingTarget {
587 Ordinary {
588 name: String,
589 target_components: Vec<String>,
590 global: bool,
591 },
592 Namespace {
593 namespace_components: Vec<String>,
594 global: bool,
595 },
596}
597
598#[derive(Clone)]
599pub struct OrdinaryTypeImport {
600 pub target: EffectiveUsingTarget,
601 pub source: ProjectFile,
602 pub declaration_byte: usize,
603 pub scope_start: usize,
604 pub scope_end: usize,
605 pub scope_depth: usize,
606 pub lexical_depth: usize,
607 pub declaration_namespace: Vec<String>,
608 pub namespace_scope: Option<Vec<String>>,
609 pub resolved_target_components: Option<Vec<String>>,
610 pub required_guards: HashSet<PreprocessorGuard>,
611}
612
613#[derive(Clone)]
614pub struct ConditionalIncludeProjection {
615 pub activation_byte: usize,
616 pub required_guards: HashSet<PreprocessorGuard>,
617}
618
619#[derive(Default)]
620pub struct SourceUsingIndex {
621 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
622 pub directives: Vec<OrdinaryTypeImport>,
623}
624
625#[derive(Default)]
626pub struct ProjectUsingIndex {
627 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
628 pub directives: Vec<OrdinaryTypeImport>,
629}
630
631type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
632
633pub struct EffectiveUsingIndex {
634 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
635}
636
637impl EffectiveUsingIndex {
638 fn new(_root: ProjectFile) -> Self {
639 Self {
640 projected_by_name: Mutex::new(HashMap::default()),
641 }
642 }
643
644 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
645 self.projected_by_name
646 .lock()
647 .expect("C++ effective-using projection cache poisoned")
648 .entry(name.to_string())
649 .or_default()
650 .clone()
651 }
652}
653
654pub enum OrdinaryTypeImportResolution {
655 Resolved {
656 target: CodeUnit,
657 target_components: Vec<String>,
658 lexical_depth: usize,
659 is_direct: bool,
660 },
661 Ambiguous {
662 lexical_depth: usize,
663 },
664 Missing,
665}
666
667type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
668type ConditionalIncludeProjectionCache =
669 HashMap<(ProjectFile, ProjectFile), Arc<[ConditionalIncludeProjection]>>;
670type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
671type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
672type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
673
674pub struct VisibilityIndex<'a> {
685 cpp: &'a dyn CppSource,
686 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
687 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
688 global_field_internal_linkage: HashMap<CodeUnit, bool>,
689 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
690 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
691 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
692 visible_parser_alias_target_names:
693 Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
694 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
695 project_using_index: OnceLock<ProjectUsingIndex>,
696 callable_reference_specs:
697 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
698 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
699 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
700 #[cfg(any(test, feature = "test-support"))]
701 include_activation_build_count: AtomicUsize,
702 #[cfg(any(test, feature = "test-support"))]
703 using_donor_activation_count: AtomicUsize,
704 #[cfg(any(test, feature = "test-support"))]
705 using_namespace_lookup_count: AtomicUsize,
706 #[cfg(any(test, feature = "test-support"))]
707 using_name_candidate_inspection_count: AtomicUsize,
708 #[cfg(any(test, feature = "test-support"))]
709 using_source_index_walk_count: AtomicUsize,
710 #[cfg(any(test, feature = "test-support"))]
711 callable_reference_spec_build_count: AtomicUsize,
712 #[cfg(any(test, feature = "test-support"))]
713 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
714 #[cfg(any(test, feature = "test-support"))]
715 visible_parser_alias_name_set_build_count: AtomicUsize,
716 #[cfg(any(test, feature = "test-support"))]
717 visible_parser_alias_target_names_build_count: AtomicUsize,
718 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
719 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
720 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
721 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
722 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
723 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
724 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
725 pub macro_environment_cursors:
731 Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
732 macro_replacements: Mutex<MacroReplacementCache>,
733 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
734 #[cfg(any(test, feature = "test-support"))]
735 pub macro_replacement_parse_count: AtomicUsize,
736 #[cfg(any(test, feature = "test-support"))]
737 pub macro_event_application_count: AtomicUsize,
738 #[cfg(any(test, feature = "test-support"))]
739 pub macro_environment_copy_count: AtomicUsize,
740 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
741 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
742 #[cfg(any(test, feature = "test-support"))]
743 qualified_candidate_inspections: AtomicUsize,
744 #[cfg(any(test, feature = "test-support"))]
745 target_preserving_type_resolution_count: AtomicUsize,
746}
747
748#[derive(Clone, Debug, PartialEq, Eq, Hash)]
749pub enum PreprocessorGuard {
750 Defined(String),
751 Undefined(String),
752 Expression(String),
753 NegatedExpression(String),
754 Constant(bool),
755}
756
757impl PreprocessorGuard {
758 fn negated(&self) -> Self {
759 match self {
760 Self::Defined(name) => Self::Undefined(name.clone()),
761 Self::Undefined(name) => Self::Defined(name.clone()),
762 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
763 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
764 Self::Constant(value) => Self::Constant(!value),
765 }
766 }
767
768 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
769 match self {
770 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
771 Self::Expression(_) | Self::NegatedExpression(_) => true,
776 Self::Constant(_) => false,
777 }
778 }
779}
780
781#[derive(Clone, PartialEq, Eq)]
782pub enum MacroDefinition {
783 Object {
784 replacement: String,
785 },
786 Function {
787 parameters: Vec<String>,
788 replacement: String,
789 },
790 Unsupported,
791}
792
793#[derive(Clone, Debug, PartialEq, Eq)]
794pub enum MacroIncludeProtection {
795 MacroGuard(String),
796 PragmaOnce,
797 None,
798}
799
800enum ParsedMacroReplacement {
801 Parsed { source: String, tree: Tree },
802 Unsupported,
803}
804
805#[derive(Clone, PartialEq, Eq)]
806pub struct MacroBinding {
807 source: ProjectFile,
808 declaration_byte: usize,
809 definition: MacroDefinition,
810 exact: bool,
811}
812
813impl MacroBinding {
814 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
815 Self {
816 source: source.clone(),
817 declaration_byte,
818 definition: MacroDefinition::Unsupported,
819 exact: false,
820 }
821 }
822
823 fn is_exact(&self) -> bool {
824 self.exact
825 }
826}
827
828#[derive(Clone)]
829pub enum MacroEvent {
830 Define {
831 name: String,
832 binding: MacroBinding,
833 byte: usize,
834 conditional: bool,
835 },
836 Undef {
837 name: String,
838 byte: usize,
839 conditional: bool,
840 },
841 Include {
842 targets: Vec<ProjectFile>,
843 byte: usize,
844 conditional: bool,
845 },
846 Invalidate {
847 byte: usize,
848 },
849}
850
851impl MacroEvent {
852 pub fn byte(&self) -> usize {
853 match self {
854 Self::Define { byte, .. }
855 | Self::Undef { byte, .. }
856 | Self::Include { byte, .. }
857 | Self::Invalidate { byte } => *byte,
858 }
859 }
860}
861
862#[derive(Clone, Copy, Debug, PartialEq, Eq)]
863pub enum CallArityEvidence {
864 Exact(usize),
865 Unknown,
866}
867
868impl CallArityEvidence {
869 pub fn exact(self) -> Option<usize> {
870 match self {
871 Self::Exact(arity) => Some(arity),
872 Self::Unknown => None,
873 }
874 }
875
876 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
877 self.exact().map(|arity| expected.accepts(arity))
878 }
879}
880
881#[derive(Clone)]
882struct DeclaredFieldTypeFact {
883 type_text: String,
884 indirection: i32,
885 template_arguments: Option<Vec<CppTemplateExpression>>,
886}
887
888#[derive(Clone)]
889enum StructuredAliasTarget {
890 Builtin,
891 Named {
892 components: Vec<String>,
893 global: bool,
894 arguments: Option<Vec<CppTemplateExpression>>,
895 },
896}
897
898struct CppAlias {
899 name: String,
900 target: String,
901 namespace: Option<String>,
902}
903
904type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
905
906#[derive(Debug, Clone, PartialEq, Eq)]
910pub enum CppTemplateResolutionError {
911 AliasCycle { alias: CodeUnit },
913 ArgumentBinding,
915 Substitution,
917 PrimarySelection,
920 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
923}
924
925fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
928 let mut distinct: Vec<CodeUnit> = Vec::new();
929 for unit in units {
930 if !distinct
931 .iter()
932 .any(|existing| same_visible_symbol(existing, unit))
933 {
934 distinct.push(unit.clone());
935 }
936 }
937 distinct
938}
939
940impl<'a> VisibilityIndex<'a> {
941 pub fn cpp(&self) -> &'a dyn CppSource {
942 self.cpp
943 }
944
945 #[cfg(any(test, feature = "test-support"))]
953 pub fn from_visible_files_for_test(
954 cpp: &'a dyn CppSource,
955 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
956 ) -> Self {
957 let visible_source_files_by_root = visible_by_file
958 .iter()
959 .map(|(file, visible)| {
960 (
961 file.clone(),
962 visible
963 .iter()
964 .map(|unit| unit.source().clone())
965 .chain(std::iter::once(file.clone()))
966 .collect(),
967 )
968 })
969 .collect();
970 let mut global_field_internal_linkage = HashMap::default();
971 Self {
972 cpp,
973 visible_by_identifier: build_visible_identifier_index(
974 &CppGraphSource::from_source(cpp),
975 &visible_by_file,
976 &visible_source_files_by_root,
977 &mut global_field_internal_linkage,
978 ),
979 global_field_internal_linkage,
980 visible_by_file,
981 visible_source_files_by_root,
982 alias_cells: Mutex::new(HashMap::default()),
983 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
984 visible_parser_alias_target_names: Mutex::new(HashMap::default()),
985 ordinary_type_import_cells: Mutex::new(HashMap::default()),
986 project_using_index: OnceLock::new(),
987 callable_reference_specs: Mutex::new(HashMap::default()),
988 include_activation_cells: Mutex::new(HashMap::default()),
989 conditional_include_projection_cells: Mutex::new(HashMap::default()),
990 include_activation_build_count: AtomicUsize::new(0),
991 using_donor_activation_count: AtomicUsize::new(0),
992 using_namespace_lookup_count: AtomicUsize::new(0),
993 using_name_candidate_inspection_count: AtomicUsize::new(0),
994 using_source_index_walk_count: AtomicUsize::new(0),
995 callable_reference_spec_build_count: AtomicUsize::new(0),
996 alias_source_parse_counts: Mutex::new(HashMap::default()),
997 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
998 visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
999 field_type_facts: Mutex::new(HashMap::default()),
1000 structured_alias_targets: Mutex::new(HashMap::default()),
1001 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1002 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1003 precise_parent_cache: Mutex::new(HashMap::default()),
1004 macro_event_cells: Mutex::new(HashMap::default()),
1005 macro_include_protection_cells: Mutex::new(HashMap::default()),
1006 macro_environment_cursors: Mutex::new(HashMap::default()),
1007 macro_replacements: Mutex::new(HashMap::default()),
1008 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1009 macro_replacement_parse_count: AtomicUsize::new(0),
1010 macro_event_application_count: AtomicUsize::new(0),
1011 macro_environment_copy_count: AtomicUsize::new(0),
1012 cpp_template_metadata: HashMap::default(),
1013 cpp_template_families: HashMap::default(),
1014 qualified_candidate_inspections: AtomicUsize::new(0),
1015 target_preserving_type_resolution_count: AtomicUsize::new(0),
1016 }
1017 }
1018
1019 fn cpp_source(&self) -> CppGraphSource<'a> {
1026 CppGraphSource::from_source(self.cpp)
1027 }
1028
1029 pub fn build(
1030 cpp: &'a dyn CppSource,
1031 analyzer: &CppGraphSource<'_>,
1032 roots: &HashSet<ProjectFile>,
1033 ) -> Self {
1034 Self::build_with_cancellation(cpp, analyzer, roots, None)
1035 }
1036
1037 pub fn build_with_cancellation(
1038 cpp: &'a dyn CppSource,
1039 analyzer: &CppGraphSource<'_>,
1040 roots: &HashSet<ProjectFile>,
1041 cancellation: Option<&CancellationToken>,
1042 ) -> Self {
1043 let include_targets = cpp.include_target_index();
1044 let VisibilityData {
1045 visible_by_file,
1046 visible_source_files_by_root,
1047 } = build_visibility_data(
1048 roots,
1049 cancellation,
1050 |file| {
1051 let imports = analyzer.import_statements(file);
1052 cpp_include_paths(&imports)
1053 .into_iter()
1054 .flat_map(|include| {
1055 resolve_include_targets_with_index(file, &include, include_targets)
1056 })
1057 .collect()
1058 },
1059 |file| analyzer.declarations(file),
1060 );
1061 let mut global_field_internal_linkage = HashMap::default();
1062 let visible_by_identifier = build_visible_identifier_index(
1063 analyzer,
1064 &visible_by_file,
1065 &visible_source_files_by_root,
1066 &mut global_field_internal_linkage,
1067 );
1068 let mut cpp_template_metadata = HashMap::default();
1069 for unit in visible_by_file
1070 .values()
1071 .flatten()
1072 .filter(|unit| unit.is_class())
1073 {
1074 if cpp_template_metadata.contains_key(unit) {
1075 continue;
1076 }
1077 if let Some(metadata) = cpp.template_metadata(unit) {
1078 cpp_template_metadata.insert(unit.clone(), metadata);
1079 }
1080 }
1081 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1082 for (unit, metadata) in &cpp_template_metadata {
1083 cpp_template_families
1084 .entry(metadata.primary_fq_name.clone())
1085 .or_default()
1086 .push(unit.clone());
1087 }
1088 Self {
1089 cpp,
1090 visible_by_file,
1091 visible_by_identifier,
1092 global_field_internal_linkage,
1093 visible_source_files_by_root,
1094 alias_cells: Mutex::new(HashMap::default()),
1095 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1096 visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1097 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1098 project_using_index: OnceLock::new(),
1099 callable_reference_specs: Mutex::new(HashMap::default()),
1100 include_activation_cells: Mutex::new(HashMap::default()),
1101 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1102 #[cfg(any(test, feature = "test-support"))]
1103 include_activation_build_count: AtomicUsize::new(0),
1104 #[cfg(any(test, feature = "test-support"))]
1105 using_donor_activation_count: AtomicUsize::new(0),
1106 #[cfg(any(test, feature = "test-support"))]
1107 using_namespace_lookup_count: AtomicUsize::new(0),
1108 #[cfg(any(test, feature = "test-support"))]
1109 using_name_candidate_inspection_count: AtomicUsize::new(0),
1110 #[cfg(any(test, feature = "test-support"))]
1111 using_source_index_walk_count: AtomicUsize::new(0),
1112 #[cfg(any(test, feature = "test-support"))]
1113 callable_reference_spec_build_count: AtomicUsize::new(0),
1114 #[cfg(any(test, feature = "test-support"))]
1115 alias_source_parse_counts: Mutex::new(HashMap::default()),
1116 #[cfg(any(test, feature = "test-support"))]
1117 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1118 #[cfg(any(test, feature = "test-support"))]
1119 visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1120 field_type_facts: Mutex::new(HashMap::default()),
1121 structured_alias_targets: Mutex::new(HashMap::default()),
1122 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1123 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1124 precise_parent_cache: Mutex::new(HashMap::default()),
1125 macro_event_cells: Mutex::new(HashMap::default()),
1126 macro_include_protection_cells: Mutex::new(HashMap::default()),
1127 macro_environment_cursors: Mutex::new(HashMap::default()),
1128 macro_replacements: Mutex::new(HashMap::default()),
1129 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1130 #[cfg(any(test, feature = "test-support"))]
1131 macro_replacement_parse_count: AtomicUsize::new(0),
1132 #[cfg(any(test, feature = "test-support"))]
1133 macro_event_application_count: AtomicUsize::new(0),
1134 #[cfg(any(test, feature = "test-support"))]
1135 macro_environment_copy_count: AtomicUsize::new(0),
1136 cpp_template_metadata,
1137 cpp_template_families,
1138 #[cfg(any(test, feature = "test-support"))]
1139 qualified_candidate_inspections: AtomicUsize::new(0),
1140 #[cfg(any(test, feature = "test-support"))]
1141 target_preserving_type_resolution_count: AtomicUsize::new(0),
1142 }
1143 }
1144
1145 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1146 if file == target.source() {
1147 return true;
1148 }
1149 if self.global_field_has_internal_linkage(target) {
1150 return self
1151 .visible_source_files_by_root
1152 .get(file)
1153 .is_some_and(|sources| sources.contains(target.source()));
1154 }
1155 self.visible_by_file
1156 .get(file)
1157 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1158 }
1159
1160 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1161 self.global_field_internal_linkage
1162 .get(unit)
1163 .copied()
1164 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1165 }
1166
1167 pub fn call_arity_evidence(
1168 &self,
1169 file: &ProjectFile,
1170 call: Node<'_>,
1171 source: &str,
1172 ) -> CallArityEvidence {
1173 let Some(arguments) = call
1174 .child_by_field_name("arguments")
1175 .or_else(|| call.child_by_field_name("parameters"))
1176 .or_else(|| call.child_by_field_name("value"))
1177 .or_else(|| first_named_child_of_kind(call, "argument_list"))
1178 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1179 else {
1180 return CallArityEvidence::Exact(0);
1181 };
1182 let arguments = argument_children(arguments).collect::<Vec<_>>();
1183 if arguments
1184 .iter()
1185 .all(|argument| !argument_shape_may_change_arity(*argument))
1186 {
1187 return CallArityEvidence::Exact(arguments.len());
1188 }
1189 let environment = self.macro_environment(file, call.start_byte());
1190 let mut stack = Vec::new();
1191 let mut total = 0usize;
1192 for argument in arguments {
1193 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1194 return CallArityEvidence::Unknown;
1195 }
1196 let CallArityEvidence::Exact(spread) =
1197 self.argument_arity_evidence(argument, source, &environment, &mut stack)
1198 else {
1199 return CallArityEvidence::Unknown;
1200 };
1201 total += spread;
1202 }
1203 CallArityEvidence::Exact(total)
1204 }
1205
1206 fn argument_arity_evidence(
1207 &self,
1208 argument: Node<'_>,
1209 source: &str,
1210 environment: &MacroEnvironment,
1211 stack: &mut Vec<(ProjectFile, usize)>,
1212 ) -> CallArityEvidence {
1213 let (name, invocation_arguments, function_like) = match argument.kind() {
1214 "identifier" => (node_text(argument, source), None, false),
1215 "call_expression" => {
1216 let Some(function) = argument.child_by_field_name("function") else {
1217 return CallArityEvidence::Exact(1);
1218 };
1219 if function.kind() != "identifier" {
1220 return CallArityEvidence::Exact(1);
1221 }
1222 let Some(arguments) = argument.child_by_field_name("arguments") else {
1223 return CallArityEvidence::Exact(1);
1224 };
1225 (node_text(function, source), Some(arguments), true)
1226 }
1227 _ => return CallArityEvidence::Exact(1),
1228 };
1229 let Some(binding) = environment.binding(name) else {
1230 return if environment.unknown_names {
1231 CallArityEvidence::Unknown
1232 } else {
1233 CallArityEvidence::Exact(1)
1234 };
1235 };
1236 match (&binding.definition, invocation_arguments, function_like) {
1237 (MacroDefinition::Object { replacement }, None, false) => self
1238 .replacement_arity_evidence(
1239 replacement,
1240 &[],
1241 &[],
1242 source,
1243 environment,
1244 stack,
1245 binding,
1246 ),
1247 (
1248 MacroDefinition::Function {
1249 parameters,
1250 replacement,
1251 },
1252 Some(arguments),
1253 true,
1254 ) => {
1255 let actuals = argument_children(arguments).collect::<Vec<_>>();
1256 if actuals.len() != parameters.len() {
1257 CallArityEvidence::Unknown
1258 } else {
1259 self.replacement_arity_evidence(
1260 replacement,
1261 parameters,
1262 &actuals,
1263 source,
1264 environment,
1265 stack,
1266 binding,
1267 )
1268 }
1269 }
1270 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1271 _ => CallArityEvidence::Unknown,
1272 }
1273 }
1274
1275 #[allow(clippy::too_many_arguments)]
1276 fn replacement_arity_evidence(
1277 &self,
1278 replacement: &str,
1279 parameters: &[String],
1280 actuals: &[Node<'_>],
1281 actual_source: &str,
1282 environment: &MacroEnvironment,
1283 stack: &mut Vec<(ProjectFile, usize)>,
1284 binding: &MacroBinding,
1285 ) -> CallArityEvidence {
1286 let identity = (binding.source.clone(), binding.declaration_byte);
1287 if stack.contains(&identity) || replacement.trim().is_empty() {
1288 return CallArityEvidence::Unknown;
1289 }
1290 stack.push(identity);
1291 let parsed = self.parsed_macro_replacement(binding, replacement);
1292 let evidence = (|| {
1293 let ParsedMacroReplacement::Parsed {
1294 source: sentinel,
1295 tree,
1296 } = parsed.as_ref()
1297 else {
1298 return None;
1299 };
1300 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1301 let arguments = call.child_by_field_name("arguments")?;
1302 let mut total = 0usize;
1303 for argument in argument_children(arguments) {
1304 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1305 return None;
1306 }
1307 if argument.kind() == "identifier"
1308 && let Some(parameter_index) = parameters
1309 .iter()
1310 .position(|parameter| parameter == node_text(argument, sentinel))
1311 {
1312 if !macro_expansion_shape_is_safe(
1313 actuals[parameter_index],
1314 actual_source,
1315 &[],
1316 environment,
1317 ) {
1318 return None;
1319 }
1320 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1321 actuals[parameter_index],
1322 actual_source,
1323 environment,
1324 stack,
1325 ) else {
1326 return None;
1327 };
1328 total += spread;
1329 continue;
1330 }
1331 let CallArityEvidence::Exact(spread) =
1332 self.argument_arity_evidence(argument, sentinel, environment, stack)
1333 else {
1334 return None;
1335 };
1336 total += spread;
1337 }
1338 Some(CallArityEvidence::Exact(total))
1339 })()
1340 .unwrap_or(CallArityEvidence::Unknown);
1341 stack.pop();
1342 evidence
1343 }
1344
1345 fn parsed_macro_replacement(
1346 &self,
1347 binding: &MacroBinding,
1348 replacement: &str,
1349 ) -> Arc<ParsedMacroReplacement> {
1350 let key = (binding.source.clone(), binding.declaration_byte);
1351 let mut cache = self
1352 .macro_replacements
1353 .lock()
1354 .expect("C++ macro replacement cache poisoned");
1355 if let Some(parsed) = cache.get(&key) {
1356 return Arc::clone(parsed);
1357 }
1358 #[cfg(any(test, feature = "test-support"))]
1359 self.macro_replacement_parse_count
1360 .fetch_add(1, Ordering::Relaxed);
1361 let source =
1362 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1363 let mut parser = Parser::new();
1364 let parsed = parser
1365 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1366 .ok()
1367 .and_then(|()| parser.parse(&source, None))
1368 .filter(|tree| !tree.root_node().has_error())
1369 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1370 ParsedMacroReplacement::Parsed { source, tree }
1371 });
1372 let parsed = Arc::new(parsed);
1373 cache.insert(key, Arc::clone(&parsed));
1374 parsed
1375 }
1376
1377 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1378 let Some(value) = node.child_by_field_name("value") else {
1379 return MacroDefinition::Unsupported;
1380 };
1381 let replacement = node_text(value, source).to_string();
1382 if node.kind() == "preproc_def" {
1383 return MacroDefinition::Object { replacement };
1384 }
1385 let Some(parameters) = node.child_by_field_name("parameters") else {
1386 return MacroDefinition::Unsupported;
1387 };
1388 if (0..parameters.child_count()).any(|index| {
1389 parameters
1390 .child(index)
1391 .is_some_and(|child| child.kind() == "...")
1392 }) {
1393 return MacroDefinition::Unsupported;
1394 }
1395 let parameters = (0..parameters.named_child_count())
1396 .filter_map(|index| parameters.named_child(index))
1397 .map(|parameter| node_text(parameter, source).to_string())
1398 .collect();
1399 MacroDefinition::Function {
1400 parameters,
1401 replacement,
1402 }
1403 }
1404
1405 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1406 self.macro_event_cells
1407 .lock()
1408 .expect("C++ macro event cache poisoned")
1409 .entry(file.clone())
1410 .or_default()
1411 .clone()
1412 }
1413
1414 pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1415 let key = (file.clone(), std::thread::current().id());
1416 self.macro_environment_cursors
1417 .lock()
1418 .expect("C++ macro environment cursor cache poisoned")
1419 .entry(key)
1420 .or_default()
1421 .clone()
1422 }
1423
1424 pub fn macro_environment(
1425 &self,
1426 file: &ProjectFile,
1427 before_byte: usize,
1428 ) -> Arc<MacroEnvironment> {
1429 let cell = self.macro_event_cell(file);
1430 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1431 let frontier = events.partition_point(|event| event.byte() < before_byte);
1432 let cursor_cell = self.macro_environment_cursor_cell(file);
1433 let mut cursor = cursor_cell
1434 .lock()
1435 .expect("C++ macro environment cursor poisoned");
1436 if frontier < cursor.frontier {
1437 *cursor = MacroEnvironmentCursor::default();
1438 }
1439 if frontier > cursor.frontier {
1440 #[cfg(any(test, feature = "test-support"))]
1441 if Arc::strong_count(&cursor.environment) > 1 {
1442 self.macro_environment_copy_count
1443 .fetch_add(1, Ordering::Relaxed);
1444 }
1445 let start = cursor.frontier;
1446 let environment = Arc::make_mut(&mut cursor.environment);
1447 let mut include_stack = HashSet::from_iter([file.clone()]);
1448 for event in &events[start..frontier] {
1449 self.apply_macro_event(file, event, environment, &mut include_stack);
1450 }
1451 cursor.frontier = frontier;
1452 }
1453 Arc::clone(&cursor.environment)
1454 }
1455
1456 pub fn object_macro_replacement_at(
1457 &self,
1458 file: &ProjectFile,
1459 name: &str,
1460 before_byte: usize,
1461 ) -> Option<String> {
1462 let environment = self.macro_environment(file, before_byte);
1463 let binding = environment.binding(name)?;
1464 if !binding.exact {
1465 return None;
1466 }
1467 match &binding.definition {
1468 MacroDefinition::Object { replacement } => Some(replacement.clone()),
1469 MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
1470 }
1471 }
1472
1473 fn apply_macro_events(
1474 &self,
1475 file: &ProjectFile,
1476 before_byte: Option<usize>,
1477 environment: &mut MacroEnvironment,
1478 include_stack: &mut HashSet<ProjectFile>,
1479 ) {
1480 if !include_stack.insert(file.clone()) {
1481 return;
1482 }
1483 if self.cpp.prepared_syntax(file).is_none() {
1484 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
1485 include_stack.remove(file);
1486 return;
1487 }
1488 match self.macro_include_protection(file) {
1489 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
1490 Some(binding) if binding.is_exact() => {
1491 include_stack.remove(file);
1492 return;
1493 }
1494 Some(_) | None if environment.unknown_names => {
1495 let mut ambiguous_seen = HashSet::default();
1496 self.mark_macro_events_ambiguous(
1497 file,
1498 environment,
1499 &mut ambiguous_seen,
1500 file,
1501 before_byte.unwrap_or_default(),
1502 );
1503 include_stack.remove(file);
1504 return;
1505 }
1506 Some(_) => {
1507 let mut ambiguous_seen = HashSet::default();
1508 self.mark_macro_events_ambiguous(
1509 file,
1510 environment,
1511 &mut ambiguous_seen,
1512 file,
1513 before_byte.unwrap_or_default(),
1514 );
1515 include_stack.remove(file);
1516 return;
1517 }
1518 None => {}
1519 },
1520 MacroIncludeProtection::PragmaOnce => {
1521 if !environment.applied_pragma_once_files.insert(file.clone()) {
1522 include_stack.remove(file);
1523 return;
1524 }
1525 if environment.maybe_applied_pragma_once_files.remove(file) {
1526 let mut ambiguous_seen = HashSet::default();
1531 environment.applied_pragma_once_files.remove(file);
1532 self.mark_macro_events_ambiguous(
1533 file,
1534 environment,
1535 &mut ambiguous_seen,
1536 file,
1537 before_byte.unwrap_or_default(),
1538 );
1539 environment.maybe_applied_pragma_once_files.remove(file);
1540 environment.applied_pragma_once_files.insert(file.clone());
1541 include_stack.remove(file);
1542 return;
1543 }
1544 }
1545 MacroIncludeProtection::None => {}
1546 }
1547 let cell = self.macro_event_cell(file);
1548 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1549 for event in events {
1550 if before_byte.is_some_and(|limit| event.byte() >= limit) {
1551 break;
1552 }
1553 self.apply_macro_event(file, event, environment, include_stack);
1554 }
1555 include_stack.remove(file);
1556 }
1557
1558 fn apply_macro_event(
1559 &self,
1560 file: &ProjectFile,
1561 event: &MacroEvent,
1562 environment: &mut MacroEnvironment,
1563 include_stack: &mut HashSet<ProjectFile>,
1564 ) {
1565 #[cfg(any(test, feature = "test-support"))]
1566 self.macro_event_application_count
1567 .fetch_add(1, Ordering::Relaxed);
1568 match event {
1569 MacroEvent::Define {
1570 name,
1571 binding,
1572 conditional,
1573 byte,
1574 } => {
1575 environment.insert(
1576 name.clone(),
1577 if *conditional {
1578 MacroBinding::ambiguous(file, *byte)
1579 } else {
1580 binding.clone()
1581 },
1582 );
1583 }
1584 MacroEvent::Undef {
1585 name,
1586 conditional,
1587 byte,
1588 } => {
1589 if *conditional {
1590 if environment.binding(name).is_some() {
1591 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
1592 }
1593 } else {
1594 environment.remove(name);
1595 }
1596 }
1597 MacroEvent::Include {
1598 targets,
1599 conditional,
1600 byte,
1601 } => {
1602 if targets.is_empty() {
1603 environment.mark_unknown_names(file, *byte);
1604 return;
1605 }
1606 if *conditional || targets.len() > 1 {
1607 let mut ambiguous_seen = HashSet::default();
1608 for target in targets {
1609 self.mark_macro_events_ambiguous(
1610 target,
1611 environment,
1612 &mut ambiguous_seen,
1613 file,
1614 *byte,
1615 );
1616 }
1617 } else if let Some(target) = targets.first() {
1618 self.apply_macro_events(target, None, environment, include_stack);
1619 }
1620 }
1621 MacroEvent::Invalidate { byte } => {
1622 for binding in environment.bindings.values_mut() {
1623 *binding = MacroBinding::ambiguous(file, *byte);
1624 }
1625 }
1626 }
1627 }
1628
1629 fn mark_macro_events_ambiguous(
1630 &self,
1631 file: &ProjectFile,
1632 environment: &mut MacroEnvironment,
1633 include_stack: &mut HashSet<ProjectFile>,
1634 conditional_file: &ProjectFile,
1635 conditional_byte: usize,
1636 ) {
1637 if !include_stack.insert(file.clone()) {
1638 return;
1639 }
1640 if self.cpp.prepared_syntax(file).is_none() {
1641 environment.mark_unknown_names(conditional_file, conditional_byte);
1642 return;
1643 }
1644 match self.macro_include_protection(file) {
1645 MacroIncludeProtection::MacroGuard(guard) => {
1646 if environment
1647 .binding(&guard)
1648 .is_some_and(MacroBinding::is_exact)
1649 {
1650 return;
1651 }
1652 }
1653 MacroIncludeProtection::PragmaOnce => {
1654 if environment.applied_pragma_once_files.contains(file) {
1655 return;
1656 }
1657 environment
1658 .maybe_applied_pragma_once_files
1659 .insert(file.clone());
1660 }
1661 MacroIncludeProtection::None => {}
1662 }
1663 let cell = self.macro_event_cell(file);
1664 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1665 for event in events {
1666 #[cfg(any(test, feature = "test-support"))]
1667 self.macro_event_application_count
1668 .fetch_add(1, Ordering::Relaxed);
1669 match event {
1670 MacroEvent::Define { name, .. } => {
1671 environment.insert(
1672 name.clone(),
1673 MacroBinding::ambiguous(conditional_file, conditional_byte),
1674 );
1675 }
1676 MacroEvent::Undef { name, .. } => {
1677 if environment.binding(name).is_some() {
1678 environment.insert(
1679 name.clone(),
1680 MacroBinding::ambiguous(conditional_file, conditional_byte),
1681 );
1682 }
1683 }
1684 MacroEvent::Include { targets, .. } => {
1685 if targets.is_empty() {
1686 environment.mark_unknown_names(conditional_file, conditional_byte);
1687 continue;
1688 }
1689 for target in targets {
1690 self.mark_macro_events_ambiguous(
1691 target,
1692 environment,
1693 include_stack,
1694 conditional_file,
1695 conditional_byte,
1696 );
1697 }
1698 }
1699 MacroEvent::Invalidate { .. } => {
1700 for binding in environment.bindings.values_mut() {
1701 *binding = MacroBinding::ambiguous(conditional_file, conditional_byte);
1702 }
1703 }
1704 }
1705 }
1706 }
1707
1708 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
1709 let cell = self
1710 .macro_include_protection_cells
1711 .lock()
1712 .expect("C++ include protection cache poisoned")
1713 .entry(file.clone())
1714 .or_default()
1715 .clone();
1716 cell.get_or_init(|| {
1717 self.cpp
1718 .prepared_syntax(file)
1719 .map_or(MacroIncludeProtection::None, |prepared| {
1720 top_level_macro_include_protection(
1721 prepared.tree().root_node(),
1722 prepared.source(),
1723 )
1724 })
1725 })
1726 .clone()
1727 }
1728
1729 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
1730 let Some(prepared) = self.cpp.prepared_syntax(file) else {
1731 return Vec::new();
1732 };
1733 let source = prepared.source();
1734 let mut events = Vec::new();
1735 let mut stack = vec![prepared.tree().root_node()];
1736 while let Some(node) = stack.pop() {
1737 let conditional = has_preprocessor_conditional_ancestor(node, source);
1738 match node.kind() {
1739 "preproc_def" | "preproc_function_def" => {
1740 let Some(name) = node.child_by_field_name("name") else {
1741 continue;
1742 };
1743 let name = node_text(name, source).to_string();
1744 events.push(MacroEvent::Define {
1745 name,
1746 binding: MacroBinding {
1747 source: file.clone(),
1748 declaration_byte: node.start_byte(),
1749 definition: Self::decode_macro_definition(node, source),
1750 exact: true,
1751 },
1752 byte: node.start_byte(),
1753 conditional,
1754 });
1755 continue;
1756 }
1757 "preproc_include" => {
1758 let Some(path) = node.child_by_field_name("path") else {
1759 events.push(MacroEvent::Include {
1760 targets: Vec::new(),
1761 byte: node.start_byte(),
1762 conditional,
1763 });
1764 continue;
1765 };
1766 let targets =
1767 structured_include_path(path, source).map_or_else(Vec::new, |path| {
1768 resolve_include_targets_with_index(
1769 file,
1770 path,
1771 self.cpp.include_target_index(),
1772 )
1773 });
1774 if targets.is_empty() && path.kind() == "system_lib_string" {
1779 continue;
1780 }
1781 events.push(MacroEvent::Include {
1782 targets,
1783 byte: node.start_byte(),
1784 conditional,
1785 });
1786 continue;
1787 }
1788 "preproc_call" => {
1789 let Some(directive) = node.child_by_field_name("directive") else {
1790 continue;
1791 };
1792 if node_text(directive, source) != "#undef" {
1793 continue;
1794 }
1795 let name = node
1796 .child_by_field_name("argument")
1797 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
1798 if let Some(name) = name {
1799 events.push(MacroEvent::Undef {
1800 name,
1801 byte: node.start_byte(),
1802 conditional,
1803 });
1804 } else {
1805 events.push(MacroEvent::Invalidate {
1806 byte: node.start_byte(),
1807 });
1808 }
1809 continue;
1810 }
1811 _ => {}
1812 }
1813 for index in (0..node.named_child_count()).rev() {
1814 if let Some(child) = node.named_child(index) {
1815 stack.push(child);
1816 }
1817 }
1818 }
1819 events.sort_by_key(MacroEvent::byte);
1820 events
1821 }
1822
1823 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
1824 self.ordinary_type_import_cells
1825 .lock()
1826 .expect("C++ ordinary type import cache poisoned")
1827 .entry(file.clone())
1828 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
1829 .clone()
1830 }
1831
1832 pub fn project_using_index(
1833 &self,
1834 build: impl FnOnce() -> ProjectUsingIndex,
1835 ) -> &ProjectUsingIndex {
1836 self.project_using_index.get_or_init(build)
1837 }
1838
1839 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
1840 let mut files = self
1841 .visible_source_files_by_root
1842 .values()
1843 .flatten()
1844 .cloned()
1845 .collect::<HashSet<_>>()
1846 .into_iter()
1847 .collect::<Vec<_>>();
1848 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
1849 files
1850 }
1851
1852 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
1853 self.visible_source_files_by_root
1854 .get(root)
1855 .is_some_and(|files| files.contains(source))
1856 }
1857
1858 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
1859 let cached = self
1860 .visible_parser_alias_name_sets
1861 .read()
1862 .expect("visible parser alias-name cache poisoned")
1863 .get(file)
1864 .cloned();
1865 let cell = if let Some(cached) = cached {
1866 cached
1867 } else {
1868 let mut cells = self
1869 .visible_parser_alias_name_sets
1870 .write()
1871 .expect("visible parser alias-name cache poisoned");
1872 Arc::clone(
1873 cells
1874 .entry(file.clone())
1875 .or_insert_with(|| Arc::new(OnceLock::new())),
1876 )
1877 };
1878 cell.get_or_init(|| {
1879 #[cfg(any(test, feature = "test-support"))]
1880 self.visible_parser_alias_name_set_build_count
1881 .fetch_add(1, Ordering::Relaxed);
1882 let mut names = HashSet::default();
1883 let visible_files = self
1884 .visible_source_files_by_root
1885 .get(file)
1886 .cloned()
1887 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
1888 for visible_file in visible_files {
1889 let aliases = {
1890 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
1891 Arc::clone(
1892 cells
1893 .entry(visible_file.clone())
1894 .or_insert_with(|| Arc::new(OnceLock::new())),
1895 )
1896 };
1897 for alias in aliases
1898 .get_or_init(|| {
1899 #[cfg(any(test, feature = "test-support"))]
1900 {
1901 *self
1902 .alias_source_parse_counts
1903 .lock()
1904 .expect("alias source parse count lock")
1905 .entry(visible_file.clone())
1906 .or_default() += 1;
1907 }
1908 aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
1909 })
1910 .iter()
1911 {
1912 names.insert(alias.name.clone());
1913 }
1914 }
1915 names
1916 })
1917 .contains(name)
1918 }
1919
1920 fn visible_parser_alias_names_for_target(
1921 &self,
1922 file: &ProjectFile,
1923 target: &CodeUnit,
1924 ) -> HashSet<String> {
1925 let cell = {
1926 let mut cells = self
1927 .visible_parser_alias_target_names
1928 .lock()
1929 .expect("visible parser alias-target cache poisoned");
1930 Arc::clone(
1931 cells
1932 .entry(file.clone())
1933 .or_insert_with(|| Arc::new(OnceLock::new())),
1934 )
1935 };
1936 let target_name = cpp_name_for(target);
1937 cell.get_or_init(|| {
1938 #[cfg(any(test, feature = "test-support"))]
1939 self.visible_parser_alias_target_names_build_count
1940 .fetch_add(1, Ordering::Relaxed);
1941 let visible_files = self
1942 .visible_source_files_by_root
1943 .get(file)
1944 .cloned()
1945 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
1946 let mut names_by_target = HashMap::<String, HashSet<String>>::default();
1947 for visible_file in visible_files {
1948 let aliases = {
1949 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
1950 Arc::clone(
1951 cells
1952 .entry(visible_file.clone())
1953 .or_insert_with(|| Arc::new(OnceLock::new())),
1954 )
1955 };
1956 for alias in aliases
1957 .get_or_init(|| {
1958 #[cfg(any(test, feature = "test-support"))]
1959 {
1960 *self
1961 .alias_source_parse_counts
1962 .lock()
1963 .expect("alias source parse count lock")
1964 .entry(visible_file.clone())
1965 .or_default() += 1;
1966 }
1967 aliases_from_prepared_source(self.cpp, &visible_file).into_boxed_slice()
1968 })
1969 .iter()
1970 {
1971 for target_name in parser_alias_target_names(alias) {
1972 names_by_target
1973 .entry(target_name)
1974 .or_default()
1975 .insert(alias.name.clone());
1976 }
1977 }
1978 }
1979 names_by_target
1980 })
1981 .get(&target_name)
1982 .cloned()
1983 .unwrap_or_default()
1984 }
1985
1986 fn callable_arities_for_target(
1987 &self,
1988 analyzer: &CppGraphSource<'_>,
1989 cpp: &dyn CppSource,
1990 file: &ProjectFile,
1991 prepared: &PreparedSyntaxTree,
1992 spec: &TargetSpec,
1993 ) -> Vec<ActivatedCallableArity> {
1994 let Some(signature) = spec.target.signature() else {
1995 return Vec::new();
1996 };
1997 let Some(candidates) = self
1998 .visible_by_identifier
1999 .get(file)
2000 .and_then(|by_name| by_name.get(&spec.member_name))
2001 else {
2002 return Vec::new();
2003 };
2004 let differing_candidates = candidates
2005 .iter()
2006 .filter(|candidate| {
2007 candidate.is_function()
2008 && candidate.fq_name() == spec.target.fq_name()
2009 && candidate.signature() == Some(signature)
2010 })
2011 .filter_map(|candidate| {
2012 analyzer
2013 .signature_metadata(candidate)
2014 .into_iter()
2015 .find_map(|metadata| metadata.callable_arity())
2016 .filter(|arity| Some(*arity) != spec.callable_arity)
2017 .map(|arity| (candidate, arity))
2018 })
2019 .collect::<Vec<_>>();
2020 if differing_candidates.is_empty() {
2021 return Vec::new();
2022 }
2023 let mut arities = Vec::with_capacity(differing_candidates.len());
2024 for (candidate, candidate_arity) in differing_candidates {
2025 let declaration_activation = if candidate.source() == file {
2026 callable_declaration_activation_in_file(analyzer, prepared, candidate, file)
2027 } else {
2028 cpp.prepared_syntax(candidate.source()).and_then(|syntax| {
2029 callable_declaration_activation_in_file(
2030 analyzer,
2031 syntax.as_ref(),
2032 candidate,
2033 file,
2034 )
2035 })
2036 };
2037 let Some(declaration_activation) = declaration_activation else {
2038 continue;
2039 };
2040 let activation_byte = if candidate.source() == file {
2041 Some(declaration_activation)
2042 } else {
2043 self.include_activation_for_source(cpp, file, prepared, candidate.source())
2044 };
2045 if let Some(activation_byte) = activation_byte {
2046 arities.push(ActivatedCallableArity {
2047 activation_byte,
2048 arity: candidate_arity,
2049 });
2050 }
2051 }
2052 arities
2053 }
2054
2055 fn callable_parameter_macro_arity(
2056 &self,
2057 target: &CodeUnit,
2058 signature: Option<&str>,
2059 ) -> Option<CallableArity> {
2060 let parameter_types = cpp_signature_param_types(signature?)?;
2061 let [macro_name] = parameter_types.as_slice() else {
2062 return None;
2063 };
2064 if macro_name.is_empty()
2065 || !macro_name
2066 .chars()
2067 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2068 {
2069 return None;
2070 }
2071 let cache_key = (target.source().clone(), macro_name.clone());
2072 if let Some(cached) = self
2073 .callable_parameter_macro_arities
2074 .lock()
2075 .expect("C++ callable parameter-macro arity cache poisoned")
2076 .get(&cache_key)
2077 .copied()
2078 {
2079 return cached;
2080 }
2081 let mut visible_files = HashSet::default();
2082 collect_include_closure(
2083 &self.cpp_source(),
2084 self.cpp.include_target_index(),
2085 target.source(),
2086 &mut visible_files,
2087 None,
2088 );
2089 let mut arities = Vec::new();
2090 for visible_file in visible_files {
2091 let cell = self.macro_event_cell(&visible_file);
2092 for event in
2093 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2094 {
2095 let MacroEvent::Define { name, binding, .. } = event else {
2096 continue;
2097 };
2098 if name != macro_name {
2099 continue;
2100 }
2101 let MacroDefinition::Object { replacement } = &binding.definition else {
2102 continue;
2103 };
2104 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2105 continue;
2106 };
2107 if !arities.contains(&arity) {
2108 arities.push(arity);
2109 }
2110 }
2111 }
2112 let resolved = (|| {
2113 let required = arities
2114 .iter()
2115 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2116 .min()?;
2117 let total = arities.iter().map(|arity| arity.total()).max()?;
2118 let repeated = arities
2119 .iter()
2120 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2121 Some(CallableArity::new(required, total, repeated))
2126 })();
2127 self.callable_parameter_macro_arities
2128 .lock()
2129 .expect("C++ callable parameter-macro arity cache poisoned")
2130 .insert(cache_key, resolved);
2131 resolved
2132 }
2133
2134 pub fn include_activation_for_source(
2135 &self,
2136 cpp: &dyn CppSource,
2137 file: &ProjectFile,
2138 prepared: &PreparedSyntaxTree,
2139 donor_source: &ProjectFile,
2140 ) -> Option<usize> {
2141 let key = (file.clone(), donor_source.clone());
2142 if let Some(cached) = self
2143 .include_activation_cells
2144 .lock()
2145 .expect("C++ include activation cache poisoned")
2146 .get(&key)
2147 .copied()
2148 {
2149 return cached;
2150 }
2151 #[cfg(any(test, feature = "test-support"))]
2152 self.include_activation_build_count
2153 .fetch_add(1, Ordering::Relaxed);
2154 let activation = find_include_activation(cpp, file, prepared, donor_source);
2155 let mut cells = self
2156 .include_activation_cells
2157 .lock()
2158 .expect("C++ include activation cache poisoned");
2159 *cells.entry(key).or_insert(activation)
2160 }
2161
2162 pub fn conditional_include_projections_for_source(
2163 &self,
2164 file: &ProjectFile,
2165 prepared: &PreparedSyntaxTree,
2166 donor_source: &ProjectFile,
2167 ) -> Arc<[ConditionalIncludeProjection]> {
2168 let key = (file.clone(), donor_source.clone());
2169 if let Some(cached) = self
2170 .conditional_include_projection_cells
2171 .lock()
2172 .expect("C++ conditional include projection cache poisoned")
2173 .get(&key)
2174 .cloned()
2175 {
2176 return cached;
2177 }
2178 let projections: Arc<[ConditionalIncludeProjection]> =
2179 find_conditional_include_projections(self.cpp, file, prepared, donor_source).into();
2180 self.conditional_include_projection_cells
2181 .lock()
2182 .expect("C++ conditional include projection cache poisoned")
2183 .entry(key)
2184 .or_insert_with(|| Arc::clone(&projections))
2185 .clone()
2186 }
2187
2188 #[cfg(any(test, feature = "test-support"))]
2189 pub fn include_activation_build_count_for_test(&self) -> usize {
2190 self.include_activation_build_count.load(Ordering::Relaxed)
2191 }
2192
2193 #[cfg(any(test, feature = "test-support"))]
2194 pub fn note_using_donor_activation_for_test(&self) {
2195 self.using_donor_activation_count
2196 .fetch_add(1, Ordering::Relaxed);
2197 }
2198
2199 #[cfg(not(any(test, feature = "test-support")))]
2200 pub fn note_using_donor_activation_for_test(&self) {}
2201
2202 #[cfg(any(test, feature = "test-support"))]
2203 pub fn note_using_namespace_lookup_for_test(&self) {
2204 self.using_namespace_lookup_count
2205 .fetch_add(1, Ordering::Relaxed);
2206 }
2207
2208 #[cfg(not(any(test, feature = "test-support")))]
2209 pub fn note_using_namespace_lookup_for_test(&self) {}
2210
2211 #[cfg(any(test, feature = "test-support"))]
2212 pub fn note_using_name_candidate_inspection_for_test(&self) {
2213 self.using_name_candidate_inspection_count
2214 .fetch_add(1, Ordering::Relaxed);
2215 }
2216
2217 #[cfg(not(any(test, feature = "test-support")))]
2218 pub fn note_using_name_candidate_inspection_for_test(&self) {}
2219
2220 #[cfg(any(test, feature = "test-support"))]
2221 pub fn note_using_source_index_walk_for_test(&self) {
2222 self.using_source_index_walk_count
2223 .fetch_add(1, Ordering::Relaxed);
2224 }
2225
2226 #[cfg(not(any(test, feature = "test-support")))]
2227 pub fn note_using_source_index_walk_for_test(&self) {}
2228
2229 #[cfg(any(test, feature = "test-support"))]
2230 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize, usize) {
2231 (
2232 self.using_source_index_walk_count.load(Ordering::Relaxed),
2233 self.using_donor_activation_count.load(Ordering::Relaxed),
2234 self.using_namespace_lookup_count.load(Ordering::Relaxed),
2235 self.callable_reference_spec_build_count
2236 .load(Ordering::Relaxed),
2237 self.using_name_candidate_inspection_count
2238 .load(Ordering::Relaxed),
2239 )
2240 }
2241
2242 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2243 file == target.source()
2244 || self
2245 .visible_by_file
2246 .get(file)
2247 .is_some_and(|visible| visible.contains(target))
2248 }
2249
2250 pub fn declaration_visible_at(
2251 &self,
2252 analyzer: &CppGraphSource<'_>,
2253 file: &ProjectFile,
2254 declaration: &CodeUnit,
2255 reference_byte: usize,
2256 ) -> bool {
2257 self.visible_identifier_candidates(file, declaration.identifier())
2258 .filter(|candidate| {
2259 same_logical_symbol(candidate, declaration)
2260 || flattened_macro_namespace_declaration_matches(
2261 analyzer,
2262 self.cpp,
2263 file,
2264 candidate,
2265 declaration,
2266 reference_byte,
2267 )
2268 })
2269 .any(|candidate| {
2270 self.physical_declaration_visible_at(analyzer, file, candidate, reference_byte)
2271 })
2272 }
2273
2274 pub fn callable_arity_at_reference(
2275 &self,
2276 analyzer: &CppGraphSource<'_>,
2277 file: &ProjectFile,
2278 candidate: &CodeUnit,
2279 reference_byte: usize,
2280 ) -> Option<CallableArity> {
2281 let key = (file.clone(), logical_symbol_key(candidate));
2282 let cell = self
2283 .callable_reference_specs
2284 .lock()
2285 .expect("C++ callable reference-spec cache poisoned")
2286 .entry(key)
2287 .or_default()
2288 .clone();
2289 let spec = cell.get_or_init(|| {
2290 let prepared = self.cpp.prepared_syntax(file)?;
2291 let spec = TargetSpec::from_target(analyzer, candidate)?;
2292 let spec = spec
2293 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
2294 .into_owned();
2295 #[cfg(any(test, feature = "test-support"))]
2296 self.callable_reference_spec_build_count
2297 .fetch_add(1, Ordering::Relaxed);
2298 Some(spec)
2299 });
2300 spec.as_ref()?.callable_arity_at(reference_byte)
2301 }
2302
2303 fn physical_declaration_visible_at(
2304 &self,
2305 analyzer: &CppGraphSource<'_>,
2306 file: &ProjectFile,
2307 declaration: &CodeUnit,
2308 reference_byte: usize,
2309 ) -> bool {
2310 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2311 return false;
2312 };
2313 if declaration.source() == file {
2314 return callable_declaration_activation_in_file(
2315 analyzer,
2316 prepared.as_ref(),
2317 declaration,
2318 file,
2319 )
2320 .is_some_and(|activation| activation < reference_byte);
2321 }
2322 let Some(donor_syntax) = self.cpp.prepared_syntax(declaration.source()) else {
2323 return false;
2324 };
2325 if callable_declaration_activation_in_file(
2326 analyzer,
2327 donor_syntax.as_ref(),
2328 declaration,
2329 file,
2330 )
2331 .is_none()
2332 {
2333 return false;
2334 }
2335 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), declaration.source())
2336 .is_some_and(|activation| activation < reference_byte)
2337 }
2338
2339 pub fn external_type_candidate_visible_at(
2340 &self,
2341 file: &ProjectFile,
2342 candidate: &CodeUnit,
2343 reference_byte: usize,
2344 ) -> bool {
2345 if candidate.source() == file {
2346 return true;
2347 }
2348 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2349 return false;
2350 };
2351 self.visible_identifier_candidates(file, candidate.identifier())
2352 .filter(|peer| same_logical_symbol(candidate, peer))
2353 .any(|peer| {
2354 peer.source() == file
2355 || self
2356 .include_activation_for_source(
2357 self.cpp,
2358 file,
2359 prepared.as_ref(),
2360 peer.source(),
2361 )
2362 .is_some_and(|activation| activation <= reference_byte)
2363 })
2364 }
2365
2366 pub fn external_type_declaration_visible_at(
2367 &self,
2368 file: &ProjectFile,
2369 candidate: &CodeUnit,
2370 reference_byte: usize,
2371 ) -> bool {
2372 if candidate.source() == file {
2373 return true;
2374 }
2375 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2376 return false;
2377 };
2378 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
2379 .is_some_and(|activation| activation <= reference_byte)
2380 }
2381
2382 pub fn external_type_candidate_visible_in_context(
2383 &self,
2384 analyzer: &CppGraphSource<'_>,
2385 file: &ProjectFile,
2386 candidate: &CodeUnit,
2387 reference: Node<'_>,
2388 ) -> bool {
2389 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2390 return false;
2391 };
2392 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
2393
2394 let directly_visible = self
2395 .visible_identifier_candidates(file, candidate.identifier())
2396 .filter(|peer| same_logical_symbol(candidate, peer))
2397 .any(|peer| {
2398 declaration_guard_requirements(analyzer, self.cpp, peer)
2399 .into_iter()
2400 .any(|(declaration_byte, declaration_guards)| {
2401 if peer.source() == file {
2402 return declaration_byte < reference.start_byte()
2403 && guard_requirements_hold_at_reference(
2404 &declaration_guards,
2405 reference_guards.as_ref(),
2406 )
2407 && self.preprocessor_guards_stable_between(
2408 file,
2409 declaration_byte,
2410 reference.start_byte(),
2411 &declaration_guards,
2412 );
2413 }
2414 if self
2415 .include_activation_for_source(
2416 self.cpp,
2417 file,
2418 prepared.as_ref(),
2419 peer.source(),
2420 )
2421 .is_some_and(|activation| {
2422 activation <= reference.start_byte()
2423 && guard_requirements_hold_at_reference(
2424 &declaration_guards,
2425 reference_guards.as_ref(),
2426 )
2427 && self.preprocessor_guards_stable_between(
2428 file,
2429 0,
2430 reference.start_byte(),
2431 &declaration_guards,
2432 )
2433 })
2434 {
2435 return true;
2436 }
2437 self.conditional_include_projections_for_source(
2438 file,
2439 prepared.as_ref(),
2440 peer.source(),
2441 )
2442 .iter()
2443 .any(|projection| {
2444 let Some(required_guards) = merge_preprocessor_guards(
2445 &projection.required_guards,
2446 &declaration_guards,
2447 ) else {
2448 return false;
2449 };
2450 projection.activation_byte <= reference.start_byte()
2451 && guard_requirements_hold_at_reference(
2452 &required_guards,
2453 reference_guards.as_ref(),
2454 )
2455 && self.preprocessor_guards_stable_between(
2456 file,
2457 0,
2458 reference.start_byte(),
2459 &required_guards,
2460 )
2461 })
2462 })
2463 });
2464 let complementary = self
2465 .visible_identifier_candidates(file, candidate.identifier())
2466 .filter(|peer| {
2467 peer.kind() == candidate.kind()
2468 && peer.fq_name() == candidate.fq_name()
2469 && peer.source() == candidate.source()
2470 })
2471 .collect::<Vec<_>>();
2472 let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
2477 declaration_guard_requirements(analyzer, self.cpp, candidate)
2478 .iter()
2479 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
2480 });
2481 let complementary_visible = candidate_branch_compatible
2482 && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
2483 && if candidate.source() == file {
2484 declaration_guard_requirements(analyzer, self.cpp, candidate)
2485 .iter()
2486 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
2487 } else {
2488 self.include_activation_for_source(
2489 self.cpp,
2490 file,
2491 prepared.as_ref(),
2492 candidate.source(),
2493 )
2494 .is_some_and(|activation| activation <= reference.start_byte())
2495 };
2496 directly_visible || complementary_visible
2497 }
2498
2499 pub fn is_exhaustive_same_fqn_type_declaration_family(
2500 &self,
2501 analyzer: &CppGraphSource<'_>,
2502 file: &ProjectFile,
2503 candidate: &CodeUnit,
2504 ) -> bool {
2505 let candidates = self
2506 .visible_identifier_candidates(file, candidate.identifier())
2507 .filter(|peer| {
2508 peer.kind() == candidate.kind()
2509 && peer.fq_name() == candidate.fq_name()
2510 && peer.source() == candidate.source()
2511 })
2512 .collect::<Vec<_>>();
2513 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
2514 }
2515
2516 pub fn dependent_member_pointer_alias_visible_in_context(
2531 &self,
2532 analyzer: &CppGraphSource<'_>,
2533 file: &ProjectFile,
2534 candidate: &CodeUnit,
2535 owner_components: &[String],
2536 reference: Node<'_>,
2537 ) -> bool {
2538 if !analyzer
2539 .type_alias_provider()
2540 .is_some_and(|provider| provider.is_type_alias(candidate))
2541 {
2542 return false;
2543 }
2544 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
2545 return false;
2546 };
2547 if terminal != candidate.identifier()
2548 || canonical_cpp_scope_components(candidate) != owner_components
2549 {
2550 return false;
2551 }
2552 let Some(expected_parent_fq_name) =
2553 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
2554 else {
2555 return false;
2556 };
2557 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
2558 return false;
2559 };
2560 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
2561 || parent_anchor.source() != candidate.source()
2562 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
2563 {
2564 return false;
2565 }
2566
2567 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
2573 || candidate.source() == file
2574 && !analyzer
2575 .ranges(candidate)
2576 .iter()
2577 .any(|range| range.start_byte < reference.start_byte())
2578 {
2579 return false;
2580 }
2581
2582 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
2583 if candidate_guards.is_empty() {
2584 return false;
2585 }
2586 let same_guard_sets =
2587 |left: &[(usize, HashSet<PreprocessorGuard>)],
2588 right: &[(usize, HashSet<PreprocessorGuard>)]| {
2589 left.iter().all(|(_, left_guards)| {
2590 right
2591 .iter()
2592 .any(|(_, right_guards)| left_guards == right_guards)
2593 })
2594 };
2595 let parent_candidates = self
2596 .visible_identifier_candidates(file, parent_anchor.identifier())
2597 .filter(|peer| {
2598 peer.kind() == parent_anchor.kind()
2599 && peer.fq_name() == expected_parent_fq_name.as_str()
2600 && peer.source() == parent_anchor.source()
2601 && canonical_cpp_scope_components(peer) == owner_prefix
2602 })
2603 .filter_map(|peer| {
2604 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
2605 (candidate_guards.len() == parent_guards.len()
2606 && same_guard_sets(&candidate_guards, &parent_guards)
2607 && same_guard_sets(&parent_guards, &candidate_guards))
2608 .then(|| (peer.clone(), parent_guards))
2609 })
2610 .collect::<Vec<_>>();
2611 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
2612 return false;
2613 };
2614
2615 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2616 return false;
2617 };
2618 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
2619 else {
2620 return false;
2621 };
2622 if !candidate_guards.iter().any(|(_, target_guards)| {
2627 merge_preprocessor_guards(target_guards, &reference_guards).is_some()
2628 && (candidate.source() != file
2629 || self.preprocessor_guards_stable_between(
2630 file,
2631 0,
2632 reference.start_byte(),
2633 target_guards,
2634 ))
2635 }) {
2636 return false;
2637 }
2638
2639 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
2640 }
2641
2642 pub fn external_type_candidate_guard_compatible_in_context(
2652 &self,
2653 analyzer: &CppGraphSource<'_>,
2654 file: &ProjectFile,
2655 candidate: &CodeUnit,
2656 reference: Node<'_>,
2657 ) -> bool {
2658 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2659 return false;
2660 };
2661 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
2662
2663 self.visible_identifier_candidates(file, candidate.identifier())
2664 .filter(|peer| same_logical_symbol(candidate, peer))
2665 .any(|peer| {
2666 declaration_guard_requirements(analyzer, self.cpp, peer)
2667 .into_iter()
2668 .any(|(declaration_byte, declaration_guards)| {
2669 if peer.source() == file {
2670 let (start, end) = if declaration_byte <= reference.start_byte() {
2671 (declaration_byte, reference.start_byte())
2672 } else {
2673 (reference.start_byte(), declaration_byte)
2674 };
2675 return guard_requirements_hold_at_reference(
2676 &declaration_guards,
2677 reference_guards.as_ref(),
2678 ) && self.preprocessor_guards_stable_between(
2679 file,
2680 start,
2681 end,
2682 &declaration_guards,
2683 );
2684 }
2685 if self
2686 .include_activation_for_source(
2687 self.cpp,
2688 file,
2689 prepared.as_ref(),
2690 peer.source(),
2691 )
2692 .is_some_and(|activation| {
2693 activation <= reference.start_byte()
2694 && guard_requirements_hold_at_reference(
2695 &declaration_guards,
2696 reference_guards.as_ref(),
2697 )
2698 && self.preprocessor_guards_stable_between(
2699 file,
2700 0,
2701 reference.start_byte(),
2702 &declaration_guards,
2703 )
2704 })
2705 {
2706 return true;
2707 }
2708 self.conditional_include_projections_for_source(
2709 file,
2710 prepared.as_ref(),
2711 peer.source(),
2712 )
2713 .iter()
2714 .any(|projection| {
2715 let Some(required_guards) = merge_preprocessor_guards(
2716 &projection.required_guards,
2717 &declaration_guards,
2718 ) else {
2719 return false;
2720 };
2721 projection.activation_byte <= reference.start_byte()
2722 && guard_requirements_hold_at_reference(
2723 &required_guards,
2724 reference_guards.as_ref(),
2725 )
2726 && self.preprocessor_guards_stable_between(
2727 file,
2728 0,
2729 reference.start_byte(),
2730 &required_guards,
2731 )
2732 })
2733 })
2734 })
2735 }
2736
2737 pub fn type_candidate_may_be_visible_before_reference(
2738 &self,
2739 analyzer: &CppGraphSource<'_>,
2740 file: &ProjectFile,
2741 candidate: &CodeUnit,
2742 reference_byte: usize,
2743 ) -> bool {
2744 let Some(prepared) = self.cpp.prepared_syntax(file) else {
2745 return false;
2746 };
2747 let root = prepared.tree().root_node();
2748 let end_byte = reference_byte
2749 .saturating_add(1)
2750 .min(prepared.source().len());
2751 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
2752 return false;
2753 };
2754 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
2755 }
2756
2757 pub fn preprocessor_guards_stable_between(
2758 &self,
2759 file: &ProjectFile,
2760 start_byte: usize,
2761 end_byte: usize,
2762 guards: &HashSet<PreprocessorGuard>,
2763 ) -> bool {
2764 if guards.is_empty() || start_byte >= end_byte {
2765 return true;
2766 }
2767 let cell = self.macro_event_cell(file);
2768 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2769 let mut visited = HashSet::from_iter([file.clone()]);
2770 !events.iter().any(|event| {
2771 event.byte() >= start_byte
2772 && event.byte() < end_byte
2773 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
2774 })
2775 }
2776
2777 fn macro_event_may_mutate_guards(
2778 &self,
2779 event: &MacroEvent,
2780 guards: &HashSet<PreprocessorGuard>,
2781 visited: &mut HashSet<ProjectFile>,
2782 ) -> bool {
2783 match event {
2784 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
2785 guards.iter().any(|guard| guard.may_depend_on_macro(name))
2786 }
2787 MacroEvent::Include { targets, .. } => {
2788 targets.is_empty()
2789 || targets
2790 .iter()
2791 .any(|target| self.source_may_mutate_guards(target, guards, visited))
2792 }
2793 MacroEvent::Invalidate { .. } => true,
2794 }
2795 }
2796
2797 fn source_may_mutate_guards(
2798 &self,
2799 file: &ProjectFile,
2800 guards: &HashSet<PreprocessorGuard>,
2801 visited: &mut HashSet<ProjectFile>,
2802 ) -> bool {
2803 if !visited.insert(file.clone()) {
2804 return false;
2805 }
2806 let cell = self.macro_event_cell(file);
2807 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2808 events
2809 .iter()
2810 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
2811 }
2812
2813 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
2814 let normalized = normalize_reference_name(raw_name)?;
2815 self.type_candidates(file, &normalized)
2816 .into_iter()
2817 .next()
2818 .cloned()
2819 }
2820
2821 pub fn resolve_type_node_result(
2822 &self,
2823 file: &ProjectFile,
2824 node: Node<'_>,
2825 source: &str,
2826 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
2827 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
2828 return Ok(None);
2829 };
2830 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
2831 return Ok(Some(primary));
2832 };
2833 self.resolve_template_arguments(file, primary, &arguments)
2834 .map(Some)
2835 }
2836
2837 pub fn resolve_type_node_primary(
2838 &self,
2839 file: &ProjectFile,
2840 node: Node<'_>,
2841 source: &str,
2842 ) -> Option<CodeUnit> {
2843 let components = cpp_type_name_components(node, source)?;
2844 self.resolve_type(file, &components.join("::"))
2845 }
2846
2847 pub fn resolve_template_arguments(
2848 &self,
2849 file: &ProjectFile,
2850 primary: CodeUnit,
2851 arguments: &[CppTemplateExpression],
2852 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
2853 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
2854 }
2855
2856 fn resolve_template_arguments_inner(
2857 &self,
2858 file: &ProjectFile,
2859 primary: CodeUnit,
2860 arguments: &[CppTemplateExpression],
2861 seen_aliases: &mut HashSet<CodeUnit>,
2862 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
2863 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
2864 && let Some(alias_target) = &metadata.alias_target
2865 {
2866 if !seen_aliases.insert(primary.clone()) {
2867 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
2868 }
2869 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
2870 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
2871 let target_name = alias_target.components.join("::");
2872 let target_primary = if alias_target.global {
2873 unique_logical_type_candidate(self.type_candidates(file, &target_name))
2874 } else {
2875 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
2876 };
2877 let Some(target_primary) = target_primary else {
2878 return Ok(primary);
2882 };
2883 let Some(target_arguments) = &alias_target.arguments else {
2884 return Ok(target_primary);
2885 };
2886 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
2887 .ok_or(CppTemplateResolutionError::Substitution)?;
2888 return self.resolve_template_arguments_inner(
2889 file,
2890 target_primary,
2891 &target_arguments,
2892 seen_aliases,
2893 );
2894 }
2895
2896 let primary_fq_name = self
2897 .cpp_template_metadata
2898 .get(&primary)
2899 .map(|metadata| metadata.primary_fq_name.clone())
2900 .unwrap_or_else(|| primary.fq_name());
2901 let has_specialization_metadata = self
2902 .cpp_template_families
2903 .get(&primary_fq_name)
2904 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
2905 if !has_specialization_metadata {
2906 return Ok(primary);
2907 }
2908 self.select_template_specialization(file, &primary, arguments)
2909 }
2910
2911 fn select_template_specialization(
2912 &self,
2913 file: &ProjectFile,
2914 resolved: &CodeUnit,
2915 explicit_arguments: &[CppTemplateExpression],
2916 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
2917 let primary_fq_name = self
2918 .cpp_template_metadata
2919 .get(resolved)
2920 .map(|metadata| metadata.primary_fq_name.clone())
2921 .unwrap_or_else(|| resolved.fq_name());
2922 let family = self
2923 .cpp_template_families
2924 .get(&primary_fq_name)
2925 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
2926 let primary_candidates = family
2927 .iter()
2928 .filter_map(|unit| {
2929 let metadata = self.cpp_template_metadata.get(unit)?;
2930 (metadata.specialization_arguments.is_empty() && self.is_visible(file, unit))
2931 .then_some((unit, metadata))
2932 })
2933 .collect::<Vec<_>>();
2934 let primary_unit = primary_candidates
2935 .iter()
2936 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
2937 .or_else(|| {
2938 primary_candidates
2939 .iter()
2940 .map(|(unit, _)| *unit)
2941 .min_by_key(|unit| {
2942 (
2943 unit.source().to_string(),
2944 unit.signature().unwrap_or_default(),
2945 )
2946 })
2947 })
2948 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
2949 let primary_parameters =
2950 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
2951 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
2952 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
2953 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
2954
2955 let mut applicable = Vec::new();
2956 for unit in family {
2957 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
2958 continue;
2959 };
2960 if metadata.specialization_arguments.is_empty() || !self.is_visible(file, unit) {
2961 continue;
2962 }
2963 if !cpp_specialization_matches(metadata, &expanded) {
2964 continue;
2965 }
2966 applicable.push((unit, metadata));
2967 }
2968 if applicable.is_empty() {
2969 return Ok(primary_unit.clone());
2970 }
2971
2972 let winners = applicable
2977 .iter()
2978 .filter(|(candidate, candidate_metadata)| {
2979 applicable.iter().all(|(other, other_metadata)| {
2980 same_visible_symbol(candidate, other)
2981 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
2982 })
2983 })
2984 .copied()
2985 .collect::<Vec<_>>();
2986 let Some((selected, _)) = winners.first() else {
2987 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
2990 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
2991 });
2992 };
2993 if winners
2994 .iter()
2995 .any(|(unit, _)| !same_visible_symbol(unit, selected))
2996 {
2997 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
2998 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
2999 });
3000 }
3001 Ok((*selected).clone())
3002 }
3003
3004 pub fn resolve_type_components_lexically(
3005 &self,
3006 analyzer: &CppGraphSource<'_>,
3007 file: &ProjectFile,
3008 components: &[String],
3009 global: bool,
3010 lexical_scope: &[String],
3011 ) -> LexicalTypeResolution {
3012 self.resolve_type_components_lexically_inner(
3013 analyzer,
3014 file,
3015 components,
3016 global,
3017 lexical_scope,
3018 TypeCandidateResolution::Canonical,
3019 )
3020 }
3021
3022 pub fn resolve_type_components_lexically_for_forward(
3023 &self,
3024 analyzer: &CppGraphSource<'_>,
3025 file: &ProjectFile,
3026 components: &[String],
3027 global: bool,
3028 lexical_scope: &[String],
3029 ) -> LexicalTypeResolution {
3030 self.resolve_type_components_lexically_inner(
3031 analyzer,
3032 file,
3033 components,
3034 global,
3035 lexical_scope,
3036 TypeCandidateResolution::PreserveAlias,
3037 )
3038 }
3039
3040 pub fn resolve_type_components_lexically_for_target(
3041 &self,
3042 analyzer: &CppGraphSource<'_>,
3043 file: &ProjectFile,
3044 components: &[String],
3045 global: bool,
3046 lexical_scope: &[String],
3047 target: &CodeUnit,
3048 ) -> LexicalTypeResolution {
3049 #[cfg(any(test, feature = "test-support"))]
3050 self.target_preserving_type_resolution_count
3051 .fetch_add(1, Ordering::Relaxed);
3052 self.resolve_type_components_lexically_inner(
3053 analyzer,
3054 file,
3055 components,
3056 global,
3057 lexical_scope,
3058 TypeCandidateResolution::PreserveTarget(target),
3059 )
3060 }
3061
3062 pub fn coarse_unqualified_type_reference_may_resolve(
3063 &self,
3064 file: &ProjectFile,
3065 name: &str,
3066 ) -> bool {
3067 if name.is_empty() {
3068 return true;
3069 }
3070 self.visible_identifier_candidates(file, name)
3071 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
3072 || self.visible_parser_alias_name_is_visible(file, name)
3073 }
3074
3075 #[allow(clippy::too_many_arguments)]
3076 pub fn structured_type_reference_may_resolve_to_target(
3077 &self,
3078 analyzer: &CppGraphSource<'_>,
3079 file: &ProjectFile,
3080 components: &[String],
3081 global: bool,
3082 lexical_scope: &[String],
3083 target: &CodeUnit,
3084 ) -> bool {
3085 if components.is_empty() {
3086 return true;
3087 }
3088 let Some(terminal) = components.last() else {
3089 return true;
3090 };
3091 let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
3092 if parser_alias_visible
3093 && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
3094 {
3095 return true;
3096 }
3097 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
3098 .map(|qualified| qualified.join("::"))
3099 .collect::<Vec<_>>();
3100 let target_name = cpp_name_for(target);
3101 if qualified_tiers
3102 .iter()
3103 .any(|qualified| qualified == &target_name)
3104 {
3105 return true;
3106 }
3107
3108 let mut saw_shape_candidate = parser_alias_visible;
3109 for candidate in self.visible_identifier_candidates(file, terminal) {
3110 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3111 {
3112 continue;
3113 }
3114 let candidate_name = cpp_name_for(candidate);
3115 let shape_matches = if global || components.len() > 1 {
3116 qualified_tiers
3117 .iter()
3118 .any(|qualified| qualified == &candidate_name)
3119 } else {
3120 true
3121 };
3122 if !shape_matches {
3123 continue;
3124 }
3125 saw_shape_candidate = true;
3126 if same_visible_symbol(candidate, target)
3127 || self.compatible_primary_template_redeclarations(candidate, target)
3128 || (declared_type_alias(analyzer, candidate)
3129 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
3130 {
3131 return true;
3132 }
3133 }
3134
3135 !saw_shape_candidate
3136 }
3137
3138 pub fn target_preserving_reference_namespace(
3139 &self,
3140 analyzer: &CppGraphSource<'_>,
3141 file: &ProjectFile,
3142 identifier: &str,
3143 target: &CodeUnit,
3144 ) -> Option<Vec<String>> {
3145 let mut namespace = None;
3146 for candidate in self.visible_identifier_candidates(file, identifier) {
3147 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
3148 {
3149 continue;
3150 }
3151 if !(same_visible_symbol(candidate, target)
3152 || self.compatible_primary_template_redeclarations(candidate, target)
3153 || declared_type_alias(analyzer, candidate)
3154 && self.structured_alias_primary_preserves_target(
3155 analyzer, file, candidate, target,
3156 ))
3157 {
3158 continue;
3159 }
3160 if namespace
3161 .as_ref()
3162 .is_some_and(|existing| existing != candidate.package_name())
3163 {
3164 return None;
3165 }
3166 namespace = Some(candidate.package_name().to_string());
3167 }
3168 let namespace = namespace?;
3169 Some(
3170 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
3171 brokk_bifrost_core::analyzer::Language::Cpp,
3172 &namespace,
3173 ),
3174 )
3175 }
3176
3177 pub fn resolve_imported_type_candidate(
3178 &self,
3179 analyzer: &CppGraphSource<'_>,
3180 file: &ProjectFile,
3181 target: &CodeUnit,
3182 target_components: &[String],
3183 direct_target: Option<&CodeUnit>,
3184 preserve_alias: bool,
3185 ) -> LexicalTypeResolution {
3186 let candidates = [target];
3187 let resolution = if preserve_alias {
3188 TypeCandidateResolution::PreserveAlias
3189 } else {
3190 direct_target.map_or(
3191 TypeCandidateResolution::Canonical,
3192 TypeCandidateResolution::PreserveTarget,
3193 )
3194 };
3195 let Some(unit) = self.resolve_type_candidates(analyzer, file, &candidates, resolution)
3196 else {
3197 return LexicalTypeResolution::Ambiguous;
3198 };
3199 LexicalTypeResolution::Resolved {
3200 unit,
3201 components: target_components.to_vec(),
3202 candidates: vec![target.clone()],
3203 }
3204 }
3205
3206 fn resolve_type_components_lexically_inner(
3207 &self,
3208 analyzer: &CppGraphSource<'_>,
3209 file: &ProjectFile,
3210 components: &[String],
3211 global: bool,
3212 lexical_scope: &[String],
3213 resolution: TypeCandidateResolution<'_>,
3214 ) -> LexicalTypeResolution {
3215 if components.is_empty() {
3216 return LexicalTypeResolution::Missing;
3217 }
3218 let mut injected = self.resolve_injected_class_name(
3228 analyzer,
3229 file,
3230 components,
3231 global,
3232 lexical_scope,
3233 resolution,
3234 );
3235 for (tier_index, qualified) in
3236 lexical_component_tiers(components, global, lexical_scope).enumerate()
3237 {
3238 let prefix_len = qualified.len().saturating_sub(components.len());
3239 if injected
3240 .as_ref()
3241 .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
3242 {
3243 return injected
3244 .take()
3245 .expect("injected class resolution was just present")
3246 .1;
3247 }
3248 let qualified_name = qualified.join("::");
3249 let candidates = self
3250 .type_candidates(file, &qualified_name)
3251 .into_iter()
3252 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3253 .collect::<Vec<_>>();
3254 if candidates.is_empty() {
3255 if tier_index == 0 && !global && components.len() == 1 {
3256 match self.resolve_inherited_type_for_lexical_scope(
3257 analyzer,
3258 file,
3259 lexical_scope,
3260 &components[0],
3261 resolution,
3262 ) {
3263 LexicalTypeResolution::Missing => {}
3264 inherited => return inherited,
3265 }
3266 }
3267 continue;
3268 }
3269 let unit = self.resolve_type_candidates(analyzer, file, &candidates, resolution);
3270 let Some(unit) = unit else {
3271 return LexicalTypeResolution::Ambiguous;
3272 };
3273 return LexicalTypeResolution::Resolved {
3274 unit,
3275 components: qualified,
3276 candidates: candidates.into_iter().cloned().collect(),
3277 };
3278 }
3279 LexicalTypeResolution::Missing
3280 }
3281
3282 fn resolve_injected_class_name(
3283 &self,
3284 analyzer: &CppGraphSource<'_>,
3285 file: &ProjectFile,
3286 components: &[String],
3287 global: bool,
3288 lexical_scope: &[String],
3289 resolution: TypeCandidateResolution<'_>,
3290 ) -> Option<(usize, LexicalTypeResolution)> {
3291 if global
3292 || components.len() != 1
3293 || file.rel_path().extension().is_some_and(|ext| ext == "c")
3294 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
3295 {
3296 return None;
3297 }
3298 let name = components.first()?;
3299 let mut matches: Vec<&CodeUnit> = Vec::new();
3300 let mut owner_len = 0;
3301 for candidate in self.visible_identifier_candidates(file, name) {
3302 if !candidate.is_class()
3303 || declared_type_alias(analyzer, candidate)
3304 || candidate.identifier() != name
3305 {
3306 continue;
3307 }
3308 let candidate_scope = canonical_cpp_scope_components(candidate);
3309 if candidate_scope.len() > lexical_scope.len()
3310 || !lexical_scope.starts_with(&candidate_scope)
3311 || candidate_scope.last().is_none_or(|last| last != name)
3312 {
3313 continue;
3314 }
3315 if candidate_scope.len() > owner_len {
3316 owner_len = candidate_scope.len();
3317 matches.clear();
3318 }
3319 if candidate_scope.len() == owner_len
3320 && !matches
3321 .iter()
3322 .any(|existing| same_logical_symbol(existing, candidate))
3323 {
3324 matches.push(candidate);
3325 }
3326 }
3327 if matches.is_empty() {
3328 return None;
3329 }
3330 if owner_len >= lexical_scope.len() {
3338 return None;
3339 }
3340 let owner_components = lexical_scope[..owner_len].to_vec();
3341 let resolved = self.resolve_type_candidates(analyzer, file, &matches, resolution);
3342 let resolution = resolved.map_or(LexicalTypeResolution::Ambiguous, |unit| {
3343 LexicalTypeResolution::Resolved {
3344 unit,
3345 components: owner_components,
3346 candidates: matches.into_iter().cloned().collect(),
3347 }
3348 });
3349 Some((owner_len, resolution))
3350 }
3351
3352 fn resolve_inherited_type_for_lexical_scope(
3353 &self,
3354 analyzer: &CppGraphSource<'_>,
3355 file: &ProjectFile,
3356 lexical_scope: &[String],
3357 name: &str,
3358 resolution: TypeCandidateResolution<'_>,
3359 ) -> LexicalTypeResolution {
3360 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
3361 return LexicalTypeResolution::Missing;
3362 };
3363 let lexical_owner_name = lexical_scope.join("::");
3364 if lexical_owner_name.is_empty() {
3365 return LexicalTypeResolution::Missing;
3366 }
3367 let owner_candidates = self
3368 .type_candidates(file, &lexical_owner_name)
3369 .into_iter()
3370 .filter(|candidate| {
3371 canonical_cpp_name_matches(candidate, &lexical_owner_name)
3372 && !declared_type_alias(analyzer, candidate)
3373 })
3374 .collect::<Vec<_>>();
3375 if owner_candidates.is_empty() {
3376 return LexicalTypeResolution::Missing;
3377 }
3378 let Some(lexical_owner) = unique_logical_type_candidate(owner_candidates) else {
3379 return LexicalTypeResolution::Ambiguous;
3380 };
3381
3382 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
3383 let mut visited_owners = HashSet::default();
3384 while !frontier.is_empty() {
3385 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
3386 let mut next_frontier = Vec::new();
3387 for owner in frontier {
3388 if !visited_owners.insert(owner.fq_name()) {
3389 continue;
3390 }
3391 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
3392 let candidates = self
3393 .type_candidates(file, &qualified_name)
3394 .into_iter()
3395 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
3396 .collect::<Vec<_>>();
3397 if candidates.is_empty() {
3398 for ancestor in hierarchy.get_direct_ancestors(&owner) {
3399 if !next_frontier
3400 .iter()
3401 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
3402 {
3403 next_frontier.push(ancestor);
3404 }
3405 }
3406 continue;
3407 }
3408 let Some(unit) =
3409 self.resolve_type_candidates(analyzer, file, &candidates, resolution)
3410 else {
3411 return LexicalTypeResolution::Ambiguous;
3412 };
3413 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
3414 }
3415 if let Some((unit, candidates)) = level_matches.first().cloned() {
3416 let Some(first_declaration) = candidates.first() else {
3417 return LexicalTypeResolution::Ambiguous;
3418 };
3419 if !level_matches.iter().all(|(_, declarations)| {
3420 declarations
3421 .iter()
3422 .all(|declaration| same_logical_symbol(first_declaration, declaration))
3423 }) {
3424 return LexicalTypeResolution::Ambiguous;
3425 }
3426 let mut components = lexical_scope.to_vec();
3427 components.push(name.to_string());
3428 return LexicalTypeResolution::Resolved {
3429 unit,
3430 components,
3431 candidates,
3432 };
3433 }
3434 frontier = next_frontier;
3435 }
3436 LexicalTypeResolution::Missing
3437 }
3438
3439 fn resolve_type_candidates(
3440 &self,
3441 analyzer: &CppGraphSource<'_>,
3442 file: &ProjectFile,
3443 candidates: &[&CodeUnit],
3444 resolution: TypeCandidateResolution<'_>,
3445 ) -> Option<CodeUnit> {
3446 match resolution {
3447 TypeCandidateResolution::Canonical => {
3448 self.unique_canonical_type_candidate(analyzer, file, candidates)
3449 }
3450 TypeCandidateResolution::PreserveAlias => {
3451 unique_type_candidate_preserving_alias(analyzer, candidates)
3452 }
3453 TypeCandidateResolution::PreserveTarget(target) => {
3454 self.unique_type_candidate_preserving_target(analyzer, file, candidates, target)
3455 }
3456 }
3457 }
3458
3459 pub fn resolve_callable_value_components_lexically(
3460 &self,
3461 analyzer: &CppGraphSource<'_>,
3462 file: &ProjectFile,
3463 owner_components: &[String],
3464 member_name: &str,
3465 global: bool,
3466 lexical_scope: &[String],
3467 ) -> LexicalCallableValueResolution {
3468 if owner_components.is_empty() || member_name.is_empty() {
3469 return LexicalCallableValueResolution::Missing;
3470 }
3471 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
3472 let owner_name = qualified_owner.join("::");
3473 let type_candidates = self
3474 .type_candidates(file, &owner_name)
3475 .into_iter()
3476 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
3477 .collect::<Vec<_>>();
3478 let resolved_type = if type_candidates.is_empty() {
3479 None
3480 } else {
3481 let Some(unit) =
3482 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
3483 else {
3484 return LexicalCallableValueResolution::Ambiguous;
3485 };
3486 Some(unit)
3487 };
3488
3489 let mut qualified_callable = qualified_owner;
3490 qualified_callable.push(member_name.to_string());
3491 let callable_name = qualified_callable.join("::");
3492 let free_function = self
3493 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
3494 .into_iter()
3495 .find(|candidate| {
3496 canonical_cpp_name_matches(candidate, &callable_name)
3497 && type_owner_of(analyzer, candidate).is_none()
3498 })
3499 .cloned();
3500
3501 match (resolved_type, free_function) {
3502 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
3503 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
3504 (None, Some(function)) => {
3505 return LexicalCallableValueResolution::FreeFunction(function);
3506 }
3507 (None, None) => {}
3508 }
3509 }
3510 LexicalCallableValueResolution::Missing
3511 }
3512
3513 fn resolve_type_for_declaration(
3514 &self,
3515 visible_from: &ProjectFile,
3516 declaration: &CodeUnit,
3517 raw_name: &str,
3518 ) -> Option<CodeUnit> {
3519 let normalized = normalize_reference_name(raw_name)?;
3520 if !normalized.contains("::")
3521 && let Some(namespace) = cpp_namespace_for(declaration)
3522 {
3523 for prefix in namespace_prefixes(&namespace) {
3524 let qualified = format!("{prefix}::{normalized}");
3525 if let Some(unit) = self
3526 .type_candidates(visible_from, &qualified)
3527 .into_iter()
3528 .next()
3529 {
3530 return Some(unit.clone());
3531 }
3532 }
3533 }
3534 self.resolve_type(visible_from, raw_name)
3535 }
3536
3537 fn resolve_unique_canonical_type_for_declaration(
3538 &self,
3539 analyzer: &CppGraphSource<'_>,
3540 visible_from: &ProjectFile,
3541 declaration: &CodeUnit,
3542 raw_name: &str,
3543 ) -> Option<CodeUnit> {
3544 let mut current =
3545 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
3546 let mut seen_aliases = HashSet::default();
3547 loop {
3548 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
3549 return current.is_class().then_some(current);
3550 };
3551 if matches!(target, StructuredAliasTarget::Builtin) {
3552 return current.is_class().then_some(current);
3553 }
3554 if !seen_aliases.insert(current.clone()) {
3555 return None;
3556 }
3557 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
3558 }
3559 }
3560
3561 pub fn canonical_type_unit(
3562 &self,
3563 analyzer: &CppGraphSource<'_>,
3564 visible_from: &ProjectFile,
3565 unit: &CodeUnit,
3566 ) -> Option<CodeUnit> {
3567 let mut current = unit.clone();
3568 let mut seen_aliases = HashSet::default();
3569 loop {
3570 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
3571 return current.is_class().then_some(current);
3572 };
3573 if matches!(target, StructuredAliasTarget::Builtin) {
3574 return current.is_class().then_some(current);
3575 }
3576 if !seen_aliases.insert(current.clone()) {
3577 return None;
3578 }
3579 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
3580 }
3581 }
3582
3583 pub fn canonical_visible_full_type_unit(
3584 &self,
3585 analyzer: &CppGraphSource<'_>,
3586 visible_from: &ProjectFile,
3587 unit: &CodeUnit,
3588 ) -> Option<CodeUnit> {
3589 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
3590 if cpp_class_declaration_strength(analyzer, &canonical)
3591 != CppClassDeclarationStrength::Forward
3592 {
3593 return Some(canonical);
3594 }
3595 let mut full = Vec::new();
3596 for candidate in self
3597 .visible_identifier_candidates(visible_from, canonical.identifier())
3598 .filter(|candidate| {
3599 candidate.is_class()
3600 && candidate.fq_name() == canonical.fq_name()
3601 && cpp_class_declaration_strength(analyzer, candidate)
3602 == CppClassDeclarationStrength::Full
3603 })
3604 {
3605 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
3606 full.push(candidate.clone());
3607 }
3608 }
3609 match full.len() {
3610 0 => Some(canonical),
3611 1 => full.pop(),
3612 _ => None,
3613 }
3614 }
3615
3616 fn resolve_structured_alias_target(
3617 &self,
3618 visible_from: &ProjectFile,
3619 declaration: &CodeUnit,
3620 target: &StructuredAliasTarget,
3621 ) -> Option<CodeUnit> {
3622 let primary = self.resolve_structured_alias_primary(visible_from, declaration, target)?;
3623 let StructuredAliasTarget::Named { arguments, .. } = target else {
3624 return None;
3625 };
3626 match arguments {
3627 Some(arguments) => self
3628 .resolve_template_arguments(visible_from, primary, arguments)
3629 .ok(),
3630 None => Some(primary),
3631 }
3632 }
3633
3634 fn resolve_structured_alias_primary(
3635 &self,
3636 visible_from: &ProjectFile,
3637 declaration: &CodeUnit,
3638 target: &StructuredAliasTarget,
3639 ) -> Option<CodeUnit> {
3640 let StructuredAliasTarget::Named {
3641 components, global, ..
3642 } = target
3643 else {
3644 return None;
3645 };
3646 let qualified = components.join("::");
3647 if *global {
3648 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
3649 } else {
3650 self.resolve_unique_type_for_declaration(visible_from, declaration, &qualified)
3651 }
3652 }
3653
3654 pub fn structured_alias_primary_preserves_target(
3655 &self,
3656 analyzer: &CppGraphSource<'_>,
3657 visible_from: &ProjectFile,
3658 candidate: &CodeUnit,
3659 target: &CodeUnit,
3660 ) -> bool {
3661 let mut current = candidate.clone();
3662 let mut seen = HashSet::default();
3663 let mut matched_target = false;
3664 loop {
3665 if same_visible_symbol(¤t, target)
3666 || self.compatible_primary_template_redeclarations(¤t, target)
3667 {
3668 matched_target = true;
3669 }
3670 if !seen.insert(current.clone()) {
3671 return false;
3672 }
3673 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
3674 return matched_target;
3675 };
3676 if matches!(alias_target, StructuredAliasTarget::Builtin) {
3677 return matched_target;
3678 };
3679 let Some(primary) =
3680 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
3681 else {
3682 return matched_target;
3688 };
3689 current = primary;
3690 }
3691 }
3692
3693 pub fn structured_class_alias_resolves_to_target(
3694 &self,
3695 analyzer: &CppGraphSource<'_>,
3696 visible_from: &ProjectFile,
3697 alias: &CodeUnit,
3698 target: &CodeUnit,
3699 ) -> bool {
3700 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
3701 return false;
3702 };
3703 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
3704 return false;
3705 };
3706 let StructuredAliasTarget::Named {
3707 components, global, ..
3708 } = &alias_target
3709 else {
3710 return false;
3711 };
3712 let lexical_scope = canonical_cpp_scope_components(&owner);
3713 match self.resolve_type_components_lexically_for_target(
3714 analyzer,
3715 visible_from,
3716 components,
3717 *global,
3718 &lexical_scope,
3719 target,
3720 ) {
3721 LexicalTypeResolution::Resolved {
3722 unit, candidates, ..
3723 } => {
3724 same_visible_symbol(&unit, target)
3725 || self.same_template_member_identity(analyzer, &unit, target)
3726 || candidates.iter().any(|candidate| {
3727 same_visible_symbol(candidate, target)
3728 || self.same_template_member_identity(analyzer, candidate, target)
3729 })
3730 }
3731 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
3732 self.structured_alias_primary_preserves_target(
3733 analyzer,
3734 visible_from,
3735 alias,
3736 target,
3737 ) || self.flattened_macro_namespace_alias_target_matches(
3738 analyzer,
3739 visible_from,
3740 alias,
3741 &alias_target,
3742 target,
3743 )
3744 }
3745 }
3746 }
3747
3748 fn flattened_macro_namespace_alias_target_matches(
3749 &self,
3750 analyzer: &CppGraphSource<'_>,
3751 visible_from: &ProjectFile,
3752 alias: &CodeUnit,
3753 alias_target: &StructuredAliasTarget,
3754 target: &CodeUnit,
3755 ) -> bool {
3756 let StructuredAliasTarget::Named {
3757 components,
3758 global: false,
3759 arguments: None,
3760 } = alias_target
3761 else {
3762 return false;
3763 };
3764 let Some((target_name, namespace_components)) = components.split_last() else {
3765 return false;
3766 };
3767 if namespace_components.is_empty()
3768 || target_name != target.identifier()
3769 || alias.source() != target.source()
3770 || alias.source() != visible_from
3771 || !target.is_class()
3772 || declared_type_alias(analyzer, target)
3773 {
3774 return false;
3775 }
3776 if self
3777 .resolve_structured_alias_target(visible_from, alias, alias_target)
3778 .is_some()
3779 {
3780 return false;
3781 }
3782
3783 let alias_ranges = analyzer.ranges(alias);
3784 let target_ranges = analyzer.ranges(target);
3785 if alias_ranges.is_empty() || target_ranges.is_empty() {
3786 return false;
3787 }
3788 let alias_start = alias_ranges
3789 .iter()
3790 .map(|range| range.start_byte)
3791 .min()
3792 .expect("non-empty alias ranges have a minimum");
3793 let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
3794 return false;
3795 };
3796 let root = prepared.tree().root_node();
3797 let has_matching_declaration = target_ranges
3798 .iter()
3799 .filter(|range| range.end_byte <= alias_start)
3800 .filter_map(|range| node_for_exact_range(root, range))
3801 .any(|node| {
3802 flattened_macro_namespace_components(node, prepared.source())
3803 .is_some_and(|recovered| recovered == namespace_components)
3804 });
3805 if !has_matching_declaration {
3806 return false;
3807 }
3808
3809 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
3810 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
3811 guard_requirement_sets_match(&alias_guards, &target_guards)
3812 }
3813
3814 pub fn template_alias_arguments_preserve_target(
3815 &self,
3816 analyzer: &CppGraphSource<'_>,
3817 visible_from: &ProjectFile,
3818 alias: &CodeUnit,
3819 arguments: &[CppTemplateExpression],
3820 target: &CodeUnit,
3821 ) -> bool {
3822 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
3823 return false;
3824 };
3825 if metadata.alias_target.is_none()
3826 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
3827 {
3828 return false;
3829 }
3830 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
3831 }
3832
3833 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
3834 self.cpp_template_metadata
3835 .get(unit)
3836 .is_some_and(|metadata| metadata.specialization_arguments.is_empty())
3837 }
3838
3839 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
3840 self.cpp_template_metadata
3841 .get(unit)
3842 .is_some_and(|metadata| !metadata.specialization_arguments.is_empty())
3843 }
3844
3845 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
3846 same_visible_symbol(left, right)
3847 || self.compatible_primary_template_redeclarations(left, right)
3848 }
3849
3850 pub fn same_template_member_identity(
3851 &self,
3852 analyzer: &CppGraphSource<'_>,
3853 left: &CodeUnit,
3854 right: &CodeUnit,
3855 ) -> bool {
3856 if same_visible_symbol(left, right) {
3857 return true;
3858 }
3859 if left.kind() != right.kind()
3860 || left.identifier() != right.identifier()
3861 || left.signature() != right.signature()
3862 {
3863 return false;
3864 }
3865 let (Some(left_owner), Some(right_owner)) =
3866 (analyzer.parent_of(left), analyzer.parent_of(right))
3867 else {
3868 return false;
3869 };
3870 left_owner.is_class()
3871 && right_owner.is_class()
3872 && self.same_template_owner_identity(&left_owner, &right_owner)
3873 }
3874
3875 fn unique_canonical_type_candidate(
3876 &self,
3877 analyzer: &CppGraphSource<'_>,
3878 visible_from: &ProjectFile,
3879 candidates: &[&CodeUnit],
3880 ) -> Option<CodeUnit> {
3881 let mut canonical = Vec::new();
3882 for candidate in candidates {
3883 let resolved = self.canonical_type_unit(analyzer, visible_from, candidate)?;
3884 if canonical
3885 .iter()
3886 .any(|existing| same_visible_symbol(existing, &resolved))
3887 {
3888 continue;
3889 }
3890 canonical.push(resolved);
3891 if canonical.len() > 1 {
3892 return None;
3893 }
3894 }
3895 canonical.pop()
3896 }
3897
3898 pub fn unique_type_candidate_preserving_target(
3899 &self,
3900 analyzer: &CppGraphSource<'_>,
3901 visible_from: &ProjectFile,
3902 candidates: &[&CodeUnit],
3903 target: &CodeUnit,
3904 ) -> Option<CodeUnit> {
3905 if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
3916 return Some(target.clone());
3917 }
3918 let mut resolved_candidates = Vec::new();
3919 for candidate in candidates {
3920 let resolved =
3921 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)?;
3922 if resolved_candidates
3923 .iter()
3924 .any(|existing| same_visible_symbol(existing, &resolved))
3925 {
3926 continue;
3927 }
3928 resolved_candidates.push(resolved);
3929 if resolved_candidates.len() > 1 {
3930 return None;
3931 }
3932 }
3933 resolved_candidates.pop()
3934 }
3935
3936 pub fn alternate_same_fqn_type_declarations(
3937 &self,
3938 analyzer: &CppGraphSource<'_>,
3939 candidates: &[&CodeUnit],
3940 target: &CodeUnit,
3941 ) -> bool {
3942 let Some(first) = candidates.first() else {
3943 return false;
3944 };
3945 let same_api = first.kind() == target.kind()
3946 && first.fq_name() == target.fq_name()
3947 && first.source() == target.source()
3948 && candidates.iter().all(|candidate| {
3949 candidate.kind() == target.kind()
3950 && candidate.fq_name() == target.fq_name()
3951 && candidate.source() == target.source()
3952 })
3953 && candidates
3954 .iter()
3955 .any(|candidate| same_symbol(candidate, target))
3956 && candidates
3957 .iter()
3958 .any(|candidate| !same_logical_symbol(candidate, target));
3959 if !same_api {
3960 return false;
3961 }
3962
3963 let requirements = candidates
3964 .iter()
3965 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
3966 .collect::<Vec<_>>();
3967 requirements.len() > 1
3968 && requirements
3969 .iter()
3970 .all(|requirement| !requirement.is_empty())
3971 && requirements.iter().enumerate().all(|(index, left)| {
3972 requirements[index + 1..].iter().all(|right| {
3973 left.iter().all(|(_, left_guards)| {
3974 right.iter().all(|(_, right_guards)| {
3975 merge_preprocessor_guards(left_guards, right_guards).is_none()
3976 })
3977 })
3978 })
3979 })
3980 }
3981
3982 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
3983 let mut pending = vec![terms.to_vec()];
3984 while let Some(branch_terms) = pending.pop() {
3985 let mut normalized = Vec::new();
3986 let mut covers_branch = false;
3987 for term in branch_terms {
3988 if term.iter().any(|guard| term.contains(&guard.negated())) {
3989 continue;
3990 }
3991 if term.is_empty() {
3992 covers_branch = true;
3993 break;
3994 }
3995 if !normalized.iter().any(|existing| existing == &term) {
3996 normalized.push(term);
3997 }
3998 }
3999 if covers_branch {
4000 continue;
4001 }
4002 let Some(split_guard) = normalized
4003 .iter()
4004 .flat_map(|term| term.iter())
4005 .next()
4006 .cloned()
4007 else {
4008 return false;
4009 };
4010 let negated_guard = split_guard.negated();
4011 let mut when_defined = Vec::new();
4012 let mut when_undefined = Vec::new();
4013 for term in normalized {
4014 if term.contains(&negated_guard) {
4015 } else if term.contains(&split_guard) {
4017 let mut reduced = term.clone();
4018 reduced.remove(&split_guard);
4019 when_defined.push(reduced);
4020 } else {
4021 when_defined.push(term.clone());
4022 }
4023 if term.contains(&split_guard) {
4024 } else if term.contains(&negated_guard) {
4026 let mut reduced = term;
4027 reduced.remove(&negated_guard);
4028 when_undefined.push(reduced);
4029 } else {
4030 when_undefined.push(term);
4031 }
4032 }
4033 pending.push(when_defined);
4034 pending.push(when_undefined);
4035 }
4036 true
4037 }
4038
4039 fn declarations_share_exhaustive_conditional_family(
4040 &self,
4041 analyzer: &CppGraphSource<'_>,
4042 candidates: &[&CodeUnit],
4043 ) -> bool {
4044 let mut family_range = None;
4049 for candidate in candidates {
4050 let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
4051 return false;
4052 };
4053 let root = prepared.tree().root_node();
4054 let mut candidate_family = None;
4055 for range in analyzer.ranges(candidate) {
4056 let Some(node) = root.descendant_for_byte_range(range.start_byte, range.end_byte)
4057 else {
4058 return false;
4059 };
4060 let Some(family) = preprocessor_conditional_family_for_declaration(node) else {
4061 return false;
4062 };
4063 let key = (family.start_byte(), family.end_byte());
4064 if candidate_family.is_some_and(|existing| existing != key) {
4065 return false;
4066 }
4067 candidate_family = Some(key);
4068 }
4069 let Some(candidate_family) = candidate_family else {
4070 return false;
4071 };
4072 if family_range.is_some_and(|existing| existing != candidate_family) {
4073 return false;
4074 }
4075 family_range = Some(candidate_family);
4076 }
4077 family_range.is_some()
4078 }
4079
4080 pub fn complementary_same_fqn_type_declarations(
4081 &self,
4082 analyzer: &CppGraphSource<'_>,
4083 candidates: &[&CodeUnit],
4084 target: &CodeUnit,
4085 ) -> bool {
4086 if candidates.len() < 2
4087 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
4088 || !self.declarations_share_exhaustive_conditional_family(analyzer, candidates)
4089 {
4090 return false;
4091 }
4092 let requirements = candidates
4093 .iter()
4094 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4095 .collect::<Vec<_>>();
4096 let terms = requirements
4097 .into_iter()
4098 .flat_map(|ranges| ranges.into_iter().map(|(_, guards)| guards))
4099 .collect::<Vec<_>>();
4100 Self::preprocessor_guard_terms_cover_all_paths(&terms)
4101 }
4102
4103 fn type_candidate_preserving_target(
4104 &self,
4105 analyzer: &CppGraphSource<'_>,
4106 visible_from: &ProjectFile,
4107 candidate: &CodeUnit,
4108 target: &CodeUnit,
4109 ) -> Option<CodeUnit> {
4110 let mut current = candidate.clone();
4111 let mut matched_target = same_visible_symbol(¤t, target)
4112 || self.compatible_primary_template_redeclarations(¤t, target);
4113 let mut seen = HashSet::default();
4114 loop {
4115 if !seen.insert(current.clone()) {
4116 return None;
4117 }
4118 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
4119 return matched_target
4120 .then(|| target.clone())
4121 .or_else(|| current.is_class().then_some(current));
4122 };
4123 if self.flattened_macro_namespace_alias_target_matches(
4124 analyzer,
4125 visible_from,
4126 ¤t,
4127 &alias_target,
4128 target,
4129 ) {
4130 return Some(target.clone());
4131 }
4132 if matches!(alias_target, StructuredAliasTarget::Builtin) {
4133 return matched_target
4134 .then(|| target.clone())
4135 .or_else(|| current.is_class().then_some(current));
4136 }
4137 if !self.cpp_template_metadata.contains_key(¤t)
4145 && let Some(primary) =
4146 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
4147 && (same_visible_symbol(&primary, target)
4148 || self.compatible_primary_template_redeclarations(&primary, target))
4149 {
4150 return Some(target.clone());
4151 }
4152 if same_visible_symbol(¤t, target) {
4153 return Some(target.clone());
4154 }
4155 if self.cpp_template_metadata.contains_key(¤t) {
4156 return None;
4157 }
4158 let Some(next) =
4159 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
4160 else {
4161 return matched_target.then(|| target.clone());
4162 };
4163 current = next;
4164 matched_target |= same_visible_symbol(¤t, target)
4165 || self.compatible_primary_template_redeclarations(¤t, target);
4166 }
4167 }
4168
4169 fn compatible_primary_template_redeclarations(
4170 &self,
4171 left: &CodeUnit,
4172 right: &CodeUnit,
4173 ) -> bool {
4174 let (Some(left_metadata), Some(right_metadata)) = (
4175 self.cpp_template_metadata.get(left),
4176 self.cpp_template_metadata.get(right),
4177 ) else {
4178 return false;
4179 };
4180 left_metadata.primary_fq_name == right_metadata.primary_fq_name
4181 && left_metadata.specialization_arguments.is_empty()
4182 && right_metadata.specialization_arguments.is_empty()
4183 && cpp_reconcile_primary_template_parameters(
4184 &[(left, left_metadata), (right, right_metadata)],
4185 right,
4186 )
4187 .is_some()
4188 }
4189
4190 fn alias_candidate_may_preserve_target(
4191 &self,
4192 analyzer: &CppGraphSource<'_>,
4193 visible_from: &ProjectFile,
4194 candidate: &CodeUnit,
4195 target: &CodeUnit,
4196 ) -> bool {
4197 let mut current = candidate.clone();
4198 let mut seen = HashSet::default();
4199 loop {
4200 if same_visible_symbol(¤t, target)
4201 || self.compatible_primary_template_redeclarations(¤t, target)
4202 {
4203 return true;
4204 }
4205 if self.cpp_template_metadata.contains_key(¤t) {
4206 return true;
4207 }
4208 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
4209 return false;
4210 };
4211 let StructuredAliasTarget::Named {
4212 components,
4213 global,
4214 arguments,
4215 } = alias_target
4216 else {
4217 return false;
4218 };
4219 if arguments.is_some() || !seen.insert(current.clone()) {
4220 return true;
4221 }
4222 let qualified = components.join("::");
4223 let next = if global {
4224 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
4225 } else {
4226 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
4227 };
4228 let Some(next) = next else {
4229 return true;
4230 };
4231 current = next;
4232 }
4233 }
4234
4235 fn resolve_unique_type_for_declaration(
4236 &self,
4237 visible_from: &ProjectFile,
4238 declaration: &CodeUnit,
4239 raw_name: &str,
4240 ) -> Option<CodeUnit> {
4241 let normalized = normalize_reference_name(raw_name)?;
4242 if let Some(namespace) = cpp_namespace_for(declaration) {
4243 for prefix in namespace_prefixes(&namespace) {
4244 let qualified = format!("{prefix}::{normalized}");
4245 let candidates = self.type_candidates(visible_from, &qualified);
4246 if !candidates.is_empty() {
4247 return unique_logical_type_candidate(candidates);
4248 }
4249 }
4250 }
4251 unique_logical_type_candidate(self.type_candidates(visible_from, &normalized))
4252 }
4253
4254 pub fn resolves_to_type(
4255 &self,
4256 analyzer: &CppGraphSource<'_>,
4257 file: &ProjectFile,
4258 raw_name: &str,
4259 target: &CodeUnit,
4260 ) -> bool {
4261 let Some(normalized) = normalize_reference_name(raw_name) else {
4262 return false;
4263 };
4264 let candidates = self.type_candidates(file, &normalized);
4265 if candidates.is_empty() {
4266 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
4267 }
4268 let Some(resolved) =
4269 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
4270 else {
4271 return false;
4272 };
4273 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
4274 }
4275
4276 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
4277 let raw_target = type_alias_target_text(alias)?;
4278 let resolved = self.resolve_type_for_declaration(alias.source(), alias, raw_target)?;
4279 match resolved.kind() {
4280 CodeUnitType::Class => Some(resolved),
4281 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
4282 _ => None,
4283 }
4284 }
4285
4286 pub fn canonical_type_for_reference(
4287 &self,
4288 file: &ProjectFile,
4289 raw_name: &str,
4290 ) -> Option<CodeUnit> {
4291 let resolved = self.resolve_type(file, raw_name)?;
4292 self.alias_target(&resolved).or(Some(resolved))
4293 }
4294
4295 pub fn parser_alias_resolves_to_type(
4296 &self,
4297 analyzer: &CppGraphSource<'_>,
4298 file: &ProjectFile,
4299 raw_name: &str,
4300 target: &CodeUnit,
4301 ) -> bool {
4302 let Some(alias_name) = normalize_reference_name(raw_name) else {
4303 return false;
4304 };
4305 let Some(cpp) = analyzer.cpp else {
4306 return false;
4307 };
4308 let matches_file = |source_file: &ProjectFile| {
4309 self.file_alias_matches(cpp, source_file, &alias_name, target)
4310 };
4311 self.visible_source_files_by_root.get(file).map_or_else(
4312 || matches_file(file),
4313 |files| files.iter().any(matches_file),
4314 )
4315 }
4316
4317 fn file_alias_matches(
4318 &self,
4319 cpp: &dyn CppSource,
4320 file: &ProjectFile,
4321 alias_name: &str,
4322 target: &CodeUnit,
4323 ) -> bool {
4324 let cell = {
4325 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4326 Arc::clone(
4327 cells
4328 .entry(file.clone())
4329 .or_insert_with(|| Arc::new(OnceLock::new())),
4330 )
4331 };
4332 cell.get_or_init(|| {
4333 #[cfg(any(test, feature = "test-support"))]
4334 {
4335 *self
4336 .alias_source_parse_counts
4337 .lock()
4338 .expect("alias source parse count lock")
4339 .entry(file.clone())
4340 .or_default() += 1;
4341 }
4342 aliases_from_prepared_source(cpp, file).into_boxed_slice()
4343 })
4344 .iter()
4345 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4346 }
4347
4348 #[cfg(any(test, feature = "test-support"))]
4349 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
4350 self.visible_source_files_by_root
4351 .get(file)
4352 .cloned()
4353 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
4354 }
4355
4356 #[cfg(any(test, feature = "test-support"))]
4357 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
4358 self.alias_source_parse_counts
4359 .lock()
4360 .expect("alias source parse count lock")
4361 .get(file)
4362 .copied()
4363 .unwrap_or(0)
4364 }
4365
4366 pub fn resolve_named(
4367 &self,
4368 file: &ProjectFile,
4369 raw_name: &str,
4370 kind: TargetKind,
4371 ) -> Option<CodeUnit> {
4372 let normalized = normalize_reference_name(raw_name)?;
4373 self.named_candidates_for_normalized(file, &normalized, kind)
4374 .into_iter()
4375 .next()
4376 .cloned()
4377 }
4378
4379 pub fn contains_named_symbol(
4380 &self,
4381 file: &ProjectFile,
4382 raw_name: &str,
4383 kind: TargetKind,
4384 target: &CodeUnit,
4385 ) -> bool {
4386 let Some(normalized) = normalize_reference_name(raw_name) else {
4387 return false;
4388 };
4389 self.named_candidates_for_normalized(file, &normalized, kind)
4390 .into_iter()
4391 .any(|unit| {
4392 matches_kind_for_lookup(unit, kind)
4393 && reference_matches_unit(&normalized, unit)
4394 && same_visible_symbol(unit, target)
4395 })
4396 }
4397
4398 pub fn named_candidates(
4399 &self,
4400 file: &ProjectFile,
4401 raw_name: &str,
4402 kind: TargetKind,
4403 ) -> Vec<CodeUnit> {
4404 let Some(normalized) = normalize_reference_name(raw_name) else {
4405 return Vec::new();
4406 };
4407 self.named_candidates_for_normalized(file, &normalized, kind)
4408 .into_iter()
4409 .cloned()
4410 .collect()
4411 }
4412
4413 pub fn resolve_known_non_target(
4414 &self,
4415 file: &ProjectFile,
4416 raw_name: &str,
4417 kind: TargetKind,
4418 target: &CodeUnit,
4419 ) -> bool {
4420 let Some(normalized) = normalize_reference_name(raw_name) else {
4421 return false;
4422 };
4423 normalized.contains("::")
4424 && self
4425 .named_candidates_for_normalized(file, &normalized, kind)
4426 .into_iter()
4427 .any(|unit| {
4428 matches_kind_for_lookup(unit, kind)
4429 && reference_matches_unit(&normalized, unit)
4430 && !same_visible_symbol(unit, target)
4431 })
4432 }
4433
4434 pub fn resolve_call_return_binding(
4435 &self,
4436 analyzer: &CppGraphSource<'_>,
4437 file: &ProjectFile,
4438 raw_name: &str,
4439 arity: usize,
4440 lexical_namespace: Option<&str>,
4441 direct_type: Option<&CodeUnit>,
4442 ) -> Option<CppScanBinding> {
4443 let normalized = normalize_reference_name(raw_name)?;
4444 let mut candidates = Vec::new();
4445 for function in
4446 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
4447 {
4448 if cpp_callable_arity(analyzer, function).accepts(arity)
4449 && !direct_type.is_some_and(|direct_type| {
4450 self.callable_is_constructor_declaration(analyzer, function)
4451 && type_owner_of(analyzer, function)
4452 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
4453 })
4454 {
4455 candidates.push(function.clone());
4456 }
4457 }
4458 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
4459 unanimous_return_binding(analyzer, self, file, &candidates)
4460 }
4461
4462 pub fn resolve_call_return_binding_without_arity(
4463 &self,
4464 analyzer: &CppGraphSource<'_>,
4465 file: &ProjectFile,
4466 raw_name: &str,
4467 lexical_namespace: Option<&str>,
4468 direct_type: Option<&CodeUnit>,
4469 ) -> (bool, Option<CppScanBinding>) {
4470 let Some(normalized) = normalize_reference_name(raw_name) else {
4471 return (false, None);
4472 };
4473 let mut candidates = self
4474 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
4475 .into_iter()
4476 .filter(|function| {
4477 function.is_function()
4478 && !direct_type.is_some_and(|direct_type| {
4479 self.callable_is_constructor_declaration(analyzer, function)
4480 && type_owner_of(analyzer, function)
4481 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
4482 })
4483 })
4484 .cloned()
4485 .collect::<Vec<_>>();
4486 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
4487 let has_candidates = !candidates.is_empty();
4488 (
4489 has_candidates,
4490 unanimous_return_binding(analyzer, self, file, &candidates),
4491 )
4492 }
4493
4494 pub fn visible_identifier_candidates<'b>(
4495 &'b self,
4496 file: &ProjectFile,
4497 identifier: &str,
4498 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
4499 self.visible_by_identifier
4500 .get(file)
4501 .and_then(|by_name| by_name.get(identifier))
4502 .into_iter()
4503 .flatten()
4504 }
4505
4506 pub fn visible_type_reference_component_names_for_target(
4513 &self,
4514 analyzer: &CppGraphSource<'_>,
4515 file: &ProjectFile,
4516 target: &CodeUnit,
4517 ) -> HashSet<String> {
4518 let mut names = HashSet::from_iter([target.identifier().to_string()]);
4519 if let Some(metadata) = self.cpp_template_metadata.get(target) {
4520 names.insert(metadata.primary_name.clone());
4521 }
4522
4523 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
4524 for (identifier, candidates) in by_identifier {
4525 if candidates.iter().any(|candidate| {
4526 (candidate.is_class()
4527 && (same_visible_symbol(candidate, target)
4528 || self.compatible_primary_template_redeclarations(candidate, target)))
4529 || (declared_type_alias(analyzer, candidate)
4530 && self.alias_candidate_may_preserve_target(
4531 analyzer, file, candidate, target,
4532 ))
4533 }) {
4534 names.insert(identifier.clone());
4535 }
4536 }
4537 }
4538
4539 names.extend(self.visible_parser_alias_names_for_target(file, target));
4540
4541 names
4542 }
4543
4544 pub fn indexed_structural_class_scope(
4545 &self,
4546 file: &ProjectFile,
4547 class: Node<'_>,
4548 source: &str,
4549 ) -> Option<Vec<String>> {
4550 let key = (file.clone(), class.start_byte(), class.end_byte());
4551 if let Some(cached) = self
4552 .indexed_structural_class_scopes
4553 .lock()
4554 .expect("C++ indexed structural-class scope cache poisoned")
4555 .get(&key)
4556 .cloned()
4557 {
4558 return cached;
4559 }
4560 let resolved = (|| {
4561 let name = class.child_by_field_name("name")?;
4562 let identifier = if name.kind() == "template_type" {
4563 node_text(name.child_by_field_name("name")?, source).to_string()
4564 } else {
4565 let mut components = Vec::new();
4566 append_cpp_name_components(name, source, &mut components)?;
4567 components.last()?.clone()
4568 };
4569 let visible = self
4570 .visible_identifier_candidates(file, &identifier)
4571 .cloned()
4572 .collect::<Vec<_>>();
4573 let mut visible = visible;
4574 for candidate in
4575 self.visible_by_file
4576 .get(file)
4577 .into_iter()
4578 .flatten()
4579 .filter(|candidate| {
4580 self.cpp_template_metadata
4581 .get(candidate)
4582 .is_some_and(|metadata| metadata.primary_name == identifier)
4583 })
4584 {
4585 if !visible
4586 .iter()
4587 .any(|existing| same_logical_symbol(existing, candidate))
4588 {
4589 visible.push(candidate.clone());
4590 }
4591 }
4592 let cpp_source = self.cpp_source();
4595 let candidates = visible
4596 .iter()
4597 .filter(|candidate| {
4598 candidate.source() == file
4599 && candidate.is_class()
4600 && !declared_type_alias(&cpp_source, candidate)
4601 && self.cpp.ranges(candidate).iter().any(|range| {
4602 range.start_byte <= class.start_byte()
4603 && class.end_byte() <= range.end_byte
4604 })
4605 })
4606 .collect::<Vec<_>>();
4607 let owner = if name.kind() == "template_type" {
4608 let expected = normalize_cpp_whitespace(node_text(name, source));
4609 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
4610 let exact = candidates
4611 .iter()
4612 .copied()
4613 .filter(|candidate| {
4614 candidate
4615 .fq()
4616 .segments()
4617 .iter()
4618 .rev()
4619 .find_map(|&segment| {
4620 let (text, kind) = interner.resolve(segment);
4621 matches!(
4622 kind,
4623 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
4624 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
4625 )
4626 .then_some(text)
4627 })
4628 .is_some_and(|text| text == expected)
4629 })
4630 .collect::<Vec<_>>();
4631 unique_logical_type_candidate(exact)
4632 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
4633 } else {
4634 unique_logical_type_candidate(candidates)?
4635 };
4636 Some(canonical_cpp_scope_components(&owner))
4637 })();
4638 self.indexed_structural_class_scopes
4639 .lock()
4640 .expect("C++ indexed structural-class scope cache poisoned")
4641 .insert(key, resolved.clone());
4642 resolved
4643 }
4644
4645 pub fn indexed_enclosing_owner_scope(
4646 &self,
4647 analyzer: &CppGraphSource<'_>,
4648 file: &ProjectFile,
4649 node: Node<'_>,
4650 ) -> Option<Vec<String>> {
4651 let anchor = std::iter::successors(Some(node), |current| current.parent())
4652 .find(|current| {
4653 matches!(
4654 current.kind(),
4655 "function_definition"
4656 | "class_specifier"
4657 | "struct_specifier"
4658 | "union_specifier"
4659 )
4660 })
4661 .unwrap_or(node);
4662 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
4663 if let Some(cached) = self
4664 .indexed_enclosing_owner_scopes
4665 .lock()
4666 .expect("C++ indexed enclosing-owner scope cache poisoned")
4667 .get(&key)
4668 .cloned()
4669 {
4670 return cached;
4671 }
4672 let resolved = (|| {
4673 let range = Range {
4674 start_byte: node.start_byte(),
4675 end_byte: node.end_byte(),
4676 start_line: node.start_position().row,
4677 end_line: node.end_position().row,
4678 };
4679 let start = analyzer.enclosing_code_unit(file, &range)?;
4680 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
4681 start,
4682 |unit| self.cached_precise_parent_of(analyzer, unit),
4683 )
4684 .find(|unit| {
4685 unit.is_class()
4686 && !analyzer
4687 .type_alias_provider()
4688 .is_some_and(|provider| provider.is_type_alias(unit))
4689 })?;
4690 Some(canonical_cpp_scope_components(&owner))
4691 })();
4692 self.indexed_enclosing_owner_scopes
4693 .lock()
4694 .expect("C++ indexed enclosing-owner scope cache poisoned")
4695 .insert(key, resolved.clone());
4696 resolved
4697 }
4698
4699 fn cached_precise_parent_of(
4700 &self,
4701 analyzer: &CppGraphSource<'_>,
4702 code_unit: &CodeUnit,
4703 ) -> Option<CodeUnit> {
4704 if let Some(cached) = self
4705 .precise_parent_cache
4706 .lock()
4707 .expect("C++ precise-parent cache poisoned")
4708 .get(code_unit)
4709 .cloned()
4710 {
4711 return cached;
4712 }
4713 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
4714 self.precise_parent_cache
4715 .lock()
4716 .expect("C++ precise-parent cache poisoned")
4717 .insert(code_unit.clone(), resolved.clone());
4718 resolved
4719 }
4720
4721 pub fn callable_is_constructor_declaration(
4722 &self,
4723 analyzer: &CppGraphSource<'_>,
4724 candidate: &CodeUnit,
4725 ) -> bool {
4726 if !candidate.is_function() {
4727 return false;
4728 }
4729 let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
4730 return false;
4731 };
4732 let root = prepared.tree().root_node();
4733 let candidate_ranges = analyzer.ranges(candidate);
4734 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
4735 let mut current = root
4736 .descendant_for_byte_range(range.start_byte, range.end_byte)
4737 .and_then(|node| node.parent());
4738 while let Some(node) = current {
4739 if matches!(
4740 node.kind(),
4741 "class_specifier" | "struct_specifier" | "union_specifier"
4742 ) {
4743 return node
4744 .child_by_field_name("name")
4745 .map(|name| terminal_name(node_text(name, prepared.source())))
4746 .is_some_and(|name| name == candidate.identifier());
4747 }
4748 current = node.parent();
4749 }
4750 false
4751 });
4752 if enclosed_by_matching_type {
4753 return true;
4754 }
4755 let indexed_containment = analyzer
4756 .declarations(candidate.source())
4757 .into_iter()
4758 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
4759 .any(|owner| {
4760 analyzer.ranges(&owner).iter().any(|owner_range| {
4761 candidate_ranges.iter().any(|candidate_range| {
4762 owner_range.start_byte <= candidate_range.start_byte
4763 && candidate_range.end_byte <= owner_range.end_byte
4764 })
4765 })
4766 });
4767 if indexed_containment {
4768 return true;
4769 }
4770 let metadata = analyzer.signature_metadata(candidate);
4771 !metadata.is_empty()
4772 && metadata
4773 .iter()
4774 .all(|signature| signature.return_type_text().is_none())
4775 }
4776
4777 pub fn type_name_candidates<'b>(
4778 &'b self,
4779 file: &ProjectFile,
4780 normalized: &str,
4781 ) -> Vec<&'b CodeUnit> {
4782 self.candidate_units(file, normalized, TargetKind::Type)
4783 }
4784
4785 pub fn visible_members_for_owner_name<'b>(
4786 &'b self,
4787 file: &ProjectFile,
4788 owner: &CodeUnit,
4789 name: &str,
4790 ) -> Vec<&'b CodeUnit> {
4791 self.visible_identifier_candidates(file, name)
4792 .filter(|unit| {
4793 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
4797 .is_some_and(|parent| parent == owner.fq_name())
4798 })
4799 .collect()
4800 }
4801
4802 pub fn visible_member_for_owner_name(
4803 &self,
4804 file: &ProjectFile,
4805 owner: &CodeUnit,
4806 name: &str,
4807 ) -> VisibleMemberResolution {
4808 let candidates = self.visible_members_for_owner_name(file, owner, name);
4809 let mut callables = Vec::new();
4810 let mut non_callable = None;
4811 for candidate in candidates {
4812 if candidate.is_function() {
4813 callables.push(candidate.clone());
4814 } else if non_callable.is_none() {
4815 non_callable = Some(candidate.clone());
4816 }
4817 }
4818 match (callables.is_empty(), non_callable) {
4819 (false, None) => VisibleMemberResolution::Callable(callables),
4820 (true, Some(_)) => VisibleMemberResolution::NonCallable,
4821 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
4822 (true, None) => VisibleMemberResolution::Missing,
4823 }
4824 }
4825
4826 fn field_declared_type_fact(
4827 &self,
4828 analyzer: &CppGraphSource<'_>,
4829 field: &CodeUnit,
4830 ) -> Option<DeclaredFieldTypeFact> {
4831 if let Some(cached) = self
4832 .field_type_facts
4833 .lock()
4834 .expect("C++ field type fact cache poisoned")
4835 .get(field)
4836 .cloned()
4837 {
4838 return cached;
4839 }
4840 let decoded = decode_field_declared_type_fact(analyzer, field);
4841 self.field_type_facts
4842 .lock()
4843 .expect("C++ field type fact cache poisoned")
4844 .insert(field.clone(), decoded.clone());
4845 decoded
4846 }
4847
4848 fn structured_alias_target(
4849 &self,
4850 analyzer: &CppGraphSource<'_>,
4851 unit: &CodeUnit,
4852 ) -> Option<StructuredAliasTarget> {
4853 if let Some(cached) = self
4854 .structured_alias_targets
4855 .lock()
4856 .expect("C++ structured alias target cache poisoned")
4857 .get(unit)
4858 .cloned()
4859 {
4860 return cached;
4861 }
4862 let decoded = decode_structured_alias_target(analyzer, unit);
4863 self.structured_alias_targets
4864 .lock()
4865 .expect("C++ structured alias target cache poisoned")
4866 .insert(unit.clone(), decoded.clone());
4867 decoded
4868 }
4869
4870 pub fn type_candidates<'b>(
4871 &'b self,
4872 file: &ProjectFile,
4873 normalized: &str,
4874 ) -> Vec<&'b CodeUnit> {
4875 let mut candidates = self
4876 .candidate_units(file, normalized, TargetKind::Type)
4877 .into_iter()
4878 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
4879 .collect::<Vec<_>>();
4880 dedup_unit_refs(&mut candidates);
4881 candidates
4882 }
4883
4884 pub fn named_candidates_for_normalized<'b>(
4885 &'b self,
4886 file: &ProjectFile,
4887 normalized: &str,
4888 kind: TargetKind,
4889 ) -> Vec<&'b CodeUnit> {
4890 let mut candidates = self
4891 .candidate_units(file, normalized, kind)
4892 .into_iter()
4893 .filter(|unit| {
4894 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
4895 })
4896 .collect::<Vec<_>>();
4897 dedup_unit_refs(&mut candidates);
4898 candidates
4899 }
4900
4901 pub fn candidate_units<'b>(
4902 &'b self,
4903 file: &ProjectFile,
4904 normalized: &str,
4905 kind: TargetKind,
4906 ) -> Vec<&'b CodeUnit> {
4907 if normalized.contains("::") {
4908 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4917 brokk_bifrost_core::analyzer::Language::Cpp,
4918 normalized,
4919 )
4920 .pop() else {
4921 return Vec::new();
4922 };
4923 let fqns = cpp_reference_fqn_candidates(normalized, kind);
4924 return self
4925 .visible_identifier_candidates(file, &identifier)
4926 .filter(|unit| {
4927 #[cfg(any(test, feature = "test-support"))]
4928 self.qualified_candidate_inspections
4929 .fetch_add(1, Ordering::Relaxed);
4930 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
4931 || canonical_cpp_name_matches(unit, normalized)
4932 })
4933 .collect();
4934 }
4935 self.visible_identifier_candidates(file, normalized)
4936 .collect()
4937 }
4938
4939 #[cfg(any(test, feature = "test-support"))]
4940 pub fn reset_qualified_candidate_inspections(&self) {
4941 self.qualified_candidate_inspections
4942 .store(0, Ordering::Relaxed);
4943 }
4944
4945 #[cfg(any(test, feature = "test-support"))]
4946 pub fn qualified_candidate_inspections(&self) -> usize {
4947 self.qualified_candidate_inspections.load(Ordering::Relaxed)
4948 }
4949
4950 #[cfg(any(test, feature = "test-support"))]
4951 pub fn reset_target_preserving_type_resolution_count(&self) {
4952 self.target_preserving_type_resolution_count
4953 .store(0, Ordering::Relaxed);
4954 }
4955
4956 #[cfg(any(test, feature = "test-support"))]
4957 pub fn target_preserving_type_resolution_count(&self) -> usize {
4958 self.target_preserving_type_resolution_count
4959 .load(Ordering::Relaxed)
4960 }
4961
4962 #[cfg(any(test, feature = "test-support"))]
4963 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
4964 self.visible_parser_alias_name_set_build_count
4965 .load(Ordering::Relaxed)
4966 }
4967
4968 #[cfg(any(test, feature = "test-support"))]
4969 pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
4970 self.visible_parser_alias_target_names_build_count
4971 .load(Ordering::Relaxed)
4972 }
4973}
4974
4975#[derive(Default)]
4976struct IncludeGraph {
4977 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
4978}
4979
4980impl IncludeGraph {
4981 fn extend_with<F>(
4982 &mut self,
4983 root: &ProjectFile,
4984 cancellation: Option<&CancellationToken>,
4985 targets_for: &mut F,
4986 ) where
4987 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
4988 {
4989 let mut stack = vec![root.clone()];
4990 while let Some(file) = stack.pop() {
4991 if cancellation.is_some_and(CancellationToken::is_cancelled) {
4992 break;
4993 }
4994 if self.targets_by_file.contains_key(&file) {
4995 continue;
4996 }
4997 let targets = targets_for(&file);
4998 stack.extend(targets.iter().cloned());
4999 self.targets_by_file.insert(file, targets);
5000 }
5001 }
5002
5003 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
5004 self.targets_by_file.keys()
5005 }
5006
5007 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
5008 self.targets_by_file
5009 .get(file)
5010 .map(Vec::as_slice)
5011 .unwrap_or_default()
5012 }
5013}
5014
5015pub struct VisibilityData {
5016 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
5017 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
5018}
5019
5020pub fn build_visibility_data<F, D>(
5021 roots: &HashSet<ProjectFile>,
5022 cancellation: Option<&CancellationToken>,
5023 mut targets_for: F,
5024 mut declarations_for: D,
5025) -> VisibilityData
5026where
5027 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5028 D: FnMut(&ProjectFile) -> BTreeSet<CodeUnit>,
5029{
5030 let mut include_graph = IncludeGraph::default();
5031 for file in roots {
5032 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5033 break;
5034 }
5035 include_graph.extend_with(file, cancellation, &mut targets_for);
5036 }
5037 let declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
5038 .files()
5039 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
5040 .map(|file| (file.clone(), declarations_for(file)))
5041 .collect();
5042 let mut visible_by_file = HashMap::default();
5043 let mut visible_source_files_by_root = HashMap::default();
5044 for file in roots {
5045 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5046 break;
5047 }
5048 let mut visited = HashSet::default();
5049 let mut visible = HashSet::default();
5050 collect_visible_declarations(
5051 &include_graph,
5052 &declarations_by_file,
5053 file,
5054 &mut visited,
5055 &mut visible,
5056 cancellation,
5057 );
5058 visible_by_file.insert(file.clone(), visible);
5059 visible_source_files_by_root.insert(file.clone(), visited);
5060 }
5061 VisibilityData {
5062 visible_by_file,
5063 visible_source_files_by_root,
5064 }
5065}
5066
5067pub enum VisibleMemberResolution {
5068 Callable(Vec<CodeUnit>),
5069 NonCallable,
5070 AmbiguousKind,
5071 Missing,
5072}
5073
5074#[derive(Clone)]
5075pub enum EnclosingMemberOwnerResolution {
5076 Owner(CodeUnit),
5077 Ambiguous,
5078 Missing,
5079}
5080
5081pub fn resolve_declaring_member_owner(
5082 analyzer: &CppGraphSource<'_>,
5083 visibility: &VisibilityIndex<'_>,
5084 file: &ProjectFile,
5085 receiver_owner: &CodeUnit,
5086 member_name: &str,
5087) -> EnclosingMemberOwnerResolution {
5088 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5089 return EnclosingMemberOwnerResolution::Missing;
5090 };
5091 let Some(receiver_owner) =
5092 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
5093 else {
5094 return EnclosingMemberOwnerResolution::Ambiguous;
5095 };
5096 let resolve_level = |frontier: &[CodeUnit]| {
5097 let mut member_owners = Vec::new();
5098 for raw_owner in frontier {
5099 let Some(owner) =
5100 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
5101 else {
5102 return EnclosingMemberOwnerResolution::Ambiguous;
5103 };
5104 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
5105 let Some(member_owner) = type_owner_of(analyzer, member) else {
5106 return EnclosingMemberOwnerResolution::Ambiguous;
5107 };
5108 if !member_owners
5109 .iter()
5110 .any(|existing| same_visible_symbol(existing, &member_owner))
5111 {
5112 member_owners.push(member_owner);
5113 }
5114 }
5115 }
5116 match member_owners.len() {
5117 0 => EnclosingMemberOwnerResolution::Missing,
5118 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
5119 _ => EnclosingMemberOwnerResolution::Ambiguous,
5120 }
5121 };
5122 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
5126 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
5127 return direct;
5128 }
5129 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
5130 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5131 let mut path_matches = Vec::new();
5132 while let Some(raw_owner) = stack.pop() {
5133 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5134 else {
5135 return EnclosingMemberOwnerResolution::Ambiguous;
5136 };
5137 let propagated = propagated_counts.entry(owner.clone()).or_default();
5141 if *propagated == 2 {
5142 continue;
5143 }
5144 *propagated += 1;
5145 match resolve_level(std::slice::from_ref(&owner)) {
5146 EnclosingMemberOwnerResolution::Owner(owner) => {
5147 path_matches.push(owner);
5148 if path_matches.len() == 2 {
5149 return EnclosingMemberOwnerResolution::Ambiguous;
5150 }
5151 }
5152 EnclosingMemberOwnerResolution::Ambiguous => {
5153 return EnclosingMemberOwnerResolution::Ambiguous;
5154 }
5155 EnclosingMemberOwnerResolution::Missing => {
5156 stack.extend(hierarchy.get_direct_ancestors(&owner));
5157 }
5158 }
5159 }
5160 match path_matches.len() {
5161 0 => EnclosingMemberOwnerResolution::Missing,
5162 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
5163 _ => unreachable!("base-path matches are capped at one before returning"),
5164 }
5165}
5166
5167pub fn lexical_component_tiers<'a>(
5168 components: &'a [String],
5169 global: bool,
5170 lexical_scope: &'a [String],
5171) -> impl Iterator<Item = Vec<String>> + 'a {
5172 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
5173 (0..=first_prefix_len).rev().map(move |prefix_len| {
5174 let mut qualified = Vec::with_capacity(prefix_len + components.len());
5175 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
5176 qualified.extend_from_slice(components);
5177 qualified
5178 })
5179}
5180
5181pub fn build_visible_identifier_index(
5182 analyzer: &CppGraphSource<'_>,
5183 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
5184 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
5185 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
5186) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
5187 let mut out = HashMap::default();
5188 for (file, visible) in visible_by_file {
5189 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
5190 for unit in visible {
5191 if unit.is_field()
5192 && !visible_source_files_by_root
5193 .get(file)
5194 .is_some_and(|sources| sources.contains(unit.source()))
5195 && cpp_global_field_has_internal_linkage_cached(
5196 analyzer,
5197 global_field_internal_linkage,
5198 unit,
5199 )
5200 {
5201 continue;
5202 }
5203 by_identifier
5204 .entry(unit.identifier().to_string())
5205 .or_default()
5206 .push(unit.clone());
5207 }
5208 for units in by_identifier.values_mut() {
5209 sort_lookup_units(units);
5210 units.dedup();
5211 }
5212 out.insert(file.clone(), by_identifier);
5213 }
5214 out
5215}
5216
5217fn sort_lookup_units(units: &mut [CodeUnit]) {
5218 units.sort_by(|left, right| {
5219 left.fq_name()
5220 .cmp(&right.fq_name())
5221 .then_with(|| left.signature().cmp(&right.signature()))
5222 .then_with(|| left.source().cmp(right.source()))
5223 });
5224}
5225
5226fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
5227 let mut deduped = Vec::with_capacity(units.len());
5228 for unit in units.drain(..) {
5229 if !deduped.contains(&unit) {
5230 deduped.push(unit);
5231 }
5232 }
5233 *units = deduped;
5234}
5235
5236pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
5237 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5241 brokk_bifrost_core::analyzer::Language::Cpp,
5242 reference,
5243 );
5244 if parts.is_empty() {
5245 return Vec::new();
5246 }
5247
5248 let mut candidates = Vec::new();
5249 for package_len in 0..parts.len() {
5250 let package = parts[..package_len].join("::");
5251 let rest = &parts[package_len..];
5252 if rest.is_empty() {
5253 continue;
5254 }
5255 match kind {
5256 TargetKind::Type | TargetKind::Constructor => {
5257 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
5258 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
5259 }
5260 TargetKind::FreeFunction
5261 | TargetKind::Method
5262 | TargetKind::GlobalField
5263 | TargetKind::MemberField => {
5264 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
5265 if rest.len() > 1 {
5266 let owner = rest[..rest.len() - 1].join("$");
5267 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
5268 push_cpp_fqn_candidate(&mut candidates, &package, &short);
5269 }
5270 }
5271 }
5272 }
5273 candidates
5274}
5275
5276fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
5277 let fqn = if package.is_empty() {
5278 short.to_string()
5279 } else {
5280 format!("{package}.{short}")
5281 };
5282 if !out.contains(&fqn) {
5283 out.push(fqn);
5284 }
5285}
5286
5287pub fn infer_cpp_initializer_type(
5288 analyzer: &CppGraphSource<'_>,
5289 visibility: &VisibilityIndex<'_>,
5290 file: &ProjectFile,
5291 source: &str,
5292 node: Node<'_>,
5293) -> Option<CodeUnit> {
5294 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
5295 .and_then(|binding| binding.unit)
5296}
5297
5298pub fn infer_cpp_initializer_binding(
5299 analyzer: &CppGraphSource<'_>,
5300 visibility: &VisibilityIndex<'_>,
5301 file: &ProjectFile,
5302 source: &str,
5303 node: Node<'_>,
5304 receiver_resolver: Option<&ReceiverResolver<'_>>,
5305) -> Option<CppScanBinding> {
5306 match node.kind() {
5307 "new_expression" => {
5308 let text = normalize_cpp_whitespace(node_text(node, source));
5309 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
5310 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
5311 let name = normalize_cpp_type_name(type_text);
5312 Some(CppScanBinding::from_type_name(
5313 name.clone(),
5314 visibility.resolve_type(file, &name),
5315 1,
5316 ))
5317 }
5318 "call_expression" => node.child_by_field_name("function").and_then(|function| {
5319 let function_text = node_text(function, source);
5320 let direct_type_binding = visibility
5321 .resolve_type(file, function_text)
5322 .map(|unit| CppScanBinding::from_unit(unit, 0));
5323 if function.kind() == "template_function" && direct_type_binding.is_some() {
5324 let lexical_namespace = enclosing_namespace_context(node, source);
5325 let arity = visibility.call_arity_evidence(file, node, source).exact();
5326 if let Some(arity) = arity
5327 && let Some(binding) = visibility.resolve_call_return_binding(
5328 analyzer,
5329 file,
5330 function_text,
5331 arity,
5332 lexical_namespace.as_deref(),
5333 direct_type_binding
5334 .as_ref()
5335 .and_then(|binding| binding.unit.as_ref()),
5336 )
5337 {
5338 return Some(binding);
5339 }
5340 let (has_callable, callable_binding) = visibility
5341 .resolve_call_return_binding_without_arity(
5342 analyzer,
5343 file,
5344 function_text,
5345 lexical_namespace.as_deref(),
5346 direct_type_binding
5347 .as_ref()
5348 .and_then(|binding| binding.unit.as_ref()),
5349 );
5350 if let Some(binding) = callable_binding {
5351 return Some(binding);
5352 }
5353 if has_callable {
5354 return None;
5355 }
5356 return direct_type_binding;
5357 }
5358 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
5359 let direct_type_binding_for_call = direct_type_binding.clone();
5360 resolve_static_method_call_return_binding(
5361 analyzer, visibility, file, source, function, arity,
5362 )
5363 .or(direct_type_binding)
5364 .or_else(|| {
5365 visibility.resolve_call_return_binding(
5366 analyzer,
5367 file,
5368 function_text,
5369 arity,
5370 enclosing_namespace_context(node, source).as_deref(),
5371 direct_type_binding_for_call
5372 .as_ref()
5373 .and_then(|binding| binding.unit.as_ref()),
5374 )
5375 })
5376 .or_else(|| {
5377 resolve_field_method_call_return_binding(
5378 analyzer,
5379 visibility,
5380 file,
5381 source,
5382 function,
5383 arity,
5384 receiver_resolver,
5385 )
5386 })
5387 }),
5388 _ => None,
5389 }
5390}
5391
5392fn resolve_static_method_call_return_binding(
5393 analyzer: &CppGraphSource<'_>,
5394 visibility: &VisibilityIndex<'_>,
5395 file: &ProjectFile,
5396 source: &str,
5397 function: Node<'_>,
5398 arity: usize,
5399) -> Option<CppScanBinding> {
5400 if function.kind() != "qualified_identifier" {
5401 return None;
5402 }
5403 let qualified = normalize_cpp_reference_text(node_text(function, source));
5404 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5411 brokk_bifrost_core::analyzer::Language::Cpp,
5412 &qualified,
5413 );
5414 let (owner_text, member_name) = match parts.split_last() {
5415 Some((member, owner_parts)) if !owner_parts.is_empty() => {
5416 (owner_parts.join("::"), member.clone())
5417 }
5418 _ => {
5419 let scope = function.child_by_field_name("scope")?;
5420 let name = function.child_by_field_name("name")?;
5421 (
5422 node_text(scope, source).to_string(),
5423 node_text(name, source).to_string(),
5424 )
5425 }
5426 };
5427 let owner = visibility.resolve_type(file, &owner_text)?;
5428 let candidates = visibility
5429 .visible_members_for_owner_name(file, &owner, &member_name)
5430 .into_iter()
5431 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
5432 .cloned()
5433 .collect::<Vec<_>>();
5434 unanimous_return_binding(analyzer, visibility, file, &candidates)
5435}
5436
5437fn resolve_field_method_call_return_binding(
5438 analyzer: &CppGraphSource<'_>,
5439 visibility: &VisibilityIndex<'_>,
5440 file: &ProjectFile,
5441 source: &str,
5442 function: Node<'_>,
5443 arity: usize,
5444 receiver_resolver: Option<&ReceiverResolver<'_>>,
5445) -> Option<CppScanBinding> {
5446 if function.kind() != "field_expression" {
5447 return None;
5448 }
5449 let receiver_resolver = receiver_resolver?;
5450 let field = function.child_by_field_name("field")?;
5451 let member_name = node_text(field, source);
5452 let receiver = function
5453 .child_by_field_name("argument")
5454 .or_else(|| function.named_child(0))?;
5455 let owners = receiver_resolver(receiver, source);
5456 let mut candidates = Vec::new();
5457 for owner in owners {
5458 candidates.extend(
5459 visibility
5460 .visible_members_for_owner_name(file, &owner, member_name)
5461 .into_iter()
5462 .filter(|unit| {
5463 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
5464 })
5465 .cloned(),
5466 );
5467 }
5468 unanimous_return_binding(analyzer, visibility, file, &candidates)
5469}
5470
5471fn unanimous_return_binding(
5472 analyzer: &CppGraphSource<'_>,
5473 visibility: &VisibilityIndex<'_>,
5474 file: &ProjectFile,
5475 candidates: &[CodeUnit],
5476) -> Option<CppScanBinding> {
5477 let mut resolved_return: Option<CppScanBinding> = None;
5478 for function in candidates {
5479 let metadata = analyzer.signature_metadata(function);
5480 let return_types = if metadata.is_empty() {
5481 vec![cpp_function_return_type_text(analyzer, function)?]
5482 } else {
5483 metadata
5484 .iter()
5485 .map(|metadata| metadata.return_type_text().map(str::to_string))
5486 .collect::<Option<Vec<_>>>()?
5487 };
5488 for return_text in return_types {
5489 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
5490 let name = normalize_cpp_type_name(&return_text);
5491 let binding = CppScanBinding::from_type_name(
5492 name.clone(),
5493 visibility
5494 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
5495 indirection,
5496 );
5497 if let Some(existing) = resolved_return.as_ref()
5498 && (existing.indirection != binding.indirection
5499 || match (&existing.unit, &binding.unit) {
5500 (Some(left), Some(right)) => !same_visible_symbol(left, right),
5501 (None, None) => existing.type_name != binding.type_name,
5502 (Some(_), None) | (None, Some(_)) => true,
5503 })
5504 {
5505 return None;
5506 }
5507 resolved_return = Some(binding);
5508 }
5509 }
5510 resolved_return
5511}
5512
5513fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
5514 let Some(prepared) = cpp.prepared_syntax(file) else {
5515 return Vec::new();
5516 };
5517 let mut aliases = Vec::new();
5518 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
5519 aliases
5520}
5521
5522fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
5523 let mut stack = vec![root];
5524 while let Some(node) = stack.pop() {
5525 match node.kind() {
5526 "alias_declaration" if alias_has_visible_file_scope(node) => {
5527 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
5528 out.push(alias);
5529 }
5530 }
5531 "type_definition" if alias_has_visible_file_scope(node) => {
5532 collect_typedef_aliases(node, source, out)
5533 }
5534 _ => {}
5535 }
5536
5537 for index in (0..node.named_child_count()).rev() {
5538 if let Some(child) = node.named_child(index) {
5539 stack.push(child);
5540 }
5541 }
5542 }
5543}
5544
5545fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
5546 let mut current = node.parent();
5547 while let Some(parent) = current {
5548 match parent.kind() {
5549 "translation_unit"
5550 | "namespace_definition"
5551 | "declaration_list"
5552 | "linkage_specification" => current = parent.parent(),
5553 "template_declaration" => current = parent.parent(),
5554 _ => return false,
5555 }
5556 }
5557 true
5558}
5559
5560fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
5561 let name = node
5562 .child_by_field_name("name")
5563 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
5564 let target = node
5565 .child_by_field_name("type")
5566 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
5567 Some(CppAlias {
5568 name,
5569 target,
5570 namespace: enclosing_namespace_context(node, source),
5571 })
5572}
5573
5574fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
5575 let Some(type_node) = node.child_by_field_name("type") else {
5576 return;
5577 };
5578 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
5579 return;
5580 };
5581
5582 let mut cursor = node.walk();
5583 for child in node.named_children(&mut cursor) {
5584 if same_node(child, type_node) {
5585 continue;
5586 }
5587 if let Some(name) = extract_typedef_declarator_name(child, source) {
5588 out.push(CppAlias {
5589 name,
5590 target: target.clone(),
5591 namespace: enclosing_namespace_context(node, source),
5592 });
5593 }
5594 }
5595}
5596
5597fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
5598 match node.kind() {
5599 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
5600 normalize_reference_name(node_text(node, source))
5601 }
5602 _ => node
5603 .child_by_field_name("declarator")
5604 .or_else(|| node.child_by_field_name("name"))
5605 .or_else(|| last_named_child(node))
5606 .and_then(|child| extract_typedef_declarator_name(child, source)),
5607 }
5608}
5609
5610fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
5611 let count = node.named_child_count();
5612 if count == 0 {
5613 None
5614 } else {
5615 node.named_child(count - 1)
5616 }
5617}
5618
5619pub fn collect_include_closure(
5620 analyzer: &CppGraphSource<'_>,
5621 include_targets: &IncludeTargetIndex,
5622 file: &ProjectFile,
5623 out: &mut HashSet<ProjectFile>,
5624 cancellation: Option<&CancellationToken>,
5625) {
5626 let mut stack = vec![file.clone()];
5627 while let Some(file) = stack.pop() {
5628 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5629 break;
5630 }
5631 if !out.insert(file.clone()) {
5632 continue;
5633 }
5634 let imports = analyzer.import_statements(&file);
5635 for include in cpp_include_paths(&imports) {
5636 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
5637 stack.push(target);
5638 }
5639 }
5640 }
5641}
5642
5643fn collect_visible_declarations(
5644 include_graph: &IncludeGraph,
5645 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
5646 file: &ProjectFile,
5647 visited: &mut HashSet<ProjectFile>,
5648 out: &mut HashSet<CodeUnit>,
5649 cancellation: Option<&CancellationToken>,
5650) {
5651 let mut stack = vec![file.clone()];
5652 while let Some(file) = stack.pop() {
5653 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5654 break;
5655 }
5656 if !visited.insert(file.clone()) {
5657 continue;
5658 }
5659 if let Some(declarations) = declarations_by_file.get(&file) {
5660 out.extend(declarations.iter().cloned());
5661 }
5662 stack.extend(include_graph.targets(&file).iter().cloned());
5663 }
5664}
5665
5666pub fn signature_arity(signature: Option<&str>) -> usize {
5667 let Some(signature) = signature else {
5668 return 0;
5669 };
5670 let inner = signature
5671 .find('(')
5672 .and_then(|open| {
5673 signature[open + 1..]
5674 .find(')')
5675 .map(|close| &signature[open + 1..open + 1 + close])
5676 })
5677 .unwrap_or(signature)
5678 .trim();
5679 if inner.is_empty() || inner == "void" {
5680 return 0;
5681 }
5682 cpp_split_top_level_commas(inner).count()
5683}
5684
5685fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
5686 let source = format!("void __bifrost_macro_parameters({replacement});");
5687 let mut parser = Parser::new();
5688 parser
5689 .set_language(&tree_sitter_cpp::LANGUAGE.into())
5690 .ok()?;
5691 let tree = parser.parse(&source, None)?;
5692 let root = tree.root_node();
5693 if root.has_error() {
5694 return None;
5695 }
5696 let declaration = root.named_child(0)?;
5697 let declarator = declaration.child_by_field_name("declarator")?;
5698 let parameters = declarator.child_by_field_name("parameters")?;
5699 let mut required = 0;
5700 let mut total = 0;
5701 let mut repeated = false;
5702 let mut cursor = parameters.walk();
5703 for parameter in parameters.children(&mut cursor) {
5704 match parameter.kind() {
5705 "parameter_declaration" => {
5706 if parameter.child_by_field_name("declarator").is_none()
5707 && parameter
5708 .child_by_field_name("type")
5709 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
5710 {
5711 continue;
5712 }
5713 required += 1;
5714 total += 1;
5715 }
5716 "optional_parameter_declaration" => total += 1,
5717 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
5718 repeated = true;
5719 }
5720 _ => {}
5721 }
5722 }
5723 Some(CallableArity::new(required, total, repeated))
5724}
5725
5726pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
5727 analyzer
5728 .signature_metadata(unit)
5729 .into_iter()
5730 .find_map(|metadata| metadata.callable_arity())
5731 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
5732}
5733
5734fn merge_compatible_callable_arities(
5735 left: CallableArity,
5736 right: CallableArity,
5737) -> Option<CallableArity> {
5738 let total = left.total();
5739 let left_repeated = left.accepts(total.saturating_add(1));
5740 let right_repeated = right.accepts(right.total().saturating_add(1));
5741 if total != right.total() || left_repeated != right_repeated {
5742 return None;
5743 }
5744 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
5745 Some(CallableArity::new(required, total, left_repeated))
5746}
5747
5748fn find_include_activation(
5749 cpp: &dyn CppSource,
5750 file: &ProjectFile,
5751 prepared: &PreparedSyntaxTree,
5752 donor_source: &ProjectFile,
5753) -> Option<usize> {
5754 let include_targets = cpp.include_target_index();
5755 let mut direct_includes = Vec::new();
5756 let mut nodes = vec![prepared.tree().root_node()];
5757 while let Some(node) = nodes.pop() {
5758 if node.kind() == "preproc_include" {
5759 if callable_preprocessor_context_is_visible_for_reference(node, prepared.source(), file)
5760 {
5761 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
5762 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
5763 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
5764 file,
5765 &include,
5766 include_targets,
5767 )) {
5768 direct_includes.push((node.end_byte(), target));
5769 }
5770 }
5771 }
5772 continue;
5773 }
5774 for index in (0..node.named_child_count()).rev() {
5775 if let Some(child) = node.named_child(index) {
5776 nodes.push(child);
5777 }
5778 }
5779 }
5780 direct_includes.sort_by_key(|(activation, _)| *activation);
5781 let mut known_missing = HashSet::default();
5782 direct_includes
5783 .into_iter()
5784 .find(|(_, direct)| {
5785 unconditional_include_reaches(
5786 cpp,
5787 include_targets,
5788 direct,
5789 donor_source,
5790 file,
5791 &mut known_missing,
5792 )
5793 })
5794 .map(|(activation, _)| activation)
5795}
5796
5797fn find_conditional_include_projections(
5798 cpp: &dyn CppSource,
5799 file: &ProjectFile,
5800 prepared: &PreparedSyntaxTree,
5801 donor_source: &ProjectFile,
5802) -> Vec<ConditionalIncludeProjection> {
5803 let include_targets = cpp.include_target_index();
5804 let mut projections = Vec::new();
5805 let mut nodes = vec![prepared.tree().root_node()];
5806 while let Some(node) = nodes.pop() {
5807 if node.kind() == "preproc_include" {
5808 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
5809 else {
5810 continue;
5811 };
5812 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
5813 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
5814 let Some(target) = unique_include_target(resolve_include_targets_with_index(
5815 file,
5816 &include,
5817 include_targets,
5818 )) else {
5819 continue;
5820 };
5821 let paths = conditional_include_requirement_paths(
5822 cpp,
5823 &target,
5824 donor_source,
5825 required_guards.clone(),
5826 );
5827 for required_guards in paths {
5828 if !projections
5829 .iter()
5830 .any(|projection: &ConditionalIncludeProjection| {
5831 projection.activation_byte == node.end_byte()
5832 && projection.required_guards == required_guards
5833 })
5834 {
5835 projections.push(ConditionalIncludeProjection {
5836 activation_byte: node.end_byte(),
5837 required_guards,
5838 });
5839 }
5840 }
5841 }
5842 continue;
5843 }
5844 for index in (0..node.named_child_count()).rev() {
5845 if let Some(child) = node.named_child(index) {
5846 nodes.push(child);
5847 }
5848 }
5849 }
5850 projections.sort_by_key(|projection| projection.activation_byte);
5851 projections
5852}
5853
5854fn conditional_include_requirement_paths(
5855 cpp: &dyn CppSource,
5856 first: &ProjectFile,
5857 donor_source: &ProjectFile,
5858 required_guards: HashSet<PreprocessorGuard>,
5859) -> Vec<HashSet<PreprocessorGuard>> {
5860 let include_targets = cpp.include_target_index();
5861 let mut paths = Vec::new();
5862 let mut stack = vec![(
5863 first.clone(),
5864 required_guards,
5865 HashSet::from_iter([first.clone()]),
5866 )];
5867 while let Some((file, required_guards, visited)) = stack.pop() {
5868 if file == *donor_source {
5869 if !paths.contains(&required_guards) {
5870 paths.push(required_guards);
5871 }
5872 continue;
5873 }
5874 let Some(prepared) = cpp.prepared_syntax(&file) else {
5875 continue;
5876 };
5877 let mut nodes = vec![prepared.tree().root_node()];
5878 while let Some(node) = nodes.pop() {
5879 if node.kind() == "preproc_include" {
5880 let Some(include_guards) = preprocessor_guard_environment(node, prepared.source())
5881 else {
5882 continue;
5883 };
5884 let Some(path_guards) =
5885 merge_preprocessor_guards(&required_guards, &include_guards)
5886 else {
5887 continue;
5888 };
5889 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
5890 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
5891 let Some(target) = unique_include_target(resolve_include_targets_with_index(
5892 &file,
5893 &include,
5894 include_targets,
5895 )) else {
5896 continue;
5897 };
5898 if visited.contains(&target) {
5899 continue;
5900 }
5901 let mut next_visited = visited.clone();
5902 next_visited.insert(target.clone());
5903 stack.push((target, path_guards.clone(), next_visited));
5904 }
5905 continue;
5906 }
5907 for index in (0..node.named_child_count()).rev() {
5908 if let Some(child) = node.named_child(index) {
5909 nodes.push(child);
5910 }
5911 }
5912 }
5913 }
5914 paths
5915}
5916
5917fn unconditional_include_reaches(
5918 cpp: &dyn CppSource,
5919 include_targets: &IncludeTargetIndex,
5920 first: &ProjectFile,
5921 donor_source: &ProjectFile,
5922 reference_file: &ProjectFile,
5923 known_missing: &mut HashSet<ProjectFile>,
5924) -> bool {
5925 if first == donor_source {
5926 return true;
5927 }
5928 if known_missing.contains(first) {
5929 return false;
5930 }
5931 let reference_is_c = reference_file
5932 .rel_path()
5933 .extension()
5934 .and_then(|extension| extension.to_str())
5935 == Some("c");
5936 if let Some(reaches) =
5937 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
5938 {
5939 return reaches;
5940 }
5941 let mut visited = HashSet::default();
5942 let mut files = vec![first.clone()];
5943 while let Some(file) = files.pop() {
5944 if file == *donor_source {
5945 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
5946 return true;
5947 }
5948 if known_missing.contains(&file) || !visited.insert(file.clone()) {
5949 continue;
5950 }
5951 let Some(prepared) = cpp.prepared_syntax(&file) else {
5952 continue;
5953 };
5954 let mut nodes = vec![prepared.tree().root_node()];
5955 while let Some(node) = nodes.pop() {
5956 if node.kind() == "preproc_include" {
5957 if callable_preprocessor_context_is_visible_for_reference(
5958 node,
5959 prepared.source(),
5960 reference_file,
5961 ) {
5962 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
5963 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
5964 if let Some(target) = unique_include_target(
5965 resolve_include_targets_with_index(&file, &include, include_targets),
5966 ) {
5967 files.push(target);
5968 }
5969 }
5970 }
5971 continue;
5972 }
5973 for index in (0..node.named_child_count()).rev() {
5974 if let Some(child) = node.named_child(index) {
5975 nodes.push(child);
5976 }
5977 }
5978 }
5979 }
5980 known_missing.extend(visited);
5981 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
5982 false
5983}
5984
5985fn declaration_guard_requirements(
5986 analyzer: &CppGraphSource<'_>,
5987 cpp: &dyn CppSource,
5988 candidate: &CodeUnit,
5989) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
5990 let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
5991 return Vec::new();
5992 };
5993 let root = prepared.tree().root_node();
5994 analyzer
5995 .ranges(candidate)
5996 .into_iter()
5997 .filter_map(|range| {
5998 root.descendant_for_byte_range(range.start_byte, range.end_byte)
5999 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
6000 .map(|required| (range.start_byte, required))
6004 })
6005 .collect()
6006}
6007
6008fn guard_requirements_hold_at_reference(
6009 required: &HashSet<PreprocessorGuard>,
6010 reference: Option<&HashSet<PreprocessorGuard>>,
6011) -> bool {
6012 reference.is_some_and(|active| required.is_subset(active))
6013}
6014
6015fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
6016 let mut ancestor = node.parent();
6017 while let Some(current) = ancestor {
6018 if is_preprocessor_conditional(current) {
6019 let family = preprocessor_conditional_family_root(current);
6020 if preprocessor_conditional_family_has_terminal_else(family) {
6021 return Some(family);
6022 }
6023 }
6024 ancestor = current.parent();
6025 }
6026 None
6027}
6028
6029fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
6030 while let Some(parent) = conditional.parent() {
6031 let is_alternative = parent
6032 .child_by_field_name("alternative")
6033 .is_some_and(|alternative| {
6034 alternative.start_byte() == conditional.start_byte()
6035 && alternative.end_byte() == conditional.end_byte()
6036 });
6037 if !is_alternative {
6038 break;
6039 }
6040 conditional = parent;
6041 }
6042 conditional
6043}
6044
6045fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
6046 loop {
6047 let Some(alternative) = conditional.child_by_field_name("alternative") else {
6048 return false;
6049 };
6050 match alternative.kind() {
6051 "preproc_else" => return true,
6052 "preproc_elif" => conditional = alternative,
6053 _ => return false,
6054 }
6055 }
6056}
6057
6058pub fn preprocessor_guard_environment(
6059 node: Node<'_>,
6060 source: &str,
6061) -> Option<HashSet<PreprocessorGuard>> {
6062 let mut guards = HashSet::default();
6063 let mut ancestor = node.parent();
6064 while let Some(conditional) = ancestor {
6065 if matches!(
6066 conditional.kind(),
6067 "preproc_if" | "preproc_ifdef" | "preproc_elif"
6068 ) && !is_file_covering_include_guard(conditional, source)
6069 {
6070 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
6071 match guard {
6072 PreprocessorGuard::Constant(true) => {
6073 ancestor = conditional.parent();
6074 continue;
6075 }
6076 PreprocessorGuard::Constant(false) => return None,
6077 _ => {}
6078 }
6079 if guards.contains(&guard.negated()) {
6080 return None;
6081 }
6082 guards.insert(guard);
6083 }
6084 ancestor = conditional.parent();
6085 }
6086 Some(guards)
6087}
6088
6089fn preprocessor_guard_for_descendant(
6090 conditional: Node<'_>,
6091 descendant: Node<'_>,
6092 source: &str,
6093) -> Option<PreprocessorGuard> {
6094 let mut guard = simple_preprocessor_guard(conditional, source)?;
6095 if conditional
6096 .child_by_field_name("alternative")
6097 .is_some_and(|alternative| {
6098 alternative.start_byte() <= descendant.start_byte()
6099 && descendant.end_byte() <= alternative.end_byte()
6100 })
6101 {
6102 let alternative = conditional.child_by_field_name("alternative")?;
6103 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
6107 return None;
6108 }
6109 guard = guard.negated();
6110 }
6111 Some(guard)
6112}
6113
6114pub fn merge_preprocessor_guards(
6115 left: &HashSet<PreprocessorGuard>,
6116 right: &HashSet<PreprocessorGuard>,
6117) -> Option<HashSet<PreprocessorGuard>> {
6118 let mut merged = left.clone();
6119 for guard in right {
6120 if merged.contains(&guard.negated()) {
6121 return None;
6122 }
6123 merged.insert(guard.clone());
6124 }
6125 Some(merged)
6126}
6127
6128fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
6129 if conditional.kind() == "preproc_ifdef" {
6130 let name = conditional.child_by_field_name("name")?;
6131 let name = node_text(name, source).to_string();
6132 return match conditional.child(0)?.kind() {
6133 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
6134 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
6135 _ => None,
6136 };
6137 }
6138 let condition = conditional.child_by_field_name("condition")?;
6139 simple_preprocessor_expression_guard(condition, source).or_else(|| {
6140 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
6141 node_text(condition, source),
6142 )))
6143 })
6144}
6145
6146fn simple_preprocessor_expression_guard(
6147 expression: Node<'_>,
6148 source: &str,
6149) -> Option<PreprocessorGuard> {
6150 match expression.kind() {
6151 "number_literal" => match node_text(expression, source).trim() {
6152 "0" => Some(PreprocessorGuard::Constant(false)),
6153 "1" => Some(PreprocessorGuard::Constant(true)),
6154 _ => None,
6155 },
6156 "preproc_defined" => {
6157 let identifier = (0..expression.named_child_count())
6158 .filter_map(|index| expression.named_child(index))
6159 .find(|child| child.kind() == "identifier")?;
6160 Some(PreprocessorGuard::Defined(
6161 node_text(identifier, source).to_string(),
6162 ))
6163 }
6164 "unary_expression"
6165 if expression
6166 .child_by_field_name("operator")
6167 .is_some_and(|operator| operator.kind() == "!") =>
6168 {
6169 simple_preprocessor_expression_guard(
6170 expression.child_by_field_name("argument")?,
6171 source,
6172 )
6173 .map(|guard| guard.negated())
6174 }
6175 "parenthesized_expression" => (0..expression.named_child_count())
6176 .filter_map(|index| expression.named_child(index))
6177 .next()
6178 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
6179 _ => None,
6180 }
6181}
6182
6183fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
6184 if targets.len() == 1 {
6185 targets.pop()
6186 } else {
6187 None
6188 }
6189}
6190
6191fn callable_declaration_activation_in_file(
6192 analyzer: &CppGraphSource<'_>,
6193 prepared: &PreparedSyntaxTree,
6194 candidate: &CodeUnit,
6195 reference_file: &ProjectFile,
6196) -> Option<usize> {
6197 let root = prepared.tree().root_node();
6198 analyzer
6199 .ranges(candidate)
6200 .into_iter()
6201 .filter_map(|range| {
6202 let mut declaration =
6203 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6204 while !matches!(
6205 declaration.kind(),
6206 "declaration" | "field_declaration" | "function_definition"
6207 ) {
6208 declaration = declaration.parent()?;
6209 }
6210 let mut ancestor = declaration.parent();
6211 while let Some(node) = ancestor {
6212 if node.kind() == "function_definition"
6219 && crate::declarations::is_recovered_exported_class_container(
6220 node,
6221 prepared.source(),
6222 )
6223 {
6224 ancestor = node.parent();
6225 continue;
6226 }
6227 if node.kind() == "compound_statement"
6228 && node.parent().is_some_and(|parent| {
6229 crate::declarations::is_recovered_exported_class_container(
6230 parent,
6231 prepared.source(),
6232 )
6233 })
6234 {
6235 ancestor = node.parent().and_then(|parent| parent.parent());
6236 continue;
6237 }
6238 if matches!(
6239 node.kind(),
6240 "compound_statement" | "function_definition" | "lambda_expression"
6241 ) {
6242 return None;
6243 }
6244 ancestor = node.parent();
6245 }
6246 callable_preprocessor_context_is_visible_for_reference(
6247 declaration,
6248 prepared.source(),
6249 reference_file,
6250 )
6251 .then_some(declaration.end_byte())
6252 })
6253 .min()
6254}
6255
6256fn callable_preprocessor_context_is_visible_for_reference(
6257 node: Node<'_>,
6258 source: &str,
6259 reference_file: &ProjectFile,
6260) -> bool {
6261 let reference_is_c = reference_file
6262 .rel_path()
6263 .extension()
6264 .and_then(|extension| extension.to_str())
6265 == Some("c");
6266 let mut ancestor = node.parent();
6267 while let Some(conditional) = ancestor {
6268 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
6269 && !is_file_covering_include_guard(conditional, source)
6270 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
6271 {
6272 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
6273 return false;
6274 };
6275 match guard {
6276 PreprocessorGuard::Constant(true) => {}
6277 PreprocessorGuard::Constant(false) => return false,
6278 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
6279 if reference_is_c {
6280 return false;
6281 }
6282 }
6283 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
6284 if !reference_is_c {
6285 return false;
6286 }
6287 }
6288 _ => return false,
6289 }
6290 }
6291 ancestor = conditional.parent();
6292 }
6293 true
6294}
6295
6296fn flattened_macro_namespace_declaration_matches(
6297 analyzer: &CppGraphSource<'_>,
6298 cpp: &dyn CppSource,
6299 reference_file: &ProjectFile,
6300 visible_declaration: &CodeUnit,
6301 qualified_candidate: &CodeUnit,
6302 reference_byte: usize,
6303) -> bool {
6304 if visible_declaration.kind() != qualified_candidate.kind()
6310 || visible_declaration.identifier() != qualified_candidate.identifier()
6311 || visible_declaration.signature() != qualified_candidate.signature()
6312 || !visible_declaration.package_name().is_empty()
6313 || qualified_candidate.package_name().is_empty()
6314 {
6315 return false;
6316 }
6317
6318 let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
6319 return false;
6320 };
6321 let root = prepared.tree().root_node();
6322 let closing_brace_limit = if visible_declaration.source() == reference_file {
6323 reference_byte
6324 } else {
6325 usize::MAX
6326 };
6327
6328 analyzer
6329 .ranges(visible_declaration)
6330 .into_iter()
6331 .any(|range| {
6332 let Some(mut declaration) =
6333 root.descendant_for_byte_range(range.start_byte, range.end_byte)
6334 else {
6335 return false;
6336 };
6337 while !matches!(
6338 declaration.kind(),
6339 "declaration" | "field_declaration" | "function_definition"
6340 ) {
6341 let Some(parent) = declaration.parent() else {
6342 return false;
6343 };
6344 declaration = parent;
6345 }
6346 if declaration
6347 .parent()
6348 .is_none_or(|parent| parent.kind() != "translation_unit")
6349 || !macro_displaced_cpp_return_type(declaration, prepared.source())
6350 {
6351 return false;
6352 }
6353
6354 let mut cursor = root.walk();
6355 root.named_children(&mut cursor).any(|sibling| {
6356 sibling.start_byte() >= declaration.end_byte()
6357 && sibling.start_byte() < closing_brace_limit
6358 && direct_unmatched_closing_brace(sibling)
6359 })
6360 })
6361}
6362
6363fn flattened_macro_namespace_components(
6364 declaration: Node<'_>,
6365 source: &str,
6366) -> Option<Vec<String>> {
6367 flattened_macro_function_namespace_components(declaration, source)
6368 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
6369}
6370
6371fn flattened_macro_function_namespace_components(
6372 declaration: Node<'_>,
6373 source: &str,
6374) -> Option<Vec<String>> {
6375 let body = declaration
6376 .parent()
6377 .filter(|parent| parent.kind() == "compound_statement")?;
6378 let function = body
6379 .parent()
6380 .filter(|parent| parent.kind() == "function_definition" && parent.has_error())?;
6381 if function.child_by_field_name("body") != Some(body) {
6382 return None;
6383 }
6384 let mut cursor = function.walk();
6385 let prefix = function
6386 .named_children(&mut cursor)
6387 .take_while(|child| child.start_byte() < body.start_byte())
6388 .filter(|child| child.kind() != "comment")
6389 .collect::<Vec<_>>();
6390 let begin_index = prefix.iter().rposition(|child| {
6391 flattened_macro_sentinel_name(*child, source)
6392 .is_some_and(|name| name.ends_with("NAMESPACE_BEGIN"))
6393 })?;
6394 let mut identifiers = Vec::new();
6395 let mut stack = prefix[begin_index + 1..]
6396 .iter()
6397 .rev()
6398 .copied()
6399 .collect::<Vec<_>>();
6400 while let Some(current) = stack.pop() {
6401 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
6402 identifiers.push(identifier);
6403 continue;
6404 }
6405 let mut cursor = current.walk();
6406 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
6407 stack.extend(children.into_iter().rev());
6408 }
6409 let [keyword, namespace_name] = identifiers.as_slice() else {
6410 return None;
6411 };
6412 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
6413 {
6414 return None;
6415 }
6416 let mut next = function.next_named_sibling();
6417 let next = loop {
6418 let candidate = next?;
6419 next = candidate.next_named_sibling();
6420 if candidate.kind() != "comment" {
6421 break candidate;
6422 }
6423 };
6424 if !flattened_macro_sentinel_name(next, source)
6425 .is_some_and(|name| name.ends_with("NAMESPACE_END"))
6426 {
6427 return None;
6428 }
6429 let mut components = enclosing_namespace_components(declaration, source)?;
6430 components.push(namespace_name.clone());
6431 Some(components)
6432}
6433
6434fn flattened_macro_error_namespace_components(
6435 declaration: Node<'_>,
6436 source: &str,
6437) -> Option<Vec<String>> {
6438 let parent = declaration
6439 .parent()
6440 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
6441 let mut cursor = parent.walk();
6442 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
6443 let declaration_index = siblings
6444 .iter()
6445 .position(|candidate| same_node(*candidate, declaration))?;
6446 let begin_index = (0..declaration_index).rev().find(|index| {
6447 flattened_macro_sentinel_name(siblings[*index], source)
6448 .is_some_and(|name| name.ends_with("NAMESPACE_BEGIN"))
6449 })?;
6450
6451 let significant = siblings[begin_index + 1..declaration_index]
6452 .iter()
6453 .copied()
6454 .filter(|node| node.kind() != "comment")
6455 .collect::<Vec<_>>();
6456 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
6457 return None;
6458 };
6459 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
6460 return None;
6461 }
6462 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
6463 if significant[2..].iter().any(|node| {
6464 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
6465 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("NAMESPACE_END")
6466 })
6467 }) {
6468 return None;
6469 }
6470
6471 let mut saw_namespace_close = false;
6472 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
6473 if sibling.kind() == "comment" {
6474 continue;
6475 }
6476 if !saw_namespace_close {
6477 if direct_unmatched_closing_brace(sibling) {
6478 saw_namespace_close = true;
6479 continue;
6480 }
6481 if flattened_macro_sentinel_name(sibling, source).is_some() {
6482 return None;
6483 }
6484 continue;
6485 }
6486 if !flattened_macro_sentinel_name(sibling, source)
6487 .is_some_and(|name| name.ends_with("NAMESPACE_END"))
6488 {
6489 return None;
6490 }
6491 let mut components = enclosing_namespace_components(declaration, source)?;
6492 components.push(namespace_name);
6493 return Some(components);
6494 }
6495 None
6496}
6497
6498fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
6499 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
6500 node.child_by_field_name("type")
6501 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
6502 })?;
6503 (cpp_export_macro_token(&candidate)
6504 && (candidate.ends_with("NAMESPACE_BEGIN") || candidate.ends_with("NAMESPACE_END")))
6505 .then_some(candidate)
6506}
6507
6508fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
6509 if node.kind() != "ERROR" || node.named_child_count() != 1 {
6510 return None;
6511 }
6512 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
6513 (!cpp_export_macro_token(&name)).then_some(name)
6514}
6515
6516fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
6517 if !matches!(
6518 node.kind(),
6519 "identifier" | "namespace_identifier" | "type_identifier"
6520 ) {
6521 return None;
6522 }
6523 let name = normalize_cpp_whitespace(node_text(node, source));
6524 (!name.is_empty()).then_some(name)
6525}
6526
6527fn guard_requirement_sets_match(
6528 left: &[(usize, HashSet<PreprocessorGuard>)],
6529 right: &[(usize, HashSet<PreprocessorGuard>)],
6530) -> bool {
6531 left.len() == right.len()
6532 && left.iter().all(|(_, left_guards)| {
6533 right
6534 .iter()
6535 .any(|(_, right_guards)| left_guards == right_guards)
6536 })
6537 && right.iter().all(|(_, right_guards)| {
6538 left.iter()
6539 .any(|(_, left_guards)| right_guards == left_guards)
6540 })
6541}
6542
6543fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
6544 let Some(type_node) = declaration.child_by_field_name("type") else {
6545 return false;
6546 };
6547 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
6548 !type_name.is_empty()
6549 && type_name
6550 .chars()
6551 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
6552 && (0..declaration.named_child_count()).any(|index| {
6553 declaration
6554 .named_child(index)
6555 .is_some_and(|child| child.kind() == "ERROR")
6556 })
6557}
6558
6559fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
6560 node.kind() == "ERROR"
6561 && (0..node.child_count())
6562 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
6563}
6564
6565pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
6566 let mut ancestor = node.parent();
6567 while let Some(parent) = ancestor {
6568 if is_preprocessor_conditional(parent)
6569 && !is_file_covering_include_guard(parent, source)
6570 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
6571 {
6572 return false;
6573 }
6574 ancestor = parent.parent();
6575 }
6576 true
6577}
6578
6579fn is_split_cpp_language_linkage_wrapper(
6580 conditional: Node<'_>,
6581 descendant: Node<'_>,
6582 source: &str,
6583) -> bool {
6584 if conditional.kind() != "preproc_ifdef"
6585 || conditional.child_by_field_name("alternative").is_some()
6586 || conditional
6587 .child_by_field_name("name")
6588 .is_none_or(|name| node_text(name, source) != "__cplusplus")
6589 {
6590 return false;
6591 }
6592 let mut current = descendant.parent();
6593 let linkage = loop {
6594 let Some(node) = current else {
6595 return false;
6596 };
6597 if node == conditional {
6598 return false;
6599 }
6600 if node.kind() == "linkage_specification" {
6601 break node;
6602 }
6603 current = node.parent();
6604 };
6605 if linkage
6606 .child_by_field_name("value")
6607 .is_none_or(|value| node_text(value, source) != "\"C\"")
6608 {
6609 return false;
6610 }
6611 let Some(body) = linkage.child_by_field_name("body") else {
6612 return false;
6613 };
6614 let closes_opening_branch = (0..body.named_child_count())
6615 .filter_map(|index| body.named_child(index))
6616 .take_while(|child| child.end_byte() <= descendant.start_byte())
6617 .any(|child| {
6618 child.kind() == "preproc_call"
6619 && child
6620 .child_by_field_name("directive")
6621 .is_some_and(|directive| node_text(directive, source) == "#endif")
6622 });
6623 let reopens_for_closing_brace = (0..body.named_child_count())
6624 .filter_map(|index| body.named_child(index))
6625 .skip_while(|child| child.start_byte() < descendant.end_byte())
6626 .any(|child| {
6627 child.kind() == "preproc_ifdef"
6628 && child
6629 .child_by_field_name("name")
6630 .is_some_and(|name| node_text(name, source) == "__cplusplus")
6631 && (0..child.child_count()).any(|index| {
6632 child
6633 .child(index)
6634 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
6635 })
6636 });
6637 closes_opening_branch && reopens_for_closing_brace
6638}
6639
6640pub fn call_arity(node: Node<'_>) -> usize {
6641 node.child_by_field_name("arguments")
6642 .or_else(|| node.child_by_field_name("parameters"))
6643 .or_else(|| node.child_by_field_name("value"))
6644 .or_else(|| first_named_child_of_kind(node, "argument_list"))
6645 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
6646 .map(|args| argument_children(args).count())
6647 .unwrap_or(0)
6648}
6649
6650pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
6651 let recovered_block_arguments = recovered_block_literal_arguments(node);
6652 (0..node.child_count())
6653 .filter_map(move |index| node.child(index))
6654 .filter(|child| child.is_named() && !child.is_extra())
6655 .flat_map(move |child| {
6656 if let Some((raw, left, right)) = recovered_block_arguments
6657 && child == raw
6658 {
6659 [Some(left), Some(right)]
6660 } else {
6661 [Some(child), None]
6662 }
6663 })
6664 .flatten()
6665}
6666
6667fn recovered_block_literal_arguments<'tree>(
6668 arguments: Node<'tree>,
6669) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
6670 if arguments.kind() != "argument_list" {
6671 return None;
6672 }
6673 let mut raw_arguments = (0..arguments.child_count())
6674 .filter_map(|index| arguments.child(index))
6675 .filter(|child| child.is_named() && !child.is_extra());
6676 let raw = raw_arguments.next()?;
6677 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
6678 return None;
6679 }
6680
6681 let left = raw.child_by_field_name("left")?;
6682 if left.is_missing() || left.start_byte() == left.end_byte() {
6683 return None;
6684 }
6685 let right = raw.child_by_field_name("right")?;
6686 if right.kind() != "compound_literal_expression"
6687 || right.is_missing()
6688 || right
6689 .child_by_field_name("type")
6690 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
6691 || right
6692 .child_by_field_name("value")
6693 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
6694 {
6695 return None;
6696 }
6697 let has_intervening_error = (0..raw.child_count())
6698 .filter_map(|index| raw.child(index))
6699 .any(|child| {
6700 child.kind() == "ERROR"
6701 && !child.is_missing()
6702 && child.start_byte() >= left.end_byte()
6703 && child.end_byte() <= right.start_byte()
6704 });
6705 has_intervening_error.then_some((raw, left, right))
6706}
6707
6708pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
6709 match node.kind() {
6710 "new_expression" => node
6711 .child_by_field_name("type")
6712 .or_else(|| node.named_child(0)),
6713 "compound_literal_expression" => node.child_by_field_name("type"),
6714 "call_expression" => node.child_by_field_name("function"),
6715 _ => None,
6716 }
6717}
6718
6719pub fn field_initializer_constructs_target(
6720 node: Node<'_>,
6721 ctx: &ScanCtx<'_>,
6722 owner: &CodeUnit,
6723) -> bool {
6724 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
6733 return qualified_base_initializer_constructs_target(node, ctx, owner);
6734 }
6735 let Some(name) = node
6736 .child_by_field_name("name")
6737 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
6738 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
6739 else {
6740 return false;
6741 };
6742 let field_name = node_text(name, ctx.source);
6743 ctx.visibility
6744 .visible_identifier_candidates(ctx.file, field_name)
6745 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
6746 .any(|unit| field_declares_type(unit, ctx, owner))
6747}
6748
6749fn qualified_base_initializer_constructs_target(
6750 node: Node<'_>,
6751 ctx: &ScanCtx<'_>,
6752 owner: &CodeUnit,
6753) -> bool {
6754 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
6755 return false;
6756 };
6757 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
6758 return false;
6759 };
6760 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
6761 return false;
6762 };
6763 let resolves_target = |components: &[String]| {
6764 matches!(
6765 ctx.visibility.resolve_type_components_lexically_for_target(
6766 &ctx.analyzer,
6767 ctx.file,
6768 components,
6769 is_globally_qualified_cpp_name(qualified),
6770 &lexical_scope,
6771 owner,
6772 ),
6773 LexicalTypeResolution::Resolved { unit, .. }
6774 if same_visible_symbol(&unit, owner)
6775 )
6776 };
6777 if resolves_target(&components) {
6778 return true;
6779 }
6780
6781 components
6787 .last()
6788 .is_some_and(|terminal| terminal == owner.identifier())
6789 && resolves_target(&components[..components.len() - 1])
6790}
6791
6792fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
6793 unit.signature()
6794 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
6795 || ctx
6796 .analyzer
6797 .get_source(unit, false)
6798 .is_some_and(|declaration| {
6799 field_declaration_type_matches(&declaration, unit, ctx, owner)
6800 })
6801}
6802
6803pub fn field_declared_binding(
6804 analyzer: &CppGraphSource<'_>,
6805 visibility: &VisibilityIndex<'_>,
6806 visible_from: &ProjectFile,
6807 field: &CodeUnit,
6808) -> Option<CppScanBinding> {
6809 let fact = visibility.field_declared_type_fact(analyzer, field)?;
6810 let normalized = normalize_field_type_text(&fact.type_text);
6811 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
6812 analyzer,
6813 visible_from,
6814 field,
6815 &normalized,
6816 );
6817 let resolved = match (resolved, fact.template_arguments.as_deref()) {
6818 (Some(primary), Some(arguments)) => visibility
6819 .resolve_template_arguments(visible_from, primary, arguments)
6820 .ok(),
6821 (resolved, None) => resolved,
6822 (None, Some(_)) => None,
6823 };
6824 Some(CppScanBinding::from_type_name(
6825 normalized,
6826 resolved,
6827 fact.indirection,
6828 ))
6829}
6830
6831fn type_alias_target_text(alias: &CodeUnit) -> Option<&str> {
6832 alias
6833 .signature()?
6834 .strip_prefix("using ")
6835 .and_then(|rest| rest.split_once('=').map(|(_, rhs)| rhs))
6836 .or_else(|| {
6837 alias
6838 .signature()?
6839 .strip_prefix("typedef ")
6840 .and_then(|rest| rest.rsplit_once(' ').map(|(lhs, _)| lhs))
6841 })
6842 .map(str::trim)
6843 .map(|target| target.trim_end_matches(';').trim())
6844}
6845
6846fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
6847 let first = candidates.first()?;
6848 candidates
6849 .iter()
6850 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
6851 .then(|| (*first).clone())
6852}
6853
6854fn unique_type_candidate_preserving_alias(
6855 analyzer: &CppGraphSource<'_>,
6856 candidates: &[&CodeUnit],
6857) -> Option<CodeUnit> {
6858 let first = *candidates.first()?;
6859 if declared_type_alias(analyzer, first) {
6860 return candidates
6861 .iter()
6862 .all(|candidate| {
6863 declared_type_alias(analyzer, candidate) && same_logical_symbol(first, candidate)
6864 })
6865 .then(|| first.clone());
6866 }
6867 candidates
6868 .iter()
6869 .all(|candidate| {
6870 !declared_type_alias(analyzer, candidate)
6871 && candidate.kind() == first.kind()
6872 && candidate.fq_name() == first.fq_name()
6873 })
6874 .then(|| first.clone())
6875}
6876
6877fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
6878 is_type_alias(unit)
6879 || analyzer
6880 .type_alias_provider()
6881 .is_some_and(|provider| provider.is_type_alias(unit))
6882}
6883
6884pub fn field_declared_type_binding(
6885 analyzer: &CppGraphSource<'_>,
6886 visibility: &VisibilityIndex<'_>,
6887 visible_from: &ProjectFile,
6888 field: &CodeUnit,
6889) -> Option<(String, Option<CodeUnit>, i32)> {
6890 let fact = visibility.field_declared_type_fact(analyzer, field)?;
6891 let normalized = normalize_field_type_text(&fact.type_text);
6892 let primary = visibility.resolve_unique_canonical_type_for_declaration(
6893 analyzer,
6894 visible_from,
6895 field,
6896 &normalized,
6897 );
6898 let resolved = match (primary, fact.template_arguments.as_deref()) {
6899 (Some(primary), Some(arguments)) => visibility
6900 .resolve_template_arguments(visible_from, primary, arguments)
6901 .ok(),
6902 (resolved, None) => resolved,
6903 (None, Some(_)) => None,
6904 };
6905 Some((normalized, resolved, fact.indirection))
6906}
6907
6908fn decode_field_declared_type_fact(
6909 analyzer: &CppGraphSource<'_>,
6910 field: &CodeUnit,
6911) -> Option<DeclaredFieldTypeFact> {
6912 let declaration = analyzer.get_source(field, false)?;
6913 let mut parser = Parser::new();
6914 parser
6915 .set_language(&tree_sitter_cpp::LANGUAGE.into())
6916 .ok()?;
6917 let tree = parser.parse(&declaration, None)?;
6918 let mut stack = vec![tree.root_node()];
6919 while let Some(node) = stack.pop() {
6920 if matches!(node.kind(), "declaration" | "field_declaration")
6921 && let Some(type_node) = node
6922 .child_by_field_name("type")
6923 .or_else(|| first_type_child(node))
6924 && let Some(indirection) =
6925 declared_name_indirection(node, type_node, field.identifier(), &declaration)
6926 {
6927 return Some(DeclaredFieldTypeFact {
6928 type_text: node_text(type_node, &declaration).to_string(),
6929 indirection,
6930 template_arguments: cpp_template_reference_arguments(type_node, &declaration),
6931 });
6932 }
6933 let mut cursor = node.walk();
6934 stack.extend(node.named_children(&mut cursor));
6935 }
6936 None
6937}
6938
6939fn decode_structured_alias_target(
6940 analyzer: &CppGraphSource<'_>,
6941 unit: &CodeUnit,
6942) -> Option<StructuredAliasTarget> {
6943 analyzer
6944 .get_source(unit, false)
6945 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
6946 .or_else(|| {
6947 let signature = unit.signature()?;
6948 decode_structured_alias_target_source(unit, signature, false)
6949 })
6950}
6951
6952fn decode_structured_alias_target_source(
6953 unit: &CodeUnit,
6954 declaration: &str,
6955 require_top_level: bool,
6956) -> Option<StructuredAliasTarget> {
6957 let mut parser = Parser::new();
6958 parser
6959 .set_language(&tree_sitter_cpp::LANGUAGE.into())
6960 .ok()?;
6961 let tree = parser.parse(declaration, None)?;
6962 let mut stack = vec![tree.root_node()];
6963 while let Some(node) = stack.pop() {
6964 let type_node = match node.kind() {
6965 "type_definition" => {
6966 if require_top_level
6967 && node
6968 .parent()
6969 .is_none_or(|parent| parent.kind() != "translation_unit")
6970 {
6971 let mut cursor = node.walk();
6972 stack.extend(node.named_children(&mut cursor));
6973 continue;
6974 }
6975 let mut declarator_cursor = node.walk();
6976 let declares_unit = node
6977 .children_by_field_name("declarator", &mut declarator_cursor)
6978 .any(|declarator| {
6979 extract_typedef_declarator_name(declarator, declaration)
6980 .is_some_and(|name| name == unit.identifier())
6981 });
6982 declares_unit.then(|| node.child_by_field_name("type"))??
6983 }
6984 "alias_declaration" => {
6985 if require_top_level
6986 && node
6987 .parent()
6988 .is_none_or(|parent| parent.kind() != "translation_unit")
6989 {
6990 let mut cursor = node.walk();
6991 stack.extend(node.named_children(&mut cursor));
6992 continue;
6993 }
6994 let name = node.child_by_field_name("name")?;
6995 (node_text(name, declaration) == unit.identifier())
6996 .then(|| node.child_by_field_name("type"))??
6997 }
6998 _ => {
6999 let mut cursor = node.walk();
7000 stack.extend(node.named_children(&mut cursor));
7001 continue;
7002 }
7003 };
7004 return structured_alias_type_target(type_node, declaration);
7005 }
7006 None
7007}
7008
7009fn structured_alias_type_target(
7010 mut type_node: Node<'_>,
7011 source: &str,
7012) -> Option<StructuredAliasTarget> {
7013 while type_node.kind() == "type_descriptor" {
7014 type_node = type_node.child_by_field_name("type")?;
7015 }
7016 if type_node.kind() == "primitive_type" {
7017 return Some(StructuredAliasTarget::Builtin);
7018 }
7019 if matches!(
7020 type_node.kind(),
7021 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
7022 ) {
7023 type_node = type_node.child_by_field_name("name")?;
7024 }
7025 let global = type_node.child_by_field_name("scope").is_none()
7026 && type_node.child(0).is_some_and(|child| child.kind() == "::");
7027 let mut components = Vec::new();
7028 append_structured_type_components(type_node, source, &mut components)?;
7029 let arguments = cpp_template_reference_arguments(type_node, source);
7030 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
7031 components,
7032 global,
7033 arguments,
7034 })
7035}
7036
7037fn append_structured_type_components(
7038 node: Node<'_>,
7039 source: &str,
7040 out: &mut Vec<String>,
7041) -> Option<()> {
7042 match node.kind() {
7043 "identifier" | "namespace_identifier" | "type_identifier" => {
7044 out.push(node_text(node, source).to_string());
7045 Some(())
7046 }
7047 "template_type" => {
7048 append_structured_type_components(node.child_by_field_name("name")?, source, out)
7049 }
7050 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
7051 if let Some(scope) = node.child_by_field_name("scope") {
7052 append_structured_type_components(scope, source, out)?;
7053 }
7054 append_structured_type_components(node.child_by_field_name("name")?, source, out)
7055 }
7056 _ => None,
7057 }
7058}
7059
7060fn declared_name_indirection(
7061 declaration: Node<'_>,
7062 type_node: Node<'_>,
7063 field_name: &str,
7064 source: &str,
7065) -> Option<i32> {
7066 let mut stack = Vec::new();
7067 let mut cursor = declaration.walk();
7068 stack.extend(
7069 declaration
7070 .named_children(&mut cursor)
7071 .filter(|child| !same_node(*child, type_node)),
7072 );
7073 while let Some(node) = stack.pop() {
7074 if matches!(node.kind(), "identifier" | "field_identifier")
7075 && node_text(node, source) == field_name
7076 {
7077 let mut indirection = 0;
7078 let mut current = node.parent();
7079 while let Some(parent) = current {
7080 if same_node(parent, declaration) {
7081 return Some(indirection);
7082 }
7083 if parent.kind() == "pointer_declarator" {
7084 indirection += 1;
7085 }
7086 current = parent.parent();
7087 }
7088 return None;
7089 }
7090 let mut cursor = node.walk();
7091 stack.extend(node.named_children(&mut cursor));
7092 }
7093 None
7094}
7095
7096fn field_declaration_type_matches(
7097 declaration: &str,
7098 unit: &CodeUnit,
7099 ctx: &ScanCtx<'_>,
7100 owner: &CodeUnit,
7101) -> bool {
7102 ctx.visibility
7103 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
7104 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
7105 let normalized = normalize_field_type_text(type_text);
7106 ctx.visibility
7107 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
7108 || ctx.visibility.resolves_to_type(
7109 &ctx.analyzer,
7110 ctx.file,
7111 normalized.as_str(),
7112 owner,
7113 )
7114 })
7115}
7116
7117fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
7118 let declaration = declaration
7119 .split(['=', ';'])
7120 .next()
7121 .unwrap_or(declaration)
7122 .trim();
7123 let index = declaration.rfind(field_name)?;
7124 let before = &declaration[..index];
7125 let after = &declaration[index + field_name.len()..];
7126 if before.chars().next_back().is_some_and(is_identifier_char)
7127 || after.chars().next().is_some_and(is_identifier_char)
7128 {
7129 return None;
7130 }
7131 Some(before.trim())
7132}
7133
7134fn normalize_field_type_text(type_text: &str) -> String {
7135 const FIELD_SPECIFIERS: [&str; 8] = [
7136 "extern ",
7137 "static ",
7138 "mutable ",
7139 "constexpr ",
7140 "constinit ",
7141 "inline ",
7142 "volatile ",
7143 "const ",
7144 ];
7145
7146 let mut normalized = normalize_type_text(type_text);
7147 loop {
7148 let Some(stripped) = FIELD_SPECIFIERS
7149 .iter()
7150 .find_map(|specifier| normalized.strip_prefix(specifier))
7151 else {
7152 return normalized;
7153 };
7154 normalized = normalize_type_text(stripped);
7155 }
7156}
7157
7158fn is_identifier_char(ch: char) -> bool {
7159 ch == '_' || ch.is_ascii_alphanumeric()
7160}
7161
7162pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
7163 let Some(type_node) = node.child_by_field_name("type") else {
7164 return false;
7165 };
7166 ctx.visibility.resolves_to_type(
7167 &ctx.analyzer,
7168 ctx.file,
7169 node_text(type_node, ctx.source),
7170 owner,
7171 )
7172}
7173
7174pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7175 !ctx.analyzer
7176 .declarations(ctx.file)
7177 .into_iter()
7178 .filter(|unit| unit.is_function())
7179 .any(|unit| {
7180 ctx.analyzer.ranges(&unit).iter().any(|range| {
7181 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
7182 })
7183 })
7184}
7185
7186pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
7187 let mut cursor = node.walk();
7188 for child in node.named_children(&mut cursor) {
7189 if child.kind() == "init_declarator" {
7190 return child
7191 .child_by_field_name("value")
7192 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
7193 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
7194 .map(declaration_init_value_arity)
7195 .unwrap_or(0);
7196 }
7197 if is_declarator_node(child) {
7198 return declaration_declarator_arity(child);
7199 }
7200 }
7201 0
7202}
7203
7204fn declaration_init_value_arity(value: Node<'_>) -> usize {
7205 match value.kind() {
7206 "argument_list" | "initializer_list" => argument_children(value).count(),
7207 "compound_literal_expression" => call_arity(value),
7208 _ => 1,
7209 }
7210}
7211
7212fn declaration_declarator_arity(node: Node<'_>) -> usize {
7213 if let Some(parameters) = node.child_by_field_name("parameters") {
7214 return argument_children(parameters).count();
7215 }
7216 node.child_by_field_name("declarator")
7217 .map(declaration_declarator_arity)
7218 .unwrap_or(0)
7219}
7220
7221fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7222 let mut cursor = node.walk();
7223 node.named_children(&mut cursor)
7224 .find(|child| child.kind() == kind)
7225}
7226
7227fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7228 let mut stack = vec![root];
7229 while let Some(node) = stack.pop() {
7230 if node.kind() == kind {
7231 return Some(node);
7232 }
7233 for index in (0..node.named_child_count()).rev() {
7234 if let Some(child) = node.named_child(index) {
7235 stack.push(child);
7236 }
7237 }
7238 }
7239 None
7240}
7241
7242fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
7243 if node.kind() == "identifier" {
7244 return true;
7245 }
7246 if node.kind() == "parenthesized_expression" {
7247 return false;
7248 }
7249 if node.kind() == "call_expression" {
7250 return node
7251 .child_by_field_name("function")
7252 .is_some_and(|function| function.kind() == "identifier");
7253 }
7254 let mut stack = vec![node];
7255 while let Some(descendant) = stack.pop() {
7256 if descendant != node && descendant.kind() == "parenthesized_expression" {
7257 continue;
7258 }
7259 if descendant.kind() == "identifier" {
7260 return true;
7261 }
7262 if descendant.kind() == "call_expression" {
7263 if descendant
7264 .child_by_field_name("function")
7265 .is_some_and(|function| function.kind() == "identifier")
7266 {
7267 return true;
7268 }
7269 continue;
7270 }
7271 for index in (0..descendant.named_child_count()).rev() {
7272 if let Some(child) = descendant.named_child(index) {
7273 stack.push(child);
7274 }
7275 }
7276 }
7277 false
7278}
7279
7280fn macro_expansion_shape_is_safe(
7281 node: Node<'_>,
7282 source: &str,
7283 parameters: &[String],
7284 environment: &MacroEnvironment,
7285) -> bool {
7286 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
7287 return true;
7288 }
7289 if node.kind() == "call_expression" {
7290 let Some(function) = node.child_by_field_name("function") else {
7291 return true;
7292 };
7293 if function.kind() != "identifier" {
7294 return true;
7295 }
7296 let function_name = node_text(function, source);
7297 if parameters
7298 .iter()
7299 .any(|parameter| parameter == function_name)
7300 {
7301 return false;
7302 }
7303 if !environment.may_bind(function_name) {
7304 return true;
7305 }
7306 let Some(arguments) = node.child_by_field_name("arguments") else {
7307 return false;
7308 };
7309 return argument_children(arguments).all(|argument| {
7310 if argument.kind() == "identifier"
7311 && parameters
7312 .iter()
7313 .any(|parameter| parameter == node_text(argument, source))
7314 {
7315 return false;
7316 }
7317 macro_expansion_shape_is_safe(argument, source, parameters, environment)
7318 });
7319 }
7320 let mut stack = vec![node];
7321 while let Some(descendant) = stack.pop() {
7322 if descendant != node {
7323 if descendant.kind() == "parenthesized_expression" {
7324 continue;
7325 }
7326 if descendant.kind() == "call_expression" {
7327 let expands = descendant
7328 .child_by_field_name("function")
7329 .filter(|function| function.kind() == "identifier")
7330 .is_some_and(|function| environment.may_bind(node_text(function, source)));
7331 if expands {
7332 return false;
7333 }
7334 continue;
7335 }
7336 }
7337 if descendant.kind() == "identifier" {
7338 let identifier = node_text(descendant, source);
7339 if parameters.iter().any(|parameter| parameter == identifier)
7340 || environment.may_bind(identifier)
7341 {
7342 return false;
7343 }
7344 }
7345 for index in (0..descendant.named_child_count()).rev() {
7346 if let Some(child) = descendant.named_child(index) {
7347 stack.push(child);
7348 }
7349 }
7350 }
7351 true
7352}
7353
7354fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
7355 let text = node_text(path, source);
7356 match path.kind() {
7357 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
7358 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
7359 _ => None,
7360 }
7361}
7362
7363fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
7364 while let Some(parent) = node.parent() {
7365 if is_preprocessor_conditional(parent) && !is_file_covering_include_guard(parent, source) {
7366 return true;
7367 }
7368 node = parent;
7369 }
7370 false
7371}
7372
7373fn is_preprocessor_conditional(node: Node<'_>) -> bool {
7374 matches!(
7375 node.kind(),
7376 "preproc_if"
7377 | "preproc_ifdef"
7378 | "preproc_ifndef"
7379 | "preproc_elif"
7380 | "preproc_elifdef"
7381 | "preproc_else"
7382 )
7383}
7384
7385fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
7386 node.parent()
7387 .filter(|parent| parent.kind() == "translation_unit")
7388 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
7389 && is_canonical_include_guard(node, source)
7390}
7391
7392fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
7393 if node.kind() != "preproc_ifdef"
7394 || node
7395 .child(0)
7396 .is_none_or(|directive| directive.kind() != "#ifndef")
7397 || node.child_by_field_name("alternative").is_some()
7398 {
7399 return false;
7400 }
7401 let Some(guard_name) = node.child_by_field_name("name") else {
7402 return false;
7403 };
7404 let mut cursor = node.walk();
7405 node.named_children(&mut cursor)
7406 .find(|child| *child != guard_name && child.kind() != "comment")
7407 .filter(|child| child.kind() == "preproc_def")
7408 .and_then(|definition| definition.child_by_field_name("name"))
7409 .is_some_and(|defined_name| {
7410 node_text(defined_name, source) == node_text(guard_name, source)
7411 })
7412}
7413
7414fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
7415 let mut guard = None;
7416 for index in 0..root.named_child_count() {
7417 let Some(child) = root.named_child(index) else {
7418 continue;
7419 };
7420 if child.kind() == "comment" || is_pragma_once(child, source) {
7421 continue;
7422 }
7423 if guard.is_none() && is_canonical_include_guard(child, source) {
7424 guard = Some(child);
7425 } else {
7426 return None;
7427 }
7428 }
7429 guard
7430 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
7431 .map(|name| node_text(name, source).to_string())
7432}
7433
7434fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
7435 if (0..root.named_child_count())
7436 .filter_map(|index| root.named_child(index))
7437 .any(|child| is_pragma_once(child, source))
7438 {
7439 return MacroIncludeProtection::PragmaOnce;
7440 }
7441 top_level_canonical_include_guard_name(root, source)
7442 .map(MacroIncludeProtection::MacroGuard)
7443 .unwrap_or(MacroIncludeProtection::None)
7444}
7445
7446fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
7447 node.kind() == "preproc_call"
7448 && node
7449 .child_by_field_name("directive")
7450 .is_some_and(|directive| node_text(directive, source) == "#pragma")
7451 && node
7452 .child_by_field_name("argument")
7453 .is_some_and(|argument| node_text(argument, source).trim() == "once")
7454}
7455
7456fn parse_preproc_identifier(argument: &str) -> Option<String> {
7457 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
7458 let mut parser = Parser::new();
7459 parser
7460 .set_language(&tree_sitter_cpp::LANGUAGE.into())
7461 .ok()?;
7462 let tree = parser.parse(&sentinel, None)?;
7463 if tree.root_node().has_error() {
7464 return None;
7465 }
7466 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
7467 let identifier = statement.named_child(0)?;
7468 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
7469 .then(|| node_text(identifier, &sentinel).to_string())
7470}
7471
7472pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
7473 match node.kind() {
7474 "identifier" | "field_identifier" => {
7475 let name = node_text(node, source).trim();
7476 (!name.is_empty()).then(|| name.to_string())
7477 }
7478 "abstract_array_declarator"
7479 | "abstract_function_declarator"
7480 | "abstract_parenthesized_declarator"
7481 | "abstract_pointer_declarator"
7482 | "abstract_reference_declarator" => None,
7483 "function_declarator" => node
7484 .child_by_field_name("declarator")
7485 .or_else(|| node.child_by_field_name("name"))
7486 .and_then(|child| extract_variable_name(child, source)),
7487 _ => node
7488 .child_by_field_name("declarator")
7489 .or_else(|| node.child_by_field_name("name"))
7490 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
7491 .and_then(|child| extract_variable_name(child, source)),
7492 }
7493}
7494
7495pub fn is_declarator_node(node: Node<'_>) -> bool {
7496 matches!(
7497 node.kind(),
7498 "identifier"
7499 | "field_identifier"
7500 | "pointer_declarator"
7501 | "reference_declarator"
7502 | "array_declarator"
7503 | "parenthesized_declarator"
7504 | "function_declarator"
7505 )
7506}
7507
7508#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7509pub enum RecoveredDeclaratorTypeContext {
7510 Declaration,
7511 FunctionDefinition,
7512}
7513
7514pub fn recovered_macro_decorated_declarator_type(
7526 node: Node<'_>,
7527) -> Option<RecoveredDeclaratorTypeContext> {
7528 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
7529}
7530
7531pub fn recovered_macro_decorated_type_node(
7536 node: Node<'_>,
7537) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
7538 if node.kind() != "namespace_identifier" || node.is_missing() {
7539 return None;
7540 }
7541 let qualified = node.parent()?;
7542 if qualified.kind() != "qualified_identifier"
7543 || qualified.child_by_field_name("scope") != Some(node)
7544 || !(0..qualified.child_count())
7545 .filter_map(|index| qualified.child(index))
7546 .any(|child| child.kind() == "::" && child.is_missing())
7547 {
7548 return None;
7549 }
7550 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
7551 return None;
7552 }
7553
7554 let (declaration, context) = recovered_declarator_container(qualified)?;
7555 let type_node = declaration
7556 .child_by_field_name("type")
7557 .filter(|type_node| {
7558 *type_node != qualified
7559 && !type_node.is_missing()
7560 && type_node.start_byte() != type_node.end_byte()
7561 })?;
7562 Some((type_node, context))
7563}
7564
7565fn recovered_declarator_container(
7566 mut declarator: Node<'_>,
7567) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
7568 loop {
7569 let parent = declarator.parent()?;
7570 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
7571 return Some((
7572 parent
7573 .parent()
7574 .filter(|declaration| declaration.kind() == "declaration")?,
7575 RecoveredDeclaratorTypeContext::Declaration,
7576 ));
7577 }
7578 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
7579 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
7580 }
7581 if parent.kind() == "function_definition"
7582 && has_field_child(parent, "declarator", declarator)
7583 {
7584 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
7585 }
7586 if !matches!(
7587 parent.kind(),
7588 "array_declarator"
7589 | "function_declarator"
7590 | "parenthesized_declarator"
7591 | "pointer_declarator"
7592 | "pointer_type_declarator"
7593 | "reference_declarator"
7594 ) || !has_field_child(parent, "declarator", declarator)
7595 {
7596 return None;
7597 }
7598 declarator = parent;
7599 }
7600}
7601
7602fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
7603 let mut cursor = parent.walk();
7604 parent
7605 .children_by_field_name(field, &mut cursor)
7606 .any(|child| child == target)
7607}
7608
7609fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
7610 loop {
7611 if node.is_missing() || node.start_byte() == node.end_byte() {
7612 return false;
7613 }
7614 match node.kind() {
7615 "identifier" | "field_identifier" | "type_identifier" => return true,
7616 "array_declarator"
7617 | "function_declarator"
7618 | "parenthesized_declarator"
7619 | "pointer_declarator"
7620 | "pointer_type_declarator"
7621 | "reference_declarator" => {
7622 let Some(declarator) = node.child_by_field_name("declarator") else {
7623 return false;
7624 };
7625 node = declarator;
7626 }
7627 _ => return false,
7628 }
7629 }
7630}
7631
7632pub enum DesignatedInitializerOwner {
7634 Resolved(CodeUnit),
7635 Unresolved,
7636}
7637
7638pub fn designated_initializer_owner(
7649 visibility: &VisibilityIndex<'_>,
7650 file: &ProjectFile,
7651 source: &str,
7652 node: Node<'_>,
7653) -> Option<DesignatedInitializerOwner> {
7654 if let Some(designator) = node
7655 .parent()
7656 .filter(|parent| parent.kind() == "field_designator")
7657 {
7658 let pair = designator.parent()?;
7659 if pair.kind() != "initializer_pair"
7660 || pair.child_by_field_name("designator") != Some(designator)
7661 {
7662 return None;
7663 }
7664 let initializer = pair.parent()?;
7665 if initializer.kind() != "initializer_list" {
7666 return None;
7667 }
7668 return Some(classified_designated_owner(initializer_list_owner(
7669 visibility,
7670 file,
7671 source,
7672 initializer,
7673 )));
7674 }
7675
7676 let init_declarator = node.parent()?;
7677 if init_declarator.child_by_field_name("declarator") != Some(node)
7678 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
7679 {
7680 return None;
7681 }
7682 Some(classified_designated_owner(declaration_owner(
7683 visibility,
7684 file,
7685 source,
7686 init_declarator.parent()?,
7687 )))
7688}
7689
7690fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
7691 owner.map_or(
7692 DesignatedInitializerOwner::Unresolved,
7693 DesignatedInitializerOwner::Resolved,
7694 )
7695}
7696
7697fn initializer_list_owner(
7698 visibility: &VisibilityIndex<'_>,
7699 file: &ProjectFile,
7700 source: &str,
7701 initializer: Node<'_>,
7702) -> Option<CodeUnit> {
7703 let mut current = initializer;
7704 let mut outer_initializer_lists = 0usize;
7705 loop {
7706 let parent = current.parent()?;
7707 match parent.kind() {
7708 "initializer_pair" => return None,
7709 "initializer_list" => {
7710 outer_initializer_lists += 1;
7711 if outer_initializer_lists > 1 {
7712 return None;
7713 }
7714 current = parent;
7715 }
7716 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
7717 let declaration = parent.parent()?;
7718 if outer_initializer_lists == 1
7719 && !parent
7720 .child_by_field_name("declarator")
7721 .is_some_and(contains_array_declarator)
7722 {
7723 return None;
7724 }
7725 return declaration_owner(visibility, file, source, declaration);
7726 }
7727 "compound_literal_expression"
7728 if parent.child_by_field_name("value") == Some(current)
7729 && outer_initializer_lists == 0 =>
7730 {
7731 let type_node = parent.child_by_field_name("type")?;
7732 return resolve_designated_owner_type(visibility, file, source, type_node);
7733 }
7734 "ERROR" => current = parent,
7735 _ => return None,
7736 }
7737 }
7738}
7739
7740fn declaration_owner(
7741 visibility: &VisibilityIndex<'_>,
7742 file: &ProjectFile,
7743 source: &str,
7744 declaration: Node<'_>,
7745) -> Option<CodeUnit> {
7746 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
7747 return None;
7748 }
7749 let type_node = declaration
7750 .child_by_field_name("type")
7751 .or_else(|| first_type_child(declaration))?;
7752 resolve_designated_owner_type(visibility, file, source, type_node)
7753}
7754
7755fn resolve_designated_owner_type(
7756 visibility: &VisibilityIndex<'_>,
7757 file: &ProjectFile,
7758 source: &str,
7759 type_node: Node<'_>,
7760) -> Option<CodeUnit> {
7761 let type_name = normalize_type_text(node_text(type_node, source));
7762 visibility
7763 .resolve_type(file, &type_name)
7764 .filter(CodeUnit::is_class)
7765}
7766
7767fn contains_array_declarator(declarator: Node<'_>) -> bool {
7768 let mut stack = vec![declarator];
7769 while let Some(node) = stack.pop() {
7770 if node.kind() == "array_declarator" {
7771 return true;
7772 }
7773 if matches!(node.kind(), "initializer_list" | "compound_statement") {
7774 continue;
7775 }
7776 let mut cursor = node.walk();
7777 stack.extend(node.named_children(&mut cursor));
7778 }
7779 false
7780}
7781
7782pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
7783 let mut cursor = node.walk();
7784 node.named_children(&mut cursor).find(|child| {
7785 matches!(
7786 child.kind(),
7787 "type_identifier"
7788 | "primitive_type"
7789 | "qualified_identifier"
7790 | "scoped_type_identifier"
7791 | "struct_specifier"
7792 | "union_specifier"
7793 | "enum_specifier"
7794 )
7795 })
7796}
7797
7798pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
7799 visibility: &VisibilityIndex<'_>,
7800 file: &ProjectFile,
7801 source: &str,
7802 declarator: Node<'_>,
7803 type_text: Option<&str>,
7804 bindings: &LocalInferenceEngine<T>,
7805) -> bool {
7806 if !has_ancestor_kind(declarator, "compound_statement") {
7807 return false;
7808 }
7809 if declarator
7810 .child_by_field_name("declarator")
7811 .is_none_or(|declarator| declarator.kind() != "identifier")
7812 {
7813 return false;
7814 }
7815 if !type_text
7816 .and_then(|text| visibility.resolve_type(file, text))
7817 .is_some_and(|unit| unit.is_class())
7818 {
7819 return false;
7820 }
7821 declarator
7822 .child_by_field_name("parameters")
7823 .is_some_and(|parameters| {
7824 constructor_parameters_look_like_expressions(parameters, source, bindings)
7825 })
7826}
7827
7828fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
7829 parameters: Node<'_>,
7830 source: &str,
7831 bindings: &LocalInferenceEngine<T>,
7832) -> bool {
7833 let mut cursor = parameters.walk();
7834 parameters.named_children(&mut cursor).any(|parameter| {
7835 !matches!(
7836 parameter.kind(),
7837 "parameter_declaration" | "optional_parameter_declaration"
7838 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
7839 })
7840}
7841
7842fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
7843 parameter: Node<'_>,
7844 source: &str,
7845 bindings: &LocalInferenceEngine<T>,
7846) -> bool {
7847 let text = node_text(parameter, source).trim();
7848 text.chars()
7849 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
7850 && bindings.is_shadowed(text)
7851}
7852
7853pub fn is_declaration_name(node: Node<'_>) -> bool {
7854 let Some(parent) = node.parent() else {
7855 return false;
7856 };
7857 if parent
7858 .child_by_field_name("name")
7859 .is_some_and(|name| same_node(name, node))
7860 {
7861 if matches!(
7862 parent.kind(),
7863 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
7864 ) {
7865 return cpp_tag_specifier_declares_name(parent);
7866 }
7867 if matches!(
7868 parent.kind(),
7869 "namespace_definition"
7870 | "namespace_alias_definition"
7871 | "alias_declaration"
7872 | "enumerator"
7873 ) {
7874 return true;
7875 }
7876 }
7877
7878 let mut current = Some(parent);
7879 while let Some(ancestor) = current {
7880 let type_definition = ancestor.kind() == "type_definition";
7881 let mut declarator_cursor = ancestor.walk();
7882 if ancestor
7883 .children_by_field_name("declarator", &mut declarator_cursor)
7884 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
7885 {
7886 return true;
7887 }
7888 if matches!(
7889 ancestor.kind(),
7890 "declaration"
7891 | "field_declaration"
7892 | "parameter_declaration"
7893 | "optional_parameter_declaration"
7894 | "function_definition"
7895 | "type_definition"
7896 | "alias_declaration"
7897 | "class_specifier"
7898 | "struct_specifier"
7899 | "union_specifier"
7900 | "enum_specifier"
7901 ) {
7902 return false;
7903 }
7904 current = ancestor.parent();
7905 }
7906 false
7907}
7908
7909pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
7917 let mut current = parameter.parent();
7918 while let Some(ancestor) = current {
7919 if ancestor.kind() == "lambda_expression" {
7920 return ancestor
7921 .child_by_field_name("declarator")
7922 .is_some_and(|declarator| {
7923 declarator.start_byte() <= parameter.start_byte()
7924 && parameter.end_byte() <= declarator.end_byte()
7925 });
7926 }
7927 if ancestor.kind() == "function_definition" {
7928 return ancestor
7929 .child_by_field_name("declarator")
7930 .is_some_and(|declarator| {
7931 declarator.start_byte() <= parameter.start_byte()
7932 && parameter.end_byte() <= declarator.end_byte()
7933 });
7934 }
7935 current = ancestor.parent();
7936 }
7937 false
7938}
7939
7940fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
7941 if specifier.child_by_field_name("body").is_some() {
7942 return true;
7943 }
7944 let mut current = specifier.parent();
7945 while let Some(ancestor) = current {
7946 match ancestor.kind() {
7947 "type_descriptor"
7948 | "parameter_declaration"
7949 | "optional_parameter_declaration"
7950 | "template_argument_list"
7951 | "cast_expression" => return false,
7952 "declaration" | "field_declaration" => {
7953 let mut cursor = ancestor.walk();
7954 return ancestor
7955 .children_by_field_name("declarator", &mut cursor)
7956 .next()
7957 .is_none();
7958 }
7959 "translation_unit" => return true,
7960 _ => current = ancestor.parent(),
7961 }
7962 }
7963 false
7964}
7965
7966pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
7967 match node.kind() {
7968 "identifier"
7969 | "field_identifier"
7970 | "qualified_identifier"
7971 | "scoped_identifier"
7972 | "operator_name"
7973 | "destructor_name"
7974 | "literal_operator_name" => Some(node),
7975 _ => node
7976 .child_by_field_name("declarator")
7977 .or_else(|| node.child_by_field_name("name"))
7978 .or_else(|| node.child_by_field_name("field"))
7979 .and_then(declarator_name_node),
7980 }
7981}
7982
7983fn declarator_name_path_contains(
7984 declarator: Node<'_>,
7985 candidate: Node<'_>,
7986 allow_type_identifier: bool,
7987) -> bool {
7988 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
7989 return false;
7990 };
7991 let mut current = Some(declarator);
7992 while let Some(node) = current {
7993 if same_node(node, candidate) {
7994 return true;
7995 }
7996 if same_node(node, name) {
7997 return false;
7998 }
7999 current = node
8000 .child_by_field_name("declarator")
8001 .or_else(|| node.child_by_field_name("name"))
8002 .or_else(|| node.child_by_field_name("field"));
8003 }
8004 false
8005}
8006
8007fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
8008 match node.kind() {
8009 "identifier"
8010 | "field_identifier"
8011 | "operator_name"
8012 | "destructor_name"
8013 | "literal_operator_name" => Some(node),
8014 "type_identifier" if allow_type_identifier => Some(node),
8015 _ => node
8016 .child_by_field_name("declarator")
8017 .or_else(|| node.child_by_field_name("name"))
8018 .or_else(|| node.child_by_field_name("field"))
8019 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
8020 }
8021}
8022
8023pub fn is_nested_type_node(node: Node<'_>) -> bool {
8026 node.parent().is_some_and(|parent| {
8027 matches!(
8028 parent.kind(),
8029 "qualified_identifier" | "scoped_type_identifier" | "template_type"
8030 )
8031 })
8032}
8033
8034pub struct OutOfLineMemberDefinitionOwners<'tree> {
8035 pub owners: Vec<(Node<'tree>, CodeUnit)>,
8036 innermost: Option<(Node<'tree>, CodeUnit)>,
8037}
8038
8039impl OutOfLineMemberDefinitionOwners<'_> {
8040 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
8041 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
8042 }
8043}
8044
8045pub struct QualifiedOwnerComponents<'tree> {
8046 pub nodes: Vec<Node<'tree>>,
8047 pub names: Vec<String>,
8048 pub global: bool,
8049}
8050
8051pub fn qualified_owner_components<'tree>(
8052 node: Node<'tree>,
8053 source: &str,
8054) -> Option<QualifiedOwnerComponents<'tree>> {
8055 let mut nodes = cpp_name_component_nodes(node)?;
8056 nodes.pop()?;
8057 if nodes.is_empty() {
8058 return None;
8059 }
8060 let names = nodes
8061 .iter()
8062 .map(|component| node_text(*component, source).to_string())
8063 .collect();
8064 Some(QualifiedOwnerComponents {
8065 nodes,
8066 names,
8067 global: is_globally_qualified_cpp_name(node),
8068 })
8069}
8070
8071pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
8075 if node.kind() != "qualified_identifier" {
8076 return None;
8077 }
8078 let destructor = node.child_by_field_name("name")?;
8079 if destructor.kind() != "destructor_name" {
8080 return None;
8081 }
8082 (0..destructor.named_child_count())
8083 .filter_map(|index| destructor.named_child(index))
8084 .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
8085}
8086
8087pub fn out_of_line_member_definition_owner<'tree>(
8088 analyzer: &CppGraphSource<'_>,
8089 visibility: &VisibilityIndex<'_>,
8090 file: &ProjectFile,
8091 source: &str,
8092 node: Node<'tree>,
8093) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
8094 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8095 || !has_ancestor_kind(node, "function_definition")
8096 || !is_function_declarator_name_root(node)
8097 {
8098 return None;
8099 }
8100 let qualified = qualified_owner_components(node, source)?;
8101 let lexical_scope = enclosing_namespace_components(node, source)?;
8102 let mut owners = Vec::new();
8103 let mut innermost = None;
8104
8105 for component_count in 1..=qualified.names.len() {
8106 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
8107 .resolve_type_components_lexically(
8108 analyzer,
8109 file,
8110 &qualified.names[..component_count],
8111 qualified.global,
8112 &lexical_scope,
8113 )
8114 && !owners
8115 .iter()
8116 .any(|(_, existing)| same_visible_symbol(existing, &unit))
8117 {
8118 if component_count == qualified.names.len() {
8119 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
8120 }
8121 owners.push((qualified.nodes[component_count - 1], unit));
8122 }
8123 }
8124
8125 if innermost.is_none() {
8135 let indexed_owner_components = visibility
8136 .indexed_enclosing_owner_scope(analyzer, file, node)
8137 .or_else(|| {
8138 if qualified.names.len() <= 1 {
8143 return None;
8144 }
8145 let range = Range {
8146 start_byte: node.start_byte(),
8147 end_byte: node.end_byte(),
8148 start_line: node.start_position().row,
8149 end_line: node.end_position().row,
8150 };
8151 let start = analyzer.enclosing_code_unit(file, &range)?;
8152 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8153 brokk_bifrost_core::analyzer::Language::Cpp,
8154 &cpp_name_for(&start),
8155 );
8156 components.pop();
8157 Some(components)
8158 });
8159 if let Some(indexed_owner_components) = indexed_owner_components
8160 && indexed_owner_components.len() > qualified.names.len()
8161 && indexed_owner_components.ends_with(&qualified.names)
8162 && indexed_namespace_path_is_recoverable(
8163 &lexical_scope,
8164 &indexed_owner_components,
8165 )
8166 && (qualified.names.len() > 1 || !qualified.global)
8171 {
8172 let namespace_count = indexed_owner_components.len() - qualified.names.len();
8173 for component_count in 1..=qualified.names.len() {
8174 let expected = &indexed_owner_components[..namespace_count + component_count];
8175 let owner_node = qualified.nodes[component_count - 1];
8176 for owner in visibility
8177 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
8178 .filter(|candidate| candidate.is_class())
8179 .filter(|candidate| {
8180 canonical_cpp_scope_components(candidate) == expected
8181 && visibility.external_type_candidate_visible_in_context(
8182 analyzer, file, candidate, node,
8183 )
8184 })
8185 {
8186 if component_count == qualified.names.len() && innermost.is_none() {
8187 innermost = Some((owner_node, owner.clone()));
8188 }
8189 if !owners
8190 .iter()
8191 .any(|(_, existing)| same_symbol(existing, owner))
8192 {
8193 owners.push((owner_node, owner.clone()));
8194 }
8195 }
8196 }
8197 }
8198 }
8199 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
8200}
8201
8202fn is_function_declarator_name_root(node: Node<'_>) -> bool {
8203 let mut current = node;
8204 while let Some(parent) = current.parent() {
8205 if parent.kind() == "function_declarator" {
8206 return parent.child_by_field_name("declarator") == Some(current);
8207 }
8208 if matches!(
8209 parent.kind(),
8210 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
8211 ) && parent.child_by_field_name("declarator") == Some(current)
8212 {
8213 current = parent;
8214 continue;
8215 }
8216 return false;
8217 }
8218 false
8219}
8220
8221pub fn append_cpp_name_components(
8222 node: Node<'_>,
8223 source: &str,
8224 out: &mut Vec<String>,
8225) -> Option<()> {
8226 out.extend(
8227 cpp_name_component_nodes(node)?
8228 .into_iter()
8229 .map(|component| node_text(component, source).to_string()),
8230 );
8231 Some(())
8232}
8233
8234pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
8235 let mut components = Vec::new();
8236 append_cpp_name_components(node, source, &mut components)?;
8237 Some(components)
8238}
8239
8240pub fn cpp_template_reference_arguments(
8241 mut node: Node<'_>,
8242 source: &str,
8243) -> Option<Vec<CppTemplateExpression>> {
8244 loop {
8245 match node.kind() {
8246 "template_type" | "template_function" => {
8247 let arguments = node.child_by_field_name("arguments")?;
8248 let mut cursor = arguments.walk();
8249 return Some(
8250 arguments
8251 .named_children(&mut cursor)
8252 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
8253 .map(|argument| CppTemplateExpression {
8254 text: normalize_cpp_whitespace(node_text(argument, source)),
8255 term: cpp_template_term(argument, source, &[]),
8256 })
8257 .collect(),
8258 );
8259 }
8260 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
8261 node = node
8262 .child_by_field_name("name")
8263 .or_else(|| node.child_by_field_name("type"))?;
8264 }
8265 _ => return None,
8266 }
8267 }
8268}
8269
8270fn cpp_reconcile_primary_template_parameters(
8271 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
8272 preferred: &CodeUnit,
8273) -> Option<Vec<CppTemplateParameterMetadata>> {
8274 let canonical = candidates
8275 .iter()
8276 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
8277 let mut merged = canonical
8278 .parameters
8279 .iter()
8280 .map(|parameter| CppTemplateParameterMetadata {
8281 name: parameter.name.clone(),
8282 kind: parameter.kind,
8283 variadic: parameter.variadic,
8284 default: None,
8285 })
8286 .collect::<Vec<_>>();
8287
8288 for (_, metadata) in candidates {
8289 if metadata.parameters.len() != merged.len() {
8290 return None;
8291 }
8292 let rename_bindings = metadata
8293 .parameters
8294 .iter()
8295 .zip(&merged)
8296 .map(|(parameter, canonical)| {
8297 (
8298 parameter.name.clone(),
8299 CppTemplateTerm::Parameter(canonical.name.clone()),
8300 )
8301 })
8302 .collect::<HashMap<_, _>>();
8303 for ((parameter, canonical), merged_parameter) in metadata
8304 .parameters
8305 .iter()
8306 .zip(&canonical.parameters)
8307 .zip(&mut merged)
8308 {
8309 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
8310 return None;
8311 }
8312 let Some(default) = ¶meter.default else {
8313 continue;
8314 };
8315 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
8316 if let Some(existing) = &merged_parameter.default {
8317 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
8318 return None;
8319 }
8320 } else {
8321 merged_parameter.default = Some(CppTemplateExpression {
8322 text: default.text.clone(),
8323 term: normalized_term,
8324 });
8325 }
8326 }
8327 }
8328 Some(merged)
8329}
8330
8331pub fn cpp_bind_template_arguments(
8332 parameters: &[CppTemplateParameterMetadata],
8333 explicit_arguments: &[CppTemplateExpression],
8334) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
8335 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
8336 if variadic_index.is_some_and(|index| {
8337 index + 1 != parameters.len()
8338 || parameters[index + 1..]
8339 .iter()
8340 .any(|parameter| parameter.variadic)
8341 }) {
8342 return None;
8343 }
8344 let fixed_count = variadic_index.unwrap_or(parameters.len());
8345 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
8346 return None;
8347 }
8348 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
8349 let mut expanded = explicit_arguments[..explicit_fixed_count]
8350 .iter()
8351 .map(cpp_clone_template_expression_iterative)
8352 .collect::<Vec<_>>();
8353 let mut bindings = HashMap::default();
8354 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
8355 bindings.insert(
8356 parameter.name.clone(),
8357 cpp_clone_template_term_iterative(&argument.term),
8358 );
8359 }
8360 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
8361 let default = parameter.default.as_ref()?;
8362 let term = cpp_substitute_template_term(&default.term, &bindings)?;
8363 bindings.insert(parameter.name.clone(), term.clone());
8364 expanded.push(CppTemplateExpression {
8365 text: default.text.clone(),
8366 term,
8367 });
8368 }
8369 if let Some(index) = variadic_index {
8370 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
8371 expanded.extend(
8372 packed_arguments
8373 .iter()
8374 .map(cpp_clone_template_expression_iterative),
8375 );
8376 bindings.insert(
8377 parameters[index].name.clone(),
8378 CppTemplateTerm::Node {
8379 kind: "parameter_pack".to_string(),
8380 children: packed_arguments
8381 .iter()
8382 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
8383 .collect(),
8384 },
8385 );
8386 }
8387 Some((expanded, bindings))
8388}
8389
8390fn cpp_specialization_matches(
8391 metadata: &CppTemplateMetadata,
8392 arguments: &[CppTemplateExpression],
8393) -> bool {
8394 if metadata.specialization_arguments.len() != arguments.len() {
8395 return false;
8396 }
8397 let parameter_names = metadata
8398 .parameters
8399 .iter()
8400 .map(|parameter| parameter.name.as_str())
8401 .collect::<HashSet<_>>();
8402 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
8403 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
8404 if !cpp_unify_template_term(
8405 &pattern.term,
8406 &argument.term,
8407 ¶meter_names,
8408 &mut bindings,
8409 ) {
8410 return false;
8411 }
8412 }
8413 true
8414}
8415
8416fn cpp_specialization_more_specialized(
8417 candidate: &CppTemplateMetadata,
8418 other: &CppTemplateMetadata,
8419) -> bool {
8420 cpp_specialization_pattern_accepts(other, candidate)
8421 && !cpp_specialization_pattern_accepts(candidate, other)
8422}
8423
8424fn cpp_specialization_pattern_accepts(
8425 broader: &CppTemplateMetadata,
8426 narrower: &CppTemplateMetadata,
8427) -> bool {
8428 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
8429 return false;
8430 }
8431 let parameter_names = broader
8432 .parameters
8433 .iter()
8434 .map(|parameter| parameter.name.as_str())
8435 .collect::<HashSet<_>>();
8436 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
8437 broader
8438 .specialization_arguments
8439 .iter()
8440 .zip(&narrower.specialization_arguments)
8441 .all(|(pattern, argument)| {
8442 cpp_unify_template_term(
8443 &pattern.term,
8444 &argument.term,
8445 ¶meter_names,
8446 &mut bindings,
8447 )
8448 })
8449}
8450
8451pub fn cpp_substitute_template_term(
8452 term: &CppTemplateTerm,
8453 bindings: &HashMap<String, CppTemplateTerm>,
8454) -> Option<CppTemplateTerm> {
8455 enum Work<'a> {
8456 Visit(&'a CppTemplateTerm),
8457 Build { kind: String, child_count: usize },
8458 }
8459
8460 let mut work = vec![Work::Visit(term)];
8461 let mut substituted = Vec::new();
8462 while let Some(next) = work.pop() {
8463 match next {
8464 Work::Visit(CppTemplateTerm::Parameter(name)) => {
8465 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
8466 }
8467 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
8468 substituted.push(CppTemplateTerm::Atom {
8469 kind: kind.clone(),
8470 text: text.clone(),
8471 });
8472 }
8473 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
8474 work.push(Work::Build {
8475 kind: kind.clone(),
8476 child_count: children.len(),
8477 });
8478 work.extend(children.iter().rev().map(Work::Visit));
8479 }
8480 Work::Build { kind, child_count } => {
8481 let children = substituted.split_off(substituted.len() - child_count);
8482 substituted.push(CppTemplateTerm::Node { kind, children });
8483 }
8484 }
8485 }
8486 substituted.pop()
8487}
8488
8489pub fn cpp_substitute_template_arguments(
8490 arguments: &[CppTemplateExpression],
8491 bindings: &HashMap<String, CppTemplateTerm>,
8492) -> Option<Vec<CppTemplateExpression>> {
8493 let mut substituted = Vec::new();
8494 for argument in arguments {
8495 let CppTemplateTerm::Node { kind, children } = &argument.term else {
8496 substituted.push(CppTemplateExpression {
8497 text: argument.text.clone(),
8498 term: cpp_substitute_template_term(&argument.term, bindings)?,
8499 });
8500 continue;
8501 };
8502 if kind != "parameter_pack_expansion" {
8503 substituted.push(CppTemplateExpression {
8504 text: argument.text.clone(),
8505 term: cpp_substitute_template_term(&argument.term, bindings)?,
8506 });
8507 continue;
8508 }
8509 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
8510 return None;
8511 };
8512 if ellipsis != "..." {
8513 return None;
8514 }
8515
8516 let mut pack_names = Vec::new();
8517 let mut work = vec![pattern];
8518 while let Some(term) = work.pop() {
8519 match term {
8520 CppTemplateTerm::Parameter(name)
8521 if matches!(
8522 bindings.get(name),
8523 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
8524 ) =>
8525 {
8526 if !pack_names.contains(name) {
8527 pack_names.push(name.clone());
8528 }
8529 }
8530 CppTemplateTerm::Node { children, .. } => work.extend(children),
8531 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
8532 }
8533 }
8534 let first_pack = pack_names.first()?;
8535 let CppTemplateTerm::Node {
8536 children: first_elements,
8537 ..
8538 } = bindings.get(first_pack)?
8539 else {
8540 return None;
8541 };
8542 let pack_len = first_elements.len();
8543 for pack_name in &pack_names {
8544 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
8545 return None;
8546 };
8547 if children.len() != pack_len {
8548 return None;
8549 }
8550 }
8551 for index in 0..pack_len {
8552 let mut element_bindings = bindings.clone();
8553 for pack_name in &pack_names {
8554 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
8555 return None;
8556 };
8557 element_bindings.insert(
8558 pack_name.clone(),
8559 cpp_clone_template_term_iterative(&children[index]),
8560 );
8561 }
8562 substituted.push(CppTemplateExpression {
8563 text: argument.text.clone(),
8564 term: cpp_substitute_template_term(pattern, &element_bindings)?,
8565 });
8566 }
8567 }
8568 Some(substituted)
8569}
8570
8571fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
8572 enum Work<'a> {
8573 Visit(&'a CppTemplateTerm),
8574 Build { kind: String, child_count: usize },
8575 }
8576
8577 let mut work = vec![Work::Visit(term)];
8578 let mut cloned = Vec::new();
8579 while let Some(next) = work.pop() {
8580 match next {
8581 Work::Visit(CppTemplateTerm::Parameter(name)) => {
8582 cloned.push(CppTemplateTerm::Parameter(name.clone()));
8583 }
8584 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
8585 cloned.push(CppTemplateTerm::Atom {
8586 kind: kind.clone(),
8587 text: text.clone(),
8588 });
8589 }
8590 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
8591 work.push(Work::Build {
8592 kind: kind.clone(),
8593 child_count: children.len(),
8594 });
8595 work.extend(children.iter().rev().map(Work::Visit));
8596 }
8597 Work::Build { kind, child_count } => {
8598 let children = cloned.split_off(cloned.len() - child_count);
8599 cloned.push(CppTemplateTerm::Node { kind, children });
8600 }
8601 }
8602 }
8603 cloned
8604 .pop()
8605 .expect("template term traversal emits one root")
8606}
8607
8608fn cpp_clone_template_expression_iterative(
8609 expression: &CppTemplateExpression,
8610) -> CppTemplateExpression {
8611 CppTemplateExpression {
8612 text: expression.text.clone(),
8613 term: cpp_clone_template_term_iterative(&expression.term),
8614 }
8615}
8616
8617pub fn cpp_unify_template_term(
8618 pattern: &CppTemplateTerm,
8619 argument: &CppTemplateTerm,
8620 parameters: &HashSet<&str>,
8621 bindings: &mut HashMap<String, CppTemplateTerm>,
8622) -> bool {
8623 let mut work = vec![(pattern, argument)];
8624 while let Some((pattern, argument)) = work.pop() {
8625 match pattern {
8626 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
8627 if let Some(bound) = bindings.get(name) {
8628 if !cpp_template_terms_equal(bound, argument) {
8629 return false;
8630 }
8631 } else {
8632 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
8633 }
8634 }
8635 CppTemplateTerm::Atom {
8636 kind: pattern_kind,
8637 text: pattern_text,
8638 } => {
8639 if !matches!(
8640 argument,
8641 CppTemplateTerm::Atom { kind, text }
8642 if kind == pattern_kind && text == pattern_text
8643 ) {
8644 return false;
8645 }
8646 }
8647 CppTemplateTerm::Node {
8648 kind: pattern_kind,
8649 children: pattern_children,
8650 } => {
8651 let CppTemplateTerm::Node { kind, children } = argument else {
8652 return false;
8653 };
8654 if kind != pattern_kind || children.len() != pattern_children.len() {
8655 return false;
8656 }
8657 work.extend(pattern_children.iter().zip(children).rev());
8658 }
8659 CppTemplateTerm::Parameter(_) => return false,
8660 }
8661 }
8662 true
8663}
8664
8665fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
8666 let mut work = vec![(left, right)];
8667 while let Some((left, right)) = work.pop() {
8668 match (left, right) {
8669 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
8670 if left != right {
8671 return false;
8672 }
8673 }
8674 (
8675 CppTemplateTerm::Atom {
8676 kind: left_kind,
8677 text: left_text,
8678 },
8679 CppTemplateTerm::Atom {
8680 kind: right_kind,
8681 text: right_text,
8682 },
8683 ) => {
8684 if left_kind != right_kind || left_text != right_text {
8685 return false;
8686 }
8687 }
8688 (
8689 CppTemplateTerm::Node {
8690 kind: left_kind,
8691 children: left_children,
8692 },
8693 CppTemplateTerm::Node {
8694 kind: right_kind,
8695 children: right_children,
8696 },
8697 ) => {
8698 if left_kind != right_kind || left_children.len() != right_children.len() {
8699 return false;
8700 }
8701 work.extend(left_children.iter().zip(right_children).rev());
8702 }
8703 _ => return false,
8704 }
8705 }
8706 true
8707}
8708
8709pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
8710 let mut components = Vec::new();
8711 let mut stack = vec![node];
8712 while let Some(current) = stack.pop() {
8713 match current.kind() {
8714 "identifier"
8715 | "field_identifier"
8716 | "namespace_identifier"
8717 | "type_identifier"
8718 | "operator_name"
8719 | "destructor_name" => components.push(current),
8720 "template_type" | "template_function" => {
8721 stack.push(current.child_by_field_name("name")?);
8722 }
8723 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
8724 stack.push(current.child_by_field_name("name")?);
8725 if let Some(scope) = current.child_by_field_name("scope") {
8726 stack.push(scope);
8727 }
8728 }
8729 "nested_namespace_specifier" => {
8730 for index in (0..current.named_child_count()).rev() {
8731 stack.push(current.named_child(index)?);
8732 }
8733 }
8734 _ => return None,
8735 }
8736 }
8737 Some(components)
8738}
8739
8740pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
8741 node.child_by_field_name("scope").is_none()
8742 && node.child(0).is_some_and(|child| child.kind() == "::")
8743}
8744
8745fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
8746 let mut namespaces = Vec::new();
8747 let mut current = node.parent();
8748 while let Some(parent) = current {
8749 if parent.kind() == "namespace_definition"
8750 && let Some(name) = parent.child_by_field_name("name")
8751 {
8752 let mut components = Vec::new();
8753 append_cpp_name_components(name, source, &mut components)?;
8754 namespaces.push(components);
8755 }
8756 current = parent.parent();
8757 }
8758 namespaces.reverse();
8759 Some(namespaces.into_iter().flatten().collect())
8760}
8761
8762fn indexed_namespace_path_is_recoverable(
8771 lexical_scope: &[String],
8772 indexed_owner_scope: &[String],
8773) -> bool {
8774 if lexical_scope.is_empty() || lexical_scope.len() >= indexed_owner_scope.len() {
8775 return false;
8776 }
8777 let mut indexed = indexed_owner_scope.iter();
8778 lexical_scope
8779 .iter()
8780 .all(|component| indexed.any(|candidate| candidate == component))
8781}
8782
8783pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
8784 let mut current = node.parent();
8785 while let Some(parent) = current {
8786 if parent.kind() == kind {
8787 return true;
8788 }
8789 current = parent.parent();
8790 }
8791 false
8792}
8793
8794pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
8800 loop {
8801 let next = match node.kind() {
8802 "qualified_identifier"
8803 | "scoped_identifier"
8804 | "template_function"
8805 | "template_type" => node.child_by_field_name("name"),
8806 "field_expression" => node.child_by_field_name("field"),
8807 _ => None,
8808 };
8809 let Some(next) = next else {
8810 return node;
8811 };
8812 node = next;
8813 }
8814}
8815
8816pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
8819 while let Some(parent) = node.parent() {
8820 match parent.kind() {
8821 "call_expression" => {
8822 return parent
8823 .child_by_field_name("function")
8824 .or_else(|| parent.named_child(0))
8825 == Some(node);
8826 }
8827 "qualified_identifier"
8828 | "scoped_identifier"
8829 | "template_function"
8830 | "template_type"
8831 | "field_expression" => node = parent,
8832 _ => return false,
8833 }
8834 }
8835 false
8836}
8837
8838pub fn type_reference_hit_node<'tree, T: Clone + Eq + Hash>(
8839 node: Node<'tree>,
8840 file: &ProjectFile,
8841 source: &str,
8842 bindings: &LocalInferenceEngine<T>,
8843) -> Node<'tree> {
8844 if is_call_callee_node(node) {
8845 return function_terminal_node(node);
8846 }
8847 if file.rel_path().extension().is_some_and(|ext| ext == "c") {
8848 return node;
8849 }
8850 let mut current = node;
8851 let declaration = loop {
8852 let Some(parent) = current.parent() else {
8853 return node;
8854 };
8855 if parent.kind() == "declaration" {
8856 break parent;
8857 }
8858 if matches!(
8859 parent.kind(),
8860 "compound_statement" | "function_definition" | "lambda_expression"
8861 ) {
8862 return node;
8863 }
8864 current = parent;
8865 };
8866 let Some(_type_node) = declaration.child_by_field_name("type").filter(|type_node| {
8867 type_node.start_byte() <= node.start_byte() && node.end_byte() <= type_node.end_byte()
8868 }) else {
8869 return node;
8870 };
8871 let mut cursor = declaration.walk();
8872 let constructs_object = declaration.named_children(&mut cursor).any(|child| {
8873 if child.kind() == "init_declarator" {
8874 return child.child_by_field_name("value").is_some()
8875 || first_named_child_of_kind(child, "initializer_list").is_some()
8876 || first_named_child_of_kind(child, "compound_literal_expression").is_some();
8877 }
8878 let declarator = if is_declarator_node(child) {
8879 Some(child)
8880 } else {
8881 None
8882 };
8883 declarator.is_some_and(|declarator| {
8884 declarator.kind() == "function_declarator"
8885 && has_ancestor_kind(declarator, "compound_statement")
8886 && declarator
8887 .child_by_field_name("declarator")
8888 .is_some_and(|name| name.kind() == "identifier")
8889 && declarator
8890 .child_by_field_name("parameters")
8891 .is_some_and(|parameters| {
8892 constructor_parameters_look_like_expressions(parameters, source, bindings)
8893 })
8894 })
8895 });
8896 if constructs_object {
8897 function_terminal_node(node)
8898 } else {
8899 node
8900 }
8901}
8902
8903pub fn normalize_type_text(value: &str) -> String {
8904 strip_tag_type_prefix(
8905 normalize_cpp_whitespace(value)
8906 .trim_start_matches("const ")
8907 .trim_end_matches('*')
8908 .trim_end_matches('&')
8909 .trim(),
8910 )
8911 .to_string()
8912}
8913
8914fn strip_tag_type_prefix(value: &str) -> &str {
8915 let value = value.trim_start_matches("const ");
8916 value
8917 .strip_prefix("struct ")
8918 .or_else(|| value.strip_prefix("class "))
8919 .or_else(|| value.strip_prefix("enum "))
8920 .unwrap_or(value)
8921 .trim()
8922}
8923
8924pub fn normalize_reference_name(value: &str) -> Option<String> {
8925 let normalized = normalize_cpp_reference_text(value);
8926 (!normalized.is_empty()).then_some(normalized)
8927}
8928
8929pub fn normalize_cpp_reference_text(value: &str) -> String {
8930 let mut text = normalize_cpp_whitespace(value)
8931 .trim_start_matches("new ")
8932 .trim()
8933 .to_string();
8934 if let Some(index) = text.find(['(', '{']) {
8935 text.truncate(index);
8936 }
8937 if let Some(index) = text.find('<') {
8938 text.truncate(index);
8939 }
8940 let normalized = text
8941 .trim()
8942 .trim_start_matches("const ")
8943 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
8944 .trim_matches(':')
8945 .trim();
8946 strip_tag_type_prefix(normalized).to_string()
8947}
8948
8949pub fn cpp_name_for(unit: &CodeUnit) -> String {
8950 let short = unit.short_name().replace(['.', '$'], "::");
8951 if unit.package_name().is_empty() {
8952 short
8953 } else {
8954 format!("{}::{}", unit.package_name(), short)
8955 }
8956}
8957
8958fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
8962 let fq = unit.fq();
8963 if fq.is_empty() {
8964 return None;
8965 }
8966 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8967 Some(
8968 fq.segments()
8969 .iter()
8970 .map(|&segment| interner.resolve(segment).0)
8971 .collect::<Vec<_>>()
8972 .join("::"),
8973 )
8974}
8975
8976fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
8977 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
8978 || unit.fq().is_empty() && cpp_name_for(unit) == expected
8979}
8980
8981pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
8990 let fq = unit.fq();
8991 if !fq.is_empty() {
8992 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8993 let scope = fq
8994 .segments()
8995 .iter()
8996 .filter_map(|&segment| {
8997 let (text, kind) = interner.resolve(segment);
8998 matches!(
8999 kind,
9000 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
9001 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9002 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9003 )
9004 .then(|| text.to_string())
9005 })
9006 .collect();
9007 return scope;
9008 }
9009 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9010 brokk_bifrost_core::analyzer::Language::Cpp,
9011 &cpp_name_for(unit),
9012 )
9013}
9014
9015pub fn terminal_name(value: &str) -> &str {
9026 value
9027 .rsplit("::")
9028 .next()
9029 .unwrap_or(value)
9030 .rsplit(['.', '-', '>'])
9031 .next()
9032 .unwrap_or(value)
9033 .trim()
9034}
9035
9036pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
9037 terminal_name(&normalize_cpp_reference_text(value)) == expected
9038}
9039
9040pub fn name_matches_callable(value: &str, expected: &str) -> bool {
9041 name_matches_terminal(value, expected)
9042 || expected.starts_with("operator")
9043 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
9044}
9045
9046pub fn name_mentions(value: &str, expected: &str) -> bool {
9047 normalize_cpp_reference_text(value)
9048 .split("::")
9049 .any(|part| part == expected)
9050}
9051
9052pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
9053 let cpp_name = cpp_name_for(unit);
9054 if reference.contains("::") {
9055 return reference == cpp_name;
9056 }
9057 reference == cpp_name
9058 || terminal_name(reference) == unit.identifier()
9059 && (unit.package_name().is_empty() || reference == unit.identifier())
9060}
9061
9062pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
9063 match kind {
9064 TargetKind::Type
9065 | TargetKind::Constructor
9066 | TargetKind::Method
9067 | TargetKind::MemberField => true,
9068 TargetKind::FreeFunction => unit.is_function(),
9069 TargetKind::GlobalField => unit.is_field(),
9070 }
9071}
9072
9073pub fn is_type_alias(unit: &CodeUnit) -> bool {
9074 unit.kind() == CodeUnitType::Field
9075 && unit.signature().is_some_and(|signature| {
9076 signature.starts_with("typedef ") || signature.starts_with("using ")
9077 })
9078}
9079
9080fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
9081 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
9082 let target_name = cpp_name_for(target);
9083 if normalized.contains("::") {
9084 return normalized == target_name;
9085 }
9086 if let Some(namespace) = alias.namespace.as_deref() {
9087 return namespace_prefixes(namespace)
9088 .into_iter()
9089 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
9090 }
9091 target.package_name().is_empty() && normalized == target.identifier()
9092}
9093
9094fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
9095 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
9096 if normalized.contains("::") {
9097 return vec![normalized];
9098 }
9099 alias
9100 .namespace
9101 .as_deref()
9102 .map(namespace_prefixes)
9103 .map(|prefixes| {
9104 prefixes
9105 .into_iter()
9106 .map(|prefix| format!("{prefix}::{normalized}"))
9107 .collect()
9108 })
9109 .unwrap_or_else(|| vec![normalized])
9110}
9111
9112pub fn cpp_function_return_type_text(
9115 analyzer: &CppGraphSource<'_>,
9116 function: &CodeUnit,
9117) -> Option<String> {
9118 let metadata = analyzer.signature_metadata(function);
9119 if !metadata.is_empty() {
9120 let first = metadata.first()?.return_type_text()?;
9121 return metadata
9122 .iter()
9123 .all(|metadata| metadata.return_type_text() == Some(first))
9124 .then(|| first.to_string());
9125 }
9126 let signature = cpp_function_signature_text(analyzer, function)?;
9127 cpp_function_return_type_text_from_signature(&signature)
9128}
9129
9130fn cpp_function_signature_text(
9131 analyzer: &CppGraphSource<'_>,
9132 function: &CodeUnit,
9133) -> Option<String> {
9134 function
9135 .signature()
9136 .filter(|signature| signature.contains(function.identifier()))
9137 .map(str::to_string)
9138 .or_else(|| analyzer.signatures(function).first().cloned())
9139 .or_else(|| analyzer.get_source(function, false))
9140}
9141
9142fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
9143 let open = signature.find('(')?;
9144 let name_at = cpp_function_name_start(signature, open)?;
9145 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
9146 return Some(return_type);
9147 }
9148 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
9149 .split_whitespace()
9150 .filter(|token| {
9151 !matches!(
9152 *token,
9153 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
9154 )
9155 })
9156 .collect::<Vec<_>>()
9157 .join(" ");
9158 let type_text = type_text.trim();
9159 (!type_text.is_empty()).then(|| type_text.to_string())
9160}
9161
9162fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
9163 let before_parameters = &signature[..open];
9164 if let Some(operator_at) = before_parameters.rfind("operator") {
9165 let boundary = operator_at == 0
9166 || before_parameters[..operator_at]
9167 .chars()
9168 .next_back()
9169 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
9170 if boundary {
9171 return Some(operator_at);
9172 }
9173 }
9174 before_parameters
9175 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
9176 .map(|index| index + 1)
9177}
9178
9179fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
9180 let open = signature_from_name.find('(')?;
9181 let mut depth = 0i32;
9182 for (offset, ch) in signature_from_name[open..].char_indices() {
9183 match ch {
9184 '(' => depth += 1,
9185 ')' => {
9186 depth -= 1;
9187 if depth == 0 {
9188 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
9189 let arrow = rest.find("->")?;
9190 let return_type = rest[arrow + 2..].trim_start();
9191 let return_type = return_type
9192 .split(['{', ';'])
9193 .next()
9194 .unwrap_or(return_type)
9195 .trim();
9196 return (!return_type.is_empty()).then(|| return_type.to_string());
9197 }
9198 }
9199 _ => {}
9200 }
9201 }
9202 None
9203}
9204
9205fn cpp_strip_leading_template_clause(text: &str) -> &str {
9208 let trimmed = text.trim_start();
9209 let Some(rest) = trimmed.strip_prefix("template") else {
9210 return text;
9211 };
9212 let rest = rest.trim_start();
9213 if !rest.starts_with('<') {
9214 return text;
9215 }
9216 let mut depth = 0i32;
9217 for (offset, ch) in rest.char_indices() {
9218 match ch {
9219 '<' => depth += 1,
9220 '>' => {
9221 depth -= 1;
9222 if depth == 0 {
9223 return rest[offset + ch.len_utf8()..].trim_start();
9224 }
9225 }
9226 _ => {}
9227 }
9228 }
9229 text
9230}
9231
9232pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
9233 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
9243 namespace
9244 .strip_prefix("anonymous_namespace::")
9245 .unwrap_or(namespace)
9246 .to_string()
9247 })
9248}
9249
9250fn namespace_prefixes(namespace: &str) -> Vec<String> {
9251 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9257 brokk_bifrost_core::analyzer::Language::Cpp,
9258 namespace,
9259 );
9260 let mut prefixes = Vec::new();
9261 while !parts.is_empty() {
9262 prefixes.push(parts.join("::"));
9263 parts.pop();
9264 }
9265 prefixes
9266}
9267
9268fn nearest_namespace_candidates(
9269 candidates: Vec<CodeUnit>,
9270 normalized: &str,
9271 lexical_namespace: Option<&str>,
9272) -> Vec<CodeUnit> {
9273 if normalized.contains("::") {
9274 return candidates;
9275 }
9276 if let Some(namespace) = lexical_namespace {
9277 for prefix in namespace_prefixes(namespace) {
9278 let scoped = candidates
9279 .iter()
9280 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
9281 .cloned()
9282 .collect::<Vec<_>>();
9283 if !scoped.is_empty() {
9284 return scoped;
9285 }
9286 }
9287 }
9288 candidates
9289 .into_iter()
9290 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
9291 .collect()
9292}
9293
9294pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
9295 let mut namespaces = Vec::new();
9296 let mut current = node.parent();
9297 while let Some(parent) = current {
9298 if parent.kind() == "namespace_definition"
9299 && let Some(name) = parent.child_by_field_name("name")
9300 {
9301 let namespace = normalize_cpp_reference_text(node_text(name, source));
9302 if !namespace.is_empty() {
9303 namespaces.push(namespace);
9304 }
9305 }
9306 current = parent.parent();
9307 }
9308 if namespaces.is_empty() {
9309 None
9310 } else {
9311 namespaces.reverse();
9312 Some(namespaces.join("::"))
9313 }
9314}
9315
9316pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
9320 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
9321}
9322
9323fn type_owner_resolution(
9324 analyzer: &CppGraphSource<'_>,
9325 code_unit: &CodeUnit,
9326) -> Option<ResolvedTypeOwner> {
9327 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
9328}
9329
9330fn target_forward_owner_resolution(
9336 analyzer: &CppGraphSource<'_>,
9337 code_unit: &CodeUnit,
9338) -> Option<ResolvedTypeOwner> {
9339 if !code_unit.is_function() {
9340 return None;
9341 }
9342 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9343 let owner_fqn = code_unit.fq().parent()?.display(interner);
9344 let cpp = analyzer.cpp?;
9345 let mut visible_files = HashSet::default();
9346 collect_include_closure(
9347 analyzer,
9348 cpp.include_target_index(),
9349 code_unit.source(),
9350 &mut visible_files,
9351 None,
9352 );
9353 let mut forward = None;
9354 for candidate in analyzer
9355 .global_usage_definition_index()
9356 .fqn(&owner_fqn)
9357 .into_iter()
9358 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
9359 {
9360 match cpp_class_declaration_strength(analyzer, candidate) {
9361 CppClassDeclarationStrength::Forward if forward.is_none() => {
9362 forward = Some(candidate.clone());
9363 }
9364 CppClassDeclarationStrength::Forward
9365 | CppClassDeclarationStrength::Full
9366 | CppClassDeclarationStrength::Unknown => return None,
9367 }
9368 }
9369 forward.map(|unit| ResolvedTypeOwner {
9370 unit,
9371 is_forward_declaration: true,
9372 })
9373}
9374
9375pub fn precise_parent_of(
9376 analyzer: &CppGraphSource<'_>,
9377 visibility: &VisibilityIndex<'_>,
9378 code_unit: &CodeUnit,
9379) -> Option<CodeUnit> {
9380 visibility.cached_precise_parent_of(analyzer, code_unit)
9381}
9382
9383fn precise_parent_resolution(
9384 analyzer: &CppGraphSource<'_>,
9385 code_unit: &CodeUnit,
9386) -> Option<ResolvedTypeOwner> {
9387 #[cfg(any(test, feature = "test-support"))]
9388 if let Some(cpp) = analyzer.cpp {
9389 cpp.record_cpp_parent_resolution_for_test();
9390 }
9391 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
9392 return Some(ResolvedTypeOwner {
9393 unit,
9394 is_forward_declaration: false,
9395 });
9396 }
9397 let fallback = analyzer.parent_of(code_unit);
9398 let Some(owner_name) = code_unit
9404 .short_name()
9405 .rsplit_once('.')
9406 .map(|(owner, _)| owner)
9407 else {
9408 return fallback.map(|unit| ResolvedTypeOwner {
9409 unit,
9410 is_forward_declaration: false,
9411 });
9412 };
9413 let owner_fqn = if code_unit.package_name().is_empty() {
9414 owner_name.to_string()
9415 } else {
9416 format!("{}.{}", code_unit.package_name(), owner_name)
9417 };
9418 match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
9419 DirectOwnerResolution::UniqueFull(owner) => {
9420 return Some(ResolvedTypeOwner {
9421 unit: owner,
9422 is_forward_declaration: false,
9423 });
9424 }
9425 DirectOwnerResolution::Ambiguous => return None,
9426 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
9427 }
9428 match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
9429 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
9430 unit: owner,
9431 is_forward_declaration: false,
9432 }),
9433 DirectOwnerResolution::Ambiguous => None,
9434 DirectOwnerResolution::ForwardsOnly(forwards) => {
9435 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
9436 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
9437 unit: owner,
9438 is_forward_declaration: false,
9439 }),
9440 FullOwnerResolution::None if forwards.len() == 1 => {
9441 forwards.into_iter().next().map(|unit| ResolvedTypeOwner {
9442 unit,
9443 is_forward_declaration: true,
9444 })
9445 }
9446 FullOwnerResolution::None | FullOwnerResolution::Ambiguous => None,
9447 }
9448 }
9449 DirectOwnerResolution::None => {
9450 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
9451 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
9452 unit: owner,
9453 is_forward_declaration: false,
9454 }),
9455 FullOwnerResolution::Ambiguous => None,
9456 FullOwnerResolution::None => fallback
9457 .filter(|parent| {
9458 parent.source() == code_unit.source()
9459 && parent.short_name() == owner_name
9460 && parent.package_name() == code_unit.package_name()
9461 && (!parent.is_class()
9462 || cpp_class_declaration_strength(analyzer, parent)
9463 == CppClassDeclarationStrength::Full)
9464 })
9465 .map(|unit| ResolvedTypeOwner {
9466 unit,
9467 is_forward_declaration: false,
9468 }),
9469 }
9470 }
9471 }
9472}
9473
9474fn exact_structural_type_parent(
9475 analyzer: &CppGraphSource<'_>,
9476 code_unit: &CodeUnit,
9477) -> Option<CodeUnit> {
9478 if !code_unit.is_function() && !code_unit.is_field() {
9479 return None;
9480 }
9481 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
9483 let parent = cpp.structural_parent_of(code_unit)?;
9484 (!parent.is_module()
9485 && parent.source() == code_unit.source()
9486 && parent.package_name() == code_unit.package_name()
9487 && parent.short_name() == encoded_owner)
9488 .then_some(parent)
9489}
9490
9491fn same_source_owner(
9492 analyzer: &CppGraphSource<'_>,
9493 code_unit: &CodeUnit,
9494 owner_fqn: &str,
9495 owner_name: &str,
9496) -> DirectOwnerResolution {
9497 let candidates = analyzer
9498 .global_usage_definition_index()
9499 .fqn(owner_fqn)
9500 .into_iter()
9501 .filter(|candidate| {
9502 candidate.is_class()
9503 && candidate.source() == code_unit.source()
9504 && candidate.short_name() == owner_name
9505 && candidate.package_name() == code_unit.package_name()
9506 })
9507 .collect::<Vec<_>>();
9508 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
9509 classify_direct_owner_candidates(analyzer, candidates.into_iter())
9510}
9511
9512fn visible_full_cpp_owner(
9513 analyzer: &CppGraphSource<'_>,
9514 code_unit: &CodeUnit,
9515 owner_fqn: &str,
9516 owner_name: &str,
9517) -> FullOwnerResolution {
9518 let Some(cpp) = analyzer.cpp else {
9519 return FullOwnerResolution::None;
9520 };
9521 let mut visible_files = HashSet::default();
9522 collect_include_closure(
9523 analyzer,
9524 cpp.include_target_index(),
9525 code_unit.source(),
9526 &mut visible_files,
9527 None,
9528 );
9529 let candidates = analyzer
9530 .global_usage_definition_index()
9531 .fqn(owner_fqn)
9532 .into_iter()
9533 .filter(|candidate| {
9534 candidate.is_class()
9535 && candidate.short_name() == owner_name
9536 && candidate.package_name() == code_unit.package_name()
9537 && visible_files.contains(candidate.source())
9538 })
9539 .collect::<Vec<_>>();
9540 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
9541 let mut full_definition = None;
9542 for candidate in candidates {
9543 match cpp_class_declaration_strength(analyzer, candidate) {
9544 CppClassDeclarationStrength::Full if full_definition.is_some() => {
9545 return FullOwnerResolution::Ambiguous;
9546 }
9547 CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
9548 CppClassDeclarationStrength::Forward => {}
9549 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
9550 }
9551 }
9552 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
9553}
9554
9555pub enum DirectOwnerResolution {
9556 None,
9557 ForwardsOnly(Vec<CodeUnit>),
9558 UniqueFull(CodeUnit),
9559 Ambiguous,
9560}
9561
9562enum FullOwnerResolution {
9563 None,
9564 Unique(CodeUnit),
9565 Ambiguous,
9566}
9567
9568#[derive(Clone, Copy, PartialEq, Eq)]
9569pub enum CppClassDeclarationStrength {
9570 Full,
9571 Forward,
9572 Unknown,
9573}
9574
9575fn directly_included_owner(
9576 analyzer: &CppGraphSource<'_>,
9577 code_unit: &CodeUnit,
9578 owner_fqn: &str,
9579 owner_name: &str,
9580) -> DirectOwnerResolution {
9581 let Some(cpp) = analyzer.cpp else {
9582 return DirectOwnerResolution::None;
9583 };
9584 let imports = analyzer.import_statements(code_unit.source());
9585 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
9586 .into_iter()
9587 .flat_map(|include| {
9588 resolve_include_targets_with_index(
9589 code_unit.source(),
9590 &include,
9591 cpp.include_target_index(),
9592 )
9593 })
9594 .collect();
9595 let candidates = analyzer
9596 .global_usage_definition_index()
9597 .fqn(owner_fqn)
9598 .into_iter()
9599 .filter(|candidate| {
9600 candidate.is_class()
9601 && candidate.short_name() == owner_name
9602 && candidate.package_name() == code_unit.package_name()
9603 && direct_includes.contains(candidate.source())
9604 })
9605 .collect::<Vec<_>>();
9606 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
9607 classify_direct_owner_candidates(analyzer, candidates.into_iter())
9608}
9609
9610fn prefer_member_declaring_owners<'a>(
9611 analyzer: &CppGraphSource<'_>,
9612 member: &CodeUnit,
9613 candidates: Vec<&'a CodeUnit>,
9614) -> Vec<&'a CodeUnit> {
9615 let matching = candidates
9616 .iter()
9617 .copied()
9618 .filter(|owner| owner_declares_member(analyzer, owner, member))
9619 .collect::<Vec<_>>();
9620 if matching.is_empty() {
9621 candidates
9622 } else {
9623 matching
9624 }
9625}
9626
9627fn owner_declares_member(
9628 analyzer: &CppGraphSource<'_>,
9629 owner: &CodeUnit,
9630 member: &CodeUnit,
9631) -> bool {
9632 analyzer.direct_children(owner).into_iter().any(|child| {
9633 child.kind() == member.kind()
9634 && child.identifier() == member.identifier()
9635 && child.signature() == member.signature()
9636 })
9637}
9638
9639fn classify_direct_owner_candidates<'a>(
9640 analyzer: &CppGraphSource<'_>,
9641 candidates: impl Iterator<Item = &'a CodeUnit>,
9642) -> DirectOwnerResolution {
9643 collapse_owner_candidates(candidates.map(|candidate| {
9644 (
9645 candidate.clone(),
9646 cpp_class_declaration_strength(analyzer, candidate),
9647 )
9648 }))
9649}
9650
9651pub fn collapse_owner_candidates(
9652 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
9653) -> DirectOwnerResolution {
9654 let mut full_definition = None;
9655 let mut forwards = Vec::new();
9656 for (candidate, strength) in candidates {
9657 match strength {
9658 CppClassDeclarationStrength::Full if full_definition.is_some() => {
9659 return DirectOwnerResolution::Ambiguous;
9660 }
9661 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
9662 CppClassDeclarationStrength::Forward => forwards.push(candidate),
9663 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
9664 }
9665 }
9666 if let Some(owner) = full_definition {
9667 DirectOwnerResolution::UniqueFull(owner)
9668 } else if !forwards.is_empty() {
9669 DirectOwnerResolution::ForwardsOnly(forwards)
9670 } else {
9671 DirectOwnerResolution::None
9672 }
9673}
9674
9675pub fn cpp_class_declaration_strength(
9676 analyzer: &CppGraphSource<'_>,
9677 candidate: &CodeUnit,
9678) -> CppClassDeclarationStrength {
9679 if let Some(prepared) = analyzer
9680 .cpp
9681 .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
9682 {
9683 return cpp_class_declaration_strength_in_tree(
9684 analyzer,
9685 candidate,
9686 prepared.source(),
9687 prepared.tree().root_node(),
9688 );
9689 }
9690 let Some(source) = analyzer.indexed_source(candidate.source()) else {
9691 return CppClassDeclarationStrength::Unknown;
9692 };
9693 #[cfg(any(test, feature = "test-support"))]
9694 if let Some(cpp) = analyzer.cpp {
9695 cpp.record_cpp_class_strength_parse_for_test();
9696 }
9697 let mut parser = Parser::new();
9698 if parser
9699 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9700 .is_err()
9701 {
9702 return CppClassDeclarationStrength::Unknown;
9703 }
9704 let Some(tree) = parser.parse(&source, None) else {
9705 return CppClassDeclarationStrength::Unknown;
9706 };
9707 cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
9708}
9709
9710fn cpp_class_declaration_strength_in_tree(
9711 analyzer: &CppGraphSource<'_>,
9712 candidate: &CodeUnit,
9713 source: &str,
9714 root: Node<'_>,
9715) -> CppClassDeclarationStrength {
9716 let ranges = analyzer.ranges(candidate);
9717 let mut saw_forward = false;
9718 for range in ranges {
9719 let mut stack = vec![root];
9720 while let Some(node) = stack.pop() {
9721 if node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
9722 continue;
9723 }
9724 if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
9725 if matches!(
9726 node.kind(),
9727 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9728 ) {
9729 if cpp_class_node_has_body(node) {
9730 return CppClassDeclarationStrength::Full;
9731 }
9732 saw_forward = true;
9733 } else if let Some(has_body) =
9734 recovered_exported_class_has_body(node, source, candidate.identifier())
9735 {
9736 if has_body {
9737 return CppClassDeclarationStrength::Full;
9738 }
9739 saw_forward = true;
9740 }
9741 }
9742 let mut cursor = node.walk();
9743 stack.extend(node.named_children(&mut cursor));
9744 }
9745 }
9746 if saw_forward {
9747 CppClassDeclarationStrength::Forward
9748 } else {
9749 CppClassDeclarationStrength::Unknown
9750 }
9751}
9752
9753fn cpp_class_node_has_body(node: Node<'_>) -> bool {
9754 node.child_by_field_name("body").is_some() || {
9755 let mut cursor = node.walk();
9756 node.named_children(&mut cursor).any(|child| {
9757 matches!(
9758 child.kind(),
9759 "declaration_list" | "field_declaration_list" | "enumerator_list"
9760 )
9761 })
9762 }
9763}
9764
9765pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
9766 let owner_name = code_unit
9771 .short_name()
9772 .rsplit_once('.')
9773 .map(|(owner, _)| owner)?;
9774 let owner_fqn = if code_unit.package_name().is_empty() {
9775 owner_name.to_string()
9776 } else {
9777 format!("{}.{}", code_unit.package_name(), owner_name)
9778 };
9779 ctx.analyzer
9780 .global_usage_definition_index()
9781 .fqn(&owner_fqn)
9782 .into_iter()
9783 .find(|candidate| {
9784 candidate.is_class()
9785 && ctx.visibility.is_visible(ctx.file, candidate)
9786 && candidate.short_name() == owner_name
9787 && candidate.package_name() == code_unit.package_name()
9788 })
9789 .cloned()
9790}
9791
9792pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
9793 left.kind() == right.kind()
9794 && left.fq_name() == right.fq_name()
9795 && left.signature() == right.signature()
9796 && left.source() == right.source()
9797}
9798
9799pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
9800 same_symbol(left, right) || same_logical_symbol(left, right)
9801}
9802
9803pub fn same_visible_global_field_symbol(
9804 analyzer: &CppGraphSource<'_>,
9805 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
9806 left: &CodeUnit,
9807 right: &CodeUnit,
9808) -> bool {
9809 if same_symbol(left, right) {
9810 return true;
9811 }
9812 if !same_logical_symbol(left, right) {
9813 return false;
9814 }
9815 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
9816 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
9817 {
9818 left.source() == right.source()
9819 } else {
9820 true
9821 }
9822}
9823
9824fn cpp_global_field_has_internal_linkage_cached(
9825 analyzer: &CppGraphSource<'_>,
9826 cache: &mut HashMap<CodeUnit, bool>,
9827 candidate: &CodeUnit,
9828) -> bool {
9829 if let Some(internal) = cache.get(candidate) {
9830 return *internal;
9831 }
9832 #[cfg(any(test, feature = "test-support"))]
9833 note_cpp_global_field_internal_linkage_classification_for_test();
9834 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
9835 cache.insert(candidate.clone(), internal);
9836 internal
9837}
9838
9839#[cfg(any(test, feature = "test-support"))]
9840thread_local! {
9841 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
9842}
9843
9844#[cfg(any(test, feature = "test-support"))]
9845fn note_cpp_global_field_internal_linkage_classification_for_test() {
9846 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
9847 count.set(count.get() + 1);
9848 });
9849}
9850
9851#[cfg(any(test, feature = "test-support"))]
9852pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
9853 body: impl FnOnce() -> T,
9854) -> (T, usize) {
9855 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
9856 count.set(0);
9857 let result = body();
9858 let observed = count.get();
9859 count.set(0);
9860 (result, observed)
9861 })
9862}
9863
9864pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
9865 left.kind() == right.kind()
9866 && left.fq_name() == right.fq_name()
9867 && left.signature() == right.signature()
9868}
9869
9870pub fn cpp_global_field_has_internal_linkage(
9871 analyzer: &CppGraphSource<'_>,
9872 candidate: &CodeUnit,
9873) -> bool {
9874 if !candidate.is_field() || candidate.short_name().contains('.') {
9875 return false;
9876 }
9877 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
9878 return false;
9879 };
9880 match local_linkage {
9881 CppFieldLinkage::Internal => true,
9882 CppFieldLinkage::External => false,
9883 CppFieldLinkage::InternalUnlessExternalPeer => {
9884 !cpp_global_field_linkage_peers(analyzer, candidate)
9885 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
9886 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
9887 }
9888 }
9889}
9890
9891fn cpp_global_field_linkage_peers<'a>(
9892 analyzer: &CppGraphSource<'a>,
9893 candidate: &'a CodeUnit,
9894) -> impl Iterator<Item = &'a CodeUnit> + 'a {
9895 let fq_name = candidate.fq_name();
9899 analyzer
9900 .global_usage_definition_index()
9901 .fqn(&fq_name)
9902 .into_iter()
9903 .filter(move |peer| {
9904 if *peer == candidate {
9905 return false;
9906 }
9907 #[cfg(any(test, feature = "test-support"))]
9908 note_cpp_global_field_linkage_peer_inspection_for_test();
9909 same_logical_symbol(peer, candidate)
9910 })
9911}
9912
9913#[cfg(any(test, feature = "test-support"))]
9914thread_local! {
9915 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
9916}
9917
9918#[cfg(any(test, feature = "test-support"))]
9919fn note_cpp_global_field_linkage_peer_inspection_for_test() {
9920 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
9921 count.set(count.get() + 1);
9922 });
9923}
9924
9925#[cfg(any(test, feature = "test-support"))]
9926pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
9927 body: impl FnOnce() -> T,
9928) -> (T, usize) {
9929 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
9930 count.set(0);
9931 let result = body();
9932 let observed = count.get();
9933 count.set(0);
9934 (result, observed)
9935 })
9936}
9937
9938fn cpp_global_field_declaration_linkage(
9939 analyzer: &CppGraphSource<'_>,
9940 candidate: &CodeUnit,
9941) -> Option<CppFieldLinkage> {
9942 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
9943 return Some(linkage);
9944 }
9945 let cpp = analyzer.cpp?;
9946 if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
9947 return cpp_global_field_declaration_linkage_in_tree(
9948 analyzer,
9949 candidate,
9950 prepared.source(),
9951 prepared.tree().root_node(),
9952 );
9953 }
9954 let source = analyzer.indexed_source(candidate.source())?;
9955 let mut parser = Parser::new();
9956 if parser
9957 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9958 .is_err()
9959 {
9960 return None;
9961 }
9962 let tree = parser.parse(&source, None)?;
9963 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
9964}
9965
9966fn cpp_global_field_declaration_linkage_in_tree(
9967 analyzer: &CppGraphSource<'_>,
9968 candidate: &CodeUnit,
9969 source: &str,
9970 root: Node<'_>,
9971) -> Option<CppFieldLinkage> {
9972 analyzer.ranges(candidate).iter().find_map(|range| {
9973 node_for_exact_range(root, range)
9974 .and_then(enclosing_cpp_field_declaration)
9975 .map(|declaration| cpp_field_declaration_linkage(declaration, source))
9976 })
9977}
9978
9979fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
9980 loop {
9981 if matches!(node.kind(), "declaration" | "field_declaration") {
9982 return Some(node);
9983 }
9984 node = node.parent()?;
9985 }
9986}
9987
9988#[cfg(test)]
9989mod tests {
9990 use super::*;
9991
9992 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
9993 let mut parser = Parser::new();
9994 parser
9995 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9996 .expect("C++ grammar");
9997 let tree = parser.parse(source, None).expect("C++ fixture tree");
9998 let mut stack = vec![tree.root_node()];
9999 while let Some(node) = stack.pop() {
10000 if node.kind() == "enum_specifier" {
10001 return flattened_macro_namespace_components(node, source);
10002 }
10003 let mut cursor = node.walk();
10004 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10005 stack.extend(children.into_iter().rev());
10006 }
10007 None
10008 }
10009
10010 #[test]
10011 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
10012 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
10013namespace detail
10014{
10015enum class value_t { null };
10016}
10017NLOHMANN_JSON_NAMESPACE_END
10018NLOHMANN_JSON_NAMESPACE_BEGIN
10019namespace next
10020{
10021struct next_type {};
10022}
10023NLOHMANN_JSON_NAMESPACE_END
10024"#;
10025 assert_eq!(
10026 first_enum_flattened_namespace(complete),
10027 Some(vec!["detail".to_string()])
10028 );
10029
10030 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
10031 assert_eq!(
10032 first_enum_flattened_namespace(&stale_end),
10033 Some(vec!["detail".to_string()]),
10034 "a stale end marker before the begin marker must not replace the intended namespace"
10035 );
10036
10037 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
10038namespace detail
10039{
10040enum class value_t { null };
10041}
10042struct next_type {};
10043"#;
10044 assert_eq!(first_enum_flattened_namespace(incomplete), None);
10045 }
10046}