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