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