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 = type_owner_resolution(analyzer, target)
404 .or_else(|| target_forward_owner_resolution(analyzer, target));
405 let owner_is_forward_declaration = owner_resolution
406 .as_ref()
407 .is_some_and(|owner| owner.is_forward_declaration);
408 let owner = owner_resolution.map(|owner| owner.unit);
409 let kind = if owner.as_ref().is_some_and(|owner| {
410 target.identifier() == owner.identifier()
411 || analyzer
412 .cpp
413 .and_then(|cpp| cpp.template_metadata(owner))
414 .is_some_and(|metadata| metadata.primary_name == target.identifier())
415 }) {
416 TargetKind::Constructor
417 } else if owner.is_some() {
418 TargetKind::Method
419 } else {
420 TargetKind::FreeFunction
421 };
422 let mut spec = Self::new(
423 target.clone(),
424 kind,
425 owner,
426 target.identifier().to_string(),
427 Some(cpp_callable_arity(analyzer, target)),
428 target.signature().and_then(cpp_signature_param_types),
429 );
430 spec.owner_is_forward_declaration = owner_is_forward_declaration;
431 return Some(spec);
432 }
433
434 if target.is_macro() {
435 return Some(Self::new(
436 target.clone(),
437 TargetKind::Macro,
438 None,
439 target.identifier().to_string(),
440 None,
441 None,
442 ));
443 }
444
445 None
446 }
447
448 pub fn with_visible_callable_arities<'a>(
449 &'a self,
450 analyzer: &CppGraphSource<'_>,
451 cpp: &dyn CppSource,
452 visibility: &VisibilityIndex<'_>,
453 file: &ProjectFile,
454 prepared: &PreparedSyntaxTree,
455 ) -> Cow<'a, Self> {
456 let macro_parameter_arity =
457 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
458 let activated_callable_arities =
459 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
460 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
461 return Cow::Borrowed(self);
462 }
463 let mut effective = self.clone();
464 if let Some(macro_parameter_arity) = macro_parameter_arity {
465 effective.callable_arity = Some(macro_parameter_arity);
466 }
467 effective.activated_callable_arities = activated_callable_arities;
468 Cow::Owned(effective)
469 }
470
471 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
472 let base = self.callable_arity?;
473 Some(
474 self.activated_callable_arities
475 .iter()
476 .filter(|candidate| candidate.activation_byte <= byte)
477 .fold(base, |arity, candidate| {
478 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
479 }),
480 )
481 }
482
483 pub fn new(
484 target: CodeUnit,
485 kind: TargetKind,
486 owner: Option<CodeUnit>,
487 member_name: String,
488 callable_arity: Option<CallableArity>,
489 param_types: Option<Vec<String>>,
490 ) -> Self {
491 Self {
492 target,
493 kind,
494 owner,
495 member_name,
496 callable_arity,
497 activated_callable_arities: Vec::new(),
498 param_types,
499 enum_owner_kind: EnumOwnerKind::NonEnum,
500 owner_is_forward_declaration: false,
501 }
502 }
503}
504
505fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
506 LogicalSymbolKey {
507 kind: unit.kind(),
508 fq_name: unit.fq_name(),
509 signature: unit.signature().map(str::to_string),
510 }
511}
512
513fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
514 let classify = |source: &str| {
515 let source = source.trim_start();
516 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
517 Some(EnumOwnerKind::Scoped)
518 } else if source.starts_with("enum ") {
519 Some(EnumOwnerKind::Unscoped)
520 } else {
521 None
522 }
523 };
524 owner
525 .signature()
526 .and_then(classify)
527 .or_else(|| {
528 analyzer
529 .get_source(owner, false)
530 .as_deref()
531 .and_then(classify)
532 })
533 .unwrap_or(EnumOwnerKind::NonEnum)
534}
535
536#[derive(Clone, PartialEq, Eq, Hash)]
537pub struct CppScanBinding {
538 pub unit: Option<CodeUnit>,
539 pub type_name: Option<String>,
540 pub indirection: i32,
541}
542
543impl CppScanBinding {
544 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
545 Self {
546 type_name: Some(cpp_name_for(&unit)),
547 unit: Some(unit),
548 indirection,
549 }
550 }
551
552 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
553 Self {
554 type_name: Some(type_name),
555 unit,
556 indirection,
557 }
558 }
559
560 pub fn as_arg_type(&self) -> Option<CppArgType> {
561 let name = self
562 .type_name
563 .clone()
564 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
565 Some(CppArgType {
566 name,
567 unit: self.unit.clone(),
568 indirection: self.indirection,
569 pointee_const: false,
570 })
571 }
572}
573
574type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
575type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
576pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
577pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
578type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
579pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
580type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
581
582#[derive(Clone, Default)]
583pub struct MacroEnvironment {
584 bindings: HashMap<String, MacroBinding>,
585 unknown_names: bool,
586 applied_pragma_once_files: HashSet<ProjectFile>,
587 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
588}
589
590#[derive(Default)]
591pub struct MacroEnvironmentCursor {
592 frontier: usize,
593 environment: Arc<MacroEnvironment>,
594}
595
596impl MacroEnvironment {
597 fn binding(&self, name: &str) -> Option<&MacroBinding> {
598 self.bindings.get(name)
599 }
600
601 fn may_bind(&self, name: &str) -> bool {
602 self.bindings.contains_key(name) || self.unknown_names
603 }
604
605 fn insert(&mut self, name: String, binding: MacroBinding) {
606 self.bindings.insert(name, binding);
607 }
608
609 fn remove(&mut self, name: &str) {
610 self.bindings.remove(name);
611 }
612
613 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
614 for binding in self.bindings.values_mut() {
615 *binding = MacroBinding::ambiguous(source, byte);
616 }
617 self.unknown_names = true;
618 }
619}
620
621#[derive(Clone)]
622pub enum EffectiveUsingTarget {
623 Ordinary {
624 name: String,
625 target_components: Vec<String>,
626 global: bool,
627 },
628 Namespace {
629 namespace_components: Vec<String>,
630 global: bool,
631 },
632}
633
634#[derive(Clone)]
635pub struct OrdinaryTypeImport {
636 pub target: EffectiveUsingTarget,
637 pub source: ProjectFile,
638 pub declaration_byte: usize,
639 pub scope_start: usize,
640 pub scope_end: usize,
641 pub scope_depth: usize,
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 fn resolve_type_candidates(
3571 &self,
3572 analyzer: &CppGraphSource<'_>,
3573 file: &ProjectFile,
3574 candidates: &[&CodeUnit],
3575 resolution: TypeCandidateResolution<'_>,
3576 ) -> Result<CodeUnit, TypeCandidateFailure> {
3577 match resolution {
3578 TypeCandidateResolution::Canonical => {
3579 self.canonical_type_candidate_resolution(analyzer, file, candidates)
3580 }
3581 TypeCandidateResolution::PreserveAlias => {
3582 unique_type_candidate_preserving_alias(analyzer, candidates)
3583 .ok_or(TypeCandidateFailure::Ambiguous)
3584 }
3585 TypeCandidateResolution::PreserveTarget(target) => self
3586 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
3587 .ok_or(TypeCandidateFailure::Ambiguous),
3588 }
3589 }
3590
3591 pub fn resolve_callable_value_components_lexically(
3592 &self,
3593 analyzer: &CppGraphSource<'_>,
3594 file: &ProjectFile,
3595 owner_components: &[String],
3596 member_name: &str,
3597 global: bool,
3598 lexical_scope: &[String],
3599 ) -> LexicalCallableValueResolution {
3600 if owner_components.is_empty() || member_name.is_empty() {
3601 return LexicalCallableValueResolution::Missing;
3602 }
3603 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
3604 let owner_name = qualified_owner.join("::");
3605 let type_candidates = self
3606 .type_candidates(file, &owner_name)
3607 .into_iter()
3608 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
3609 .collect::<Vec<_>>();
3610 let resolved_type = if type_candidates.is_empty() {
3611 None
3612 } else {
3613 let Some(unit) =
3614 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
3615 else {
3616 return LexicalCallableValueResolution::Ambiguous;
3617 };
3618 Some(unit)
3619 };
3620
3621 let mut qualified_callable = qualified_owner;
3622 qualified_callable.push(member_name.to_string());
3623 let callable_name = qualified_callable.join("::");
3624 let free_function = self
3625 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
3626 .into_iter()
3627 .find(|candidate| {
3628 canonical_cpp_name_matches(candidate, &callable_name)
3629 && type_owner_of(analyzer, candidate).is_none()
3630 })
3631 .cloned();
3632
3633 match (resolved_type, free_function) {
3634 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
3635 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
3636 (None, Some(function)) => {
3637 return LexicalCallableValueResolution::FreeFunction(function);
3638 }
3639 (None, None) => {}
3640 }
3641 }
3642 LexicalCallableValueResolution::Missing
3643 }
3644
3645 fn resolve_type_for_declaration(
3646 &self,
3647 visible_from: &ProjectFile,
3648 declaration: &CodeUnit,
3649 raw_name: &str,
3650 ) -> Option<CodeUnit> {
3651 let normalized = normalize_reference_name(raw_name)?;
3652 if !normalized.contains("::")
3653 && let Some(namespace) = cpp_namespace_for(declaration)
3654 {
3655 for prefix in namespace_prefixes(&namespace) {
3656 let qualified = format!("{prefix}::{normalized}");
3657 if let Some(unit) = self
3658 .type_candidates(visible_from, &qualified)
3659 .into_iter()
3660 .next()
3661 {
3662 return Some(unit.clone());
3663 }
3664 }
3665 }
3666 self.resolve_type(visible_from, raw_name)
3667 }
3668
3669 fn resolve_unique_canonical_type_for_declaration(
3670 &self,
3671 analyzer: &CppGraphSource<'_>,
3672 visible_from: &ProjectFile,
3673 declaration: &CodeUnit,
3674 raw_name: &str,
3675 ) -> Option<CodeUnit> {
3676 let mut current =
3677 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
3678 let mut seen_aliases = HashSet::default();
3679 loop {
3680 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
3681 return current.is_class().then_some(current);
3682 };
3683 if matches!(target, StructuredAliasTarget::Builtin) {
3684 return current.is_class().then_some(current);
3685 }
3686 if !seen_aliases.insert(current.clone()) {
3687 return None;
3688 }
3689 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
3690 }
3691 }
3692
3693 pub fn canonical_type_unit(
3694 &self,
3695 analyzer: &CppGraphSource<'_>,
3696 visible_from: &ProjectFile,
3697 unit: &CodeUnit,
3698 ) -> Option<CodeUnit> {
3699 self.canonical_type_resolution(analyzer, visible_from, unit)
3700 .ok()
3701 }
3702
3703 fn canonical_type_resolution(
3711 &self,
3712 analyzer: &CppGraphSource<'_>,
3713 visible_from: &ProjectFile,
3714 unit: &CodeUnit,
3715 ) -> Result<CodeUnit, TypeCandidateFailure> {
3716 let mut current = unit.clone();
3717 let mut seen_aliases = HashSet::default();
3718 loop {
3719 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
3720 return current
3721 .is_class()
3722 .then_some(current)
3723 .ok_or(TypeCandidateFailure::Unresolvable);
3724 };
3725 if matches!(target, StructuredAliasTarget::Builtin) {
3726 return current
3727 .is_class()
3728 .then_some(current)
3729 .ok_or(TypeCandidateFailure::Unresolvable);
3730 }
3731 if !seen_aliases.insert(current.clone()) {
3732 return Err(TypeCandidateFailure::Unresolvable);
3733 }
3734 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
3735 }
3736 }
3737
3738 pub fn canonical_visible_full_type_unit(
3739 &self,
3740 analyzer: &CppGraphSource<'_>,
3741 visible_from: &ProjectFile,
3742 unit: &CodeUnit,
3743 ) -> Option<CodeUnit> {
3744 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
3745 if cpp_class_declaration_strength(analyzer, &canonical)
3746 != CppClassDeclarationStrength::Forward
3747 {
3748 return Some(canonical);
3749 }
3750 let mut full = Vec::new();
3751 for candidate in self
3752 .visible_identifier_candidates(visible_from, canonical.identifier())
3753 .filter(|candidate| {
3754 candidate.is_class()
3755 && candidate.fq_name() == canonical.fq_name()
3756 && cpp_class_declaration_strength(analyzer, candidate)
3757 == CppClassDeclarationStrength::Full
3758 })
3759 {
3760 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
3761 full.push(candidate.clone());
3762 }
3763 }
3764 match full.len() {
3765 0 => Some(canonical),
3766 1 => full.pop(),
3767 _ => None,
3768 }
3769 }
3770
3771 fn resolve_structured_alias_target(
3772 &self,
3773 visible_from: &ProjectFile,
3774 declaration: &CodeUnit,
3775 target: &StructuredAliasTarget,
3776 ) -> Option<CodeUnit> {
3777 self.structured_alias_target_resolution(visible_from, declaration, target)
3778 .ok()
3779 }
3780
3781 fn structured_alias_target_resolution(
3782 &self,
3783 visible_from: &ProjectFile,
3784 declaration: &CodeUnit,
3785 target: &StructuredAliasTarget,
3786 ) -> Result<CodeUnit, TypeCandidateFailure> {
3787 let primary =
3788 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
3789 let StructuredAliasTarget::Named { arguments, .. } = target else {
3790 return Err(TypeCandidateFailure::Unresolvable);
3791 };
3792 match arguments {
3793 Some(arguments) => self
3794 .resolve_template_arguments(visible_from, primary, arguments)
3795 .map_err(|error| match error {
3796 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
3797 TypeCandidateFailure::Ambiguous
3798 }
3799 _ => TypeCandidateFailure::Unresolvable,
3800 }),
3801 None => Ok(primary),
3802 }
3803 }
3804
3805 fn resolve_structured_alias_primary(
3806 &self,
3807 visible_from: &ProjectFile,
3808 declaration: &CodeUnit,
3809 target: &StructuredAliasTarget,
3810 ) -> Option<CodeUnit> {
3811 self.structured_alias_primary_resolution(visible_from, declaration, target)
3812 .ok()
3813 }
3814
3815 fn structured_alias_primary_resolution(
3816 &self,
3817 visible_from: &ProjectFile,
3818 declaration: &CodeUnit,
3819 target: &StructuredAliasTarget,
3820 ) -> Result<CodeUnit, TypeCandidateFailure> {
3821 let StructuredAliasTarget::Named {
3822 components, global, ..
3823 } = target
3824 else {
3825 return Err(TypeCandidateFailure::Unresolvable);
3826 };
3827 let qualified = components.join("::");
3828 let candidates = if *global {
3829 self.type_candidates(visible_from, &qualified)
3830 } else {
3831 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
3832 };
3833 logical_type_candidate(candidates)
3834 }
3835
3836 pub fn structured_alias_primary_preserves_target(
3837 &self,
3838 analyzer: &CppGraphSource<'_>,
3839 visible_from: &ProjectFile,
3840 candidate: &CodeUnit,
3841 target: &CodeUnit,
3842 ) -> bool {
3843 let mut current = candidate.clone();
3844 let mut seen = HashSet::default();
3845 let mut matched_target = false;
3846 loop {
3847 if same_visible_symbol(¤t, target)
3848 || self.compatible_primary_template_redeclarations(¤t, target)
3849 {
3850 matched_target = true;
3851 }
3852 if !seen.insert(current.clone()) {
3853 return false;
3854 }
3855 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
3856 return matched_target;
3857 };
3858 if matches!(alias_target, StructuredAliasTarget::Builtin) {
3859 return matched_target;
3860 };
3861 let Some(primary) =
3862 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
3863 else {
3864 return matched_target;
3870 };
3871 current = primary;
3872 }
3873 }
3874
3875 pub fn structured_class_alias_resolves_to_target(
3876 &self,
3877 analyzer: &CppGraphSource<'_>,
3878 visible_from: &ProjectFile,
3879 alias: &CodeUnit,
3880 target: &CodeUnit,
3881 ) -> bool {
3882 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
3883 return false;
3884 };
3885 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
3886 return false;
3887 };
3888 let StructuredAliasTarget::Named {
3889 components, global, ..
3890 } = &alias_target
3891 else {
3892 return false;
3893 };
3894 let lexical_scope = canonical_cpp_scope_components(&owner);
3895 match self.resolve_type_components_lexically_for_target(
3896 analyzer,
3897 visible_from,
3898 components,
3899 *global,
3900 &lexical_scope,
3901 target,
3902 ) {
3903 LexicalTypeResolution::Resolved {
3904 unit, candidates, ..
3905 } => {
3906 same_visible_symbol(&unit, target)
3907 || self.same_template_member_identity(analyzer, &unit, target)
3908 || candidates.iter().any(|candidate| {
3909 same_visible_symbol(candidate, target)
3910 || self.same_template_member_identity(analyzer, candidate, target)
3911 })
3912 }
3913 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
3914 self.structured_alias_primary_preserves_target(
3915 analyzer,
3916 visible_from,
3917 alias,
3918 target,
3919 ) || self.flattened_macro_namespace_alias_target_matches(
3920 analyzer,
3921 visible_from,
3922 alias,
3923 &alias_target,
3924 target,
3925 )
3926 }
3927 }
3928 }
3929
3930 pub fn structured_class_alias_path_preserves_target(
3938 &self,
3939 analyzer: &CppGraphSource<'_>,
3940 visible_from: &ProjectFile,
3941 alias: &CodeUnit,
3942 target: &CodeUnit,
3943 ) -> bool {
3944 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
3945 return false;
3946 };
3947 let Some(StructuredAliasTarget::Named {
3948 components, global, ..
3949 }) = self.structured_alias_target(analyzer, alias)
3950 else {
3951 return false;
3952 };
3953 let lexical_scope = canonical_cpp_scope_components(&owner);
3954 (1..components.len()).rev().any(|component_count| {
3955 matches!(
3956 self.resolve_type_components_lexically_for_target(
3957 analyzer,
3958 visible_from,
3959 &components[..component_count],
3960 global,
3961 &lexical_scope,
3962 target,
3963 ),
3964 LexicalTypeResolution::Resolved {
3965 ref unit,
3966 ref candidates,
3967 ..
3968 } if same_visible_symbol(unit, target)
3969 || self.same_template_member_identity(analyzer, unit, target)
3970 || candidates.iter().any(|candidate| {
3971 same_visible_symbol(candidate, target)
3972 || self.same_template_member_identity(analyzer, candidate, target)
3973 })
3974 )
3975 })
3976 }
3977
3978 fn flattened_macro_namespace_alias_target_matches(
3979 &self,
3980 analyzer: &CppGraphSource<'_>,
3981 visible_from: &ProjectFile,
3982 alias: &CodeUnit,
3983 alias_target: &StructuredAliasTarget,
3984 target: &CodeUnit,
3985 ) -> bool {
3986 let StructuredAliasTarget::Named {
3987 components,
3988 global: false,
3989 arguments: None,
3990 } = alias_target
3991 else {
3992 return false;
3993 };
3994 let Some((target_name, namespace_components)) = components.split_last() else {
3995 return false;
3996 };
3997 if namespace_components.is_empty()
3998 || target_name != target.identifier()
3999 || alias.source() != target.source()
4000 || alias.source() != visible_from
4001 || !target.is_class()
4002 || declared_type_alias(analyzer, target)
4003 {
4004 return false;
4005 }
4006 if self
4007 .resolve_structured_alias_target(visible_from, alias, alias_target)
4008 .is_some()
4009 {
4010 return false;
4011 }
4012
4013 let alias_ranges = analyzer.ranges(alias);
4014 let target_ranges = analyzer.ranges(target);
4015 if alias_ranges.is_empty() || target_ranges.is_empty() {
4016 return false;
4017 }
4018 let alias_start = alias_ranges
4019 .iter()
4020 .map(|range| range.start_byte)
4021 .min()
4022 .expect("non-empty alias ranges have a minimum");
4023 let Some(prepared) = self.cpp.prepared_syntax(target.source()) else {
4024 return false;
4025 };
4026 let root = prepared.tree().root_node();
4027 let has_matching_declaration = target_ranges
4028 .iter()
4029 .filter(|range| range.end_byte <= alias_start)
4030 .filter_map(|range| node_for_exact_range(root, range))
4031 .any(|node| {
4032 flattened_macro_namespace_components(node, prepared.source())
4033 .is_some_and(|recovered| recovered == namespace_components)
4034 });
4035 if !has_matching_declaration {
4036 return false;
4037 }
4038
4039 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
4040 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
4041 guard_requirement_sets_match(&alias_guards, &target_guards)
4042 }
4043
4044 pub fn template_alias_arguments_preserve_target(
4045 &self,
4046 analyzer: &CppGraphSource<'_>,
4047 visible_from: &ProjectFile,
4048 alias: &CodeUnit,
4049 arguments: &[CppTemplateExpression],
4050 target: &CodeUnit,
4051 ) -> bool {
4052 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
4053 return false;
4054 };
4055 if metadata.alias_target.is_none()
4056 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
4057 {
4058 return false;
4059 }
4060 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
4061 }
4062
4063 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
4064 self.cpp_template_metadata
4065 .get(unit)
4066 .is_some_and(|metadata| metadata.specialization_arguments.is_empty())
4067 }
4068
4069 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
4070 self.cpp_template_metadata
4071 .get(unit)
4072 .is_some_and(|metadata| !metadata.specialization_arguments.is_empty())
4073 }
4074
4075 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
4076 same_visible_symbol(left, right)
4077 || self.compatible_primary_template_redeclarations(left, right)
4078 }
4079
4080 pub fn same_template_member_identity(
4081 &self,
4082 analyzer: &CppGraphSource<'_>,
4083 left: &CodeUnit,
4084 right: &CodeUnit,
4085 ) -> bool {
4086 if same_visible_symbol(left, right) {
4087 return true;
4088 }
4089 if left.kind() != right.kind()
4090 || left.identifier() != right.identifier()
4091 || left.signature() != right.signature()
4092 {
4093 return false;
4094 }
4095 let (Some(left_owner), Some(right_owner)) =
4096 (analyzer.parent_of(left), analyzer.parent_of(right))
4097 else {
4098 return false;
4099 };
4100 left_owner.is_class()
4101 && right_owner.is_class()
4102 && self.same_template_owner_identity(&left_owner, &right_owner)
4103 }
4104
4105 fn unique_canonical_type_candidate(
4106 &self,
4107 analyzer: &CppGraphSource<'_>,
4108 visible_from: &ProjectFile,
4109 candidates: &[&CodeUnit],
4110 ) -> Option<CodeUnit> {
4111 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
4112 .ok()
4113 }
4114
4115 fn canonical_type_candidate_resolution(
4116 &self,
4117 analyzer: &CppGraphSource<'_>,
4118 visible_from: &ProjectFile,
4119 candidates: &[&CodeUnit],
4120 ) -> Result<CodeUnit, TypeCandidateFailure> {
4121 let mut canonical = Vec::new();
4122 for candidate in candidates {
4123 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
4124 if canonical
4125 .iter()
4126 .any(|existing| same_visible_symbol(existing, &resolved))
4127 {
4128 continue;
4129 }
4130 canonical.push(resolved);
4131 if canonical.len() > 1 {
4132 return Err(TypeCandidateFailure::Ambiguous);
4133 }
4134 }
4135 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
4136 }
4137
4138 pub fn unique_type_candidate_preserving_target(
4139 &self,
4140 analyzer: &CppGraphSource<'_>,
4141 visible_from: &ProjectFile,
4142 candidates: &[&CodeUnit],
4143 target: &CodeUnit,
4144 ) -> Option<CodeUnit> {
4145 if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
4156 return Some(target.clone());
4157 }
4158 let mut resolved_candidates = Vec::new();
4159 for candidate in candidates {
4160 let resolved =
4161 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)?;
4162 if resolved_candidates
4163 .iter()
4164 .any(|existing| same_visible_symbol(existing, &resolved))
4165 {
4166 continue;
4167 }
4168 resolved_candidates.push(resolved);
4169 }
4170 match resolved_candidates.as_slice() {
4171 [] => None,
4172 [single] => Some(single.clone()),
4173 _ => self
4178 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
4179 .map(|_| target.clone()),
4180 }
4181 }
4182
4183 pub fn same_fqn_type_spelling_for_target<'b>(
4200 &self,
4201 analyzer: &CppGraphSource<'_>,
4202 visible_from: &ProjectFile,
4203 candidates: &[&'b CodeUnit],
4204 target: &CodeUnit,
4205 ) -> Option<&'b CodeUnit> {
4206 let [first, rest @ ..] = candidates else {
4207 return None;
4208 };
4209 if rest.is_empty()
4210 || !rest.iter().all(|candidate| {
4211 candidate.kind() == first.kind()
4212 && candidate.fq_name() == first.fq_name()
4213 && candidate.source() == first.source()
4214 })
4215 {
4216 return None;
4217 }
4218 candidates
4219 .iter()
4220 .copied()
4221 .find(|candidate| same_symbol(candidate, target))
4222 .or_else(|| {
4223 candidates.iter().copied().find(|candidate| {
4224 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
4225 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
4226 })
4227 })
4228 }
4229
4230 pub fn alternate_same_fqn_type_declarations(
4231 &self,
4232 analyzer: &CppGraphSource<'_>,
4233 candidates: &[&CodeUnit],
4234 target: &CodeUnit,
4235 ) -> bool {
4236 let Some(first) = candidates.first() else {
4237 return false;
4238 };
4239 let same_api = first.kind() == target.kind()
4240 && first.fq_name() == target.fq_name()
4241 && first.source() == target.source()
4242 && candidates.iter().all(|candidate| {
4243 candidate.kind() == target.kind()
4244 && candidate.fq_name() == target.fq_name()
4245 && candidate.source() == target.source()
4246 })
4247 && candidates
4248 .iter()
4249 .any(|candidate| same_symbol(candidate, target))
4250 && candidates
4251 .iter()
4252 .any(|candidate| !same_logical_symbol(candidate, target));
4253 if !same_api {
4254 return false;
4255 }
4256
4257 let requirements = candidates
4258 .iter()
4259 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4260 .collect::<Vec<_>>();
4261 requirements.len() > 1
4262 && requirements
4263 .iter()
4264 .all(|requirement| !requirement.is_empty())
4265 && requirements.iter().enumerate().all(|(index, left)| {
4266 requirements[index + 1..].iter().all(|right| {
4267 left.iter().all(|(_, left_guards)| {
4268 right.iter().all(|(_, right_guards)| {
4269 merge_preprocessor_guards(left_guards, right_guards).is_none()
4270 })
4271 })
4272 })
4273 })
4274 }
4275
4276 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
4277 let mut pending = vec![terms.to_vec()];
4278 while let Some(branch_terms) = pending.pop() {
4279 let mut normalized = Vec::new();
4280 let mut covers_branch = false;
4281 for term in branch_terms {
4282 if term.iter().any(|guard| term.contains(&guard.negated())) {
4283 continue;
4284 }
4285 if term.is_empty() {
4286 covers_branch = true;
4287 break;
4288 }
4289 if !normalized.iter().any(|existing| existing == &term) {
4290 normalized.push(term);
4291 }
4292 }
4293 if covers_branch {
4294 continue;
4295 }
4296 let Some(split_guard) = normalized
4297 .iter()
4298 .flat_map(|term| term.iter())
4299 .next()
4300 .cloned()
4301 else {
4302 return false;
4303 };
4304 let negated_guard = split_guard.negated();
4305 let mut when_defined = Vec::new();
4306 let mut when_undefined = Vec::new();
4307 for term in normalized {
4308 if term.contains(&negated_guard) {
4309 } else if term.contains(&split_guard) {
4311 let mut reduced = term.clone();
4312 reduced.remove(&split_guard);
4313 when_defined.push(reduced);
4314 } else {
4315 when_defined.push(term.clone());
4316 }
4317 if term.contains(&split_guard) {
4318 } else if term.contains(&negated_guard) {
4320 let mut reduced = term;
4321 reduced.remove(&negated_guard);
4322 when_undefined.push(reduced);
4323 } else {
4324 when_undefined.push(term);
4325 }
4326 }
4327 pending.push(when_defined);
4328 pending.push(when_undefined);
4329 }
4330 true
4331 }
4332
4333 fn declarations_share_exhaustive_conditional_family(
4342 &self,
4343 analyzer: &CppGraphSource<'_>,
4344 candidates: &[&CodeUnit],
4345 ) -> Option<(usize, usize)> {
4346 let mut family_range = None;
4347 for candidate in candidates {
4348 let prepared = self.cpp.prepared_syntax(candidate.source())?;
4349 let root = prepared.tree().root_node();
4350 let mut candidate_family = None;
4351 for range in analyzer.ranges(candidate) {
4352 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
4353 let family = preprocessor_conditional_family_for_declaration(node)?;
4354 let key = (family.start_byte(), family.end_byte());
4355 if candidate_family.is_some_and(|existing| existing != key) {
4356 return None;
4357 }
4358 candidate_family = Some(key);
4359 }
4360 let candidate_family = candidate_family?;
4361 if family_range.is_some_and(|existing| existing != candidate_family) {
4362 return None;
4363 }
4364 family_range = Some(candidate_family);
4365 }
4366 family_range
4367 }
4368
4369 pub fn complementary_same_fqn_type_declarations(
4370 &self,
4371 analyzer: &CppGraphSource<'_>,
4372 candidates: &[&CodeUnit],
4373 target: &CodeUnit,
4374 ) -> bool {
4375 if candidates.len() < 2
4376 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
4377 || self
4378 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
4379 .is_none()
4380 {
4381 return false;
4382 }
4383 Self::preprocessor_guard_terms_cover_all_paths(
4384 &self.declaration_family_guard_terms(analyzer, candidates),
4385 )
4386 }
4387
4388 fn declaration_family_guard_terms(
4389 &self,
4390 analyzer: &CppGraphSource<'_>,
4391 candidates: &[&CodeUnit],
4392 ) -> Vec<HashSet<PreprocessorGuard>> {
4393 candidates
4394 .iter()
4395 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
4396 .map(|(_, guards)| guards)
4397 .collect()
4398 }
4399
4400 fn exhaustive_guard_family_activation(
4416 &self,
4417 analyzer: &CppGraphSource<'_>,
4418 prepared: &PreparedSyntaxTree,
4419 candidate: &CodeUnit,
4420 reference: &CallableReferenceContext<'_>,
4421 ) -> Option<usize> {
4422 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
4425 return None;
4426 }
4427 let family = self
4428 .visible_identifier_candidates(candidate.source(), candidate.identifier())
4429 .filter(|peer| {
4430 peer.kind() == candidate.kind()
4431 && peer.fq_name() == candidate.fq_name()
4432 && peer.source() == candidate.source()
4433 })
4434 .collect::<Vec<_>>();
4435 let (_, family_end) =
4436 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
4437 if !Self::preprocessor_guard_terms_cover_all_paths(
4438 &self.declaration_family_guard_terms(analyzer, &family),
4439 ) {
4440 return None;
4441 }
4442 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
4446 .iter()
4447 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
4448 {
4449 return None;
4450 }
4451 (first_declaration_byte(analyzer, candidate)?
4452 == family
4453 .iter()
4454 .filter_map(|peer| first_declaration_byte(analyzer, peer))
4455 .min()?)
4456 .then_some(family_end)
4457 }
4458
4459 fn type_candidate_preserving_target(
4460 &self,
4461 analyzer: &CppGraphSource<'_>,
4462 visible_from: &ProjectFile,
4463 candidate: &CodeUnit,
4464 target: &CodeUnit,
4465 ) -> Option<CodeUnit> {
4466 let mut current = candidate.clone();
4467 let mut matched_target = same_visible_symbol(¤t, target)
4468 || self.compatible_primary_template_redeclarations(¤t, target);
4469 let mut seen = HashSet::default();
4470 loop {
4471 if !seen.insert(current.clone()) {
4472 return None;
4473 }
4474 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
4475 return matched_target
4476 .then(|| target.clone())
4477 .or_else(|| current.is_class().then_some(current));
4478 };
4479 if self.flattened_macro_namespace_alias_target_matches(
4480 analyzer,
4481 visible_from,
4482 ¤t,
4483 &alias_target,
4484 target,
4485 ) {
4486 return Some(target.clone());
4487 }
4488 if matches!(alias_target, StructuredAliasTarget::Builtin) {
4489 return matched_target
4490 .then(|| target.clone())
4491 .or_else(|| current.is_class().then_some(current));
4492 }
4493 if !self.cpp_template_metadata.contains_key(¤t)
4501 && let Some(primary) =
4502 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
4503 && (same_visible_symbol(&primary, target)
4504 || self.compatible_primary_template_redeclarations(&primary, target))
4505 {
4506 return Some(target.clone());
4507 }
4508 if same_visible_symbol(¤t, target) {
4509 return Some(target.clone());
4510 }
4511 if self.cpp_template_metadata.contains_key(¤t) {
4512 return None;
4513 }
4514 let Some(next) =
4515 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
4516 else {
4517 return matched_target.then(|| target.clone());
4518 };
4519 current = next;
4520 matched_target |= same_visible_symbol(¤t, target)
4521 || self.compatible_primary_template_redeclarations(¤t, target);
4522 }
4523 }
4524
4525 fn compatible_primary_template_redeclarations(
4526 &self,
4527 left: &CodeUnit,
4528 right: &CodeUnit,
4529 ) -> bool {
4530 let (Some(left_metadata), Some(right_metadata)) = (
4531 self.cpp_template_metadata.get(left),
4532 self.cpp_template_metadata.get(right),
4533 ) else {
4534 return false;
4535 };
4536 left_metadata.primary_fq_name == right_metadata.primary_fq_name
4537 && left_metadata.specialization_arguments.is_empty()
4538 && right_metadata.specialization_arguments.is_empty()
4539 && cpp_reconcile_primary_template_parameters(
4540 &[(left, left_metadata), (right, right_metadata)],
4541 right,
4542 )
4543 .is_some()
4544 }
4545
4546 fn alias_candidate_may_preserve_target(
4547 &self,
4548 analyzer: &CppGraphSource<'_>,
4549 visible_from: &ProjectFile,
4550 candidate: &CodeUnit,
4551 target: &CodeUnit,
4552 ) -> bool {
4553 let mut current = candidate.clone();
4554 let mut seen = HashSet::default();
4555 loop {
4556 if same_visible_symbol(¤t, target)
4557 || self.compatible_primary_template_redeclarations(¤t, target)
4558 {
4559 return true;
4560 }
4561 if self.cpp_template_metadata.contains_key(¤t) {
4562 return true;
4563 }
4564 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
4565 return false;
4566 };
4567 let StructuredAliasTarget::Named {
4568 components,
4569 global,
4570 arguments,
4571 } = alias_target
4572 else {
4573 return false;
4574 };
4575 if arguments.is_some() || !seen.insert(current.clone()) {
4576 return true;
4577 }
4578 let qualified = components.join("::");
4579 let next = if global {
4580 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
4581 } else {
4582 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
4583 };
4584 let Some(next) = next else {
4585 return true;
4586 };
4587 current = next;
4588 }
4589 }
4590
4591 fn type_candidates_for_declaration<'b>(
4595 &'b self,
4596 visible_from: &ProjectFile,
4597 declaration: &CodeUnit,
4598 raw_name: &str,
4599 ) -> Vec<&'b CodeUnit> {
4600 let Some(normalized) = normalize_reference_name(raw_name) else {
4601 return Vec::new();
4602 };
4603 if let Some(namespace) = cpp_namespace_for(declaration) {
4604 for prefix in namespace_prefixes(&namespace) {
4605 let qualified = format!("{prefix}::{normalized}");
4606 let candidates = self.type_candidates(visible_from, &qualified);
4607 if !candidates.is_empty() {
4608 return candidates;
4609 }
4610 }
4611 }
4612 self.type_candidates(visible_from, &normalized)
4613 }
4614
4615 fn resolve_unique_type_for_declaration(
4616 &self,
4617 visible_from: &ProjectFile,
4618 declaration: &CodeUnit,
4619 raw_name: &str,
4620 ) -> Option<CodeUnit> {
4621 unique_logical_type_candidate(self.type_candidates_for_declaration(
4622 visible_from,
4623 declaration,
4624 raw_name,
4625 ))
4626 }
4627
4628 pub fn resolves_to_type(
4629 &self,
4630 analyzer: &CppGraphSource<'_>,
4631 file: &ProjectFile,
4632 raw_name: &str,
4633 target: &CodeUnit,
4634 ) -> bool {
4635 let Some(normalized) = normalize_reference_name(raw_name) else {
4636 return false;
4637 };
4638 let candidates = self.type_candidates(file, &normalized);
4639 if candidates.is_empty() {
4640 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
4641 }
4642 let Some(resolved) =
4643 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
4644 else {
4645 return false;
4646 };
4647 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
4648 }
4649
4650 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
4651 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
4652 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
4653 match resolved.kind() {
4654 CodeUnitType::Class => Some(resolved),
4655 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
4656 _ => None,
4657 }
4658 }
4659
4660 pub fn canonical_type_for_reference(
4661 &self,
4662 file: &ProjectFile,
4663 raw_name: &str,
4664 ) -> Option<CodeUnit> {
4665 let resolved = self.resolve_type(file, raw_name)?;
4666 self.alias_target(&resolved).or(Some(resolved))
4667 }
4668
4669 pub fn parser_alias_resolves_to_type(
4670 &self,
4671 analyzer: &CppGraphSource<'_>,
4672 file: &ProjectFile,
4673 raw_name: &str,
4674 target: &CodeUnit,
4675 ) -> bool {
4676 let Some(alias_name) = normalize_reference_name(raw_name) else {
4677 return false;
4678 };
4679 let Some(cpp) = analyzer.cpp else {
4680 return false;
4681 };
4682 let matches_file = |source_file: &ProjectFile| {
4683 self.file_alias_matches(cpp, source_file, &alias_name, target)
4684 };
4685 self.visible_source_files_by_root.get(file).map_or_else(
4686 || matches_file(file),
4687 |files| files.iter().any(matches_file),
4688 )
4689 }
4690
4691 fn file_alias_matches(
4692 &self,
4693 cpp: &dyn CppSource,
4694 file: &ProjectFile,
4695 alias_name: &str,
4696 target: &CodeUnit,
4697 ) -> bool {
4698 let cell = {
4699 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
4700 Arc::clone(
4701 cells
4702 .entry(file.clone())
4703 .or_insert_with(|| Arc::new(OnceLock::new())),
4704 )
4705 };
4706 cell.get_or_init(|| {
4707 #[cfg(any(test, feature = "test-support"))]
4708 {
4709 *self
4710 .alias_source_parse_counts
4711 .lock()
4712 .expect("alias source parse count lock")
4713 .entry(file.clone())
4714 .or_default() += 1;
4715 }
4716 aliases_from_prepared_source(cpp, file).into_boxed_slice()
4717 })
4718 .iter()
4719 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
4720 }
4721
4722 #[cfg(any(test, feature = "test-support"))]
4723 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
4724 self.visible_source_files_by_root
4725 .get(file)
4726 .cloned()
4727 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
4728 }
4729
4730 #[cfg(any(test, feature = "test-support"))]
4731 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
4732 self.alias_source_parse_counts
4733 .lock()
4734 .expect("alias source parse count lock")
4735 .get(file)
4736 .copied()
4737 .unwrap_or(0)
4738 }
4739
4740 pub fn resolve_named(
4741 &self,
4742 file: &ProjectFile,
4743 raw_name: &str,
4744 kind: TargetKind,
4745 ) -> Option<CodeUnit> {
4746 let normalized = normalize_reference_name(raw_name)?;
4747 self.named_candidates_for_normalized(file, &normalized, kind)
4748 .into_iter()
4749 .next()
4750 .cloned()
4751 }
4752
4753 pub fn contains_named_symbol(
4754 &self,
4755 file: &ProjectFile,
4756 raw_name: &str,
4757 kind: TargetKind,
4758 target: &CodeUnit,
4759 ) -> bool {
4760 let Some(normalized) = normalize_reference_name(raw_name) else {
4761 return false;
4762 };
4763 self.named_candidates_for_normalized(file, &normalized, kind)
4764 .into_iter()
4765 .any(|unit| {
4766 matches_kind_for_lookup(unit, kind)
4767 && reference_matches_unit(&normalized, unit)
4768 && same_visible_symbol(unit, target)
4769 })
4770 }
4771
4772 pub fn named_candidates(
4773 &self,
4774 file: &ProjectFile,
4775 raw_name: &str,
4776 kind: TargetKind,
4777 ) -> Vec<CodeUnit> {
4778 let Some(normalized) = normalize_reference_name(raw_name) else {
4779 return Vec::new();
4780 };
4781 self.named_candidates_for_normalized(file, &normalized, kind)
4782 .into_iter()
4783 .cloned()
4784 .collect()
4785 }
4786
4787 pub fn resolve_known_non_target(
4788 &self,
4789 file: &ProjectFile,
4790 raw_name: &str,
4791 kind: TargetKind,
4792 target: &CodeUnit,
4793 ) -> bool {
4794 let Some(normalized) = normalize_reference_name(raw_name) else {
4795 return false;
4796 };
4797 normalized.contains("::")
4798 && self
4799 .named_candidates_for_normalized(file, &normalized, kind)
4800 .into_iter()
4801 .any(|unit| {
4802 matches_kind_for_lookup(unit, kind)
4803 && reference_matches_unit(&normalized, unit)
4804 && !same_visible_symbol(unit, target)
4805 })
4806 }
4807
4808 pub fn resolve_call_return_binding(
4809 &self,
4810 analyzer: &CppGraphSource<'_>,
4811 file: &ProjectFile,
4812 raw_name: &str,
4813 arity: usize,
4814 lexical_namespace: Option<&str>,
4815 direct_type: Option<&CodeUnit>,
4816 ) -> Option<CppScanBinding> {
4817 let normalized = normalize_reference_name(raw_name)?;
4818 let mut candidates = Vec::new();
4819 for function in
4820 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
4821 {
4822 if cpp_callable_arity(analyzer, function).accepts(arity)
4823 && !direct_type.is_some_and(|direct_type| {
4824 self.callable_is_constructor_declaration(analyzer, function)
4825 && type_owner_of(analyzer, function)
4826 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
4827 })
4828 {
4829 candidates.push(function.clone());
4830 }
4831 }
4832 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
4833 unanimous_return_binding(analyzer, self, file, &candidates)
4834 }
4835
4836 pub fn resolve_call_return_binding_without_arity(
4837 &self,
4838 analyzer: &CppGraphSource<'_>,
4839 file: &ProjectFile,
4840 raw_name: &str,
4841 lexical_namespace: Option<&str>,
4842 direct_type: Option<&CodeUnit>,
4843 ) -> (bool, Option<CppScanBinding>) {
4844 let Some(normalized) = normalize_reference_name(raw_name) else {
4845 return (false, None);
4846 };
4847 let mut candidates = self
4848 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
4849 .into_iter()
4850 .filter(|function| {
4851 function.is_function()
4852 && !direct_type.is_some_and(|direct_type| {
4853 self.callable_is_constructor_declaration(analyzer, function)
4854 && type_owner_of(analyzer, function)
4855 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
4856 })
4857 })
4858 .cloned()
4859 .collect::<Vec<_>>();
4860 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
4861 let has_candidates = !candidates.is_empty();
4862 (
4863 has_candidates,
4864 unanimous_return_binding(analyzer, self, file, &candidates),
4865 )
4866 }
4867
4868 pub fn visible_identifier_candidates<'b>(
4869 &'b self,
4870 file: &ProjectFile,
4871 identifier: &str,
4872 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
4873 self.visible_by_identifier
4874 .get(file)
4875 .and_then(|by_name| by_name.get(identifier))
4876 .into_iter()
4877 .flatten()
4878 }
4879
4880 pub fn visible_type_reference_component_names_for_target(
4887 &self,
4888 analyzer: &CppGraphSource<'_>,
4889 file: &ProjectFile,
4890 target: &CodeUnit,
4891 ) -> HashSet<String> {
4892 let mut names = HashSet::from_iter([target.identifier().to_string()]);
4893 if let Some(metadata) = self.cpp_template_metadata.get(target) {
4894 names.insert(metadata.primary_name.clone());
4895 }
4896
4897 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
4898 for (identifier, candidates) in by_identifier {
4899 if candidates.iter().any(|candidate| {
4900 (candidate.is_class()
4901 && (same_visible_symbol(candidate, target)
4902 || self.compatible_primary_template_redeclarations(candidate, target)))
4903 || (declared_type_alias(analyzer, candidate)
4904 && self.alias_candidate_may_preserve_target(
4905 analyzer, file, candidate, target,
4906 ))
4907 }) {
4908 names.insert(identifier.clone());
4909 }
4910 }
4911 }
4912
4913 names.extend(self.visible_parser_alias_names_for_target(file, target));
4914
4915 names
4916 }
4917
4918 pub fn indexed_structural_class_scope(
4919 &self,
4920 file: &ProjectFile,
4921 class: Node<'_>,
4922 source: &str,
4923 ) -> Option<Vec<String>> {
4924 let key = (file.clone(), class.start_byte(), class.end_byte());
4925 if let Some(cached) = self
4926 .indexed_structural_class_scopes
4927 .lock()
4928 .expect("C++ indexed structural-class scope cache poisoned")
4929 .get(&key)
4930 .cloned()
4931 {
4932 return cached;
4933 }
4934 let resolved = (|| {
4935 let name = class.child_by_field_name("name")?;
4936 let identifier = if name.kind() == "template_type" {
4937 node_text(name.child_by_field_name("name")?, source).to_string()
4938 } else {
4939 let mut components = Vec::new();
4940 append_cpp_name_components(name, source, &mut components)?;
4941 components.last()?.clone()
4942 };
4943 let visible = self
4944 .visible_identifier_candidates(file, &identifier)
4945 .cloned()
4946 .collect::<Vec<_>>();
4947 let mut visible = visible;
4948 for candidate in
4949 self.visible_by_file
4950 .get(file)
4951 .into_iter()
4952 .flatten()
4953 .filter(|candidate| {
4954 self.cpp_template_metadata
4955 .get(candidate)
4956 .is_some_and(|metadata| metadata.primary_name == identifier)
4957 })
4958 {
4959 if !visible
4960 .iter()
4961 .any(|existing| same_logical_symbol(existing, candidate))
4962 {
4963 visible.push(candidate.clone());
4964 }
4965 }
4966 let cpp_source = self.cpp_source();
4969 let candidates = visible
4970 .iter()
4971 .filter(|candidate| {
4972 candidate.source() == file
4973 && candidate.is_class()
4974 && !declared_type_alias(&cpp_source, candidate)
4975 && self.cpp.ranges(candidate).iter().any(|range| {
4976 range.start_byte <= class.start_byte()
4977 && class.end_byte() <= range.end_byte
4978 })
4979 })
4980 .collect::<Vec<_>>();
4981 let owner = if name.kind() == "template_type" {
4982 let expected = normalize_cpp_whitespace(node_text(name, source));
4983 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
4984 let exact = candidates
4985 .iter()
4986 .copied()
4987 .filter(|candidate| {
4988 candidate
4989 .fq()
4990 .segments()
4991 .iter()
4992 .rev()
4993 .find_map(|&segment| {
4994 let (text, kind) = interner.resolve(segment);
4995 matches!(
4996 kind,
4997 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
4998 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
4999 )
5000 .then_some(text)
5001 })
5002 .is_some_and(|text| text == expected)
5003 })
5004 .collect::<Vec<_>>();
5005 unique_logical_type_candidate(exact)
5006 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
5007 } else {
5008 unique_logical_type_candidate(candidates)?
5009 };
5010 Some(canonical_cpp_scope_components(&owner))
5011 })();
5012 self.indexed_structural_class_scopes
5013 .lock()
5014 .expect("C++ indexed structural-class scope cache poisoned")
5015 .insert(key, resolved.clone());
5016 resolved
5017 }
5018
5019 pub fn indexed_enclosing_owner_scope(
5020 &self,
5021 analyzer: &CppGraphSource<'_>,
5022 file: &ProjectFile,
5023 node: Node<'_>,
5024 ) -> Option<Vec<String>> {
5025 let anchor = std::iter::successors(Some(node), |current| current.parent())
5026 .find(|current| {
5027 matches!(
5028 current.kind(),
5029 "function_definition"
5030 | "class_specifier"
5031 | "struct_specifier"
5032 | "union_specifier"
5033 )
5034 })
5035 .unwrap_or(node);
5036 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
5037 if let Some(cached) = self
5038 .indexed_enclosing_owner_scopes
5039 .lock()
5040 .expect("C++ indexed enclosing-owner scope cache poisoned")
5041 .get(&key)
5042 .cloned()
5043 {
5044 return cached;
5045 }
5046 let resolved = (|| {
5047 let range = Range {
5048 start_byte: node.start_byte(),
5049 end_byte: node.end_byte(),
5050 start_line: node.start_position().row,
5051 end_line: node.end_position().row,
5052 };
5053 let start = analyzer.enclosing_code_unit(file, &range)?;
5054 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
5055 start,
5056 |unit| self.cached_precise_parent_of(analyzer, unit),
5057 )
5058 .find(|unit| {
5059 unit.is_class()
5060 && !analyzer
5061 .type_alias_provider()
5062 .is_some_and(|provider| provider.is_type_alias(unit))
5063 })?;
5064 Some(canonical_cpp_scope_components(&owner))
5065 })();
5066 self.indexed_enclosing_owner_scopes
5067 .lock()
5068 .expect("C++ indexed enclosing-owner scope cache poisoned")
5069 .insert(key, resolved.clone());
5070 resolved
5071 }
5072
5073 fn cached_precise_parent_of(
5074 &self,
5075 analyzer: &CppGraphSource<'_>,
5076 code_unit: &CodeUnit,
5077 ) -> Option<CodeUnit> {
5078 if let Some(cached) = self
5079 .precise_parent_cache
5080 .lock()
5081 .expect("C++ precise-parent cache poisoned")
5082 .get(code_unit)
5083 .cloned()
5084 {
5085 return cached;
5086 }
5087 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
5088 self.precise_parent_cache
5089 .lock()
5090 .expect("C++ precise-parent cache poisoned")
5091 .insert(code_unit.clone(), resolved.clone());
5092 resolved
5093 }
5094
5095 pub fn callable_is_constructor_declaration(
5096 &self,
5097 analyzer: &CppGraphSource<'_>,
5098 candidate: &CodeUnit,
5099 ) -> bool {
5100 if !candidate.is_function() {
5101 return false;
5102 }
5103 let Some(prepared) = self.cpp.prepared_syntax(candidate.source()) else {
5104 return false;
5105 };
5106 let root = prepared.tree().root_node();
5107 let candidate_ranges = analyzer.ranges(candidate);
5108 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
5109 let mut current = root
5110 .descendant_for_byte_range(range.start_byte, range.end_byte)
5111 .and_then(|node| node.parent());
5112 while let Some(node) = current {
5113 if matches!(
5114 node.kind(),
5115 "class_specifier" | "struct_specifier" | "union_specifier"
5116 ) {
5117 return node
5118 .child_by_field_name("name")
5119 .map(|name| terminal_name(node_text(name, prepared.source())))
5120 .is_some_and(|name| name == candidate.identifier());
5121 }
5122 current = node.parent();
5123 }
5124 false
5125 });
5126 if enclosed_by_matching_type {
5127 return true;
5128 }
5129 let indexed_containment = analyzer
5130 .declarations(candidate.source())
5131 .into_iter()
5132 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
5133 .any(|owner| {
5134 analyzer.ranges(&owner).iter().any(|owner_range| {
5135 candidate_ranges.iter().any(|candidate_range| {
5136 owner_range.start_byte <= candidate_range.start_byte
5137 && candidate_range.end_byte <= owner_range.end_byte
5138 })
5139 })
5140 });
5141 if indexed_containment {
5142 return true;
5143 }
5144 let metadata = analyzer.signature_metadata(candidate);
5145 !metadata.is_empty()
5146 && metadata
5147 .iter()
5148 .all(|signature| signature.return_type_text().is_none())
5149 }
5150
5151 pub fn type_name_candidates<'b>(
5152 &'b self,
5153 file: &ProjectFile,
5154 normalized: &str,
5155 ) -> Vec<&'b CodeUnit> {
5156 self.candidate_units(file, normalized, TargetKind::Type)
5157 }
5158
5159 pub fn visible_members_for_owner_name<'b>(
5160 &'b self,
5161 file: &ProjectFile,
5162 owner: &CodeUnit,
5163 name: &str,
5164 ) -> Vec<&'b CodeUnit> {
5165 self.visible_identifier_candidates(file, name)
5166 .filter(|unit| {
5167 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
5171 .is_some_and(|parent| parent == owner.fq_name())
5172 })
5173 .collect()
5174 }
5175
5176 pub fn visible_member_for_owner_name(
5177 &self,
5178 file: &ProjectFile,
5179 owner: &CodeUnit,
5180 name: &str,
5181 ) -> VisibleMemberResolution {
5182 let candidates = self.visible_members_for_owner_name(file, owner, name);
5183 let mut callables = Vec::new();
5184 let mut non_callable = None;
5185 for candidate in candidates {
5186 if candidate.is_function() {
5187 callables.push(candidate.clone());
5188 } else if non_callable.is_none() {
5189 non_callable = Some(candidate.clone());
5190 }
5191 }
5192 match (callables.is_empty(), non_callable) {
5193 (false, None) => VisibleMemberResolution::Callable(callables),
5194 (true, Some(_)) => VisibleMemberResolution::NonCallable,
5195 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
5196 (true, None) => VisibleMemberResolution::Missing,
5197 }
5198 }
5199
5200 fn field_declared_type_fact(
5201 &self,
5202 analyzer: &CppGraphSource<'_>,
5203 field: &CodeUnit,
5204 ) -> Option<DeclaredFieldTypeFact> {
5205 if let Some(cached) = self
5206 .field_type_facts
5207 .lock()
5208 .expect("C++ field type fact cache poisoned")
5209 .get(field)
5210 .cloned()
5211 {
5212 return cached;
5213 }
5214 let decoded = decode_field_declared_type_fact(analyzer, field);
5215 self.field_type_facts
5216 .lock()
5217 .expect("C++ field type fact cache poisoned")
5218 .insert(field.clone(), decoded.clone());
5219 decoded
5220 }
5221
5222 fn structured_alias_target(
5223 &self,
5224 analyzer: &CppGraphSource<'_>,
5225 unit: &CodeUnit,
5226 ) -> Option<StructuredAliasTarget> {
5227 if let Some(cached) = self
5228 .structured_alias_targets
5229 .lock()
5230 .expect("C++ structured alias target cache poisoned")
5231 .get(unit)
5232 .cloned()
5233 {
5234 return cached;
5235 }
5236 let decoded = decode_structured_alias_target(analyzer, unit);
5237 self.structured_alias_targets
5238 .lock()
5239 .expect("C++ structured alias target cache poisoned")
5240 .insert(unit.clone(), decoded.clone());
5241 decoded
5242 }
5243
5244 pub fn type_candidates<'b>(
5245 &'b self,
5246 file: &ProjectFile,
5247 normalized: &str,
5248 ) -> Vec<&'b CodeUnit> {
5249 let mut candidates = self
5250 .candidate_units(file, normalized, TargetKind::Type)
5251 .into_iter()
5252 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
5253 .collect::<Vec<_>>();
5254 dedup_unit_refs(&mut candidates);
5255 candidates
5256 }
5257
5258 pub fn named_candidates_for_normalized<'b>(
5259 &'b self,
5260 file: &ProjectFile,
5261 normalized: &str,
5262 kind: TargetKind,
5263 ) -> Vec<&'b CodeUnit> {
5264 let mut candidates = self
5265 .candidate_units(file, normalized, kind)
5266 .into_iter()
5267 .filter(|unit| {
5268 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
5269 })
5270 .collect::<Vec<_>>();
5271 dedup_unit_refs(&mut candidates);
5272 candidates
5273 }
5274
5275 pub fn candidate_units<'b>(
5276 &'b self,
5277 file: &ProjectFile,
5278 normalized: &str,
5279 kind: TargetKind,
5280 ) -> Vec<&'b CodeUnit> {
5281 if normalized.contains("::") {
5282 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5291 brokk_bifrost_core::analyzer::Language::Cpp,
5292 normalized,
5293 )
5294 .pop() else {
5295 return Vec::new();
5296 };
5297 let fqns = cpp_reference_fqn_candidates(normalized, kind);
5298 return self
5299 .visible_identifier_candidates(file, &identifier)
5300 .filter(|unit| {
5301 #[cfg(any(test, feature = "test-support"))]
5302 self.qualified_candidate_inspections
5303 .fetch_add(1, Ordering::Relaxed);
5304 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
5305 || canonical_cpp_name_matches(unit, normalized)
5306 })
5307 .collect();
5308 }
5309 self.visible_identifier_candidates(file, normalized)
5310 .collect()
5311 }
5312
5313 #[cfg(any(test, feature = "test-support"))]
5314 pub fn reset_qualified_candidate_inspections(&self) {
5315 self.qualified_candidate_inspections
5316 .store(0, Ordering::Relaxed);
5317 }
5318
5319 #[cfg(any(test, feature = "test-support"))]
5320 pub fn qualified_candidate_inspections(&self) -> usize {
5321 self.qualified_candidate_inspections.load(Ordering::Relaxed)
5322 }
5323
5324 #[cfg(any(test, feature = "test-support"))]
5325 pub fn reset_target_preserving_type_resolution_count(&self) {
5326 self.target_preserving_type_resolution_count
5327 .store(0, Ordering::Relaxed);
5328 }
5329
5330 #[cfg(any(test, feature = "test-support"))]
5331 pub fn target_preserving_type_resolution_count(&self) -> usize {
5332 self.target_preserving_type_resolution_count
5333 .load(Ordering::Relaxed)
5334 }
5335
5336 #[cfg(any(test, feature = "test-support"))]
5337 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
5338 self.visible_parser_alias_name_set_build_count
5339 .load(Ordering::Relaxed)
5340 }
5341
5342 #[cfg(any(test, feature = "test-support"))]
5343 pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
5344 self.visible_parser_alias_target_names_build_count
5345 .load(Ordering::Relaxed)
5346 }
5347}
5348
5349#[derive(Default)]
5350struct IncludeGraph {
5351 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
5352}
5353
5354impl IncludeGraph {
5355 fn extend_with<F>(
5356 &mut self,
5357 root: &ProjectFile,
5358 cancellation: Option<&CancellationToken>,
5359 targets_for: &mut F,
5360 ) where
5361 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5362 {
5363 let mut stack = vec![root.clone()];
5364 while let Some(file) = stack.pop() {
5365 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5366 break;
5367 }
5368 if self.targets_by_file.contains_key(&file) {
5369 continue;
5370 }
5371 let targets = targets_for(&file);
5372 stack.extend(targets.iter().cloned());
5373 self.targets_by_file.insert(file, targets);
5374 }
5375 }
5376
5377 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
5378 self.targets_by_file.keys()
5379 }
5380
5381 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
5382 self.targets_by_file
5383 .get(file)
5384 .map(Vec::as_slice)
5385 .unwrap_or_default()
5386 }
5387}
5388
5389pub struct VisibilityData {
5390 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
5391 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
5392}
5393
5394pub fn build_visibility_data<F, D>(
5395 roots: &HashSet<ProjectFile>,
5396 cancellation: Option<&CancellationToken>,
5397 mut targets_for: F,
5398 mut declarations_for: D,
5399) -> VisibilityData
5400where
5401 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
5402 D: FnMut(&ProjectFile) -> BTreeSet<CodeUnit>,
5403{
5404 let mut include_graph = IncludeGraph::default();
5405 for file in roots {
5406 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5407 break;
5408 }
5409 include_graph.extend_with(file, cancellation, &mut targets_for);
5410 }
5411 let declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
5412 .files()
5413 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
5414 .map(|file| (file.clone(), declarations_for(file)))
5415 .collect();
5416 let mut visible_by_file = HashMap::default();
5417 let mut visible_source_files_by_root = HashMap::default();
5418 for file in roots {
5419 if cancellation.is_some_and(CancellationToken::is_cancelled) {
5420 break;
5421 }
5422 let mut visited = HashSet::default();
5423 let mut visible = HashSet::default();
5424 collect_visible_declarations(
5425 &include_graph,
5426 &declarations_by_file,
5427 file,
5428 &mut visited,
5429 &mut visible,
5430 cancellation,
5431 );
5432 visible_by_file.insert(file.clone(), visible);
5433 visible_source_files_by_root.insert(file.clone(), visited);
5434 }
5435 VisibilityData {
5436 visible_by_file,
5437 visible_source_files_by_root,
5438 }
5439}
5440
5441fn extend_with_out_of_line_owner_bindings(
5460 cpp: &dyn CppSource,
5461 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
5462) {
5463 for (file, visible) in visible_by_file.iter_mut() {
5464 let mut unseen_owners: HashSet<String> = visible
5468 .iter()
5469 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
5470 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
5471 .collect();
5472 if unseen_owners.is_empty() {
5473 continue;
5474 }
5475 for unit in visible.iter().filter(|unit| unit.is_class()) {
5476 unseen_owners.remove(&unit.fq_name());
5477 }
5478 let admitted = unseen_owners
5479 .iter()
5480 .flat_map(|owner| cpp.definitions(owner))
5481 .filter(CodeUnit::is_class)
5482 .collect::<Vec<_>>();
5483 visible.extend(admitted);
5484 }
5485}
5486
5487pub enum VisibleMemberResolution {
5488 Callable(Vec<CodeUnit>),
5489 NonCallable,
5490 AmbiguousKind,
5491 Missing,
5492}
5493
5494#[derive(Clone)]
5495pub enum EnclosingMemberOwnerResolution {
5496 Owner(CodeUnit),
5497 Ambiguous,
5498 Missing,
5499}
5500
5501pub fn resolve_declaring_member_owner(
5502 analyzer: &CppGraphSource<'_>,
5503 visibility: &VisibilityIndex<'_>,
5504 file: &ProjectFile,
5505 receiver_owner: &CodeUnit,
5506 member_name: &str,
5507) -> EnclosingMemberOwnerResolution {
5508 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
5509 return EnclosingMemberOwnerResolution::Missing;
5510 };
5511 let Some(receiver_owner) =
5512 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
5513 else {
5514 return EnclosingMemberOwnerResolution::Ambiguous;
5515 };
5516 let resolve_level = |frontier: &[CodeUnit]| {
5517 let mut member_owners = Vec::new();
5518 for raw_owner in frontier {
5519 let Some(owner) =
5520 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
5521 else {
5522 return EnclosingMemberOwnerResolution::Ambiguous;
5523 };
5524 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
5525 let Some(member_owner) = type_owner_of(analyzer, member) else {
5526 return EnclosingMemberOwnerResolution::Ambiguous;
5527 };
5528 if !member_owners
5529 .iter()
5530 .any(|existing| same_visible_symbol(existing, &member_owner))
5531 {
5532 member_owners.push(member_owner);
5533 }
5534 }
5535 }
5536 match member_owners.len() {
5537 0 => EnclosingMemberOwnerResolution::Missing,
5538 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
5539 _ => EnclosingMemberOwnerResolution::Ambiguous,
5540 }
5541 };
5542 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
5546 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
5547 return direct;
5548 }
5549 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
5550 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
5551 let mut path_matches = Vec::new();
5552 while let Some(raw_owner) = stack.pop() {
5553 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
5554 else {
5555 return EnclosingMemberOwnerResolution::Ambiguous;
5556 };
5557 let propagated = propagated_counts.entry(owner.clone()).or_default();
5561 if *propagated == 2 {
5562 continue;
5563 }
5564 *propagated += 1;
5565 match resolve_level(std::slice::from_ref(&owner)) {
5566 EnclosingMemberOwnerResolution::Owner(owner) => {
5567 path_matches.push(owner);
5568 if path_matches.len() == 2 {
5569 return EnclosingMemberOwnerResolution::Ambiguous;
5570 }
5571 }
5572 EnclosingMemberOwnerResolution::Ambiguous => {
5573 return EnclosingMemberOwnerResolution::Ambiguous;
5574 }
5575 EnclosingMemberOwnerResolution::Missing => {
5576 stack.extend(hierarchy.get_direct_ancestors(&owner));
5577 }
5578 }
5579 }
5580 match path_matches.len() {
5581 0 => EnclosingMemberOwnerResolution::Missing,
5582 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
5583 _ => unreachable!("base-path matches are capped at one before returning"),
5584 }
5585}
5586
5587pub fn lexical_component_tiers<'a>(
5588 components: &'a [String],
5589 global: bool,
5590 lexical_scope: &'a [String],
5591) -> impl Iterator<Item = Vec<String>> + 'a {
5592 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
5593 (0..=first_prefix_len).rev().map(move |prefix_len| {
5594 let mut qualified = Vec::with_capacity(prefix_len + components.len());
5595 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
5596 qualified.extend_from_slice(components);
5597 qualified
5598 })
5599}
5600
5601pub fn build_visible_identifier_index(
5602 analyzer: &CppGraphSource<'_>,
5603 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
5604 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
5605 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
5606) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
5607 let mut out = HashMap::default();
5608 for (file, visible) in visible_by_file {
5609 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
5610 for unit in visible {
5611 if unit.is_field()
5612 && !visible_source_files_by_root
5613 .get(file)
5614 .is_some_and(|sources| sources.contains(unit.source()))
5615 && cpp_global_field_has_internal_linkage_cached(
5616 analyzer,
5617 global_field_internal_linkage,
5618 unit,
5619 )
5620 {
5621 continue;
5622 }
5623 by_identifier
5624 .entry(unit.identifier().to_string())
5625 .or_default()
5626 .push(unit.clone());
5627 }
5628 for units in by_identifier.values_mut() {
5629 sort_lookup_units(units);
5630 units.dedup();
5631 }
5632 out.insert(file.clone(), by_identifier);
5633 }
5634 out
5635}
5636
5637fn sort_lookup_units(units: &mut [CodeUnit]) {
5638 units.sort_by(|left, right| {
5639 left.fq_name()
5640 .cmp(&right.fq_name())
5641 .then_with(|| left.signature().cmp(&right.signature()))
5642 .then_with(|| left.source().cmp(right.source()))
5643 });
5644}
5645
5646fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
5647 let mut deduped = Vec::with_capacity(units.len());
5648 for unit in units.drain(..) {
5649 if !deduped.contains(&unit) {
5650 deduped.push(unit);
5651 }
5652 }
5653 *units = deduped;
5654}
5655
5656pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
5657 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5661 brokk_bifrost_core::analyzer::Language::Cpp,
5662 reference,
5663 );
5664 if parts.is_empty() {
5665 return Vec::new();
5666 }
5667
5668 let mut candidates = Vec::new();
5669 for package_len in 0..parts.len() {
5670 let package = parts[..package_len].join("::");
5671 let rest = &parts[package_len..];
5672 if rest.is_empty() {
5673 continue;
5674 }
5675 match kind {
5676 TargetKind::Type | TargetKind::Constructor => {
5677 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
5678 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
5679 }
5680 TargetKind::FreeFunction
5681 | TargetKind::Method
5682 | TargetKind::GlobalField
5683 | TargetKind::MemberField
5684 | TargetKind::Macro => {
5685 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
5686 if rest.len() > 1 {
5687 let owner = rest[..rest.len() - 1].join("$");
5688 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
5689 push_cpp_fqn_candidate(&mut candidates, &package, &short);
5690 }
5691 }
5692 }
5693 }
5694 candidates
5695}
5696
5697fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
5698 let fqn = if package.is_empty() {
5699 short.to_string()
5700 } else {
5701 format!("{package}.{short}")
5702 };
5703 if !out.contains(&fqn) {
5704 out.push(fqn);
5705 }
5706}
5707
5708pub fn infer_cpp_initializer_type(
5709 analyzer: &CppGraphSource<'_>,
5710 visibility: &VisibilityIndex<'_>,
5711 file: &ProjectFile,
5712 source: &str,
5713 node: Node<'_>,
5714) -> Option<CodeUnit> {
5715 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
5716 .and_then(|binding| binding.unit)
5717}
5718
5719pub fn infer_cpp_initializer_binding(
5720 analyzer: &CppGraphSource<'_>,
5721 visibility: &VisibilityIndex<'_>,
5722 file: &ProjectFile,
5723 source: &str,
5724 node: Node<'_>,
5725 receiver_resolver: Option<&ReceiverResolver<'_>>,
5726) -> Option<CppScanBinding> {
5727 match node.kind() {
5728 "new_expression" => {
5729 let text = normalize_cpp_whitespace(node_text(node, source));
5730 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
5731 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
5732 let name = normalize_cpp_type_name(type_text);
5733 Some(CppScanBinding::from_type_name(
5734 name.clone(),
5735 visibility.resolve_type(file, &name),
5736 1,
5737 ))
5738 }
5739 "call_expression" => node.child_by_field_name("function").and_then(|function| {
5740 let function_text = node_text(function, source);
5741 let direct_type_binding = visibility
5742 .resolve_type(file, function_text)
5743 .map(|unit| CppScanBinding::from_unit(unit, 0));
5744 if function.kind() == "template_function" && direct_type_binding.is_some() {
5745 let lexical_namespace = enclosing_namespace_context(node, source);
5746 let arity = visibility.call_arity_evidence(file, node, source).exact();
5747 if let Some(arity) = arity
5748 && let Some(binding) = visibility.resolve_call_return_binding(
5749 analyzer,
5750 file,
5751 function_text,
5752 arity,
5753 lexical_namespace.as_deref(),
5754 direct_type_binding
5755 .as_ref()
5756 .and_then(|binding| binding.unit.as_ref()),
5757 )
5758 {
5759 return Some(binding);
5760 }
5761 let (has_callable, callable_binding) = visibility
5762 .resolve_call_return_binding_without_arity(
5763 analyzer,
5764 file,
5765 function_text,
5766 lexical_namespace.as_deref(),
5767 direct_type_binding
5768 .as_ref()
5769 .and_then(|binding| binding.unit.as_ref()),
5770 );
5771 if let Some(binding) = callable_binding {
5772 return Some(binding);
5773 }
5774 if has_callable {
5775 return None;
5776 }
5777 return direct_type_binding;
5778 }
5779 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
5780 let direct_type_binding_for_call = direct_type_binding.clone();
5781 resolve_static_method_call_return_binding(
5782 analyzer, visibility, file, source, function, arity,
5783 )
5784 .or(direct_type_binding)
5785 .or_else(|| {
5786 visibility.resolve_call_return_binding(
5787 analyzer,
5788 file,
5789 function_text,
5790 arity,
5791 enclosing_namespace_context(node, source).as_deref(),
5792 direct_type_binding_for_call
5793 .as_ref()
5794 .and_then(|binding| binding.unit.as_ref()),
5795 )
5796 })
5797 .or_else(|| {
5798 resolve_field_method_call_return_binding(
5799 analyzer,
5800 visibility,
5801 file,
5802 source,
5803 function,
5804 arity,
5805 receiver_resolver,
5806 )
5807 })
5808 }),
5809 _ => None,
5810 }
5811}
5812
5813fn resolve_static_method_call_return_binding(
5814 analyzer: &CppGraphSource<'_>,
5815 visibility: &VisibilityIndex<'_>,
5816 file: &ProjectFile,
5817 source: &str,
5818 function: Node<'_>,
5819 arity: usize,
5820) -> Option<CppScanBinding> {
5821 if function.kind() != "qualified_identifier" {
5822 return None;
5823 }
5824 let qualified = normalize_cpp_reference_text(node_text(function, source));
5825 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5832 brokk_bifrost_core::analyzer::Language::Cpp,
5833 &qualified,
5834 );
5835 let (owner_text, member_name) = match parts.split_last() {
5836 Some((member, owner_parts)) if !owner_parts.is_empty() => {
5837 (owner_parts.join("::"), member.clone())
5838 }
5839 _ => {
5840 let scope = function.child_by_field_name("scope")?;
5841 let name = function.child_by_field_name("name")?;
5842 (
5843 node_text(scope, source).to_string(),
5844 node_text(name, source).to_string(),
5845 )
5846 }
5847 };
5848 let owner = visibility.resolve_type(file, &owner_text)?;
5849 let candidates = visibility
5850 .visible_members_for_owner_name(file, &owner, &member_name)
5851 .into_iter()
5852 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
5853 .cloned()
5854 .collect::<Vec<_>>();
5855 unanimous_return_binding(analyzer, visibility, file, &candidates)
5856}
5857
5858fn resolve_field_method_call_return_binding(
5859 analyzer: &CppGraphSource<'_>,
5860 visibility: &VisibilityIndex<'_>,
5861 file: &ProjectFile,
5862 source: &str,
5863 function: Node<'_>,
5864 arity: usize,
5865 receiver_resolver: Option<&ReceiverResolver<'_>>,
5866) -> Option<CppScanBinding> {
5867 if function.kind() != "field_expression" {
5868 return None;
5869 }
5870 let receiver_resolver = receiver_resolver?;
5871 let field = function.child_by_field_name("field")?;
5872 let member_name = node_text(field, source);
5873 let receiver = function
5874 .child_by_field_name("argument")
5875 .or_else(|| function.named_child(0))?;
5876 let owners = receiver_resolver(receiver, source);
5877 let mut candidates = Vec::new();
5878 for owner in owners {
5879 candidates.extend(
5880 visibility
5881 .visible_members_for_owner_name(file, &owner, member_name)
5882 .into_iter()
5883 .filter(|unit| {
5884 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
5885 })
5886 .cloned(),
5887 );
5888 }
5889 unanimous_return_binding(analyzer, visibility, file, &candidates)
5890}
5891
5892fn unanimous_return_binding(
5893 analyzer: &CppGraphSource<'_>,
5894 visibility: &VisibilityIndex<'_>,
5895 file: &ProjectFile,
5896 candidates: &[CodeUnit],
5897) -> Option<CppScanBinding> {
5898 let mut resolved_return: Option<CppScanBinding> = None;
5899 for function in candidates {
5900 let metadata = analyzer.signature_metadata(function);
5901 let return_types = if metadata.is_empty() {
5902 vec![cpp_function_return_type_text(analyzer, function)?]
5903 } else {
5904 metadata
5905 .iter()
5906 .map(|metadata| metadata.return_type_text().map(str::to_string))
5907 .collect::<Option<Vec<_>>>()?
5908 };
5909 for return_text in return_types {
5910 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
5911 let name = normalize_cpp_type_name(&return_text);
5912 let binding = CppScanBinding::from_type_name(
5913 name.clone(),
5914 visibility
5915 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
5916 indirection,
5917 );
5918 if let Some(existing) = resolved_return.as_ref()
5919 && (existing.indirection != binding.indirection
5920 || match (&existing.unit, &binding.unit) {
5921 (Some(left), Some(right)) => !same_visible_symbol(left, right),
5922 (None, None) => existing.type_name != binding.type_name,
5923 (Some(_), None) | (None, Some(_)) => true,
5924 })
5925 {
5926 return None;
5927 }
5928 resolved_return = Some(binding);
5929 }
5930 }
5931 resolved_return
5932}
5933
5934fn aliases_from_prepared_source(cpp: &dyn CppSource, file: &ProjectFile) -> Vec<CppAlias> {
5935 let Some(prepared) = cpp.prepared_syntax(file) else {
5936 return Vec::new();
5937 };
5938 let mut aliases = Vec::new();
5939 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
5940 aliases
5941}
5942
5943fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
5944 let mut stack = vec![root];
5945 while let Some(node) = stack.pop() {
5946 match node.kind() {
5947 "alias_declaration" if alias_has_visible_file_scope(node) => {
5948 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
5949 out.push(alias);
5950 }
5951 }
5952 "type_definition" if alias_has_visible_file_scope(node) => {
5953 collect_typedef_aliases(node, source, out)
5954 }
5955 _ => {}
5956 }
5957
5958 for index in (0..node.named_child_count()).rev() {
5959 if let Some(child) = node.named_child(index) {
5960 stack.push(child);
5961 }
5962 }
5963 }
5964}
5965
5966fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
5967 let mut current = node.parent();
5968 while let Some(parent) = current {
5969 match parent.kind() {
5970 "translation_unit"
5971 | "namespace_definition"
5972 | "declaration_list"
5973 | "linkage_specification" => current = parent.parent(),
5974 "template_declaration" => current = parent.parent(),
5975 _ => return false,
5976 }
5977 }
5978 true
5979}
5980
5981fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
5982 let name = node
5983 .child_by_field_name("name")
5984 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
5985 let target = node
5986 .child_by_field_name("type")
5987 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
5988 Some(CppAlias {
5989 name,
5990 target,
5991 namespace: enclosing_namespace_context(node, source),
5992 })
5993}
5994
5995fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
5996 let Some(type_node) = node.child_by_field_name("type") else {
5997 return;
5998 };
5999 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
6000 return;
6001 };
6002
6003 let mut cursor = node.walk();
6004 for child in node.named_children(&mut cursor) {
6005 if same_node(child, type_node) {
6006 continue;
6007 }
6008 if let Some(name) = extract_typedef_declarator_name(child, source) {
6009 out.push(CppAlias {
6010 name,
6011 target: target.clone(),
6012 namespace: enclosing_namespace_context(node, source),
6013 });
6014 }
6015 }
6016}
6017
6018fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
6019 match node.kind() {
6020 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
6021 normalize_reference_name(node_text(node, source))
6022 }
6023 _ => node
6024 .child_by_field_name("declarator")
6025 .or_else(|| node.child_by_field_name("name"))
6026 .or_else(|| last_named_child(node))
6027 .and_then(|child| extract_typedef_declarator_name(child, source)),
6028 }
6029}
6030
6031fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
6032 let count = node.named_child_count();
6033 if count == 0 {
6034 None
6035 } else {
6036 node.named_child(count - 1)
6037 }
6038}
6039
6040pub fn collect_include_closure(
6041 analyzer: &CppGraphSource<'_>,
6042 include_targets: &IncludeTargetIndex,
6043 file: &ProjectFile,
6044 out: &mut HashSet<ProjectFile>,
6045 cancellation: Option<&CancellationToken>,
6046) {
6047 let mut stack = vec![file.clone()];
6048 while let Some(file) = stack.pop() {
6049 if cancellation.is_some_and(CancellationToken::is_cancelled) {
6050 break;
6051 }
6052 if !out.insert(file.clone()) {
6053 continue;
6054 }
6055 let imports = analyzer.import_statements(&file);
6056 for include in cpp_include_paths(&imports) {
6057 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
6058 stack.push(target);
6059 }
6060 }
6061 }
6062}
6063
6064fn collect_visible_declarations(
6065 include_graph: &IncludeGraph,
6066 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
6067 file: &ProjectFile,
6068 visited: &mut HashSet<ProjectFile>,
6069 out: &mut HashSet<CodeUnit>,
6070 cancellation: Option<&CancellationToken>,
6071) {
6072 let mut stack = vec![file.clone()];
6073 while let Some(file) = stack.pop() {
6074 if cancellation.is_some_and(CancellationToken::is_cancelled) {
6075 break;
6076 }
6077 if !visited.insert(file.clone()) {
6078 continue;
6079 }
6080 if let Some(declarations) = declarations_by_file.get(&file) {
6081 out.extend(declarations.iter().cloned());
6082 }
6083 stack.extend(include_graph.targets(&file).iter().cloned());
6084 }
6085}
6086
6087pub fn signature_arity(signature: Option<&str>) -> usize {
6088 let Some(signature) = signature else {
6089 return 0;
6090 };
6091 let inner = signature
6092 .find('(')
6093 .and_then(|open| {
6094 signature[open + 1..]
6095 .find(')')
6096 .map(|close| &signature[open + 1..open + 1 + close])
6097 })
6098 .unwrap_or(signature)
6099 .trim();
6100 if inner.is_empty() || inner == "void" {
6101 return 0;
6102 }
6103 cpp_split_top_level_commas(inner).count()
6104}
6105
6106fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
6107 let source = format!("void __bifrost_macro_parameters({replacement});");
6108 let mut parser = Parser::new();
6109 parser
6110 .set_language(&tree_sitter_cpp::LANGUAGE.into())
6111 .ok()?;
6112 let tree = parser.parse(&source, None)?;
6113 let root = tree.root_node();
6114 if root.has_error() {
6115 return None;
6116 }
6117 let declaration = root.named_child(0)?;
6118 let declarator = declaration.child_by_field_name("declarator")?;
6119 let parameters = declarator.child_by_field_name("parameters")?;
6120 let mut required = 0;
6121 let mut total = 0;
6122 let mut repeated = false;
6123 let mut cursor = parameters.walk();
6124 for parameter in parameters.children(&mut cursor) {
6125 match parameter.kind() {
6126 "parameter_declaration" => {
6127 if parameter.child_by_field_name("declarator").is_none()
6128 && parameter
6129 .child_by_field_name("type")
6130 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
6131 {
6132 continue;
6133 }
6134 required += 1;
6135 total += 1;
6136 }
6137 "optional_parameter_declaration" => total += 1,
6138 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
6139 repeated = true;
6140 }
6141 _ => {}
6142 }
6143 }
6144 Some(CallableArity::new(required, total, repeated))
6145}
6146
6147pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
6148 analyzer
6149 .signature_metadata(unit)
6150 .into_iter()
6151 .find_map(|metadata| metadata.callable_arity())
6152 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
6153}
6154
6155fn merge_compatible_callable_arities(
6156 left: CallableArity,
6157 right: CallableArity,
6158) -> Option<CallableArity> {
6159 let total = left.total();
6160 let left_repeated = left.accepts(total.saturating_add(1));
6161 let right_repeated = right.accepts(right.total().saturating_add(1));
6162 if total != right.total() || left_repeated != right_repeated {
6163 return None;
6164 }
6165 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
6166 Some(CallableArity::new(required, total, left_repeated))
6167}
6168
6169fn find_include_activation(
6170 cpp: &dyn CppSource,
6171 file: &ProjectFile,
6172 prepared: &PreparedSyntaxTree,
6173 donor_source: &ProjectFile,
6174) -> Option<usize> {
6175 let include_targets = cpp.include_target_index();
6176 let mut direct_includes = Vec::new();
6177 let mut nodes = vec![prepared.tree().root_node()];
6178 let reference = CallableReferenceContext {
6181 file,
6182 position: None,
6183 };
6184 while let Some(node) = nodes.pop() {
6185 if node.kind() == "preproc_include" {
6186 if callable_preprocessor_context_is_visible_for_reference(
6187 node,
6188 prepared.source(),
6189 &reference,
6190 ) {
6191 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6192 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6193 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
6194 file,
6195 &include,
6196 include_targets,
6197 )) {
6198 direct_includes.push((node.end_byte(), target));
6199 }
6200 }
6201 }
6202 continue;
6203 }
6204 for index in (0..node.named_child_count()).rev() {
6205 if let Some(child) = node.named_child(index) {
6206 nodes.push(child);
6207 }
6208 }
6209 }
6210 direct_includes.sort_by_key(|(activation, _)| *activation);
6211 let mut known_missing = HashSet::default();
6212 direct_includes
6213 .into_iter()
6214 .find(|(_, direct)| {
6215 unconditional_include_reaches(
6216 cpp,
6217 include_targets,
6218 direct,
6219 donor_source,
6220 file,
6221 &mut known_missing,
6222 )
6223 })
6224 .map(|(activation, _)| activation)
6225}
6226
6227fn find_conditional_include_projections(
6228 cpp: &dyn CppSource,
6229 file: &ProjectFile,
6230 prepared: &PreparedSyntaxTree,
6231 donor_source: &ProjectFile,
6232) -> Vec<ConditionalIncludeProjection> {
6233 let include_targets = cpp.include_target_index();
6234 let mut projections = Vec::new();
6235 let mut nodes = vec![prepared.tree().root_node()];
6236 while let Some(node) = nodes.pop() {
6237 if node.kind() == "preproc_include" {
6238 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
6239 else {
6240 continue;
6241 };
6242 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6243 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6244 let Some(target) = unique_include_target(resolve_include_targets_with_index(
6245 file,
6246 &include,
6247 include_targets,
6248 )) else {
6249 continue;
6250 };
6251 let paths = conditional_include_requirement_paths(
6252 cpp,
6253 &target,
6254 donor_source,
6255 required_guards.clone(),
6256 );
6257 for required_guards in paths {
6258 if !projections
6259 .iter()
6260 .any(|projection: &ConditionalIncludeProjection| {
6261 projection.activation_byte == node.end_byte()
6262 && projection.required_guards == required_guards
6263 })
6264 {
6265 projections.push(ConditionalIncludeProjection {
6266 activation_byte: node.end_byte(),
6267 required_guards,
6268 });
6269 }
6270 }
6271 }
6272 continue;
6273 }
6274 for index in (0..node.named_child_count()).rev() {
6275 if let Some(child) = node.named_child(index) {
6276 nodes.push(child);
6277 }
6278 }
6279 }
6280 projections.sort_by_key(|projection| projection.activation_byte);
6281 projections
6282}
6283
6284fn conditional_include_requirement_paths(
6285 cpp: &dyn CppSource,
6286 first: &ProjectFile,
6287 donor_source: &ProjectFile,
6288 required_guards: HashSet<PreprocessorGuard>,
6289) -> Vec<HashSet<PreprocessorGuard>> {
6290 let include_targets = cpp.include_target_index();
6291 let mut paths = Vec::new();
6292 let mut stack = vec![(
6293 first.clone(),
6294 required_guards,
6295 HashSet::from_iter([first.clone()]),
6296 )];
6297 while let Some((file, required_guards, visited)) = stack.pop() {
6298 if file == *donor_source {
6299 if !paths.contains(&required_guards) {
6300 paths.push(required_guards);
6301 }
6302 continue;
6303 }
6304 let Some(prepared) = cpp.prepared_syntax(&file) else {
6305 continue;
6306 };
6307 let mut nodes = vec![prepared.tree().root_node()];
6308 while let Some(node) = nodes.pop() {
6309 if node.kind() == "preproc_include" {
6310 let Some(include_guards) = preprocessor_guard_environment(node, prepared.source())
6311 else {
6312 continue;
6313 };
6314 let Some(path_guards) =
6315 merge_preprocessor_guards(&required_guards, &include_guards)
6316 else {
6317 continue;
6318 };
6319 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6320 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6321 let Some(target) = unique_include_target(resolve_include_targets_with_index(
6322 &file,
6323 &include,
6324 include_targets,
6325 )) else {
6326 continue;
6327 };
6328 if visited.contains(&target) {
6329 continue;
6330 }
6331 let mut next_visited = visited.clone();
6332 next_visited.insert(target.clone());
6333 stack.push((target, path_guards.clone(), next_visited));
6334 }
6335 continue;
6336 }
6337 for index in (0..node.named_child_count()).rev() {
6338 if let Some(child) = node.named_child(index) {
6339 nodes.push(child);
6340 }
6341 }
6342 }
6343 }
6344 paths
6345}
6346
6347fn unconditional_include_reaches(
6348 cpp: &dyn CppSource,
6349 include_targets: &IncludeTargetIndex,
6350 first: &ProjectFile,
6351 donor_source: &ProjectFile,
6352 reference_file: &ProjectFile,
6353 known_missing: &mut HashSet<ProjectFile>,
6354) -> bool {
6355 if first == donor_source {
6356 return true;
6357 }
6358 if known_missing.contains(first) {
6359 return false;
6360 }
6361 let reference_is_c = reference_file
6362 .rel_path()
6363 .extension()
6364 .and_then(|extension| extension.to_str())
6365 == Some("c");
6366 if let Some(reaches) =
6367 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
6368 {
6369 return reaches;
6370 }
6371 let mut visited = HashSet::default();
6372 let mut files = vec![first.clone()];
6373 let reference = CallableReferenceContext {
6376 file: reference_file,
6377 position: None,
6378 };
6379 while let Some(file) = files.pop() {
6380 if file == *donor_source {
6381 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
6382 return true;
6383 }
6384 if known_missing.contains(&file) || !visited.insert(file.clone()) {
6385 continue;
6386 }
6387 let Some(prepared) = cpp.prepared_syntax(&file) else {
6388 continue;
6389 };
6390 let mut nodes = vec![prepared.tree().root_node()];
6391 while let Some(node) = nodes.pop() {
6392 if node.kind() == "preproc_include" {
6393 if callable_preprocessor_context_is_visible_for_reference(
6394 node,
6395 prepared.source(),
6396 &reference,
6397 ) {
6398 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
6399 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
6400 if let Some(target) = unique_include_target(
6401 resolve_include_targets_with_index(&file, &include, include_targets),
6402 ) {
6403 files.push(target);
6404 }
6405 }
6406 }
6407 continue;
6408 }
6409 for index in (0..node.named_child_count()).rev() {
6410 if let Some(child) = node.named_child(index) {
6411 nodes.push(child);
6412 }
6413 }
6414 }
6415 }
6416 known_missing.extend(visited);
6417 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
6418 false
6419}
6420
6421fn declaration_guard_requirements(
6422 analyzer: &CppGraphSource<'_>,
6423 cpp: &dyn CppSource,
6424 candidate: &CodeUnit,
6425) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
6426 let Some(prepared) = cpp.prepared_syntax(candidate.source()) else {
6427 return Vec::new();
6428 };
6429 let root = prepared.tree().root_node();
6430 analyzer
6431 .ranges(candidate)
6432 .into_iter()
6433 .filter_map(|range| {
6434 root.descendant_for_byte_range(range.start_byte, range.end_byte)
6435 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
6436 .map(|required| (range.start_byte, required))
6440 })
6441 .collect()
6442}
6443
6444fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
6445 analyzer
6446 .ranges(candidate)
6447 .into_iter()
6448 .map(|range| range.start_byte)
6449 .min()
6450}
6451
6452fn guard_requirements_hold_at_reference(
6453 required: &HashSet<PreprocessorGuard>,
6454 reference: Option<&HashSet<PreprocessorGuard>>,
6455) -> bool {
6456 reference.is_some_and(|active| required.is_subset(active))
6457}
6458
6459fn guards_compatible_at_reference(
6464 declaration: &HashSet<PreprocessorGuard>,
6465 reference: Option<&HashSet<PreprocessorGuard>>,
6466) -> bool {
6467 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
6468}
6469
6470pub fn preprocessor_conditional_family_range(
6479 root: Node<'_>,
6480 start_byte: usize,
6481 end_byte: usize,
6482) -> Option<(usize, usize)> {
6483 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
6484 let mut ancestor = Some(node);
6485 while let Some(current) = ancestor {
6486 if is_preprocessor_conditional(current) {
6487 let family = preprocessor_conditional_family_root(current);
6488 return Some((family.start_byte(), family.end_byte()));
6489 }
6490 ancestor = current.parent();
6491 }
6492 None
6493}
6494
6495fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
6496 let mut ancestor = node.parent();
6497 while let Some(current) = ancestor {
6498 if is_preprocessor_conditional(current) {
6499 let family = preprocessor_conditional_family_root(current);
6500 if preprocessor_conditional_family_has_terminal_else(family) {
6501 return Some(family);
6502 }
6503 }
6504 ancestor = current.parent();
6505 }
6506 None
6507}
6508
6509fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
6510 while let Some(parent) = conditional.parent() {
6511 let is_alternative = parent
6512 .child_by_field_name("alternative")
6513 .is_some_and(|alternative| {
6514 alternative.start_byte() == conditional.start_byte()
6515 && alternative.end_byte() == conditional.end_byte()
6516 });
6517 if !is_alternative {
6518 break;
6519 }
6520 conditional = parent;
6521 }
6522 conditional
6523}
6524
6525fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
6526 loop {
6527 let Some(alternative) = conditional.child_by_field_name("alternative") else {
6528 return false;
6529 };
6530 match alternative.kind() {
6531 "preproc_else" => return true,
6532 "preproc_elif" => conditional = alternative,
6533 _ => return false,
6534 }
6535 }
6536}
6537
6538pub fn preprocessor_guard_environment(
6539 node: Node<'_>,
6540 source: &str,
6541) -> Option<HashSet<PreprocessorGuard>> {
6542 let mut guards = HashSet::default();
6543 let mut ancestor = node.parent();
6544 while let Some(conditional) = ancestor {
6545 if matches!(
6546 conditional.kind(),
6547 "preproc_if" | "preproc_ifdef" | "preproc_elif"
6548 ) && !is_file_covering_include_guard(conditional, source)
6549 {
6550 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
6551 match guard {
6552 PreprocessorGuard::Constant(true) => {
6553 ancestor = conditional.parent();
6554 continue;
6555 }
6556 PreprocessorGuard::Constant(false) => return None,
6557 _ => {}
6558 }
6559 if guards.contains(&guard.negated()) {
6560 return None;
6561 }
6562 guards.insert(guard);
6563 }
6564 ancestor = conditional.parent();
6565 }
6566 Some(guards)
6567}
6568
6569fn preprocessor_guard_for_descendant(
6570 conditional: Node<'_>,
6571 descendant: Node<'_>,
6572 source: &str,
6573) -> Option<PreprocessorGuard> {
6574 let mut guard = simple_preprocessor_guard(conditional, source)?;
6575 if conditional
6576 .child_by_field_name("alternative")
6577 .is_some_and(|alternative| {
6578 alternative.start_byte() <= descendant.start_byte()
6579 && descendant.end_byte() <= alternative.end_byte()
6580 })
6581 {
6582 let alternative = conditional.child_by_field_name("alternative")?;
6583 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
6587 return None;
6588 }
6589 guard = guard.negated();
6590 }
6591 Some(guard)
6592}
6593
6594pub fn merge_preprocessor_guards(
6595 left: &HashSet<PreprocessorGuard>,
6596 right: &HashSet<PreprocessorGuard>,
6597) -> Option<HashSet<PreprocessorGuard>> {
6598 let mut merged = left.clone();
6599 for guard in right {
6600 if merged.contains(&guard.negated()) {
6601 return None;
6602 }
6603 merged.insert(guard.clone());
6604 }
6605 Some(merged)
6606}
6607
6608fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
6609 if conditional.kind() == "preproc_ifdef" {
6610 let name = conditional.child_by_field_name("name")?;
6611 let name = node_text(name, source).to_string();
6612 return match conditional.child(0)?.kind() {
6613 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
6614 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
6615 _ => None,
6616 };
6617 }
6618 let condition = conditional.child_by_field_name("condition")?;
6619 simple_preprocessor_expression_guard(condition, source).or_else(|| {
6620 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
6621 node_text(condition, source),
6622 )))
6623 })
6624}
6625
6626fn simple_preprocessor_expression_guard(
6627 expression: Node<'_>,
6628 source: &str,
6629) -> Option<PreprocessorGuard> {
6630 match expression.kind() {
6631 "number_literal" => match node_text(expression, source).trim() {
6632 "0" => Some(PreprocessorGuard::Constant(false)),
6633 "1" => Some(PreprocessorGuard::Constant(true)),
6634 _ => None,
6635 },
6636 "preproc_defined" => {
6637 let identifier = (0..expression.named_child_count())
6638 .filter_map(|index| expression.named_child(index))
6639 .find(|child| child.kind() == "identifier")?;
6640 Some(PreprocessorGuard::Defined(
6641 node_text(identifier, source).to_string(),
6642 ))
6643 }
6644 "unary_expression"
6645 if expression
6646 .child_by_field_name("operator")
6647 .is_some_and(|operator| operator.kind() == "!") =>
6648 {
6649 simple_preprocessor_expression_guard(
6650 expression.child_by_field_name("argument")?,
6651 source,
6652 )
6653 .map(|guard| guard.negated())
6654 }
6655 "parenthesized_expression" => (0..expression.named_child_count())
6656 .filter_map(|index| expression.named_child(index))
6657 .next()
6658 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
6659 _ => None,
6660 }
6661}
6662
6663fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
6664 if targets.len() == 1 {
6665 targets.pop()
6666 } else {
6667 None
6668 }
6669}
6670
6671fn nameable_callable_declaration_nodes<'tree>(
6680 analyzer: &CppGraphSource<'_>,
6681 prepared: &'tree PreparedSyntaxTree,
6682 candidate: &CodeUnit,
6683) -> Vec<Node<'tree>> {
6684 let root = prepared.tree().root_node();
6685 analyzer
6686 .ranges(candidate)
6687 .into_iter()
6688 .filter_map(|range| {
6689 let mut declaration =
6690 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
6691 while !matches!(
6692 declaration.kind(),
6693 "declaration" | "field_declaration" | "function_definition"
6694 ) {
6695 declaration = declaration.parent()?;
6696 }
6697 let mut ancestor = declaration.parent();
6698 while let Some(node) = ancestor {
6699 if node.kind() == "function_definition"
6700 && is_recovered_declaration_scope_container(node, prepared.source())
6701 {
6702 ancestor = node.parent();
6703 continue;
6704 }
6705 if node.kind() == "compound_statement"
6706 && node.parent().is_some_and(|parent| {
6707 is_recovered_declaration_scope_container(parent, prepared.source())
6708 })
6709 {
6710 ancestor = node.parent().and_then(|parent| parent.parent());
6711 continue;
6712 }
6713 if matches!(
6714 node.kind(),
6715 "compound_statement" | "function_definition" | "lambda_expression"
6716 ) {
6717 return None;
6718 }
6719 ancestor = node.parent();
6720 }
6721 Some(declaration)
6722 })
6723 .collect()
6724}
6725
6726fn callable_declaration_activation_in_file(
6727 analyzer: &CppGraphSource<'_>,
6728 prepared: &PreparedSyntaxTree,
6729 candidate: &CodeUnit,
6730 reference: &CallableReferenceContext<'_>,
6731) -> Option<usize> {
6732 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
6733 .into_iter()
6734 .filter(|declaration| {
6735 callable_preprocessor_context_is_visible_for_reference(
6736 *declaration,
6737 prepared.source(),
6738 reference,
6739 )
6740 })
6741 .map(callable_declaration_activation_byte)
6742 .min()
6743}
6744
6745fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
6750 if declaration.kind() != "function_definition" {
6751 return declaration.end_byte();
6752 }
6753 declaration
6754 .child_by_field_name("declarator")
6755 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
6756}
6757
6758struct CallableReferenceContext<'a> {
6764 file: &'a ProjectFile,
6765 position: Option<CallableReferencePosition<'a>>,
6766}
6767
6768struct CallableReferencePosition<'a> {
6772 prepared: &'a PreparedSyntaxTree,
6773 byte: usize,
6774 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
6775}
6776
6777impl CallableReferenceContext<'_> {
6778 fn is_c(&self) -> bool {
6779 self.file
6780 .rel_path()
6781 .extension()
6782 .and_then(|extension| extension.to_str())
6783 == Some("c")
6784 }
6785
6786 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
6787 let position = self.position.as_ref()?;
6788 position
6789 .guards
6790 .get_or_init(|| {
6791 position
6792 .prepared
6793 .tree()
6794 .root_node()
6795 .descendant_for_byte_range(position.byte, position.byte)
6796 .and_then(|node| {
6797 preprocessor_guard_environment(node, position.prepared.source())
6798 })
6799 })
6800 .as_ref()
6801 }
6802}
6803
6804fn callable_preprocessor_context_is_visible_for_reference(
6805 node: Node<'_>,
6806 source: &str,
6807 reference: &CallableReferenceContext<'_>,
6808) -> bool {
6809 let reference_is_c = reference.is_c();
6810 let mut ancestor = node.parent();
6811 while let Some(conditional) = ancestor {
6812 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
6813 && !is_file_covering_include_guard(conditional, source)
6814 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
6815 {
6816 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
6817 return false;
6818 };
6819 match guard {
6820 PreprocessorGuard::Constant(true) => {}
6821 PreprocessorGuard::Constant(false) => return false,
6822 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
6823 if reference_is_c {
6824 return false;
6825 }
6826 }
6827 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
6828 if !reference_is_c {
6829 return false;
6830 }
6831 }
6832 guard => {
6839 if !reference
6840 .guards()
6841 .is_some_and(|active| active.contains(&guard))
6842 {
6843 return false;
6844 }
6845 }
6846 }
6847 }
6848 ancestor = conditional.parent();
6849 }
6850 true
6851}
6852
6853fn flattened_macro_namespace_declaration_matches(
6854 analyzer: &CppGraphSource<'_>,
6855 cpp: &dyn CppSource,
6856 reference_file: &ProjectFile,
6857 visible_declaration: &CodeUnit,
6858 qualified_candidate: &CodeUnit,
6859 reference_byte: usize,
6860) -> bool {
6861 if visible_declaration.kind() != qualified_candidate.kind()
6867 || visible_declaration.identifier() != qualified_candidate.identifier()
6868 || visible_declaration.signature() != qualified_candidate.signature()
6869 || !visible_declaration.package_name().is_empty()
6870 || qualified_candidate.package_name().is_empty()
6871 {
6872 return false;
6873 }
6874
6875 let Some(prepared) = cpp.prepared_syntax(visible_declaration.source()) else {
6876 return false;
6877 };
6878 let root = prepared.tree().root_node();
6879 let closing_brace_limit = if visible_declaration.source() == reference_file {
6880 reference_byte
6881 } else {
6882 usize::MAX
6883 };
6884
6885 analyzer
6886 .ranges(visible_declaration)
6887 .into_iter()
6888 .any(|range| {
6889 let Some(mut declaration) =
6890 root.descendant_for_byte_range(range.start_byte, range.end_byte)
6891 else {
6892 return false;
6893 };
6894 while !matches!(
6895 declaration.kind(),
6896 "declaration" | "field_declaration" | "function_definition"
6897 ) {
6898 let Some(parent) = declaration.parent() else {
6899 return false;
6900 };
6901 declaration = parent;
6902 }
6903 if declaration
6904 .parent()
6905 .is_none_or(|parent| parent.kind() != "translation_unit")
6906 || !macro_displaced_cpp_return_type(declaration, prepared.source())
6907 {
6908 return false;
6909 }
6910
6911 let mut cursor = root.walk();
6912 root.named_children(&mut cursor).any(|sibling| {
6913 sibling.start_byte() >= declaration.end_byte()
6914 && sibling.start_byte() < closing_brace_limit
6915 && direct_unmatched_closing_brace(sibling)
6916 })
6917 })
6918}
6919
6920fn flattened_macro_namespace_components(
6921 declaration: Node<'_>,
6922 source: &str,
6923) -> Option<Vec<String>> {
6924 flattened_macro_function_namespace_components(declaration, source)
6925 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
6926}
6927
6928fn flattened_macro_function_namespace_components(
6929 declaration: Node<'_>,
6930 source: &str,
6931) -> Option<Vec<String>> {
6932 let body = declaration
6933 .parent()
6934 .filter(|parent| parent.kind() == "compound_statement")?;
6935 let function = body.parent()?;
6936 if function.child_by_field_name("body") != Some(body) {
6937 return None;
6938 }
6939 let namespace_name = recovered_macro_namespace_name(function, source)?;
6940 let mut components = enclosing_namespace_components(declaration, source)?;
6941 components.push(namespace_name);
6942 Some(components)
6943}
6944
6945fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
6956 if function.kind() != "function_definition" || !function.has_error() {
6957 return None;
6958 }
6959 let body = function
6960 .child_by_field_name("body")
6961 .filter(|body| body.kind() == "compound_statement")?;
6962 let mut cursor = function.walk();
6963 let prefix = function
6964 .named_children(&mut cursor)
6965 .take_while(|child| child.start_byte() < body.start_byte())
6966 .filter(|child| child.kind() != "comment")
6967 .collect::<Vec<_>>();
6968 let begin_index = prefix.iter().rposition(|child| {
6969 flattened_macro_sentinel_name(*child, source)
6970 .is_some_and(|name| is_namespace_begin_sentinel(&name))
6971 })?;
6972 let mut identifiers = Vec::new();
6973 let mut stack = prefix[begin_index + 1..]
6974 .iter()
6975 .rev()
6976 .copied()
6977 .collect::<Vec<_>>();
6978 while let Some(current) = stack.pop() {
6979 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
6980 identifiers.push(identifier);
6981 continue;
6982 }
6983 let mut cursor = current.walk();
6984 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
6985 stack.extend(children.into_iter().rev());
6986 }
6987 let [keyword, namespace_name] = identifiers.as_slice() else {
6988 return None;
6989 };
6990 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
6991 {
6992 return None;
6993 }
6994 let mut next = function.next_named_sibling();
6995 let next = loop {
6996 let candidate = next?;
6997 next = candidate.next_named_sibling();
6998 if candidate.kind() != "comment" {
6999 break candidate;
7000 }
7001 };
7002 flattened_macro_sentinel_name(next, source)
7003 .is_some_and(|name| is_namespace_end_sentinel(&name))
7004 .then(|| namespace_name.clone())
7005}
7006
7007fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
7012 crate::declarations::is_recovered_exported_class_container(node, source)
7013 || recovered_macro_namespace_name(node, source).is_some()
7014}
7015
7016fn flattened_macro_error_namespace_components(
7017 declaration: Node<'_>,
7018 source: &str,
7019) -> Option<Vec<String>> {
7020 let parent = declaration
7021 .parent()
7022 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
7023 let mut cursor = parent.walk();
7024 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
7025 let declaration_index = siblings
7026 .iter()
7027 .position(|candidate| same_node(*candidate, declaration))?;
7028 let begin_index = (0..declaration_index).rev().find(|index| {
7029 flattened_macro_sentinel_name(siblings[*index], source)
7030 .is_some_and(|name| is_namespace_begin_sentinel(&name))
7031 })?;
7032
7033 let significant = siblings[begin_index + 1..declaration_index]
7034 .iter()
7035 .copied()
7036 .filter(|node| node.kind() != "comment")
7037 .collect::<Vec<_>>();
7038 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
7039 return None;
7040 };
7041 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
7042 return None;
7043 }
7044 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
7045 if significant[2..].iter().any(|node| {
7046 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
7047 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
7048 })
7049 }) {
7050 return None;
7051 }
7052
7053 let mut saw_namespace_close = false;
7054 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
7055 if sibling.kind() == "comment" {
7056 continue;
7057 }
7058 if !saw_namespace_close {
7059 if direct_unmatched_closing_brace(sibling) {
7060 saw_namespace_close = true;
7061 continue;
7062 }
7063 if flattened_macro_sentinel_name(sibling, source).is_some() {
7064 return None;
7065 }
7066 continue;
7067 }
7068 if !flattened_macro_sentinel_name(sibling, source)
7069 .is_some_and(|name| is_namespace_end_sentinel(&name))
7070 {
7071 return None;
7072 }
7073 let mut components = enclosing_namespace_components(declaration, source)?;
7074 components.push(namespace_name);
7075 return Some(components);
7076 }
7077 None
7078}
7079
7080fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
7081 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
7085 node.named_child(0)?
7086 } else {
7087 node
7088 };
7089 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
7090 node.child_by_field_name("type")
7091 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
7092 })?;
7093 (cpp_export_macro_token(&candidate)
7094 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
7095 .then_some(candidate)
7096}
7097
7098fn is_namespace_begin_sentinel(name: &str) -> bool {
7101 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
7102}
7103
7104fn is_namespace_end_sentinel(name: &str) -> bool {
7105 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
7106}
7107
7108fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
7109 if node.kind() != "ERROR" || node.named_child_count() != 1 {
7110 return None;
7111 }
7112 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
7113 (!cpp_export_macro_token(&name)).then_some(name)
7114}
7115
7116fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
7117 if !matches!(
7118 node.kind(),
7119 "identifier" | "namespace_identifier" | "type_identifier"
7120 ) {
7121 return None;
7122 }
7123 let name = normalize_cpp_whitespace(node_text(node, source));
7124 (!name.is_empty()).then_some(name)
7125}
7126
7127fn guard_requirement_sets_match(
7128 left: &[(usize, HashSet<PreprocessorGuard>)],
7129 right: &[(usize, HashSet<PreprocessorGuard>)],
7130) -> bool {
7131 left.len() == right.len()
7132 && left.iter().all(|(_, left_guards)| {
7133 right
7134 .iter()
7135 .any(|(_, right_guards)| left_guards == right_guards)
7136 })
7137 && right.iter().all(|(_, right_guards)| {
7138 left.iter()
7139 .any(|(_, left_guards)| right_guards == left_guards)
7140 })
7141}
7142
7143fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
7144 let Some(type_node) = declaration.child_by_field_name("type") else {
7145 return false;
7146 };
7147 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
7148 !type_name.is_empty()
7149 && type_name
7150 .chars()
7151 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
7152 && (0..declaration.named_child_count()).any(|index| {
7153 declaration
7154 .named_child(index)
7155 .is_some_and(|child| child.kind() == "ERROR")
7156 })
7157}
7158
7159fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
7160 node.kind() == "ERROR"
7161 && (0..node.child_count())
7162 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
7163}
7164
7165pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
7166 let mut ancestor = node.parent();
7167 while let Some(parent) = ancestor {
7168 if is_preprocessor_conditional(parent)
7169 && !is_file_covering_include_guard(parent, source)
7170 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
7171 {
7172 return false;
7173 }
7174 ancestor = parent.parent();
7175 }
7176 true
7177}
7178
7179fn is_split_cpp_language_linkage_wrapper(
7180 conditional: Node<'_>,
7181 descendant: Node<'_>,
7182 source: &str,
7183) -> bool {
7184 if conditional.kind() != "preproc_ifdef"
7185 || conditional.child_by_field_name("alternative").is_some()
7186 || conditional
7187 .child_by_field_name("name")
7188 .is_none_or(|name| node_text(name, source) != "__cplusplus")
7189 {
7190 return false;
7191 }
7192 let mut current = descendant.parent();
7193 let linkage = loop {
7194 let Some(node) = current else {
7195 return false;
7196 };
7197 if node == conditional {
7198 return false;
7199 }
7200 if node.kind() == "linkage_specification" {
7201 break node;
7202 }
7203 current = node.parent();
7204 };
7205 if linkage
7206 .child_by_field_name("value")
7207 .is_none_or(|value| node_text(value, source) != "\"C\"")
7208 {
7209 return false;
7210 }
7211 let Some(body) = linkage.child_by_field_name("body") else {
7212 return false;
7213 };
7214 let closes_opening_branch = (0..body.named_child_count())
7215 .filter_map(|index| body.named_child(index))
7216 .take_while(|child| child.end_byte() <= descendant.start_byte())
7217 .any(|child| {
7218 child.kind() == "preproc_call"
7219 && child
7220 .child_by_field_name("directive")
7221 .is_some_and(|directive| node_text(directive, source) == "#endif")
7222 });
7223 let reopens_for_closing_brace = (0..body.named_child_count())
7224 .filter_map(|index| body.named_child(index))
7225 .skip_while(|child| child.start_byte() < descendant.end_byte())
7226 .any(|child| {
7227 child.kind() == "preproc_ifdef"
7228 && child
7229 .child_by_field_name("name")
7230 .is_some_and(|name| node_text(name, source) == "__cplusplus")
7231 && (0..child.child_count()).any(|index| {
7232 child
7233 .child(index)
7234 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
7235 })
7236 });
7237 closes_opening_branch && reopens_for_closing_brace
7238}
7239
7240pub fn call_arity(node: Node<'_>) -> usize {
7241 node.child_by_field_name("arguments")
7242 .or_else(|| node.child_by_field_name("parameters"))
7243 .or_else(|| node.child_by_field_name("value"))
7244 .or_else(|| first_named_child_of_kind(node, "argument_list"))
7245 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
7246 .map(|args| argument_children(args).count())
7247 .unwrap_or(0)
7248}
7249
7250pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
7251 let recovered_block_arguments = recovered_block_literal_arguments(node);
7252 (0..node.child_count())
7253 .filter_map(move |index| node.child(index))
7254 .filter(|child| child.is_named() && !child.is_extra())
7255 .flat_map(move |child| {
7256 if let Some((raw, left, right)) = recovered_block_arguments
7257 && child == raw
7258 {
7259 [Some(left), Some(right)]
7260 } else {
7261 [Some(child), None]
7262 }
7263 })
7264 .flatten()
7265}
7266
7267fn recovered_block_literal_arguments<'tree>(
7268 arguments: Node<'tree>,
7269) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
7270 if arguments.kind() != "argument_list" {
7271 return None;
7272 }
7273 let mut raw_arguments = (0..arguments.child_count())
7274 .filter_map(|index| arguments.child(index))
7275 .filter(|child| child.is_named() && !child.is_extra());
7276 let raw = raw_arguments.next()?;
7277 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
7278 return None;
7279 }
7280
7281 let left = raw.child_by_field_name("left")?;
7282 if left.is_missing() || left.start_byte() == left.end_byte() {
7283 return None;
7284 }
7285 let right = raw.child_by_field_name("right")?;
7286 if right.kind() != "compound_literal_expression"
7287 || right.is_missing()
7288 || right
7289 .child_by_field_name("type")
7290 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
7291 || right
7292 .child_by_field_name("value")
7293 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
7294 {
7295 return None;
7296 }
7297 let has_intervening_error = (0..raw.child_count())
7298 .filter_map(|index| raw.child(index))
7299 .any(|child| {
7300 child.kind() == "ERROR"
7301 && !child.is_missing()
7302 && child.start_byte() >= left.end_byte()
7303 && child.end_byte() <= right.start_byte()
7304 });
7305 has_intervening_error.then_some((raw, left, right))
7306}
7307
7308pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
7309 match node.kind() {
7310 "new_expression" => node
7311 .child_by_field_name("type")
7312 .or_else(|| node.named_child(0)),
7313 "compound_literal_expression" => node.child_by_field_name("type"),
7314 "call_expression" => node.child_by_field_name("function"),
7315 _ => None,
7316 }
7317}
7318
7319pub fn field_initializer_constructs_target(
7320 node: Node<'_>,
7321 ctx: &ScanCtx<'_>,
7322 owner: &CodeUnit,
7323) -> bool {
7324 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
7333 return qualified_base_initializer_constructs_target(node, ctx, owner);
7334 }
7335 let Some(name) = node
7336 .child_by_field_name("name")
7337 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
7338 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
7339 else {
7340 return false;
7341 };
7342 let field_name = node_text(name, ctx.source);
7343 ctx.visibility
7344 .visible_identifier_candidates(ctx.file, field_name)
7345 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
7346 .any(|unit| field_declares_type(unit, ctx, owner))
7347}
7348
7349fn qualified_base_initializer_constructs_target(
7350 node: Node<'_>,
7351 ctx: &ScanCtx<'_>,
7352 owner: &CodeUnit,
7353) -> bool {
7354 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
7355 return false;
7356 };
7357 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
7358 return false;
7359 };
7360 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
7361 return false;
7362 };
7363 let resolves_target = |components: &[String]| {
7364 matches!(
7365 ctx.visibility.resolve_type_components_lexically_for_target(
7366 &ctx.analyzer,
7367 ctx.file,
7368 components,
7369 is_globally_qualified_cpp_name(qualified),
7370 &lexical_scope,
7371 owner,
7372 ),
7373 LexicalTypeResolution::Resolved { unit, .. }
7374 if same_visible_symbol(&unit, owner)
7375 )
7376 };
7377 if resolves_target(&components) {
7378 return true;
7379 }
7380
7381 components
7387 .last()
7388 .is_some_and(|terminal| terminal == owner.identifier())
7389 && resolves_target(&components[..components.len() - 1])
7390}
7391
7392fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
7393 unit.signature()
7394 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
7395 || ctx
7396 .analyzer
7397 .get_source(unit, false)
7398 .is_some_and(|declaration| {
7399 field_declaration_type_matches(&declaration, unit, ctx, owner)
7400 })
7401}
7402
7403pub fn field_declared_binding(
7404 analyzer: &CppGraphSource<'_>,
7405 visibility: &VisibilityIndex<'_>,
7406 visible_from: &ProjectFile,
7407 field: &CodeUnit,
7408) -> Option<CppScanBinding> {
7409 let fact = visibility.field_declared_type_fact(analyzer, field)?;
7410 let normalized = normalize_field_type_text(&fact.type_text);
7411 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
7412 analyzer,
7413 visible_from,
7414 field,
7415 &normalized,
7416 );
7417 let resolved = match (resolved, fact.template_arguments.as_deref()) {
7418 (Some(primary), Some(arguments)) => visibility
7419 .resolve_template_arguments(visible_from, primary, arguments)
7420 .ok(),
7421 (resolved, None) => resolved,
7422 (None, Some(_)) => None,
7423 };
7424 Some(CppScanBinding::from_type_name(
7425 normalized,
7426 resolved,
7427 fact.indirection,
7428 ))
7429}
7430
7431fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
7433 let Some(first) = candidates.first() else {
7434 return Err(TypeCandidateFailure::Unresolvable);
7435 };
7436 if candidates
7437 .iter()
7438 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
7439 {
7440 Ok((*first).clone())
7441 } else {
7442 Err(TypeCandidateFailure::Ambiguous)
7443 }
7444}
7445
7446fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
7447 logical_type_candidate(candidates).ok()
7448}
7449
7450fn unique_type_candidate_preserving_alias(
7451 analyzer: &CppGraphSource<'_>,
7452 candidates: &[&CodeUnit],
7453) -> Option<CodeUnit> {
7454 let first = *candidates.first()?;
7455 if declared_type_alias(analyzer, first) {
7456 return candidates
7457 .iter()
7458 .all(|candidate| {
7459 declared_type_alias(analyzer, candidate) && same_logical_symbol(first, candidate)
7460 })
7461 .then(|| first.clone());
7462 }
7463 candidates
7464 .iter()
7465 .all(|candidate| {
7466 !declared_type_alias(analyzer, candidate)
7467 && candidate.kind() == first.kind()
7468 && candidate.fq_name() == first.fq_name()
7469 })
7470 .then(|| first.clone())
7471}
7472
7473fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
7474 is_type_alias(unit)
7475 || analyzer
7476 .type_alias_provider()
7477 .is_some_and(|provider| provider.is_type_alias(unit))
7478}
7479
7480pub fn field_declared_type_binding(
7481 analyzer: &CppGraphSource<'_>,
7482 visibility: &VisibilityIndex<'_>,
7483 visible_from: &ProjectFile,
7484 field: &CodeUnit,
7485) -> Option<(String, Option<CodeUnit>, i32)> {
7486 let fact = visibility.field_declared_type_fact(analyzer, field)?;
7487 let normalized = normalize_field_type_text(&fact.type_text);
7488 let primary = visibility.resolve_unique_canonical_type_for_declaration(
7489 analyzer,
7490 visible_from,
7491 field,
7492 &normalized,
7493 );
7494 let resolved = match (primary, fact.template_arguments.as_deref()) {
7495 (Some(primary), Some(arguments)) => visibility
7496 .resolve_template_arguments(visible_from, primary, arguments)
7497 .ok(),
7498 (resolved, None) => resolved,
7499 (None, Some(_)) => None,
7500 };
7501 Some((normalized, resolved, fact.indirection))
7502}
7503
7504fn decode_field_declared_type_fact(
7505 analyzer: &CppGraphSource<'_>,
7506 field: &CodeUnit,
7507) -> Option<DeclaredFieldTypeFact> {
7508 let declaration = analyzer.get_source(field, false)?;
7509 let mut parser = Parser::new();
7510 parser
7511 .set_language(&tree_sitter_cpp::LANGUAGE.into())
7512 .ok()?;
7513 let tree = parser.parse(&declaration, None)?;
7514 let mut stack = vec![tree.root_node()];
7515 while let Some(node) = stack.pop() {
7516 if matches!(node.kind(), "declaration" | "field_declaration")
7517 && let Some(type_node) = node
7518 .child_by_field_name("type")
7519 .or_else(|| first_type_child(node))
7520 && let Some(indirection) =
7521 declared_name_indirection(node, type_node, field.identifier(), &declaration)
7522 {
7523 return Some(DeclaredFieldTypeFact {
7524 type_text: node_text(type_node, &declaration).to_string(),
7525 indirection,
7526 template_arguments: cpp_template_reference_arguments(type_node, &declaration),
7527 });
7528 }
7529 let mut cursor = node.walk();
7530 stack.extend(node.named_children(&mut cursor));
7531 }
7532 None
7533}
7534
7535pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
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 let type_node = match node.kind() {
7557 "type_definition" => {
7558 let mut cursor = node.walk();
7559 if node
7560 .children_by_field_name("declarator", &mut cursor)
7561 .any(declarator_names_function_type)
7562 {
7563 return None;
7564 }
7565 node.child_by_field_name("type")?
7566 }
7567 "alias_declaration" => {
7568 let type_node = node.child_by_field_name("type")?;
7569 if type_node
7570 .child_by_field_name("declarator")
7571 .is_some_and(declarator_names_function_type)
7572 {
7573 return None;
7574 }
7575 type_node
7576 }
7577 _ => {
7578 let mut cursor = node.walk();
7579 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
7580 stack.extend(children.into_iter().rev());
7581 continue;
7582 }
7583 };
7584 return Some(node_text(type_node, declaration).to_string());
7585 }
7586 None
7587}
7588
7589fn declarator_names_function_type(declarator: Node<'_>) -> bool {
7595 let mut current = Some(declarator);
7596 while let Some(node) = current {
7597 match node.kind() {
7598 "function_declarator" | "abstract_function_declarator" => return true,
7599 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
7600 current = node.named_child(0);
7601 }
7602 _ => current = node.child_by_field_name("declarator"),
7603 }
7604 }
7605 false
7606}
7607
7608fn decode_structured_alias_target(
7609 analyzer: &CppGraphSource<'_>,
7610 unit: &CodeUnit,
7611) -> Option<StructuredAliasTarget> {
7612 analyzer
7613 .get_source(unit, false)
7614 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
7615 .or_else(|| {
7616 let signature = unit.signature()?;
7617 decode_structured_alias_target_source(unit, signature, false)
7618 })
7619}
7620
7621fn decode_structured_alias_target_source(
7622 unit: &CodeUnit,
7623 declaration: &str,
7624 require_top_level: bool,
7625) -> Option<StructuredAliasTarget> {
7626 let mut parser = Parser::new();
7627 parser
7628 .set_language(&tree_sitter_cpp::LANGUAGE.into())
7629 .ok()?;
7630 let tree = parser.parse(declaration, None)?;
7631 let mut stack = vec![tree.root_node()];
7632 while let Some(node) = stack.pop() {
7633 let type_node = match node.kind() {
7634 "type_definition" => {
7635 if require_top_level
7636 && node
7637 .parent()
7638 .is_none_or(|parent| parent.kind() != "translation_unit")
7639 {
7640 let mut cursor = node.walk();
7641 stack.extend(node.named_children(&mut cursor));
7642 continue;
7643 }
7644 let mut declarator_cursor = node.walk();
7645 let declarator = node
7646 .children_by_field_name("declarator", &mut declarator_cursor)
7647 .find(|declarator| {
7648 extract_typedef_declarator_name(*declarator, declaration)
7649 .is_some_and(|name| name == unit.identifier())
7650 })?;
7651 if declarator_names_function_type(declarator) {
7652 return None;
7653 }
7654 node.child_by_field_name("type")?
7655 }
7656 "alias_declaration" => {
7657 if require_top_level
7658 && node
7659 .parent()
7660 .is_none_or(|parent| parent.kind() != "translation_unit")
7661 {
7662 let mut cursor = node.walk();
7663 stack.extend(node.named_children(&mut cursor));
7664 continue;
7665 }
7666 let name = node.child_by_field_name("name")?;
7667 if node_text(name, declaration) != unit.identifier() {
7668 return None;
7669 }
7670 let type_node = node.child_by_field_name("type")?;
7671 if type_node
7672 .child_by_field_name("declarator")
7673 .is_some_and(declarator_names_function_type)
7674 {
7675 return None;
7676 }
7677 type_node
7678 }
7679 _ => {
7680 let mut cursor = node.walk();
7681 stack.extend(node.named_children(&mut cursor));
7682 continue;
7683 }
7684 };
7685 return structured_alias_type_target(type_node, declaration);
7686 }
7687 None
7688}
7689
7690fn structured_alias_type_target(
7691 mut type_node: Node<'_>,
7692 source: &str,
7693) -> Option<StructuredAliasTarget> {
7694 while type_node.kind() == "type_descriptor" {
7695 type_node = type_node.child_by_field_name("type")?;
7696 }
7697 if type_node.kind() == "primitive_type" {
7698 return Some(StructuredAliasTarget::Builtin);
7699 }
7700 if matches!(
7701 type_node.kind(),
7702 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
7703 ) {
7704 type_node = type_node.child_by_field_name("name")?;
7705 }
7706 let global = type_node.child_by_field_name("scope").is_none()
7707 && type_node.child(0).is_some_and(|child| child.kind() == "::");
7708 let mut components = Vec::new();
7709 append_structured_type_components(type_node, source, &mut components)?;
7710 let arguments = cpp_template_reference_arguments(type_node, source);
7711 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
7712 components,
7713 global,
7714 arguments,
7715 })
7716}
7717
7718fn append_structured_type_components(
7719 node: Node<'_>,
7720 source: &str,
7721 out: &mut Vec<String>,
7722) -> Option<()> {
7723 match node.kind() {
7724 "identifier" | "namespace_identifier" | "type_identifier" => {
7725 out.push(node_text(node, source).to_string());
7726 Some(())
7727 }
7728 "template_type" => {
7729 append_structured_type_components(node.child_by_field_name("name")?, source, out)
7730 }
7731 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
7732 if let Some(scope) = node.child_by_field_name("scope") {
7733 append_structured_type_components(scope, source, out)?;
7734 }
7735 append_structured_type_components(node.child_by_field_name("name")?, source, out)
7736 }
7737 _ => None,
7738 }
7739}
7740
7741fn declared_name_indirection(
7742 declaration: Node<'_>,
7743 type_node: Node<'_>,
7744 field_name: &str,
7745 source: &str,
7746) -> Option<i32> {
7747 let mut stack = Vec::new();
7748 let mut cursor = declaration.walk();
7749 stack.extend(
7750 declaration
7751 .named_children(&mut cursor)
7752 .filter(|child| !same_node(*child, type_node)),
7753 );
7754 while let Some(node) = stack.pop() {
7755 if matches!(node.kind(), "identifier" | "field_identifier")
7756 && node_text(node, source) == field_name
7757 {
7758 let mut indirection = 0;
7759 let mut current = node.parent();
7760 while let Some(parent) = current {
7761 if same_node(parent, declaration) {
7762 return Some(indirection);
7763 }
7764 if parent.kind() == "pointer_declarator" {
7765 indirection += 1;
7766 }
7767 current = parent.parent();
7768 }
7769 return None;
7770 }
7771 let mut cursor = node.walk();
7772 stack.extend(node.named_children(&mut cursor));
7773 }
7774 None
7775}
7776
7777fn field_declaration_type_matches(
7778 declaration: &str,
7779 unit: &CodeUnit,
7780 ctx: &ScanCtx<'_>,
7781 owner: &CodeUnit,
7782) -> bool {
7783 ctx.visibility
7784 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
7785 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
7786 let normalized = normalize_field_type_text(type_text);
7787 ctx.visibility
7788 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
7789 || ctx.visibility.resolves_to_type(
7790 &ctx.analyzer,
7791 ctx.file,
7792 normalized.as_str(),
7793 owner,
7794 )
7795 })
7796}
7797
7798fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
7799 let declaration = declaration
7800 .split(['=', ';'])
7801 .next()
7802 .unwrap_or(declaration)
7803 .trim();
7804 let index = declaration.rfind(field_name)?;
7805 let before = &declaration[..index];
7806 let after = &declaration[index + field_name.len()..];
7807 if before.chars().next_back().is_some_and(is_identifier_char)
7808 || after.chars().next().is_some_and(is_identifier_char)
7809 {
7810 return None;
7811 }
7812 Some(before.trim())
7813}
7814
7815fn normalize_field_type_text(type_text: &str) -> String {
7816 const FIELD_SPECIFIERS: [&str; 8] = [
7817 "extern ",
7818 "static ",
7819 "mutable ",
7820 "constexpr ",
7821 "constinit ",
7822 "inline ",
7823 "volatile ",
7824 "const ",
7825 ];
7826
7827 let mut normalized = normalize_type_text(type_text);
7828 loop {
7829 let Some(stripped) = FIELD_SPECIFIERS
7830 .iter()
7831 .find_map(|specifier| normalized.strip_prefix(specifier))
7832 else {
7833 return normalized;
7834 };
7835 normalized = normalize_type_text(stripped);
7836 }
7837}
7838
7839fn is_identifier_char(ch: char) -> bool {
7840 ch == '_' || ch.is_ascii_alphanumeric()
7841}
7842
7843pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
7844 let Some(type_node) = node.child_by_field_name("type") else {
7845 return false;
7846 };
7847 ctx.visibility.resolves_to_type(
7848 &ctx.analyzer,
7849 ctx.file,
7850 node_text(type_node, ctx.source),
7851 owner,
7852 )
7853}
7854
7855pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
7856 !ctx.analyzer
7857 .declarations(ctx.file)
7858 .into_iter()
7859 .filter(|unit| unit.is_function())
7860 .any(|unit| {
7861 ctx.analyzer.ranges(&unit).iter().any(|range| {
7862 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
7863 })
7864 })
7865}
7866
7867pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
7868 let mut cursor = node.walk();
7869 for child in node.named_children(&mut cursor) {
7870 if child.kind() == "init_declarator" {
7871 return child
7872 .child_by_field_name("value")
7873 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
7874 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
7875 .map(declaration_init_value_arity)
7876 .unwrap_or(0);
7877 }
7878 if is_declarator_node(child) {
7879 return declaration_declarator_arity(child);
7880 }
7881 }
7882 0
7883}
7884
7885fn declaration_init_value_arity(value: Node<'_>) -> usize {
7886 match value.kind() {
7887 "argument_list" | "initializer_list" => argument_children(value).count(),
7888 "compound_literal_expression" => call_arity(value),
7889 _ => 1,
7890 }
7891}
7892
7893fn declaration_declarator_arity(node: Node<'_>) -> usize {
7894 if let Some(parameters) = node.child_by_field_name("parameters") {
7895 return argument_children(parameters).count();
7896 }
7897 node.child_by_field_name("declarator")
7898 .map(declaration_declarator_arity)
7899 .unwrap_or(0)
7900}
7901
7902fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7903 let mut cursor = node.walk();
7904 node.named_children(&mut cursor)
7905 .find(|child| child.kind() == kind)
7906}
7907
7908fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
7909 let mut stack = vec![root];
7910 while let Some(node) = stack.pop() {
7911 if node.kind() == kind {
7912 return Some(node);
7913 }
7914 for index in (0..node.named_child_count()).rev() {
7915 if let Some(child) = node.named_child(index) {
7916 stack.push(child);
7917 }
7918 }
7919 }
7920 None
7921}
7922
7923fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
7924 if node.kind() == "identifier" {
7925 return true;
7926 }
7927 if node.kind() == "parenthesized_expression" {
7928 return false;
7929 }
7930 if node.kind() == "call_expression" {
7931 return node
7932 .child_by_field_name("function")
7933 .is_some_and(|function| function.kind() == "identifier");
7934 }
7935 let mut stack = vec![node];
7936 while let Some(descendant) = stack.pop() {
7937 if descendant != node && descendant.kind() == "parenthesized_expression" {
7938 continue;
7939 }
7940 if descendant.kind() == "identifier" {
7941 return true;
7942 }
7943 if descendant.kind() == "call_expression" {
7944 if descendant
7945 .child_by_field_name("function")
7946 .is_some_and(|function| function.kind() == "identifier")
7947 {
7948 return true;
7949 }
7950 continue;
7951 }
7952 for index in (0..descendant.named_child_count()).rev() {
7953 if let Some(child) = descendant.named_child(index) {
7954 stack.push(child);
7955 }
7956 }
7957 }
7958 false
7959}
7960
7961fn macro_expansion_shape_is_safe(
7962 node: Node<'_>,
7963 source: &str,
7964 parameters: &[String],
7965 environment: &MacroEnvironment,
7966) -> bool {
7967 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
7968 return true;
7969 }
7970 if node.kind() == "call_expression" {
7971 let Some(function) = node.child_by_field_name("function") else {
7972 return true;
7973 };
7974 if function.kind() != "identifier" {
7975 return true;
7976 }
7977 let function_name = node_text(function, source);
7978 if parameters
7979 .iter()
7980 .any(|parameter| parameter == function_name)
7981 {
7982 return false;
7983 }
7984 if !environment.may_bind(function_name) {
7985 return true;
7986 }
7987 let Some(arguments) = node.child_by_field_name("arguments") else {
7988 return false;
7989 };
7990 return argument_children(arguments).all(|argument| {
7991 if argument.kind() == "identifier"
7992 && parameters
7993 .iter()
7994 .any(|parameter| parameter == node_text(argument, source))
7995 {
7996 return false;
7997 }
7998 macro_expansion_shape_is_safe(argument, source, parameters, environment)
7999 });
8000 }
8001 let mut stack = vec![node];
8002 while let Some(descendant) = stack.pop() {
8003 if descendant != node {
8004 if descendant.kind() == "parenthesized_expression" {
8005 continue;
8006 }
8007 if descendant.kind() == "call_expression" {
8008 let expands = descendant
8009 .child_by_field_name("function")
8010 .filter(|function| function.kind() == "identifier")
8011 .is_some_and(|function| environment.may_bind(node_text(function, source)));
8012 if expands {
8013 return false;
8014 }
8015 continue;
8016 }
8017 }
8018 if descendant.kind() == "identifier" {
8019 let identifier = node_text(descendant, source);
8020 if parameters.iter().any(|parameter| parameter == identifier)
8021 || environment.may_bind(identifier)
8022 {
8023 return false;
8024 }
8025 }
8026 for index in (0..descendant.named_child_count()).rev() {
8027 if let Some(child) = descendant.named_child(index) {
8028 stack.push(child);
8029 }
8030 }
8031 }
8032 true
8033}
8034
8035fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
8036 let text = node_text(path, source);
8037 match path.kind() {
8038 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
8039 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
8040 _ => None,
8041 }
8042}
8043
8044fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
8045 while let Some(parent) = node.parent() {
8046 if is_preprocessor_conditional(parent) && !is_file_covering_include_guard(parent, source) {
8047 return true;
8048 }
8049 node = parent;
8050 }
8051 false
8052}
8053
8054fn is_preprocessor_conditional(node: Node<'_>) -> bool {
8055 matches!(
8056 node.kind(),
8057 "preproc_if"
8058 | "preproc_ifdef"
8059 | "preproc_ifndef"
8060 | "preproc_elif"
8061 | "preproc_elifdef"
8062 | "preproc_else"
8063 )
8064}
8065
8066fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
8067 node.parent()
8068 .filter(|parent| parent.kind() == "translation_unit")
8069 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
8070 && is_canonical_include_guard(node, source)
8071}
8072
8073fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
8074 if node.kind() != "preproc_ifdef"
8075 || node
8076 .child(0)
8077 .is_none_or(|directive| directive.kind() != "#ifndef")
8078 || node.child_by_field_name("alternative").is_some()
8079 {
8080 return false;
8081 }
8082 let Some(guard_name) = node.child_by_field_name("name") else {
8083 return false;
8084 };
8085 let mut cursor = node.walk();
8086 node.named_children(&mut cursor)
8087 .find(|child| *child != guard_name && child.kind() != "comment")
8088 .filter(|child| child.kind() == "preproc_def")
8089 .and_then(|definition| definition.child_by_field_name("name"))
8090 .is_some_and(|defined_name| {
8091 node_text(defined_name, source) == node_text(guard_name, source)
8092 })
8093}
8094
8095fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
8096 let mut guard = None;
8097 for index in 0..root.named_child_count() {
8098 let Some(child) = root.named_child(index) else {
8099 continue;
8100 };
8101 if child.kind() == "comment" || is_pragma_once(child, source) {
8102 continue;
8103 }
8104 if guard.is_none() && is_canonical_include_guard(child, source) {
8105 guard = Some(child);
8106 } else {
8107 return None;
8108 }
8109 }
8110 guard
8111 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
8112 .map(|name| node_text(name, source).to_string())
8113}
8114
8115fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
8116 if (0..root.named_child_count())
8117 .filter_map(|index| root.named_child(index))
8118 .any(|child| is_pragma_once(child, source))
8119 {
8120 return MacroIncludeProtection::PragmaOnce;
8121 }
8122 top_level_canonical_include_guard_name(root, source)
8123 .map(MacroIncludeProtection::MacroGuard)
8124 .unwrap_or(MacroIncludeProtection::None)
8125}
8126
8127fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
8128 node.kind() == "preproc_call"
8129 && node
8130 .child_by_field_name("directive")
8131 .is_some_and(|directive| node_text(directive, source) == "#pragma")
8132 && node
8133 .child_by_field_name("argument")
8134 .is_some_and(|argument| node_text(argument, source).trim() == "once")
8135}
8136
8137fn parse_preproc_identifier(argument: &str) -> Option<String> {
8138 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
8139 let mut parser = Parser::new();
8140 parser
8141 .set_language(&tree_sitter_cpp::LANGUAGE.into())
8142 .ok()?;
8143 let tree = parser.parse(&sentinel, None)?;
8144 if tree.root_node().has_error() {
8145 return None;
8146 }
8147 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
8148 let identifier = statement.named_child(0)?;
8149 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
8150 .then(|| node_text(identifier, &sentinel).to_string())
8151}
8152
8153pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
8154 match node.kind() {
8155 "identifier" | "field_identifier" => {
8156 let name = node_text(node, source).trim();
8157 (!name.is_empty()).then(|| name.to_string())
8158 }
8159 "abstract_array_declarator"
8160 | "abstract_function_declarator"
8161 | "abstract_parenthesized_declarator"
8162 | "abstract_pointer_declarator"
8163 | "abstract_reference_declarator" => None,
8164 "function_declarator" => node
8165 .child_by_field_name("declarator")
8166 .or_else(|| node.child_by_field_name("name"))
8167 .and_then(|child| extract_variable_name(child, source)),
8168 _ => node
8169 .child_by_field_name("declarator")
8170 .or_else(|| node.child_by_field_name("name"))
8171 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
8172 .and_then(|child| extract_variable_name(child, source)),
8173 }
8174}
8175
8176pub fn is_declarator_node(node: Node<'_>) -> bool {
8177 matches!(
8178 node.kind(),
8179 "identifier"
8180 | "field_identifier"
8181 | "pointer_declarator"
8182 | "reference_declarator"
8183 | "array_declarator"
8184 | "parenthesized_declarator"
8185 | "function_declarator"
8186 )
8187}
8188
8189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8190pub enum RecoveredDeclaratorTypeContext {
8191 Declaration,
8192 FunctionDefinition,
8193 Parameter,
8194}
8195
8196pub fn recovered_macro_decorated_declarator_type(
8210 node: Node<'_>,
8211) -> Option<RecoveredDeclaratorTypeContext> {
8212 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
8213}
8214
8215pub fn recovered_macro_decorated_type_node(
8220 node: Node<'_>,
8221) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
8222 if node.kind() != "namespace_identifier" || node.is_missing() {
8223 return None;
8224 }
8225 let qualified = node.parent()?;
8226 if qualified.kind() != "qualified_identifier"
8227 || qualified.child_by_field_name("scope") != Some(node)
8228 || !(0..qualified.child_count())
8229 .filter_map(|index| qualified.child(index))
8230 .any(|child| child.kind() == "::" && child.is_missing())
8231 {
8232 return None;
8233 }
8234 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
8235 return None;
8236 }
8237
8238 let (declaration, context) = recovered_declarator_container(qualified)?;
8239 let type_node = declaration
8240 .child_by_field_name("type")
8241 .filter(|type_node| {
8242 *type_node != qualified
8243 && !type_node.is_missing()
8244 && type_node.start_byte() != type_node.end_byte()
8245 })?;
8246 Some((type_node, context))
8247}
8248
8249fn recovered_declarator_container(
8250 mut declarator: Node<'_>,
8251) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
8252 loop {
8253 let parent = declarator.parent()?;
8254 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
8255 return Some((
8256 parent
8257 .parent()
8258 .filter(|declaration| declaration.kind() == "declaration")?,
8259 RecoveredDeclaratorTypeContext::Declaration,
8260 ));
8261 }
8262 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
8263 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
8264 }
8265 if parent.kind() == "function_definition"
8266 && has_field_child(parent, "declarator", declarator)
8267 {
8268 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
8269 }
8270 if matches!(
8276 parent.kind(),
8277 "parameter_declaration" | "optional_parameter_declaration"
8278 ) && has_field_child(parent, "declarator", declarator)
8279 {
8280 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
8281 }
8282 if !matches!(
8283 parent.kind(),
8284 "array_declarator"
8285 | "function_declarator"
8286 | "parenthesized_declarator"
8287 | "pointer_declarator"
8288 | "pointer_type_declarator"
8289 | "reference_declarator"
8290 ) || !has_field_child(parent, "declarator", declarator)
8291 {
8292 return None;
8293 }
8294 declarator = parent;
8295 }
8296}
8297
8298fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
8299 let mut cursor = parent.walk();
8300 parent
8301 .children_by_field_name(field, &mut cursor)
8302 .any(|child| child == target)
8303}
8304
8305fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
8306 loop {
8307 if node.is_missing() || node.start_byte() == node.end_byte() {
8308 return false;
8309 }
8310 match node.kind() {
8311 "identifier" | "field_identifier" | "type_identifier" => return true,
8312 "array_declarator"
8313 | "function_declarator"
8314 | "parenthesized_declarator"
8315 | "pointer_declarator"
8316 | "pointer_type_declarator"
8317 | "reference_declarator" => {
8318 let Some(declarator) = node.child_by_field_name("declarator") else {
8319 return false;
8320 };
8321 node = declarator;
8322 }
8323 _ => return false,
8324 }
8325 }
8326}
8327
8328pub enum DesignatedInitializerOwner {
8330 Resolved(CodeUnit),
8331 Unresolved,
8332}
8333
8334pub fn designated_initializer_owner(
8345 visibility: &VisibilityIndex<'_>,
8346 file: &ProjectFile,
8347 source: &str,
8348 node: Node<'_>,
8349) -> Option<DesignatedInitializerOwner> {
8350 if let Some(designator) = node
8351 .parent()
8352 .filter(|parent| parent.kind() == "field_designator")
8353 {
8354 let pair = designator.parent()?;
8355 if pair.kind() != "initializer_pair"
8356 || pair.child_by_field_name("designator") != Some(designator)
8357 {
8358 return None;
8359 }
8360 let initializer = pair.parent()?;
8361 if initializer.kind() != "initializer_list" {
8362 return None;
8363 }
8364 return Some(classified_designated_owner(initializer_list_owner(
8365 visibility,
8366 file,
8367 source,
8368 initializer,
8369 )));
8370 }
8371
8372 let init_declarator = node.parent()?;
8373 if init_declarator.child_by_field_name("declarator") != Some(node)
8374 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
8375 {
8376 return None;
8377 }
8378 Some(classified_designated_owner(declaration_owner(
8379 visibility,
8380 file,
8381 source,
8382 init_declarator.parent()?,
8383 )))
8384}
8385
8386fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
8387 owner.map_or(
8388 DesignatedInitializerOwner::Unresolved,
8389 DesignatedInitializerOwner::Resolved,
8390 )
8391}
8392
8393fn initializer_list_owner(
8394 visibility: &VisibilityIndex<'_>,
8395 file: &ProjectFile,
8396 source: &str,
8397 initializer: Node<'_>,
8398) -> Option<CodeUnit> {
8399 let mut current = initializer;
8400 let mut outer_initializer_lists = 0usize;
8401 loop {
8402 let parent = current.parent()?;
8403 match parent.kind() {
8404 "initializer_pair" => return None,
8405 "initializer_list" => {
8406 outer_initializer_lists += 1;
8407 if outer_initializer_lists > 1 {
8408 return None;
8409 }
8410 current = parent;
8411 }
8412 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
8413 let declaration = parent.parent()?;
8414 if outer_initializer_lists == 1
8415 && !parent
8416 .child_by_field_name("declarator")
8417 .is_some_and(contains_array_declarator)
8418 {
8419 return None;
8420 }
8421 return declaration_owner(visibility, file, source, declaration);
8422 }
8423 "compound_literal_expression"
8424 if parent.child_by_field_name("value") == Some(current)
8425 && outer_initializer_lists == 0 =>
8426 {
8427 let type_node = parent.child_by_field_name("type")?;
8428 return resolve_designated_owner_type(visibility, file, source, type_node);
8429 }
8430 "ERROR" => current = parent,
8431 _ => return None,
8432 }
8433 }
8434}
8435
8436fn declaration_owner(
8437 visibility: &VisibilityIndex<'_>,
8438 file: &ProjectFile,
8439 source: &str,
8440 declaration: Node<'_>,
8441) -> Option<CodeUnit> {
8442 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
8443 return None;
8444 }
8445 let type_node = declaration
8446 .child_by_field_name("type")
8447 .or_else(|| first_type_child(declaration))?;
8448 resolve_designated_owner_type(visibility, file, source, type_node)
8449}
8450
8451fn resolve_designated_owner_type(
8452 visibility: &VisibilityIndex<'_>,
8453 file: &ProjectFile,
8454 source: &str,
8455 type_node: Node<'_>,
8456) -> Option<CodeUnit> {
8457 let type_name = normalize_type_text(node_text(type_node, source));
8458 visibility
8459 .resolve_type(file, &type_name)
8460 .filter(CodeUnit::is_class)
8461}
8462
8463fn contains_array_declarator(declarator: Node<'_>) -> bool {
8464 let mut stack = vec![declarator];
8465 while let Some(node) = stack.pop() {
8466 if node.kind() == "array_declarator" {
8467 return true;
8468 }
8469 if matches!(node.kind(), "initializer_list" | "compound_statement") {
8470 continue;
8471 }
8472 let mut cursor = node.walk();
8473 stack.extend(node.named_children(&mut cursor));
8474 }
8475 false
8476}
8477
8478pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
8479 let mut cursor = node.walk();
8480 node.named_children(&mut cursor).find(|child| {
8481 matches!(
8482 child.kind(),
8483 "type_identifier"
8484 | "primitive_type"
8485 | "qualified_identifier"
8486 | "scoped_type_identifier"
8487 | "struct_specifier"
8488 | "union_specifier"
8489 | "enum_specifier"
8490 )
8491 })
8492}
8493
8494pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
8495 visibility: &VisibilityIndex<'_>,
8496 file: &ProjectFile,
8497 source: &str,
8498 declarator: Node<'_>,
8499 type_text: Option<&str>,
8500 bindings: &LocalInferenceEngine<T>,
8501) -> bool {
8502 if !has_ancestor_kind(declarator, "compound_statement") {
8503 return false;
8504 }
8505 if declarator
8506 .child_by_field_name("declarator")
8507 .is_none_or(|declarator| declarator.kind() != "identifier")
8508 {
8509 return false;
8510 }
8511 if !type_text
8512 .and_then(|text| visibility.resolve_type(file, text))
8513 .is_some_and(|unit| unit.is_class())
8514 {
8515 return false;
8516 }
8517 declarator
8518 .child_by_field_name("parameters")
8519 .is_some_and(|parameters| {
8520 constructor_parameters_look_like_expressions(parameters, source, bindings)
8521 })
8522}
8523
8524fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
8525 parameters: Node<'_>,
8526 source: &str,
8527 bindings: &LocalInferenceEngine<T>,
8528) -> bool {
8529 let mut cursor = parameters.walk();
8530 parameters.named_children(&mut cursor).any(|parameter| {
8531 !matches!(
8532 parameter.kind(),
8533 "parameter_declaration" | "optional_parameter_declaration"
8534 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
8535 })
8536}
8537
8538fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
8539 parameter: Node<'_>,
8540 source: &str,
8541 bindings: &LocalInferenceEngine<T>,
8542) -> bool {
8543 let text = node_text(parameter, source).trim();
8544 text.chars()
8545 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
8546 && bindings.is_shadowed(text)
8547}
8548
8549pub fn is_declaration_name(node: Node<'_>) -> bool {
8550 let Some(parent) = node.parent() else {
8551 return false;
8552 };
8553 if parent
8554 .child_by_field_name("name")
8555 .is_some_and(|name| same_node(name, node))
8556 {
8557 if matches!(
8558 parent.kind(),
8559 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
8560 ) {
8561 return cpp_tag_specifier_declares_name(parent);
8562 }
8563 if matches!(
8564 parent.kind(),
8565 "namespace_definition"
8566 | "namespace_alias_definition"
8567 | "alias_declaration"
8568 | "enumerator"
8569 ) {
8570 return true;
8571 }
8572 }
8573
8574 let mut current = Some(parent);
8575 while let Some(ancestor) = current {
8576 let type_definition = ancestor.kind() == "type_definition";
8577 let mut declarator_cursor = ancestor.walk();
8578 if ancestor
8579 .children_by_field_name("declarator", &mut declarator_cursor)
8580 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
8581 {
8582 return true;
8583 }
8584 if matches!(
8585 ancestor.kind(),
8586 "declaration"
8587 | "field_declaration"
8588 | "parameter_declaration"
8589 | "optional_parameter_declaration"
8590 | "function_definition"
8591 | "type_definition"
8592 | "alias_declaration"
8593 | "class_specifier"
8594 | "struct_specifier"
8595 | "union_specifier"
8596 | "enum_specifier"
8597 ) {
8598 return false;
8599 }
8600 current = ancestor.parent();
8601 }
8602 false
8603}
8604
8605pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
8613 let mut current = parameter.parent();
8614 while let Some(ancestor) = current {
8615 if ancestor.kind() == "lambda_expression" {
8616 return ancestor
8617 .child_by_field_name("declarator")
8618 .is_some_and(|declarator| {
8619 declarator.start_byte() <= parameter.start_byte()
8620 && parameter.end_byte() <= declarator.end_byte()
8621 });
8622 }
8623 if ancestor.kind() == "function_definition" {
8624 return ancestor
8625 .child_by_field_name("declarator")
8626 .is_some_and(|declarator| {
8627 declarator.start_byte() <= parameter.start_byte()
8628 && parameter.end_byte() <= declarator.end_byte()
8629 });
8630 }
8631 current = ancestor.parent();
8632 }
8633 false
8634}
8635
8636fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
8637 if specifier.child_by_field_name("body").is_some() {
8638 return true;
8639 }
8640 let mut current = specifier.parent();
8641 while let Some(ancestor) = current {
8642 match ancestor.kind() {
8643 "type_descriptor"
8644 | "parameter_declaration"
8645 | "optional_parameter_declaration"
8646 | "template_argument_list"
8647 | "cast_expression" => return false,
8648 "declaration" | "field_declaration" => {
8649 let mut cursor = ancestor.walk();
8650 return ancestor
8651 .children_by_field_name("declarator", &mut cursor)
8652 .next()
8653 .is_none();
8654 }
8655 "translation_unit" => return true,
8656 _ => current = ancestor.parent(),
8657 }
8658 }
8659 false
8660}
8661
8662pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
8663 match node.kind() {
8664 "identifier"
8665 | "field_identifier"
8666 | "qualified_identifier"
8667 | "scoped_identifier"
8668 | "operator_name"
8669 | "destructor_name"
8670 | "literal_operator_name" => Some(node),
8671 _ => node
8672 .child_by_field_name("declarator")
8673 .or_else(|| node.child_by_field_name("name"))
8674 .or_else(|| node.child_by_field_name("field"))
8675 .and_then(declarator_name_node),
8676 }
8677}
8678
8679fn declarator_name_path_contains(
8680 declarator: Node<'_>,
8681 candidate: Node<'_>,
8682 allow_type_identifier: bool,
8683) -> bool {
8684 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
8685 return false;
8686 };
8687 let mut current = Some(declarator);
8688 while let Some(node) = current {
8689 if same_node(node, candidate) {
8690 return true;
8691 }
8692 if same_node(node, name) {
8693 return false;
8694 }
8695 current = node
8696 .child_by_field_name("declarator")
8697 .or_else(|| node.child_by_field_name("name"))
8698 .or_else(|| node.child_by_field_name("field"));
8699 }
8700 false
8701}
8702
8703fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
8704 match node.kind() {
8705 "identifier"
8706 | "field_identifier"
8707 | "operator_name"
8708 | "destructor_name"
8709 | "literal_operator_name" => Some(node),
8710 "type_identifier" if allow_type_identifier => 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(|child| declarator_name_leaf(child, allow_type_identifier)),
8716 }
8717}
8718
8719pub fn is_nested_type_node(node: Node<'_>) -> bool {
8722 node.parent().is_some_and(|parent| {
8723 matches!(
8724 parent.kind(),
8725 "qualified_identifier" | "scoped_type_identifier" | "template_type"
8726 )
8727 })
8728}
8729
8730pub struct OutOfLineMemberDefinitionOwners<'tree> {
8731 pub owners: Vec<(Node<'tree>, CodeUnit)>,
8732 innermost: Option<(Node<'tree>, CodeUnit)>,
8733}
8734
8735impl OutOfLineMemberDefinitionOwners<'_> {
8736 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
8737 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
8738 }
8739}
8740
8741pub struct QualifiedOwnerComponents<'tree> {
8742 pub nodes: Vec<Node<'tree>>,
8743 pub names: Vec<String>,
8744 pub global: bool,
8745}
8746
8747pub fn qualified_owner_components<'tree>(
8748 node: Node<'tree>,
8749 source: &str,
8750) -> Option<QualifiedOwnerComponents<'tree>> {
8751 let mut nodes = cpp_name_component_nodes(node)?;
8752 nodes.pop()?;
8753 if nodes.is_empty() {
8754 return None;
8755 }
8756 let names = nodes
8757 .iter()
8758 .map(|component| node_text(*component, source).to_string())
8759 .collect();
8760 Some(QualifiedOwnerComponents {
8761 nodes,
8762 names,
8763 global: is_globally_qualified_cpp_name(node),
8764 })
8765}
8766
8767pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
8776 if node.kind() != "qualified_identifier" {
8777 return None;
8778 }
8779 let mut qualified = node;
8780 let destructor = loop {
8781 let name = qualified.child_by_field_name("name")?;
8782 match name.kind() {
8783 "qualified_identifier" => qualified = name,
8784 "destructor_name" => break name,
8785 _ => return None,
8786 }
8787 };
8788 (0..destructor.named_child_count())
8789 .filter_map(|index| destructor.named_child(index))
8790 .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
8791}
8792
8793pub fn out_of_line_member_definition_owner<'tree>(
8794 analyzer: &CppGraphSource<'_>,
8795 visibility: &VisibilityIndex<'_>,
8796 file: &ProjectFile,
8797 source: &str,
8798 node: Node<'tree>,
8799) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
8800 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
8801 || !has_ancestor_kind(node, "function_definition")
8802 || !is_function_declarator_name_root(node)
8803 {
8804 return None;
8805 }
8806 let qualified = qualified_owner_components(node, source)?;
8807 let lexical_scope = enclosing_namespace_components(node, source)?;
8808 let mut owners = Vec::new();
8809 let mut innermost = None;
8810
8811 for component_count in 1..=qualified.names.len() {
8812 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
8813 .resolve_type_components_lexically(
8814 analyzer,
8815 file,
8816 &qualified.names[..component_count],
8817 qualified.global,
8818 &lexical_scope,
8819 )
8820 && !owners
8821 .iter()
8822 .any(|(_, existing)| same_visible_symbol(existing, &unit))
8823 {
8824 if component_count == qualified.names.len() {
8825 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
8826 }
8827 owners.push((qualified.nodes[component_count - 1], unit));
8828 }
8829 }
8830
8831 if innermost.is_none() {
8841 let indexed_owner_components = visibility
8842 .indexed_enclosing_owner_scope(analyzer, file, node)
8843 .or_else(|| {
8844 if qualified.names.len() <= 1 {
8849 return None;
8850 }
8851 let range = Range {
8852 start_byte: node.start_byte(),
8853 end_byte: node.end_byte(),
8854 start_line: node.start_position().row,
8855 end_line: node.end_position().row,
8856 };
8857 let start = analyzer.enclosing_code_unit(file, &range)?;
8858 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8859 brokk_bifrost_core::analyzer::Language::Cpp,
8860 &cpp_name_for(&start),
8861 );
8862 components.pop();
8863 Some(components)
8864 });
8865 if let Some(indexed_owner_components) = indexed_owner_components
8866 && indexed_owner_components.len() > qualified.names.len()
8867 && indexed_owner_components.ends_with(&qualified.names)
8868 && indexed_namespace_path_is_recoverable(
8869 &lexical_scope,
8870 &indexed_owner_components,
8871 )
8872 && (qualified.names.len() > 1 || !qualified.global)
8877 {
8878 let namespace_count = indexed_owner_components.len() - qualified.names.len();
8879 for component_count in 1..=qualified.names.len() {
8880 let expected = &indexed_owner_components[..namespace_count + component_count];
8881 let owner_node = qualified.nodes[component_count - 1];
8882 for owner in visibility
8883 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
8884 .filter(|candidate| candidate.is_class())
8885 .filter(|candidate| {
8886 canonical_cpp_scope_components(candidate) == expected
8887 && visibility.external_type_candidate_visible_in_context(
8888 analyzer, file, candidate, node,
8889 )
8890 })
8891 {
8892 if component_count == qualified.names.len() && innermost.is_none() {
8893 innermost = Some((owner_node, owner.clone()));
8894 }
8895 if !owners
8896 .iter()
8897 .any(|(_, existing)| same_symbol(existing, owner))
8898 {
8899 owners.push((owner_node, owner.clone()));
8900 }
8901 }
8902 }
8903 }
8904 }
8905 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
8906}
8907
8908fn is_function_declarator_name_root(node: Node<'_>) -> bool {
8909 let mut current = node;
8910 while let Some(parent) = current.parent() {
8911 if parent.kind() == "function_declarator" {
8912 return parent.child_by_field_name("declarator") == Some(current);
8913 }
8914 if matches!(
8915 parent.kind(),
8916 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
8917 ) && parent.child_by_field_name("declarator") == Some(current)
8918 {
8919 current = parent;
8920 continue;
8921 }
8922 return false;
8923 }
8924 false
8925}
8926
8927pub fn append_cpp_name_components(
8928 node: Node<'_>,
8929 source: &str,
8930 out: &mut Vec<String>,
8931) -> Option<()> {
8932 out.extend(
8933 cpp_name_component_nodes(node)?
8934 .into_iter()
8935 .map(|component| node_text(component, source).to_string()),
8936 );
8937 Some(())
8938}
8939
8940pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
8941 let mut components = Vec::new();
8942 append_cpp_name_components(node, source, &mut components)?;
8943 Some(components)
8944}
8945
8946pub fn cpp_template_reference_arguments(
8947 mut node: Node<'_>,
8948 source: &str,
8949) -> Option<Vec<CppTemplateExpression>> {
8950 loop {
8951 match node.kind() {
8952 "template_type" | "template_function" => {
8953 let arguments = node.child_by_field_name("arguments")?;
8954 let mut cursor = arguments.walk();
8955 return Some(
8956 arguments
8957 .named_children(&mut cursor)
8958 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
8959 .map(|argument| CppTemplateExpression {
8960 text: normalize_cpp_whitespace(node_text(argument, source)),
8961 term: cpp_template_term(argument, source, &[]),
8962 })
8963 .collect(),
8964 );
8965 }
8966 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
8967 node = node
8968 .child_by_field_name("name")
8969 .or_else(|| node.child_by_field_name("type"))?;
8970 }
8971 _ => return None,
8972 }
8973 }
8974}
8975
8976fn cpp_reconcile_primary_template_parameters(
8977 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
8978 preferred: &CodeUnit,
8979) -> Option<Vec<CppTemplateParameterMetadata>> {
8980 let canonical = candidates
8981 .iter()
8982 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
8983 let mut merged = canonical
8984 .parameters
8985 .iter()
8986 .map(|parameter| CppTemplateParameterMetadata {
8987 name: parameter.name.clone(),
8988 kind: parameter.kind,
8989 variadic: parameter.variadic,
8990 default: None,
8991 })
8992 .collect::<Vec<_>>();
8993
8994 for (_, metadata) in candidates {
8995 if metadata.parameters.len() != merged.len() {
8996 return None;
8997 }
8998 let rename_bindings = metadata
8999 .parameters
9000 .iter()
9001 .zip(&merged)
9002 .map(|(parameter, canonical)| {
9003 (
9004 parameter.name.clone(),
9005 CppTemplateTerm::Parameter(canonical.name.clone()),
9006 )
9007 })
9008 .collect::<HashMap<_, _>>();
9009 for ((parameter, canonical), merged_parameter) in metadata
9010 .parameters
9011 .iter()
9012 .zip(&canonical.parameters)
9013 .zip(&mut merged)
9014 {
9015 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
9016 return None;
9017 }
9018 let Some(default) = ¶meter.default else {
9019 continue;
9020 };
9021 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
9022 if let Some(existing) = &merged_parameter.default {
9023 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
9024 return None;
9025 }
9026 } else {
9027 merged_parameter.default = Some(CppTemplateExpression {
9028 text: default.text.clone(),
9029 term: normalized_term,
9030 });
9031 }
9032 }
9033 }
9034 Some(merged)
9035}
9036
9037pub fn cpp_bind_template_arguments(
9038 parameters: &[CppTemplateParameterMetadata],
9039 explicit_arguments: &[CppTemplateExpression],
9040) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
9041 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
9042 if variadic_index.is_some_and(|index| {
9043 index + 1 != parameters.len()
9044 || parameters[index + 1..]
9045 .iter()
9046 .any(|parameter| parameter.variadic)
9047 }) {
9048 return None;
9049 }
9050 let fixed_count = variadic_index.unwrap_or(parameters.len());
9051 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
9052 return None;
9053 }
9054 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
9055 let mut expanded = explicit_arguments[..explicit_fixed_count]
9056 .iter()
9057 .map(cpp_clone_template_expression_iterative)
9058 .collect::<Vec<_>>();
9059 let mut bindings = HashMap::default();
9060 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
9061 bindings.insert(
9062 parameter.name.clone(),
9063 cpp_clone_template_term_iterative(&argument.term),
9064 );
9065 }
9066 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
9067 let default = parameter.default.as_ref()?;
9068 let term = cpp_substitute_template_term(&default.term, &bindings)?;
9069 bindings.insert(parameter.name.clone(), term.clone());
9070 expanded.push(CppTemplateExpression {
9071 text: default.text.clone(),
9072 term,
9073 });
9074 }
9075 if let Some(index) = variadic_index {
9076 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
9077 expanded.extend(
9078 packed_arguments
9079 .iter()
9080 .map(cpp_clone_template_expression_iterative),
9081 );
9082 bindings.insert(
9083 parameters[index].name.clone(),
9084 CppTemplateTerm::Node {
9085 kind: "parameter_pack".to_string(),
9086 children: packed_arguments
9087 .iter()
9088 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
9089 .collect(),
9090 },
9091 );
9092 }
9093 Some((expanded, bindings))
9094}
9095
9096fn cpp_specialization_matches(
9097 metadata: &CppTemplateMetadata,
9098 arguments: &[CppTemplateExpression],
9099) -> bool {
9100 if metadata.specialization_arguments.len() != arguments.len() {
9101 return false;
9102 }
9103 let parameter_names = metadata
9104 .parameters
9105 .iter()
9106 .map(|parameter| parameter.name.as_str())
9107 .collect::<HashSet<_>>();
9108 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
9109 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
9110 if !cpp_unify_template_term(
9111 &pattern.term,
9112 &argument.term,
9113 ¶meter_names,
9114 &mut bindings,
9115 ) {
9116 return false;
9117 }
9118 }
9119 true
9120}
9121
9122fn cpp_specialization_more_specialized(
9123 candidate: &CppTemplateMetadata,
9124 other: &CppTemplateMetadata,
9125) -> bool {
9126 cpp_specialization_pattern_accepts(other, candidate)
9127 && !cpp_specialization_pattern_accepts(candidate, other)
9128}
9129
9130fn cpp_specialization_pattern_accepts(
9131 broader: &CppTemplateMetadata,
9132 narrower: &CppTemplateMetadata,
9133) -> bool {
9134 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
9135 return false;
9136 }
9137 let parameter_names = broader
9138 .parameters
9139 .iter()
9140 .map(|parameter| parameter.name.as_str())
9141 .collect::<HashSet<_>>();
9142 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
9143 broader
9144 .specialization_arguments
9145 .iter()
9146 .zip(&narrower.specialization_arguments)
9147 .all(|(pattern, argument)| {
9148 cpp_unify_template_term(
9149 &pattern.term,
9150 &argument.term,
9151 ¶meter_names,
9152 &mut bindings,
9153 )
9154 })
9155}
9156
9157pub fn cpp_substitute_template_term(
9158 term: &CppTemplateTerm,
9159 bindings: &HashMap<String, CppTemplateTerm>,
9160) -> Option<CppTemplateTerm> {
9161 enum Work<'a> {
9162 Visit(&'a CppTemplateTerm),
9163 Build { kind: String, child_count: usize },
9164 }
9165
9166 let mut work = vec![Work::Visit(term)];
9167 let mut substituted = Vec::new();
9168 while let Some(next) = work.pop() {
9169 match next {
9170 Work::Visit(CppTemplateTerm::Parameter(name)) => {
9171 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
9172 }
9173 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
9174 substituted.push(CppTemplateTerm::Atom {
9175 kind: kind.clone(),
9176 text: text.clone(),
9177 });
9178 }
9179 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
9180 work.push(Work::Build {
9181 kind: kind.clone(),
9182 child_count: children.len(),
9183 });
9184 work.extend(children.iter().rev().map(Work::Visit));
9185 }
9186 Work::Build { kind, child_count } => {
9187 let children = substituted.split_off(substituted.len() - child_count);
9188 substituted.push(CppTemplateTerm::Node { kind, children });
9189 }
9190 }
9191 }
9192 substituted.pop()
9193}
9194
9195pub fn cpp_substitute_template_arguments(
9196 arguments: &[CppTemplateExpression],
9197 bindings: &HashMap<String, CppTemplateTerm>,
9198) -> Option<Vec<CppTemplateExpression>> {
9199 let mut substituted = Vec::new();
9200 for argument in arguments {
9201 let CppTemplateTerm::Node { kind, children } = &argument.term else {
9202 substituted.push(CppTemplateExpression {
9203 text: argument.text.clone(),
9204 term: cpp_substitute_template_term(&argument.term, bindings)?,
9205 });
9206 continue;
9207 };
9208 if kind != "parameter_pack_expansion" {
9209 substituted.push(CppTemplateExpression {
9210 text: argument.text.clone(),
9211 term: cpp_substitute_template_term(&argument.term, bindings)?,
9212 });
9213 continue;
9214 }
9215 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
9216 return None;
9217 };
9218 if ellipsis != "..." {
9219 return None;
9220 }
9221
9222 let mut pack_names = Vec::new();
9223 let mut work = vec![pattern];
9224 while let Some(term) = work.pop() {
9225 match term {
9226 CppTemplateTerm::Parameter(name)
9227 if matches!(
9228 bindings.get(name),
9229 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
9230 ) =>
9231 {
9232 if !pack_names.contains(name) {
9233 pack_names.push(name.clone());
9234 }
9235 }
9236 CppTemplateTerm::Node { children, .. } => work.extend(children),
9237 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
9238 }
9239 }
9240 let first_pack = pack_names.first()?;
9241 let CppTemplateTerm::Node {
9242 children: first_elements,
9243 ..
9244 } = bindings.get(first_pack)?
9245 else {
9246 return None;
9247 };
9248 let pack_len = first_elements.len();
9249 for pack_name in &pack_names {
9250 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
9251 return None;
9252 };
9253 if children.len() != pack_len {
9254 return None;
9255 }
9256 }
9257 for index in 0..pack_len {
9258 let mut element_bindings = bindings.clone();
9259 for pack_name in &pack_names {
9260 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
9261 return None;
9262 };
9263 element_bindings.insert(
9264 pack_name.clone(),
9265 cpp_clone_template_term_iterative(&children[index]),
9266 );
9267 }
9268 substituted.push(CppTemplateExpression {
9269 text: argument.text.clone(),
9270 term: cpp_substitute_template_term(pattern, &element_bindings)?,
9271 });
9272 }
9273 }
9274 Some(substituted)
9275}
9276
9277fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
9278 enum Work<'a> {
9279 Visit(&'a CppTemplateTerm),
9280 Build { kind: String, child_count: usize },
9281 }
9282
9283 let mut work = vec![Work::Visit(term)];
9284 let mut cloned = Vec::new();
9285 while let Some(next) = work.pop() {
9286 match next {
9287 Work::Visit(CppTemplateTerm::Parameter(name)) => {
9288 cloned.push(CppTemplateTerm::Parameter(name.clone()));
9289 }
9290 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
9291 cloned.push(CppTemplateTerm::Atom {
9292 kind: kind.clone(),
9293 text: text.clone(),
9294 });
9295 }
9296 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
9297 work.push(Work::Build {
9298 kind: kind.clone(),
9299 child_count: children.len(),
9300 });
9301 work.extend(children.iter().rev().map(Work::Visit));
9302 }
9303 Work::Build { kind, child_count } => {
9304 let children = cloned.split_off(cloned.len() - child_count);
9305 cloned.push(CppTemplateTerm::Node { kind, children });
9306 }
9307 }
9308 }
9309 cloned
9310 .pop()
9311 .expect("template term traversal emits one root")
9312}
9313
9314fn cpp_clone_template_expression_iterative(
9315 expression: &CppTemplateExpression,
9316) -> CppTemplateExpression {
9317 CppTemplateExpression {
9318 text: expression.text.clone(),
9319 term: cpp_clone_template_term_iterative(&expression.term),
9320 }
9321}
9322
9323pub fn cpp_unify_template_term(
9324 pattern: &CppTemplateTerm,
9325 argument: &CppTemplateTerm,
9326 parameters: &HashSet<&str>,
9327 bindings: &mut HashMap<String, CppTemplateTerm>,
9328) -> bool {
9329 let mut work = vec![(pattern, argument)];
9330 while let Some((pattern, argument)) = work.pop() {
9331 match pattern {
9332 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
9333 if let Some(bound) = bindings.get(name) {
9334 if !cpp_template_terms_equal(bound, argument) {
9335 return false;
9336 }
9337 } else {
9338 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
9339 }
9340 }
9341 CppTemplateTerm::Atom {
9342 kind: pattern_kind,
9343 text: pattern_text,
9344 } => {
9345 if !matches!(
9346 argument,
9347 CppTemplateTerm::Atom { kind, text }
9348 if kind == pattern_kind && text == pattern_text
9349 ) {
9350 return false;
9351 }
9352 }
9353 CppTemplateTerm::Node {
9354 kind: pattern_kind,
9355 children: pattern_children,
9356 } => {
9357 let CppTemplateTerm::Node { kind, children } = argument else {
9358 return false;
9359 };
9360 if kind != pattern_kind || children.len() != pattern_children.len() {
9361 return false;
9362 }
9363 work.extend(pattern_children.iter().zip(children).rev());
9364 }
9365 CppTemplateTerm::Parameter(_) => return false,
9366 }
9367 }
9368 true
9369}
9370
9371fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
9372 let mut work = vec![(left, right)];
9373 while let Some((left, right)) = work.pop() {
9374 match (left, right) {
9375 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
9376 if left != right {
9377 return false;
9378 }
9379 }
9380 (
9381 CppTemplateTerm::Atom {
9382 kind: left_kind,
9383 text: left_text,
9384 },
9385 CppTemplateTerm::Atom {
9386 kind: right_kind,
9387 text: right_text,
9388 },
9389 ) => {
9390 if left_kind != right_kind || left_text != right_text {
9391 return false;
9392 }
9393 }
9394 (
9395 CppTemplateTerm::Node {
9396 kind: left_kind,
9397 children: left_children,
9398 },
9399 CppTemplateTerm::Node {
9400 kind: right_kind,
9401 children: right_children,
9402 },
9403 ) => {
9404 if left_kind != right_kind || left_children.len() != right_children.len() {
9405 return false;
9406 }
9407 work.extend(left_children.iter().zip(right_children).rev());
9408 }
9409 _ => return false,
9410 }
9411 }
9412 true
9413}
9414
9415pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
9416 let mut components = Vec::new();
9417 let mut stack = vec![node];
9418 while let Some(current) = stack.pop() {
9419 match current.kind() {
9420 "identifier"
9421 | "field_identifier"
9422 | "namespace_identifier"
9423 | "type_identifier"
9424 | "operator_name"
9425 | "destructor_name" => components.push(current),
9426 "template_type" | "template_function" => {
9427 stack.push(current.child_by_field_name("name")?);
9428 }
9429 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
9430 stack.push(current.child_by_field_name("name")?);
9431 if let Some(scope) = current.child_by_field_name("scope") {
9432 stack.push(scope);
9433 }
9434 }
9435 "nested_namespace_specifier" => {
9436 for index in (0..current.named_child_count()).rev() {
9437 stack.push(current.named_child(index)?);
9438 }
9439 }
9440 _ => return None,
9441 }
9442 }
9443 Some(components)
9444}
9445
9446pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
9447 node.child_by_field_name("scope").is_none()
9448 && node.child(0).is_some_and(|child| child.kind() == "::")
9449}
9450
9451fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
9452 let mut namespaces = Vec::new();
9453 let mut current = node.parent();
9454 while let Some(parent) = current {
9455 if parent.kind() == "namespace_definition"
9456 && let Some(name) = parent.child_by_field_name("name")
9457 {
9458 let mut components = Vec::new();
9459 append_cpp_name_components(name, source, &mut components)?;
9460 namespaces.push(components);
9461 }
9462 current = parent.parent();
9463 }
9464 namespaces.reverse();
9465 Some(namespaces.into_iter().flatten().collect())
9466}
9467
9468fn indexed_namespace_path_is_recoverable(
9477 lexical_scope: &[String],
9478 indexed_owner_scope: &[String],
9479) -> bool {
9480 if lexical_scope.is_empty() || lexical_scope.len() >= indexed_owner_scope.len() {
9481 return false;
9482 }
9483 let mut indexed = indexed_owner_scope.iter();
9484 lexical_scope
9485 .iter()
9486 .all(|component| indexed.any(|candidate| candidate == component))
9487}
9488
9489pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
9490 let mut current = node.parent();
9491 while let Some(parent) = current {
9492 if parent.kind() == kind {
9493 return true;
9494 }
9495 current = parent.parent();
9496 }
9497 false
9498}
9499
9500pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
9506 loop {
9507 let next = match node.kind() {
9508 "qualified_identifier"
9509 | "scoped_identifier"
9510 | "template_function"
9511 | "template_type" => node.child_by_field_name("name"),
9512 "field_expression" => node.child_by_field_name("field"),
9513 _ => None,
9514 };
9515 let Some(next) = next else {
9516 return node;
9517 };
9518 node = next;
9519 }
9520}
9521
9522pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
9525 while let Some(parent) = node.parent() {
9526 match parent.kind() {
9527 "call_expression" => {
9528 return parent
9529 .child_by_field_name("function")
9530 .or_else(|| parent.named_child(0))
9531 == Some(node);
9532 }
9533 "qualified_identifier"
9534 | "scoped_identifier"
9535 | "template_function"
9536 | "template_type"
9537 | "field_expression" => node = parent,
9538 _ => return false,
9539 }
9540 }
9541 false
9542}
9543
9544pub fn type_reference_hit_node<'tree, T: Clone + Eq + Hash>(
9545 node: Node<'tree>,
9546 file: &ProjectFile,
9547 source: &str,
9548 bindings: &LocalInferenceEngine<T>,
9549) -> Node<'tree> {
9550 if is_call_callee_node(node) {
9551 return function_terminal_node(node);
9552 }
9553 if file.rel_path().extension().is_some_and(|ext| ext == "c") {
9554 return node;
9555 }
9556 let mut current = node;
9557 let declaration = loop {
9558 let Some(parent) = current.parent() else {
9559 return node;
9560 };
9561 if parent.kind() == "declaration" {
9562 break parent;
9563 }
9564 if matches!(
9565 parent.kind(),
9566 "compound_statement" | "function_definition" | "lambda_expression"
9567 ) {
9568 return node;
9569 }
9570 current = parent;
9571 };
9572 let Some(_type_node) = declaration.child_by_field_name("type").filter(|type_node| {
9573 type_node.start_byte() <= node.start_byte() && node.end_byte() <= type_node.end_byte()
9574 }) else {
9575 return node;
9576 };
9577 let mut cursor = declaration.walk();
9578 let constructs_object = declaration.named_children(&mut cursor).any(|child| {
9579 if child.kind() == "init_declarator" {
9580 return child.child_by_field_name("value").is_some()
9581 || first_named_child_of_kind(child, "initializer_list").is_some()
9582 || first_named_child_of_kind(child, "compound_literal_expression").is_some();
9583 }
9584 let declarator = if is_declarator_node(child) {
9585 Some(child)
9586 } else {
9587 None
9588 };
9589 declarator.is_some_and(|declarator| {
9590 declarator.kind() == "function_declarator"
9591 && has_ancestor_kind(declarator, "compound_statement")
9592 && declarator
9593 .child_by_field_name("declarator")
9594 .is_some_and(|name| name.kind() == "identifier")
9595 && declarator
9596 .child_by_field_name("parameters")
9597 .is_some_and(|parameters| {
9598 constructor_parameters_look_like_expressions(parameters, source, bindings)
9599 })
9600 })
9601 });
9602 if constructs_object {
9603 function_terminal_node(node)
9604 } else {
9605 node
9606 }
9607}
9608
9609pub fn normalize_type_text(value: &str) -> String {
9610 strip_tag_type_prefix(
9611 normalize_cpp_whitespace(value)
9612 .trim_start_matches("const ")
9613 .trim_end_matches('*')
9614 .trim_end_matches('&')
9615 .trim(),
9616 )
9617 .to_string()
9618}
9619
9620fn strip_tag_type_prefix(value: &str) -> &str {
9621 let value = value.trim_start_matches("const ");
9622 value
9623 .strip_prefix("struct ")
9624 .or_else(|| value.strip_prefix("class "))
9625 .or_else(|| value.strip_prefix("enum "))
9626 .unwrap_or(value)
9627 .trim()
9628}
9629
9630pub fn normalize_reference_name(value: &str) -> Option<String> {
9631 let normalized = normalize_cpp_reference_text(value);
9632 (!normalized.is_empty()).then_some(normalized)
9633}
9634
9635pub fn normalize_cpp_reference_text(value: &str) -> String {
9636 let mut text = normalize_cpp_whitespace(value)
9637 .trim_start_matches("new ")
9638 .trim()
9639 .to_string();
9640 if let Some(index) = text.find(['(', '{']) {
9641 text.truncate(index);
9642 }
9643 if let Some(index) = text.find('<') {
9644 text.truncate(index);
9645 }
9646 let normalized = text
9647 .trim()
9648 .trim_start_matches("const ")
9649 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
9650 .trim_matches(':')
9651 .trim();
9652 strip_tag_type_prefix(normalized).to_string()
9653}
9654
9655pub fn cpp_name_for(unit: &CodeUnit) -> String {
9656 let short = unit.short_name().replace(['.', '$'], "::");
9657 if unit.package_name().is_empty() {
9658 short
9659 } else {
9660 format!("{}::{}", unit.package_name(), short)
9661 }
9662}
9663
9664fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
9668 let fq = unit.fq();
9669 if fq.is_empty() {
9670 return None;
9671 }
9672 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9673 Some(
9674 fq.segments()
9675 .iter()
9676 .map(|&segment| interner.resolve(segment).0)
9677 .collect::<Vec<_>>()
9678 .join("::"),
9679 )
9680}
9681
9682fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
9683 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
9684 || unit.fq().is_empty() && cpp_name_for(unit) == expected
9685}
9686
9687pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
9696 let fq = unit.fq();
9697 if !fq.is_empty() {
9698 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9699 let scope = fq
9700 .segments()
9701 .iter()
9702 .filter_map(|&segment| {
9703 let (text, kind) = interner.resolve(segment);
9704 matches!(
9705 kind,
9706 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
9707 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9708 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9709 )
9710 .then(|| text.to_string())
9711 })
9712 .collect();
9713 return scope;
9714 }
9715 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9716 brokk_bifrost_core::analyzer::Language::Cpp,
9717 &cpp_name_for(unit),
9718 )
9719}
9720
9721pub fn terminal_name(value: &str) -> &str {
9732 value
9733 .rsplit("::")
9734 .next()
9735 .unwrap_or(value)
9736 .rsplit(['.', '-', '>'])
9737 .next()
9738 .unwrap_or(value)
9739 .trim()
9740}
9741
9742pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
9743 terminal_name(&normalize_cpp_reference_text(value)) == expected
9744}
9745
9746pub fn name_matches_callable(value: &str, expected: &str) -> bool {
9747 name_matches_terminal(value, expected)
9748 || expected.starts_with("operator")
9749 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
9750}
9751
9752pub fn name_mentions(value: &str, expected: &str) -> bool {
9753 normalize_cpp_reference_text(value)
9754 .split("::")
9755 .any(|part| part == expected)
9756}
9757
9758pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
9759 let cpp_name = cpp_name_for(unit);
9760 if reference.contains("::") {
9761 return reference == cpp_name;
9762 }
9763 reference == cpp_name
9764 || terminal_name(reference) == unit.identifier()
9765 && (unit.package_name().is_empty() || reference == unit.identifier())
9766}
9767
9768pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
9769 match kind {
9770 TargetKind::Type
9771 | TargetKind::Constructor
9772 | TargetKind::Method
9773 | TargetKind::MemberField => true,
9774 TargetKind::FreeFunction => unit.is_function(),
9775 TargetKind::GlobalField => unit.is_field(),
9776 TargetKind::Macro => unit.is_macro(),
9777 }
9778}
9779
9780pub fn is_type_alias(unit: &CodeUnit) -> bool {
9781 unit.kind() == CodeUnitType::Field
9782 && unit.signature().is_some_and(|signature| {
9783 signature.starts_with("typedef ") || signature.starts_with("using ")
9784 })
9785}
9786
9787fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
9788 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
9789 let target_name = cpp_name_for(target);
9790 if normalized.contains("::") {
9791 return normalized == target_name;
9792 }
9793 if let Some(namespace) = alias.namespace.as_deref() {
9794 return namespace_prefixes(namespace)
9795 .into_iter()
9796 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
9797 }
9798 target.package_name().is_empty() && normalized == target.identifier()
9799}
9800
9801fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
9802 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
9803 if normalized.contains("::") {
9804 return vec![normalized];
9805 }
9806 alias
9807 .namespace
9808 .as_deref()
9809 .map(namespace_prefixes)
9810 .map(|prefixes| {
9811 prefixes
9812 .into_iter()
9813 .map(|prefix| format!("{prefix}::{normalized}"))
9814 .collect()
9815 })
9816 .unwrap_or_else(|| vec![normalized])
9817}
9818
9819pub fn cpp_function_return_type_text(
9822 analyzer: &CppGraphSource<'_>,
9823 function: &CodeUnit,
9824) -> Option<String> {
9825 let metadata = analyzer.signature_metadata(function);
9826 if !metadata.is_empty() {
9827 let first = metadata.first()?.return_type_text()?;
9828 return metadata
9829 .iter()
9830 .all(|metadata| metadata.return_type_text() == Some(first))
9831 .then(|| first.to_string());
9832 }
9833 let signature = cpp_function_signature_text(analyzer, function)?;
9834 cpp_function_return_type_text_from_signature(&signature)
9835}
9836
9837fn cpp_function_signature_text(
9838 analyzer: &CppGraphSource<'_>,
9839 function: &CodeUnit,
9840) -> Option<String> {
9841 function
9842 .signature()
9843 .filter(|signature| signature.contains(function.identifier()))
9844 .map(str::to_string)
9845 .or_else(|| analyzer.signatures(function).first().cloned())
9846 .or_else(|| analyzer.get_source(function, false))
9847}
9848
9849fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
9850 let open = signature.find('(')?;
9851 let name_at = cpp_function_name_start(signature, open)?;
9852 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
9853 return Some(return_type);
9854 }
9855 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
9856 .split_whitespace()
9857 .filter(|token| {
9858 !matches!(
9859 *token,
9860 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
9861 )
9862 })
9863 .collect::<Vec<_>>()
9864 .join(" ");
9865 let type_text = type_text.trim();
9866 (!type_text.is_empty()).then(|| type_text.to_string())
9867}
9868
9869fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
9870 let before_parameters = &signature[..open];
9871 if let Some(operator_at) = before_parameters.rfind("operator") {
9872 let boundary = operator_at == 0
9873 || before_parameters[..operator_at]
9874 .chars()
9875 .next_back()
9876 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
9877 if boundary {
9878 return Some(operator_at);
9879 }
9880 }
9881 before_parameters
9882 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
9883 .map(|index| index + 1)
9884}
9885
9886fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
9887 let open = signature_from_name.find('(')?;
9888 let mut depth = 0i32;
9889 for (offset, ch) in signature_from_name[open..].char_indices() {
9890 match ch {
9891 '(' => depth += 1,
9892 ')' => {
9893 depth -= 1;
9894 if depth == 0 {
9895 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
9896 let arrow = rest.find("->")?;
9897 let return_type = rest[arrow + 2..].trim_start();
9898 let return_type = return_type
9899 .split(['{', ';'])
9900 .next()
9901 .unwrap_or(return_type)
9902 .trim();
9903 return (!return_type.is_empty()).then(|| return_type.to_string());
9904 }
9905 }
9906 _ => {}
9907 }
9908 }
9909 None
9910}
9911
9912fn cpp_strip_leading_template_clause(text: &str) -> &str {
9915 let trimmed = text.trim_start();
9916 let Some(rest) = trimmed.strip_prefix("template") else {
9917 return text;
9918 };
9919 let rest = rest.trim_start();
9920 if !rest.starts_with('<') {
9921 return text;
9922 }
9923 let mut depth = 0i32;
9924 for (offset, ch) in rest.char_indices() {
9925 match ch {
9926 '<' => depth += 1,
9927 '>' => {
9928 depth -= 1;
9929 if depth == 0 {
9930 return rest[offset + ch.len_utf8()..].trim_start();
9931 }
9932 }
9933 _ => {}
9934 }
9935 }
9936 text
9937}
9938
9939pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
9940 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
9950 namespace
9951 .strip_prefix("anonymous_namespace::")
9952 .unwrap_or(namespace)
9953 .to_string()
9954 })
9955}
9956
9957fn namespace_prefixes(namespace: &str) -> Vec<String> {
9958 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9964 brokk_bifrost_core::analyzer::Language::Cpp,
9965 namespace,
9966 );
9967 let mut prefixes = Vec::new();
9968 while !parts.is_empty() {
9969 prefixes.push(parts.join("::"));
9970 parts.pop();
9971 }
9972 prefixes
9973}
9974
9975fn nearest_namespace_candidates(
9976 candidates: Vec<CodeUnit>,
9977 normalized: &str,
9978 lexical_namespace: Option<&str>,
9979) -> Vec<CodeUnit> {
9980 if normalized.contains("::") {
9981 return candidates;
9982 }
9983 if let Some(namespace) = lexical_namespace {
9984 for prefix in namespace_prefixes(namespace) {
9985 let scoped = candidates
9986 .iter()
9987 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
9988 .cloned()
9989 .collect::<Vec<_>>();
9990 if !scoped.is_empty() {
9991 return scoped;
9992 }
9993 }
9994 }
9995 candidates
9996 .into_iter()
9997 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
9998 .collect()
9999}
10000
10001pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
10002 let mut namespaces = Vec::new();
10003 let mut current = node.parent();
10004 while let Some(parent) = current {
10005 if parent.kind() == "namespace_definition"
10006 && let Some(name) = parent.child_by_field_name("name")
10007 {
10008 let namespace = normalize_cpp_reference_text(node_text(name, source));
10009 if !namespace.is_empty() {
10010 namespaces.push(namespace);
10011 }
10012 }
10013 current = parent.parent();
10014 }
10015 if namespaces.is_empty() {
10016 None
10017 } else {
10018 namespaces.reverse();
10019 Some(namespaces.join("::"))
10020 }
10021}
10022
10023pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
10027 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
10028}
10029
10030fn type_owner_resolution(
10031 analyzer: &CppGraphSource<'_>,
10032 code_unit: &CodeUnit,
10033) -> Option<ResolvedTypeOwner> {
10034 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
10035}
10036
10037fn target_forward_owner_resolution(
10043 analyzer: &CppGraphSource<'_>,
10044 code_unit: &CodeUnit,
10045) -> Option<ResolvedTypeOwner> {
10046 if !code_unit.is_function() {
10047 return None;
10048 }
10049 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
10050 let owner_fqn = code_unit.fq().parent()?.display(interner);
10051 let cpp = analyzer.cpp?;
10052 let mut visible_files = HashSet::default();
10053 collect_include_closure(
10054 analyzer,
10055 cpp.include_target_index(),
10056 code_unit.source(),
10057 &mut visible_files,
10058 None,
10059 );
10060 let mut forward = None;
10061 for candidate in analyzer
10062 .global_usage_definition_index()
10063 .fqn(&owner_fqn)
10064 .into_iter()
10065 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
10066 {
10067 match cpp_class_declaration_strength(analyzer, candidate) {
10068 CppClassDeclarationStrength::Forward if forward.is_none() => {
10069 forward = Some(candidate.clone());
10070 }
10071 CppClassDeclarationStrength::Forward
10072 | CppClassDeclarationStrength::Full
10073 | CppClassDeclarationStrength::Unknown => return None,
10074 }
10075 }
10076 forward.map(|unit| ResolvedTypeOwner {
10077 unit,
10078 is_forward_declaration: true,
10079 })
10080}
10081
10082pub fn precise_parent_of(
10083 analyzer: &CppGraphSource<'_>,
10084 visibility: &VisibilityIndex<'_>,
10085 code_unit: &CodeUnit,
10086) -> Option<CodeUnit> {
10087 visibility.cached_precise_parent_of(analyzer, code_unit)
10088}
10089
10090fn precise_parent_resolution(
10091 analyzer: &CppGraphSource<'_>,
10092 code_unit: &CodeUnit,
10093) -> Option<ResolvedTypeOwner> {
10094 #[cfg(any(test, feature = "test-support"))]
10095 if let Some(cpp) = analyzer.cpp {
10096 cpp.record_cpp_parent_resolution_for_test();
10097 }
10098 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
10099 return Some(ResolvedTypeOwner {
10100 unit,
10101 is_forward_declaration: false,
10102 });
10103 }
10104 let fallback = analyzer.parent_of(code_unit);
10105 let Some(owner_name) = code_unit
10111 .short_name()
10112 .rsplit_once('.')
10113 .map(|(owner, _)| owner)
10114 else {
10115 return fallback.map(|unit| ResolvedTypeOwner {
10116 unit,
10117 is_forward_declaration: false,
10118 });
10119 };
10120 let owner_fqn = if code_unit.package_name().is_empty() {
10121 owner_name.to_string()
10122 } else {
10123 format!("{}.{}", code_unit.package_name(), owner_name)
10124 };
10125 match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10126 DirectOwnerResolution::UniqueFull(owner) => {
10127 return Some(ResolvedTypeOwner {
10128 unit: owner,
10129 is_forward_declaration: false,
10130 });
10131 }
10132 DirectOwnerResolution::Ambiguous => return None,
10133 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
10134 }
10135 match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10136 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
10137 unit: owner,
10138 is_forward_declaration: false,
10139 }),
10140 DirectOwnerResolution::Ambiguous => None,
10141 DirectOwnerResolution::ForwardsOnly(forwards) => {
10142 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10143 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
10144 unit: owner,
10145 is_forward_declaration: false,
10146 }),
10147 FullOwnerResolution::None if forwards.len() == 1 => {
10148 forwards.into_iter().next().map(|unit| ResolvedTypeOwner {
10149 unit,
10150 is_forward_declaration: true,
10151 })
10152 }
10153 FullOwnerResolution::None | FullOwnerResolution::Ambiguous => None,
10154 }
10155 }
10156 DirectOwnerResolution::None => {
10157 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
10158 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
10159 unit: owner,
10160 is_forward_declaration: false,
10161 }),
10162 FullOwnerResolution::Ambiguous => None,
10163 FullOwnerResolution::None => fallback
10164 .filter(|parent| {
10165 parent.source() == code_unit.source()
10166 && parent.short_name() == owner_name
10167 && parent.package_name() == code_unit.package_name()
10168 && (!parent.is_class()
10169 || cpp_class_declaration_strength(analyzer, parent)
10170 == CppClassDeclarationStrength::Full)
10171 })
10172 .map(|unit| ResolvedTypeOwner {
10173 unit,
10174 is_forward_declaration: false,
10175 }),
10176 }
10177 }
10178 }
10179}
10180
10181fn exact_structural_type_parent(
10182 analyzer: &CppGraphSource<'_>,
10183 code_unit: &CodeUnit,
10184) -> Option<CodeUnit> {
10185 if !code_unit.is_function() && !code_unit.is_field() {
10186 return None;
10187 }
10188 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
10190 let parent = cpp.structural_parent_of(code_unit)?;
10191 (!parent.is_module()
10192 && parent.source() == code_unit.source()
10193 && parent.package_name() == code_unit.package_name()
10194 && parent.short_name() == encoded_owner)
10195 .then_some(parent)
10196}
10197
10198fn same_source_owner(
10199 analyzer: &CppGraphSource<'_>,
10200 code_unit: &CodeUnit,
10201 owner_fqn: &str,
10202 owner_name: &str,
10203) -> DirectOwnerResolution {
10204 let candidates = analyzer
10205 .global_usage_definition_index()
10206 .fqn(owner_fqn)
10207 .into_iter()
10208 .filter(|candidate| {
10209 candidate.is_class()
10210 && candidate.source() == code_unit.source()
10211 && candidate.short_name() == owner_name
10212 && candidate.package_name() == code_unit.package_name()
10213 })
10214 .collect::<Vec<_>>();
10215 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10216 classify_direct_owner_candidates(analyzer, candidates.into_iter())
10217}
10218
10219fn visible_full_cpp_owner(
10220 analyzer: &CppGraphSource<'_>,
10221 code_unit: &CodeUnit,
10222 owner_fqn: &str,
10223 owner_name: &str,
10224) -> FullOwnerResolution {
10225 let Some(cpp) = analyzer.cpp else {
10226 return FullOwnerResolution::None;
10227 };
10228 let mut visible_files = HashSet::default();
10229 collect_include_closure(
10230 analyzer,
10231 cpp.include_target_index(),
10232 code_unit.source(),
10233 &mut visible_files,
10234 None,
10235 );
10236 let candidates = analyzer
10237 .global_usage_definition_index()
10238 .fqn(owner_fqn)
10239 .into_iter()
10240 .filter(|candidate| {
10241 candidate.is_class()
10242 && candidate.short_name() == owner_name
10243 && candidate.package_name() == code_unit.package_name()
10244 && visible_files.contains(candidate.source())
10245 })
10246 .collect::<Vec<_>>();
10247 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10248 let mut full_definition = None;
10249 for candidate in candidates {
10250 match cpp_class_declaration_strength(analyzer, candidate) {
10251 CppClassDeclarationStrength::Full if full_definition.is_some() => {
10252 return FullOwnerResolution::Ambiguous;
10253 }
10254 CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
10255 CppClassDeclarationStrength::Forward => {}
10256 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
10257 }
10258 }
10259 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
10260}
10261
10262pub enum DirectOwnerResolution {
10263 None,
10264 ForwardsOnly(Vec<CodeUnit>),
10265 UniqueFull(CodeUnit),
10266 Ambiguous,
10267}
10268
10269enum FullOwnerResolution {
10270 None,
10271 Unique(CodeUnit),
10272 Ambiguous,
10273}
10274
10275#[derive(Clone, Copy, PartialEq, Eq)]
10276pub enum CppClassDeclarationStrength {
10277 Full,
10278 Forward,
10279 Unknown,
10280}
10281
10282fn directly_included_owner(
10283 analyzer: &CppGraphSource<'_>,
10284 code_unit: &CodeUnit,
10285 owner_fqn: &str,
10286 owner_name: &str,
10287) -> DirectOwnerResolution {
10288 let Some(cpp) = analyzer.cpp else {
10289 return DirectOwnerResolution::None;
10290 };
10291 let imports = analyzer.import_statements(code_unit.source());
10292 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
10293 .into_iter()
10294 .flat_map(|include| {
10295 resolve_include_targets_with_index(
10296 code_unit.source(),
10297 &include,
10298 cpp.include_target_index(),
10299 )
10300 })
10301 .collect();
10302 let candidates = analyzer
10303 .global_usage_definition_index()
10304 .fqn(owner_fqn)
10305 .into_iter()
10306 .filter(|candidate| {
10307 candidate.is_class()
10308 && candidate.short_name() == owner_name
10309 && candidate.package_name() == code_unit.package_name()
10310 && direct_includes.contains(candidate.source())
10311 })
10312 .collect::<Vec<_>>();
10313 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
10314 classify_direct_owner_candidates(analyzer, candidates.into_iter())
10315}
10316
10317fn prefer_member_declaring_owners<'a>(
10318 analyzer: &CppGraphSource<'_>,
10319 member: &CodeUnit,
10320 candidates: Vec<&'a CodeUnit>,
10321) -> Vec<&'a CodeUnit> {
10322 let matching = candidates
10323 .iter()
10324 .copied()
10325 .filter(|owner| owner_declares_member(analyzer, owner, member))
10326 .collect::<Vec<_>>();
10327 if matching.is_empty() {
10328 candidates
10329 } else {
10330 matching
10331 }
10332}
10333
10334fn owner_declares_member(
10335 analyzer: &CppGraphSource<'_>,
10336 owner: &CodeUnit,
10337 member: &CodeUnit,
10338) -> bool {
10339 analyzer.direct_children(owner).into_iter().any(|child| {
10340 child.kind() == member.kind()
10341 && child.identifier() == member.identifier()
10342 && child.signature() == member.signature()
10343 })
10344}
10345
10346fn classify_direct_owner_candidates<'a>(
10347 analyzer: &CppGraphSource<'_>,
10348 candidates: impl Iterator<Item = &'a CodeUnit>,
10349) -> DirectOwnerResolution {
10350 collapse_owner_candidates(candidates.map(|candidate| {
10351 (
10352 candidate.clone(),
10353 cpp_class_declaration_strength(analyzer, candidate),
10354 )
10355 }))
10356}
10357
10358pub fn collapse_owner_candidates(
10359 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
10360) -> DirectOwnerResolution {
10361 let mut full_definition = None;
10362 let mut forwards = Vec::new();
10363 for (candidate, strength) in candidates {
10364 match strength {
10365 CppClassDeclarationStrength::Full if full_definition.is_some() => {
10366 return DirectOwnerResolution::Ambiguous;
10367 }
10368 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
10369 CppClassDeclarationStrength::Forward => forwards.push(candidate),
10370 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
10371 }
10372 }
10373 if let Some(owner) = full_definition {
10374 DirectOwnerResolution::UniqueFull(owner)
10375 } else if !forwards.is_empty() {
10376 DirectOwnerResolution::ForwardsOnly(forwards)
10377 } else {
10378 DirectOwnerResolution::None
10379 }
10380}
10381
10382pub fn cpp_class_declaration_strength(
10383 analyzer: &CppGraphSource<'_>,
10384 candidate: &CodeUnit,
10385) -> CppClassDeclarationStrength {
10386 if let Some(prepared) = analyzer
10387 .cpp
10388 .and_then(|cpp| cpp.prepared_syntax(candidate.source()))
10389 {
10390 return cpp_class_declaration_strength_in_tree(
10391 analyzer,
10392 candidate,
10393 prepared.source(),
10394 prepared.tree().root_node(),
10395 );
10396 }
10397 let Some(source) = analyzer.indexed_source(candidate.source()) else {
10398 return CppClassDeclarationStrength::Unknown;
10399 };
10400 #[cfg(any(test, feature = "test-support"))]
10401 if let Some(cpp) = analyzer.cpp {
10402 cpp.record_cpp_class_strength_parse_for_test();
10403 }
10404 let mut parser = Parser::new();
10405 if parser
10406 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10407 .is_err()
10408 {
10409 return CppClassDeclarationStrength::Unknown;
10410 }
10411 let Some(tree) = parser.parse(&source, None) else {
10412 return CppClassDeclarationStrength::Unknown;
10413 };
10414 cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
10415}
10416
10417fn cpp_class_declaration_strength_in_tree(
10418 analyzer: &CppGraphSource<'_>,
10419 candidate: &CodeUnit,
10420 source: &str,
10421 root: Node<'_>,
10422) -> CppClassDeclarationStrength {
10423 let ranges = analyzer.ranges(candidate);
10424 let mut saw_forward = false;
10425 for range in ranges {
10426 let mut stack = vec![root];
10427 while let Some(node) = stack.pop() {
10428 if node.start_byte() > range.start_byte || node.end_byte() < range.end_byte {
10429 continue;
10430 }
10431 if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
10432 if matches!(
10433 node.kind(),
10434 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10435 ) {
10436 if cpp_class_node_has_body(node) {
10437 return CppClassDeclarationStrength::Full;
10438 }
10439 saw_forward = true;
10440 } else if let Some(has_body) =
10441 recovered_exported_class_has_body(node, source, candidate.identifier())
10442 {
10443 if has_body {
10444 return CppClassDeclarationStrength::Full;
10445 }
10446 saw_forward = true;
10447 }
10448 }
10449 let mut cursor = node.walk();
10450 stack.extend(node.named_children(&mut cursor));
10451 }
10452 }
10453 if saw_forward {
10454 CppClassDeclarationStrength::Forward
10455 } else {
10456 CppClassDeclarationStrength::Unknown
10457 }
10458}
10459
10460fn cpp_class_node_has_body(node: Node<'_>) -> bool {
10461 node.child_by_field_name("body").is_some() || {
10462 let mut cursor = node.walk();
10463 node.named_children(&mut cursor).any(|child| {
10464 matches!(
10465 child.kind(),
10466 "declaration_list" | "field_declaration_list" | "enumerator_list"
10467 )
10468 })
10469 }
10470}
10471
10472pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
10473 let owner_name = code_unit
10478 .short_name()
10479 .rsplit_once('.')
10480 .map(|(owner, _)| owner)?;
10481 let owner_fqn = if code_unit.package_name().is_empty() {
10482 owner_name.to_string()
10483 } else {
10484 format!("{}.{}", code_unit.package_name(), owner_name)
10485 };
10486 ctx.analyzer
10487 .global_usage_definition_index()
10488 .fqn(&owner_fqn)
10489 .into_iter()
10490 .find(|candidate| {
10491 candidate.is_class()
10492 && ctx.visibility.is_visible(ctx.file, candidate)
10493 && candidate.short_name() == owner_name
10494 && candidate.package_name() == code_unit.package_name()
10495 })
10496 .cloned()
10497}
10498
10499pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
10500 left.kind() == right.kind()
10501 && left.fq_name() == right.fq_name()
10502 && left.signature() == right.signature()
10503 && left.source() == right.source()
10504}
10505
10506pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
10507 same_symbol(left, right) || same_logical_symbol(left, right)
10508}
10509
10510pub fn same_visible_global_field_symbol(
10511 analyzer: &CppGraphSource<'_>,
10512 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
10513 left: &CodeUnit,
10514 right: &CodeUnit,
10515) -> bool {
10516 if same_symbol(left, right) {
10517 return true;
10518 }
10519 if !same_logical_symbol(left, right) {
10520 return false;
10521 }
10522 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
10523 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
10524 {
10525 left.source() == right.source()
10526 } else {
10527 true
10528 }
10529}
10530
10531fn cpp_global_field_has_internal_linkage_cached(
10532 analyzer: &CppGraphSource<'_>,
10533 cache: &mut HashMap<CodeUnit, bool>,
10534 candidate: &CodeUnit,
10535) -> bool {
10536 if let Some(internal) = cache.get(candidate) {
10537 return *internal;
10538 }
10539 #[cfg(any(test, feature = "test-support"))]
10540 note_cpp_global_field_internal_linkage_classification_for_test();
10541 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
10542 cache.insert(candidate.clone(), internal);
10543 internal
10544}
10545
10546#[cfg(any(test, feature = "test-support"))]
10547thread_local! {
10548 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
10549}
10550
10551#[cfg(any(test, feature = "test-support"))]
10552fn note_cpp_global_field_internal_linkage_classification_for_test() {
10553 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
10554 count.set(count.get() + 1);
10555 });
10556}
10557
10558#[cfg(any(test, feature = "test-support"))]
10559pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
10560 body: impl FnOnce() -> T,
10561) -> (T, usize) {
10562 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
10563 count.set(0);
10564 let result = body();
10565 let observed = count.get();
10566 count.set(0);
10567 (result, observed)
10568 })
10569}
10570
10571pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
10572 left.kind() == right.kind()
10573 && left.fq_name() == right.fq_name()
10574 && left.signature() == right.signature()
10575}
10576
10577pub fn cpp_global_field_has_internal_linkage(
10578 analyzer: &CppGraphSource<'_>,
10579 candidate: &CodeUnit,
10580) -> bool {
10581 if !candidate.is_field() || candidate.short_name().contains('.') {
10582 return false;
10583 }
10584 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
10585 return false;
10586 };
10587 match local_linkage {
10588 CppFieldLinkage::Internal => true,
10589 CppFieldLinkage::External => false,
10590 CppFieldLinkage::InternalUnlessExternalPeer => {
10591 !cpp_global_field_linkage_peers(analyzer, candidate)
10592 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
10593 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
10594 }
10595 }
10596}
10597
10598fn cpp_global_field_linkage_peers<'a>(
10599 analyzer: &CppGraphSource<'a>,
10600 candidate: &'a CodeUnit,
10601) -> impl Iterator<Item = &'a CodeUnit> + 'a {
10602 let fq_name = candidate.fq_name();
10606 analyzer
10607 .global_usage_definition_index()
10608 .fqn(&fq_name)
10609 .into_iter()
10610 .filter(move |peer| {
10611 if *peer == candidate {
10612 return false;
10613 }
10614 #[cfg(any(test, feature = "test-support"))]
10615 note_cpp_global_field_linkage_peer_inspection_for_test();
10616 same_logical_symbol(peer, candidate)
10617 })
10618}
10619
10620#[cfg(any(test, feature = "test-support"))]
10621thread_local! {
10622 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
10623}
10624
10625#[cfg(any(test, feature = "test-support"))]
10626fn note_cpp_global_field_linkage_peer_inspection_for_test() {
10627 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
10628 count.set(count.get() + 1);
10629 });
10630}
10631
10632#[cfg(any(test, feature = "test-support"))]
10633pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
10634 body: impl FnOnce() -> T,
10635) -> (T, usize) {
10636 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
10637 count.set(0);
10638 let result = body();
10639 let observed = count.get();
10640 count.set(0);
10641 (result, observed)
10642 })
10643}
10644
10645fn cpp_global_field_declaration_linkage(
10646 analyzer: &CppGraphSource<'_>,
10647 candidate: &CodeUnit,
10648) -> Option<CppFieldLinkage> {
10649 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
10650 return Some(linkage);
10651 }
10652 let cpp = analyzer.cpp?;
10653 if let Some(prepared) = cpp.prepared_syntax(candidate.source()) {
10654 return cpp_global_field_declaration_linkage_in_tree(
10655 analyzer,
10656 candidate,
10657 prepared.source(),
10658 prepared.tree().root_node(),
10659 );
10660 }
10661 let source = analyzer.indexed_source(candidate.source())?;
10662 let mut parser = Parser::new();
10663 if parser
10664 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10665 .is_err()
10666 {
10667 return None;
10668 }
10669 let tree = parser.parse(&source, None)?;
10670 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
10671}
10672
10673fn cpp_global_field_declaration_linkage_in_tree(
10674 analyzer: &CppGraphSource<'_>,
10675 candidate: &CodeUnit,
10676 source: &str,
10677 root: Node<'_>,
10678) -> Option<CppFieldLinkage> {
10679 analyzer.ranges(candidate).iter().find_map(|range| {
10680 node_for_exact_range(root, range)
10681 .and_then(enclosing_cpp_field_declaration)
10682 .map(|declaration| cpp_field_declaration_linkage(declaration, source))
10683 })
10684}
10685
10686fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
10687 loop {
10688 if matches!(node.kind(), "declaration" | "field_declaration") {
10689 return Some(node);
10690 }
10691 node = node.parent()?;
10692 }
10693}
10694
10695#[cfg(test)]
10696mod tests {
10697 use super::*;
10698
10699 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
10700 let mut parser = Parser::new();
10701 parser
10702 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10703 .expect("C++ grammar");
10704 let tree = parser.parse(source, None).expect("C++ fixture tree");
10705 let mut stack = vec![tree.root_node()];
10706 while let Some(node) = stack.pop() {
10707 if node.kind() == "enum_specifier" {
10708 return flattened_macro_namespace_components(node, source);
10709 }
10710 let mut cursor = node.walk();
10711 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
10712 stack.extend(children.into_iter().rev());
10713 }
10714 None
10715 }
10716
10717 #[test]
10718 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
10719 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
10720namespace detail
10721{
10722enum class value_t { null };
10723}
10724NLOHMANN_JSON_NAMESPACE_END
10725NLOHMANN_JSON_NAMESPACE_BEGIN
10726namespace next
10727{
10728struct next_type {};
10729}
10730NLOHMANN_JSON_NAMESPACE_END
10731"#;
10732 assert_eq!(
10733 first_enum_flattened_namespace(complete),
10734 Some(vec!["detail".to_string()])
10735 );
10736
10737 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
10738 assert_eq!(
10739 first_enum_flattened_namespace(&stale_end),
10740 Some(vec!["detail".to_string()]),
10741 "a stale end marker before the begin marker must not replace the intended namespace"
10742 );
10743
10744 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
10745namespace detail
10746{
10747enum class value_t { null };
10748}
10749struct next_type {};
10750"#;
10751 assert_eq!(first_enum_flattened_namespace(incomplete), None);
10752 }
10753}