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