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