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