1use crate::call_match::{
2 CppArgType, cpp_signature_param_types, cpp_split_top_level_commas, normalize_cpp_type_name,
3};
4use crate::compile_context::CppCompileContext;
5#[cfg(test)]
6use crate::declarations::cpp_displaced_preprocessor_terminator;
7use crate::declarations::{
8 CppComparableNode, CppComparableParameter, CppComparableSlot, cpp_callable_identity_suffix,
9 cpp_comparable_parameter_shapes, cpp_declarator_adds_indirection,
10 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_field_declaration_linkage,
11 cpp_function_declarator_at, cpp_template_term, node_text, normalize_cpp_whitespace,
12 recovered_exported_class_has_body, recovered_fragmented_plain_class_has_body,
13};
14use crate::graph::CppGraphSource;
15use crate::graph::extractor::ScanCtx;
16use crate::graph_support::CppSource;
17use crate::imports::{
18 IncludeTargetIndex, include_paths as cpp_include_paths, resolve_include_targets_with_index,
19};
20use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
21use brokk_bifrost_core::analyzer::model::{
22 CallableArity, CodeUnitType, CppFieldLinkage, CppTemplateExpression, CppTemplateMetadata,
23 CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect, StructuredTypeName,
24};
25use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
26use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
27use brokk_bifrost_core::analyzer::query_token::QueryToken;
28use brokk_bifrost_core::analyzer::tree_walk::{ParentIndex, node_for_exact_range};
29use brokk_bifrost_core::analyzer::usages::common::same_node;
30use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
31use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
32use brokk_bifrost_core::cancellation::CancellationToken;
33use brokk_bifrost_core::hash::{HashMap, HashSet};
34use std::borrow::Cow;
35#[cfg(any(test, feature = "test-support"))]
36use std::cell::Cell;
37use std::cell::OnceCell;
38use std::cmp::Ordering as CmpOrdering;
39use std::collections::BTreeSet;
40use std::hash::Hash;
41#[cfg(any(test, feature = "test-support"))]
42use std::sync::atomic::{AtomicUsize, Ordering};
43use std::sync::{Arc, Mutex, OnceLock, RwLock};
44use std::thread::ThreadId;
45use tree_sitter::{Node, Parser, Tree};
46
47#[derive(Clone, Copy, PartialEq, Eq)]
48pub enum TargetKind {
49 Type,
50 Constructor,
51 FreeFunction,
52 Method,
53 GlobalField,
54 MemberField,
55 Macro,
56}
57
58pub enum LexicalTypeResolution {
59 Resolved {
60 unit: CodeUnit,
61 components: Vec<String>,
62 candidates: Vec<CodeUnit>,
63 },
64 Ambiguous,
65 Missing,
66}
67
68#[derive(Clone, Copy)]
69enum TypeCandidateResolution<'a> {
70 Canonical,
71 PreserveAlias,
72 PreserveTarget(&'a CodeUnit),
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
84enum TypeCandidateFailure {
85 Ambiguous,
86 Unresolvable,
87}
88
89impl TypeCandidateFailure {
90 fn lexical_resolution(self) -> LexicalTypeResolution {
91 match self {
92 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
93 Self::Unresolvable => LexicalTypeResolution::Missing,
94 }
95 }
96}
97
98pub enum LexicalCallableValueResolution {
99 Type(CodeUnit),
100 FreeFunction(CodeUnit),
101 Ambiguous,
102 Missing,
103}
104
105pub enum UsingEnumMemberResolution {
106 Resolved { owner: CodeUnit, member: CodeUnit },
107 Ambiguous,
108 Missing,
109}
110
111pub enum NamespaceValueResolution {
112 Resolved,
113 Ambiguous,
114 Missing,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
118pub enum OrdinaryMacroReferenceResolution {
119 Resolved(CodeUnit),
120 Ambiguous,
121 Missing,
122}
123
124#[derive(Clone, Debug, PartialEq, Eq)]
125pub enum RecoveredCReferenceRanges {
126 Complete(Vec<Range>),
127 LimitExceeded,
128}
129
130pub fn resolve_namespace_value(
131 analyzer: &CppGraphSource<'_>,
132 visibility: &VisibilityIndex<'_>,
133 file: &ProjectFile,
134 namespace: &str,
135 name: &str,
136 before_byte: usize,
137) -> NamespaceValueResolution {
138 let mut matches = Vec::new();
139 for candidate in visibility.visible_identifier_candidates(file, name) {
140 if type_owner_of(analyzer, candidate).is_some()
141 || candidate.package_name() != namespace
142 || (candidate.source() == file
143 && !analyzer
144 .ranges(candidate)
145 .iter()
146 .any(|range| range.start_byte < before_byte))
147 || matches
148 .iter()
149 .any(|existing| same_visible_symbol(existing, candidate))
150 {
151 continue;
152 }
153 matches.push(candidate.clone());
154 if matches.len() > 1 {
155 return NamespaceValueResolution::Ambiguous;
156 }
157 }
158 matches
159 .pop()
160 .map(|_| NamespaceValueResolution::Resolved)
161 .unwrap_or(NamespaceValueResolution::Missing)
162}
163
164pub(crate) struct ScopedUsingEnumOwners {
165 scopes: Vec<Vec<CodeUnit>>,
166}
167
168pub(crate) struct SemanticUsingEnumOwners {
173 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
174 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
175}
176
177pub(crate) enum SemanticUsingEnumMemberResolution {
178 Class(UsingEnumMemberResolution),
179 Namespace(UsingEnumMemberResolution),
180 Missing,
181}
182
183impl SemanticUsingEnumOwners {
184 pub(crate) fn new() -> Self {
185 Self {
186 class_imports: HashMap::default(),
187 namespace_imports: HashMap::default(),
188 }
189 }
190
191 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
192 let imports = self.class_imports.entry(class).or_default();
193 if !imports
194 .iter()
195 .any(|existing| same_visible_symbol(existing, &enum_owner))
196 {
197 imports.push(enum_owner);
198 }
199 }
200
201 pub fn import_namespace(
202 &mut self,
203 namespace: Vec<String>,
204 declaration_byte: usize,
205 enum_owner: CodeUnit,
206 ) {
207 let imports = self.namespace_imports.entry(namespace).or_default();
208 if !imports
209 .iter()
210 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
211 {
212 imports.push((declaration_byte, enum_owner));
213 }
214 }
215
216 pub fn resolve_member(
217 &self,
218 visibility: &VisibilityIndex<'_>,
219 file: &ProjectFile,
220 class: Option<&CodeUnit>,
221 namespace: &[String],
222 before_byte: usize,
223 name: &str,
224 ) -> SemanticUsingEnumMemberResolution {
225 if let Some(class) = class
226 && let Some((_, imports)) = self
227 .class_imports
228 .iter()
229 .find(|(owner, _)| same_visible_symbol(owner, class))
230 {
231 let resolution =
232 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
233 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
234 return SemanticUsingEnumMemberResolution::Class(resolution);
235 }
236 }
237 for prefix_len in (0..=namespace.len()).rev() {
238 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
239 continue;
240 };
241 let owners = imports
242 .iter()
243 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
244 .map(|(_, owner)| owner);
245 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
246 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
247 return SemanticUsingEnumMemberResolution::Namespace(resolution);
248 }
249 }
250 SemanticUsingEnumMemberResolution::Missing
251 }
252}
253
254fn resolve_using_enum_member_for_owners<'a>(
255 visibility: &VisibilityIndex<'_>,
256 file: &ProjectFile,
257 owners: impl IntoIterator<Item = &'a CodeUnit>,
258 name: &str,
259) -> UsingEnumMemberResolution {
260 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
261 for owner in owners {
262 for member in visibility.visible_members_for_owner_name(file, owner, name) {
263 if !member.is_field()
264 || matches.iter().any(|(existing_owner, existing_member)| {
265 same_visible_symbol(existing_owner, owner)
266 && same_visible_symbol(existing_member, member)
267 })
268 {
269 continue;
270 }
271 matches.push((owner.clone(), member.clone()));
272 }
273 }
274 match matches.len() {
275 0 => UsingEnumMemberResolution::Missing,
276 1 => {
277 let (owner, member) = matches.pop().expect("one using-enum match");
278 UsingEnumMemberResolution::Resolved { owner, member }
279 }
280 _ => UsingEnumMemberResolution::Ambiguous,
281 }
282}
283
284impl ScopedUsingEnumOwners {
285 pub(crate) fn new() -> Self {
286 Self {
287 scopes: vec![Vec::new()],
288 }
289 }
290
291 pub fn enter_scope(&mut self) {
292 self.scopes.push(Vec::new());
293 }
294
295 pub fn exit_scope(&mut self) {
296 if self.scopes.len() > 1 {
297 self.scopes.pop();
298 }
299 }
300
301 pub fn import(&mut self, owner: CodeUnit) {
302 let scope = self
303 .scopes
304 .last_mut()
305 .expect("using-enum scope stack is never empty");
306 if !scope
307 .iter()
308 .any(|existing| same_visible_symbol(existing, &owner))
309 {
310 scope.push(owner);
311 }
312 }
313
314 pub fn resolve_member(
315 &self,
316 visibility: &VisibilityIndex<'_>,
317 file: &ProjectFile,
318 name: &str,
319 ) -> UsingEnumMemberResolution {
320 for scope in self.scopes.iter().rev() {
321 let resolution =
322 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
323 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
324 return resolution;
325 }
326 }
327 UsingEnumMemberResolution::Missing
328 }
329}
330
331#[derive(Clone)]
332pub struct TargetSpec {
333 pub target: CodeUnit,
334 pub kind: TargetKind,
335 pub owner: Option<CodeUnit>,
336 pub member_name: String,
337 pub callable_arity: Option<CallableArity>,
338 pub activated_callable_arities: Vec<ActivatedCallableArity>,
339 pub param_types: Option<Vec<String>>,
340 pub enum_owner_kind: EnumOwnerKind,
341 pub owner_is_forward_declaration: bool,
342}
343
344#[derive(Clone, Copy)]
345pub struct ActivatedCallableArity {
346 pub activation_byte: usize,
347 pub arity: CallableArity,
348}
349
350#[derive(Debug, PartialEq, Eq, Hash)]
351pub struct TypeScanKey {
352 target: LogicalSymbolKey,
353 member_name: String,
354}
355
356#[derive(Clone, Debug, PartialEq, Eq, Hash)]
357struct LogicalSymbolKey {
358 kind: CodeUnitType,
359 fq_name: String,
360 signature: Option<String>,
361}
362
363struct ResolvedTypeOwner {
364 unit: CodeUnit,
365 is_forward_declaration: bool,
366}
367
368#[derive(Clone, Copy, PartialEq, Eq)]
369pub enum EnumOwnerKind {
370 Scoped,
371 Unscoped,
372 NonEnum,
373}
374
375impl TargetSpec {
376 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
377 (self.kind == TargetKind::Type).then(|| TypeScanKey {
378 target: logical_symbol_key(&self.target),
379 member_name: self.member_name.clone(),
380 })
381 }
382
383 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
384 if target.is_class() {
385 return Some(Self::new(
386 target.clone(),
387 TargetKind::Type,
388 Some(target.clone()),
389 target.identifier().to_string(),
390 None,
391 None,
392 ));
393 }
394
395 if target.is_field() {
396 let owner = type_owner_of(analyzer, target);
402 let kind = if owner.is_some() {
403 TargetKind::MemberField
404 } else {
405 TargetKind::GlobalField
406 };
407 let enum_owner_kind = owner
408 .as_ref()
409 .map(|owner| classify_enum_owner(analyzer, owner))
410 .unwrap_or(EnumOwnerKind::NonEnum);
411 let mut spec = Self::new(
412 target.clone(),
413 kind,
414 owner,
415 target.identifier().to_string(),
416 None,
417 None,
418 );
419 spec.enum_owner_kind = enum_owner_kind;
420 return Some(spec);
421 }
422
423 if target.is_function() {
424 let owner_resolution = target_type_owner_resolution(analyzer, target);
427 let owner_is_forward_declaration = owner_resolution
428 .as_ref()
429 .is_some_and(|owner| owner.is_forward_declaration);
430 let owner = owner_resolution.map(|owner| owner.unit);
431 let kind = if owner.as_ref().is_some_and(|owner| {
432 target.identifier() == owner.identifier()
433 || analyzer
434 .cpp
435 .and_then(|cpp| cpp.template_metadata(owner))
436 .is_some_and(|metadata| metadata.primary_name == target.identifier())
437 }) {
438 TargetKind::Constructor
439 } else if owner.is_some() {
440 TargetKind::Method
441 } else {
442 TargetKind::FreeFunction
443 };
444 let mut spec = Self::new(
445 target.clone(),
446 kind,
447 owner,
448 target.identifier().to_string(),
449 Some(cpp_callable_arity(analyzer, target)),
450 cpp_callable_parameter_types(analyzer, target),
451 );
452 spec.owner_is_forward_declaration = owner_is_forward_declaration;
453 return Some(spec);
454 }
455
456 if target.is_macro() {
457 return Some(Self::new(
458 target.clone(),
459 TargetKind::Macro,
460 None,
461 target.identifier().to_string(),
462 None,
463 None,
464 ));
465 }
466
467 None
468 }
469
470 pub fn with_visible_callable_arities<'a>(
471 &'a self,
472 analyzer: &CppGraphSource<'_>,
473 cpp: &dyn CppSource,
474 visibility: &VisibilityIndex<'_>,
475 file: &ProjectFile,
476 prepared: &PreparedSyntaxTree,
477 ) -> Cow<'a, Self> {
478 let macro_parameter_arity =
479 visibility.callable_parameter_macro_arity(&self.target, self.target.signature());
480 let activated_callable_arities =
481 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
482 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
483 return Cow::Borrowed(self);
484 }
485 let mut effective = self.clone();
486 if let Some(macro_parameter_arity) = macro_parameter_arity {
487 effective.callable_arity = Some(macro_parameter_arity);
488 }
489 effective.activated_callable_arities = activated_callable_arities;
490 Cow::Owned(effective)
491 }
492
493 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
494 let base = self.callable_arity?;
495 Some(
496 self.activated_callable_arities
497 .iter()
498 .filter(|candidate| candidate.activation_byte <= byte)
499 .fold(base, |arity, candidate| {
500 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
501 }),
502 )
503 }
504
505 pub fn new(
506 target: CodeUnit,
507 kind: TargetKind,
508 owner: Option<CodeUnit>,
509 member_name: String,
510 callable_arity: Option<CallableArity>,
511 param_types: Option<Vec<String>>,
512 ) -> Self {
513 Self {
514 target,
515 kind,
516 owner,
517 member_name,
518 callable_arity,
519 activated_callable_arities: Vec::new(),
520 param_types,
521 enum_owner_kind: EnumOwnerKind::NonEnum,
522 owner_is_forward_declaration: false,
523 }
524 }
525}
526
527fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
528 LogicalSymbolKey {
529 kind: unit.kind(),
530 fq_name: unit.fq_name(),
531 signature: unit.signature().map(str::to_string),
532 }
533}
534
535fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
536 let classify = |source: &str| {
537 let source = source.trim_start();
538 if source.starts_with("enum class ") || source.starts_with("enum struct ") {
539 Some(EnumOwnerKind::Scoped)
540 } else if source.starts_with("enum ") {
541 Some(EnumOwnerKind::Unscoped)
542 } else {
543 None
544 }
545 };
546 owner
547 .signature()
548 .and_then(classify)
549 .or_else(|| {
550 analyzer
551 .get_source(owner, false)
552 .as_deref()
553 .and_then(classify)
554 })
555 .unwrap_or(EnumOwnerKind::NonEnum)
556}
557
558#[derive(Clone, PartialEq, Eq, Hash)]
559pub struct CppScanBinding {
560 pub unit: Option<CodeUnit>,
561 pub type_name: Option<String>,
562 pub indirection: i32,
563}
564
565impl CppScanBinding {
566 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
567 Self {
568 type_name: Some(cpp_name_for(&unit)),
569 unit: Some(unit),
570 indirection,
571 }
572 }
573
574 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
575 Self {
576 type_name: Some(type_name),
577 unit,
578 indirection,
579 }
580 }
581
582 pub fn as_arg_type(&self) -> Option<CppArgType> {
583 let name = self
584 .type_name
585 .clone()
586 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
587 Some(CppArgType {
588 name,
589 unit: self.unit.clone(),
590 indirection: self.indirection,
591 pointee_const: false,
592 })
593 }
594}
595
596type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
597type VisibleParserAliasTargetNamesCell = Arc<OnceLock<HashMap<String, HashSet<String>>>>;
598pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
599pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
600type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
601pub type MacroEnvironmentCursorCell = Arc<Mutex<MacroEnvironmentCursor>>;
602type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
603type MacroLocalBindingTemplateCache =
604 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
605
606#[derive(Clone, Default)]
607pub struct MacroEnvironment {
608 bindings: HashMap<String, MacroBinding>,
609 known_undefined_names: HashSet<String>,
610 build_proven_defines: HashSet<String>,
615 unknown_names: bool,
616 applied_pragma_once_files: HashSet<ProjectFile>,
617 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
618}
619
620#[derive(Default)]
621pub struct MacroEnvironmentCursor {
622 frontier: usize,
623 environment: Arc<MacroEnvironment>,
624}
625
626impl MacroEnvironment {
627 fn binding(&self, name: &str) -> Option<&MacroBinding> {
628 self.bindings.get(name)
629 }
630
631 fn may_bind(&self, name: &str) -> bool {
632 self.bindings.contains_key(name) || self.unknown_names
633 }
634
635 fn insert(&mut self, name: String, binding: MacroBinding) {
636 self.known_undefined_names.remove(&name);
637 self.bindings.insert(name, binding);
638 }
639
640 fn remove(&mut self, name: &str) {
641 self.bindings.remove(name);
642 self.known_undefined_names.insert(name.to_string());
643 }
644
645 fn remove_known_undefined(&mut self, name: &str) {
646 self.known_undefined_names.remove(name);
647 }
648
649 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
650 for binding in self.bindings.values_mut() {
651 *binding = MacroBinding::uncertain_from(binding, source, byte);
652 }
653 self.known_undefined_names.clear();
654 self.build_proven_defines.clear();
659 self.unknown_names = true;
660 }
661
662 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
663 guards.iter().all(|guard| self.guard_may_hold(guard))
664 }
665
666 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
667 let Some(expression) = guard.as_boolean_expression() else {
668 return true;
669 };
670 self.boolean_guard_may_hold(&expression)
671 }
672
673 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
674 match expression {
675 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
676 BooleanGuardExpression::Undefined(name) => {
677 self.bindings
678 .get(name)
679 .is_none_or(|binding| !binding.is_exact())
680 && (!self.build_proven_defines.contains(name)
681 || self.known_undefined_names.contains(name))
682 }
683 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
684 BooleanGuardExpression::Opaque(_)
685 | BooleanGuardExpression::NegatedOpaque(_)
686 | BooleanGuardExpression::Constant(true) => true,
687 BooleanGuardExpression::Constant(false) => false,
688 BooleanGuardExpression::All(expressions) => expressions
689 .iter()
690 .all(|expression| self.boolean_guard_may_hold(expression)),
691 BooleanGuardExpression::Any(expressions) => expressions
692 .iter()
693 .any(|expression| self.boolean_guard_may_hold(expression)),
694 }
695 }
696}
697
698#[derive(Clone)]
699pub enum EffectiveUsingTarget {
700 Ordinary {
701 name: String,
702 target_components: Vec<String>,
703 global: bool,
704 },
705 Namespace {
706 namespace_components: Vec<String>,
707 global: bool,
708 },
709}
710
711#[derive(Clone)]
712pub struct OrdinaryTypeImport {
713 pub target: EffectiveUsingTarget,
714 pub source: ProjectFile,
715 pub declaration_byte: usize,
716 pub scope_start: usize,
717 pub scope_end: usize,
718 pub scope_depth: usize,
719 pub block_scope: bool,
720 pub lexical_depth: usize,
721 pub declaration_namespace: Vec<String>,
722 pub namespace_scope: Option<Vec<String>>,
723 pub resolved_target_components: Option<Vec<String>>,
724 pub required_guards: HashSet<PreprocessorGuard>,
725}
726
727#[derive(Clone)]
728pub struct ConditionalIncludeProjection {
729 pub activation_byte: usize,
730 pub required_guards: HashSet<PreprocessorGuard>,
731}
732
733#[derive(Default)]
734pub struct SourceUsingIndex {
735 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
736 pub directives: Vec<OrdinaryTypeImport>,
737}
738
739#[derive(Default)]
740pub struct ProjectUsingIndex {
741 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
742 pub directives: Vec<OrdinaryTypeImport>,
743}
744
745type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
746
747pub struct EffectiveUsingIndex {
748 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
749}
750
751impl EffectiveUsingIndex {
752 fn new(_root: ProjectFile) -> Self {
753 Self {
754 projected_by_name: Mutex::new(HashMap::default()),
755 }
756 }
757
758 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
759 self.projected_by_name
760 .lock()
761 .expect("C++ effective-using projection cache poisoned")
762 .entry(name.to_string())
763 .or_default()
764 .clone()
765 }
766}
767
768pub enum OrdinaryTypeImportResolution {
769 Resolved {
770 target: CodeUnit,
771 target_components: Vec<String>,
772 lexical_depth: usize,
773 is_direct: bool,
774 },
775 Ambiguous {
776 lexical_depth: usize,
777 },
778 Missing,
779}
780
781type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
782type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
783type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
784type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
785type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
786type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
787type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
788
789struct ExtractedComparable {
794 shapes: Vec<CppComparableSlot>,
795 suffix: String,
796}
797
798const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
802
803pub struct VisibilityIndex<'a> {
814 cpp: &'a dyn CppSource,
815 token: QueryToken<'a>,
820 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
821 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
822 global_field_internal_linkage: HashMap<CodeUnit, bool>,
823 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
824 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
825 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
826 visible_parser_alias_target_names:
827 Mutex<HashMap<ProjectFile, VisibleParserAliasTargetNamesCell>>,
828 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
829 project_using_index: OnceLock<ProjectUsingIndex>,
830 callable_reference_specs:
831 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
832 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
833 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
834 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
835 #[cfg(any(test, feature = "test-support"))]
836 conditional_include_projection_index_build_count: AtomicUsize,
837 #[cfg(any(test, feature = "test-support"))]
838 conditional_include_projection_state_count: AtomicUsize,
839 #[cfg(any(test, feature = "test-support"))]
840 include_activation_build_count: AtomicUsize,
841 #[cfg(any(test, feature = "test-support"))]
842 using_donor_activation_count: AtomicUsize,
843 #[cfg(any(test, feature = "test-support"))]
844 using_namespace_lookup_count: AtomicUsize,
845 #[cfg(any(test, feature = "test-support"))]
846 using_name_candidate_inspection_count: AtomicUsize,
847 #[cfg(any(test, feature = "test-support"))]
848 callable_reference_spec_build_count: AtomicUsize,
849 #[cfg(any(test, feature = "test-support"))]
850 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
851 #[cfg(any(test, feature = "test-support"))]
852 visible_parser_alias_name_set_build_count: AtomicUsize,
853 #[cfg(any(test, feature = "test-support"))]
854 visible_parser_alias_target_names_build_count: AtomicUsize,
855 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
856 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
857 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
858 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
859 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
860 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
861 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
862 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
863 pub macro_environment_cursors:
869 Mutex<HashMap<(ProjectFile, ThreadId), MacroEnvironmentCursorCell>>,
870 macro_replacements: Mutex<MacroReplacementCache>,
871 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
872 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
873 #[cfg(any(test, feature = "test-support"))]
874 pub macro_replacement_parse_count: AtomicUsize,
875 #[cfg(any(test, feature = "test-support"))]
876 pub macro_event_application_count: AtomicUsize,
877 #[cfg(any(test, feature = "test-support"))]
878 pub macro_environment_copy_count: AtomicUsize,
879 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
880 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
881 #[cfg(any(test, feature = "test-support"))]
882 qualified_candidate_inspections: AtomicUsize,
883 #[cfg(any(test, feature = "test-support"))]
884 target_preserving_type_resolution_count: AtomicUsize,
885}
886
887#[derive(Clone, Debug, PartialEq, Eq, Hash)]
888pub enum PreprocessorGuard {
889 Defined(String),
890 Undefined(String),
891 Boolean(BooleanGuardExpression),
892 Expression(String),
893 NegatedExpression(String),
894 Constant(bool),
895}
896
897#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
898pub enum BooleanGuardExpression {
899 Defined(String),
900 Undefined(String),
901 Truthy(String),
902 Falsy(String),
903 Opaque(String),
904 NegatedOpaque(String),
905 All(Vec<BooleanGuardExpression>),
906 Any(Vec<BooleanGuardExpression>),
907 Constant(bool),
908}
909
910impl BooleanGuardExpression {
911 fn negated(&self) -> Self {
912 match self {
913 Self::Defined(name) => Self::Undefined(name.clone()),
914 Self::Undefined(name) => Self::Defined(name.clone()),
915 Self::Truthy(name) => Self::Falsy(name.clone()),
916 Self::Falsy(name) => Self::Truthy(name.clone()),
917 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
918 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
919 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
920 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
921 Self::Constant(value) => Self::Constant(!value),
922 }
923 }
924
925 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
926 Self::normalized(expressions, true)
927 }
928
929 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
930 Self::normalized(expressions, false)
931 }
932
933 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
934 let mut normalized = Vec::new();
935 for expression in expressions {
936 match expression {
937 Self::All(nested) if conjunction => normalized.extend(nested),
938 Self::Any(nested) if !conjunction => normalized.extend(nested),
939 Self::Constant(value) if value == conjunction => {}
940 Self::Constant(value) => return Self::Constant(value),
941 expression => normalized.push(expression),
942 }
943 }
944 normalized.sort_unstable();
945 normalized.dedup();
946 match normalized.len() {
947 0 => Self::Constant(conjunction),
948 1 => normalized.pop().expect("one Boolean guard expression"),
949 _ if conjunction => Self::All(normalized),
950 _ => Self::Any(normalized),
951 }
952 }
953
954 fn implies(&self, required: &Self) -> bool {
955 if self == required
956 || matches!(self, Self::Constant(false))
957 || matches!(required, Self::Constant(true))
958 {
959 return true;
960 }
961 match self {
962 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
963 Self::All(active) => match required {
964 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
965 _ => active.iter().any(|expression| expression.implies(required)),
966 },
967 _ => match required {
968 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
969 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
970 _ => false,
971 },
972 }
973 }
974
975 pub fn heap_size(&self) -> usize {
976 match self {
977 Self::Defined(value)
978 | Self::Undefined(value)
979 | Self::Truthy(value)
980 | Self::Falsy(value)
981 | Self::Opaque(value)
982 | Self::NegatedOpaque(value) => value.len(),
983 Self::All(expressions) | Self::Any(expressions) => {
984 expressions
985 .iter()
986 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
987 size.saturating_add(std::mem::size_of::<Self>())
988 .saturating_add(expression.heap_size())
989 })
990 }
991 Self::Constant(_) => 0,
992 }
993 }
994}
995
996impl PreprocessorGuard {
997 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
998 match self {
999 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1000 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1001 Self::Boolean(expression) => Some(expression.clone()),
1002 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1003 Self::Expression(_) | Self::NegatedExpression(_) => None,
1004 }
1005 }
1006
1007 fn negated(&self) -> Self {
1008 match self {
1009 Self::Defined(name) => Self::Undefined(name.clone()),
1010 Self::Undefined(name) => Self::Defined(name.clone()),
1011 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1012 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1013 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1014 Self::Constant(value) => Self::Constant(!value),
1015 }
1016 }
1017
1018 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1019 match self {
1020 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1021 Self::Boolean(_) | Self::Expression(_) | Self::NegatedExpression(_) => true,
1026 Self::Constant(_) => false,
1027 }
1028 }
1029}
1030
1031#[derive(Clone, PartialEq, Eq)]
1032pub enum MacroDefinition {
1033 Object {
1034 replacement: String,
1035 },
1036 Function {
1037 parameters: Vec<String>,
1038 replacement: String,
1039 },
1040 Unsupported,
1041}
1042
1043#[derive(Clone, Debug, PartialEq, Eq)]
1044pub enum MacroIncludeProtection {
1045 MacroGuard(String),
1046 PragmaOnce,
1047 None,
1048}
1049
1050enum ParsedMacroReplacement {
1051 Parsed { source: String, tree: Tree },
1052 Unsupported,
1053}
1054
1055#[derive(Clone)]
1056enum MacroLocalBindingTypeTemplate {
1057 Parameter(usize),
1058 Fixed(String),
1059}
1060
1061#[derive(Clone)]
1062struct MacroLocalBindingTemplate {
1063 name: String,
1064 declared_type: MacroLocalBindingTypeTemplate,
1065 pointer_depth: i32,
1066}
1067
1068pub struct MacroLocalBinding<'tree> {
1074 pub name: String,
1075 pub type_name: String,
1076 pub type_node: Option<Node<'tree>>,
1077 pub pointer_depth: i32,
1078}
1079
1080fn recognized_c_macro_declarator_binding<'tree>(
1085 statement: Node<'tree>,
1086 source: &str,
1087) -> Option<MacroLocalBinding<'tree>> {
1088 let assignment = match statement.kind() {
1089 "assignment_expression" => statement,
1090 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1091 _ => return None,
1092 };
1093 if assignment.kind() != "assignment_expression" {
1094 return None;
1095 }
1096 let call = assignment.child_by_field_name("left")?;
1097 if call.kind() != "call_expression" {
1098 return None;
1099 }
1100 let function = call.child_by_field_name("function")?;
1101 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1102 return None;
1103 }
1104 let arguments = call.child_by_field_name("arguments")?;
1105 let mut actuals = argument_children(arguments);
1106 let type_node = actuals.next()?;
1107 if actuals.next().is_some()
1108 || !matches!(
1109 type_node.kind(),
1110 "identifier"
1111 | "type_identifier"
1112 | "qualified_identifier"
1113 | "scoped_type_identifier"
1114 | "template_type"
1115 )
1116 {
1117 return None;
1118 }
1119 let name_node = (0..assignment.named_child_count())
1120 .filter_map(|index| assignment.named_child(index))
1121 .filter(|child| child.kind() == "ERROR")
1122 .filter_map(|error| {
1123 (error.named_child_count() == 1)
1124 .then(|| error.named_child(0))
1125 .flatten()
1126 })
1127 .find(|node| node.kind() == "identifier")?;
1128 let name = node_text(name_node, source).trim();
1129 let type_name = node_text(type_node, source).trim();
1130 if name.is_empty() || type_name.is_empty() {
1131 return None;
1132 }
1133 Some(MacroLocalBinding {
1134 name: name.to_string(),
1135 type_name: type_name.to_string(),
1136 type_node: Some(type_node),
1137 pointer_depth: 1,
1138 })
1139}
1140
1141#[derive(Clone, PartialEq, Eq)]
1142pub struct MacroBinding {
1143 source: ProjectFile,
1144 declaration_byte: usize,
1145 definition: MacroDefinition,
1146 exact: bool,
1147}
1148
1149impl MacroBinding {
1150 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1151 Self {
1152 source: source.clone(),
1153 declaration_byte,
1154 definition: MacroDefinition::Unsupported,
1155 exact: false,
1156 }
1157 }
1158
1159 fn is_exact(&self) -> bool {
1160 self.exact
1161 }
1162
1163 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1164 Self {
1165 source: source.clone(),
1166 declaration_byte,
1167 definition: current.definition.clone(),
1168 exact: false,
1169 }
1170 }
1171}
1172
1173#[derive(Clone)]
1174pub enum MacroEvent {
1175 Define {
1176 name: String,
1177 binding: MacroBinding,
1178 byte: usize,
1179 conditional: bool,
1180 },
1181 Undef {
1182 name: String,
1183 byte: usize,
1184 conditional: bool,
1185 },
1186 Include {
1187 targets: Vec<ProjectFile>,
1188 byte: usize,
1189 conditional: bool,
1190 },
1191 Invalidate {
1192 byte: usize,
1193 },
1194}
1195
1196impl MacroEvent {
1197 pub fn byte(&self) -> usize {
1198 match self {
1199 Self::Define { byte, .. }
1200 | Self::Undef { byte, .. }
1201 | Self::Include { byte, .. }
1202 | Self::Invalidate { byte } => *byte,
1203 }
1204 }
1205}
1206
1207#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1208pub enum CallArityEvidence {
1209 Exact(usize),
1210 Unknown,
1211}
1212
1213impl CallArityEvidence {
1214 pub fn exact(self) -> Option<usize> {
1215 match self {
1216 Self::Exact(arity) => Some(arity),
1217 Self::Unknown => None,
1218 }
1219 }
1220
1221 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1222 self.exact().map(|arity| expected.accepts(arity))
1223 }
1224}
1225
1226#[derive(Clone)]
1227struct DeclaredFieldTypeFact {
1228 type_text: String,
1229 indirection: i32,
1230 template_arguments: Option<Vec<CppTemplateExpression>>,
1231}
1232
1233#[derive(Clone, PartialEq, Eq)]
1234enum StructuredAliasTarget {
1235 Builtin,
1236 Named {
1237 components: Vec<String>,
1238 global: bool,
1239 arguments: Option<Vec<CppTemplateExpression>>,
1240 },
1241}
1242
1243struct CppAlias {
1244 name: String,
1245 target: String,
1246 namespace: Option<String>,
1247}
1248
1249type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1250
1251#[derive(Debug, Clone, PartialEq, Eq)]
1255pub enum CppTemplateResolutionError {
1256 AliasCycle { alias: CodeUnit },
1258 ArgumentBinding,
1260 Substitution,
1262 PrimarySelection,
1265 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1268}
1269
1270fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1273 let mut distinct: Vec<CodeUnit> = Vec::new();
1274 for unit in units {
1275 if !distinct
1276 .iter()
1277 .any(|existing| same_visible_symbol(existing, unit))
1278 {
1279 distinct.push(unit.clone());
1280 }
1281 }
1282 distinct
1283}
1284
1285impl<'a> VisibilityIndex<'a> {
1286 pub fn cpp(&self) -> &'a dyn CppSource {
1287 self.cpp
1288 }
1289
1290 pub fn token(&self) -> QueryToken<'a> {
1292 self.token
1293 }
1294
1295 #[cfg(any(test, feature = "test-support"))]
1303 pub fn from_visible_files_for_test(
1304 cpp: &'a dyn CppSource,
1305 token: QueryToken<'a>,
1306 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1307 ) -> Self {
1308 let visible_source_files_by_root = visible_by_file
1309 .iter()
1310 .map(|(file, visible)| {
1311 (
1312 file.clone(),
1313 visible
1314 .iter()
1315 .map(|unit| unit.source().clone())
1316 .chain(std::iter::once(file.clone()))
1317 .collect(),
1318 )
1319 })
1320 .collect();
1321 let mut global_field_internal_linkage = HashMap::default();
1322 Self {
1323 cpp,
1324 token,
1325 visible_by_identifier: build_visible_identifier_index(
1326 &CppGraphSource::from_source(cpp, token),
1327 &visible_by_file,
1328 &visible_source_files_by_root,
1329 &mut global_field_internal_linkage,
1330 ),
1331 global_field_internal_linkage,
1332 visible_by_file,
1333 visible_source_files_by_root,
1334 alias_cells: Mutex::new(HashMap::default()),
1335 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1336 visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1337 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1338 project_using_index: OnceLock::new(),
1339 callable_reference_specs: Mutex::new(HashMap::default()),
1340 include_activation_cells: Mutex::new(HashMap::default()),
1341 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1342 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1343 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1344 conditional_include_projection_state_count: AtomicUsize::new(0),
1345 include_activation_build_count: AtomicUsize::new(0),
1346 using_donor_activation_count: AtomicUsize::new(0),
1347 using_namespace_lookup_count: AtomicUsize::new(0),
1348 using_name_candidate_inspection_count: AtomicUsize::new(0),
1349 callable_reference_spec_build_count: AtomicUsize::new(0),
1350 alias_source_parse_counts: Mutex::new(HashMap::default()),
1351 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1352 visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1353 field_type_facts: Mutex::new(HashMap::default()),
1354 structured_alias_targets: Mutex::new(HashMap::default()),
1355 callable_comparables: Mutex::new(HashMap::default()),
1356 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1357 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1358 precise_parent_cache: Mutex::new(HashMap::default()),
1359 macro_event_cells: Mutex::new(HashMap::default()),
1360 macro_include_protection_cells: Mutex::new(HashMap::default()),
1361 macro_environment_cursors: Mutex::new(HashMap::default()),
1362 macro_replacements: Mutex::new(HashMap::default()),
1363 macro_local_binding_templates: Mutex::new(HashMap::default()),
1364 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1365 macro_replacement_parse_count: AtomicUsize::new(0),
1366 macro_event_application_count: AtomicUsize::new(0),
1367 macro_environment_copy_count: AtomicUsize::new(0),
1368 cpp_template_metadata: HashMap::default(),
1369 cpp_template_families: HashMap::default(),
1370 qualified_candidate_inspections: AtomicUsize::new(0),
1371 target_preserving_type_resolution_count: AtomicUsize::new(0),
1372 }
1373 }
1374
1375 fn cpp_source(&self) -> CppGraphSource<'a> {
1382 CppGraphSource::from_source(self.cpp, self.token)
1383 }
1384
1385 pub fn build(
1386 cpp: &'a dyn CppSource,
1387 token: QueryToken<'a>,
1388 analyzer: &CppGraphSource<'_>,
1389 roots: &HashSet<ProjectFile>,
1390 ) -> Self {
1391 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
1392 }
1393
1394 pub fn build_with_cancellation(
1395 cpp: &'a dyn CppSource,
1396 token: QueryToken<'a>,
1397 analyzer: &CppGraphSource<'_>,
1398 roots: &HashSet<ProjectFile>,
1399 cancellation: Option<&CancellationToken>,
1400 ) -> Self {
1401 let include_targets = cpp.include_target_index();
1402 let VisibilityData {
1403 mut visible_by_file,
1404 visible_source_files_by_root,
1405 } = build_visibility_data(
1406 roots,
1407 cancellation,
1408 |file| {
1409 let imports = analyzer.import_statements(file);
1410 cpp_include_paths(&imports)
1411 .into_iter()
1412 .flat_map(|include| {
1413 resolve_include_targets_with_index(file, &include, include_targets)
1414 })
1415 .collect()
1416 },
1417 |root| analyzer.reference_uses_c_semantics(root),
1418 |file, c_semantics| analyzer.declarations_in_reading(file, c_semantics),
1419 );
1420 extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
1421 let mut global_field_internal_linkage = HashMap::default();
1422 let visible_by_identifier = build_visible_identifier_index(
1423 analyzer,
1424 &visible_by_file,
1425 &visible_source_files_by_root,
1426 &mut global_field_internal_linkage,
1427 );
1428 let mut cpp_template_metadata = HashMap::default();
1429 for unit in visible_by_file
1430 .values()
1431 .flatten()
1432 .filter(|unit| unit.is_class())
1433 {
1434 if cpp_template_metadata.contains_key(unit) {
1435 continue;
1436 }
1437 if let Some(metadata) = cpp.template_metadata(unit) {
1438 cpp_template_metadata.insert(unit.clone(), metadata);
1439 }
1440 }
1441 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
1442 for (unit, metadata) in &cpp_template_metadata {
1443 cpp_template_families
1444 .entry(metadata.primary_fq_name.clone())
1445 .or_default()
1446 .push(unit.clone());
1447 }
1448 for family in cpp_template_families.values_mut() {
1457 sort_lookup_units(family);
1458 }
1459 Self {
1460 cpp,
1461 token,
1462 visible_by_file,
1463 visible_by_identifier,
1464 global_field_internal_linkage,
1465 visible_source_files_by_root,
1466 alias_cells: Mutex::new(HashMap::default()),
1467 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
1468 visible_parser_alias_target_names: Mutex::new(HashMap::default()),
1469 ordinary_type_import_cells: Mutex::new(HashMap::default()),
1470 project_using_index: OnceLock::new(),
1471 callable_reference_specs: Mutex::new(HashMap::default()),
1472 include_activation_cells: Mutex::new(HashMap::default()),
1473 compile_proven_guard_cells: Mutex::new(HashMap::default()),
1474 conditional_include_projection_cells: Mutex::new(HashMap::default()),
1475 #[cfg(any(test, feature = "test-support"))]
1476 conditional_include_projection_index_build_count: AtomicUsize::new(0),
1477 #[cfg(any(test, feature = "test-support"))]
1478 conditional_include_projection_state_count: AtomicUsize::new(0),
1479 #[cfg(any(test, feature = "test-support"))]
1480 include_activation_build_count: AtomicUsize::new(0),
1481 #[cfg(any(test, feature = "test-support"))]
1482 using_donor_activation_count: AtomicUsize::new(0),
1483 #[cfg(any(test, feature = "test-support"))]
1484 using_namespace_lookup_count: AtomicUsize::new(0),
1485 #[cfg(any(test, feature = "test-support"))]
1486 using_name_candidate_inspection_count: AtomicUsize::new(0),
1487 #[cfg(any(test, feature = "test-support"))]
1488 callable_reference_spec_build_count: AtomicUsize::new(0),
1489 #[cfg(any(test, feature = "test-support"))]
1490 alias_source_parse_counts: Mutex::new(HashMap::default()),
1491 #[cfg(any(test, feature = "test-support"))]
1492 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
1493 #[cfg(any(test, feature = "test-support"))]
1494 visible_parser_alias_target_names_build_count: AtomicUsize::new(0),
1495 field_type_facts: Mutex::new(HashMap::default()),
1496 structured_alias_targets: Mutex::new(HashMap::default()),
1497 callable_comparables: Mutex::new(HashMap::default()),
1498 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
1499 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
1500 precise_parent_cache: Mutex::new(HashMap::default()),
1501 macro_event_cells: Mutex::new(HashMap::default()),
1502 macro_include_protection_cells: Mutex::new(HashMap::default()),
1503 macro_environment_cursors: Mutex::new(HashMap::default()),
1504 macro_replacements: Mutex::new(HashMap::default()),
1505 macro_local_binding_templates: Mutex::new(HashMap::default()),
1506 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
1507 #[cfg(any(test, feature = "test-support"))]
1508 macro_replacement_parse_count: AtomicUsize::new(0),
1509 #[cfg(any(test, feature = "test-support"))]
1510 macro_event_application_count: AtomicUsize::new(0),
1511 #[cfg(any(test, feature = "test-support"))]
1512 macro_environment_copy_count: AtomicUsize::new(0),
1513 cpp_template_metadata,
1514 cpp_template_families,
1515 #[cfg(any(test, feature = "test-support"))]
1516 qualified_candidate_inspections: AtomicUsize::new(0),
1517 #[cfg(any(test, feature = "test-support"))]
1518 target_preserving_type_resolution_count: AtomicUsize::new(0),
1519 }
1520 }
1521
1522 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
1523 if file == target.source() {
1524 return true;
1525 }
1526 if self.global_field_has_internal_linkage(target) {
1527 return self
1528 .visible_source_files_by_root
1529 .get(file)
1530 .is_some_and(|sources| sources.contains(target.source()));
1531 }
1532 self.visible_by_file
1533 .get(file)
1534 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
1535 }
1536
1537 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
1538 self.global_field_internal_linkage
1539 .get(unit)
1540 .copied()
1541 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
1542 }
1543
1544 pub fn call_arity_evidence(
1545 &self,
1546 file: &ProjectFile,
1547 call: Node<'_>,
1548 source: &str,
1549 ) -> CallArityEvidence {
1550 let Some(arguments) = call
1551 .child_by_field_name("arguments")
1552 .or_else(|| call.child_by_field_name("parameters"))
1553 .or_else(|| call.child_by_field_name("value"))
1554 .or_else(|| first_named_child_of_kind(call, "argument_list"))
1555 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
1556 else {
1557 return CallArityEvidence::Exact(0);
1558 };
1559 let recovered_c_keyword_arguments =
1560 recovered_c_keyword_argument_count(file, call, arguments, source);
1561 let arguments = argument_children(arguments).collect::<Vec<_>>();
1562 if arguments
1563 .iter()
1564 .all(|argument| !argument_shape_may_change_arity(*argument))
1565 {
1566 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
1567 }
1568 let environment = self.macro_environment(file, call.start_byte());
1569 let mut stack = Vec::new();
1570 let mut total = recovered_c_keyword_arguments;
1571 for argument in arguments {
1572 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
1573 return CallArityEvidence::Unknown;
1574 }
1575 let CallArityEvidence::Exact(spread) =
1576 self.argument_arity_evidence(argument, source, &environment, &mut stack)
1577 else {
1578 return CallArityEvidence::Unknown;
1579 };
1580 total += spread;
1581 }
1582 CallArityEvidence::Exact(total)
1583 }
1584
1585 fn argument_arity_evidence(
1586 &self,
1587 argument: Node<'_>,
1588 source: &str,
1589 environment: &MacroEnvironment,
1590 stack: &mut Vec<(ProjectFile, usize)>,
1591 ) -> CallArityEvidence {
1592 let (name, invocation_arguments, function_like) = match argument.kind() {
1593 "identifier" => (node_text(argument, source), None, false),
1594 "call_expression" => {
1595 let Some(function) = argument.child_by_field_name("function") else {
1596 return CallArityEvidence::Exact(1);
1597 };
1598 if function.kind() != "identifier" {
1599 return CallArityEvidence::Exact(1);
1600 }
1601 let Some(arguments) = argument.child_by_field_name("arguments") else {
1602 return CallArityEvidence::Exact(1);
1603 };
1604 (node_text(function, source), Some(arguments), true)
1605 }
1606 _ => return CallArityEvidence::Exact(1),
1607 };
1608 let Some(binding) = environment.binding(name) else {
1609 return if environment.unknown_names {
1610 CallArityEvidence::Unknown
1611 } else {
1612 CallArityEvidence::Exact(1)
1613 };
1614 };
1615 if !binding.is_exact() {
1616 return CallArityEvidence::Unknown;
1617 }
1618 match (&binding.definition, invocation_arguments, function_like) {
1619 (MacroDefinition::Object { replacement }, None, false) => self
1620 .replacement_arity_evidence(
1621 replacement,
1622 &[],
1623 &[],
1624 source,
1625 environment,
1626 stack,
1627 binding,
1628 ),
1629 (
1630 MacroDefinition::Function {
1631 parameters,
1632 replacement,
1633 },
1634 Some(arguments),
1635 true,
1636 ) => {
1637 let actuals = argument_children(arguments).collect::<Vec<_>>();
1638 if actuals.len() != parameters.len() {
1639 CallArityEvidence::Unknown
1640 } else {
1641 self.replacement_arity_evidence(
1642 replacement,
1643 parameters,
1644 &actuals,
1645 source,
1646 environment,
1647 stack,
1648 binding,
1649 )
1650 }
1651 }
1652 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
1653 _ => CallArityEvidence::Unknown,
1654 }
1655 }
1656
1657 #[allow(clippy::too_many_arguments)]
1658 fn replacement_arity_evidence(
1659 &self,
1660 replacement: &str,
1661 parameters: &[String],
1662 actuals: &[Node<'_>],
1663 actual_source: &str,
1664 environment: &MacroEnvironment,
1665 stack: &mut Vec<(ProjectFile, usize)>,
1666 binding: &MacroBinding,
1667 ) -> CallArityEvidence {
1668 let identity = (binding.source.clone(), binding.declaration_byte);
1669 if stack.contains(&identity) || replacement.trim().is_empty() {
1670 return CallArityEvidence::Unknown;
1671 }
1672 stack.push(identity);
1673 let parsed = self.parsed_macro_replacement(binding, replacement);
1674 let evidence = (|| {
1675 let ParsedMacroReplacement::Parsed {
1676 source: sentinel,
1677 tree,
1678 } = parsed.as_ref()
1679 else {
1680 return None;
1681 };
1682 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
1683 let arguments = call.child_by_field_name("arguments")?;
1684 let mut total = 0usize;
1685 for argument in argument_children(arguments) {
1686 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
1687 return None;
1688 }
1689 if argument.kind() == "identifier"
1690 && let Some(parameter_index) = parameters
1691 .iter()
1692 .position(|parameter| parameter == node_text(argument, sentinel))
1693 {
1694 if !macro_expansion_shape_is_safe(
1695 actuals[parameter_index],
1696 actual_source,
1697 &[],
1698 environment,
1699 ) {
1700 return None;
1701 }
1702 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
1703 actuals[parameter_index],
1704 actual_source,
1705 environment,
1706 stack,
1707 ) else {
1708 return None;
1709 };
1710 total += spread;
1711 continue;
1712 }
1713 let CallArityEvidence::Exact(spread) =
1714 self.argument_arity_evidence(argument, sentinel, environment, stack)
1715 else {
1716 return None;
1717 };
1718 total += spread;
1719 }
1720 Some(CallArityEvidence::Exact(total))
1721 })()
1722 .unwrap_or(CallArityEvidence::Unknown);
1723 stack.pop();
1724 evidence
1725 }
1726
1727 fn parsed_macro_replacement(
1728 &self,
1729 binding: &MacroBinding,
1730 replacement: &str,
1731 ) -> Arc<ParsedMacroReplacement> {
1732 let key = (binding.source.clone(), binding.declaration_byte);
1733 let mut cache = self
1734 .macro_replacements
1735 .lock()
1736 .expect("C++ macro replacement cache poisoned");
1737 if let Some(parsed) = cache.get(&key) {
1738 return Arc::clone(parsed);
1739 }
1740 #[cfg(any(test, feature = "test-support"))]
1741 self.macro_replacement_parse_count
1742 .fetch_add(1, Ordering::Relaxed);
1743 let source =
1744 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
1745 let mut parser = Parser::new();
1746 let parsed = parser
1747 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1748 .ok()
1749 .and_then(|()| parser.parse(&source, None))
1750 .filter(|tree| !tree.root_node().has_error())
1751 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
1752 ParsedMacroReplacement::Parsed { source, tree }
1753 });
1754 let parsed = Arc::new(parsed);
1755 cache.insert(key, Arc::clone(&parsed));
1756 parsed
1757 }
1758
1759 pub fn function_macro_local_binding<'tree>(
1769 &self,
1770 file: &ProjectFile,
1771 statement: Node<'tree>,
1772 source: &str,
1773 ) -> Option<MacroLocalBinding<'tree>> {
1774 if !is_c_source_file(file) {
1775 return None;
1776 }
1777 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
1778 return Some(binding);
1779 }
1780 let call = match statement.kind() {
1781 "call_expression" => statement,
1782 "expression_statement" if statement.named_child_count() == 1 => {
1783 statement.named_child(0)?
1784 }
1785 _ => return None,
1786 };
1787 if call.kind() != "call_expression" {
1788 return None;
1789 }
1790 let function = call.child_by_field_name("function")?;
1791 if function.kind() != "identifier" {
1792 return None;
1793 }
1794 let arguments = call.child_by_field_name("arguments")?;
1795 let actuals = argument_children(arguments).collect::<Vec<_>>();
1796 let environment = self.macro_environment(file, call.start_byte());
1797 let function_name = node_text(function, source);
1798 let binding = environment.binding(function_name)?;
1799 let MacroDefinition::Function {
1800 parameters,
1801 replacement,
1802 } = &binding.definition
1803 else {
1804 return None;
1805 };
1806 if actuals.len() != parameters.len() {
1807 return None;
1808 }
1809 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
1810 let (type_name, type_node) = match &template.declared_type {
1811 MacroLocalBindingTypeTemplate::Parameter(index) => {
1812 let actual = *actuals.get(*index)?;
1813 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
1814 return None;
1815 }
1816 (node_text(actual, source).trim().to_string(), Some(actual))
1817 }
1818 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
1819 };
1820 if type_name.is_empty() {
1821 return None;
1822 }
1823 Some(MacroLocalBinding {
1824 name: template.name.clone(),
1825 type_name,
1826 type_node,
1827 pointer_depth: template.pointer_depth,
1828 })
1829 }
1830
1831 fn macro_local_binding_template(
1832 &self,
1833 binding: &MacroBinding,
1834 parameters: &[String],
1835 replacement: &str,
1836 ) -> Option<Arc<MacroLocalBindingTemplate>> {
1837 let key = (binding.source.clone(), binding.declaration_byte);
1838 let mut cache = self
1839 .macro_local_binding_templates
1840 .lock()
1841 .expect("C++ macro local-binding cache poisoned");
1842 if let Some(template) = cache.get(&key) {
1843 return template.clone();
1844 }
1845 let sentinel = format!("void __bifrost_macro_local() {{ {replacement}; }}");
1846 let template = (|| {
1847 let mut parser = Parser::new();
1848 parser
1849 .set_language(&tree_sitter_cpp::LANGUAGE.into())
1850 .ok()?;
1851 let tree = parser.parse(&sentinel, None)?;
1852 if tree.root_node().has_error() {
1853 return None;
1854 }
1855 let function = first_descendant_of_kind(tree.root_node(), "function_definition")?;
1856 let body = function.child_by_field_name("body")?;
1857 if body.named_child_count() != 1 {
1858 return None;
1859 }
1860 let declaration = body.named_child(0)?;
1861 if declaration.kind() != "declaration" {
1862 return None;
1863 }
1864 let type_node = declaration
1865 .child_by_field_name("type")
1866 .or_else(|| first_type_child(declaration))?;
1867 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
1868 let mut cursor = declaration.walk();
1869 declaration.named_children(&mut cursor).find_map(|child| {
1870 if child.kind() == "init_declarator" {
1871 child.child_by_field_name("declarator")
1872 } else {
1873 is_declarator_node(child).then_some(child)
1874 }
1875 })
1876 })?;
1877 let name = extract_variable_name(declarator, &sentinel)?;
1878 let pointer_depth =
1879 declared_name_indirection(declaration, type_node, &name, &sentinel)?;
1880 let type_text = node_text(type_node, &sentinel).trim();
1881 let declared_type = parameters
1882 .iter()
1883 .position(|parameter| parameter == type_text)
1884 .map(MacroLocalBindingTypeTemplate::Parameter)
1885 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
1886 Some(Arc::new(MacroLocalBindingTemplate {
1887 name,
1888 declared_type,
1889 pointer_depth,
1890 }))
1891 })();
1892 cache.insert(key, template.clone());
1893 template
1894 }
1895
1896 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
1897 let Some(value) = node.child_by_field_name("value") else {
1898 return MacroDefinition::Unsupported;
1899 };
1900 let replacement = node_text(value, source).to_string();
1901 if node.kind() == "preproc_def" {
1902 return MacroDefinition::Object { replacement };
1903 }
1904 let Some(parameters) = node.child_by_field_name("parameters") else {
1905 return MacroDefinition::Unsupported;
1906 };
1907 if (0..parameters.child_count()).any(|index| {
1908 parameters
1909 .child(index)
1910 .is_some_and(|child| child.kind() == "...")
1911 }) {
1912 return MacroDefinition::Unsupported;
1913 }
1914 let parameters = (0..parameters.named_child_count())
1915 .filter_map(|index| parameters.named_child(index))
1916 .map(|parameter| node_text(parameter, source).to_string())
1917 .collect();
1918 MacroDefinition::Function {
1919 parameters,
1920 replacement,
1921 }
1922 }
1923
1924 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
1925 self.macro_event_cells
1926 .lock()
1927 .expect("C++ macro event cache poisoned")
1928 .entry(file.clone())
1929 .or_default()
1930 .clone()
1931 }
1932
1933 pub fn macro_environment_cursor_cell(&self, file: &ProjectFile) -> MacroEnvironmentCursorCell {
1934 let key = (file.clone(), std::thread::current().id());
1935 self.macro_environment_cursors
1936 .lock()
1937 .expect("C++ macro environment cursor cache poisoned")
1938 .entry(key)
1939 .or_default()
1940 .clone()
1941 }
1942
1943 pub fn macro_environment(
1944 &self,
1945 file: &ProjectFile,
1946 before_byte: usize,
1947 ) -> Arc<MacroEnvironment> {
1948 let cell = self.macro_event_cell(file);
1949 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
1950 let frontier = events.partition_point(|event| event.byte() < before_byte);
1951 let cursor_cell = self.macro_environment_cursor_cell(file);
1952 let mut cursor = cursor_cell
1953 .lock()
1954 .expect("C++ macro environment cursor poisoned");
1955 if frontier < cursor.frontier {
1956 *cursor = MacroEnvironmentCursor::default();
1957 }
1958 if cursor.frontier == 0 {
1963 let proven = self.compile_proven_guards(file);
1964 if !proven.is_empty() && cursor.environment.build_proven_defines.len() != proven.len() {
1965 Arc::make_mut(&mut cursor.environment).build_proven_defines = proven
1966 .iter()
1967 .filter_map(|guard| match guard {
1968 PreprocessorGuard::Defined(name) => Some(name.clone()),
1969 _ => None,
1970 })
1971 .collect();
1972 }
1973 }
1974 if frontier > cursor.frontier {
1975 #[cfg(any(test, feature = "test-support"))]
1976 if Arc::strong_count(&cursor.environment) > 1 {
1977 self.macro_environment_copy_count
1978 .fetch_add(1, Ordering::Relaxed);
1979 }
1980 let start = cursor.frontier;
1981 let environment = Arc::make_mut(&mut cursor.environment);
1982 let mut include_stack = HashSet::from_iter([file.clone()]);
1983 for event in &events[start..frontier] {
1984 self.apply_macro_event(file, event, environment, &mut include_stack);
1985 }
1986 cursor.frontier = frontier;
1987 }
1988 Arc::clone(&cursor.environment)
1989 }
1990
1991 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
2000 self.macro_environment(file, before_byte)
2001 .binding(name)
2002 .is_some()
2003 }
2004
2005 pub fn macro_name_may_be_bound_at(
2006 &self,
2007 file: &ProjectFile,
2008 name: &str,
2009 before_byte: usize,
2010 ) -> bool {
2011 self.macro_environment(file, before_byte).may_bind(name)
2012 }
2013
2014 pub fn macro_binding_matches_target_at(
2018 &self,
2019 analyzer: &CppGraphSource<'_>,
2020 file: &ProjectFile,
2021 name: &str,
2022 before_byte: usize,
2023 target: &CodeUnit,
2024 ) -> bool {
2025 let environment = self.macro_environment(file, before_byte);
2026 let Some(binding) = environment.binding(name) else {
2027 return false;
2028 };
2029 if binding.source != *target.source() {
2033 return false;
2034 }
2035 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
2036 return false;
2037 };
2038 analyzer.ranges(target).iter().any(|range| {
2039 let Some(mut node) = node_for_exact_range(prepared.tree().root_node(), range) else {
2040 return false;
2041 };
2042 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
2043 let Some(parent) = node.parent() else {
2044 return false;
2045 };
2046 node = parent;
2047 }
2048 node.start_byte() == binding.declaration_byte
2049 })
2050 }
2051
2052 pub fn resolve_ordinary_macro_reference(
2059 &self,
2060 analyzer: &CppGraphSource<'_>,
2061 file: &ProjectFile,
2062 node: Node<'_>,
2063 source: &str,
2064 ) -> OrdinaryMacroReferenceResolution {
2065 if !is_ordinary_macro_reference_node(node) {
2066 return OrdinaryMacroReferenceResolution::Missing;
2067 }
2068 let name = node_text(node, source);
2069 if name.is_empty() {
2070 return OrdinaryMacroReferenceResolution::Missing;
2071 }
2072 let visible = self
2073 .visible_identifier_candidates(file, name)
2074 .filter(|candidate| candidate.is_macro())
2075 .cloned()
2076 .collect::<Vec<_>>();
2077 let mut exact = Vec::new();
2078 for candidate in &visible {
2079 if self.macro_binding_matches_target_at(
2080 analyzer,
2081 file,
2082 name,
2083 node.start_byte(),
2084 candidate,
2085 ) && !exact
2086 .iter()
2087 .any(|existing| same_visible_symbol(existing, candidate))
2088 {
2089 exact.push(candidate.clone());
2090 }
2091 }
2092 match exact.len() {
2093 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
2094 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
2095 0 if !visible.is_empty()
2096 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
2097 {
2098 OrdinaryMacroReferenceResolution::Ambiguous
2099 }
2100 0 => OrdinaryMacroReferenceResolution::Missing,
2101 }
2102 }
2103
2104 pub fn recovered_c_reference_ranges(
2112 &self,
2113 file: &ProjectFile,
2114 root: Node<'_>,
2115 source: &str,
2116 limit: usize,
2117 ) -> RecoveredCReferenceRanges {
2118 if !is_c_source_file(file) {
2119 return RecoveredCReferenceRanges::Complete(Vec::new());
2120 }
2121 let mut ranges = Vec::new();
2122 let mut seen = HashSet::default();
2123 let mut stack = vec![(root, root.is_error())];
2124 while let Some((node, inside_error)) = stack.pop() {
2125 let inside_error = inside_error || node.is_error();
2126 if inside_error
2127 && recovered_c_reference_node(self, file, node, source)
2128 && seen.insert((node.start_byte(), node.end_byte()))
2129 {
2130 if ranges.len() == limit {
2131 return RecoveredCReferenceRanges::LimitExceeded;
2132 }
2133 ranges.push(Range {
2134 start_byte: node.start_byte(),
2135 end_byte: node.end_byte(),
2136 start_line: node.start_position().row,
2137 end_line: node.end_position().row,
2138 });
2139 }
2140 let mut cursor = node.walk();
2141 for child in node.named_children(&mut cursor) {
2142 stack.push((child, inside_error));
2143 }
2144 }
2145 ranges.sort_unstable();
2146 RecoveredCReferenceRanges::Complete(ranges)
2147 }
2148
2149 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2155 self.visible_identifier_candidates(file, target.identifier())
2156 .filter(|candidate| candidate.is_macro())
2157 .any(|candidate| {
2158 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
2159 })
2160 }
2161
2162 pub fn object_macro_replacement_at(
2163 &self,
2164 file: &ProjectFile,
2165 name: &str,
2166 before_byte: usize,
2167 ) -> Option<String> {
2168 let environment = self.macro_environment(file, before_byte);
2169 let binding = environment.binding(name)?;
2170 if !binding.exact {
2171 return None;
2172 }
2173 match &binding.definition {
2174 MacroDefinition::Object { replacement } => Some(replacement.clone()),
2175 MacroDefinition::Function { .. } | MacroDefinition::Unsupported => None,
2176 }
2177 }
2178
2179 fn apply_macro_events(
2180 &self,
2181 file: &ProjectFile,
2182 before_byte: Option<usize>,
2183 environment: &mut MacroEnvironment,
2184 include_stack: &mut HashSet<ProjectFile>,
2185 ) {
2186 if !include_stack.insert(file.clone()) {
2187 return;
2188 }
2189 if self.cpp.prepared_syntax(self.token, file).is_none() {
2190 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
2191 include_stack.remove(file);
2192 return;
2193 }
2194 match self.macro_include_protection(file) {
2195 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
2196 Some(binding) if binding.is_exact() => {
2197 include_stack.remove(file);
2198 return;
2199 }
2200 Some(_) | None if environment.unknown_names => {
2201 let mut ambiguous_seen = HashSet::default();
2202 self.mark_macro_events_ambiguous(
2203 file,
2204 environment,
2205 &mut ambiguous_seen,
2206 file,
2207 before_byte.unwrap_or_default(),
2208 );
2209 include_stack.remove(file);
2210 return;
2211 }
2212 Some(_) => {
2213 let mut ambiguous_seen = HashSet::default();
2214 self.mark_macro_events_ambiguous(
2215 file,
2216 environment,
2217 &mut ambiguous_seen,
2218 file,
2219 before_byte.unwrap_or_default(),
2220 );
2221 include_stack.remove(file);
2222 return;
2223 }
2224 None => {}
2225 },
2226 MacroIncludeProtection::PragmaOnce => {
2227 if !environment.applied_pragma_once_files.insert(file.clone()) {
2228 include_stack.remove(file);
2229 return;
2230 }
2231 if environment.maybe_applied_pragma_once_files.remove(file) {
2232 let mut ambiguous_seen = HashSet::default();
2237 environment.applied_pragma_once_files.remove(file);
2238 self.mark_macro_events_ambiguous(
2239 file,
2240 environment,
2241 &mut ambiguous_seen,
2242 file,
2243 before_byte.unwrap_or_default(),
2244 );
2245 environment.maybe_applied_pragma_once_files.remove(file);
2246 environment.applied_pragma_once_files.insert(file.clone());
2247 include_stack.remove(file);
2248 return;
2249 }
2250 }
2251 MacroIncludeProtection::None => {}
2252 }
2253 let cell = self.macro_event_cell(file);
2254 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2255 for event in events {
2256 if before_byte.is_some_and(|limit| event.byte() >= limit) {
2257 break;
2258 }
2259 self.apply_macro_event(file, event, environment, include_stack);
2260 }
2261 include_stack.remove(file);
2262 }
2263
2264 fn apply_macro_event(
2265 &self,
2266 file: &ProjectFile,
2267 event: &MacroEvent,
2268 environment: &mut MacroEnvironment,
2269 include_stack: &mut HashSet<ProjectFile>,
2270 ) {
2271 #[cfg(any(test, feature = "test-support"))]
2272 self.macro_event_application_count
2273 .fetch_add(1, Ordering::Relaxed);
2274 match event {
2275 MacroEvent::Define {
2276 name,
2277 binding,
2278 conditional,
2279 byte,
2280 } => {
2281 if *conditional {
2282 Self::merge_conditional_macro_definition(
2283 environment,
2284 name,
2285 binding,
2286 file,
2287 *byte,
2288 );
2289 } else {
2290 environment.insert(name.clone(), binding.clone());
2291 }
2292 }
2293 MacroEvent::Undef {
2294 name,
2295 conditional,
2296 byte,
2297 } => {
2298 if *conditional {
2299 if environment.binding(name).is_some() {
2300 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
2301 }
2302 } else {
2303 environment.remove(name);
2304 }
2305 }
2306 MacroEvent::Include {
2307 targets,
2308 conditional,
2309 byte,
2310 } => {
2311 if targets.is_empty() {
2312 environment.mark_unknown_names(file, *byte);
2313 return;
2314 }
2315 if *conditional || targets.len() > 1 {
2316 let mut ambiguous_seen = HashSet::default();
2317 for target in targets {
2318 self.mark_macro_events_ambiguous(
2319 target,
2320 environment,
2321 &mut ambiguous_seen,
2322 file,
2323 *byte,
2324 );
2325 }
2326 } else if let Some(target) = targets.first() {
2327 self.apply_macro_events(target, None, environment, include_stack);
2328 }
2329 }
2330 MacroEvent::Invalidate { byte } => {
2331 for binding in environment.bindings.values_mut() {
2332 *binding = MacroBinding::uncertain_from(binding, file, *byte);
2333 }
2334 }
2335 }
2336 }
2337
2338 fn mark_macro_events_ambiguous(
2339 &self,
2340 file: &ProjectFile,
2341 environment: &mut MacroEnvironment,
2342 include_stack: &mut HashSet<ProjectFile>,
2343 conditional_file: &ProjectFile,
2344 conditional_byte: usize,
2345 ) {
2346 if !include_stack.insert(file.clone()) {
2347 return;
2348 }
2349 if self.cpp.prepared_syntax(self.token, file).is_none() {
2350 environment.mark_unknown_names(conditional_file, conditional_byte);
2351 return;
2352 }
2353 match self.macro_include_protection(file) {
2354 MacroIncludeProtection::MacroGuard(guard) => {
2355 if environment
2356 .binding(&guard)
2357 .is_some_and(MacroBinding::is_exact)
2358 {
2359 return;
2360 }
2361 }
2362 MacroIncludeProtection::PragmaOnce => {
2363 if environment.applied_pragma_once_files.contains(file) {
2364 return;
2365 }
2366 environment
2367 .maybe_applied_pragma_once_files
2368 .insert(file.clone());
2369 }
2370 MacroIncludeProtection::None => {}
2371 }
2372 let cell = self.macro_event_cell(file);
2373 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2374 for event in events {
2375 #[cfg(any(test, feature = "test-support"))]
2376 self.macro_event_application_count
2377 .fetch_add(1, Ordering::Relaxed);
2378 match event {
2379 MacroEvent::Define { name, binding, .. } => {
2380 Self::merge_conditional_macro_definition(
2381 environment,
2382 name,
2383 binding,
2384 conditional_file,
2385 conditional_byte,
2386 );
2387 }
2388 MacroEvent::Undef { name, .. } => {
2389 if environment.binding(name).is_some() {
2390 environment.insert(
2391 name.clone(),
2392 MacroBinding::ambiguous(conditional_file, conditional_byte),
2393 );
2394 } else {
2395 environment.remove_known_undefined(name);
2396 }
2397 }
2398 MacroEvent::Include { targets, .. } => {
2399 if targets.is_empty() {
2400 environment.mark_unknown_names(conditional_file, conditional_byte);
2401 continue;
2402 }
2403 for target in targets {
2404 self.mark_macro_events_ambiguous(
2405 target,
2406 environment,
2407 include_stack,
2408 conditional_file,
2409 conditional_byte,
2410 );
2411 }
2412 }
2413 MacroEvent::Invalidate { .. } => {
2414 for binding in environment.bindings.values_mut() {
2415 *binding = MacroBinding::uncertain_from(
2416 binding,
2417 conditional_file,
2418 conditional_byte,
2419 );
2420 }
2421 }
2422 }
2423 }
2424 }
2425
2426 fn merge_conditional_macro_definition(
2427 environment: &mut MacroEnvironment,
2428 name: &str,
2429 possible_binding: &MacroBinding,
2430 conditional_file: &ProjectFile,
2431 conditional_byte: usize,
2432 ) {
2433 if environment.binding(name).is_some_and(|current| {
2438 current.definition != MacroDefinition::Unsupported
2439 && current.definition == possible_binding.definition
2440 }) {
2441 return;
2442 }
2443 environment.insert(
2444 name.to_string(),
2445 MacroBinding::ambiguous(conditional_file, conditional_byte),
2446 );
2447 }
2448
2449 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
2450 let cell = self
2451 .macro_include_protection_cells
2452 .lock()
2453 .expect("C++ include protection cache poisoned")
2454 .entry(file.clone())
2455 .or_default()
2456 .clone();
2457 cell.get_or_init(|| {
2458 self.cpp.prepared_syntax(self.token, file).map_or(
2459 MacroIncludeProtection::None,
2460 |prepared| {
2461 top_level_macro_include_protection(
2462 prepared.tree().root_node(),
2463 prepared.source(),
2464 )
2465 },
2466 )
2467 })
2468 .clone()
2469 }
2470
2471 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
2472 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2473 return Vec::new();
2474 };
2475 let source = prepared.source();
2476 let mut events = Vec::new();
2477 let mut stack = vec![prepared.tree().root_node()];
2478 while let Some(node) = stack.pop() {
2479 let conditional = has_preprocessor_conditional_ancestor(node, source);
2480 match node.kind() {
2481 "preproc_def" | "preproc_function_def" => {
2482 let Some(name) = node.child_by_field_name("name") else {
2483 continue;
2484 };
2485 let name = node_text(name, source).to_string();
2486 events.push(MacroEvent::Define {
2487 name,
2488 binding: MacroBinding {
2489 source: file.clone(),
2490 declaration_byte: node.start_byte(),
2491 definition: Self::decode_macro_definition(node, source),
2492 exact: true,
2493 },
2494 byte: node.start_byte(),
2495 conditional,
2496 });
2497 continue;
2498 }
2499 "preproc_include" => {
2500 let Some(path) = node.child_by_field_name("path") else {
2501 events.push(MacroEvent::Include {
2502 targets: Vec::new(),
2503 byte: node.start_byte(),
2504 conditional,
2505 });
2506 continue;
2507 };
2508 let targets =
2509 structured_include_path(path, source).map_or_else(Vec::new, |path| {
2510 resolve_include_targets_with_index(
2511 file,
2512 path,
2513 self.cpp.include_target_index(),
2514 )
2515 });
2516 if targets.is_empty() && path.kind() == "system_lib_string" {
2521 continue;
2522 }
2523 events.push(MacroEvent::Include {
2524 targets,
2525 byte: node.start_byte(),
2526 conditional,
2527 });
2528 continue;
2529 }
2530 "preproc_call" => {
2531 let Some(directive) = node.child_by_field_name("directive") else {
2532 continue;
2533 };
2534 if node_text(directive, source) != "#undef" {
2535 continue;
2536 }
2537 let name = node
2538 .child_by_field_name("argument")
2539 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
2540 if let Some(name) = name {
2541 events.push(MacroEvent::Undef {
2542 name,
2543 byte: node.start_byte(),
2544 conditional,
2545 });
2546 } else {
2547 events.push(MacroEvent::Invalidate {
2548 byte: node.start_byte(),
2549 });
2550 }
2551 continue;
2552 }
2553 _ => {}
2554 }
2555 for index in (0..node.named_child_count()).rev() {
2556 if let Some(child) = node.named_child(index) {
2557 stack.push(child);
2558 }
2559 }
2560 }
2561 events.sort_by_key(MacroEvent::byte);
2562 events
2563 }
2564
2565 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
2566 self.ordinary_type_import_cells
2567 .lock()
2568 .expect("C++ ordinary type import cache poisoned")
2569 .entry(file.clone())
2570 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
2571 .clone()
2572 }
2573
2574 pub fn project_using_index(
2575 &self,
2576 build: impl FnOnce() -> ProjectUsingIndex,
2577 ) -> &ProjectUsingIndex {
2578 self.project_using_index.get_or_init(build)
2579 }
2580
2581 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
2582 let mut files = self
2583 .visible_source_files_by_root
2584 .values()
2585 .flatten()
2586 .cloned()
2587 .collect::<HashSet<_>>()
2588 .into_iter()
2589 .collect::<Vec<_>>();
2590 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
2591 files
2592 }
2593
2594 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
2595 self.visible_source_files_by_root
2596 .get(root)
2597 .is_some_and(|files| files.contains(source))
2598 }
2599
2600 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
2601 let cached = self
2602 .visible_parser_alias_name_sets
2603 .read()
2604 .expect("visible parser alias-name cache poisoned")
2605 .get(file)
2606 .cloned();
2607 let cell = if let Some(cached) = cached {
2608 cached
2609 } else {
2610 let mut cells = self
2611 .visible_parser_alias_name_sets
2612 .write()
2613 .expect("visible parser alias-name cache poisoned");
2614 Arc::clone(
2615 cells
2616 .entry(file.clone())
2617 .or_insert_with(|| Arc::new(OnceLock::new())),
2618 )
2619 };
2620 cell.get_or_init(|| {
2621 #[cfg(any(test, feature = "test-support"))]
2622 self.visible_parser_alias_name_set_build_count
2623 .fetch_add(1, Ordering::Relaxed);
2624 let mut names = HashSet::default();
2625 let visible_files = self
2626 .visible_source_files_by_root
2627 .get(file)
2628 .cloned()
2629 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2630 for visible_file in visible_files {
2631 let aliases = {
2632 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2633 Arc::clone(
2634 cells
2635 .entry(visible_file.clone())
2636 .or_insert_with(|| Arc::new(OnceLock::new())),
2637 )
2638 };
2639 for alias in aliases
2640 .get_or_init(|| {
2641 #[cfg(any(test, feature = "test-support"))]
2642 {
2643 *self
2644 .alias_source_parse_counts
2645 .lock()
2646 .expect("alias source parse count lock")
2647 .entry(visible_file.clone())
2648 .or_default() += 1;
2649 }
2650 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2651 .into_boxed_slice()
2652 })
2653 .iter()
2654 {
2655 names.insert(alias.name.clone());
2656 }
2657 }
2658 names
2659 })
2660 .contains(name)
2661 }
2662
2663 fn visible_parser_alias_names_for_target(
2664 &self,
2665 file: &ProjectFile,
2666 target: &CodeUnit,
2667 ) -> HashSet<String> {
2668 let cell = {
2669 let mut cells = self
2670 .visible_parser_alias_target_names
2671 .lock()
2672 .expect("visible parser alias-target cache poisoned");
2673 Arc::clone(
2674 cells
2675 .entry(file.clone())
2676 .or_insert_with(|| Arc::new(OnceLock::new())),
2677 )
2678 };
2679 let target_name = cpp_name_for(target);
2680 cell.get_or_init(|| {
2681 #[cfg(any(test, feature = "test-support"))]
2682 self.visible_parser_alias_target_names_build_count
2683 .fetch_add(1, Ordering::Relaxed);
2684 let visible_files = self
2685 .visible_source_files_by_root
2686 .get(file)
2687 .cloned()
2688 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
2689 let mut names_by_target = HashMap::<String, HashSet<String>>::default();
2690 for visible_file in visible_files {
2691 let aliases = {
2692 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
2693 Arc::clone(
2694 cells
2695 .entry(visible_file.clone())
2696 .or_insert_with(|| Arc::new(OnceLock::new())),
2697 )
2698 };
2699 for alias in aliases
2700 .get_or_init(|| {
2701 #[cfg(any(test, feature = "test-support"))]
2702 {
2703 *self
2704 .alias_source_parse_counts
2705 .lock()
2706 .expect("alias source parse count lock")
2707 .entry(visible_file.clone())
2708 .or_default() += 1;
2709 }
2710 aliases_from_prepared_source(self.cpp, self.token, &visible_file)
2711 .into_boxed_slice()
2712 })
2713 .iter()
2714 {
2715 for target_name in parser_alias_target_names(alias) {
2716 names_by_target
2717 .entry(target_name)
2718 .or_default()
2719 .insert(alias.name.clone());
2720 }
2721 }
2722 }
2723 names_by_target
2724 })
2725 .get(&target_name)
2726 .cloned()
2727 .unwrap_or_default()
2728 }
2729
2730 fn callable_arities_for_target(
2731 &self,
2732 analyzer: &CppGraphSource<'_>,
2733 cpp: &dyn CppSource,
2734 file: &ProjectFile,
2735 prepared: &PreparedSyntaxTree,
2736 spec: &TargetSpec,
2737 ) -> Vec<ActivatedCallableArity> {
2738 let Some(signature) = spec.target.signature() else {
2739 return Vec::new();
2740 };
2741 let Some(candidates) = self
2742 .visible_by_identifier
2743 .get(file)
2744 .and_then(|by_name| by_name.get(&spec.member_name))
2745 else {
2746 return Vec::new();
2747 };
2748 let differing_candidates = candidates
2749 .iter()
2750 .filter(|candidate| {
2751 candidate.is_function()
2752 && candidate.fq_name() == spec.target.fq_name()
2753 && candidate.signature() == Some(signature)
2754 })
2755 .filter_map(|candidate| {
2756 analyzer
2757 .signature_metadata(candidate)
2758 .into_iter()
2759 .find_map(|metadata| metadata.callable_arity())
2760 .filter(|arity| Some(*arity) != spec.callable_arity)
2761 .map(|arity| (candidate, arity))
2762 })
2763 .collect::<Vec<_>>();
2764 if differing_candidates.is_empty() {
2765 return Vec::new();
2766 }
2767 let mut arities = Vec::with_capacity(differing_candidates.len());
2768 let reference = CallableReferenceContext {
2771 file,
2772 position: None,
2773 };
2774 for (candidate, candidate_arity) in differing_candidates {
2775 let declaration_activation = if candidate.source() == file {
2776 callable_declaration_activation_in_file(analyzer, prepared, candidate, &reference)
2777 } else {
2778 cpp.prepared_syntax(self.token, candidate.source())
2779 .and_then(|syntax| {
2780 callable_declaration_activation_in_file(
2781 analyzer,
2782 syntax.as_ref(),
2783 candidate,
2784 &reference,
2785 )
2786 })
2787 };
2788 let Some(declaration_activation) = declaration_activation else {
2789 continue;
2790 };
2791 let activation_byte = if candidate.source() == file {
2792 Some(declaration_activation)
2793 } else {
2794 self.include_activation_for_source(cpp, file, prepared, candidate.source())
2795 };
2796 if let Some(activation_byte) = activation_byte {
2797 arities.push(ActivatedCallableArity {
2798 activation_byte,
2799 arity: candidate_arity,
2800 });
2801 }
2802 }
2803 arities
2804 }
2805
2806 fn callable_parameter_macro_arity(
2807 &self,
2808 target: &CodeUnit,
2809 signature: Option<&str>,
2810 ) -> Option<CallableArity> {
2811 let parameter_types = cpp_signature_param_types(signature?)?;
2812 let [macro_name] = parameter_types.as_slice() else {
2813 return None;
2814 };
2815 if macro_name.is_empty()
2816 || !macro_name
2817 .chars()
2818 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
2819 {
2820 return None;
2821 }
2822 let cache_key = (target.source().clone(), macro_name.clone());
2823 if let Some(cached) = self
2824 .callable_parameter_macro_arities
2825 .lock()
2826 .expect("C++ callable parameter-macro arity cache poisoned")
2827 .get(&cache_key)
2828 .copied()
2829 {
2830 return cached;
2831 }
2832 let mut visible_files = HashSet::default();
2833 collect_include_closure(
2834 &self.cpp_source(),
2835 self.cpp.include_target_index(),
2836 target.source(),
2837 &mut visible_files,
2838 None,
2839 );
2840 let mut arities = Vec::new();
2841 for visible_file in visible_files {
2842 let cell = self.macro_event_cell(&visible_file);
2843 for event in
2844 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
2845 {
2846 let MacroEvent::Define { name, binding, .. } = event else {
2847 continue;
2848 };
2849 if name != macro_name {
2850 continue;
2851 }
2852 let MacroDefinition::Object { replacement } = &binding.definition else {
2853 continue;
2854 };
2855 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
2856 continue;
2857 };
2858 if !arities.contains(&arity) {
2859 arities.push(arity);
2860 }
2861 }
2862 }
2863 let resolved = (|| {
2864 let required = arities
2865 .iter()
2866 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
2867 .min()?;
2868 let total = arities.iter().map(|arity| arity.total()).max()?;
2869 let repeated = arities
2870 .iter()
2871 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
2872 Some(CallableArity::new(required, total, repeated))
2877 })();
2878 self.callable_parameter_macro_arities
2879 .lock()
2880 .expect("C++ callable parameter-macro arity cache poisoned")
2881 .insert(cache_key, resolved);
2882 resolved
2883 }
2884
2885 pub fn include_activation_for_source(
2886 &self,
2887 cpp: &dyn CppSource,
2888 file: &ProjectFile,
2889 prepared: &PreparedSyntaxTree,
2890 donor_source: &ProjectFile,
2891 ) -> Option<usize> {
2892 let key = (file.clone(), donor_source.clone());
2893 if let Some(cached) = self
2894 .include_activation_cells
2895 .lock()
2896 .expect("C++ include activation cache poisoned")
2897 .get(&key)
2898 .copied()
2899 {
2900 return cached;
2901 }
2902 #[cfg(any(test, feature = "test-support"))]
2903 self.include_activation_build_count
2904 .fetch_add(1, Ordering::Relaxed);
2905 let activation = find_include_activation(cpp, self.token, file, prepared, donor_source);
2906 let mut cells = self
2907 .include_activation_cells
2908 .lock()
2909 .expect("C++ include activation cache poisoned");
2910 *cells.entry(key).or_insert(activation)
2911 }
2912
2913 pub fn conditional_include_projections_for_source(
2914 &self,
2915 file: &ProjectFile,
2916 prepared: &PreparedSyntaxTree,
2917 donor_source: &ProjectFile,
2918 ) -> Arc<[ConditionalIncludeProjection]> {
2919 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
2920 let cell = self
2921 .conditional_include_projection_cells
2922 .lock()
2923 .expect("C++ conditional include projection cache poisoned")
2924 .entry(file.clone())
2925 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
2926 .clone();
2927 let index = cell.get_or_build_pool_independent(|| {
2928 #[cfg(any(test, feature = "test-support"))]
2929 self.conditional_include_projection_index_build_count
2930 .fetch_add(1, Ordering::Relaxed);
2931 find_conditional_include_projection_index(self.cpp, self.token, file, prepared, &|| {
2932 #[cfg(any(test, feature = "test-support"))]
2933 self.conditional_include_projection_state_count
2934 .fetch_add(1, Ordering::Relaxed);
2935 })
2936 });
2937 index
2938 .get(donor_source)
2939 .cloned()
2940 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
2941 }
2942
2943 #[cfg(any(test, feature = "test-support"))]
2944 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
2945 (
2946 self.conditional_include_projection_index_build_count
2947 .load(Ordering::Relaxed),
2948 self.conditional_include_projection_state_count
2949 .load(Ordering::Relaxed),
2950 )
2951 }
2952
2953 #[cfg(any(test, feature = "test-support"))]
2954 pub fn include_activation_build_count_for_test(&self) -> usize {
2955 self.include_activation_build_count.load(Ordering::Relaxed)
2956 }
2957
2958 #[cfg(any(test, feature = "test-support"))]
2959 pub fn note_using_donor_activation_for_test(&self) {
2960 self.using_donor_activation_count
2961 .fetch_add(1, Ordering::Relaxed);
2962 }
2963
2964 #[cfg(not(any(test, feature = "test-support")))]
2965 pub fn note_using_donor_activation_for_test(&self) {}
2966
2967 #[cfg(any(test, feature = "test-support"))]
2968 pub fn note_using_namespace_lookup_for_test(&self) {
2969 self.using_namespace_lookup_count
2970 .fetch_add(1, Ordering::Relaxed);
2971 }
2972
2973 #[cfg(not(any(test, feature = "test-support")))]
2974 pub fn note_using_namespace_lookup_for_test(&self) {}
2975
2976 #[cfg(any(test, feature = "test-support"))]
2977 pub fn note_using_name_candidate_inspection_for_test(&self) {
2978 self.using_name_candidate_inspection_count
2979 .fetch_add(1, Ordering::Relaxed);
2980 }
2981
2982 #[cfg(not(any(test, feature = "test-support")))]
2983 pub fn note_using_name_candidate_inspection_for_test(&self) {}
2984
2985 #[cfg(any(test, feature = "test-support"))]
2986 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
2987 (
2988 self.using_donor_activation_count.load(Ordering::Relaxed),
2989 self.using_namespace_lookup_count.load(Ordering::Relaxed),
2990 self.callable_reference_spec_build_count
2991 .load(Ordering::Relaxed),
2992 self.using_name_candidate_inspection_count
2993 .load(Ordering::Relaxed),
2994 )
2995 }
2996
2997 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2998 file == target.source()
2999 || self
3000 .visible_by_file
3001 .get(file)
3002 .is_some_and(|visible| visible.contains(target))
3003 }
3004
3005 pub fn declaration_visible_at(
3017 &self,
3018 analyzer: &CppGraphSource<'_>,
3019 file: &ProjectFile,
3020 declaration: &CodeUnit,
3021 reference_byte: usize,
3022 ) -> bool {
3023 let reference_guards = OnceCell::new();
3024 self.visible_identifier_candidates(file, declaration.identifier())
3025 .filter(|candidate| {
3026 self.same_logical_callable(analyzer, candidate, declaration)
3027 || flattened_macro_namespace_declaration_matches(
3028 analyzer,
3029 self.cpp,
3030 file,
3031 candidate,
3032 declaration,
3033 reference_byte,
3034 )
3035 })
3036 .any(|candidate| {
3037 self.physical_declaration_visible_at(
3038 analyzer,
3039 file,
3040 candidate,
3041 reference_byte,
3042 &reference_guards,
3043 )
3044 })
3045 }
3046
3047 pub fn callable_arity_at_reference(
3048 &self,
3049 analyzer: &CppGraphSource<'_>,
3050 file: &ProjectFile,
3051 candidate: &CodeUnit,
3052 reference_byte: usize,
3053 ) -> Option<CallableArity> {
3054 let key = (file.clone(), logical_symbol_key(candidate));
3055 let cell = self
3056 .callable_reference_specs
3057 .lock()
3058 .expect("C++ callable reference-spec cache poisoned")
3059 .entry(key)
3060 .or_default()
3061 .clone();
3062 let spec = cell.get_or_init(|| {
3063 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3064 let spec = TargetSpec::from_target(analyzer, candidate)?;
3065 let spec = spec
3066 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
3067 .into_owned();
3068 #[cfg(any(test, feature = "test-support"))]
3069 self.callable_reference_spec_build_count
3070 .fetch_add(1, Ordering::Relaxed);
3071 Some(spec)
3072 });
3073 spec.as_ref()?.callable_arity_at(reference_byte)
3074 }
3075
3076 fn physical_declaration_visible_at(
3077 &self,
3078 analyzer: &CppGraphSource<'_>,
3079 file: &ProjectFile,
3080 declaration: &CodeUnit,
3081 reference_byte: usize,
3082 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
3083 ) -> bool {
3084 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3085 return false;
3086 };
3087 let reference = CallableReferenceContext {
3088 file,
3089 position: Some(CallableReferencePosition {
3090 prepared: prepared.as_ref(),
3091 byte: reference_byte,
3092 guards: reference_guards,
3093 }),
3094 };
3095 if declaration.source() == file {
3096 return callable_declaration_activation_in_file(
3097 analyzer,
3098 prepared.as_ref(),
3099 declaration,
3100 &reference,
3101 )
3102 .or_else(|| {
3103 self.exhaustive_guard_family_activation(
3104 analyzer,
3105 prepared.as_ref(),
3106 declaration,
3107 &reference,
3108 )
3109 })
3110 .is_some_and(|activation| activation < reference_byte);
3111 }
3112 let Some(donor_syntax) = self.cpp.prepared_syntax(self.token, declaration.source()) else {
3113 return false;
3114 };
3115 if callable_declaration_activation_in_file(
3116 analyzer,
3117 donor_syntax.as_ref(),
3118 declaration,
3119 &reference,
3120 )
3121 .or_else(|| {
3122 self.exhaustive_guard_family_activation(
3123 analyzer,
3124 donor_syntax.as_ref(),
3125 declaration,
3126 &reference,
3127 )
3128 })
3129 .is_none()
3130 {
3131 return false;
3132 }
3133 declaration_guard_requirements(analyzer, self.cpp, declaration)
3134 .into_iter()
3135 .any(|(_, declaration_guards)| {
3136 self.foreign_declaration_reachable_at_reference(
3137 file,
3138 prepared.as_ref(),
3139 declaration.source(),
3140 &declaration_guards,
3141 reference.guards(),
3142 reference_byte,
3143 )
3144 })
3145 }
3146
3147 pub fn external_type_candidate_visible_at(
3148 &self,
3149 file: &ProjectFile,
3150 candidate: &CodeUnit,
3151 reference_byte: usize,
3152 ) -> bool {
3153 if candidate.source() == file {
3154 return true;
3155 }
3156 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3157 return false;
3158 };
3159 self.visible_identifier_candidates(file, candidate.identifier())
3160 .filter(|peer| same_logical_symbol(candidate, peer))
3161 .any(|peer| {
3162 peer.source() == file
3163 || self
3164 .include_activation_for_source(
3165 self.cpp,
3166 file,
3167 prepared.as_ref(),
3168 peer.source(),
3169 )
3170 .is_some_and(|activation| activation <= reference_byte)
3171 })
3172 }
3173
3174 pub fn external_type_declaration_visible_at(
3175 &self,
3176 file: &ProjectFile,
3177 candidate: &CodeUnit,
3178 reference_byte: usize,
3179 ) -> bool {
3180 if candidate.source() == file {
3181 return true;
3182 }
3183 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3184 return false;
3185 };
3186 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
3187 .is_some_and(|activation| activation <= reference_byte)
3188 }
3189
3190 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
3209 if let Some(cached) = self
3210 .compile_proven_guard_cells
3211 .lock()
3212 .expect("C++ compile-proven guard cache poisoned")
3213 .get(file)
3214 {
3215 return Arc::clone(cached);
3216 }
3217 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
3218 Some(names) => names,
3219 None => {
3220 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
3221 let seed = translation_units.next().and_then(|translation_unit| {
3222 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
3223 });
3224 match seed {
3225 None => HashSet::default(),
3226 Some(mut names) => {
3227 for translation_unit in translation_units {
3228 let Some(reached) = context_fact_names(
3229 self.cpp.compile_contexts_for(&translation_unit),
3230 ) else {
3231 names.clear();
3232 break;
3233 };
3234 names.retain(|name| reached.contains(name));
3235 if names.is_empty() {
3236 break;
3237 }
3238 }
3239 names
3240 }
3241 }
3242 }
3243 };
3244 let proven = Arc::new(
3245 names
3246 .into_iter()
3247 .map(PreprocessorGuard::Defined)
3248 .collect::<HashSet<_>>(),
3249 );
3250 self.compile_proven_guard_cells
3251 .lock()
3252 .expect("C++ compile-proven guard cache poisoned")
3253 .insert(file.clone(), Arc::clone(&proven));
3254 proven
3255 }
3256
3257 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
3264 if !self.cpp.compile_contexts_for(file).is_empty() {
3265 return false;
3266 }
3267 let translation_units = self.cpp.reaching_translation_units(file);
3268 translation_units.is_empty()
3269 || translation_units
3270 .iter()
3271 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
3272 }
3273
3274 pub fn miss_requires_compile_context(
3286 &self,
3287 file: &ProjectFile,
3288 identifier: &str,
3289 reference: Node<'_>,
3290 ) -> bool {
3291 if !self.compile_context_is_absent(file) {
3292 return false;
3293 }
3294 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3295 return false;
3296 };
3297 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3298 let reference_byte = reference.start_byte();
3299 let mut sources = self
3300 .visible_identifier_candidates(file, identifier)
3301 .map(CodeUnit::source)
3302 .filter(|source| *source != file)
3303 .collect::<Vec<_>>();
3304 sources.sort();
3305 sources.dedup();
3306 sources.into_iter().any(|declaration_source| {
3307 self.conditional_include_projections_for_source(
3308 file,
3309 prepared.as_ref(),
3310 declaration_source,
3311 )
3312 .iter()
3313 .any(|projection| {
3314 projection.activation_byte <= reference_byte
3315 && !guard_requirements_hold_at_reference(
3316 &projection.required_guards,
3317 reference_guards.as_ref(),
3318 )
3319 && guards_compatible_at_reference(
3320 &projection.required_guards,
3321 reference_guards.as_ref(),
3322 )
3323 })
3324 })
3325 }
3326
3327 fn foreign_declaration_reachable_at_reference(
3338 &self,
3339 file: &ProjectFile,
3340 prepared: &PreparedSyntaxTree,
3341 declaration_source: &ProjectFile,
3342 declaration_guards: &HashSet<PreprocessorGuard>,
3343 reference_guards: Option<&HashSet<PreprocessorGuard>>,
3344 reference_byte: usize,
3345 ) -> bool {
3346 let proven = self.compile_proven_guards(file);
3352 let augmented;
3353 let reference_guards = match reference_guards {
3354 Some(active) if !proven.is_empty() => {
3355 augmented = active.union(&proven).cloned().collect();
3356 Some(&augmented)
3357 }
3358 other => other,
3359 };
3360 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
3361 return false;
3362 }
3363 if self
3364 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
3365 .is_some_and(|activation| activation <= reference_byte)
3366 {
3367 return true;
3368 }
3369 self.conditional_include_projections_for_source(file, prepared, declaration_source)
3370 .iter()
3371 .any(|projection| {
3372 projection.activation_byte <= reference_byte
3373 && guard_requirements_hold_at_reference(
3374 &projection.required_guards,
3375 reference_guards,
3376 )
3377 && self.preprocessor_guards_stable_between(
3378 file,
3379 projection.activation_byte,
3380 reference_byte,
3381 &projection.required_guards,
3382 )
3383 })
3384 }
3385
3386 pub fn external_type_candidate_visible_in_context(
3387 &self,
3388 analyzer: &CppGraphSource<'_>,
3389 file: &ProjectFile,
3390 candidate: &CodeUnit,
3391 reference: Node<'_>,
3392 ) -> bool {
3393 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3394 return false;
3395 };
3396 let macro_environment = self.macro_environment(file, reference.start_byte());
3397 let reference_guards = preprocessor_guard_environment(reference, prepared.source())
3398 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
3399
3400 let directly_visible = self
3401 .visible_identifier_candidates(file, candidate.identifier())
3402 .filter(|peer| same_logical_symbol(candidate, peer))
3403 .any(|peer| {
3404 declaration_guard_requirements(analyzer, self.cpp, peer)
3405 .into_iter()
3406 .any(|(declaration_byte, declaration_guards)| {
3407 if peer.source() == file {
3408 return declaration_byte < reference.start_byte()
3409 && guard_requirements_hold_at_reference(
3410 &declaration_guards,
3411 reference_guards.as_ref(),
3412 )
3413 && self.preprocessor_guards_stable_between(
3414 file,
3415 declaration_byte,
3416 reference.start_byte(),
3417 &declaration_guards,
3418 );
3419 }
3420 self.foreign_declaration_reachable_at_reference(
3421 file,
3422 prepared.as_ref(),
3423 peer.source(),
3424 &declaration_guards,
3425 reference_guards.as_ref(),
3426 reference.start_byte(),
3427 )
3428 })
3429 });
3430 let complementary = self
3431 .visible_identifier_candidates(file, candidate.identifier())
3432 .filter(|peer| {
3433 peer.kind() == candidate.kind()
3434 && peer.fq_name() == candidate.fq_name()
3435 && peer.source() == candidate.source()
3436 })
3437 .collect::<Vec<_>>();
3438 let candidate_branch_compatible = reference_guards.as_ref().is_some_and(|active| {
3443 declaration_guard_requirements(analyzer, self.cpp, candidate)
3444 .iter()
3445 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
3446 });
3447 let complementary_visible = candidate_branch_compatible
3448 && self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate)
3449 && if candidate.source() == file {
3450 declaration_guard_requirements(analyzer, self.cpp, candidate)
3451 .iter()
3452 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
3453 } else {
3454 self.include_activation_for_source(
3455 self.cpp,
3456 file,
3457 prepared.as_ref(),
3458 candidate.source(),
3459 )
3460 .is_some_and(|activation| activation <= reference.start_byte())
3461 };
3462 directly_visible || complementary_visible
3463 }
3464
3465 pub fn is_exhaustive_same_fqn_type_declaration_family(
3466 &self,
3467 analyzer: &CppGraphSource<'_>,
3468 file: &ProjectFile,
3469 candidate: &CodeUnit,
3470 ) -> bool {
3471 let candidates = self
3472 .visible_identifier_candidates(file, candidate.identifier())
3473 .filter(|peer| {
3474 peer.kind() == candidate.kind()
3475 && peer.fq_name() == candidate.fq_name()
3476 && peer.source() == candidate.source()
3477 })
3478 .collect::<Vec<_>>();
3479 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
3480 }
3481
3482 pub fn dependent_member_pointer_alias_visible_in_context(
3497 &self,
3498 analyzer: &CppGraphSource<'_>,
3499 file: &ProjectFile,
3500 candidate: &CodeUnit,
3501 owner_components: &[String],
3502 reference: Node<'_>,
3503 ) -> bool {
3504 if !analyzer
3505 .type_alias_provider()
3506 .is_some_and(|provider| provider.is_type_alias(candidate))
3507 {
3508 return false;
3509 }
3510 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
3511 return false;
3512 };
3513 if terminal != candidate.identifier()
3514 || canonical_cpp_scope_components(candidate) != owner_components
3515 {
3516 return false;
3517 }
3518 let Some(expected_parent_fq_name) =
3519 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
3520 else {
3521 return false;
3522 };
3523 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
3524 return false;
3525 };
3526 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
3527 || parent_anchor.source() != candidate.source()
3528 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
3529 {
3530 return false;
3531 }
3532
3533 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
3539 || candidate.source() == file
3540 && !analyzer
3541 .ranges(candidate)
3542 .iter()
3543 .any(|range| range.start_byte < reference.start_byte())
3544 {
3545 return false;
3546 }
3547
3548 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
3549 if candidate_guards.is_empty() {
3550 return false;
3551 }
3552 let same_guard_sets =
3553 |left: &[(usize, HashSet<PreprocessorGuard>)],
3554 right: &[(usize, HashSet<PreprocessorGuard>)]| {
3555 left.iter().all(|(_, left_guards)| {
3556 right
3557 .iter()
3558 .any(|(_, right_guards)| left_guards == right_guards)
3559 })
3560 };
3561 let parent_candidates = self
3562 .visible_identifier_candidates(file, parent_anchor.identifier())
3563 .filter(|peer| {
3564 peer.kind() == parent_anchor.kind()
3565 && peer.fq_name() == expected_parent_fq_name.as_str()
3566 && peer.source() == parent_anchor.source()
3567 && canonical_cpp_scope_components(peer) == owner_prefix
3568 })
3569 .filter_map(|peer| {
3570 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
3571 (candidate_guards.len() == parent_guards.len()
3572 && same_guard_sets(&candidate_guards, &parent_guards)
3573 && same_guard_sets(&parent_guards, &candidate_guards))
3574 .then(|| (peer.clone(), parent_guards))
3575 })
3576 .collect::<Vec<_>>();
3577 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
3578 return false;
3579 };
3580
3581 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3582 return false;
3583 };
3584 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
3585 else {
3586 return false;
3587 };
3588 if !candidate_guards.iter().any(|(_, target_guards)| {
3593 guards_compatible_at_reference(target_guards, Some(&reference_guards))
3594 && (candidate.source() != file
3595 || self.preprocessor_guards_stable_between(
3596 file,
3597 0,
3598 reference.start_byte(),
3599 target_guards,
3600 ))
3601 }) {
3602 return false;
3603 }
3604
3605 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
3606 }
3607
3608 pub fn external_type_candidate_guard_compatible_in_context(
3618 &self,
3619 analyzer: &CppGraphSource<'_>,
3620 file: &ProjectFile,
3621 candidate: &CodeUnit,
3622 reference: Node<'_>,
3623 ) -> bool {
3624 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3625 return false;
3626 };
3627 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
3628
3629 self.visible_identifier_candidates(file, candidate.identifier())
3630 .filter(|peer| same_logical_symbol(candidate, peer))
3631 .any(|peer| {
3632 declaration_guard_requirements(analyzer, self.cpp, peer)
3633 .into_iter()
3634 .any(|(declaration_byte, declaration_guards)| {
3635 if peer.source() == file {
3636 let (start, end) = if declaration_byte <= reference.start_byte() {
3637 (declaration_byte, reference.start_byte())
3638 } else {
3639 (reference.start_byte(), declaration_byte)
3640 };
3641 return guard_requirements_hold_at_reference(
3642 &declaration_guards,
3643 reference_guards.as_ref(),
3644 ) && self.preprocessor_guards_stable_between(
3645 file,
3646 start,
3647 end,
3648 &declaration_guards,
3649 );
3650 }
3651 self.foreign_declaration_reachable_at_reference(
3652 file,
3653 prepared.as_ref(),
3654 peer.source(),
3655 &declaration_guards,
3656 reference_guards.as_ref(),
3657 reference.start_byte(),
3658 )
3659 })
3660 })
3661 }
3662
3663 pub fn same_file_callable_guard_compatible_ignoring_order(
3671 &self,
3672 analyzer: &CppGraphSource<'_>,
3673 file: &ProjectFile,
3674 candidate: &CodeUnit,
3675 reference: Node<'_>,
3676 ) -> bool {
3677 if candidate.source() != file || !candidate.is_callable() {
3678 return false;
3679 }
3680 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3681 return false;
3682 };
3683 let guards = OnceCell::new();
3684 let context = CallableReferenceContext {
3685 file,
3686 position: Some(CallableReferencePosition {
3687 prepared: prepared.as_ref(),
3688 byte: reference.start_byte(),
3689 guards: &guards,
3690 }),
3691 };
3692 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
3693 .into_iter()
3694 .any(|declaration| {
3695 callable_preprocessor_context_is_visible_for_reference(
3696 declaration,
3697 prepared.source(),
3698 &context,
3699 )
3700 })
3701 }
3702
3703 pub fn type_candidate_may_be_visible_before_reference(
3704 &self,
3705 analyzer: &CppGraphSource<'_>,
3706 file: &ProjectFile,
3707 candidate: &CodeUnit,
3708 reference_byte: usize,
3709 ) -> bool {
3710 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
3711 return false;
3712 };
3713 let root = prepared.tree().root_node();
3714 let end_byte = reference_byte
3715 .saturating_add(1)
3716 .min(prepared.source().len());
3717 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
3718 return false;
3719 };
3720 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
3721 }
3722
3723 pub fn preprocessor_guards_stable_between(
3724 &self,
3725 file: &ProjectFile,
3726 start_byte: usize,
3727 end_byte: usize,
3728 guards: &HashSet<PreprocessorGuard>,
3729 ) -> bool {
3730 if guards.is_empty() || start_byte >= end_byte {
3731 return true;
3732 }
3733 let cell = self.macro_event_cell(file);
3734 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3735 let mut visited = HashSet::from_iter([file.clone()]);
3736 !events.iter().any(|event| {
3737 event.byte() >= start_byte
3738 && event.byte() < end_byte
3739 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
3740 })
3741 }
3742
3743 fn macro_event_may_mutate_guards(
3744 &self,
3745 event: &MacroEvent,
3746 guards: &HashSet<PreprocessorGuard>,
3747 visited: &mut HashSet<ProjectFile>,
3748 ) -> bool {
3749 match event {
3750 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
3751 guards.iter().any(|guard| guard.may_depend_on_macro(name))
3752 }
3753 MacroEvent::Include { targets, .. } => {
3754 targets.is_empty()
3755 || targets
3756 .iter()
3757 .any(|target| self.source_may_mutate_guards(target, guards, visited))
3758 }
3759 MacroEvent::Invalidate { .. } => true,
3760 }
3761 }
3762
3763 fn source_may_mutate_guards(
3764 &self,
3765 file: &ProjectFile,
3766 guards: &HashSet<PreprocessorGuard>,
3767 visited: &mut HashSet<ProjectFile>,
3768 ) -> bool {
3769 if !visited.insert(file.clone()) {
3770 return false;
3771 }
3772 let cell = self.macro_event_cell(file);
3773 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3774 events
3775 .iter()
3776 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
3777 }
3778
3779 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
3780 let normalized = normalize_reference_name(raw_name)?;
3781 self.type_candidates(file, &normalized)
3782 .into_iter()
3783 .next()
3784 .cloned()
3785 }
3786
3787 pub fn unique_visible_parameter_type_fallback(
3796 &self,
3797 analyzer: &CppGraphSource<'_>,
3798 file: &ProjectFile,
3799 node: Node<'_>,
3800 source: &str,
3801 ) -> Option<CodeUnit> {
3802 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
3803 return None;
3804 }
3805 let name = node_text(node, source);
3806 let candidates = self
3807 .visible_identifier_candidates(file, name)
3808 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
3809 .filter(|candidate| {
3810 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
3811 })
3812 .collect::<Vec<_>>();
3813 self.unique_canonical_type_candidate(analyzer, file, &candidates)
3814 }
3815
3816 pub fn resolve_type_node_result(
3817 &self,
3818 file: &ProjectFile,
3819 node: Node<'_>,
3820 source: &str,
3821 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
3822 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
3823 return Ok(None);
3824 };
3825 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
3826 return Ok(Some(primary));
3827 };
3828 self.resolve_template_arguments(file, primary, &arguments)
3829 .map(Some)
3830 }
3831
3832 pub fn resolve_type_node_primary(
3833 &self,
3834 file: &ProjectFile,
3835 node: Node<'_>,
3836 source: &str,
3837 ) -> Option<CodeUnit> {
3838 let components = cpp_type_name_components(node, source)?;
3839 self.resolve_type(file, &components.join("::"))
3840 }
3841
3842 pub fn resolve_template_arguments(
3843 &self,
3844 file: &ProjectFile,
3845 primary: CodeUnit,
3846 arguments: &[CppTemplateExpression],
3847 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3848 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
3849 }
3850
3851 fn resolve_template_arguments_inner(
3852 &self,
3853 file: &ProjectFile,
3854 primary: CodeUnit,
3855 arguments: &[CppTemplateExpression],
3856 seen_aliases: &mut HashSet<CodeUnit>,
3857 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3858 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
3859 && let Some(alias_target) = &metadata.alias_target
3860 {
3861 if !seen_aliases.insert(primary.clone()) {
3862 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
3863 }
3864 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
3865 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3866 let target_name = alias_target.components.join("::");
3867 let target_primary = if alias_target.global {
3868 unique_logical_type_candidate(self.type_candidates(file, &target_name))
3869 } else {
3870 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
3871 };
3872 let Some(target_primary) = target_primary else {
3873 return Ok(primary);
3877 };
3878 let Some(target_arguments) = &alias_target.arguments else {
3879 return Ok(target_primary);
3880 };
3881 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
3882 .ok_or(CppTemplateResolutionError::Substitution)?;
3883 return self.resolve_template_arguments_inner(
3884 file,
3885 target_primary,
3886 &target_arguments,
3887 seen_aliases,
3888 );
3889 }
3890
3891 let primary_fq_name = self
3892 .cpp_template_metadata
3893 .get(&primary)
3894 .map(|metadata| metadata.primary_fq_name.clone())
3895 .unwrap_or_else(|| primary.fq_name());
3896 let has_specialization_metadata = self
3897 .cpp_template_families
3898 .get(&primary_fq_name)
3899 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
3900 if !has_specialization_metadata {
3901 return Ok(primary);
3902 }
3903 self.select_template_specialization(file, &primary, arguments)
3904 }
3905
3906 fn select_template_specialization(
3907 &self,
3908 file: &ProjectFile,
3909 resolved: &CodeUnit,
3910 explicit_arguments: &[CppTemplateExpression],
3911 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
3912 let primary_fq_name = self
3913 .cpp_template_metadata
3914 .get(resolved)
3915 .map(|metadata| metadata.primary_fq_name.clone())
3916 .unwrap_or_else(|| resolved.fq_name());
3917 let family = self
3918 .cpp_template_families
3919 .get(&primary_fq_name)
3920 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3921 let primary_candidates = family
3922 .iter()
3923 .filter_map(|unit| {
3924 let metadata = self.cpp_template_metadata.get(unit)?;
3925 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
3926 })
3927 .collect::<Vec<_>>();
3928 let primary_unit = primary_candidates
3929 .iter()
3930 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
3931 .or_else(|| {
3932 primary_candidates
3933 .iter()
3934 .map(|(unit, _)| *unit)
3935 .min_by_key(|unit| {
3936 (
3937 unit.source().to_string(),
3938 unit.signature().unwrap_or_default(),
3939 )
3940 })
3941 })
3942 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3943 let primary_parameters =
3944 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
3945 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
3946 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
3947 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
3948
3949 let mut applicable = Vec::new();
3950 for unit in family {
3951 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
3952 continue;
3953 };
3954 if metadata.is_primary() || !self.is_visible(file, unit) {
3955 continue;
3956 }
3957 if !cpp_specialization_matches(metadata, &expanded) {
3958 continue;
3959 }
3960 applicable.push((unit, metadata));
3961 }
3962 if applicable.is_empty() {
3963 return Ok(primary_unit.clone());
3964 }
3965
3966 let winners = applicable
3971 .iter()
3972 .filter(|(candidate, candidate_metadata)| {
3973 applicable.iter().all(|(other, other_metadata)| {
3974 same_visible_symbol(candidate, other)
3975 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
3976 })
3977 })
3978 .copied()
3979 .collect::<Vec<_>>();
3980 let Some((selected, _)) = winners.first() else {
3981 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3984 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
3985 });
3986 };
3987 if winners
3988 .iter()
3989 .any(|(unit, _)| !same_visible_symbol(unit, selected))
3990 {
3991 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
3992 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
3993 });
3994 }
3995 Ok((*selected).clone())
3996 }
3997
3998 pub fn resolve_type_components_lexically(
3999 &self,
4000 analyzer: &CppGraphSource<'_>,
4001 file: &ProjectFile,
4002 components: &[String],
4003 global: bool,
4004 lexical_scope: &[String],
4005 ) -> LexicalTypeResolution {
4006 self.resolve_type_components_lexically_inner(
4007 analyzer,
4008 file,
4009 components,
4010 global,
4011 lexical_scope,
4012 TypeCandidateResolution::Canonical,
4013 )
4014 }
4015
4016 pub fn resolve_type_components_lexically_for_forward(
4017 &self,
4018 analyzer: &CppGraphSource<'_>,
4019 file: &ProjectFile,
4020 components: &[String],
4021 global: bool,
4022 lexical_scope: &[String],
4023 ) -> LexicalTypeResolution {
4024 self.resolve_type_components_lexically_inner(
4025 analyzer,
4026 file,
4027 components,
4028 global,
4029 lexical_scope,
4030 TypeCandidateResolution::PreserveAlias,
4031 )
4032 }
4033
4034 pub fn resolve_type_components_lexically_for_target(
4035 &self,
4036 analyzer: &CppGraphSource<'_>,
4037 file: &ProjectFile,
4038 components: &[String],
4039 global: bool,
4040 lexical_scope: &[String],
4041 target: &CodeUnit,
4042 ) -> LexicalTypeResolution {
4043 #[cfg(any(test, feature = "test-support"))]
4044 self.target_preserving_type_resolution_count
4045 .fetch_add(1, Ordering::Relaxed);
4046 self.resolve_type_components_lexically_inner(
4047 analyzer,
4048 file,
4049 components,
4050 global,
4051 lexical_scope,
4052 TypeCandidateResolution::PreserveTarget(target),
4053 )
4054 }
4055
4056 pub fn coarse_unqualified_type_reference_may_resolve(
4057 &self,
4058 file: &ProjectFile,
4059 name: &str,
4060 ) -> bool {
4061 if name.is_empty() {
4062 return true;
4063 }
4064 self.visible_identifier_candidates(file, name)
4065 .any(|candidate| candidate.kind() == CodeUnitType::Class || is_type_alias(candidate))
4066 || self.visible_parser_alias_name_is_visible(file, name)
4067 }
4068
4069 #[allow(clippy::too_many_arguments)]
4070 pub fn structured_type_reference_may_resolve_to_target(
4071 &self,
4072 analyzer: &CppGraphSource<'_>,
4073 file: &ProjectFile,
4074 components: &[String],
4075 global: bool,
4076 lexical_scope: &[String],
4077 target: &CodeUnit,
4078 ) -> bool {
4079 if components.is_empty() {
4080 return true;
4081 }
4082 let Some(terminal) = components.last() else {
4083 return true;
4084 };
4085 let parser_alias_visible = self.visible_parser_alias_name_is_visible(file, terminal);
4086 if parser_alias_visible
4087 && self.parser_alias_resolves_to_type(analyzer, file, terminal, target)
4088 {
4089 return true;
4090 }
4091 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
4092 .map(|qualified| qualified.join("::"))
4093 .collect::<Vec<_>>();
4094 let target_name = cpp_name_for(target);
4095 if qualified_tiers
4096 .iter()
4097 .any(|qualified| qualified == &target_name)
4098 {
4099 return true;
4100 }
4101
4102 let mut saw_shape_candidate = parser_alias_visible;
4103 for candidate in self.visible_identifier_candidates(file, terminal) {
4104 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4105 {
4106 continue;
4107 }
4108 let candidate_name = cpp_name_for(candidate);
4109 let shape_matches = if global || components.len() > 1 {
4110 qualified_tiers
4111 .iter()
4112 .any(|qualified| qualified == &candidate_name)
4113 } else {
4114 true
4115 };
4116 if !shape_matches {
4117 continue;
4118 }
4119 saw_shape_candidate = true;
4120 if same_visible_symbol(candidate, target)
4121 || self.compatible_primary_template_redeclarations(candidate, target)
4122 || (declared_type_alias(analyzer, candidate)
4123 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
4124 {
4125 return true;
4126 }
4127 }
4128
4129 !saw_shape_candidate
4130 }
4131
4132 pub fn target_preserving_reference_namespace(
4133 &self,
4134 analyzer: &CppGraphSource<'_>,
4135 file: &ProjectFile,
4136 identifier: &str,
4137 target: &CodeUnit,
4138 ) -> Option<Vec<String>> {
4139 let mut namespace = None;
4140 for candidate in self.visible_identifier_candidates(file, identifier) {
4141 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
4142 {
4143 continue;
4144 }
4145 if !(same_visible_symbol(candidate, target)
4146 || self.compatible_primary_template_redeclarations(candidate, target)
4147 || declared_type_alias(analyzer, candidate)
4148 && self.structured_alias_primary_preserves_target(
4149 analyzer, file, candidate, target,
4150 ))
4151 {
4152 continue;
4153 }
4154 if namespace
4155 .as_ref()
4156 .is_some_and(|existing| existing != candidate.package_name())
4157 {
4158 return None;
4159 }
4160 namespace = Some(candidate.package_name().to_string());
4161 }
4162 let namespace = namespace?;
4163 Some(
4164 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
4165 brokk_bifrost_core::analyzer::Language::Cpp,
4166 &namespace,
4167 ),
4168 )
4169 }
4170
4171 pub fn resolve_imported_type_candidate(
4172 &self,
4173 analyzer: &CppGraphSource<'_>,
4174 file: &ProjectFile,
4175 target: &CodeUnit,
4176 target_components: &[String],
4177 direct_target: Option<&CodeUnit>,
4178 preserve_alias: bool,
4179 ) -> LexicalTypeResolution {
4180 let candidates = [target];
4181 let resolution = if preserve_alias {
4182 TypeCandidateResolution::PreserveAlias
4183 } else {
4184 direct_target.map_or(
4185 TypeCandidateResolution::Canonical,
4186 TypeCandidateResolution::PreserveTarget,
4187 )
4188 };
4189 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4193 Ok(unit) => LexicalTypeResolution::Resolved {
4194 unit,
4195 components: target_components.to_vec(),
4196 candidates: vec![target.clone()],
4197 },
4198 Err(failure) => failure.lexical_resolution(),
4199 }
4200 }
4201
4202 fn resolve_type_components_lexically_inner(
4203 &self,
4204 analyzer: &CppGraphSource<'_>,
4205 file: &ProjectFile,
4206 components: &[String],
4207 global: bool,
4208 lexical_scope: &[String],
4209 resolution: TypeCandidateResolution<'_>,
4210 ) -> LexicalTypeResolution {
4211 if components.is_empty() {
4212 return LexicalTypeResolution::Missing;
4213 }
4214 let mut injected = self.resolve_injected_class_name(
4224 analyzer,
4225 file,
4226 components,
4227 global,
4228 lexical_scope,
4229 resolution,
4230 );
4231 for qualified in lexical_component_tiers(components, global, lexical_scope) {
4232 let prefix_len = qualified.len().saturating_sub(components.len());
4233 if injected
4234 .as_ref()
4235 .is_some_and(|(owner_len, _)| prefix_len < *owner_len)
4236 {
4237 return injected
4238 .take()
4239 .expect("injected class resolution was just present")
4240 .1;
4241 }
4242 let qualified_name = qualified.join("::");
4243 let candidates = self
4244 .type_candidates(file, &qualified_name)
4245 .into_iter()
4246 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4247 .collect::<Vec<_>>();
4248 if candidates.is_empty() {
4249 if !global && components.len() == 1 {
4250 match self.resolve_inherited_type_for_lexical_scope(
4251 analyzer,
4252 file,
4253 &qualified[..prefix_len],
4254 &components[0],
4255 resolution,
4256 ) {
4257 LexicalTypeResolution::Missing => {}
4258 inherited => return inherited,
4259 }
4260 }
4261 continue;
4262 }
4263 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4264 Ok(unit) => unit,
4265 Err(failure) => return failure.lexical_resolution(),
4266 };
4267 return LexicalTypeResolution::Resolved {
4268 unit,
4269 components: qualified,
4270 candidates: candidates.into_iter().cloned().collect(),
4271 };
4272 }
4273 LexicalTypeResolution::Missing
4274 }
4275
4276 fn resolve_injected_class_name(
4277 &self,
4278 analyzer: &CppGraphSource<'_>,
4279 file: &ProjectFile,
4280 components: &[String],
4281 global: bool,
4282 lexical_scope: &[String],
4283 resolution: TypeCandidateResolution<'_>,
4284 ) -> Option<(usize, LexicalTypeResolution)> {
4285 if global
4286 || components.len() != 1
4287 || file.rel_path().extension().is_some_and(|ext| ext == "c")
4288 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
4289 {
4290 return None;
4291 }
4292 let name = components.first()?;
4293 let mut matches: Vec<&CodeUnit> = Vec::new();
4294 let mut owner_len = 0;
4295 for candidate in self.visible_identifier_candidates(file, name) {
4296 if !candidate.is_class()
4297 || declared_type_alias(analyzer, candidate)
4298 || candidate.identifier() != name
4299 {
4300 continue;
4301 }
4302 let candidate_scope = canonical_cpp_scope_components(candidate);
4303 if candidate_scope.len() > lexical_scope.len()
4304 || !lexical_scope.starts_with(&candidate_scope)
4305 || candidate_scope.last().is_none_or(|last| last != name)
4306 {
4307 continue;
4308 }
4309 if candidate_scope.len() > owner_len {
4310 owner_len = candidate_scope.len();
4311 matches.clear();
4312 }
4313 if candidate_scope.len() == owner_len
4314 && !matches
4315 .iter()
4316 .any(|existing| same_logical_symbol(existing, candidate))
4317 {
4318 matches.push(candidate);
4319 }
4320 }
4321 if matches.is_empty() {
4322 return None;
4323 }
4324 if owner_len >= lexical_scope.len() {
4332 return None;
4333 }
4334 let owner_components = lexical_scope[..owner_len].to_vec();
4335 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
4336 Ok(unit) => LexicalTypeResolution::Resolved {
4337 unit,
4338 components: owner_components,
4339 candidates: matches.into_iter().cloned().collect(),
4340 },
4341 Err(failure) => failure.lexical_resolution(),
4342 };
4343 Some((owner_len, resolution))
4344 }
4345
4346 fn resolve_inherited_type_for_lexical_scope(
4347 &self,
4348 analyzer: &CppGraphSource<'_>,
4349 file: &ProjectFile,
4350 lexical_scope: &[String],
4351 name: &str,
4352 resolution: TypeCandidateResolution<'_>,
4353 ) -> LexicalTypeResolution {
4354 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
4355 return LexicalTypeResolution::Missing;
4356 };
4357 let lexical_owner_name = lexical_scope.join("::");
4358 if lexical_owner_name.is_empty() {
4359 return LexicalTypeResolution::Missing;
4360 }
4361 let owner_candidates = self
4362 .type_candidates(file, &lexical_owner_name)
4363 .into_iter()
4364 .filter(|candidate| {
4365 canonical_cpp_name_matches(candidate, &lexical_owner_name)
4366 && !declared_type_alias(analyzer, candidate)
4367 })
4368 .collect::<Vec<_>>();
4369 if owner_candidates.is_empty() {
4370 return LexicalTypeResolution::Missing;
4371 }
4372 let physical_owner_candidates = owner_candidates
4377 .iter()
4378 .copied()
4379 .filter(|candidate| candidate.source() == file)
4380 .collect::<Vec<_>>();
4381 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
4382 owner_candidates
4383 } else {
4384 physical_owner_candidates
4385 };
4386 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
4387 return LexicalTypeResolution::Ambiguous;
4388 };
4389
4390 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
4391 let mut visited_owners = HashSet::default();
4392 while !frontier.is_empty() {
4393 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
4394 let mut next_frontier = Vec::new();
4395 for owner in frontier {
4396 if !visited_owners.insert(owner.fq_name()) {
4397 continue;
4398 }
4399 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
4400 let candidates = self
4401 .type_candidates(file, &qualified_name)
4402 .into_iter()
4403 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
4404 .collect::<Vec<_>>();
4405 if candidates.is_empty() {
4406 for ancestor in hierarchy.get_direct_ancestors(&owner) {
4407 if !next_frontier
4408 .iter()
4409 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
4410 {
4411 next_frontier.push(ancestor);
4412 }
4413 }
4414 continue;
4415 }
4416 let unit =
4417 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
4418 Ok(unit) => unit,
4419 Err(failure) => return failure.lexical_resolution(),
4420 };
4421 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
4422 }
4423 if let Some((unit, candidates)) = level_matches.first().cloned() {
4424 let Some(first_declaration) = candidates.first() else {
4425 return LexicalTypeResolution::Ambiguous;
4426 };
4427 if !level_matches.iter().all(|(_, declarations)| {
4428 declarations
4429 .iter()
4430 .all(|declaration| same_logical_symbol(first_declaration, declaration))
4431 }) {
4432 return LexicalTypeResolution::Ambiguous;
4433 }
4434 let mut components = lexical_scope.to_vec();
4435 components.push(name.to_string());
4436 return LexicalTypeResolution::Resolved {
4437 unit,
4438 components,
4439 candidates,
4440 };
4441 }
4442 frontier = next_frontier;
4443 }
4444 LexicalTypeResolution::Missing
4445 }
4446
4447 pub fn inherited_injected_class_owner(
4450 &self,
4451 analyzer: &CppGraphSource<'_>,
4452 file: &ProjectFile,
4453 enclosing_owner: &CodeUnit,
4454 injected_name: &str,
4455 ) -> Option<CodeUnit> {
4456 let hierarchy = analyzer.type_hierarchy_provider()?;
4457 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
4458 let mut visited = HashSet::default();
4459 while !frontier.is_empty() {
4460 let mut level_matches = Vec::new();
4461 let mut next_frontier = Vec::new();
4462 for raw_owner in frontier {
4463 let owner = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)?;
4464 if !visited.insert(owner.clone()) {
4465 continue;
4466 }
4467 if owner.identifier() == injected_name
4468 && !level_matches
4469 .iter()
4470 .any(|existing| same_logical_symbol(existing, &owner))
4471 {
4472 level_matches.push(owner.clone());
4473 }
4474 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
4475 }
4476 if let Some(first) = level_matches.first() {
4477 return level_matches
4478 .iter()
4479 .all(|candidate| same_logical_symbol(candidate, first))
4480 .then(|| first.clone());
4481 }
4482 frontier = next_frontier;
4483 }
4484 None
4485 }
4486
4487 fn resolve_type_candidates(
4492 &self,
4493 analyzer: &CppGraphSource<'_>,
4494 file: &ProjectFile,
4495 candidates: &[&CodeUnit],
4496 resolution: TypeCandidateResolution<'_>,
4497 ) -> Result<CodeUnit, TypeCandidateFailure> {
4498 match resolution {
4499 TypeCandidateResolution::Canonical => {
4500 self.canonical_type_candidate_resolution(analyzer, file, candidates)
4501 }
4502 TypeCandidateResolution::PreserveAlias => {
4503 let same_fqn_alias_family = candidates.len() > 1
4510 && candidates.iter().all(|candidate| {
4511 declared_type_alias(analyzer, candidate)
4512 && same_logical_symbol(candidates[0], candidate)
4513 })
4514 && candidates
4515 .iter()
4516 .any(|candidate| candidate.source() != candidates[0].source());
4517 if same_fqn_alias_family {
4518 let physically_visible = candidates
4519 .iter()
4520 .copied()
4521 .filter(|candidate| self.is_physically_visible(file, candidate))
4522 .collect::<Vec<_>>();
4523 let one_structured_target = physically_visible.len() > 1
4533 && physically_visible.iter().skip(1).all(|candidate| {
4534 let target = self.structured_alias_target(analyzer, candidate);
4535 target.is_some()
4536 && target
4537 == self.structured_alias_target(analyzer, physically_visible[0])
4538 });
4539 if physically_visible.len() == 1 || one_structured_target {
4540 return Ok(physically_visible[0].clone());
4541 }
4542 }
4543 unique_type_candidate_preserving_alias(analyzer, candidates)
4544 .ok_or(TypeCandidateFailure::Ambiguous)
4545 }
4546 TypeCandidateResolution::PreserveTarget(target) => self
4547 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
4548 .ok_or(TypeCandidateFailure::Ambiguous),
4549 }
4550 }
4551
4552 pub fn resolve_callable_value_components_lexically(
4553 &self,
4554 analyzer: &CppGraphSource<'_>,
4555 file: &ProjectFile,
4556 owner_components: &[String],
4557 member_name: &str,
4558 global: bool,
4559 lexical_scope: &[String],
4560 ) -> LexicalCallableValueResolution {
4561 if owner_components.is_empty() || member_name.is_empty() {
4562 return LexicalCallableValueResolution::Missing;
4563 }
4564 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
4565 let owner_name = qualified_owner.join("::");
4566 let type_candidates = self
4567 .type_candidates(file, &owner_name)
4568 .into_iter()
4569 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
4570 .collect::<Vec<_>>();
4571 let resolved_type = if type_candidates.is_empty() {
4572 None
4573 } else {
4574 let Some(unit) =
4575 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
4576 else {
4577 return LexicalCallableValueResolution::Ambiguous;
4578 };
4579 Some(unit)
4580 };
4581
4582 let mut qualified_callable = qualified_owner;
4583 qualified_callable.push(member_name.to_string());
4584 let callable_name = qualified_callable.join("::");
4585 let free_function = self
4586 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
4587 .into_iter()
4588 .find(|candidate| {
4589 canonical_cpp_name_matches(candidate, &callable_name)
4590 && type_owner_of(analyzer, candidate).is_none()
4591 })
4592 .cloned();
4593
4594 match (resolved_type, free_function) {
4595 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
4596 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
4597 (None, Some(function)) => {
4598 return LexicalCallableValueResolution::FreeFunction(function);
4599 }
4600 (None, None) => {}
4601 }
4602 }
4603 LexicalCallableValueResolution::Missing
4604 }
4605
4606 fn resolve_type_for_declaration(
4607 &self,
4608 visible_from: &ProjectFile,
4609 declaration: &CodeUnit,
4610 raw_name: &str,
4611 ) -> Option<CodeUnit> {
4612 let normalized = normalize_reference_name(raw_name)?;
4613 if !normalized.contains("::")
4614 && let Some(namespace) = cpp_namespace_for(declaration)
4615 {
4616 for prefix in namespace_prefixes(&namespace) {
4617 let qualified = format!("{prefix}::{normalized}");
4618 if let Some(unit) = self
4619 .type_candidates(visible_from, &qualified)
4620 .into_iter()
4621 .next()
4622 {
4623 return Some(unit.clone());
4624 }
4625 }
4626 }
4627 self.resolve_type(visible_from, raw_name)
4628 }
4629
4630 fn resolve_unique_canonical_type_for_declaration(
4631 &self,
4632 analyzer: &CppGraphSource<'_>,
4633 visible_from: &ProjectFile,
4634 declaration: &CodeUnit,
4635 raw_name: &str,
4636 ) -> Option<CodeUnit> {
4637 let mut current =
4638 self.resolve_unique_type_for_declaration(visible_from, declaration, raw_name)?;
4639 let mut seen_aliases = HashSet::default();
4640 loop {
4641 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
4642 return current.is_class().then_some(current);
4643 };
4644 if matches!(target, StructuredAliasTarget::Builtin) {
4645 return current.is_class().then_some(current);
4646 }
4647 if !seen_aliases.insert(current.clone()) {
4648 return None;
4649 }
4650 current = self.resolve_structured_alias_target(visible_from, ¤t, &target)?;
4651 }
4652 }
4653
4654 pub fn canonical_type_unit(
4655 &self,
4656 analyzer: &CppGraphSource<'_>,
4657 visible_from: &ProjectFile,
4658 unit: &CodeUnit,
4659 ) -> Option<CodeUnit> {
4660 self.canonical_type_resolution(analyzer, visible_from, unit)
4661 .ok()
4662 }
4663
4664 fn canonical_type_resolution(
4672 &self,
4673 analyzer: &CppGraphSource<'_>,
4674 visible_from: &ProjectFile,
4675 unit: &CodeUnit,
4676 ) -> Result<CodeUnit, TypeCandidateFailure> {
4677 let mut current = unit.clone();
4678 let mut seen_aliases = HashSet::default();
4679 loop {
4680 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
4681 return current
4682 .is_class()
4683 .then_some(current)
4684 .ok_or(TypeCandidateFailure::Unresolvable);
4685 };
4686 if matches!(target, StructuredAliasTarget::Builtin) {
4687 return current
4688 .is_class()
4689 .then_some(current)
4690 .ok_or(TypeCandidateFailure::Unresolvable);
4691 }
4692 if !seen_aliases.insert(current.clone()) {
4693 return Err(TypeCandidateFailure::Unresolvable);
4694 }
4695 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
4696 }
4697 }
4698
4699 pub fn canonical_visible_full_type_unit(
4700 &self,
4701 analyzer: &CppGraphSource<'_>,
4702 visible_from: &ProjectFile,
4703 unit: &CodeUnit,
4704 ) -> Option<CodeUnit> {
4705 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
4706 if cpp_class_declaration_strength(analyzer, &canonical)
4707 != CppClassDeclarationStrength::Forward
4708 {
4709 return Some(canonical);
4710 }
4711 let mut full = Vec::new();
4712 for candidate in self
4713 .visible_identifier_candidates(visible_from, canonical.identifier())
4714 .filter(|candidate| {
4715 candidate.is_class()
4716 && candidate.fq_name() == canonical.fq_name()
4717 && cpp_class_declaration_strength(analyzer, candidate)
4718 == CppClassDeclarationStrength::Full
4719 })
4720 {
4721 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
4722 full.push(candidate.clone());
4723 }
4724 }
4725 match full.len() {
4726 0 => Some(canonical),
4727 1 => full.pop(),
4728 _ => None,
4729 }
4730 }
4731
4732 fn resolve_structured_alias_target(
4733 &self,
4734 visible_from: &ProjectFile,
4735 declaration: &CodeUnit,
4736 target: &StructuredAliasTarget,
4737 ) -> Option<CodeUnit> {
4738 self.structured_alias_target_resolution(visible_from, declaration, target)
4739 .ok()
4740 }
4741
4742 fn structured_alias_target_resolution(
4743 &self,
4744 visible_from: &ProjectFile,
4745 declaration: &CodeUnit,
4746 target: &StructuredAliasTarget,
4747 ) -> Result<CodeUnit, TypeCandidateFailure> {
4748 let primary =
4749 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
4750 let StructuredAliasTarget::Named { arguments, .. } = target else {
4751 return Err(TypeCandidateFailure::Unresolvable);
4752 };
4753 match arguments {
4754 Some(arguments) => self
4755 .resolve_template_arguments(visible_from, primary, arguments)
4756 .map_err(|error| match error {
4757 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
4758 TypeCandidateFailure::Ambiguous
4759 }
4760 _ => TypeCandidateFailure::Unresolvable,
4761 }),
4762 None => Ok(primary),
4763 }
4764 }
4765
4766 fn resolve_structured_alias_primary(
4767 &self,
4768 visible_from: &ProjectFile,
4769 declaration: &CodeUnit,
4770 target: &StructuredAliasTarget,
4771 ) -> Option<CodeUnit> {
4772 self.structured_alias_primary_resolution(visible_from, declaration, target)
4773 .ok()
4774 }
4775
4776 fn structured_alias_primary_resolution(
4777 &self,
4778 visible_from: &ProjectFile,
4779 declaration: &CodeUnit,
4780 target: &StructuredAliasTarget,
4781 ) -> Result<CodeUnit, TypeCandidateFailure> {
4782 let StructuredAliasTarget::Named {
4783 components, global, ..
4784 } = target
4785 else {
4786 return Err(TypeCandidateFailure::Unresolvable);
4787 };
4788 let qualified = components.join("::");
4789 let candidates = if *global {
4790 let mut candidates = self.type_candidates(visible_from, &qualified);
4797 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
4798 candidates
4799 } else {
4800 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
4801 };
4802 logical_type_candidate(candidates)
4803 }
4804
4805 pub fn structured_alias_primary_preserves_target(
4806 &self,
4807 analyzer: &CppGraphSource<'_>,
4808 visible_from: &ProjectFile,
4809 candidate: &CodeUnit,
4810 target: &CodeUnit,
4811 ) -> bool {
4812 let mut current = candidate.clone();
4813 let mut seen = HashSet::default();
4814 let mut matched_target = false;
4815 loop {
4816 if same_visible_symbol(¤t, target)
4817 || self.compatible_primary_template_redeclarations(¤t, target)
4818 {
4819 matched_target = true;
4820 }
4821 if !seen.insert(current.clone()) {
4822 return false;
4823 }
4824 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
4825 return matched_target;
4826 };
4827 if matches!(alias_target, StructuredAliasTarget::Builtin) {
4828 return matched_target;
4829 };
4830 let Some(primary) =
4831 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
4832 else {
4833 return matched_target;
4839 };
4840 current = primary;
4841 }
4842 }
4843
4844 pub fn structured_class_alias_resolves_to_target(
4845 &self,
4846 analyzer: &CppGraphSource<'_>,
4847 visible_from: &ProjectFile,
4848 alias: &CodeUnit,
4849 target: &CodeUnit,
4850 ) -> bool {
4851 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4852 return false;
4853 };
4854 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
4855 return false;
4856 };
4857 let StructuredAliasTarget::Named {
4858 components, global, ..
4859 } = &alias_target
4860 else {
4861 return false;
4862 };
4863 let lexical_scope = canonical_cpp_scope_components(&owner);
4864 match self.resolve_type_components_lexically_for_target(
4865 analyzer,
4866 visible_from,
4867 components,
4868 *global,
4869 &lexical_scope,
4870 target,
4871 ) {
4872 LexicalTypeResolution::Resolved {
4873 unit, candidates, ..
4874 } => {
4875 same_visible_symbol(&unit, target)
4876 || self.same_template_member_identity(analyzer, &unit, target)
4877 || candidates.iter().any(|candidate| {
4878 same_visible_symbol(candidate, target)
4879 || self.same_template_member_identity(analyzer, candidate, target)
4880 })
4881 }
4882 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
4883 self.structured_alias_primary_preserves_target(
4884 analyzer,
4885 visible_from,
4886 alias,
4887 target,
4888 ) || self.flattened_macro_namespace_alias_target_matches(
4889 analyzer,
4890 visible_from,
4891 alias,
4892 &alias_target,
4893 target,
4894 )
4895 }
4896 }
4897 }
4898
4899 pub fn structured_class_alias_path_preserves_target(
4907 &self,
4908 analyzer: &CppGraphSource<'_>,
4909 visible_from: &ProjectFile,
4910 alias: &CodeUnit,
4911 target: &CodeUnit,
4912 ) -> bool {
4913 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
4914 return false;
4915 };
4916 let Some(StructuredAliasTarget::Named {
4917 components, global, ..
4918 }) = self.structured_alias_target(analyzer, alias)
4919 else {
4920 return false;
4921 };
4922 let lexical_scope = canonical_cpp_scope_components(&owner);
4923 (1..components.len()).rev().any(|component_count| {
4924 matches!(
4925 self.resolve_type_components_lexically_for_target(
4926 analyzer,
4927 visible_from,
4928 &components[..component_count],
4929 global,
4930 &lexical_scope,
4931 target,
4932 ),
4933 LexicalTypeResolution::Resolved {
4934 ref unit,
4935 ref candidates,
4936 ..
4937 } if same_visible_symbol(unit, target)
4938 || self.same_template_member_identity(analyzer, unit, target)
4939 || candidates.iter().any(|candidate| {
4940 same_visible_symbol(candidate, target)
4941 || self.same_template_member_identity(analyzer, candidate, target)
4942 })
4943 )
4944 })
4945 }
4946
4947 fn flattened_macro_namespace_alias_target_matches(
4948 &self,
4949 analyzer: &CppGraphSource<'_>,
4950 visible_from: &ProjectFile,
4951 alias: &CodeUnit,
4952 alias_target: &StructuredAliasTarget,
4953 target: &CodeUnit,
4954 ) -> bool {
4955 let StructuredAliasTarget::Named {
4956 components,
4957 global: false,
4958 arguments: None,
4959 } = alias_target
4960 else {
4961 return false;
4962 };
4963 let Some((target_name, namespace_components)) = components.split_last() else {
4964 return false;
4965 };
4966 if namespace_components.is_empty()
4967 || target_name != target.identifier()
4968 || alias.source() != target.source()
4969 || alias.source() != visible_from
4970 || !target.is_class()
4971 || declared_type_alias(analyzer, target)
4972 {
4973 return false;
4974 }
4975 if self
4976 .resolve_structured_alias_target(visible_from, alias, alias_target)
4977 .is_some()
4978 {
4979 return false;
4980 }
4981
4982 let alias_ranges = analyzer.ranges(alias);
4983 let target_ranges = analyzer.ranges(target);
4984 if alias_ranges.is_empty() || target_ranges.is_empty() {
4985 return false;
4986 }
4987 let alias_start = alias_ranges
4988 .iter()
4989 .map(|range| range.start_byte)
4990 .min()
4991 .expect("non-empty alias ranges have a minimum");
4992 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
4993 return false;
4994 };
4995 let root = prepared.tree().root_node();
4996 let has_matching_declaration = target_ranges
4997 .iter()
4998 .filter(|range| range.end_byte <= alias_start)
4999 .filter_map(|range| node_for_exact_range(root, range))
5000 .any(|node| {
5001 flattened_macro_namespace_components(node, prepared.source())
5002 .is_some_and(|recovered| recovered == namespace_components)
5003 });
5004 if !has_matching_declaration {
5005 return false;
5006 }
5007
5008 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
5009 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
5010 guard_requirement_sets_match(&alias_guards, &target_guards)
5011 }
5012
5013 pub fn template_alias_arguments_preserve_target(
5014 &self,
5015 analyzer: &CppGraphSource<'_>,
5016 visible_from: &ProjectFile,
5017 alias: &CodeUnit,
5018 arguments: &[CppTemplateExpression],
5019 target: &CodeUnit,
5020 ) -> bool {
5021 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
5022 return false;
5023 };
5024 if metadata.alias_target.is_none()
5025 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
5026 {
5027 return false;
5028 }
5029 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
5030 }
5031
5032 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
5033 self.cpp_template_metadata
5034 .get(unit)
5035 .is_some_and(CppTemplateMetadata::is_primary)
5036 }
5037
5038 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
5039 self.cpp_template_metadata
5040 .get(unit)
5041 .is_some_and(CppTemplateMetadata::is_specialization)
5042 }
5043
5044 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
5045 same_visible_symbol(left, right)
5046 || self.compatible_primary_template_redeclarations(left, right)
5047 }
5048
5049 pub fn same_template_member_identity(
5050 &self,
5051 analyzer: &CppGraphSource<'_>,
5052 left: &CodeUnit,
5053 right: &CodeUnit,
5054 ) -> bool {
5055 if same_visible_symbol(left, right) {
5056 return true;
5057 }
5058 if left.kind() != right.kind()
5059 || left.identifier() != right.identifier()
5060 || left.signature() != right.signature()
5061 {
5062 return false;
5063 }
5064 let (Some(left_owner), Some(right_owner)) =
5065 (analyzer.parent_of(left), analyzer.parent_of(right))
5066 else {
5067 return false;
5068 };
5069 left_owner.is_class()
5070 && right_owner.is_class()
5071 && self.same_template_owner_identity(&left_owner, &right_owner)
5072 }
5073
5074 fn unique_canonical_type_candidate(
5075 &self,
5076 analyzer: &CppGraphSource<'_>,
5077 visible_from: &ProjectFile,
5078 candidates: &[&CodeUnit],
5079 ) -> Option<CodeUnit> {
5080 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
5081 .ok()
5082 }
5083
5084 fn canonical_type_candidate_resolution(
5085 &self,
5086 analyzer: &CppGraphSource<'_>,
5087 visible_from: &ProjectFile,
5088 candidates: &[&CodeUnit],
5089 ) -> Result<CodeUnit, TypeCandidateFailure> {
5090 let mut canonical = Vec::new();
5091 for candidate in candidates {
5092 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
5093 if canonical
5094 .iter()
5095 .any(|existing| same_visible_symbol(existing, &resolved))
5096 {
5097 continue;
5098 }
5099 if let Some(existing) = canonical.iter_mut().find(|existing| {
5100 self.compatible_primary_template_redeclarations(existing, &resolved)
5101 }) {
5102 if matches!(
5111 (
5112 cpp_class_declaration_strength(analyzer, existing),
5113 cpp_class_declaration_strength(analyzer, &resolved),
5114 ),
5115 (
5116 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
5117 CppClassDeclarationStrength::Full,
5118 ) | (
5119 CppClassDeclarationStrength::Unknown,
5120 CppClassDeclarationStrength::Forward,
5121 )
5122 ) {
5123 *existing = resolved;
5124 }
5125 continue;
5126 }
5127 canonical.push(resolved);
5128 if canonical.len() > 1 {
5129 return Err(TypeCandidateFailure::Ambiguous);
5130 }
5131 }
5132 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
5133 }
5134
5135 pub fn unique_type_candidate_preserving_target(
5136 &self,
5137 analyzer: &CppGraphSource<'_>,
5138 visible_from: &ProjectFile,
5139 candidates: &[&CodeUnit],
5140 target: &CodeUnit,
5141 ) -> Option<CodeUnit> {
5142 if self.alternate_same_fqn_type_declarations(analyzer, candidates, target) {
5153 return Some(target.clone());
5154 }
5155 let mut resolved_candidates = Vec::new();
5156 for candidate in candidates {
5157 let Some(resolved) =
5163 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5164 else {
5165 continue;
5166 };
5167 if resolved_candidates
5168 .iter()
5169 .any(|existing| same_visible_symbol(existing, &resolved))
5170 {
5171 continue;
5172 }
5173 resolved_candidates.push(resolved);
5174 }
5175 match resolved_candidates.as_slice() {
5176 [] => None,
5177 [single] => Some(single.clone()),
5178 _ => self
5183 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
5184 .map(|_| target.clone()),
5185 }
5186 }
5187
5188 pub fn same_fqn_type_spelling_for_target<'b>(
5205 &self,
5206 analyzer: &CppGraphSource<'_>,
5207 visible_from: &ProjectFile,
5208 candidates: &[&'b CodeUnit],
5209 target: &CodeUnit,
5210 ) -> Option<&'b CodeUnit> {
5211 let [first, rest @ ..] = candidates else {
5212 return None;
5213 };
5214 if rest.is_empty()
5215 || !rest.iter().all(|candidate| {
5216 candidate.kind() == first.kind()
5217 && candidate.fq_name() == first.fq_name()
5218 && candidate.source() == first.source()
5219 })
5220 {
5221 return None;
5222 }
5223 candidates
5224 .iter()
5225 .copied()
5226 .find(|candidate| same_symbol(candidate, target))
5227 .or_else(|| {
5228 candidates.iter().copied().find(|candidate| {
5229 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
5230 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
5231 })
5232 })
5233 }
5234
5235 pub fn alternate_same_fqn_type_declarations(
5236 &self,
5237 analyzer: &CppGraphSource<'_>,
5238 candidates: &[&CodeUnit],
5239 target: &CodeUnit,
5240 ) -> bool {
5241 let Some(first) = candidates.first() else {
5242 return false;
5243 };
5244 let same_api = first.kind() == target.kind()
5245 && first.fq_name() == target.fq_name()
5246 && first.source() == target.source()
5247 && candidates.iter().all(|candidate| {
5248 candidate.kind() == target.kind()
5249 && candidate.fq_name() == target.fq_name()
5250 && candidate.source() == target.source()
5251 })
5252 && candidates
5253 .iter()
5254 .any(|candidate| same_symbol(candidate, target))
5255 && candidates
5256 .iter()
5257 .any(|candidate| !same_logical_symbol(candidate, target));
5258 if !same_api {
5259 return false;
5260 }
5261
5262 let requirements = candidates
5263 .iter()
5264 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5265 .collect::<Vec<_>>();
5266 requirements.len() > 1
5267 && requirements
5268 .iter()
5269 .all(|requirement| !requirement.is_empty())
5270 && requirements.iter().enumerate().all(|(index, left)| {
5271 requirements[index + 1..].iter().all(|right| {
5272 left.iter().all(|(_, left_guards)| {
5273 right.iter().all(|(_, right_guards)| {
5274 merge_preprocessor_guards(left_guards, right_guards).is_none()
5275 })
5276 })
5277 })
5278 })
5279 }
5280
5281 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
5282 let mut pending = vec![terms.to_vec()];
5283 while let Some(branch_terms) = pending.pop() {
5284 let mut normalized = Vec::new();
5285 let mut covers_branch = false;
5286 for term in branch_terms {
5287 if term.iter().any(|guard| term.contains(&guard.negated())) {
5288 continue;
5289 }
5290 if term.is_empty() {
5291 covers_branch = true;
5292 break;
5293 }
5294 if !normalized.iter().any(|existing| existing == &term) {
5295 normalized.push(term);
5296 }
5297 }
5298 if covers_branch {
5299 continue;
5300 }
5301 let Some(split_guard) = normalized
5302 .iter()
5303 .flat_map(|term| term.iter())
5304 .next()
5305 .cloned()
5306 else {
5307 return false;
5308 };
5309 let negated_guard = split_guard.negated();
5310 let mut when_defined = Vec::new();
5311 let mut when_undefined = Vec::new();
5312 for term in normalized {
5313 if term.contains(&negated_guard) {
5314 } else if term.contains(&split_guard) {
5316 let mut reduced = term.clone();
5317 reduced.remove(&split_guard);
5318 when_defined.push(reduced);
5319 } else {
5320 when_defined.push(term.clone());
5321 }
5322 if term.contains(&split_guard) {
5323 } else if term.contains(&negated_guard) {
5325 let mut reduced = term;
5326 reduced.remove(&negated_guard);
5327 when_undefined.push(reduced);
5328 } else {
5329 when_undefined.push(term);
5330 }
5331 }
5332 pending.push(when_defined);
5333 pending.push(when_undefined);
5334 }
5335 true
5336 }
5337
5338 fn declarations_share_exhaustive_conditional_family(
5347 &self,
5348 analyzer: &CppGraphSource<'_>,
5349 candidates: &[&CodeUnit],
5350 ) -> Option<(usize, usize)> {
5351 let mut family_range = None;
5352 for candidate in candidates {
5353 let prepared = self.cpp.prepared_syntax(self.token, candidate.source())?;
5354 let root = prepared.tree().root_node();
5355 let mut candidate_family = None;
5356 for range in analyzer.ranges(candidate) {
5357 let node = root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
5358 let family = preprocessor_conditional_family_for_declaration(node)?;
5359 let key = (family.start_byte(), family.end_byte());
5360 if candidate_family.is_some_and(|existing| existing != key) {
5361 return None;
5362 }
5363 candidate_family = Some(key);
5364 }
5365 let candidate_family = candidate_family?;
5366 if family_range.is_some_and(|existing| existing != candidate_family) {
5367 return None;
5368 }
5369 family_range = Some(candidate_family);
5370 }
5371 family_range
5372 }
5373
5374 pub fn complementary_same_fqn_type_declarations(
5375 &self,
5376 analyzer: &CppGraphSource<'_>,
5377 candidates: &[&CodeUnit],
5378 target: &CodeUnit,
5379 ) -> bool {
5380 if candidates.len() < 2
5381 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
5382 || self
5383 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
5384 .is_none()
5385 {
5386 return false;
5387 }
5388 Self::preprocessor_guard_terms_cover_all_paths(
5389 &self.declaration_family_guard_terms(analyzer, candidates),
5390 )
5391 }
5392
5393 fn declaration_family_guard_terms(
5394 &self,
5395 analyzer: &CppGraphSource<'_>,
5396 candidates: &[&CodeUnit],
5397 ) -> Vec<HashSet<PreprocessorGuard>> {
5398 candidates
5399 .iter()
5400 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
5401 .map(|(_, guards)| guards)
5402 .collect()
5403 }
5404
5405 fn exhaustive_guard_family_activation(
5421 &self,
5422 analyzer: &CppGraphSource<'_>,
5423 prepared: &PreparedSyntaxTree,
5424 candidate: &CodeUnit,
5425 reference: &CallableReferenceContext<'_>,
5426 ) -> Option<usize> {
5427 if nameable_callable_declaration_nodes(analyzer, prepared, candidate).is_empty() {
5430 return None;
5431 }
5432 let family = self
5433 .visible_identifier_candidates(candidate.source(), candidate.identifier())
5434 .filter(|peer| {
5435 peer.kind() == candidate.kind()
5436 && peer.fq_name() == candidate.fq_name()
5437 && peer.source() == candidate.source()
5438 })
5439 .collect::<Vec<_>>();
5440 let (_, family_end) =
5441 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
5442 if !Self::preprocessor_guard_terms_cover_all_paths(
5443 &self.declaration_family_guard_terms(analyzer, &family),
5444 ) {
5445 return None;
5446 }
5447 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
5451 .iter()
5452 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
5453 {
5454 return None;
5455 }
5456 (first_declaration_byte(analyzer, candidate)?
5457 == family
5458 .iter()
5459 .filter_map(|peer| first_declaration_byte(analyzer, peer))
5460 .min()?)
5461 .then_some(family_end)
5462 }
5463
5464 fn type_candidate_preserving_target(
5465 &self,
5466 analyzer: &CppGraphSource<'_>,
5467 visible_from: &ProjectFile,
5468 candidate: &CodeUnit,
5469 target: &CodeUnit,
5470 ) -> Option<CodeUnit> {
5471 let mut current = candidate.clone();
5472 let mut matched_target = same_visible_symbol(¤t, target)
5473 || self.compatible_primary_template_redeclarations(¤t, target);
5474 let mut seen = HashSet::default();
5475 loop {
5476 if !seen.insert(current.clone()) {
5477 return None;
5478 }
5479 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
5480 return matched_target
5481 .then(|| target.clone())
5482 .or_else(|| current.is_class().then_some(current));
5483 };
5484 if self.flattened_macro_namespace_alias_target_matches(
5485 analyzer,
5486 visible_from,
5487 ¤t,
5488 &alias_target,
5489 target,
5490 ) {
5491 return Some(target.clone());
5492 }
5493 if matches!(alias_target, StructuredAliasTarget::Builtin) {
5494 return matched_target
5495 .then(|| target.clone())
5496 .or_else(|| current.is_class().then_some(current));
5497 }
5498 if !self.cpp_template_metadata.contains_key(¤t)
5506 && let Some(primary) =
5507 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
5508 && (same_visible_symbol(&primary, target)
5509 || self.compatible_primary_template_redeclarations(&primary, target))
5510 {
5511 return Some(target.clone());
5512 }
5513 if same_visible_symbol(¤t, target) {
5514 return Some(target.clone());
5515 }
5516 if self.cpp_template_metadata.contains_key(¤t) {
5517 return None;
5518 }
5519 let Some(next) =
5520 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
5521 else {
5522 return matched_target.then(|| target.clone());
5523 };
5524 current = next;
5525 matched_target |= same_visible_symbol(¤t, target)
5526 || self.compatible_primary_template_redeclarations(¤t, target);
5527 }
5528 }
5529
5530 fn compatible_primary_template_redeclarations(
5531 &self,
5532 left: &CodeUnit,
5533 right: &CodeUnit,
5534 ) -> bool {
5535 let (Some(left_metadata), Some(right_metadata)) = (
5536 self.cpp_template_metadata.get(left),
5537 self.cpp_template_metadata.get(right),
5538 ) else {
5539 return false;
5540 };
5541 left_metadata.primary_fq_name == right_metadata.primary_fq_name
5542 && left_metadata.is_primary()
5543 && right_metadata.is_primary()
5544 && cpp_reconcile_primary_template_parameters(
5545 &[(left, left_metadata), (right, right_metadata)],
5546 right,
5547 )
5548 .is_some()
5549 }
5550
5551 fn alias_candidate_may_preserve_target(
5552 &self,
5553 analyzer: &CppGraphSource<'_>,
5554 visible_from: &ProjectFile,
5555 candidate: &CodeUnit,
5556 target: &CodeUnit,
5557 ) -> bool {
5558 let mut current = candidate.clone();
5559 let mut seen = HashSet::default();
5560 loop {
5561 if same_visible_symbol(¤t, target)
5562 || self.compatible_primary_template_redeclarations(¤t, target)
5563 {
5564 return true;
5565 }
5566 if self.cpp_template_metadata.contains_key(¤t) {
5567 return true;
5568 }
5569 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
5570 return false;
5571 };
5572 let StructuredAliasTarget::Named {
5573 components,
5574 global,
5575 arguments,
5576 } = alias_target
5577 else {
5578 return false;
5579 };
5580 if arguments.is_some() || !seen.insert(current.clone()) {
5581 return true;
5582 }
5583 let qualified = components.join("::");
5584 let next = if global {
5585 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
5586 } else {
5587 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
5588 };
5589 let Some(next) = next else {
5590 return true;
5591 };
5592 current = next;
5593 }
5594 }
5595
5596 fn type_candidates_for_declaration<'b>(
5600 &'b self,
5601 visible_from: &ProjectFile,
5602 declaration: &CodeUnit,
5603 raw_name: &str,
5604 ) -> Vec<&'b CodeUnit> {
5605 let Some(normalized) = normalize_reference_name(raw_name) else {
5606 return Vec::new();
5607 };
5608 if let Some(namespace) = cpp_namespace_for(declaration) {
5609 for prefix in namespace_prefixes(&namespace) {
5610 let qualified = format!("{prefix}::{normalized}");
5611 let candidates = self.type_candidates(visible_from, &qualified);
5612 if !candidates.is_empty() {
5613 return candidates;
5614 }
5615 }
5616 }
5617 self.type_candidates(visible_from, &normalized)
5618 }
5619
5620 fn resolve_unique_type_for_declaration(
5621 &self,
5622 visible_from: &ProjectFile,
5623 declaration: &CodeUnit,
5624 raw_name: &str,
5625 ) -> Option<CodeUnit> {
5626 unique_logical_type_candidate(self.type_candidates_for_declaration(
5627 visible_from,
5628 declaration,
5629 raw_name,
5630 ))
5631 }
5632
5633 pub fn resolves_to_type(
5634 &self,
5635 analyzer: &CppGraphSource<'_>,
5636 file: &ProjectFile,
5637 raw_name: &str,
5638 target: &CodeUnit,
5639 ) -> bool {
5640 let Some(normalized) = normalize_reference_name(raw_name) else {
5641 return false;
5642 };
5643 let candidates = self.type_candidates(file, &normalized);
5644 if candidates.is_empty() {
5645 return self.parser_alias_resolves_to_type(analyzer, file, raw_name, target);
5646 }
5647 let Some(resolved) =
5648 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
5649 else {
5650 return false;
5651 };
5652 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
5653 }
5654
5655 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
5656 let raw_target = cpp_alias_declaration_target_text(alias.signature()?)?;
5657 let resolved = self.resolve_type_for_declaration(alias.source(), alias, &raw_target)?;
5658 match resolved.kind() {
5659 CodeUnitType::Class => Some(resolved),
5660 _ if is_type_alias(&resolved) => self.alias_target(&resolved),
5661 _ => None,
5662 }
5663 }
5664
5665 pub fn same_logical_callable(
5680 &self,
5681 analyzer: &CppGraphSource<'_>,
5682 left: &CodeUnit,
5683 right: &CodeUnit,
5684 ) -> bool {
5685 if same_logical_symbol(left, right) {
5686 return true;
5687 }
5688 if left.kind() != right.kind()
5689 || !left.is_callable()
5690 || !right.is_callable()
5691 || left.fq_name() != right.fq_name()
5692 {
5693 return false;
5694 }
5695 if self.callable_is_template_declaration(analyzer, left)
5701 || self.callable_is_template_declaration(analyzer, right)
5702 {
5703 return false;
5704 }
5705 let (Some(left_comparable), Some(right_comparable)) = (
5706 self.callable_comparable(analyzer, left),
5707 self.callable_comparable(analyzer, right),
5708 ) else {
5709 return false;
5710 };
5711 if left_comparable.suffix != right_comparable.suffix
5716 || left_comparable.shapes.len() != right_comparable.shapes.len()
5717 {
5718 return false;
5719 }
5720 left_comparable
5721 .shapes
5722 .iter()
5723 .zip(right_comparable.shapes.iter())
5724 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
5725 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
5726 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
5727 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
5728 }
5729 _ => false,
5733 })
5734 }
5735
5736 fn comparable_shapes_agree(
5742 &self,
5743 analyzer: &CppGraphSource<'_>,
5744 left: &CppComparableParameter,
5745 right: &CppComparableParameter,
5746 ) -> bool {
5747 let mut stack = vec![(left.root(), right.root())];
5748 while let Some((left_index, right_index)) = stack.pop() {
5749 match (left.node(left_index), right.node(right_index)) {
5750 (
5751 CppComparableNode::Named {
5752 name: left_name,
5753 primitive: left_primitive,
5754 konst: left_konst,
5755 volatil: left_volatil,
5756 },
5757 CppComparableNode::Named {
5758 name: right_name,
5759 primitive: right_primitive,
5760 konst: right_konst,
5761 volatil: right_volatil,
5762 },
5763 ) => {
5764 if left_konst != right_konst
5765 || left_volatil != right_volatil
5766 || left_primitive != right_primitive
5767 || !self.comparable_names_agree(
5768 analyzer,
5769 left_name,
5770 right_name,
5771 *left_primitive,
5772 )
5773 {
5774 return false;
5775 }
5776 }
5777 (
5778 CppComparableNode::Pointer {
5779 inner: left_inner,
5780 konst: left_konst,
5781 volatil: left_volatil,
5782 },
5783 CppComparableNode::Pointer {
5784 inner: right_inner,
5785 konst: right_konst,
5786 volatil: right_volatil,
5787 },
5788 ) => {
5789 if left_konst != right_konst || left_volatil != right_volatil {
5790 return false;
5791 }
5792 stack.push((*left_inner, *right_inner));
5793 }
5794 (
5795 CppComparableNode::Reference { inner: left_inner },
5796 CppComparableNode::Reference { inner: right_inner },
5797 )
5798 | (
5799 CppComparableNode::Array { inner: left_inner },
5800 CppComparableNode::Array { inner: right_inner },
5801 ) => stack.push((*left_inner, *right_inner)),
5802 (
5803 CppComparableNode::Generic {
5804 base: left_base,
5805 arguments: left_arguments,
5806 },
5807 CppComparableNode::Generic {
5808 base: right_base,
5809 arguments: right_arguments,
5810 },
5811 ) => {
5812 if left_arguments.len() != right_arguments.len() {
5813 return false;
5814 }
5815 stack.push((*left_base, *right_base));
5816 stack.extend(
5817 left_arguments.iter().zip(right_arguments.iter()).map(
5818 |(left_argument, right_argument)| (*left_argument, *right_argument),
5819 ),
5820 );
5821 }
5822 _ => return false,
5823 }
5824 }
5825 true
5826 }
5827
5828 fn comparable_names_agree(
5838 &self,
5839 analyzer: &CppGraphSource<'_>,
5840 left: &StructuredTypeName,
5841 right: &StructuredTypeName,
5842 primitive: bool,
5843 ) -> bool {
5844 if primitive {
5845 return left.path() == right.path();
5846 }
5847 match (
5848 self.comparable_name_terminal(analyzer, left),
5849 self.comparable_name_terminal(analyzer, right),
5850 ) {
5851 (Some(left_terminal), Some(right_terminal)) => {
5852 same_logical_symbol(&left_terminal, &right_terminal)
5853 }
5854 (None, None) => {
5855 left.path() == right.path() && left.is_absolute() == right.is_absolute()
5856 }
5857 _ => false,
5858 }
5859 }
5860
5861 fn comparable_name_terminal(
5872 &self,
5873 analyzer: &CppGraphSource<'_>,
5874 name: &StructuredTypeName,
5875 ) -> Option<CodeUnit> {
5876 let mut current = self.comparable_name_declaration(analyzer, name)?;
5877 let mut visited = HashSet::default();
5878 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
5879 if !declared_type_alias(analyzer, ¤t) {
5886 return current.is_class().then_some(current);
5887 }
5888 if !visited.insert(current.clone()) {
5889 return None;
5890 }
5891 let signature = current.signature()?;
5892 if cpp_alias_declaration_adds_indirection(signature) {
5897 return None;
5898 }
5899 let raw_target = cpp_alias_declaration_target_text(signature)?;
5900 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
5901 }
5902 None
5903 }
5904
5905 fn comparable_alias_target(
5916 &self,
5917 analyzer: &CppGraphSource<'_>,
5918 alias: &CodeUnit,
5919 raw_target: &str,
5920 ) -> Option<CodeUnit> {
5921 let absolute = raw_target.trim_start().starts_with("::");
5926 let normalized = normalize_reference_name(raw_target)?;
5927 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5928 brokk_bifrost_core::analyzer::Language::Cpp,
5929 &normalized,
5930 );
5931 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
5932 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
5933 brokk_bifrost_core::analyzer::Language::Cpp,
5934 &namespace,
5935 )
5936 });
5937 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
5938 self.comparable_name_declaration(analyzer, &name)
5939 }
5940
5941 fn comparable_name_declaration(
5949 &self,
5950 analyzer: &CppGraphSource<'_>,
5951 name: &StructuredTypeName,
5952 ) -> Option<CodeUnit> {
5953 let definitions = analyzer.global_usage_definition_index();
5954 let first_depth = if name.is_absolute() {
5955 0
5956 } else {
5957 name.lexical_scope().len()
5958 };
5959 for depth in (0..=first_depth).rev() {
5960 let mut components = Vec::with_capacity(depth.saturating_add(name.path().len()));
5961 components.extend_from_slice(&name.lexical_scope()[..depth]);
5962 components.extend_from_slice(name.path());
5963 let mut candidates =
5964 definitions
5965 .fqn(&components.join("."))
5966 .into_iter()
5967 .filter(|unit| {
5968 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
5969 });
5970 let Some(first) = candidates.next() else {
5971 continue;
5972 };
5973 return candidates
5974 .all(|unit| same_logical_symbol(unit, first))
5975 .then(|| first.clone());
5976 }
5977 None
5978 }
5979
5980 fn callable_comparable(
5986 &self,
5987 analyzer: &CppGraphSource<'_>,
5988 unit: &CodeUnit,
5989 ) -> Option<Arc<ExtractedComparable>> {
5990 if let Some(cached) = self
5991 .callable_comparables
5992 .lock()
5993 .expect("C++ callable comparable cache poisoned")
5994 .get(unit)
5995 .cloned()
5996 {
5997 return cached;
5998 }
5999 let extracted = self
6000 .extract_callable_comparable(analyzer, unit)
6001 .map(Arc::new);
6002 self.callable_comparables
6003 .lock()
6004 .expect("C++ callable comparable cache poisoned")
6005 .insert(unit.clone(), extracted.clone());
6006 extracted
6007 }
6008
6009 fn extract_callable_comparable(
6010 &self,
6011 analyzer: &CppGraphSource<'_>,
6012 unit: &CodeUnit,
6013 ) -> Option<ExtractedComparable> {
6014 let prepared = self.cpp.prepared_syntax(self.token, unit.source())?;
6015 let root = prepared.tree().root_node();
6016 let declarator = analyzer
6017 .ranges(unit)
6018 .into_iter()
6019 .find_map(|range| cpp_function_declarator_at(root, range.start_byte))?;
6020 Some(ExtractedComparable {
6021 shapes: cpp_comparable_parameter_shapes(
6024 declarator,
6025 prepared.source(),
6026 &ParentIndex::unindexed(),
6027 ),
6028 suffix: cpp_callable_identity_suffix(declarator, prepared.source())?,
6029 })
6030 }
6031
6032 pub fn canonical_type_for_reference(
6033 &self,
6034 file: &ProjectFile,
6035 raw_name: &str,
6036 ) -> Option<CodeUnit> {
6037 let resolved = self.resolve_type(file, raw_name)?;
6038 self.alias_target(&resolved).or(Some(resolved))
6039 }
6040
6041 pub fn parser_alias_resolves_to_type(
6042 &self,
6043 analyzer: &CppGraphSource<'_>,
6044 file: &ProjectFile,
6045 raw_name: &str,
6046 target: &CodeUnit,
6047 ) -> bool {
6048 let Some(alias_name) = normalize_reference_name(raw_name) else {
6049 return false;
6050 };
6051 let Some(cpp) = analyzer.cpp else {
6052 return false;
6053 };
6054 let matches_file = |source_file: &ProjectFile| {
6055 self.file_alias_matches(cpp, source_file, &alias_name, target)
6056 };
6057 self.visible_source_files_by_root.get(file).map_or_else(
6058 || matches_file(file),
6059 |files| files.iter().any(matches_file),
6060 )
6061 }
6062
6063 fn file_alias_matches(
6064 &self,
6065 cpp: &dyn CppSource,
6066 file: &ProjectFile,
6067 alias_name: &str,
6068 target: &CodeUnit,
6069 ) -> bool {
6070 let cell = {
6071 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
6072 Arc::clone(
6073 cells
6074 .entry(file.clone())
6075 .or_insert_with(|| Arc::new(OnceLock::new())),
6076 )
6077 };
6078 cell.get_or_init(|| {
6079 #[cfg(any(test, feature = "test-support"))]
6080 {
6081 *self
6082 .alias_source_parse_counts
6083 .lock()
6084 .expect("alias source parse count lock")
6085 .entry(file.clone())
6086 .or_default() += 1;
6087 }
6088 aliases_from_prepared_source(cpp, self.token, file).into_boxed_slice()
6089 })
6090 .iter()
6091 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
6092 }
6093
6094 #[cfg(any(test, feature = "test-support"))]
6095 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
6096 self.visible_source_files_by_root
6097 .get(file)
6098 .cloned()
6099 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
6100 }
6101
6102 #[cfg(any(test, feature = "test-support"))]
6103 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
6104 self.alias_source_parse_counts
6105 .lock()
6106 .expect("alias source parse count lock")
6107 .get(file)
6108 .copied()
6109 .unwrap_or(0)
6110 }
6111
6112 pub fn resolve_named(
6113 &self,
6114 file: &ProjectFile,
6115 raw_name: &str,
6116 kind: TargetKind,
6117 ) -> Option<CodeUnit> {
6118 let normalized = normalize_reference_name(raw_name)?;
6119 self.named_candidates_for_normalized(file, &normalized, kind)
6120 .into_iter()
6121 .next()
6122 .cloned()
6123 }
6124
6125 pub fn contains_named_symbol(
6126 &self,
6127 file: &ProjectFile,
6128 raw_name: &str,
6129 kind: TargetKind,
6130 target: &CodeUnit,
6131 ) -> bool {
6132 let Some(normalized) = normalize_reference_name(raw_name) else {
6133 return false;
6134 };
6135 self.named_candidates_for_normalized(file, &normalized, kind)
6136 .into_iter()
6137 .any(|unit| {
6138 matches_kind_for_lookup(unit, kind)
6139 && reference_matches_unit(&normalized, unit)
6140 && same_visible_symbol(unit, target)
6141 })
6142 }
6143
6144 pub fn named_candidates(
6145 &self,
6146 file: &ProjectFile,
6147 raw_name: &str,
6148 kind: TargetKind,
6149 ) -> Vec<CodeUnit> {
6150 let Some(normalized) = normalize_reference_name(raw_name) else {
6151 return Vec::new();
6152 };
6153 self.named_candidates_for_normalized(file, &normalized, kind)
6154 .into_iter()
6155 .cloned()
6156 .collect()
6157 }
6158
6159 pub fn resolve_known_non_target(
6160 &self,
6161 file: &ProjectFile,
6162 raw_name: &str,
6163 kind: TargetKind,
6164 target: &CodeUnit,
6165 ) -> bool {
6166 let Some(normalized) = normalize_reference_name(raw_name) else {
6167 return false;
6168 };
6169 normalized.contains("::")
6170 && self
6171 .named_candidates_for_normalized(file, &normalized, kind)
6172 .into_iter()
6173 .any(|unit| {
6174 matches_kind_for_lookup(unit, kind)
6175 && reference_matches_unit(&normalized, unit)
6176 && !same_visible_symbol(unit, target)
6177 })
6178 }
6179
6180 pub fn resolve_call_return_binding(
6181 &self,
6182 analyzer: &CppGraphSource<'_>,
6183 file: &ProjectFile,
6184 raw_name: &str,
6185 arity: usize,
6186 lexical_namespace: Option<&str>,
6187 direct_type: Option<&CodeUnit>,
6188 ) -> Option<CppScanBinding> {
6189 let normalized = normalize_reference_name(raw_name)?;
6190 let mut candidates = Vec::new();
6191 for function in
6192 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6193 {
6194 if cpp_callable_arity(analyzer, function).accepts(arity)
6195 && !direct_type.is_some_and(|direct_type| {
6196 self.callable_is_constructor_declaration(analyzer, function)
6197 && type_owner_of(analyzer, function)
6198 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6199 })
6200 {
6201 candidates.push(function.clone());
6202 }
6203 }
6204 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6205 unanimous_return_binding(analyzer, self, file, &candidates)
6206 }
6207
6208 pub fn resolve_call_return_binding_without_arity(
6209 &self,
6210 analyzer: &CppGraphSource<'_>,
6211 file: &ProjectFile,
6212 raw_name: &str,
6213 lexical_namespace: Option<&str>,
6214 direct_type: Option<&CodeUnit>,
6215 ) -> (bool, Option<CppScanBinding>) {
6216 let Some(normalized) = normalize_reference_name(raw_name) else {
6217 return (false, None);
6218 };
6219 let mut candidates = self
6220 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
6221 .into_iter()
6222 .filter(|function| {
6223 function.is_function()
6224 && !direct_type.is_some_and(|direct_type| {
6225 self.callable_is_constructor_declaration(analyzer, function)
6226 && type_owner_of(analyzer, function)
6227 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
6228 })
6229 })
6230 .cloned()
6231 .collect::<Vec<_>>();
6232 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
6233 let has_candidates = !candidates.is_empty();
6234 (
6235 has_candidates,
6236 unanimous_return_binding(analyzer, self, file, &candidates),
6237 )
6238 }
6239
6240 pub fn visible_identifier_candidates<'b>(
6241 &'b self,
6242 file: &ProjectFile,
6243 identifier: &str,
6244 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
6245 self.visible_by_identifier
6246 .get(file)
6247 .and_then(|by_name| by_name.get(identifier))
6248 .into_iter()
6249 .flatten()
6250 }
6251
6252 pub fn visible_type_reference_component_names_for_target(
6259 &self,
6260 analyzer: &CppGraphSource<'_>,
6261 file: &ProjectFile,
6262 target: &CodeUnit,
6263 ) -> HashSet<String> {
6264 let mut names = HashSet::from_iter([target.identifier().to_string()]);
6265 if let Some(metadata) = self.cpp_template_metadata.get(target) {
6266 names.insert(metadata.primary_name.clone());
6267 }
6268
6269 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
6270 for (identifier, candidates) in by_identifier {
6271 if candidates.iter().any(|candidate| {
6272 (candidate.is_class()
6273 && (same_visible_symbol(candidate, target)
6274 || self.compatible_primary_template_redeclarations(candidate, target)))
6275 || (declared_type_alias(analyzer, candidate)
6276 && self.alias_candidate_may_preserve_target(
6277 analyzer, file, candidate, target,
6278 ))
6279 }) {
6280 names.insert(identifier.clone());
6281 }
6282 }
6283 }
6284
6285 names.extend(self.visible_parser_alias_names_for_target(file, target));
6286
6287 names
6288 }
6289
6290 pub fn indexed_structural_class_scope(
6291 &self,
6292 file: &ProjectFile,
6293 class: Node<'_>,
6294 source: &str,
6295 ) -> Option<Vec<String>> {
6296 let key = (file.clone(), class.start_byte(), class.end_byte());
6297 if let Some(cached) = self
6298 .indexed_structural_class_scopes
6299 .lock()
6300 .expect("C++ indexed structural-class scope cache poisoned")
6301 .get(&key)
6302 .cloned()
6303 {
6304 return cached;
6305 }
6306 let resolved = (|| {
6307 let name = class.child_by_field_name("name")?;
6308 let identifier = if name.kind() == "template_type" {
6309 node_text(name.child_by_field_name("name")?, source).to_string()
6310 } else {
6311 let mut components = Vec::new();
6312 append_cpp_name_components(name, source, &mut components)?;
6313 components.last()?.clone()
6314 };
6315 let visible = self
6316 .visible_identifier_candidates(file, &identifier)
6317 .cloned()
6318 .collect::<Vec<_>>();
6319 let mut visible = visible;
6320 for candidate in
6321 self.visible_by_file
6322 .get(file)
6323 .into_iter()
6324 .flatten()
6325 .filter(|candidate| {
6326 self.cpp_template_metadata
6327 .get(candidate)
6328 .is_some_and(|metadata| metadata.primary_name == identifier)
6329 })
6330 {
6331 if !visible
6332 .iter()
6333 .any(|existing| same_logical_symbol(existing, candidate))
6334 {
6335 visible.push(candidate.clone());
6336 }
6337 }
6338 let cpp_source = self.cpp_source();
6341 let candidates = visible
6342 .iter()
6343 .filter(|candidate| {
6344 candidate.source() == file
6345 && candidate.is_class()
6346 && !declared_type_alias(&cpp_source, candidate)
6347 && self.cpp.ranges(candidate).iter().any(|range| {
6348 range.start_byte <= class.start_byte()
6349 && class.end_byte() <= range.end_byte
6350 })
6351 })
6352 .collect::<Vec<_>>();
6353 let owner = if name.kind() == "template_type" {
6354 let expected = normalize_cpp_whitespace(node_text(name, source));
6355 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
6356 let exact = candidates
6357 .iter()
6358 .copied()
6359 .filter(|candidate| {
6360 candidate
6361 .fq()
6362 .segments()
6363 .iter()
6364 .rev()
6365 .find_map(|&segment| {
6366 let (text, kind) = interner.resolve(segment);
6367 matches!(
6368 kind,
6369 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
6370 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
6371 )
6372 .then_some(text)
6373 })
6374 .is_some_and(|text| text == expected)
6375 })
6376 .collect::<Vec<_>>();
6377 unique_logical_type_candidate(exact)
6378 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
6379 } else {
6380 unique_logical_type_candidate(candidates)?
6381 };
6382 Some(canonical_cpp_scope_components(&owner))
6383 })();
6384 self.indexed_structural_class_scopes
6385 .lock()
6386 .expect("C++ indexed structural-class scope cache poisoned")
6387 .insert(key, resolved.clone());
6388 resolved
6389 }
6390
6391 pub fn indexed_enclosing_owner_scope(
6392 &self,
6393 analyzer: &CppGraphSource<'_>,
6394 file: &ProjectFile,
6395 node: Node<'_>,
6396 ) -> Option<Vec<String>> {
6397 let anchor = std::iter::successors(Some(node), |current| current.parent())
6398 .find(|current| {
6399 matches!(
6400 current.kind(),
6401 "function_definition"
6402 | "class_specifier"
6403 | "struct_specifier"
6404 | "union_specifier"
6405 )
6406 })
6407 .unwrap_or(node);
6408 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
6409 if let Some(cached) = self
6410 .indexed_enclosing_owner_scopes
6411 .lock()
6412 .expect("C++ indexed enclosing-owner scope cache poisoned")
6413 .get(&key)
6414 .cloned()
6415 {
6416 return cached;
6417 }
6418 let resolved = (|| {
6419 let range = Range {
6420 start_byte: node.start_byte(),
6421 end_byte: node.end_byte(),
6422 start_line: node.start_position().row,
6423 end_line: node.end_position().row,
6424 };
6425 let start = analyzer.enclosing_code_unit(file, &range)?;
6426 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
6427 start,
6428 |unit| self.cached_precise_parent_of(analyzer, unit),
6429 )
6430 .find(|unit| {
6431 unit.is_class()
6432 && !analyzer
6433 .type_alias_provider()
6434 .is_some_and(|provider| provider.is_type_alias(unit))
6435 })?;
6436 Some(canonical_cpp_scope_components(&owner))
6437 })();
6438 self.indexed_enclosing_owner_scopes
6439 .lock()
6440 .expect("C++ indexed enclosing-owner scope cache poisoned")
6441 .insert(key, resolved.clone());
6442 resolved
6443 }
6444
6445 fn cached_precise_parent_of(
6446 &self,
6447 analyzer: &CppGraphSource<'_>,
6448 code_unit: &CodeUnit,
6449 ) -> Option<CodeUnit> {
6450 if let Some(cached) = self
6451 .precise_parent_cache
6452 .lock()
6453 .expect("C++ precise-parent cache poisoned")
6454 .get(code_unit)
6455 .cloned()
6456 {
6457 return cached;
6458 }
6459 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
6460 self.precise_parent_cache
6461 .lock()
6462 .expect("C++ precise-parent cache poisoned")
6463 .insert(code_unit.clone(), resolved.clone());
6464 resolved
6465 }
6466
6467 pub fn callable_is_constructor_declaration(
6468 &self,
6469 analyzer: &CppGraphSource<'_>,
6470 candidate: &CodeUnit,
6471 ) -> bool {
6472 if !candidate.is_function() {
6473 return false;
6474 }
6475 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6476 return false;
6477 };
6478 let root = prepared.tree().root_node();
6479 let candidate_ranges = analyzer.ranges(candidate);
6480 let enclosed_by_matching_type = candidate_ranges.iter().any(|range| {
6481 let mut current = root
6482 .descendant_for_byte_range(range.start_byte, range.end_byte)
6483 .and_then(|node| node.parent());
6484 while let Some(node) = current {
6485 if matches!(
6486 node.kind(),
6487 "class_specifier" | "struct_specifier" | "union_specifier"
6488 ) {
6489 return node
6490 .child_by_field_name("name")
6491 .map(|name| terminal_name(node_text(name, prepared.source())))
6492 .is_some_and(|name| name == candidate.identifier());
6493 }
6494 current = node.parent();
6495 }
6496 false
6497 });
6498 if enclosed_by_matching_type {
6499 return true;
6500 }
6501 let indexed_containment = analyzer
6502 .declarations(candidate.source())
6503 .into_iter()
6504 .filter(|unit| unit.is_class() && unit.identifier() == candidate.identifier())
6505 .any(|owner| {
6506 analyzer.ranges(&owner).iter().any(|owner_range| {
6507 candidate_ranges.iter().any(|candidate_range| {
6508 owner_range.start_byte <= candidate_range.start_byte
6509 && candidate_range.end_byte <= owner_range.end_byte
6510 })
6511 })
6512 });
6513 if indexed_containment {
6514 return true;
6515 }
6516 let metadata = analyzer.signature_metadata(candidate);
6517 !metadata.is_empty()
6518 && metadata
6519 .iter()
6520 .all(|signature| signature.return_type_text().is_none())
6521 }
6522
6523 pub fn callable_is_deduction_guide_declaration(
6531 &self,
6532 analyzer: &CppGraphSource<'_>,
6533 candidate: &CodeUnit,
6534 ) -> bool {
6535 if !candidate.is_function() {
6536 return false;
6537 }
6538 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6539 return false;
6540 };
6541 nameable_callable_declaration_nodes(analyzer, prepared.as_ref(), candidate)
6542 .into_iter()
6543 .any(|declaration| {
6544 if declaration.kind() != "declaration"
6545 || declaration.child_by_field_name("type").is_some()
6546 {
6547 return false;
6548 }
6549 let Some(declarator) = declaration.child_by_field_name("declarator") else {
6550 return false;
6551 };
6552 if declarator.kind() != "function_declarator" {
6553 return false;
6554 }
6555 let mut cursor = declarator.walk();
6556 let has_trailing_return = declarator
6557 .named_children(&mut cursor)
6558 .any(|child| child.kind() == "trailing_return_type");
6559 has_trailing_return
6560 && declarator_name_node(declarator).is_some_and(|name| {
6561 node_text(name, prepared.source()) == candidate.identifier()
6562 })
6563 })
6564 }
6565
6566 pub fn callable_is_template_declaration(
6570 &self,
6571 analyzer: &CppGraphSource<'_>,
6572 candidate: &CodeUnit,
6573 ) -> bool {
6574 if !candidate.is_function() {
6575 return false;
6576 }
6577 let Some(prepared) = self.cpp.prepared_syntax(self.token, candidate.source()) else {
6578 return false;
6579 };
6580 let root = prepared.tree().root_node();
6581 analyzer.ranges(candidate).iter().any(|range| {
6582 let Some(node) = node_for_exact_range(root, range)
6583 .or_else(|| root.descendant_for_byte_range(range.start_byte, range.end_byte))
6584 else {
6585 return false;
6586 };
6587 node.parent().is_some_and(|parent| {
6588 parent.kind() == "template_declaration"
6589 && parent
6590 .named_child(parent.named_child_count().saturating_sub(1))
6591 .is_some_and(|declaration| same_node(declaration, node))
6592 })
6593 })
6594 }
6595
6596 pub fn type_name_candidates<'b>(
6597 &'b self,
6598 file: &ProjectFile,
6599 normalized: &str,
6600 ) -> Vec<&'b CodeUnit> {
6601 self.candidate_units(file, normalized, TargetKind::Type)
6602 }
6603
6604 pub fn visible_members_for_owner_name<'b>(
6605 &'b self,
6606 file: &ProjectFile,
6607 owner: &CodeUnit,
6608 name: &str,
6609 ) -> Vec<&'b CodeUnit> {
6610 self.visible_identifier_candidates(file, name)
6611 .filter(|unit| {
6612 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
6616 .is_some_and(|parent| parent == owner.fq_name())
6617 })
6618 .collect()
6619 }
6620
6621 pub fn visible_member_for_owner_name(
6622 &self,
6623 file: &ProjectFile,
6624 owner: &CodeUnit,
6625 name: &str,
6626 ) -> VisibleMemberResolution {
6627 let candidates = self.visible_members_for_owner_name(file, owner, name);
6628 let mut callables = Vec::new();
6629 let mut non_callable = None;
6630 for candidate in candidates {
6631 if candidate.is_function() {
6632 callables.push(candidate.clone());
6633 } else if non_callable.is_none() {
6634 non_callable = Some(candidate.clone());
6635 }
6636 }
6637 match (callables.is_empty(), non_callable) {
6638 (false, None) => VisibleMemberResolution::Callable(callables),
6639 (true, Some(_)) => VisibleMemberResolution::NonCallable,
6640 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
6641 (true, None) => VisibleMemberResolution::Missing,
6642 }
6643 }
6644
6645 fn field_declared_type_fact(
6646 &self,
6647 analyzer: &CppGraphSource<'_>,
6648 field: &CodeUnit,
6649 ) -> Option<DeclaredFieldTypeFact> {
6650 if let Some(cached) = self
6651 .field_type_facts
6652 .lock()
6653 .expect("C++ field type fact cache poisoned")
6654 .get(field)
6655 .cloned()
6656 {
6657 return cached;
6658 }
6659 let decoded = decode_field_declared_type_fact(analyzer, field);
6660 self.field_type_facts
6661 .lock()
6662 .expect("C++ field type fact cache poisoned")
6663 .insert(field.clone(), decoded.clone());
6664 decoded
6665 }
6666
6667 fn structured_alias_target(
6668 &self,
6669 analyzer: &CppGraphSource<'_>,
6670 unit: &CodeUnit,
6671 ) -> Option<StructuredAliasTarget> {
6672 if let Some(cached) = self
6673 .structured_alias_targets
6674 .lock()
6675 .expect("C++ structured alias target cache poisoned")
6676 .get(unit)
6677 .cloned()
6678 {
6679 return cached;
6680 }
6681 let decoded = decode_structured_alias_target(analyzer, unit);
6682 self.structured_alias_targets
6683 .lock()
6684 .expect("C++ structured alias target cache poisoned")
6685 .insert(unit.clone(), decoded.clone());
6686 decoded
6687 }
6688
6689 pub fn type_candidates<'b>(
6690 &'b self,
6691 file: &ProjectFile,
6692 normalized: &str,
6693 ) -> Vec<&'b CodeUnit> {
6694 let mut candidates = self
6695 .candidate_units(file, normalized, TargetKind::Type)
6696 .into_iter()
6697 .filter(|unit| unit.kind() == CodeUnitType::Class || is_type_alias(unit))
6698 .collect::<Vec<_>>();
6699 dedup_unit_refs(&mut candidates);
6700 candidates
6701 }
6702
6703 pub fn named_candidates_for_normalized<'b>(
6704 &'b self,
6705 file: &ProjectFile,
6706 normalized: &str,
6707 kind: TargetKind,
6708 ) -> Vec<&'b CodeUnit> {
6709 let mut candidates = self
6710 .candidate_units(file, normalized, kind)
6711 .into_iter()
6712 .filter(|unit| {
6713 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
6714 })
6715 .collect::<Vec<_>>();
6716 dedup_unit_refs(&mut candidates);
6717 candidates
6718 }
6719
6720 pub fn candidate_units<'b>(
6721 &'b self,
6722 file: &ProjectFile,
6723 normalized: &str,
6724 kind: TargetKind,
6725 ) -> Vec<&'b CodeUnit> {
6726 if normalized.contains("::") {
6727 let Some(identifier) = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6736 brokk_bifrost_core::analyzer::Language::Cpp,
6737 normalized,
6738 )
6739 .pop() else {
6740 return Vec::new();
6741 };
6742 let fqns = cpp_reference_fqn_candidates(normalized, kind);
6743 return self
6744 .visible_identifier_candidates(file, &identifier)
6745 .filter(|unit| {
6746 #[cfg(any(test, feature = "test-support"))]
6747 self.qualified_candidate_inspections
6748 .fetch_add(1, Ordering::Relaxed);
6749 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
6750 || canonical_cpp_name_matches(unit, normalized)
6751 })
6752 .collect();
6753 }
6754 self.visible_identifier_candidates(file, normalized)
6755 .collect()
6756 }
6757
6758 #[cfg(any(test, feature = "test-support"))]
6759 pub fn reset_qualified_candidate_inspections(&self) {
6760 self.qualified_candidate_inspections
6761 .store(0, Ordering::Relaxed);
6762 }
6763
6764 #[cfg(any(test, feature = "test-support"))]
6765 pub fn qualified_candidate_inspections(&self) -> usize {
6766 self.qualified_candidate_inspections.load(Ordering::Relaxed)
6767 }
6768
6769 #[cfg(any(test, feature = "test-support"))]
6770 pub fn reset_target_preserving_type_resolution_count(&self) {
6771 self.target_preserving_type_resolution_count
6772 .store(0, Ordering::Relaxed);
6773 }
6774
6775 #[cfg(any(test, feature = "test-support"))]
6776 pub fn target_preserving_type_resolution_count(&self) -> usize {
6777 self.target_preserving_type_resolution_count
6778 .load(Ordering::Relaxed)
6779 }
6780
6781 #[cfg(any(test, feature = "test-support"))]
6782 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
6783 self.visible_parser_alias_name_set_build_count
6784 .load(Ordering::Relaxed)
6785 }
6786
6787 #[cfg(any(test, feature = "test-support"))]
6788 pub fn visible_parser_alias_target_names_build_count(&self) -> usize {
6789 self.visible_parser_alias_target_names_build_count
6790 .load(Ordering::Relaxed)
6791 }
6792}
6793
6794#[derive(Default)]
6795struct IncludeGraph {
6796 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
6797}
6798
6799impl IncludeGraph {
6800 fn extend_with<F>(
6801 &mut self,
6802 root: &ProjectFile,
6803 cancellation: Option<&CancellationToken>,
6804 targets_for: &mut F,
6805 ) where
6806 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6807 {
6808 let mut stack = vec![root.clone()];
6809 while let Some(file) = stack.pop() {
6810 if cancellation.is_some_and(CancellationToken::is_cancelled) {
6811 break;
6812 }
6813 if self.targets_by_file.contains_key(&file) {
6814 continue;
6815 }
6816 let targets = targets_for(&file);
6817 stack.extend(targets.iter().cloned());
6818 self.targets_by_file.insert(file, targets);
6819 }
6820 }
6821
6822 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
6823 self.targets_by_file.keys()
6824 }
6825
6826 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
6827 self.targets_by_file
6828 .get(file)
6829 .map(Vec::as_slice)
6830 .unwrap_or_default()
6831 }
6832}
6833
6834pub struct VisibilityData {
6835 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
6836 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
6837}
6838
6839pub fn build_visibility_data<F, R, D>(
6849 roots: &HashSet<ProjectFile>,
6850 cancellation: Option<&CancellationToken>,
6851 mut targets_for: F,
6852 mut reading_is_c_for: R,
6853 mut declarations_for: D,
6854) -> VisibilityData
6855where
6856 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
6857 R: FnMut(&ProjectFile) -> bool,
6858 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
6859{
6860 let mut include_graph = IncludeGraph::default();
6861 for file in roots {
6862 if cancellation.is_some_and(CancellationToken::is_cancelled) {
6863 break;
6864 }
6865 include_graph.extend_with(file, cancellation, &mut targets_for);
6866 }
6867 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
6868 .files()
6869 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
6870 .map(|file| (file.clone(), declarations_for(file, false)))
6871 .collect();
6872 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
6873 let mut visible_by_file = HashMap::default();
6874 let mut visible_source_files_by_root = HashMap::default();
6875 for file in roots {
6876 if cancellation.is_some_and(CancellationToken::is_cancelled) {
6877 break;
6878 }
6879 let mut visited = HashSet::default();
6880 let mut visible = HashSet::default();
6881 let declarations_by_file = if reading_is_c_for(file) {
6882 for reached in cpp_declarations_by_file.keys() {
6883 if !c_declarations_by_file.contains_key(reached) {
6884 let declarations = declarations_for(reached, true);
6885 c_declarations_by_file.insert(reached.clone(), declarations);
6886 }
6887 }
6888 &c_declarations_by_file
6889 } else {
6890 &cpp_declarations_by_file
6891 };
6892 collect_visible_declarations(
6893 &include_graph,
6894 declarations_by_file,
6895 file,
6896 &mut visited,
6897 &mut visible,
6898 cancellation,
6899 );
6900 visible_by_file.insert(file.clone(), visible);
6901 visible_source_files_by_root.insert(file.clone(), visited);
6902 }
6903 VisibilityData {
6904 visible_by_file,
6905 visible_source_files_by_root,
6906 }
6907}
6908
6909fn extend_with_out_of_line_owner_bindings(
6928 cpp: &dyn CppSource,
6929 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
6930) {
6931 for (file, visible) in visible_by_file.iter_mut() {
6932 let mut unseen_owners: HashSet<String> = visible
6936 .iter()
6937 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
6938 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
6939 .collect();
6940 if unseen_owners.is_empty() {
6941 continue;
6942 }
6943 for unit in visible.iter().filter(|unit| unit.is_class()) {
6944 unseen_owners.remove(&unit.fq_name());
6945 }
6946 let admitted = unseen_owners
6947 .iter()
6948 .flat_map(|owner| cpp.definitions(owner))
6949 .filter(CodeUnit::is_class)
6950 .collect::<Vec<_>>();
6951 visible.extend(admitted);
6952 }
6953}
6954
6955pub enum VisibleMemberResolution {
6956 Callable(Vec<CodeUnit>),
6957 NonCallable,
6958 AmbiguousKind,
6959 Missing,
6960}
6961
6962#[derive(Clone)]
6963pub enum EnclosingMemberOwnerResolution {
6964 Owner(CodeUnit),
6965 Ambiguous,
6966 Missing,
6967}
6968
6969pub fn resolve_declaring_member_owner(
6970 analyzer: &CppGraphSource<'_>,
6971 visibility: &VisibilityIndex<'_>,
6972 file: &ProjectFile,
6973 receiver_owner: &CodeUnit,
6974 member_name: &str,
6975) -> EnclosingMemberOwnerResolution {
6976 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6977 return EnclosingMemberOwnerResolution::Missing;
6978 };
6979 let Some(receiver_owner) =
6980 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
6981 else {
6982 return EnclosingMemberOwnerResolution::Ambiguous;
6983 };
6984 let resolve_level = |frontier: &[CodeUnit]| {
6985 let mut member_owners = Vec::new();
6986 for raw_owner in frontier {
6987 let Some(owner) =
6988 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
6989 else {
6990 return EnclosingMemberOwnerResolution::Ambiguous;
6991 };
6992 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
6993 let Some(member_owner) = type_owner_of(analyzer, member) else {
6994 return EnclosingMemberOwnerResolution::Ambiguous;
6995 };
6996 if !member_owners
6997 .iter()
6998 .any(|existing| same_visible_symbol(existing, &member_owner))
6999 {
7000 member_owners.push(member_owner);
7001 }
7002 }
7003 }
7004 match member_owners.len() {
7005 0 => EnclosingMemberOwnerResolution::Missing,
7006 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
7007 _ => EnclosingMemberOwnerResolution::Ambiguous,
7008 }
7009 };
7010 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
7014 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
7015 return direct;
7016 }
7017 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
7018 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7019 let mut path_matches = Vec::new();
7020 while let Some(raw_owner) = stack.pop() {
7021 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7022 else {
7023 return EnclosingMemberOwnerResolution::Ambiguous;
7024 };
7025 let propagated = propagated_counts.entry(owner.clone()).or_default();
7029 if *propagated == 2 {
7030 continue;
7031 }
7032 *propagated += 1;
7033 match resolve_level(std::slice::from_ref(&owner)) {
7034 EnclosingMemberOwnerResolution::Owner(owner) => {
7035 path_matches.push(owner);
7036 if path_matches.len() == 2 {
7037 return EnclosingMemberOwnerResolution::Ambiguous;
7038 }
7039 }
7040 EnclosingMemberOwnerResolution::Ambiguous => {
7041 return EnclosingMemberOwnerResolution::Ambiguous;
7042 }
7043 EnclosingMemberOwnerResolution::Missing => {
7044 stack.extend(hierarchy.get_direct_ancestors(&owner));
7045 }
7046 }
7047 }
7048 match path_matches.len() {
7049 0 => EnclosingMemberOwnerResolution::Missing,
7050 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
7051 _ => unreachable!("base-path matches are capped at one before returning"),
7052 }
7053}
7054
7055pub fn resolve_declaring_callable_owner(
7070 analyzer: &CppGraphSource<'_>,
7071 visibility: &VisibilityIndex<'_>,
7072 file: &ProjectFile,
7073 ordinary: EnclosingMemberOwnerResolution,
7074 member_name: &str,
7075 call_arity: usize,
7076) -> EnclosingMemberOwnerResolution {
7077 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
7078 return ordinary;
7079 };
7080 if visibility
7081 .visible_members_for_owner_name(file, ordinary_owner, member_name)
7082 .into_iter()
7083 .any(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity))
7084 {
7085 return ordinary;
7086 }
7087
7088 let mut pending = match member_using_declaration_bases(
7089 analyzer,
7090 visibility,
7091 file,
7092 ordinary_owner,
7093 member_name,
7094 ) {
7095 Ok(bases) => bases,
7096 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7097 };
7098 let mut visited = HashSet::default();
7099 let mut introduced_owners = Vec::new();
7100 while let Some(owner) = pending.pop() {
7101 if !visited.insert(owner.clone()) {
7102 continue;
7103 }
7104 let accepts_arity = visibility
7105 .visible_members_for_owner_name(file, &owner, member_name)
7106 .into_iter()
7107 .any(|unit| {
7108 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(call_arity)
7109 });
7110 if accepts_arity {
7111 if !introduced_owners
7112 .iter()
7113 .any(|existing| same_visible_symbol(existing, &owner))
7114 {
7115 introduced_owners.push(owner);
7116 }
7117 continue;
7118 }
7119 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
7120 Ok(bases) => pending.extend(bases),
7121 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
7122 }
7123 }
7124 match introduced_owners.as_slice() {
7125 [] => ordinary,
7126 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
7127 _ => EnclosingMemberOwnerResolution::Ambiguous,
7128 }
7129}
7130
7131fn member_using_declaration_bases(
7132 analyzer: &CppGraphSource<'_>,
7133 visibility: &VisibilityIndex<'_>,
7134 file: &ProjectFile,
7135 owner: &CodeUnit,
7136 member_name: &str,
7137) -> Result<Vec<CodeUnit>, ()> {
7138 let Some(source) = analyzer.get_source(owner, false) else {
7139 return Ok(Vec::new());
7140 };
7141 let scopes = cpp_member_using_declaration_scopes(&source, member_name);
7142 if scopes.is_empty() {
7143 return Ok(Vec::new());
7144 }
7145 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7146 return Ok(Vec::new());
7147 };
7148 let mut bases = Vec::new();
7149 for raw_ancestor in hierarchy.get_ancestors(owner) {
7150 let Some(ancestor) =
7151 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
7152 else {
7153 return Err(());
7154 };
7155 let qualified = cpp_name_for(&ancestor);
7156 if scopes
7157 .iter()
7158 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
7159 && !bases
7160 .iter()
7161 .any(|existing| same_visible_symbol(existing, &ancestor))
7162 {
7163 bases.push(ancestor);
7164 }
7165 }
7166 Ok(bases)
7167}
7168
7169pub fn lexical_component_tiers<'a>(
7170 components: &'a [String],
7171 global: bool,
7172 lexical_scope: &'a [String],
7173) -> impl Iterator<Item = Vec<String>> + 'a {
7174 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
7175 (0..=first_prefix_len).rev().map(move |prefix_len| {
7176 let mut qualified = Vec::with_capacity(prefix_len + components.len());
7177 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
7178 qualified.extend_from_slice(components);
7179 qualified
7180 })
7181}
7182
7183pub fn build_visible_identifier_index(
7184 analyzer: &CppGraphSource<'_>,
7185 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
7186 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
7187 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
7188) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
7189 let mut out = HashMap::default();
7190 for (file, visible) in visible_by_file {
7191 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
7192 for unit in visible {
7193 if unit.is_field()
7194 && !visible_source_files_by_root
7195 .get(file)
7196 .is_some_and(|sources| sources.contains(unit.source()))
7197 && cpp_global_field_has_internal_linkage_cached(
7198 analyzer,
7199 global_field_internal_linkage,
7200 unit,
7201 )
7202 {
7203 continue;
7204 }
7205 by_identifier
7206 .entry(unit.identifier().to_string())
7207 .or_default()
7208 .push(unit.clone());
7209 }
7210 for units in by_identifier.values_mut() {
7211 sort_lookup_units(units);
7212 units.dedup();
7213 }
7214 out.insert(file.clone(), by_identifier);
7215 }
7216 out
7217}
7218
7219fn sort_lookup_units(units: &mut [CodeUnit]) {
7220 units.sort_by(|left, right| {
7221 left.fq_name()
7222 .cmp(&right.fq_name())
7223 .then_with(|| left.signature().cmp(&right.signature()))
7224 .then_with(|| left.source().cmp(right.source()))
7225 .then_with(|| left.kind().cmp(&right.kind()))
7226 .then_with(|| {
7227 left.package_segment_count()
7228 .cmp(&right.package_segment_count())
7229 })
7230 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
7231 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
7232 });
7233}
7234
7235fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
7236 let interner = segment_interner();
7237 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
7238 let (left_text, left_kind) = interner.resolve(left_id);
7239 let (right_text, right_kind) = interner.resolve(right_id);
7240 let order = left_text
7241 .cmp(right_text)
7242 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
7243 if order != CmpOrdering::Equal {
7244 return order;
7245 }
7246 }
7247 left.len().cmp(&right.len())
7248}
7249
7250const fn segment_kind_order(kind: SegmentKind) -> u8 {
7251 match kind {
7252 SegmentKind::Path => 0,
7253 SegmentKind::Package => 1,
7254 SegmentKind::Type => 2,
7255 SegmentKind::Companion => 3,
7256 SegmentKind::Nested => 4,
7257 SegmentKind::Member => 5,
7258 SegmentKind::Unknown => 6,
7259 }
7260}
7261
7262fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
7263 let mut deduped = Vec::with_capacity(units.len());
7264 for unit in units.drain(..) {
7265 if !deduped.contains(&unit) {
7266 deduped.push(unit);
7267 }
7268 }
7269 *units = deduped;
7270}
7271
7272pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
7273 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7277 brokk_bifrost_core::analyzer::Language::Cpp,
7278 reference,
7279 );
7280 if parts.is_empty() {
7281 return Vec::new();
7282 }
7283
7284 let mut candidates = Vec::new();
7285 for package_len in 0..parts.len() {
7286 let package = parts[..package_len].join("::");
7287 let rest = &parts[package_len..];
7288 if rest.is_empty() {
7289 continue;
7290 }
7291 match kind {
7292 TargetKind::Type | TargetKind::Constructor => {
7293 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
7294 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7295 }
7296 TargetKind::FreeFunction
7297 | TargetKind::Method
7298 | TargetKind::GlobalField
7299 | TargetKind::MemberField
7300 | TargetKind::Macro => {
7301 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
7302 if rest.len() > 1 {
7303 let owner = rest[..rest.len() - 1].join("$");
7304 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
7305 push_cpp_fqn_candidate(&mut candidates, &package, &short);
7306 }
7307 }
7308 }
7309 }
7310 candidates
7311}
7312
7313fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
7314 let fqn = if package.is_empty() {
7315 short.to_string()
7316 } else {
7317 format!("{package}.{short}")
7318 };
7319 if !out.contains(&fqn) {
7320 out.push(fqn);
7321 }
7322}
7323
7324pub fn infer_cpp_initializer_type(
7325 analyzer: &CppGraphSource<'_>,
7326 visibility: &VisibilityIndex<'_>,
7327 file: &ProjectFile,
7328 source: &str,
7329 node: Node<'_>,
7330) -> Option<CodeUnit> {
7331 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
7332 .and_then(|binding| binding.unit)
7333}
7334
7335pub fn infer_cpp_initializer_binding(
7336 analyzer: &CppGraphSource<'_>,
7337 visibility: &VisibilityIndex<'_>,
7338 file: &ProjectFile,
7339 source: &str,
7340 node: Node<'_>,
7341 receiver_resolver: Option<&ReceiverResolver<'_>>,
7342) -> Option<CppScanBinding> {
7343 match node.kind() {
7344 "new_expression" => {
7345 let text = normalize_cpp_whitespace(node_text(node, source));
7346 let rest = text.strip_prefix("new ").unwrap_or(text.as_str());
7347 let type_text = rest.split(['(', '{']).next().unwrap_or(rest);
7348 let name = normalize_cpp_type_name(type_text);
7349 Some(CppScanBinding::from_type_name(
7350 name.clone(),
7351 visibility.resolve_type(file, &name),
7352 1,
7353 ))
7354 }
7355 "call_expression" => node.child_by_field_name("function").and_then(|function| {
7356 let function_text = node_text(function, source);
7357 let direct_type_binding = visibility
7358 .resolve_type(file, function_text)
7359 .map(|unit| CppScanBinding::from_unit(unit, 0));
7360 if function.kind() == "template_function" && direct_type_binding.is_some() {
7361 let lexical_namespace = enclosing_namespace_context(node, source);
7362 let arity = visibility.call_arity_evidence(file, node, source).exact();
7363 if let Some(arity) = arity
7364 && let Some(binding) = visibility.resolve_call_return_binding(
7365 analyzer,
7366 file,
7367 function_text,
7368 arity,
7369 lexical_namespace.as_deref(),
7370 direct_type_binding
7371 .as_ref()
7372 .and_then(|binding| binding.unit.as_ref()),
7373 )
7374 {
7375 return Some(binding);
7376 }
7377 let (has_callable, callable_binding) = visibility
7378 .resolve_call_return_binding_without_arity(
7379 analyzer,
7380 file,
7381 function_text,
7382 lexical_namespace.as_deref(),
7383 direct_type_binding
7384 .as_ref()
7385 .and_then(|binding| binding.unit.as_ref()),
7386 );
7387 if let Some(binding) = callable_binding {
7388 return Some(binding);
7389 }
7390 if has_callable {
7391 return None;
7392 }
7393 return direct_type_binding;
7394 }
7395 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
7396 let direct_type_binding_for_call = direct_type_binding.clone();
7397 resolve_static_method_call_return_binding(
7398 analyzer, visibility, file, source, function, arity,
7399 )
7400 .or_else(|| {
7401 visibility.resolve_call_return_binding(
7406 analyzer,
7407 file,
7408 function_text,
7409 arity,
7410 enclosing_namespace_context(node, source).as_deref(),
7411 direct_type_binding_for_call
7412 .as_ref()
7413 .and_then(|binding| binding.unit.as_ref()),
7414 )
7415 })
7416 .or(direct_type_binding)
7417 .or_else(|| {
7418 resolve_field_method_call_return_binding(
7419 analyzer,
7420 visibility,
7421 file,
7422 source,
7423 function,
7424 arity,
7425 receiver_resolver,
7426 )
7427 })
7428 }),
7429 _ => None,
7430 }
7431}
7432
7433fn resolve_static_method_call_return_binding(
7434 analyzer: &CppGraphSource<'_>,
7435 visibility: &VisibilityIndex<'_>,
7436 file: &ProjectFile,
7437 source: &str,
7438 function: Node<'_>,
7439 arity: usize,
7440) -> Option<CppScanBinding> {
7441 if function.kind() != "qualified_identifier" {
7442 return None;
7443 }
7444 let qualified = normalize_cpp_reference_text(node_text(function, source));
7445 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
7452 brokk_bifrost_core::analyzer::Language::Cpp,
7453 &qualified,
7454 );
7455 let (owner_text, member_name) = match parts.split_last() {
7456 Some((member, owner_parts)) if !owner_parts.is_empty() => {
7457 (owner_parts.join("::"), member.clone())
7458 }
7459 _ => {
7460 let scope = function.child_by_field_name("scope")?;
7461 let name = function.child_by_field_name("name")?;
7462 (
7463 node_text(scope, source).to_string(),
7464 node_text(name, source).to_string(),
7465 )
7466 }
7467 };
7468 let owner = visibility.resolve_type(file, &owner_text)?;
7469 let candidates = visibility
7470 .visible_members_for_owner_name(file, &owner, &member_name)
7471 .into_iter()
7472 .filter(|unit| unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity))
7473 .cloned()
7474 .collect::<Vec<_>>();
7475 unanimous_return_binding(analyzer, visibility, file, &candidates)
7476}
7477
7478fn resolve_field_method_call_return_binding(
7479 analyzer: &CppGraphSource<'_>,
7480 visibility: &VisibilityIndex<'_>,
7481 file: &ProjectFile,
7482 source: &str,
7483 function: Node<'_>,
7484 arity: usize,
7485 receiver_resolver: Option<&ReceiverResolver<'_>>,
7486) -> Option<CppScanBinding> {
7487 if function.kind() != "field_expression" {
7488 return None;
7489 }
7490 let receiver_resolver = receiver_resolver?;
7491 let field = function.child_by_field_name("field")?;
7492 let member_name = node_text(function_terminal_node(field), source);
7493 let receiver = function
7494 .child_by_field_name("argument")
7495 .or_else(|| function.named_child(0))?;
7496 let owners = receiver_resolver(receiver, source);
7497 let mut candidates = Vec::new();
7498 for owner in owners {
7499 let declaring_owner =
7500 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
7501 EnclosingMemberOwnerResolution::Owner(owner) => owner,
7502 EnclosingMemberOwnerResolution::Missing => continue,
7503 EnclosingMemberOwnerResolution::Ambiguous => return None,
7504 };
7505 candidates.extend(
7506 visibility
7507 .visible_members_for_owner_name(file, &declaring_owner, member_name)
7508 .into_iter()
7509 .filter(|unit| {
7510 unit.is_function() && cpp_callable_arity(analyzer, unit).accepts(arity)
7511 })
7512 .cloned(),
7513 );
7514 }
7515 unanimous_return_binding(analyzer, visibility, file, &candidates)
7516}
7517
7518fn unanimous_return_binding(
7519 analyzer: &CppGraphSource<'_>,
7520 visibility: &VisibilityIndex<'_>,
7521 file: &ProjectFile,
7522 candidates: &[CodeUnit],
7523) -> Option<CppScanBinding> {
7524 let mut resolved_return: Option<CppScanBinding> = None;
7525 for function in candidates {
7526 let metadata = analyzer.signature_metadata(function);
7527 let return_types = if metadata.is_empty() {
7528 vec![cpp_function_return_type_text(analyzer, function)?]
7529 } else {
7530 metadata
7531 .iter()
7532 .map(|metadata| metadata.return_type_text().map(str::to_string))
7533 .collect::<Option<Vec<_>>>()?
7534 };
7535 for return_text in return_types {
7536 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
7537 let name = normalize_cpp_type_name(&return_text);
7538 let binding = CppScanBinding::from_type_name(
7539 name.clone(),
7540 visibility
7541 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
7542 indirection,
7543 );
7544 if let Some(existing) = resolved_return.as_ref()
7545 && (existing.indirection != binding.indirection
7546 || match (&existing.unit, &binding.unit) {
7547 (Some(left), Some(right)) => !same_visible_symbol(left, right),
7548 (None, None) => existing.type_name != binding.type_name,
7549 (Some(_), None) | (None, Some(_)) => true,
7550 })
7551 {
7552 return None;
7553 }
7554 resolved_return = Some(binding);
7555 }
7556 }
7557 resolved_return
7558}
7559
7560fn aliases_from_prepared_source(
7561 cpp: &dyn CppSource,
7562 token: QueryToken<'_>,
7563 file: &ProjectFile,
7564) -> Vec<CppAlias> {
7565 let Some(prepared) = cpp.prepared_syntax(token, file) else {
7566 return Vec::new();
7567 };
7568 let mut aliases = Vec::new();
7569 collect_cpp_aliases(prepared.tree().root_node(), prepared.source(), &mut aliases);
7570 aliases
7571}
7572
7573fn collect_cpp_aliases(root: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7574 let mut stack = vec![root];
7575 while let Some(node) = stack.pop() {
7576 match node.kind() {
7577 "alias_declaration" if alias_has_visible_file_scope(node) => {
7578 if let Some(alias) = cpp_alias_from_alias_declaration(node, source) {
7579 out.push(alias);
7580 }
7581 }
7582 "type_definition" if alias_has_visible_file_scope(node) => {
7583 collect_typedef_aliases(node, source, out)
7584 }
7585 _ => {}
7586 }
7587
7588 for index in (0..node.named_child_count()).rev() {
7589 if let Some(child) = node.named_child(index) {
7590 stack.push(child);
7591 }
7592 }
7593 }
7594}
7595
7596fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
7597 let mut current = node.parent();
7598 while let Some(parent) = current {
7599 match parent.kind() {
7600 "translation_unit"
7601 | "namespace_definition"
7602 | "declaration_list"
7603 | "linkage_specification" => current = parent.parent(),
7604 "template_declaration" => current = parent.parent(),
7605 _ => return false,
7606 }
7607 }
7608 true
7609}
7610
7611fn cpp_alias_from_alias_declaration(node: Node<'_>, source: &str) -> Option<CppAlias> {
7612 let name = node
7613 .child_by_field_name("name")
7614 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7615 let target = node
7616 .child_by_field_name("type")
7617 .and_then(|node| normalize_reference_name(node_text(node, source)))?;
7618 Some(CppAlias {
7619 name,
7620 target,
7621 namespace: enclosing_namespace_context(node, source),
7622 })
7623}
7624
7625fn collect_typedef_aliases(node: Node<'_>, source: &str, out: &mut Vec<CppAlias>) {
7626 let Some(type_node) = node.child_by_field_name("type") else {
7627 return;
7628 };
7629 let Some(target) = normalize_reference_name(node_text(type_node, source)) else {
7630 return;
7631 };
7632
7633 let mut cursor = node.walk();
7634 for child in node.named_children(&mut cursor) {
7635 if same_node(child, type_node) {
7636 continue;
7637 }
7638 if let Some(name) = extract_typedef_declarator_name(child, source) {
7639 out.push(CppAlias {
7640 name,
7641 target: target.clone(),
7642 namespace: enclosing_namespace_context(node, source),
7643 });
7644 }
7645 }
7646}
7647
7648fn extract_typedef_declarator_name(node: Node<'_>, source: &str) -> Option<String> {
7649 match node.kind() {
7650 "identifier" | "field_identifier" | "type_identifier" | "qualified_identifier" => {
7651 normalize_reference_name(node_text(node, source))
7652 }
7653 _ => node
7654 .child_by_field_name("declarator")
7655 .or_else(|| node.child_by_field_name("name"))
7656 .or_else(|| last_named_child(node))
7657 .and_then(|child| extract_typedef_declarator_name(child, source)),
7658 }
7659}
7660
7661fn last_named_child(node: Node<'_>) -> Option<Node<'_>> {
7662 let count = node.named_child_count();
7663 if count == 0 {
7664 None
7665 } else {
7666 node.named_child(count - 1)
7667 }
7668}
7669
7670pub fn collect_include_closure(
7671 analyzer: &CppGraphSource<'_>,
7672 include_targets: &IncludeTargetIndex,
7673 file: &ProjectFile,
7674 out: &mut HashSet<ProjectFile>,
7675 cancellation: Option<&CancellationToken>,
7676) {
7677 let mut stack = vec![file.clone()];
7678 while let Some(file) = stack.pop() {
7679 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7680 break;
7681 }
7682 if !out.insert(file.clone()) {
7683 continue;
7684 }
7685 let imports = analyzer.import_statements(&file);
7686 for include in cpp_include_paths(&imports) {
7687 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
7688 stack.push(target);
7689 }
7690 }
7691 }
7692}
7693
7694fn collect_visible_declarations(
7695 include_graph: &IncludeGraph,
7696 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
7697 file: &ProjectFile,
7698 visited: &mut HashSet<ProjectFile>,
7699 out: &mut HashSet<CodeUnit>,
7700 cancellation: Option<&CancellationToken>,
7701) {
7702 let mut stack = vec![file.clone()];
7703 while let Some(file) = stack.pop() {
7704 if cancellation.is_some_and(CancellationToken::is_cancelled) {
7705 break;
7706 }
7707 if !visited.insert(file.clone()) {
7708 continue;
7709 }
7710 if let Some(declarations) = declarations_by_file.get(&file) {
7711 out.extend(declarations.iter().cloned());
7712 }
7713 stack.extend(include_graph.targets(&file).iter().cloned());
7714 }
7715}
7716
7717pub fn signature_arity(signature: Option<&str>) -> usize {
7718 let Some(signature) = signature else {
7719 return 0;
7720 };
7721 let inner = signature
7722 .find('(')
7723 .and_then(|open| {
7724 signature[open + 1..]
7725 .find(')')
7726 .map(|close| &signature[open + 1..open + 1 + close])
7727 })
7728 .unwrap_or(signature)
7729 .trim();
7730 if inner.is_empty() || inner == "void" {
7731 return 0;
7732 }
7733 cpp_split_top_level_commas(inner).count()
7734}
7735
7736fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
7737 let source = format!("void __bifrost_macro_parameters({replacement});");
7738 let mut parser = Parser::new();
7739 parser
7740 .set_language(&tree_sitter_cpp::LANGUAGE.into())
7741 .ok()?;
7742 let tree = parser.parse(&source, None)?;
7743 let root = tree.root_node();
7744 if root.has_error() {
7745 return None;
7746 }
7747 let declaration = root.named_child(0)?;
7748 let declarator = declaration.child_by_field_name("declarator")?;
7749 let parameters = declarator.child_by_field_name("parameters")?;
7750 let mut required = 0;
7751 let mut total = 0;
7752 let mut repeated = false;
7753 let mut cursor = parameters.walk();
7754 for parameter in parameters.children(&mut cursor) {
7755 match parameter.kind() {
7756 "parameter_declaration" => {
7757 if parameter.child_by_field_name("declarator").is_none()
7758 && parameter
7759 .child_by_field_name("type")
7760 .is_some_and(|type_node| node_text(type_node, &source).trim() == "void")
7761 {
7762 continue;
7763 }
7764 required += 1;
7765 total += 1;
7766 }
7767 "optional_parameter_declaration" => total += 1,
7768 "variadic_parameter" | "variadic_parameter_declaration" | "..." => {
7769 repeated = true;
7770 }
7771 _ => {}
7772 }
7773 }
7774 Some(CallableArity::new(required, total, repeated))
7775}
7776
7777pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> CallableArity {
7778 analyzer
7779 .signature_metadata(unit)
7780 .into_iter()
7781 .find_map(|metadata| metadata.callable_arity())
7782 .unwrap_or_else(|| CallableArity::exact(signature_arity(unit.signature())))
7783}
7784
7785pub fn cpp_callable_parameter_types(
7786 analyzer: &CppGraphSource<'_>,
7787 unit: &CodeUnit,
7788) -> Option<Vec<String>> {
7789 analyzer
7790 .signature_metadata(unit)
7791 .into_iter()
7792 .find_map(|metadata| metadata.callable_parameter_types().map(<[String]>::to_vec))
7793 .or_else(|| unit.signature().and_then(cpp_signature_param_types))
7794}
7795
7796fn merge_compatible_callable_arities(
7797 left: CallableArity,
7798 right: CallableArity,
7799) -> Option<CallableArity> {
7800 let total = left.total();
7801 let left_repeated = left.accepts(total.saturating_add(1));
7802 let right_repeated = right.accepts(right.total().saturating_add(1));
7803 if total != right.total() || left_repeated != right_repeated {
7804 return None;
7805 }
7806 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
7807 Some(CallableArity::new(required, total, left_repeated))
7808}
7809
7810fn find_include_activation(
7811 cpp: &dyn CppSource,
7812 token: QueryToken<'_>,
7813 file: &ProjectFile,
7814 prepared: &PreparedSyntaxTree,
7815 donor_source: &ProjectFile,
7816) -> Option<usize> {
7817 let include_targets = cpp.include_target_index();
7818 let mut direct_includes = Vec::new();
7819 let mut nodes = vec![prepared.tree().root_node()];
7820 let reference = CallableReferenceContext {
7823 file,
7824 position: None,
7825 };
7826 while let Some(node) = nodes.pop() {
7827 if node.kind() == "preproc_include" {
7828 if callable_preprocessor_context_is_visible_for_reference(
7829 node,
7830 prepared.source(),
7831 &reference,
7832 ) {
7833 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7834 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7835 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
7836 file,
7837 &include,
7838 include_targets,
7839 )) {
7840 direct_includes.push((node.end_byte(), target));
7841 }
7842 }
7843 }
7844 continue;
7845 }
7846 for index in (0..node.named_child_count()).rev() {
7847 if let Some(child) = node.named_child(index) {
7848 nodes.push(child);
7849 }
7850 }
7851 }
7852 direct_includes.sort_by_key(|(activation, _)| *activation);
7853 let mut known_missing = HashSet::default();
7854 direct_includes
7855 .into_iter()
7856 .find(|(_, direct)| {
7857 unconditional_include_reaches(
7858 cpp,
7859 token,
7860 include_targets,
7861 direct,
7862 donor_source,
7863 file,
7864 &mut known_missing,
7865 )
7866 })
7867 .map(|(activation, _)| activation)
7868}
7869
7870fn find_conditional_include_projection_index(
7871 cpp: &dyn CppSource,
7872 token: QueryToken<'_>,
7873 file: &ProjectFile,
7874 prepared: &PreparedSyntaxTree,
7875 on_state: &dyn Fn(),
7876) -> ConditionalIncludeProjectionIndex {
7877 let include_targets = cpp.include_target_index();
7878 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
7879 HashMap::default();
7880 let mut pending = Vec::new();
7881 let mut nodes = vec![prepared.tree().root_node()];
7882 while let Some(node) = nodes.pop() {
7883 if node.kind() == "preproc_include" {
7884 let Some(required_guards) = preprocessor_guard_environment(node, prepared.source())
7885 else {
7886 continue;
7887 };
7888 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
7889 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7890 let Some(target) = unique_include_target(resolve_include_targets_with_index(
7891 file,
7892 &include,
7893 include_targets,
7894 )) else {
7895 continue;
7896 };
7897 pending.push((target, node.end_byte(), required_guards.clone()));
7898 }
7899 continue;
7900 }
7901 for index in (0..node.named_child_count()).rev() {
7902 if let Some(child) = node.named_child(index) {
7903 nodes.push(child);
7904 }
7905 }
7906 }
7907
7908 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
7920 HashMap::default();
7921 while let Some((current_file, activation_byte, required_guards)) = pending.pop() {
7922 let guard_sets = expanded
7923 .entry((current_file.clone(), activation_byte))
7924 .or_default();
7925 if guard_sets
7926 .iter()
7927 .any(|existing| existing.is_subset(&required_guards))
7928 {
7929 continue;
7930 }
7931 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
7932 .drain(..)
7933 .partition(|existing| required_guards.is_subset(existing));
7934 *guard_sets = kept;
7935 guard_sets.push(required_guards.clone());
7936 if !evicted.is_empty()
7937 && let Some(projections) = projections_by_source.get_mut(¤t_file)
7938 {
7939 projections.retain(|projection| {
7940 projection.activation_byte != activation_byte
7941 || !evicted.contains(&projection.required_guards)
7942 });
7943 }
7944 on_state();
7945
7946 projections_by_source
7949 .entry(current_file.clone())
7950 .or_default()
7951 .push(ConditionalIncludeProjection {
7952 activation_byte,
7953 required_guards: required_guards.clone(),
7954 });
7955
7956 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
7957 continue;
7958 };
7959 let mut nodes = vec![current_prepared.tree().root_node()];
7960 while let Some(node) = nodes.pop() {
7961 if node.kind() == "preproc_include" {
7962 let Some(include_guards) =
7963 preprocessor_guard_environment(node, current_prepared.source())
7964 else {
7965 continue;
7966 };
7967 let Some(path_guards) =
7968 merge_preprocessor_guards(&required_guards, &include_guards)
7969 else {
7970 continue;
7971 };
7972 let raw = normalize_cpp_whitespace(node_text(node, current_prepared.source()));
7973 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
7974 let Some(target) = unique_include_target(resolve_include_targets_with_index(
7975 ¤t_file,
7976 &include,
7977 include_targets,
7978 )) else {
7979 continue;
7980 };
7981 pending.push((target, activation_byte, path_guards.clone()));
7982 }
7983 continue;
7984 }
7985 for index in (0..node.named_child_count()).rev() {
7986 if let Some(child) = node.named_child(index) {
7987 nodes.push(child);
7988 }
7989 }
7990 }
7991 }
7992
7993 projections_by_source
7994 .into_iter()
7995 .map(|(source, mut projections)| {
7996 projections.sort_by_key(|projection| projection.activation_byte);
7997 (source, Arc::from(projections))
7998 })
7999 .collect()
8000}
8001
8002fn unconditional_include_reaches(
8003 cpp: &dyn CppSource,
8004 token: QueryToken<'_>,
8005 include_targets: &IncludeTargetIndex,
8006 first: &ProjectFile,
8007 donor_source: &ProjectFile,
8008 reference_file: &ProjectFile,
8009 known_missing: &mut HashSet<ProjectFile>,
8010) -> bool {
8011 if first == donor_source {
8012 return true;
8013 }
8014 if known_missing.contains(first) {
8015 return false;
8016 }
8017 let reference_is_c = reference_file
8018 .rel_path()
8019 .extension()
8020 .and_then(|extension| extension.to_str())
8021 == Some("c");
8022 if let Some(reaches) =
8023 cpp.cached_unconditional_include_reachability(first, donor_source, reference_is_c)
8024 {
8025 return reaches;
8026 }
8027 let mut visited = HashSet::default();
8028 let mut files = vec![first.clone()];
8029 let reference = CallableReferenceContext {
8032 file: reference_file,
8033 position: None,
8034 };
8035 while let Some(file) = files.pop() {
8036 if file == *donor_source {
8037 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, true);
8038 return true;
8039 }
8040 if known_missing.contains(&file) || !visited.insert(file.clone()) {
8041 continue;
8042 }
8043 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
8044 continue;
8045 };
8046 let mut nodes = vec![prepared.tree().root_node()];
8047 while let Some(node) = nodes.pop() {
8048 if node.kind() == "preproc_include" {
8049 if callable_preprocessor_context_is_visible_for_reference(
8050 node,
8051 prepared.source(),
8052 &reference,
8053 ) {
8054 let raw = normalize_cpp_whitespace(node_text(node, prepared.source()));
8055 for include in cpp_include_paths(std::slice::from_ref(&raw)) {
8056 if let Some(target) = unique_include_target(
8057 resolve_include_targets_with_index(&file, &include, include_targets),
8058 ) {
8059 files.push(target);
8060 }
8061 }
8062 }
8063 continue;
8064 }
8065 for index in (0..node.named_child_count()).rev() {
8066 if let Some(child) = node.named_child(index) {
8067 nodes.push(child);
8068 }
8069 }
8070 }
8071 }
8072 known_missing.extend(visited);
8073 cpp.cache_unconditional_include_reachability(first, donor_source, reference_is_c, false);
8074 false
8075}
8076
8077fn declaration_guard_requirements(
8078 analyzer: &CppGraphSource<'_>,
8079 cpp: &dyn CppSource,
8080 candidate: &CodeUnit,
8081) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
8082 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
8083 return Vec::new();
8084 };
8085 let root = prepared.tree().root_node();
8086 analyzer
8087 .ranges(candidate)
8088 .into_iter()
8089 .filter_map(|range| {
8090 root.descendant_for_byte_range(range.start_byte, range.end_byte)
8091 .and_then(|node| preprocessor_guard_environment(node, prepared.source()))
8092 .map(|required| (range.start_byte, required))
8096 })
8097 .collect()
8098}
8099
8100fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
8101 analyzer
8102 .ranges(candidate)
8103 .into_iter()
8104 .map(|range| range.start_byte)
8105 .min()
8106}
8107
8108fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
8114 let (first, rest) = contexts.split_first()?;
8115 Some(
8116 first
8117 .defined_macros
8118 .iter()
8119 .filter(|name| {
8120 rest.iter()
8121 .all(|context| context.defined_macros.contains(*name))
8122 })
8123 .cloned()
8124 .collect(),
8125 )
8126}
8127
8128fn guard_requirements_hold_at_reference(
8129 required: &HashSet<PreprocessorGuard>,
8130 reference: Option<&HashSet<PreprocessorGuard>>,
8131) -> bool {
8132 reference.is_some_and(|active| {
8133 required
8134 .iter()
8135 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
8136 })
8137}
8138
8139fn preprocessor_guard_holds_at_reference(
8140 required: &PreprocessorGuard,
8141 active: &HashSet<PreprocessorGuard>,
8142) -> bool {
8143 if active.contains(required) {
8144 return true;
8145 }
8146 let active_expression = BooleanGuardExpression::all(
8147 active
8148 .iter()
8149 .filter_map(PreprocessorGuard::as_boolean_expression),
8150 );
8151 required
8152 .as_boolean_expression()
8153 .is_some_and(|required| active_expression.implies(&required))
8154}
8155
8156fn guards_compatible_at_reference(
8161 declaration: &HashSet<PreprocessorGuard>,
8162 reference: Option<&HashSet<PreprocessorGuard>>,
8163) -> bool {
8164 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
8165}
8166
8167pub fn preprocessor_conditional_family_range(
8176 root: Node<'_>,
8177 start_byte: usize,
8178 end_byte: usize,
8179) -> Option<(usize, usize)> {
8180 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
8181 let mut ancestor = Some(node);
8182 while let Some(current) = ancestor {
8183 if is_preprocessor_conditional(current)
8184 && preprocessor_conditional_contains_descendant(current, node)
8185 {
8186 let family = preprocessor_conditional_family_root(current);
8187 return Some((family.start_byte(), family.end_byte()));
8188 }
8189 ancestor = current.parent();
8190 }
8191 None
8192}
8193
8194fn preprocessor_conditional_family_for_declaration(node: Node<'_>) -> Option<Node<'_>> {
8195 let mut ancestor = node.parent();
8196 while let Some(current) = ancestor {
8197 if is_preprocessor_conditional(current)
8198 && preprocessor_conditional_contains_descendant(current, node)
8199 {
8200 let family = preprocessor_conditional_family_root(current);
8201 if preprocessor_conditional_family_has_terminal_else(family) {
8202 return Some(family);
8203 }
8204 }
8205 ancestor = current.parent();
8206 }
8207 None
8208}
8209
8210fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
8211 while let Some(parent) = conditional.parent() {
8212 let is_alternative = parent
8213 .child_by_field_name("alternative")
8214 .is_some_and(|alternative| {
8215 alternative.start_byte() == conditional.start_byte()
8216 && alternative.end_byte() == conditional.end_byte()
8217 });
8218 if !is_alternative {
8219 break;
8220 }
8221 conditional = parent;
8222 }
8223 conditional
8224}
8225
8226fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
8227 loop {
8228 let Some(alternative) = conditional.child_by_field_name("alternative") else {
8229 return false;
8230 };
8231 match alternative.kind() {
8232 "preproc_else" => return true,
8233 "preproc_elif" => conditional = alternative,
8234 _ => return false,
8235 }
8236 }
8237}
8238
8239pub fn preprocessor_guard_environment(
8240 node: Node<'_>,
8241 source: &str,
8242) -> Option<HashSet<PreprocessorGuard>> {
8243 let mut guards = HashSet::default();
8244 let mut ancestor = node.parent();
8245 while let Some(conditional) = ancestor {
8246 if matches!(
8247 conditional.kind(),
8248 "preproc_if" | "preproc_ifdef" | "preproc_elif"
8249 ) && !is_file_covering_include_guard(conditional, source)
8250 && preprocessor_conditional_contains_descendant(conditional, node)
8251 {
8252 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
8253 match guard {
8254 PreprocessorGuard::Constant(true) => {
8255 ancestor = conditional.parent();
8256 continue;
8257 }
8258 PreprocessorGuard::Constant(false) => return None,
8259 _ => {}
8260 }
8261 if guards.contains(&guard.negated()) {
8262 return None;
8263 }
8264 guards.insert(guard);
8265 }
8266 ancestor = conditional.parent();
8267 }
8268 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
8269 match guard {
8270 PreprocessorGuard::Constant(true) => {}
8271 PreprocessorGuard::Constant(false) => return None,
8272 _ => {
8273 if guards.contains(&guard.negated()) {
8274 return None;
8275 }
8276 guards.insert(guard);
8277 }
8278 }
8279 }
8280 Some(guards)
8281}
8282
8283fn fragmented_statement_preprocessor_guard(
8284 descendant: Node<'_>,
8285 source: &str,
8286) -> Option<PreprocessorGuard> {
8287 let mut ancestor = descendant.parent();
8293 while let Some(statement) = ancestor {
8294 if statement.kind() == "if_statement"
8295 && let (Some(consequence), Some(alternative)) = (
8296 statement.child_by_field_name("consequence"),
8297 statement.child_by_field_name("alternative"),
8298 )
8299 && alternative.start_byte() <= descendant.start_byte()
8300 && descendant.end_byte() <= alternative.end_byte()
8301 {
8302 let mut cursor = consequence.walk();
8303 let openers = consequence
8304 .named_children(&mut cursor)
8305 .filter(|child| {
8306 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
8307 && child
8308 .child(child.child_count().saturating_sub(1))
8309 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
8310 })
8311 .collect::<Vec<_>>();
8312 if openers.len() != 1 {
8313 ancestor = statement.parent();
8314 continue;
8315 }
8316
8317 let mut terminators = Vec::new();
8318 let mut stack = vec![alternative];
8319 while let Some(node) = stack.pop() {
8320 if node.kind() == "preproc_call"
8321 && node.start_byte() >= descendant.end_byte()
8322 && node
8323 .child_by_field_name("directive")
8324 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
8325 {
8326 terminators.push(node);
8327 continue;
8328 }
8329 for index in (0..node.named_child_count()).rev() {
8330 if let Some(child) = node.named_child(index) {
8331 stack.push(child);
8332 }
8333 }
8334 }
8335 if terminators.len() == 1 {
8336 return simple_preprocessor_guard(openers[0], source);
8337 }
8338 }
8339 ancestor = statement.parent();
8340 }
8341 None
8342}
8343
8344fn preprocessor_guard_for_descendant(
8345 conditional: Node<'_>,
8346 descendant: Node<'_>,
8347 source: &str,
8348) -> Option<PreprocessorGuard> {
8349 let mut guard = simple_preprocessor_guard(conditional, source)?;
8350 if conditional
8351 .child_by_field_name("alternative")
8352 .is_some_and(|alternative| {
8353 alternative.start_byte() <= descendant.start_byte()
8354 && descendant.end_byte() <= alternative.end_byte()
8355 })
8356 {
8357 let alternative = conditional.child_by_field_name("alternative")?;
8358 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
8362 return None;
8363 }
8364 guard = guard.negated();
8365 }
8366 Some(guard)
8367}
8368
8369fn preprocessor_conditional_contains_descendant(
8370 conditional: Node<'_>,
8371 descendant: Node<'_>,
8372) -> bool {
8373 cpp_displaced_preprocessor_boundary(conditional)
8374 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
8375}
8376
8377pub fn merge_preprocessor_guards(
8378 left: &HashSet<PreprocessorGuard>,
8379 right: &HashSet<PreprocessorGuard>,
8380) -> Option<HashSet<PreprocessorGuard>> {
8381 let mut merged = left.clone();
8382 for guard in right {
8383 if merged.contains(&guard.negated()) {
8384 return None;
8385 }
8386 merged.insert(guard.clone());
8387 }
8388 Some(merged)
8389}
8390
8391fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
8392 if conditional.kind() == "preproc_ifdef" {
8393 let name = conditional.child_by_field_name("name")?;
8394 let name = node_text(name, source).to_string();
8395 return match conditional.child(0)?.kind() {
8396 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
8397 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
8398 _ => None,
8399 };
8400 }
8401 let condition = conditional.child_by_field_name("condition")?;
8402 simple_preprocessor_expression_guard(condition, source).or_else(|| {
8403 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
8404 node_text(condition, source),
8405 )))
8406 })
8407}
8408
8409fn simple_preprocessor_expression_guard(
8410 expression: Node<'_>,
8411 source: &str,
8412) -> Option<PreprocessorGuard> {
8413 match expression.kind() {
8414 "number_literal" => match node_text(expression, source).trim() {
8415 "0" => Some(PreprocessorGuard::Constant(false)),
8416 "1" => Some(PreprocessorGuard::Constant(true)),
8417 _ => None,
8418 },
8419 "preproc_defined" => {
8420 let identifier = (0..expression.named_child_count())
8421 .filter_map(|index| expression.named_child(index))
8422 .find(|child| child.kind() == "identifier")?;
8423 Some(PreprocessorGuard::Defined(
8424 node_text(identifier, source).to_string(),
8425 ))
8426 }
8427 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
8428 node_text(expression, source).to_string(),
8429 ))),
8430 "unary_expression"
8431 if expression
8432 .child_by_field_name("operator")
8433 .is_some_and(|operator| operator.kind() == "!") =>
8434 {
8435 simple_preprocessor_expression_guard(
8436 expression.child_by_field_name("argument")?,
8437 source,
8438 )
8439 .map(|guard| guard.negated())
8440 }
8441 "parenthesized_expression" => (0..expression.named_child_count())
8442 .filter_map(|index| expression.named_child(index))
8443 .next()
8444 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
8445 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
8446 expression, source,
8447 ))),
8448 _ => None,
8449 }
8450}
8451
8452fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
8453 match expression.kind() {
8454 "number_literal" => match node_text(expression, source).trim() {
8455 "0" => BooleanGuardExpression::Constant(false),
8456 "1" => BooleanGuardExpression::Constant(true),
8457 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8458 expression, source,
8459 ))),
8460 },
8461 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
8462 "preproc_defined" => {
8463 let identifier = (0..expression.named_child_count())
8464 .filter_map(|index| expression.named_child(index))
8465 .find(|child| child.kind() == "identifier");
8466 identifier.map_or_else(
8467 || {
8468 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8469 expression, source,
8470 )))
8471 },
8472 |identifier| {
8473 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
8474 },
8475 )
8476 }
8477 "unary_expression"
8478 if expression
8479 .child_by_field_name("operator")
8480 .is_some_and(|operator| operator.kind() == "!") =>
8481 {
8482 expression.child_by_field_name("argument").map_or_else(
8483 || {
8484 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8485 expression, source,
8486 )))
8487 },
8488 |argument| boolean_preprocessor_expression(argument, source).negated(),
8489 )
8490 }
8491 "parenthesized_expression" => (0..expression.named_child_count())
8492 .filter_map(|index| expression.named_child(index))
8493 .next()
8494 .map_or_else(
8495 || {
8496 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8497 expression, source,
8498 )))
8499 },
8500 |child| boolean_preprocessor_expression(child, source),
8501 ),
8502 "binary_expression" => {
8503 let operands = || {
8504 Some((
8505 boolean_preprocessor_expression(
8506 expression.child_by_field_name("left")?,
8507 source,
8508 ),
8509 boolean_preprocessor_expression(
8510 expression.child_by_field_name("right")?,
8511 source,
8512 ),
8513 ))
8514 };
8515 match expression
8516 .child_by_field_name("operator")
8517 .map(|operator| operator.kind())
8518 {
8519 Some("&&") => operands().map_or_else(
8520 || {
8521 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8522 expression, source,
8523 )))
8524 },
8525 |(left, right)| BooleanGuardExpression::all([left, right]),
8526 ),
8527 Some("||") => operands().map_or_else(
8528 || {
8529 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8530 expression, source,
8531 )))
8532 },
8533 |(left, right)| BooleanGuardExpression::any([left, right]),
8534 ),
8535 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
8536 expression, source,
8537 ))),
8538 }
8539 }
8540 _ => {
8541 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
8542 }
8543 }
8544}
8545
8546fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
8547 if targets.len() == 1 {
8548 targets.pop()
8549 } else {
8550 None
8551 }
8552}
8553
8554fn nameable_callable_declaration_nodes<'tree>(
8563 analyzer: &CppGraphSource<'_>,
8564 prepared: &'tree PreparedSyntaxTree,
8565 candidate: &CodeUnit,
8566) -> Vec<Node<'tree>> {
8567 let root = prepared.tree().root_node();
8568 analyzer
8569 .ranges(candidate)
8570 .into_iter()
8571 .filter_map(|range| {
8572 let mut declaration =
8573 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
8574 while !matches!(
8575 declaration.kind(),
8576 "declaration" | "field_declaration" | "function_definition"
8577 ) {
8578 declaration = declaration.parent()?;
8579 }
8580 let mut ancestor = declaration.parent();
8581 while let Some(node) = ancestor {
8582 if node.kind() == "function_definition"
8583 && is_recovered_declaration_scope_container(node, prepared.source())
8584 {
8585 ancestor = node.parent();
8586 continue;
8587 }
8588 if node.kind() == "compound_statement"
8589 && node.parent().is_some_and(|parent| {
8590 is_recovered_declaration_scope_container(parent, prepared.source())
8591 })
8592 {
8593 ancestor = node.parent().and_then(|parent| parent.parent());
8594 continue;
8595 }
8596 if matches!(
8597 node.kind(),
8598 "compound_statement" | "function_definition" | "lambda_expression"
8599 ) {
8600 return None;
8601 }
8602 ancestor = node.parent();
8603 }
8604 Some(declaration)
8605 })
8606 .collect()
8607}
8608
8609fn callable_declaration_activation_in_file(
8610 analyzer: &CppGraphSource<'_>,
8611 prepared: &PreparedSyntaxTree,
8612 candidate: &CodeUnit,
8613 reference: &CallableReferenceContext<'_>,
8614) -> Option<usize> {
8615 nameable_callable_declaration_nodes(analyzer, prepared, candidate)
8616 .into_iter()
8617 .filter(|declaration| {
8618 callable_preprocessor_context_is_visible_for_reference(
8619 *declaration,
8620 prepared.source(),
8621 reference,
8622 )
8623 })
8624 .map(callable_declaration_activation_byte)
8625 .min()
8626}
8627
8628fn callable_declaration_activation_byte(declaration: Node<'_>) -> usize {
8633 if declaration.kind() != "function_definition" {
8634 return declaration.end_byte();
8635 }
8636 declaration
8637 .child_by_field_name("declarator")
8638 .map_or(declaration.end_byte(), |declarator| declarator.end_byte())
8639}
8640
8641struct CallableReferenceContext<'a> {
8647 file: &'a ProjectFile,
8648 position: Option<CallableReferencePosition<'a>>,
8649}
8650
8651struct CallableReferencePosition<'a> {
8655 prepared: &'a PreparedSyntaxTree,
8656 byte: usize,
8657 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
8658}
8659
8660impl CallableReferenceContext<'_> {
8661 fn is_c(&self) -> bool {
8662 self.file
8663 .rel_path()
8664 .extension()
8665 .and_then(|extension| extension.to_str())
8666 == Some("c")
8667 }
8668
8669 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
8670 let position = self.position.as_ref()?;
8671 position
8672 .guards
8673 .get_or_init(|| {
8674 position
8675 .prepared
8676 .tree()
8677 .root_node()
8678 .descendant_for_byte_range(position.byte, position.byte)
8679 .and_then(|node| {
8680 preprocessor_guard_environment(node, position.prepared.source())
8681 })
8682 })
8683 .as_ref()
8684 }
8685}
8686
8687fn callable_preprocessor_context_is_visible_for_reference(
8688 node: Node<'_>,
8689 source: &str,
8690 reference: &CallableReferenceContext<'_>,
8691) -> bool {
8692 let reference_is_c = reference.is_c();
8693 let mut ancestor = node.parent();
8694 while let Some(conditional) = ancestor {
8695 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
8696 && !is_file_covering_include_guard(conditional, source)
8697 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
8698 && preprocessor_conditional_contains_descendant(conditional, node)
8699 {
8700 let Some(guard) = preprocessor_guard_for_descendant(conditional, node, source) else {
8701 return false;
8702 };
8703 match guard {
8704 PreprocessorGuard::Constant(true) => {}
8705 PreprocessorGuard::Constant(false) => return false,
8706 PreprocessorGuard::Defined(name) if name == "__cplusplus" => {
8707 if reference_is_c {
8708 return false;
8709 }
8710 }
8711 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
8712 if !reference_is_c {
8713 return false;
8714 }
8715 }
8716 guard => {
8722 if !reference
8723 .guards()
8724 .is_some_and(|active| preprocessor_guard_holds_at_reference(&guard, active))
8725 {
8726 return false;
8727 }
8728 }
8729 }
8730 }
8731 ancestor = conditional.parent();
8732 }
8733 true
8734}
8735
8736fn flattened_macro_namespace_declaration_matches(
8737 analyzer: &CppGraphSource<'_>,
8738 cpp: &dyn CppSource,
8739 reference_file: &ProjectFile,
8740 visible_declaration: &CodeUnit,
8741 qualified_candidate: &CodeUnit,
8742 reference_byte: usize,
8743) -> bool {
8744 if visible_declaration.kind() != qualified_candidate.kind()
8750 || visible_declaration.identifier() != qualified_candidate.identifier()
8751 || visible_declaration.signature() != qualified_candidate.signature()
8752 || !visible_declaration.package_name().is_empty()
8753 || qualified_candidate.package_name().is_empty()
8754 {
8755 return false;
8756 }
8757
8758 let Some(prepared) = cpp.prepared_syntax(analyzer.token, visible_declaration.source()) else {
8759 return false;
8760 };
8761 let root = prepared.tree().root_node();
8762 let closing_brace_limit = if visible_declaration.source() == reference_file {
8763 reference_byte
8764 } else {
8765 usize::MAX
8766 };
8767
8768 analyzer
8769 .ranges(visible_declaration)
8770 .into_iter()
8771 .any(|range| {
8772 let Some(mut declaration) =
8773 root.descendant_for_byte_range(range.start_byte, range.end_byte)
8774 else {
8775 return false;
8776 };
8777 while !matches!(
8778 declaration.kind(),
8779 "declaration" | "field_declaration" | "function_definition"
8780 ) {
8781 let Some(parent) = declaration.parent() else {
8782 return false;
8783 };
8784 declaration = parent;
8785 }
8786 if declaration
8787 .parent()
8788 .is_none_or(|parent| parent.kind() != "translation_unit")
8789 || !macro_displaced_cpp_return_type(declaration, prepared.source())
8790 {
8791 return false;
8792 }
8793
8794 let mut cursor = root.walk();
8795 root.named_children(&mut cursor).any(|sibling| {
8796 sibling.start_byte() >= declaration.end_byte()
8797 && sibling.start_byte() < closing_brace_limit
8798 && direct_unmatched_closing_brace(sibling)
8799 })
8800 })
8801}
8802
8803fn flattened_macro_namespace_components(
8804 declaration: Node<'_>,
8805 source: &str,
8806) -> Option<Vec<String>> {
8807 flattened_macro_function_namespace_components(declaration, source)
8808 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
8809}
8810
8811fn flattened_macro_function_namespace_components(
8812 declaration: Node<'_>,
8813 source: &str,
8814) -> Option<Vec<String>> {
8815 let body = declaration
8816 .parent()
8817 .filter(|parent| parent.kind() == "compound_statement")?;
8818 let function = body.parent()?;
8819 if function.child_by_field_name("body") != Some(body) {
8820 return None;
8821 }
8822 let namespace_name = recovered_macro_namespace_name(function, source)?;
8823 let mut components = enclosing_namespace_components(declaration, source)?;
8824 components.push(namespace_name);
8825 Some(components)
8826}
8827
8828fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
8839 if function.kind() != "function_definition" || !function.has_error() {
8840 return None;
8841 }
8842 let body = function
8843 .child_by_field_name("body")
8844 .filter(|body| body.kind() == "compound_statement")?;
8845 let mut cursor = function.walk();
8846 let prefix = function
8847 .named_children(&mut cursor)
8848 .take_while(|child| child.start_byte() < body.start_byte())
8849 .filter(|child| child.kind() != "comment")
8850 .collect::<Vec<_>>();
8851 let begin_index = prefix.iter().rposition(|child| {
8852 flattened_macro_sentinel_name(*child, source)
8853 .is_some_and(|name| is_namespace_begin_sentinel(&name))
8854 })?;
8855 let mut identifiers = Vec::new();
8856 let mut stack = prefix[begin_index + 1..]
8857 .iter()
8858 .rev()
8859 .copied()
8860 .collect::<Vec<_>>();
8861 while let Some(current) = stack.pop() {
8862 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
8863 identifiers.push(identifier);
8864 continue;
8865 }
8866 let mut cursor = current.walk();
8867 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
8868 stack.extend(children.into_iter().rev());
8869 }
8870 let [keyword, namespace_name] = identifiers.as_slice() else {
8871 return None;
8872 };
8873 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
8874 {
8875 return None;
8876 }
8877 let mut next = function.next_named_sibling();
8878 let next = loop {
8879 let candidate = next?;
8880 next = candidate.next_named_sibling();
8881 if candidate.kind() != "comment" {
8882 break candidate;
8883 }
8884 };
8885 flattened_macro_sentinel_name(next, source)
8886 .is_some_and(|name| is_namespace_end_sentinel(&name))
8887 .then(|| namespace_name.clone())
8888}
8889
8890fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
8895 crate::declarations::is_recovered_exported_class_container(node, source)
8896 || recovered_macro_namespace_name(node, source).is_some()
8897}
8898
8899fn flattened_macro_error_namespace_components(
8900 declaration: Node<'_>,
8901 source: &str,
8902) -> Option<Vec<String>> {
8903 let parent = declaration
8904 .parent()
8905 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
8906 let mut cursor = parent.walk();
8907 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
8908 let declaration_index = siblings
8909 .iter()
8910 .position(|candidate| same_node(*candidate, declaration))?;
8911 let begin_index = (0..declaration_index).rev().find(|index| {
8912 flattened_macro_sentinel_name(siblings[*index], source)
8913 .is_some_and(|name| is_namespace_begin_sentinel(&name))
8914 })?;
8915
8916 let significant = siblings[begin_index + 1..declaration_index]
8917 .iter()
8918 .copied()
8919 .filter(|node| node.kind() != "comment")
8920 .collect::<Vec<_>>();
8921 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
8922 return None;
8923 };
8924 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
8925 return None;
8926 }
8927 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
8928 if significant[2..].iter().any(|node| {
8929 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
8930 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
8931 })
8932 }) {
8933 return None;
8934 }
8935
8936 let mut saw_namespace_close = false;
8937 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
8938 if sibling.kind() == "comment" {
8939 continue;
8940 }
8941 if !saw_namespace_close {
8942 if direct_unmatched_closing_brace(sibling) {
8943 saw_namespace_close = true;
8944 continue;
8945 }
8946 if flattened_macro_sentinel_name(sibling, source).is_some() {
8947 return None;
8948 }
8949 continue;
8950 }
8951 if !flattened_macro_sentinel_name(sibling, source)
8952 .is_some_and(|name| is_namespace_end_sentinel(&name))
8953 {
8954 return None;
8955 }
8956 let mut components = enclosing_namespace_components(declaration, source)?;
8957 components.push(namespace_name);
8958 return Some(components);
8959 }
8960 None
8961}
8962
8963fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
8964 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
8968 node.named_child(0)?
8969 } else {
8970 node
8971 };
8972 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
8973 node.child_by_field_name("type")
8974 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
8975 })?;
8976 (cpp_export_macro_token(&candidate)
8977 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
8978 .then_some(candidate)
8979}
8980
8981fn is_namespace_begin_sentinel(name: &str) -> bool {
8984 name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE")
8985}
8986
8987fn is_namespace_end_sentinel(name: &str) -> bool {
8988 name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE")
8989}
8990
8991fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
8992 if node.kind() != "ERROR" || node.named_child_count() != 1 {
8993 return None;
8994 }
8995 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
8996 (!cpp_export_macro_token(&name)).then_some(name)
8997}
8998
8999fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
9000 if !matches!(
9001 node.kind(),
9002 "identifier" | "namespace_identifier" | "type_identifier"
9003 ) {
9004 return None;
9005 }
9006 let name = normalize_cpp_whitespace(node_text(node, source));
9007 (!name.is_empty()).then_some(name)
9008}
9009
9010fn guard_requirement_sets_match(
9011 left: &[(usize, HashSet<PreprocessorGuard>)],
9012 right: &[(usize, HashSet<PreprocessorGuard>)],
9013) -> bool {
9014 left.len() == right.len()
9015 && left.iter().all(|(_, left_guards)| {
9016 right
9017 .iter()
9018 .any(|(_, right_guards)| left_guards == right_guards)
9019 })
9020 && right.iter().all(|(_, right_guards)| {
9021 left.iter()
9022 .any(|(_, left_guards)| right_guards == left_guards)
9023 })
9024}
9025
9026fn macro_displaced_cpp_return_type(declaration: Node<'_>, source: &str) -> bool {
9027 let Some(type_node) = declaration.child_by_field_name("type") else {
9028 return false;
9029 };
9030 let type_name = normalize_cpp_whitespace(node_text(type_node, source));
9031 !type_name.is_empty()
9032 && type_name
9033 .chars()
9034 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
9035 && (0..declaration.named_child_count()).any(|index| {
9036 declaration
9037 .named_child(index)
9038 .is_some_and(|child| child.kind() == "ERROR")
9039 })
9040}
9041
9042fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
9043 node.kind() == "ERROR"
9044 && (0..node.child_count())
9045 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
9046}
9047
9048pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
9049 let mut ancestor = node.parent();
9050 while let Some(parent) = ancestor {
9051 if is_preprocessor_conditional(parent)
9052 && !is_file_covering_include_guard(parent, source)
9053 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
9054 {
9055 return false;
9056 }
9057 ancestor = parent.parent();
9058 }
9059 true
9060}
9061
9062fn is_split_cpp_language_linkage_wrapper(
9063 conditional: Node<'_>,
9064 descendant: Node<'_>,
9065 source: &str,
9066) -> bool {
9067 if conditional.child_by_field_name("alternative").is_some()
9068 || !matches!(
9069 simple_preprocessor_guard(conditional, source),
9070 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9071 )
9072 {
9073 return false;
9074 }
9075 let mut current = descendant.parent();
9076 let linkage = loop {
9077 let Some(node) = current else {
9078 return false;
9079 };
9080 if node == conditional {
9081 return false;
9082 }
9083 if node.kind() == "linkage_specification" {
9084 break node;
9085 }
9086 current = node.parent();
9087 };
9088 if linkage
9089 .child_by_field_name("value")
9090 .is_none_or(|value| node_text(value, source) != "\"C\"")
9091 {
9092 return false;
9093 }
9094 let Some(body) = linkage.child_by_field_name("body") else {
9095 return false;
9096 };
9097 let closes_opening_branch = (0..body.named_child_count())
9098 .filter_map(|index| body.named_child(index))
9099 .take_while(|child| child.end_byte() <= descendant.start_byte())
9100 .any(|child| {
9101 child.kind() == "preproc_call"
9102 && child
9103 .child_by_field_name("directive")
9104 .is_some_and(|directive| node_text(directive, source) == "#endif")
9105 });
9106 let reopens_for_closing_brace = (0..body.named_child_count())
9107 .filter_map(|index| body.named_child(index))
9108 .skip_while(|child| child.start_byte() < descendant.end_byte())
9109 .any(|child| {
9110 matches!(
9111 simple_preprocessor_guard(child, source),
9112 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
9113 ) && (0..child.child_count()).any(|index| {
9114 child
9115 .child(index)
9116 .is_some_and(|token| token.kind() == "#endif" && token.is_missing())
9117 })
9118 });
9119 closes_opening_branch && reopens_for_closing_brace
9120}
9121
9122pub fn call_arity(node: Node<'_>) -> usize {
9123 node.child_by_field_name("arguments")
9124 .or_else(|| node.child_by_field_name("parameters"))
9125 .or_else(|| node.child_by_field_name("value"))
9126 .or_else(|| first_named_child_of_kind(node, "argument_list"))
9127 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
9128 .map(|args| argument_children(args).count())
9129 .unwrap_or(0)
9130}
9131
9132pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
9133 let recovered_block_arguments = recovered_block_literal_arguments(node);
9134 (0..node.child_count())
9135 .filter_map(move |index| node.child(index))
9136 .filter(|child| child.is_named() && !child.is_extra())
9137 .flat_map(move |child| {
9138 if let Some((raw, left, right)) = recovered_block_arguments
9139 && child == raw
9140 {
9141 [Some(left), Some(right)]
9142 } else {
9143 [Some(child), None]
9144 }
9145 })
9146 .flatten()
9147}
9148
9149fn recovered_c_keyword_argument_count(
9150 file: &ProjectFile,
9151 call: Node<'_>,
9152 arguments: Node<'_>,
9153 source: &str,
9154) -> usize {
9155 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
9160 return 0;
9161 }
9162 let mut ancestor = Some(call);
9163 let function = loop {
9164 let Some(current) = ancestor else {
9165 return 0;
9166 };
9167 if current.kind() == "function_definition" {
9168 break current;
9169 }
9170 ancestor = current.parent();
9171 };
9172 let Some(parameters) = function
9173 .child_by_field_name("declarator")
9174 .and_then(|declarator| declarator.child_by_field_name("parameters"))
9175 else {
9176 return 0;
9177 };
9178 let displaced_parameter_keywords = (0..parameters.child_count())
9179 .filter_map(|index| parameters.child(index))
9180 .filter(|error| error.kind() == "ERROR")
9181 .filter_map(|error| {
9182 let parameter = error.prev_named_sibling()?;
9183 if parameter.kind() != "parameter_declaration"
9184 || parameter.end_byte() != error.start_byte()
9185 || extract_variable_name(parameter, source).is_some()
9186 {
9187 return None;
9188 }
9189 let mut children = (0..error.child_count())
9190 .filter_map(|index| error.child(index))
9191 .filter(|child| !child.is_extra() && !child.is_missing());
9192 let keyword = children.next()?;
9193 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
9194 .then_some(keyword)
9195 })
9196 .collect::<Vec<_>>();
9197 if displaced_parameter_keywords.is_empty() {
9198 return 0;
9199 }
9200
9201 (0..arguments.child_count())
9202 .filter_map(|index| arguments.child(index))
9203 .filter(|error| error.kind() == "ERROR" && error.is_extra())
9204 .filter(|error| {
9205 let mut children = (0..error.child_count())
9206 .filter_map(|index| error.child(index))
9207 .filter(|child| !child.is_extra() && !child.is_missing());
9208 let Some(comma) = children.next() else {
9209 return false;
9210 };
9211 let Some(keyword) = children.next() else {
9212 return false;
9213 };
9214 children.next().is_none()
9215 && comma.kind() == ","
9216 && !keyword.is_named()
9217 && keyword.child_count() == 0
9218 && displaced_parameter_keywords
9219 .iter()
9220 .any(|parameter| parameter.kind_id() == keyword.kind_id())
9221 })
9222 .count()
9223}
9224
9225fn recovered_block_literal_arguments<'tree>(
9226 arguments: Node<'tree>,
9227) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
9228 if arguments.kind() != "argument_list" {
9229 return None;
9230 }
9231 let mut raw_arguments = (0..arguments.child_count())
9232 .filter_map(|index| arguments.child(index))
9233 .filter(|child| child.is_named() && !child.is_extra());
9234 let raw = raw_arguments.next()?;
9235 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
9236 return None;
9237 }
9238
9239 let left = raw.child_by_field_name("left")?;
9240 if left.is_missing() || left.start_byte() == left.end_byte() {
9241 return None;
9242 }
9243 let right = raw.child_by_field_name("right")?;
9244 if right.kind() != "compound_literal_expression"
9245 || right.is_missing()
9246 || right
9247 .child_by_field_name("type")
9248 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
9249 || right
9250 .child_by_field_name("value")
9251 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
9252 {
9253 return None;
9254 }
9255 let has_intervening_error = (0..raw.child_count())
9256 .filter_map(|index| raw.child(index))
9257 .any(|child| {
9258 child.kind() == "ERROR"
9259 && !child.is_missing()
9260 && child.start_byte() >= left.end_byte()
9261 && child.end_byte() <= right.start_byte()
9262 });
9263 has_intervening_error.then_some((raw, left, right))
9264}
9265
9266pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
9267 match node.kind() {
9268 "new_expression" => node
9269 .child_by_field_name("type")
9270 .or_else(|| node.named_child(0)),
9271 "compound_literal_expression" => node.child_by_field_name("type"),
9272 "call_expression" => node.child_by_field_name("function"),
9273 _ => None,
9274 }
9275}
9276
9277pub fn field_initializer_constructs_target(
9278 node: Node<'_>,
9279 ctx: &ScanCtx<'_>,
9280 owner: &CodeUnit,
9281) -> bool {
9282 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
9291 return qualified_base_initializer_constructs_target(node, ctx, owner);
9292 }
9293 let Some(name) = node
9294 .child_by_field_name("name")
9295 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
9296 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
9297 else {
9298 return false;
9299 };
9300 let field_name = node_text(name, ctx.source);
9301 ctx.visibility
9302 .visible_identifier_candidates(ctx.file, field_name)
9303 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
9304 .any(|unit| field_declares_type(unit, ctx, owner))
9305}
9306
9307fn qualified_base_initializer_constructs_target(
9308 node: Node<'_>,
9309 ctx: &ScanCtx<'_>,
9310 owner: &CodeUnit,
9311) -> bool {
9312 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
9313 return false;
9314 };
9315 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
9316 return false;
9317 };
9318 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
9319 return false;
9320 };
9321 let resolves_target = |components: &[String]| {
9322 matches!(
9323 ctx.visibility.resolve_type_components_lexically_for_target(
9324 &ctx.analyzer,
9325 ctx.file,
9326 components,
9327 is_globally_qualified_cpp_name(qualified),
9328 &lexical_scope,
9329 owner,
9330 ),
9331 LexicalTypeResolution::Resolved { unit, .. }
9332 if same_visible_symbol(&unit, owner)
9333 )
9334 };
9335 if resolves_target(&components) {
9336 return true;
9337 }
9338
9339 components
9345 .last()
9346 .is_some_and(|terminal| terminal == owner.identifier())
9347 && resolves_target(&components[..components.len() - 1])
9348}
9349
9350fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9351 unit.signature()
9352 .is_some_and(|declaration| field_declaration_type_matches(declaration, unit, ctx, owner))
9353 || ctx
9354 .analyzer
9355 .get_source(unit, false)
9356 .is_some_and(|declaration| {
9357 field_declaration_type_matches(&declaration, unit, ctx, owner)
9358 })
9359}
9360
9361pub fn field_declared_binding(
9362 analyzer: &CppGraphSource<'_>,
9363 visibility: &VisibilityIndex<'_>,
9364 visible_from: &ProjectFile,
9365 field: &CodeUnit,
9366) -> Option<CppScanBinding> {
9367 let fact = visibility.field_declared_type_fact(analyzer, field)?;
9368 let normalized = normalize_field_type_text(&fact.type_text);
9369 let resolved = visibility.resolve_unique_canonical_type_for_declaration(
9370 analyzer,
9371 visible_from,
9372 field,
9373 &normalized,
9374 );
9375 let resolved = match (resolved, fact.template_arguments.as_deref()) {
9376 (Some(primary), Some(arguments)) => visibility
9377 .resolve_template_arguments(visible_from, primary, arguments)
9378 .ok(),
9379 (resolved, None) => resolved,
9380 (None, Some(_)) => None,
9381 };
9382 Some(CppScanBinding::from_type_name(
9383 normalized,
9384 resolved,
9385 fact.indirection,
9386 ))
9387}
9388
9389fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
9391 let Some(first) = candidates.first() else {
9392 return Err(TypeCandidateFailure::Unresolvable);
9393 };
9394 if candidates
9395 .iter()
9396 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
9397 {
9398 Ok((*first).clone())
9399 } else {
9400 Err(TypeCandidateFailure::Ambiguous)
9401 }
9402}
9403
9404fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
9405 logical_type_candidate(candidates).ok()
9406}
9407
9408fn unique_type_candidate_preserving_alias(
9409 analyzer: &CppGraphSource<'_>,
9410 candidates: &[&CodeUnit],
9411) -> Option<CodeUnit> {
9412 let first = *candidates.first()?;
9413 if declared_type_alias(analyzer, first) {
9414 return candidates
9415 .iter()
9416 .all(|candidate| {
9417 declared_type_alias(analyzer, candidate)
9418 && candidate.kind() == first.kind()
9419 && candidate.fq_name() == first.fq_name()
9420 && candidate.source() == first.source()
9421 })
9422 .then(|| first.clone());
9423 }
9424 candidates
9425 .iter()
9426 .all(|candidate| {
9427 !declared_type_alias(analyzer, candidate)
9428 && candidate.kind() == first.kind()
9429 && candidate.fq_name() == first.fq_name()
9430 })
9431 .then(|| first.clone())
9432}
9433
9434fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
9435 is_type_alias(unit)
9436 || analyzer
9437 .type_alias_provider()
9438 .is_some_and(|provider| provider.is_type_alias(unit))
9439}
9440
9441pub fn field_declared_type_binding(
9442 analyzer: &CppGraphSource<'_>,
9443 visibility: &VisibilityIndex<'_>,
9444 visible_from: &ProjectFile,
9445 field: &CodeUnit,
9446) -> Option<(String, Option<CodeUnit>, i32)> {
9447 let fact = visibility.field_declared_type_fact(analyzer, field)?;
9448 let normalized = normalize_field_type_text(&fact.type_text);
9449 let primary = visibility.resolve_unique_canonical_type_for_declaration(
9450 analyzer,
9451 visible_from,
9452 field,
9453 &normalized,
9454 );
9455 let resolved = match (primary, fact.template_arguments.as_deref()) {
9456 (Some(primary), Some(arguments)) => visibility
9457 .resolve_template_arguments(visible_from, primary, arguments)
9458 .ok(),
9459 (resolved, None) => resolved,
9460 (None, Some(_)) => None,
9461 };
9462 Some((normalized, resolved, fact.indirection))
9463}
9464
9465fn decode_field_declared_type_fact(
9466 analyzer: &CppGraphSource<'_>,
9467 field: &CodeUnit,
9468) -> Option<DeclaredFieldTypeFact> {
9469 let declaration = analyzer.get_source(field, false)?;
9470 let mut parser = Parser::new();
9471 parser
9472 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9473 .ok()?;
9474 let tree = parser.parse(&declaration, None)?;
9475 let mut stack = vec![tree.root_node()];
9476 while let Some(node) = stack.pop() {
9477 if matches!(node.kind(), "declaration" | "field_declaration")
9478 && let Some(type_node) = node
9479 .child_by_field_name("type")
9480 .or_else(|| first_type_child(node))
9481 && let Some(indirection) =
9482 declared_name_indirection(node, type_node, field.identifier(), &declaration)
9483 {
9484 let declared_type = if matches!(
9485 type_node.kind(),
9486 "class_specifier" | "struct_specifier" | "union_specifier"
9487 ) {
9488 type_node.child_by_field_name("name")
9489 } else {
9490 Some(type_node)
9491 };
9492 let type_text = declared_type.map_or_else(
9493 || field.identifier().to_string(),
9494 |declared_type| node_text(declared_type, &declaration).to_string(),
9495 );
9496 return Some(DeclaredFieldTypeFact {
9497 type_text,
9498 indirection,
9499 template_arguments: declared_type.and_then(|declared_type| {
9500 cpp_template_reference_arguments(declared_type, &declaration)
9501 }),
9502 });
9503 }
9504 let mut cursor = node.walk();
9505 stack.extend(node.named_children(&mut cursor));
9506 }
9507 None
9508}
9509
9510pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
9524 let mut parser = Parser::new();
9525 parser
9526 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9527 .ok()?;
9528 let tree = parser.parse(declaration, None)?;
9529 let mut stack = vec![tree.root_node()];
9530 while let Some(node) = stack.pop() {
9531 let type_node = match node.kind() {
9532 "type_definition" => {
9533 let mut cursor = node.walk();
9534 if node
9535 .children_by_field_name("declarator", &mut cursor)
9536 .any(declarator_names_function_type)
9537 {
9538 return None;
9539 }
9540 node.child_by_field_name("type")?
9541 }
9542 "alias_declaration" => {
9543 let type_node = node.child_by_field_name("type")?;
9544 if type_node
9545 .child_by_field_name("declarator")
9546 .is_some_and(declarator_names_function_type)
9547 {
9548 return None;
9549 }
9550 type_node
9551 }
9552 _ => {
9553 let mut cursor = node.walk();
9554 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9555 stack.extend(children.into_iter().rev());
9556 continue;
9557 }
9558 };
9559 return Some(node_text(type_node, declaration).to_string());
9560 }
9561 None
9562}
9563
9564fn cpp_alias_declaration_adds_indirection(declaration: &str) -> bool {
9573 let mut parser = Parser::new();
9574 if parser
9575 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9576 .is_err()
9577 {
9578 return true;
9579 }
9580 let Some(tree) = parser.parse(declaration, None) else {
9581 return true;
9582 };
9583 let mut stack = vec![tree.root_node()];
9584 while let Some(node) = stack.pop() {
9585 let declarators = match node.kind() {
9586 "type_definition" => {
9587 let mut cursor = node.walk();
9588 node.children_by_field_name("declarator", &mut cursor)
9589 .collect::<Vec<_>>()
9590 }
9591 "alias_declaration" => node
9592 .child_by_field_name("type")
9593 .and_then(|type_node| type_node.child_by_field_name("declarator"))
9594 .into_iter()
9595 .collect::<Vec<_>>(),
9596 _ => {
9597 let mut cursor = node.walk();
9598 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
9599 stack.extend(children.into_iter().rev());
9600 continue;
9601 }
9602 };
9603 return declarators.into_iter().any(cpp_declarator_adds_indirection);
9604 }
9605 true
9606}
9607
9608fn declarator_names_function_type(declarator: Node<'_>) -> bool {
9614 let mut current = Some(declarator);
9615 while let Some(node) = current {
9616 match node.kind() {
9617 "function_declarator" | "abstract_function_declarator" => return true,
9618 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
9619 current = node.named_child(0);
9620 }
9621 _ => current = node.child_by_field_name("declarator"),
9622 }
9623 }
9624 false
9625}
9626
9627fn decode_structured_alias_target(
9628 analyzer: &CppGraphSource<'_>,
9629 unit: &CodeUnit,
9630) -> Option<StructuredAliasTarget> {
9631 analyzer
9632 .get_source(unit, false)
9633 .and_then(|declaration| decode_structured_alias_target_source(unit, &declaration, true))
9634 .or_else(|| {
9635 let signature = unit.signature()?;
9636 decode_structured_alias_target_source(unit, signature, false)
9637 })
9638}
9639
9640fn decode_structured_alias_target_source(
9641 unit: &CodeUnit,
9642 declaration: &str,
9643 require_top_level: bool,
9644) -> Option<StructuredAliasTarget> {
9645 let mut parser = Parser::new();
9646 parser
9647 .set_language(&tree_sitter_cpp::LANGUAGE.into())
9648 .ok()?;
9649 let tree = parser.parse(declaration, None)?;
9650 let mut stack = vec![tree.root_node()];
9651 while let Some(node) = stack.pop() {
9652 let type_node = match node.kind() {
9653 "type_definition" => {
9654 if require_top_level
9655 && node
9656 .parent()
9657 .is_none_or(|parent| parent.kind() != "translation_unit")
9658 {
9659 let mut cursor = node.walk();
9660 stack.extend(node.named_children(&mut cursor));
9661 continue;
9662 }
9663 let mut declarator_cursor = node.walk();
9664 let declarator = node
9665 .children_by_field_name("declarator", &mut declarator_cursor)
9666 .find(|declarator| {
9667 extract_typedef_declarator_name(*declarator, declaration)
9668 .is_some_and(|name| name == unit.identifier())
9669 })?;
9670 if declarator_names_function_type(declarator) {
9671 return None;
9672 }
9673 node.child_by_field_name("type")?
9674 }
9675 "alias_declaration" => {
9676 if require_top_level
9677 && node
9678 .parent()
9679 .is_none_or(|parent| parent.kind() != "translation_unit")
9680 {
9681 let mut cursor = node.walk();
9682 stack.extend(node.named_children(&mut cursor));
9683 continue;
9684 }
9685 let name = node.child_by_field_name("name")?;
9686 if node_text(name, declaration) != unit.identifier() {
9687 return None;
9688 }
9689 let type_node = node.child_by_field_name("type")?;
9690 if type_node
9691 .child_by_field_name("declarator")
9692 .is_some_and(declarator_names_function_type)
9693 {
9694 return None;
9695 }
9696 type_node
9697 }
9698 _ => {
9699 let mut cursor = node.walk();
9700 stack.extend(node.named_children(&mut cursor));
9701 continue;
9702 }
9703 };
9704 return structured_alias_type_target(type_node, declaration);
9705 }
9706 None
9707}
9708
9709fn structured_alias_type_target(
9710 mut type_node: Node<'_>,
9711 source: &str,
9712) -> Option<StructuredAliasTarget> {
9713 while type_node.kind() == "type_descriptor" {
9714 type_node = type_node.child_by_field_name("type")?;
9715 }
9716 if type_node.kind() == "primitive_type" {
9717 return Some(StructuredAliasTarget::Builtin);
9718 }
9719 if matches!(
9720 type_node.kind(),
9721 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
9722 ) {
9723 type_node = type_node.child_by_field_name("name")?;
9724 }
9725 let global = type_node.child_by_field_name("scope").is_none()
9726 && type_node.child(0).is_some_and(|child| child.kind() == "::");
9727 let mut components = Vec::new();
9728 append_structured_type_components(type_node, source, &mut components)?;
9729 let arguments = cpp_template_reference_arguments(type_node, source);
9730 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
9731 components,
9732 global,
9733 arguments,
9734 })
9735}
9736
9737fn append_structured_type_components(
9738 node: Node<'_>,
9739 source: &str,
9740 out: &mut Vec<String>,
9741) -> Option<()> {
9742 match node.kind() {
9743 "identifier" | "namespace_identifier" | "type_identifier" => {
9744 out.push(node_text(node, source).to_string());
9745 Some(())
9746 }
9747 "template_type" => {
9748 append_structured_type_components(node.child_by_field_name("name")?, source, out)
9749 }
9750 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
9751 if let Some(scope) = node.child_by_field_name("scope") {
9752 append_structured_type_components(scope, source, out)?;
9753 }
9754 append_structured_type_components(node.child_by_field_name("name")?, source, out)
9755 }
9756 _ => None,
9757 }
9758}
9759
9760fn declared_name_indirection(
9761 declaration: Node<'_>,
9762 type_node: Node<'_>,
9763 field_name: &str,
9764 source: &str,
9765) -> Option<i32> {
9766 let mut stack = Vec::new();
9767 let mut cursor = declaration.walk();
9768 stack.extend(
9769 declaration
9770 .named_children(&mut cursor)
9771 .filter(|child| !same_node(*child, type_node)),
9772 );
9773 while let Some(node) = stack.pop() {
9774 if matches!(node.kind(), "identifier" | "field_identifier")
9775 && node_text(node, source) == field_name
9776 {
9777 let mut indirection = 0;
9778 let mut current = node.parent();
9779 while let Some(parent) = current {
9780 if same_node(parent, declaration) {
9781 return Some(indirection);
9782 }
9783 if parent.kind() == "pointer_declarator" {
9784 indirection += 1;
9785 }
9786 current = parent.parent();
9787 }
9788 return None;
9789 }
9790 let mut cursor = node.walk();
9791 stack.extend(node.named_children(&mut cursor));
9792 }
9793 None
9794}
9795
9796fn field_declaration_type_matches(
9797 declaration: &str,
9798 unit: &CodeUnit,
9799 ctx: &ScanCtx<'_>,
9800 owner: &CodeUnit,
9801) -> bool {
9802 ctx.visibility
9803 .resolves_to_type(&ctx.analyzer, ctx.file, declaration, owner)
9804 || field_type_prefix(declaration, unit.identifier()).is_some_and(|type_text| {
9805 let normalized = normalize_field_type_text(type_text);
9806 ctx.visibility
9807 .resolves_to_type(&ctx.analyzer, ctx.file, type_text, owner)
9808 || ctx.visibility.resolves_to_type(
9809 &ctx.analyzer,
9810 ctx.file,
9811 normalized.as_str(),
9812 owner,
9813 )
9814 })
9815}
9816
9817fn field_type_prefix<'a>(declaration: &'a str, field_name: &str) -> Option<&'a str> {
9818 let declaration = declaration
9819 .split(['=', ';'])
9820 .next()
9821 .unwrap_or(declaration)
9822 .trim();
9823 let index = declaration.rfind(field_name)?;
9824 let before = &declaration[..index];
9825 let after = &declaration[index + field_name.len()..];
9826 if before.chars().next_back().is_some_and(is_identifier_char)
9827 || after.chars().next().is_some_and(is_identifier_char)
9828 {
9829 return None;
9830 }
9831 Some(before.trim())
9832}
9833
9834fn normalize_field_type_text(type_text: &str) -> String {
9835 const FIELD_SPECIFIERS: [&str; 8] = [
9836 "extern ",
9837 "static ",
9838 "mutable ",
9839 "constexpr ",
9840 "constinit ",
9841 "inline ",
9842 "volatile ",
9843 "const ",
9844 ];
9845
9846 let mut normalized = normalize_type_text(type_text);
9847 loop {
9848 let Some(stripped) = FIELD_SPECIFIERS
9849 .iter()
9850 .find_map(|specifier| normalized.strip_prefix(specifier))
9851 else {
9852 return normalized;
9853 };
9854 normalized = normalize_type_text(stripped);
9855 }
9856}
9857
9858fn is_identifier_char(ch: char) -> bool {
9859 ch == '_' || ch.is_ascii_alphanumeric()
9860}
9861
9862pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
9863 let Some(type_node) = node.child_by_field_name("type") else {
9864 return false;
9865 };
9866 ctx.visibility.resolves_to_type(
9867 &ctx.analyzer,
9868 ctx.file,
9869 node_text(type_node, ctx.source),
9870 owner,
9871 )
9872}
9873
9874pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
9875 !ctx.analyzer
9876 .declarations(ctx.file)
9877 .into_iter()
9878 .filter(|unit| unit.is_function())
9879 .any(|unit| {
9880 ctx.analyzer.ranges(&unit).iter().any(|range| {
9881 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
9882 })
9883 })
9884}
9885
9886pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
9887 let mut cursor = node.walk();
9888 for child in node.named_children(&mut cursor) {
9889 if child.kind() == "init_declarator" {
9890 return child
9891 .child_by_field_name("value")
9892 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
9893 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
9894 .map(declaration_init_value_arity)
9895 .unwrap_or(0);
9896 }
9897 if is_declarator_node(child) {
9898 return declaration_declarator_arity(child);
9899 }
9900 }
9901 0
9902}
9903
9904fn declaration_init_value_arity(value: Node<'_>) -> usize {
9905 match value.kind() {
9906 "argument_list" | "initializer_list" => argument_children(value).count(),
9907 "compound_literal_expression" => call_arity(value),
9908 _ => 1,
9909 }
9910}
9911
9912fn declaration_declarator_arity(node: Node<'_>) -> usize {
9913 if let Some(parameters) = node.child_by_field_name("parameters") {
9914 return argument_children(parameters).count();
9915 }
9916 node.child_by_field_name("declarator")
9917 .map(declaration_declarator_arity)
9918 .unwrap_or(0)
9919}
9920
9921fn first_named_child_of_kind<'tree>(node: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9922 let mut cursor = node.walk();
9923 node.named_children(&mut cursor)
9924 .find(|child| child.kind() == kind)
9925}
9926
9927fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
9928 let mut stack = vec![root];
9929 while let Some(node) = stack.pop() {
9930 if node.kind() == kind {
9931 return Some(node);
9932 }
9933 for index in (0..node.named_child_count()).rev() {
9934 if let Some(child) = node.named_child(index) {
9935 stack.push(child);
9936 }
9937 }
9938 }
9939 None
9940}
9941
9942fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
9943 if node.kind() == "identifier" {
9944 return true;
9945 }
9946 if node.kind() == "parenthesized_expression" {
9947 return false;
9948 }
9949 if node.kind() == "call_expression" {
9950 return node
9951 .child_by_field_name("function")
9952 .is_some_and(|function| function.kind() == "identifier");
9953 }
9954 let mut stack = vec![node];
9955 while let Some(descendant) = stack.pop() {
9956 if descendant != node && descendant.kind() == "parenthesized_expression" {
9957 continue;
9958 }
9959 if descendant.kind() == "identifier" {
9960 return true;
9961 }
9962 if descendant.kind() == "call_expression" {
9963 if descendant
9964 .child_by_field_name("function")
9965 .is_some_and(|function| function.kind() == "identifier")
9966 {
9967 return true;
9968 }
9969 continue;
9970 }
9971 for index in (0..descendant.named_child_count()).rev() {
9972 if let Some(child) = descendant.named_child(index) {
9973 stack.push(child);
9974 }
9975 }
9976 }
9977 false
9978}
9979
9980fn macro_expansion_shape_is_safe(
9981 node: Node<'_>,
9982 source: &str,
9983 parameters: &[String],
9984 environment: &MacroEnvironment,
9985) -> bool {
9986 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
9987 return true;
9988 }
9989 if node.kind() == "call_expression" {
9990 let Some(function) = node.child_by_field_name("function") else {
9991 return true;
9992 };
9993 if function.kind() != "identifier" {
9994 return true;
9995 }
9996 let function_name = node_text(function, source);
9997 if parameters
9998 .iter()
9999 .any(|parameter| parameter == function_name)
10000 {
10001 return false;
10002 }
10003 if !environment.may_bind(function_name) {
10004 return true;
10005 }
10006 let Some(arguments) = node.child_by_field_name("arguments") else {
10007 return false;
10008 };
10009 return argument_children(arguments).all(|argument| {
10010 if argument.kind() == "identifier"
10011 && parameters
10012 .iter()
10013 .any(|parameter| parameter == node_text(argument, source))
10014 {
10015 return false;
10016 }
10017 macro_expansion_shape_is_safe(argument, source, parameters, environment)
10018 });
10019 }
10020 let mut stack = vec![node];
10021 while let Some(descendant) = stack.pop() {
10022 if descendant != node {
10023 if descendant.kind() == "parenthesized_expression" {
10024 continue;
10025 }
10026 if descendant.kind() == "call_expression" {
10027 let expands = descendant
10028 .child_by_field_name("function")
10029 .filter(|function| function.kind() == "identifier")
10030 .is_some_and(|function| environment.may_bind(node_text(function, source)));
10031 if expands {
10032 return false;
10033 }
10034 continue;
10035 }
10036 }
10037 if descendant.kind() == "identifier" {
10038 let identifier = node_text(descendant, source);
10039 if parameters.iter().any(|parameter| parameter == identifier)
10040 || environment.may_bind(identifier)
10041 {
10042 return false;
10043 }
10044 }
10045 for index in (0..descendant.named_child_count()).rev() {
10046 if let Some(child) = descendant.named_child(index) {
10047 stack.push(child);
10048 }
10049 }
10050 }
10051 true
10052}
10053
10054fn structured_include_path<'a>(path: Node<'_>, source: &'a str) -> Option<&'a str> {
10055 let text = node_text(path, source);
10056 match path.kind() {
10057 "string_literal" => text.strip_prefix('"')?.strip_suffix('"'),
10058 "system_lib_string" => text.strip_prefix('<')?.strip_suffix('>'),
10059 _ => None,
10060 }
10061}
10062
10063fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
10064 let descendant = node;
10065 while let Some(parent) = node.parent() {
10066 if is_preprocessor_conditional(parent)
10067 && !is_file_covering_include_guard(parent, source)
10068 && preprocessor_conditional_contains_descendant(parent, descendant)
10069 {
10070 return true;
10071 }
10072 node = parent;
10073 }
10074 false
10075}
10076
10077fn is_preprocessor_conditional(node: Node<'_>) -> bool {
10078 matches!(
10079 node.kind(),
10080 "preproc_if"
10081 | "preproc_ifdef"
10082 | "preproc_ifndef"
10083 | "preproc_elif"
10084 | "preproc_elifdef"
10085 | "preproc_else"
10086 )
10087}
10088
10089fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
10090 node.parent()
10091 .filter(|parent| parent.kind() == "translation_unit")
10092 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
10093 && is_canonical_include_guard(node, source)
10094}
10095
10096fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
10097 if node.kind() != "preproc_ifdef"
10098 || node
10099 .child(0)
10100 .is_none_or(|directive| directive.kind() != "#ifndef")
10101 || node.child_by_field_name("alternative").is_some()
10102 {
10103 return false;
10104 }
10105 let Some(guard_name) = node.child_by_field_name("name") else {
10106 return false;
10107 };
10108 let mut cursor = node.walk();
10109 node.named_children(&mut cursor)
10110 .find(|child| *child != guard_name && child.kind() != "comment")
10111 .filter(|child| child.kind() == "preproc_def")
10112 .and_then(|definition| definition.child_by_field_name("name"))
10113 .is_some_and(|defined_name| {
10114 node_text(defined_name, source) == node_text(guard_name, source)
10115 })
10116}
10117
10118fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
10119 let mut guard = None;
10120 for index in 0..root.named_child_count() {
10121 let Some(child) = root.named_child(index) else {
10122 continue;
10123 };
10124 if child.kind() == "comment" || is_pragma_once(child, source) {
10125 continue;
10126 }
10127 if guard.is_none() && is_canonical_include_guard(child, source) {
10128 guard = Some(child);
10129 } else {
10130 return None;
10131 }
10132 }
10133 guard
10134 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
10135 .map(|name| node_text(name, source).to_string())
10136}
10137
10138fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
10139 if (0..root.named_child_count())
10140 .filter_map(|index| root.named_child(index))
10141 .any(|child| is_pragma_once(child, source))
10142 {
10143 return MacroIncludeProtection::PragmaOnce;
10144 }
10145 top_level_canonical_include_guard_name(root, source)
10146 .map(MacroIncludeProtection::MacroGuard)
10147 .unwrap_or(MacroIncludeProtection::None)
10148}
10149
10150fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
10151 node.kind() == "preproc_call"
10152 && node
10153 .child_by_field_name("directive")
10154 .is_some_and(|directive| node_text(directive, source) == "#pragma")
10155 && node
10156 .child_by_field_name("argument")
10157 .is_some_and(|argument| node_text(argument, source).trim() == "once")
10158}
10159
10160fn parse_preproc_identifier(argument: &str) -> Option<String> {
10161 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
10162 let mut parser = Parser::new();
10163 parser
10164 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10165 .ok()?;
10166 let tree = parser.parse(&sentinel, None)?;
10167 if tree.root_node().has_error() {
10168 return None;
10169 }
10170 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
10171 let identifier = statement.named_child(0)?;
10172 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
10173 .then(|| node_text(identifier, &sentinel).to_string())
10174}
10175
10176pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
10177 match node.kind() {
10178 "identifier" | "field_identifier" => {
10179 let name = node_text(node, source).trim();
10180 (!name.is_empty()).then(|| name.to_string())
10181 }
10182 "abstract_array_declarator"
10183 | "abstract_function_declarator"
10184 | "abstract_parenthesized_declarator"
10185 | "abstract_pointer_declarator"
10186 | "abstract_reference_declarator" => None,
10187 "function_declarator" => node
10188 .child_by_field_name("declarator")
10189 .or_else(|| node.child_by_field_name("name"))
10190 .and_then(|child| extract_variable_name(child, source)),
10191 _ => node
10192 .child_by_field_name("declarator")
10193 .or_else(|| node.child_by_field_name("name"))
10194 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
10195 .and_then(|child| extract_variable_name(child, source)),
10196 }
10197}
10198
10199pub fn is_c_source_file(file: &ProjectFile) -> bool {
10210 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
10211}
10212
10213pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
10226 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
10227}
10228
10229pub fn is_declarator_node(node: Node<'_>) -> bool {
10230 matches!(
10231 node.kind(),
10232 "identifier"
10233 | "field_identifier"
10234 | "pointer_declarator"
10235 | "reference_declarator"
10236 | "array_declarator"
10237 | "parenthesized_declarator"
10238 | "function_declarator"
10239 )
10240}
10241
10242#[derive(Clone, Default)]
10243pub struct OrphanedNamespaceTypeScopeIndex {
10244 scopes: Vec<OrphanedNamespaceTypeScope>,
10245}
10246
10247#[derive(Clone)]
10248struct OrphanedNamespaceTypeScope {
10249 body_end: usize,
10250 scope_end: usize,
10251 components: Vec<String>,
10252}
10253
10254impl OrphanedNamespaceTypeScopeIndex {
10255 pub fn build(root: Node<'_>, source: &str) -> Self {
10261 let mut scopes = Vec::new();
10262 let mut stack = vec![root];
10263 while let Some(current) = stack.pop() {
10264 if current.kind() == "namespace_definition"
10265 && current.has_error()
10266 && let Some(body) = current.child_by_field_name("body")
10267 && current.end_byte() == body.end_byte()
10268 && let Some(name) = current.child_by_field_name("name")
10269 {
10270 let mut components =
10271 enclosing_namespace_components(current, source).unwrap_or_default();
10272 if append_cpp_name_components(name, source, &mut components).is_some()
10273 && !components.is_empty()
10274 {
10275 let mut following = current.next_named_sibling();
10276 while let Some(candidate) = following {
10277 if direct_unmatched_closing_brace(candidate) {
10278 scopes.push(OrphanedNamespaceTypeScope {
10279 body_end: body.end_byte(),
10280 scope_end: candidate.start_byte(),
10281 components,
10282 });
10283 break;
10284 }
10285 following = candidate.next_named_sibling();
10286 }
10287 }
10288 }
10289 if !current.has_error() {
10290 continue;
10291 }
10292 let mut cursor = current.walk();
10293 stack.extend(
10294 current
10295 .named_children(&mut cursor)
10296 .filter(|child| child.has_error()),
10297 );
10298 }
10299 Self { scopes }
10300 }
10301
10302 pub fn scope_at(&self, byte: usize) -> Option<(usize, &[String])> {
10303 self.scopes
10304 .iter()
10305 .filter(|scope| scope.body_end < byte && byte < scope.scope_end)
10306 .max_by_key(|scope| (scope.components.len(), scope.body_end))
10307 .map(|scope| (scope.body_end, scope.components.as_slice()))
10308 }
10309}
10310
10311#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10312pub enum RecoveredDeclaratorTypeContext {
10313 Declaration,
10314 FunctionDefinition,
10315 Parameter,
10316}
10317
10318pub fn recovered_macro_decorated_declarator_type(
10333 node: Node<'_>,
10334) -> Option<RecoveredDeclaratorTypeContext> {
10335 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
10336}
10337
10338pub fn recovered_macro_decorated_type_node(
10343 node: Node<'_>,
10344) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10345 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
10346 return None;
10347 }
10348 let qualified = node.parent()?;
10349 if qualified.kind() != "qualified_identifier"
10350 || qualified.child_by_field_name("scope") != Some(node)
10351 || !(0..qualified.child_count())
10352 .filter_map(|index| qualified.child(index))
10353 .any(|child| child.kind() == "::" && child.is_missing())
10354 {
10355 return None;
10356 }
10357 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
10358 return None;
10359 }
10360
10361 let (declaration, context) = recovered_declarator_container(qualified)?;
10362 let type_node = declaration
10363 .child_by_field_name("type")
10364 .filter(|type_node| {
10365 *type_node != qualified
10366 && !type_node.is_missing()
10367 && type_node.start_byte() != type_node.end_byte()
10368 })?;
10369 Some((type_node, context))
10370}
10371
10372fn recovered_declarator_container(
10373 mut declarator: Node<'_>,
10374) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
10375 loop {
10376 let parent = declarator.parent()?;
10377 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
10378 return Some((
10379 parent
10380 .parent()
10381 .filter(|declaration| declaration.kind() == "declaration")?,
10382 RecoveredDeclaratorTypeContext::Declaration,
10383 ));
10384 }
10385 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
10386 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
10387 }
10388 if parent.kind() == "function_definition"
10389 && has_field_child(parent, "declarator", declarator)
10390 {
10391 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
10392 }
10393 if matches!(
10399 parent.kind(),
10400 "parameter_declaration" | "optional_parameter_declaration"
10401 ) && has_field_child(parent, "declarator", declarator)
10402 {
10403 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
10404 }
10405 if !matches!(
10406 parent.kind(),
10407 "array_declarator"
10408 | "function_declarator"
10409 | "parenthesized_declarator"
10410 | "pointer_declarator"
10411 | "pointer_type_declarator"
10412 | "reference_declarator"
10413 ) || !has_field_child(parent, "declarator", declarator)
10414 {
10415 return None;
10416 }
10417 declarator = parent;
10418 }
10419}
10420
10421fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
10422 let mut cursor = parent.walk();
10423 parent
10424 .children_by_field_name(field, &mut cursor)
10425 .any(|child| child == target)
10426}
10427
10428fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
10429 loop {
10430 if node.is_missing() || node.start_byte() == node.end_byte() {
10431 return false;
10432 }
10433 match node.kind() {
10434 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
10435 return true;
10436 }
10437 "array_declarator"
10438 | "function_declarator"
10439 | "parenthesized_declarator"
10440 | "pointer_declarator"
10441 | "pointer_type_declarator"
10442 | "reference_declarator" => {
10443 let Some(declarator) = node.child_by_field_name("declarator") else {
10444 return false;
10445 };
10446 node = declarator;
10447 }
10448 _ => return false,
10449 }
10450 }
10451}
10452
10453pub enum DesignatedInitializerOwner {
10455 Resolved(CodeUnit),
10456 Unresolved,
10457}
10458
10459pub fn designated_initializer_owner(
10470 visibility: &VisibilityIndex<'_>,
10471 file: &ProjectFile,
10472 source: &str,
10473 node: Node<'_>,
10474) -> Option<DesignatedInitializerOwner> {
10475 if let Some(designator) = node
10476 .parent()
10477 .filter(|parent| parent.kind() == "field_designator")
10478 {
10479 let pair = designator.parent()?;
10480 if pair.kind() != "initializer_pair"
10481 || pair.child_by_field_name("designator") != Some(designator)
10482 {
10483 return None;
10484 }
10485 let initializer = pair.parent()?;
10486 if initializer.kind() != "initializer_list" {
10487 return None;
10488 }
10489 return Some(classified_designated_owner(initializer_list_owner(
10490 visibility,
10491 file,
10492 source,
10493 initializer,
10494 )));
10495 }
10496
10497 let init_declarator = node.parent()?;
10498 if init_declarator.child_by_field_name("declarator") != Some(node)
10499 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
10500 {
10501 return None;
10502 }
10503 Some(classified_designated_owner(declaration_owner(
10504 visibility,
10505 file,
10506 source,
10507 init_declarator.parent()?,
10508 )))
10509}
10510
10511fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
10512 owner.map_or(
10513 DesignatedInitializerOwner::Unresolved,
10514 DesignatedInitializerOwner::Resolved,
10515 )
10516}
10517
10518fn initializer_list_owner(
10519 visibility: &VisibilityIndex<'_>,
10520 file: &ProjectFile,
10521 source: &str,
10522 initializer: Node<'_>,
10523) -> Option<CodeUnit> {
10524 let mut current = initializer;
10525 let mut outer_initializer_lists = 0usize;
10526 loop {
10527 let parent = current.parent()?;
10528 match parent.kind() {
10529 "initializer_pair" => return None,
10530 "initializer_list" => {
10531 outer_initializer_lists += 1;
10532 if outer_initializer_lists > 1 {
10533 return None;
10534 }
10535 current = parent;
10536 }
10537 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
10538 let declaration = parent.parent()?;
10539 if outer_initializer_lists == 1
10540 && !parent
10541 .child_by_field_name("declarator")
10542 .is_some_and(contains_array_declarator)
10543 {
10544 return None;
10545 }
10546 return declaration_owner(visibility, file, source, declaration);
10547 }
10548 "compound_literal_expression"
10549 if parent.child_by_field_name("value") == Some(current)
10550 && outer_initializer_lists == 0 =>
10551 {
10552 let type_node = parent.child_by_field_name("type")?;
10553 return resolve_designated_owner_type(visibility, file, source, type_node);
10554 }
10555 "ERROR" => current = parent,
10556 _ => return None,
10557 }
10558 }
10559}
10560
10561fn declaration_owner(
10562 visibility: &VisibilityIndex<'_>,
10563 file: &ProjectFile,
10564 source: &str,
10565 declaration: Node<'_>,
10566) -> Option<CodeUnit> {
10567 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
10568 return None;
10569 }
10570 let type_node = declaration
10571 .child_by_field_name("type")
10572 .or_else(|| first_type_child(declaration))?;
10573 resolve_designated_owner_type(visibility, file, source, type_node)
10574}
10575
10576fn resolve_designated_owner_type(
10577 visibility: &VisibilityIndex<'_>,
10578 file: &ProjectFile,
10579 source: &str,
10580 type_node: Node<'_>,
10581) -> Option<CodeUnit> {
10582 let type_name = normalize_type_text(node_text(type_node, source));
10583 visibility
10584 .resolve_type(file, &type_name)
10585 .filter(CodeUnit::is_class)
10586}
10587
10588fn contains_array_declarator(declarator: Node<'_>) -> bool {
10589 let mut stack = vec![declarator];
10590 while let Some(node) = stack.pop() {
10591 if node.kind() == "array_declarator" {
10592 return true;
10593 }
10594 if matches!(node.kind(), "initializer_list" | "compound_statement") {
10595 continue;
10596 }
10597 let mut cursor = node.walk();
10598 stack.extend(node.named_children(&mut cursor));
10599 }
10600 false
10601}
10602
10603pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
10604 let mut cursor = node.walk();
10605 node.named_children(&mut cursor).find(|child| {
10606 matches!(
10607 child.kind(),
10608 "type_identifier"
10609 | "primitive_type"
10610 | "qualified_identifier"
10611 | "scoped_type_identifier"
10612 | "struct_specifier"
10613 | "union_specifier"
10614 | "enum_specifier"
10615 )
10616 })
10617}
10618
10619pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
10620 visibility: &VisibilityIndex<'_>,
10621 file: &ProjectFile,
10622 source: &str,
10623 declarator: Node<'_>,
10624 type_text: Option<&str>,
10625 bindings: &LocalInferenceEngine<T>,
10626) -> bool {
10627 if !has_ancestor_kind(declarator, "compound_statement") {
10628 return false;
10629 }
10630 if declarator
10631 .child_by_field_name("declarator")
10632 .is_none_or(|declarator| declarator.kind() != "identifier")
10633 {
10634 return false;
10635 }
10636 if !type_text
10637 .and_then(|text| visibility.resolve_type(file, text))
10638 .is_some_and(|unit| unit.is_class())
10639 {
10640 return false;
10641 }
10642 declarator
10643 .child_by_field_name("parameters")
10644 .is_some_and(|parameters| {
10645 constructor_parameters_look_like_expressions(parameters, source, bindings)
10646 })
10647}
10648
10649fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
10650 parameters: Node<'_>,
10651 source: &str,
10652 bindings: &LocalInferenceEngine<T>,
10653) -> bool {
10654 let mut cursor = parameters.walk();
10655 parameters.named_children(&mut cursor).any(|parameter| {
10656 !matches!(
10657 parameter.kind(),
10658 "parameter_declaration" | "optional_parameter_declaration"
10659 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
10660 })
10661}
10662
10663fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
10664 parameter: Node<'_>,
10665 source: &str,
10666 bindings: &LocalInferenceEngine<T>,
10667) -> bool {
10668 let text = node_text(parameter, source).trim();
10669 if text
10670 .chars()
10671 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
10672 && bindings.is_shadowed(text)
10673 {
10674 return true;
10675 }
10676
10677 let Some(base) = parameter
10678 .child_by_field_name("type")
10679 .filter(|base| base.kind() == "type_identifier")
10680 else {
10681 return false;
10682 };
10683 let Some(subscript) = parameter
10684 .child_by_field_name("declarator")
10685 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
10686 else {
10687 return false;
10688 };
10689 subscript.child_by_field_name("size").is_some()
10690 && bindings.is_shadowed(node_text(base, source).trim())
10691}
10692
10693pub fn is_declaration_name(node: Node<'_>) -> bool {
10694 let Some(parent) = node.parent() else {
10695 return false;
10696 };
10697 if parent
10698 .child_by_field_name("name")
10699 .is_some_and(|name| same_node(name, node))
10700 {
10701 if matches!(
10702 parent.kind(),
10703 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
10704 ) {
10705 return cpp_tag_specifier_declares_name(parent);
10706 }
10707 if matches!(
10708 parent.kind(),
10709 "namespace_definition"
10710 | "namespace_alias_definition"
10711 | "alias_declaration"
10712 | "enumerator"
10713 ) {
10714 return true;
10715 }
10716 }
10717
10718 let mut current = Some(parent);
10719 while let Some(ancestor) = current {
10720 let type_definition = ancestor.kind() == "type_definition";
10721 let mut declarator_cursor = ancestor.walk();
10722 if ancestor
10723 .children_by_field_name("declarator", &mut declarator_cursor)
10724 .any(|declarator| declarator_name_path_contains(declarator, node, type_definition))
10725 {
10726 return true;
10727 }
10728 if matches!(
10729 ancestor.kind(),
10730 "declaration"
10731 | "field_declaration"
10732 | "parameter_declaration"
10733 | "optional_parameter_declaration"
10734 | "function_definition"
10735 | "type_definition"
10736 | "alias_declaration"
10737 | "class_specifier"
10738 | "struct_specifier"
10739 | "union_specifier"
10740 | "enum_specifier"
10741 ) {
10742 return false;
10743 }
10744 current = ancestor.parent();
10745 }
10746 false
10747}
10748
10749pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
10758 if !matches!(
10759 node.kind(),
10760 "qualified_identifier" | "scoped_type_identifier"
10761 ) {
10762 return false;
10763 }
10764 let Some(declaration) = node
10765 .parent()
10766 .filter(|parent| parent.kind() == "declaration")
10767 else {
10768 return false;
10769 };
10770 if declaration.child_by_field_name("declarator") != Some(node)
10771 || !declaration
10772 .child_by_field_name("type")
10773 .is_some_and(|friend| {
10774 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
10775 })
10776 {
10777 return false;
10778 }
10779 let mut cursor = declaration.walk();
10780 let mut errors = declaration
10781 .named_children(&mut cursor)
10782 .filter(|child| child.kind() == "ERROR");
10783 let Some(error) = errors.next() else {
10784 return false;
10785 };
10786 errors.next().is_none()
10787 && error.named_child_count() == 1
10788 && error.named_child(0).is_some_and(|class| {
10789 class.kind() == "identifier" && node_text(class, source) == "class"
10790 })
10791}
10792
10793pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
10794 if !matches!(node.kind(), "identifier" | "field_identifier") || is_declaration_name(node) {
10795 return false;
10796 }
10797 if let Some(parent) = node.parent() {
10798 if parent.kind() == "call_expression"
10799 && parent.child_by_field_name("function") == Some(node)
10800 {
10801 return false;
10802 }
10803 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
10804 && parent.child_by_field_name("label") == Some(node)
10805 {
10806 return false;
10807 }
10808 }
10809 let mut current = node.parent();
10810 while let Some(ancestor) = current {
10811 if ancestor.kind().starts_with("preproc_") {
10812 return false;
10813 }
10814 if matches!(
10815 ancestor.kind(),
10816 "translation_unit" | "function_definition" | "compound_statement"
10817 ) {
10818 break;
10819 }
10820 current = ancestor.parent();
10821 }
10822 true
10823}
10824
10825fn recovered_c_reference_node(
10826 visibility: &VisibilityIndex<'_>,
10827 file: &ProjectFile,
10828 node: Node<'_>,
10829 source: &str,
10830) -> bool {
10831 if node.start_byte() >= node.end_byte()
10832 || node.is_error()
10833 || node.is_missing()
10834 || !matches!(
10835 node.kind(),
10836 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
10837 )
10838 || recovered_c_macro_binding_role(node)
10839 || recovered_c_label_role(node)
10840 {
10841 return false;
10842 }
10843
10844 let name = node_text(node, source);
10845 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
10846 return true;
10847 }
10848 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
10849 return true;
10850 }
10851 if is_declaration_name(node) {
10852 return false;
10853 }
10854 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
10855 return true;
10856 }
10857 recovered_c_reference_anchor(node)
10858}
10859
10860fn recovered_c_explicit_assignment_callee(
10861 visibility: &VisibilityIndex<'_>,
10862 file: &ProjectFile,
10863 node: Node<'_>,
10864 name: &str,
10865) -> bool {
10866 let mut current = node;
10867 let error = loop {
10868 let Some(parent) = current.parent() else {
10869 return false;
10870 };
10871 if parent.is_error() {
10872 break parent;
10873 }
10874 current = parent;
10875 };
10876 let mut cursor = error.walk();
10877 let explicit_recovery_precedes_callee = error
10878 .named_children(&mut cursor)
10879 .take_while(|child| child.start_byte() < node.start_byte())
10880 .any(|child| child.kind() == "explicit_function_specifier");
10881 if !explicit_recovery_precedes_callee {
10882 return false;
10883 }
10884 visibility
10885 .cpp
10886 .declarations(file)
10887 .iter()
10888 .chain(visibility.visible_by_file.get(file).into_iter().flatten())
10889 .any(|candidate| candidate.identifier() == name && candidate.is_function())
10890}
10891
10892fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
10893 while let Some(parent) = node.parent() {
10894 if matches!(
10895 parent.kind(),
10896 "preproc_def" | "preproc_function_def" | "preproc_params"
10897 ) {
10898 return true;
10899 }
10900 if parent.is_error()
10901 || matches!(
10902 parent.kind(),
10903 "translation_unit" | "function_definition" | "compound_statement"
10904 )
10905 {
10906 return false;
10907 }
10908 node = parent;
10909 }
10910 false
10911}
10912
10913fn recovered_c_label_role(node: Node<'_>) -> bool {
10914 node.parent().is_some_and(|parent| {
10915 matches!(parent.kind(), "labeled_statement" | "goto_statement")
10916 && parent.child_by_field_name("label") == Some(node)
10917 })
10918}
10919
10920fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
10921 while let Some(parent) = node.parent() {
10922 if parent.is_error() {
10923 return false;
10924 }
10925 if parent.kind().ends_with("_expression")
10926 || matches!(
10927 parent.kind(),
10928 "argument_list"
10929 | "return_statement"
10930 | "expression_statement"
10931 | "case_statement"
10932 | "initializer_list"
10933 | "init_declarator"
10934 | "array_declarator"
10935 | "field_designator"
10936 | "enumerator"
10937 )
10938 {
10939 return true;
10940 }
10941 if matches!(
10942 parent.kind(),
10943 "translation_unit"
10944 | "function_definition"
10945 | "compound_statement"
10946 | "declaration"
10947 | "field_declaration"
10948 | "parameter_declaration"
10949 ) {
10950 return false;
10951 }
10952 node = parent;
10953 }
10954 false
10955}
10956
10957pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
10965 let mut current = parameter.parent();
10966 while let Some(ancestor) = current {
10967 if ancestor.kind() == "lambda_expression" {
10968 return ancestor
10969 .child_by_field_name("declarator")
10970 .is_some_and(|declarator| {
10971 declarator.start_byte() <= parameter.start_byte()
10972 && parameter.end_byte() <= declarator.end_byte()
10973 });
10974 }
10975 if ancestor.kind() == "function_definition" {
10976 return ancestor
10977 .child_by_field_name("declarator")
10978 .is_some_and(|declarator| {
10979 declarator.start_byte() <= parameter.start_byte()
10980 && parameter.end_byte() <= declarator.end_byte()
10981 });
10982 }
10983 current = ancestor.parent();
10984 }
10985 false
10986}
10987
10988pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
10989 let mut current = node.parent();
10990 while let Some(ancestor) = current {
10991 if matches!(
10992 ancestor.kind(),
10993 "parameter_declaration" | "optional_parameter_declaration"
10994 ) {
10995 return ancestor
10996 .child_by_field_name("type")
10997 .is_some_and(|type_node| {
10998 type_node.start_byte() <= node.start_byte()
10999 && node.end_byte() <= type_node.end_byte()
11000 });
11001 }
11002 if matches!(
11003 ancestor.kind(),
11004 "function_definition" | "lambda_expression" | "compound_statement"
11005 ) {
11006 return false;
11007 }
11008 current = ancestor.parent();
11009 }
11010 false
11011}
11012
11013fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
11014 if specifier.child_by_field_name("body").is_some() {
11015 return true;
11016 }
11017 let mut current = specifier.parent();
11018 while let Some(ancestor) = current {
11019 match ancestor.kind() {
11020 "type_descriptor"
11021 | "parameter_declaration"
11022 | "optional_parameter_declaration"
11023 | "template_argument_list"
11024 | "cast_expression" => return false,
11025 "declaration" | "field_declaration" => {
11026 let mut cursor = ancestor.walk();
11027 return ancestor
11028 .children_by_field_name("declarator", &mut cursor)
11029 .next()
11030 .is_none();
11031 }
11032 "translation_unit" => return true,
11033 _ => current = ancestor.parent(),
11034 }
11035 }
11036 false
11037}
11038
11039pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
11040 match node.kind() {
11041 "identifier"
11042 | "field_identifier"
11043 | "qualified_identifier"
11044 | "scoped_identifier"
11045 | "operator_name"
11046 | "destructor_name"
11047 | "literal_operator_name" => Some(node),
11048 "reference_declarator" | "parenthesized_declarator" => {
11049 node.named_child(0).and_then(declarator_name_node)
11050 }
11051 _ => node
11052 .child_by_field_name("declarator")
11053 .or_else(|| node.child_by_field_name("name"))
11054 .or_else(|| node.child_by_field_name("field"))
11055 .and_then(declarator_name_node),
11056 }
11057}
11058
11059fn declarator_name_path_contains(
11060 declarator: Node<'_>,
11061 candidate: Node<'_>,
11062 allow_type_identifier: bool,
11063) -> bool {
11064 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
11065 return false;
11066 };
11067 let mut current = Some(declarator);
11068 while let Some(node) = current {
11069 if same_node(node, candidate) {
11070 return true;
11071 }
11072 if same_node(node, name) {
11073 return false;
11074 }
11075 current = node
11076 .child_by_field_name("declarator")
11077 .or_else(|| node.child_by_field_name("name"))
11078 .or_else(|| node.child_by_field_name("field"));
11079 }
11080 false
11081}
11082
11083fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
11084 match node.kind() {
11085 "identifier"
11086 | "field_identifier"
11087 | "operator_name"
11088 | "destructor_name"
11089 | "literal_operator_name" => Some(node),
11090 "type_identifier" if allow_type_identifier => Some(node),
11091 _ => node
11092 .child_by_field_name("declarator")
11093 .or_else(|| node.child_by_field_name("name"))
11094 .or_else(|| node.child_by_field_name("field"))
11095 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
11096 }
11097}
11098
11099pub fn is_nested_type_node(node: Node<'_>) -> bool {
11102 node.parent().is_some_and(|parent| {
11103 matches!(
11104 parent.kind(),
11105 "qualified_identifier" | "scoped_type_identifier" | "template_type"
11106 )
11107 })
11108}
11109
11110pub struct OutOfLineMemberDefinitionOwners<'tree> {
11111 pub owners: Vec<(Node<'tree>, CodeUnit)>,
11112 innermost: Option<(Node<'tree>, CodeUnit)>,
11113}
11114
11115impl OutOfLineMemberDefinitionOwners<'_> {
11116 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
11117 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
11118 }
11119}
11120
11121pub struct QualifiedOwnerComponents<'tree> {
11122 pub nodes: Vec<Node<'tree>>,
11123 pub names: Vec<String>,
11124 pub global: bool,
11125}
11126
11127pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
11132 let mut stack = vec![node];
11133 let mut found_separator = false;
11134 while let Some(current) = stack.pop() {
11135 if !matches!(
11136 current.kind(),
11137 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11138 ) {
11139 continue;
11140 }
11141 let mut current_has_separator = false;
11142 for index in 0..current.child_count() {
11143 let Some(child) = current.child(index) else {
11144 continue;
11145 };
11146 if child.kind() == "::" {
11147 if child.is_missing() {
11148 return false;
11149 }
11150 current_has_separator = true;
11151 found_separator = true;
11152 }
11153 }
11154 if !current_has_separator {
11155 return false;
11156 }
11157 for field in ["scope", "name"] {
11158 if let Some(child) = current.child_by_field_name(field)
11159 && matches!(
11160 child.kind(),
11161 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
11162 )
11163 {
11164 stack.push(child);
11165 }
11166 }
11167 }
11168 found_separator
11169}
11170
11171pub fn qualified_owner_components<'tree>(
11172 node: Node<'tree>,
11173 source: &str,
11174) -> Option<QualifiedOwnerComponents<'tree>> {
11175 if !qualified_name_has_concrete_scope_separators(node) {
11176 return None;
11177 }
11178 let mut nodes = cpp_name_component_nodes(node)?;
11179 nodes.pop()?;
11180 if nodes.is_empty() {
11181 return None;
11182 }
11183 let names = nodes
11184 .iter()
11185 .map(|component| node_text(*component, source).to_string())
11186 .collect();
11187 Some(QualifiedOwnerComponents {
11188 nodes,
11189 names,
11190 global: is_globally_qualified_cpp_name(node),
11191 })
11192}
11193
11194pub fn out_of_line_destructor_type_reference(node: Node<'_>) -> Option<Node<'_>> {
11203 if node.kind() != "qualified_identifier" {
11204 return None;
11205 }
11206 let mut qualified = node;
11207 let destructor = loop {
11208 let name = qualified.child_by_field_name("name")?;
11209 match name.kind() {
11210 "qualified_identifier" => qualified = name,
11211 "destructor_name" => break name,
11212 _ => return None,
11213 }
11214 };
11215 (0..destructor.named_child_count())
11216 .filter_map(|index| destructor.named_child(index))
11217 .find(|child| matches!(child.kind(), "identifier" | "type_identifier"))
11218}
11219
11220pub fn out_of_line_member_definition_owner<'tree>(
11221 analyzer: &CppGraphSource<'_>,
11222 visibility: &VisibilityIndex<'_>,
11223 file: &ProjectFile,
11224 source: &str,
11225 node: Node<'tree>,
11226) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
11227 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
11228 || !has_ancestor_kind(node, "function_definition")
11229 || !is_function_declarator_name_root(node)
11230 {
11231 return None;
11232 }
11233 let qualified = qualified_owner_components(node, source)?;
11234 let lexical_scope = enclosing_namespace_components(node, source)?;
11235 let mut owners = Vec::new();
11236 let mut innermost = None;
11237
11238 for component_count in 1..=qualified.names.len() {
11239 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
11240 .resolve_type_components_lexically(
11241 analyzer,
11242 file,
11243 &qualified.names[..component_count],
11244 qualified.global,
11245 &lexical_scope,
11246 )
11247 && !owners
11248 .iter()
11249 .any(|(_, existing)| same_visible_symbol(existing, &unit))
11250 {
11251 if component_count == qualified.names.len() {
11252 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
11253 }
11254 owners.push((qualified.nodes[component_count - 1], unit));
11255 }
11256 }
11257
11258 if innermost.is_none() {
11268 let indexed_owner_components = visibility
11269 .indexed_enclosing_owner_scope(analyzer, file, node)
11270 .or_else(|| {
11271 if qualified.names.len() <= 1 {
11276 return None;
11277 }
11278 let range = Range {
11279 start_byte: node.start_byte(),
11280 end_byte: node.end_byte(),
11281 start_line: node.start_position().row,
11282 end_line: node.end_position().row,
11283 };
11284 let start = analyzer.enclosing_code_unit(file, &range)?;
11285 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11286 brokk_bifrost_core::analyzer::Language::Cpp,
11287 &cpp_name_for(&start),
11288 );
11289 components.pop();
11290 Some(components)
11291 });
11292 if let Some(indexed_owner_components) = indexed_owner_components
11293 && indexed_owner_components.len() > qualified.names.len()
11294 && indexed_owner_components.ends_with(&qualified.names)
11295 && indexed_namespace_path_is_recoverable(
11296 &lexical_scope,
11297 &indexed_owner_components,
11298 qualified.names.len(),
11299 )
11300 && (qualified.names.len() > 1 || !qualified.global)
11305 {
11306 let namespace_count = indexed_owner_components.len() - qualified.names.len();
11307 for component_count in 1..=qualified.names.len() {
11308 let expected = &indexed_owner_components[..namespace_count + component_count];
11309 let owner_node = qualified.nodes[component_count - 1];
11310 for owner in visibility
11311 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
11312 .filter(|candidate| candidate.is_class())
11313 .filter(|candidate| {
11314 canonical_cpp_scope_components(candidate) == expected
11315 && visibility.external_type_candidate_visible_in_context(
11316 analyzer, file, candidate, node,
11317 )
11318 })
11319 {
11320 if component_count == qualified.names.len() && innermost.is_none() {
11321 innermost = Some((owner_node, owner.clone()));
11322 }
11323 if !owners
11324 .iter()
11325 .any(|(_, existing)| same_symbol(existing, owner))
11326 {
11327 owners.push((owner_node, owner.clone()));
11328 }
11329 }
11330 }
11331 }
11332 }
11333 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
11334}
11335
11336fn is_function_declarator_name_root(node: Node<'_>) -> bool {
11337 let mut current = node;
11338 while let Some(parent) = current.parent() {
11339 if parent.kind() == "function_declarator" {
11340 return parent.child_by_field_name("declarator") == Some(current);
11341 }
11342 if matches!(
11343 parent.kind(),
11344 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
11345 ) && parent.child_by_field_name("declarator") == Some(current)
11346 {
11347 current = parent;
11348 continue;
11349 }
11350 return false;
11351 }
11352 false
11353}
11354
11355pub fn append_cpp_name_components(
11356 node: Node<'_>,
11357 source: &str,
11358 out: &mut Vec<String>,
11359) -> Option<()> {
11360 out.extend(
11361 cpp_name_component_nodes(node)?
11362 .into_iter()
11363 .map(|component| node_text(component, source).to_string()),
11364 );
11365 Some(())
11366}
11367
11368pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11369 let mut components = Vec::new();
11370 append_cpp_name_components(node, source, &mut components)?;
11371 Some(components)
11372}
11373
11374pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
11382 let mut parser = Parser::new();
11383 if parser
11384 .set_language(&tree_sitter_cpp::LANGUAGE.into())
11385 .is_err()
11386 {
11387 return Vec::new();
11388 }
11389 let Some(tree) = parser.parse(source, None) else {
11390 return Vec::new();
11391 };
11392 let mut scopes = Vec::new();
11393 let mut pending = vec![tree.root_node()];
11394 while let Some(node) = pending.pop() {
11395 if node.kind() == "using_declaration" {
11396 let Some(imported) = node.named_child(0) else {
11397 continue;
11398 };
11399 let Some(mut components) = cpp_type_name_components(imported, source) else {
11400 continue;
11401 };
11402 if components.pop().as_deref() == Some(member) && !components.is_empty() {
11403 scopes.push(components.join("::"));
11404 }
11405 continue;
11406 }
11407 for index in (0..node.named_child_count()).rev() {
11408 if let Some(child) = node.named_child(index) {
11409 pending.push(child);
11410 }
11411 }
11412 }
11413 scopes
11414}
11415
11416pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
11420 qualified == scope
11421 || qualified
11422 .strip_suffix(scope)
11423 .is_some_and(|prefix| prefix.ends_with("::"))
11424}
11425
11426pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
11431 let Some(type_descriptor) = node.parent() else {
11432 return false;
11433 };
11434 if type_descriptor.kind() != "type_descriptor"
11435 || type_descriptor.child_by_field_name("type") != Some(node)
11436 {
11437 return false;
11438 }
11439 let Some(arguments) = type_descriptor.parent() else {
11440 return false;
11441 };
11442 if arguments.kind() != "template_argument_list" {
11443 return false;
11444 }
11445 arguments.parent().is_some_and(|parent| {
11446 matches!(parent.kind(), "template_type" | "template_function")
11447 && parent.child_by_field_name("arguments") == Some(arguments)
11448 })
11449}
11450
11451pub fn cpp_template_reference_arguments(
11452 mut node: Node<'_>,
11453 source: &str,
11454) -> Option<Vec<CppTemplateExpression>> {
11455 loop {
11456 match node.kind() {
11457 "template_type" | "template_function" => {
11458 let arguments = node.child_by_field_name("arguments")?;
11459 let mut cursor = arguments.walk();
11460 return Some(
11461 arguments
11462 .named_children(&mut cursor)
11463 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
11464 .map(|argument| CppTemplateExpression {
11465 text: normalize_cpp_whitespace(node_text(argument, source)),
11466 term: cpp_template_term(
11468 argument,
11469 source,
11470 &[],
11471 &ParentIndex::unindexed(),
11472 ),
11473 })
11474 .collect(),
11475 );
11476 }
11477 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
11478 node = node
11479 .child_by_field_name("name")
11480 .or_else(|| node.child_by_field_name("type"))?;
11481 }
11482 _ => return None,
11483 }
11484 }
11485}
11486
11487fn cpp_reconcile_primary_template_parameters(
11488 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
11489 preferred: &CodeUnit,
11490) -> Option<Vec<CppTemplateParameterMetadata>> {
11491 let canonical = candidates
11492 .iter()
11493 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
11494 let mut merged = canonical
11495 .parameters
11496 .iter()
11497 .map(|parameter| CppTemplateParameterMetadata {
11498 name: parameter.name.clone(),
11499 kind: parameter.kind,
11500 variadic: parameter.variadic,
11501 default: None,
11502 })
11503 .collect::<Vec<_>>();
11504
11505 for (_, metadata) in candidates {
11506 if metadata.parameters.len() != merged.len() {
11507 return None;
11508 }
11509 let rename_bindings = metadata
11510 .parameters
11511 .iter()
11512 .zip(&merged)
11513 .map(|(parameter, canonical)| {
11514 (
11515 parameter.name.clone(),
11516 CppTemplateTerm::Parameter(canonical.name.clone()),
11517 )
11518 })
11519 .collect::<HashMap<_, _>>();
11520 for ((parameter, canonical), merged_parameter) in metadata
11521 .parameters
11522 .iter()
11523 .zip(&canonical.parameters)
11524 .zip(&mut merged)
11525 {
11526 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
11527 return None;
11528 }
11529 let Some(default) = ¶meter.default else {
11530 continue;
11531 };
11532 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
11533 if let Some(existing) = &merged_parameter.default {
11534 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
11535 return None;
11536 }
11537 } else {
11538 merged_parameter.default = Some(CppTemplateExpression {
11539 text: default.text.clone(),
11540 term: normalized_term,
11541 });
11542 }
11543 }
11544 }
11545 Some(merged)
11546}
11547
11548pub fn cpp_bind_template_arguments(
11549 parameters: &[CppTemplateParameterMetadata],
11550 explicit_arguments: &[CppTemplateExpression],
11551) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
11552 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
11553 if variadic_index.is_some_and(|index| {
11554 index + 1 != parameters.len()
11555 || parameters[index + 1..]
11556 .iter()
11557 .any(|parameter| parameter.variadic)
11558 }) {
11559 return None;
11560 }
11561 let fixed_count = variadic_index.unwrap_or(parameters.len());
11562 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
11563 return None;
11564 }
11565 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
11566 let mut expanded = explicit_arguments[..explicit_fixed_count]
11567 .iter()
11568 .map(cpp_clone_template_expression_iterative)
11569 .collect::<Vec<_>>();
11570 let mut bindings = HashMap::default();
11571 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
11572 bindings.insert(
11573 parameter.name.clone(),
11574 cpp_clone_template_term_iterative(&argument.term),
11575 );
11576 }
11577 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
11578 let default = parameter.default.as_ref()?;
11579 let term = cpp_substitute_template_term(&default.term, &bindings)?;
11580 bindings.insert(parameter.name.clone(), term.clone());
11581 expanded.push(CppTemplateExpression {
11582 text: default.text.clone(),
11583 term,
11584 });
11585 }
11586 if let Some(index) = variadic_index {
11587 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
11588 expanded.extend(
11589 packed_arguments
11590 .iter()
11591 .map(cpp_clone_template_expression_iterative),
11592 );
11593 bindings.insert(
11594 parameters[index].name.clone(),
11595 CppTemplateTerm::Node {
11596 kind: "parameter_pack".to_string(),
11597 children: packed_arguments
11598 .iter()
11599 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
11600 .collect(),
11601 },
11602 );
11603 }
11604 Some((expanded, bindings))
11605}
11606
11607fn cpp_specialization_matches(
11608 metadata: &CppTemplateMetadata,
11609 arguments: &[CppTemplateExpression],
11610) -> bool {
11611 if metadata.specialization_arguments.len() != arguments.len() {
11612 return false;
11613 }
11614 let parameter_names = metadata
11615 .parameters
11616 .iter()
11617 .map(|parameter| parameter.name.as_str())
11618 .collect::<HashSet<_>>();
11619 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11620 for (pattern, argument) in metadata.specialization_arguments.iter().zip(arguments) {
11621 if !cpp_unify_template_term(
11622 &pattern.term,
11623 &argument.term,
11624 ¶meter_names,
11625 &mut bindings,
11626 ) {
11627 return false;
11628 }
11629 }
11630 true
11631}
11632
11633fn cpp_specialization_more_specialized(
11634 candidate: &CppTemplateMetadata,
11635 other: &CppTemplateMetadata,
11636) -> bool {
11637 cpp_specialization_pattern_accepts(other, candidate)
11638 && !cpp_specialization_pattern_accepts(candidate, other)
11639}
11640
11641fn cpp_specialization_pattern_accepts(
11642 broader: &CppTemplateMetadata,
11643 narrower: &CppTemplateMetadata,
11644) -> bool {
11645 if broader.specialization_arguments.len() != narrower.specialization_arguments.len() {
11646 return false;
11647 }
11648 let parameter_names = broader
11649 .parameters
11650 .iter()
11651 .map(|parameter| parameter.name.as_str())
11652 .collect::<HashSet<_>>();
11653 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
11654 broader
11655 .specialization_arguments
11656 .iter()
11657 .zip(&narrower.specialization_arguments)
11658 .all(|(pattern, argument)| {
11659 cpp_unify_template_term(
11660 &pattern.term,
11661 &argument.term,
11662 ¶meter_names,
11663 &mut bindings,
11664 )
11665 })
11666}
11667
11668pub fn cpp_substitute_template_term(
11669 term: &CppTemplateTerm,
11670 bindings: &HashMap<String, CppTemplateTerm>,
11671) -> Option<CppTemplateTerm> {
11672 enum Work<'a> {
11673 Visit(&'a CppTemplateTerm),
11674 Build { kind: String, child_count: usize },
11675 }
11676
11677 let mut work = vec![Work::Visit(term)];
11678 let mut substituted = Vec::new();
11679 while let Some(next) = work.pop() {
11680 match next {
11681 Work::Visit(CppTemplateTerm::Parameter(name)) => {
11682 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
11683 }
11684 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11685 substituted.push(CppTemplateTerm::Atom {
11686 kind: kind.clone(),
11687 text: text.clone(),
11688 });
11689 }
11690 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11691 work.push(Work::Build {
11692 kind: kind.clone(),
11693 child_count: children.len(),
11694 });
11695 work.extend(children.iter().rev().map(Work::Visit));
11696 }
11697 Work::Build { kind, child_count } => {
11698 let children = substituted.split_off(substituted.len() - child_count);
11699 substituted.push(CppTemplateTerm::Node { kind, children });
11700 }
11701 }
11702 }
11703 substituted.pop()
11704}
11705
11706pub fn cpp_substitute_template_arguments(
11707 arguments: &[CppTemplateExpression],
11708 bindings: &HashMap<String, CppTemplateTerm>,
11709) -> Option<Vec<CppTemplateExpression>> {
11710 let mut substituted = Vec::new();
11711 for argument in arguments {
11712 let CppTemplateTerm::Node { kind, children } = &argument.term else {
11713 substituted.push(CppTemplateExpression {
11714 text: argument.text.clone(),
11715 term: cpp_substitute_template_term(&argument.term, bindings)?,
11716 });
11717 continue;
11718 };
11719 if kind != "parameter_pack_expansion" {
11720 substituted.push(CppTemplateExpression {
11721 text: argument.text.clone(),
11722 term: cpp_substitute_template_term(&argument.term, bindings)?,
11723 });
11724 continue;
11725 }
11726 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
11727 return None;
11728 };
11729 if ellipsis != "..." {
11730 return None;
11731 }
11732
11733 let mut pack_names = Vec::new();
11734 let mut work = vec![pattern];
11735 while let Some(term) = work.pop() {
11736 match term {
11737 CppTemplateTerm::Parameter(name)
11738 if matches!(
11739 bindings.get(name),
11740 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
11741 ) =>
11742 {
11743 if !pack_names.contains(name) {
11744 pack_names.push(name.clone());
11745 }
11746 }
11747 CppTemplateTerm::Node { children, .. } => work.extend(children),
11748 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
11749 }
11750 }
11751 let first_pack = pack_names.first()?;
11752 let CppTemplateTerm::Node {
11753 children: first_elements,
11754 ..
11755 } = bindings.get(first_pack)?
11756 else {
11757 return None;
11758 };
11759 let pack_len = first_elements.len();
11760 for pack_name in &pack_names {
11761 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11762 return None;
11763 };
11764 if children.len() != pack_len {
11765 return None;
11766 }
11767 }
11768 for index in 0..pack_len {
11769 let mut element_bindings = bindings.clone();
11770 for pack_name in &pack_names {
11771 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
11772 return None;
11773 };
11774 element_bindings.insert(
11775 pack_name.clone(),
11776 cpp_clone_template_term_iterative(&children[index]),
11777 );
11778 }
11779 substituted.push(CppTemplateExpression {
11780 text: argument.text.clone(),
11781 term: cpp_substitute_template_term(pattern, &element_bindings)?,
11782 });
11783 }
11784 }
11785 Some(substituted)
11786}
11787
11788fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
11789 enum Work<'a> {
11790 Visit(&'a CppTemplateTerm),
11791 Build { kind: String, child_count: usize },
11792 }
11793
11794 let mut work = vec![Work::Visit(term)];
11795 let mut cloned = Vec::new();
11796 while let Some(next) = work.pop() {
11797 match next {
11798 Work::Visit(CppTemplateTerm::Parameter(name)) => {
11799 cloned.push(CppTemplateTerm::Parameter(name.clone()));
11800 }
11801 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
11802 cloned.push(CppTemplateTerm::Atom {
11803 kind: kind.clone(),
11804 text: text.clone(),
11805 });
11806 }
11807 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
11808 work.push(Work::Build {
11809 kind: kind.clone(),
11810 child_count: children.len(),
11811 });
11812 work.extend(children.iter().rev().map(Work::Visit));
11813 }
11814 Work::Build { kind, child_count } => {
11815 let children = cloned.split_off(cloned.len() - child_count);
11816 cloned.push(CppTemplateTerm::Node { kind, children });
11817 }
11818 }
11819 }
11820 cloned
11821 .pop()
11822 .expect("template term traversal emits one root")
11823}
11824
11825fn cpp_clone_template_expression_iterative(
11826 expression: &CppTemplateExpression,
11827) -> CppTemplateExpression {
11828 CppTemplateExpression {
11829 text: expression.text.clone(),
11830 term: cpp_clone_template_term_iterative(&expression.term),
11831 }
11832}
11833
11834pub fn cpp_unify_template_term(
11835 pattern: &CppTemplateTerm,
11836 argument: &CppTemplateTerm,
11837 parameters: &HashSet<&str>,
11838 bindings: &mut HashMap<String, CppTemplateTerm>,
11839) -> bool {
11840 let mut work = vec![(pattern, argument)];
11841 while let Some((pattern, argument)) = work.pop() {
11842 match pattern {
11843 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
11844 if let Some(bound) = bindings.get(name) {
11845 if !cpp_template_terms_equal(bound, argument) {
11846 return false;
11847 }
11848 } else {
11849 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
11850 }
11851 }
11852 CppTemplateTerm::Atom {
11853 kind: pattern_kind,
11854 text: pattern_text,
11855 } => {
11856 if !matches!(
11857 argument,
11858 CppTemplateTerm::Atom { kind, text }
11859 if kind == pattern_kind && text == pattern_text
11860 ) {
11861 return false;
11862 }
11863 }
11864 CppTemplateTerm::Node {
11865 kind: pattern_kind,
11866 children: pattern_children,
11867 } => {
11868 let CppTemplateTerm::Node { kind, children } = argument else {
11869 return false;
11870 };
11871 if kind != pattern_kind || children.len() != pattern_children.len() {
11872 return false;
11873 }
11874 work.extend(pattern_children.iter().zip(children).rev());
11875 }
11876 CppTemplateTerm::Parameter(_) => return false,
11877 }
11878 }
11879 true
11880}
11881
11882fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
11883 let mut work = vec![(left, right)];
11884 while let Some((left, right)) = work.pop() {
11885 match (left, right) {
11886 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
11887 if left != right {
11888 return false;
11889 }
11890 }
11891 (
11892 CppTemplateTerm::Atom {
11893 kind: left_kind,
11894 text: left_text,
11895 },
11896 CppTemplateTerm::Atom {
11897 kind: right_kind,
11898 text: right_text,
11899 },
11900 ) => {
11901 if left_kind != right_kind || left_text != right_text {
11902 return false;
11903 }
11904 }
11905 (
11906 CppTemplateTerm::Node {
11907 kind: left_kind,
11908 children: left_children,
11909 },
11910 CppTemplateTerm::Node {
11911 kind: right_kind,
11912 children: right_children,
11913 },
11914 ) => {
11915 if left_kind != right_kind || left_children.len() != right_children.len() {
11916 return false;
11917 }
11918 work.extend(left_children.iter().zip(right_children).rev());
11919 }
11920 _ => return false,
11921 }
11922 }
11923 true
11924}
11925
11926pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
11927 let mut components = Vec::new();
11928 let mut stack = vec![node];
11929 while let Some(current) = stack.pop() {
11930 match current.kind() {
11931 "identifier"
11932 | "field_identifier"
11933 | "namespace_identifier"
11934 | "type_identifier"
11935 | "operator_name"
11936 | "destructor_name" => components.push(current),
11937 "template_type" | "template_function" => {
11938 stack.push(current.child_by_field_name("name")?);
11939 }
11940 "dependent_name" => stack.push(current.named_child(0)?),
11941 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
11942 stack.push(current.child_by_field_name("name")?);
11943 if let Some(scope) = current.child_by_field_name("scope") {
11944 stack.push(scope);
11945 }
11946 }
11947 "nested_namespace_specifier" => {
11948 for index in (0..current.named_child_count()).rev() {
11949 stack.push(current.named_child(index)?);
11950 }
11951 }
11952 _ => return None,
11953 }
11954 }
11955 Some(components)
11956}
11957
11958pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
11959 node.child_by_field_name("scope").is_none()
11960 && node.child(0).is_some_and(|child| child.kind() == "::")
11961}
11962
11963fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
11964 let mut namespaces = Vec::new();
11965 let mut current = node.parent();
11966 while let Some(parent) = current {
11967 if parent.kind() == "namespace_definition"
11968 && let Some(name) = parent.child_by_field_name("name")
11969 {
11970 let mut components = Vec::new();
11971 append_cpp_name_components(name, source, &mut components)?;
11972 namespaces.push(components);
11973 }
11974 current = parent.parent();
11975 }
11976 namespaces.reverse();
11977 Some(namespaces.into_iter().flatten().collect())
11978}
11979
11980fn indexed_namespace_path_is_recoverable(
11991 lexical_scope: &[String],
11992 indexed_owner_scope: &[String],
11993 explicit_owner_component_count: usize,
11994) -> bool {
11995 if lexical_scope.is_empty() {
11996 return explicit_owner_component_count > 1;
11997 }
11998 if lexical_scope.len() >= indexed_owner_scope.len() {
11999 return false;
12000 }
12001 let mut indexed = indexed_owner_scope.iter();
12002 lexical_scope
12003 .iter()
12004 .all(|component| indexed.any(|candidate| candidate == component))
12005}
12006
12007pub fn has_ancestor_kind(node: Node<'_>, kind: &str) -> bool {
12008 let mut current = node.parent();
12009 while let Some(parent) = current {
12010 if parent.kind() == kind {
12011 return true;
12012 }
12013 current = parent.parent();
12014 }
12015 false
12016}
12017
12018pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
12024 let mut current = Some(node);
12025 while let Some(candidate) = current {
12026 if candidate.kind() == "declaration" {
12027 let Some(type_node) = candidate.child_by_field_name("type") else {
12028 return false;
12029 };
12030 if !(type_node.start_byte() <= node.start_byte()
12031 && node.end_byte() <= type_node.end_byte())
12032 {
12033 return false;
12034 }
12035 let mut cursor = candidate.walk();
12036 return candidate.named_children(&mut cursor).any(|child| {
12037 child.kind() == "init_declarator"
12038 && child
12039 .child_by_field_name("value")
12040 .is_some_and(|value| value.kind() == "cast_expression")
12041 });
12042 }
12043 current = candidate.parent();
12044 }
12045 false
12046}
12047
12048#[derive(Clone, Copy, PartialEq, Eq)]
12049pub(crate) enum QualifiedAliasReferenceKind {
12050 Ordinary,
12051 ConstructorWithExpressionArgument,
12052 ExhaustiveTemplate,
12053}
12054
12055pub(crate) fn qualified_alias_reference_preserves_target(
12062 node: Node<'_>,
12063 target: &CodeUnit,
12064 analyzer: &CppGraphSource<'_>,
12065 visibility: &VisibilityIndex<'_>,
12066 file: &ProjectFile,
12067 source: &str,
12068) -> Option<QualifiedAliasReferenceKind> {
12069 if !matches!(
12070 node.kind(),
12071 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
12072 ) {
12073 return None;
12074 }
12075 let components = cpp_type_name_components(node, source)?;
12076 let name = components.last()?;
12077 analyzer.type_alias_provider().and_then(|provider| {
12078 visibility
12079 .visible_identifier_candidates(file, name)
12080 .find_map(|candidate| {
12081 let proof = provider.is_type_alias(candidate)
12082 && canonical_cpp_scope_components(candidate) == components
12083 && visibility.external_type_candidate_visible_in_context(
12084 analyzer, file, candidate, node,
12085 )
12086 && match cpp_template_reference_arguments(node, source) {
12087 Some(arguments) => visibility.template_alias_arguments_preserve_target(
12088 analyzer, file, candidate, &arguments, target,
12089 ),
12090 None => visibility.structured_alias_primary_preserves_target(
12091 analyzer, file, candidate, target,
12092 ),
12093 };
12094 proof.then(|| {
12095 if cpp_template_reference_arguments(node, source).is_some()
12096 && visibility.is_exhaustive_same_fqn_type_declaration_family(
12097 analyzer, file, candidate,
12098 )
12099 {
12100 QualifiedAliasReferenceKind::ExhaustiveTemplate
12101 } else if qualified_alias_constructor_has_expression_argument(node)
12102 || qualified_alias_local_constructor_declaration(node)
12103 {
12104 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12105 } else {
12106 QualifiedAliasReferenceKind::Ordinary
12107 }
12108 })
12109 })
12110 })
12111}
12112
12113pub(crate) fn qualified_alias_reference_requires_terminal(
12114 reference: Option<QualifiedAliasReferenceKind>,
12115) -> bool {
12116 matches!(
12117 reference,
12118 Some(
12119 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
12120 | QualifiedAliasReferenceKind::ExhaustiveTemplate
12121 )
12122 )
12123}
12124
12125fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
12126 let Some(declaration) = node.parent().filter(|parent| {
12127 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12128 }) else {
12129 return false;
12130 };
12131 let mut cursor = declaration.walk();
12132 declaration.named_children(&mut cursor).any(|child| {
12133 child.kind() == "init_declarator"
12134 && child
12135 .child_by_field_name("value")
12136 .filter(|value| value.kind() == "argument_list")
12137 .is_some_and(|arguments| {
12138 let mut cursor = arguments.walk();
12139 arguments.named_children(&mut cursor).any(|argument| {
12140 let is_parameter = matches!(
12141 argument.kind(),
12142 "parameter_declaration" | "optional_parameter_declaration"
12143 );
12144 if is_parameter {
12145 argument
12146 .child_by_field_name("type")
12147 .is_some_and(|type_node| {
12148 type_node.kind() == "type_identifier"
12149 && argument.child_by_field_name("declarator").is_none()
12150 })
12151 } else {
12152 !argument.kind().ends_with("_literal")
12153 && !matches!(argument.kind(), "true" | "false" | "nullptr")
12154 }
12155 })
12156 })
12157 })
12158}
12159
12160fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
12165 let Some(declaration) = node.parent().filter(|parent| {
12166 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
12167 }) else {
12168 return false;
12169 };
12170 if declaration
12171 .parent()
12172 .is_none_or(|parent| parent.kind() != "compound_statement")
12173 {
12174 return false;
12175 }
12176 let mut cursor = declaration.walk();
12177 declaration
12178 .named_children(&mut cursor)
12179 .any(|child| child.kind() == "function_declarator")
12180}
12181
12182pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
12188 loop {
12189 let next = match node.kind() {
12190 "qualified_identifier"
12191 | "scoped_identifier"
12192 | "template_method"
12193 | "template_function"
12194 | "template_type" => node.child_by_field_name("name"),
12195 "field_expression" => node.child_by_field_name("field"),
12196 _ => None,
12197 };
12198 let Some(next) = next else {
12199 return node;
12200 };
12201 node = next;
12202 }
12203}
12204
12205#[derive(Clone, Copy)]
12206pub struct RecoveredRelationalTemplateMemberCall<'tree> {
12207 pub receiver: Node<'tree>,
12208 pub member: Node<'tree>,
12209 pub arity: usize,
12210}
12211
12212pub fn recovered_relational_template_member_call(
12220 field: Node<'_>,
12221) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
12222 if field.kind() != "field_expression" {
12223 return None;
12224 }
12225 let receiver = field
12226 .child_by_field_name("argument")
12227 .or_else(|| field.child_by_field_name("object"))?;
12228 let member = field.child_by_field_name("field")?;
12229 let less = field.parent()?;
12230 if less.kind() != "binary_expression"
12231 || less.child_by_field_name("left") != Some(field)
12232 || less
12233 .child_by_field_name("operator")
12234 .is_none_or(|operator| operator.kind() != "<")
12235 || less.child_by_field_name("right").is_none()
12236 {
12237 return None;
12238 }
12239 let greater = less.parent()?;
12240 if greater.kind() != "binary_expression"
12241 || greater.child_by_field_name("left") != Some(less)
12242 || greater
12243 .child_by_field_name("operator")
12244 .is_none_or(|operator| operator.kind() != ">")
12245 {
12246 return None;
12247 }
12248 let arguments = greater.child_by_field_name("right")?;
12249 if arguments.kind() != "parenthesized_expression" {
12250 return None;
12251 }
12252 let arity = parenthesized_call_argument_arity(arguments)?;
12253 Some(RecoveredRelationalTemplateMemberCall {
12254 receiver,
12255 member,
12256 arity,
12257 })
12258}
12259
12260fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
12261 let expression = arguments.named_child(0)?;
12262 if expression.kind() != "comma_expression" {
12263 return Some(1);
12264 }
12265 let mut arity = 0usize;
12266 let mut stack = vec![expression];
12267 while let Some(node) = stack.pop() {
12268 if node.kind() == "comma_expression" {
12269 stack.push(node.child_by_field_name("right")?);
12270 stack.push(node.child_by_field_name("left")?);
12271 } else {
12272 arity += 1;
12273 }
12274 }
12275 Some(arity)
12276}
12277
12278pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
12281 while let Some(parent) = node.parent() {
12282 match parent.kind() {
12283 "call_expression" => {
12284 return parent
12285 .child_by_field_name("function")
12286 .or_else(|| parent.named_child(0))
12287 == Some(node);
12288 }
12289 "qualified_identifier"
12290 | "scoped_identifier"
12291 | "template_function"
12292 | "template_type"
12293 | "field_expression" => node = parent,
12294 _ => return false,
12295 }
12296 }
12297 false
12298}
12299
12300pub fn type_reference_hit_node(node: Node<'_>) -> Node<'_> {
12301 if is_call_callee_node(node) {
12302 function_terminal_node(node)
12303 } else {
12304 node
12305 }
12306}
12307
12308pub fn normalize_type_text(value: &str) -> String {
12309 strip_tag_type_prefix(
12310 normalize_cpp_whitespace(value)
12311 .trim_start_matches("const ")
12312 .trim_end_matches('*')
12313 .trim_end_matches('&')
12314 .trim(),
12315 )
12316 .to_string()
12317}
12318
12319fn strip_tag_type_prefix(value: &str) -> &str {
12320 let value = value.trim_start_matches("const ");
12321 value
12322 .strip_prefix("struct ")
12323 .or_else(|| value.strip_prefix("class "))
12324 .or_else(|| value.strip_prefix("enum "))
12325 .unwrap_or(value)
12326 .trim()
12327}
12328
12329pub fn normalize_reference_name(value: &str) -> Option<String> {
12330 let normalized = normalize_cpp_reference_text(value);
12331 (!normalized.is_empty()).then_some(normalized)
12332}
12333
12334pub fn normalize_cpp_reference_text(value: &str) -> String {
12335 let mut text = normalize_cpp_whitespace(value)
12336 .trim_start_matches("new ")
12337 .trim()
12338 .to_string();
12339 if let Some(index) = text.find(['(', '{']) {
12340 text.truncate(index);
12341 }
12342 if let Some(index) = text.find('<') {
12343 text.truncate(index);
12344 }
12345 let normalized = text
12346 .trim()
12347 .trim_start_matches("const ")
12348 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
12349 .trim_matches(':')
12350 .trim();
12351 strip_tag_type_prefix(normalized).to_string()
12352}
12353
12354pub fn cpp_name_for(unit: &CodeUnit) -> String {
12355 let short = unit.short_name().replace(['.', '$'], "::");
12356 if unit.package_name().is_empty() {
12357 short
12358 } else {
12359 format!("{}::{}", unit.package_name(), short)
12360 }
12361}
12362
12363fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
12367 let fq = unit.fq();
12368 if fq.is_empty() {
12369 return None;
12370 }
12371 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12372 Some(
12373 fq.segments()
12374 .iter()
12375 .map(|&segment| interner.resolve(segment).0)
12376 .collect::<Vec<_>>()
12377 .join("::"),
12378 )
12379}
12380
12381fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
12382 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
12383 || unit.fq().is_empty() && cpp_name_for(unit) == expected
12384}
12385
12386pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
12395 let fq = unit.fq();
12396 if !fq.is_empty() {
12397 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
12398 let scope = fq
12399 .segments()
12400 .iter()
12401 .filter_map(|&segment| {
12402 let (text, kind) = interner.resolve(segment);
12403 matches!(
12404 kind,
12405 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
12406 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
12407 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
12408 )
12409 .then(|| text.to_string())
12410 })
12411 .collect();
12412 return scope;
12413 }
12414 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12415 brokk_bifrost_core::analyzer::Language::Cpp,
12416 &cpp_name_for(unit),
12417 )
12418}
12419
12420pub fn terminal_name(value: &str) -> &str {
12431 value
12432 .rsplit("::")
12433 .next()
12434 .unwrap_or(value)
12435 .rsplit(['.', '-', '>'])
12436 .next()
12437 .unwrap_or(value)
12438 .trim()
12439}
12440
12441pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
12442 terminal_name(&normalize_cpp_reference_text(value)) == expected
12443}
12444
12445pub fn name_matches_callable(value: &str, expected: &str) -> bool {
12446 name_matches_terminal(value, expected)
12447 || expected.starts_with("operator")
12448 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
12449}
12450
12451pub fn name_mentions(value: &str, expected: &str) -> bool {
12452 normalize_cpp_reference_text(value)
12453 .split("::")
12454 .any(|part| part == expected)
12455}
12456
12457pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
12458 let cpp_name = cpp_name_for(unit);
12459 if reference.contains("::") {
12460 return reference == cpp_name;
12461 }
12462 reference == cpp_name
12463 || terminal_name(reference) == unit.identifier()
12464 && (unit.package_name().is_empty() || reference == unit.identifier())
12465}
12466
12467pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
12468 match kind {
12469 TargetKind::Type
12470 | TargetKind::Constructor
12471 | TargetKind::Method
12472 | TargetKind::MemberField => true,
12473 TargetKind::FreeFunction => unit.is_function(),
12474 TargetKind::GlobalField => unit.is_field(),
12475 TargetKind::Macro => unit.is_macro(),
12476 }
12477}
12478
12479pub fn is_type_alias(unit: &CodeUnit) -> bool {
12480 unit.kind() == CodeUnitType::Field
12481 && unit.signature().is_some_and(|signature| {
12482 signature.starts_with("typedef ") || signature.starts_with("using ")
12483 })
12484}
12485
12486fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
12487 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12488 let target_name = cpp_name_for(target);
12489 if normalized.contains("::") {
12490 return normalized == target_name;
12491 }
12492 if let Some(namespace) = alias.namespace.as_deref() {
12493 return namespace_prefixes(namespace)
12494 .into_iter()
12495 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
12496 }
12497 target.package_name().is_empty() && normalized == target.identifier()
12498}
12499
12500fn parser_alias_target_names(alias: &CppAlias) -> Vec<String> {
12501 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
12502 if normalized.contains("::") {
12503 return vec![normalized];
12504 }
12505 alias
12506 .namespace
12507 .as_deref()
12508 .map(namespace_prefixes)
12509 .map(|prefixes| {
12510 prefixes
12511 .into_iter()
12512 .map(|prefix| format!("{prefix}::{normalized}"))
12513 .collect()
12514 })
12515 .unwrap_or_else(|| vec![normalized])
12516}
12517
12518pub fn cpp_function_return_type_text(
12521 analyzer: &CppGraphSource<'_>,
12522 function: &CodeUnit,
12523) -> Option<String> {
12524 let metadata = analyzer.signature_metadata(function);
12525 if !metadata.is_empty() {
12526 let first = metadata.first()?.return_type_text()?;
12527 return metadata
12528 .iter()
12529 .all(|metadata| metadata.return_type_text() == Some(first))
12530 .then(|| first.to_string());
12531 }
12532 let signature = cpp_function_signature_text(analyzer, function)?;
12533 cpp_function_return_type_text_from_signature(&signature)
12534}
12535
12536fn cpp_function_signature_text(
12537 analyzer: &CppGraphSource<'_>,
12538 function: &CodeUnit,
12539) -> Option<String> {
12540 function
12541 .signature()
12542 .filter(|signature| signature.contains(function.identifier()))
12543 .map(str::to_string)
12544 .or_else(|| analyzer.signatures(function).first().cloned())
12545 .or_else(|| analyzer.get_source(function, false))
12546}
12547
12548fn cpp_function_return_type_text_from_signature(signature: &str) -> Option<String> {
12549 let open = signature.find('(')?;
12550 let name_at = cpp_function_name_start(signature, open)?;
12551 if let Some(return_type) = cpp_trailing_return_type(&signature[name_at..]) {
12552 return Some(return_type);
12553 }
12554 let type_text = cpp_strip_leading_template_clause(&signature[..name_at])
12555 .split_whitespace()
12556 .filter(|token| {
12557 !matches!(
12558 *token,
12559 "static" | "virtual" | "inline" | "constexpr" | "explicit" | "friend"
12560 )
12561 })
12562 .collect::<Vec<_>>()
12563 .join(" ");
12564 let type_text = type_text.trim();
12565 (!type_text.is_empty()).then(|| type_text.to_string())
12566}
12567
12568fn cpp_function_name_start(signature: &str, open: usize) -> Option<usize> {
12569 let before_parameters = &signature[..open];
12570 if let Some(operator_at) = before_parameters.rfind("operator") {
12571 let boundary = operator_at == 0
12572 || before_parameters[..operator_at]
12573 .chars()
12574 .next_back()
12575 .is_some_and(|ch| !(ch == '_' || ch.is_ascii_alphanumeric()));
12576 if boundary {
12577 return Some(operator_at);
12578 }
12579 }
12580 before_parameters
12581 .rfind(|ch: char| !(ch == '_' || ch.is_ascii_alphanumeric()))
12582 .map(|index| index + 1)
12583}
12584
12585fn cpp_trailing_return_type(signature_from_name: &str) -> Option<String> {
12586 let open = signature_from_name.find('(')?;
12587 let mut depth = 0i32;
12588 for (offset, ch) in signature_from_name[open..].char_indices() {
12589 match ch {
12590 '(' => depth += 1,
12591 ')' => {
12592 depth -= 1;
12593 if depth == 0 {
12594 let rest = signature_from_name[open + offset + ch.len_utf8()..].trim_start();
12595 let arrow = rest.find("->")?;
12596 let return_type = rest[arrow + 2..].trim_start();
12597 let return_type = return_type
12598 .split(['{', ';'])
12599 .next()
12600 .unwrap_or(return_type)
12601 .trim();
12602 return (!return_type.is_empty()).then(|| return_type.to_string());
12603 }
12604 }
12605 _ => {}
12606 }
12607 }
12608 None
12609}
12610
12611fn cpp_strip_leading_template_clause(text: &str) -> &str {
12614 let trimmed = text.trim_start();
12615 let Some(rest) = trimmed.strip_prefix("template") else {
12616 return text;
12617 };
12618 let rest = rest.trim_start();
12619 if !rest.starts_with('<') {
12620 return text;
12621 }
12622 let mut depth = 0i32;
12623 for (offset, ch) in rest.char_indices() {
12624 match ch {
12625 '<' => depth += 1,
12626 '>' => {
12627 depth -= 1;
12628 if depth == 0 {
12629 return rest[offset + ch.len_utf8()..].trim_start();
12630 }
12631 }
12632 _ => {}
12633 }
12634 }
12635 text
12636}
12637
12638pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
12639 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
12649 namespace
12650 .strip_prefix("anonymous_namespace::")
12651 .unwrap_or(namespace)
12652 .to_string()
12653 })
12654}
12655
12656fn namespace_prefixes(namespace: &str) -> Vec<String> {
12657 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
12663 brokk_bifrost_core::analyzer::Language::Cpp,
12664 namespace,
12665 );
12666 let mut prefixes = Vec::new();
12667 while !parts.is_empty() {
12668 prefixes.push(parts.join("::"));
12669 parts.pop();
12670 }
12671 prefixes
12672}
12673
12674fn nearest_namespace_candidates(
12675 candidates: Vec<CodeUnit>,
12676 normalized: &str,
12677 lexical_namespace: Option<&str>,
12678) -> Vec<CodeUnit> {
12679 if normalized.contains("::") {
12680 return candidates;
12681 }
12682 if let Some(namespace) = lexical_namespace {
12683 for prefix in namespace_prefixes(namespace) {
12684 let scoped = candidates
12685 .iter()
12686 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
12687 .cloned()
12688 .collect::<Vec<_>>();
12689 if !scoped.is_empty() {
12690 return scoped;
12691 }
12692 }
12693 }
12694 candidates
12695 .into_iter()
12696 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
12697 .collect()
12698}
12699
12700pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
12701 let mut namespaces = Vec::new();
12702 let mut current = node.parent();
12703 while let Some(parent) = current {
12704 if parent.kind() == "namespace_definition"
12705 && let Some(name) = parent.child_by_field_name("name")
12706 {
12707 let namespace = normalize_cpp_reference_text(node_text(name, source));
12708 if !namespace.is_empty() {
12709 namespaces.push(namespace);
12710 }
12711 }
12712 current = parent.parent();
12713 }
12714 if namespaces.is_empty() {
12715 None
12716 } else {
12717 namespaces.reverse();
12718 Some(namespaces.join("::"))
12719 }
12720}
12721
12722pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
12726 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
12727}
12728
12729fn type_owner_resolution(
12730 analyzer: &CppGraphSource<'_>,
12731 code_unit: &CodeUnit,
12732) -> Option<ResolvedTypeOwner> {
12733 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
12734}
12735
12736fn target_type_owner_resolution(
12737 analyzer: &CppGraphSource<'_>,
12738 code_unit: &CodeUnit,
12739) -> Option<ResolvedTypeOwner> {
12740 match type_owner_resolution(analyzer, code_unit) {
12741 Some(owner) if !owner.is_forward_declaration => Some(owner),
12742 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
12743 }
12744}
12745
12746fn target_forward_owner_resolution(
12752 analyzer: &CppGraphSource<'_>,
12753 code_unit: &CodeUnit,
12754) -> Option<ResolvedTypeOwner> {
12755 if !code_unit.is_function() {
12756 return None;
12757 }
12758 let owner_fqn = brokk_bifrost_core::analyzer::default_parent_fq_name(code_unit)?;
12759 let cpp = analyzer.cpp?;
12760 let mut visible_files = HashSet::default();
12761 collect_include_closure(
12762 analyzer,
12763 cpp.include_target_index(),
12764 code_unit.source(),
12765 &mut visible_files,
12766 None,
12767 );
12768 let mut forward = None;
12769 for candidate in analyzer
12770 .global_usage_definition_index()
12771 .fqn(&owner_fqn)
12772 .into_iter()
12773 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
12774 {
12775 match cpp_class_declaration_strength(analyzer, candidate) {
12776 CppClassDeclarationStrength::Forward if forward.is_none() => {
12777 forward = Some(candidate.clone());
12778 }
12779 CppClassDeclarationStrength::Forward
12780 | CppClassDeclarationStrength::Full
12781 | CppClassDeclarationStrength::Unknown => return None,
12782 }
12783 }
12784 forward.map(|unit| ResolvedTypeOwner {
12785 unit,
12786 is_forward_declaration: true,
12787 })
12788}
12789
12790pub fn precise_parent_of(
12791 analyzer: &CppGraphSource<'_>,
12792 visibility: &VisibilityIndex<'_>,
12793 code_unit: &CodeUnit,
12794) -> Option<CodeUnit> {
12795 visibility.cached_precise_parent_of(analyzer, code_unit)
12796}
12797
12798fn precise_parent_resolution(
12799 analyzer: &CppGraphSource<'_>,
12800 code_unit: &CodeUnit,
12801) -> Option<ResolvedTypeOwner> {
12802 #[cfg(any(test, feature = "test-support"))]
12803 if let Some(cpp) = analyzer.cpp {
12804 cpp.record_cpp_parent_resolution_for_test();
12805 }
12806 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
12807 return Some(ResolvedTypeOwner {
12808 unit,
12809 is_forward_declaration: false,
12810 });
12811 }
12812 let fallback = analyzer.parent_of(code_unit);
12813 let Some(owner_name) = code_unit
12819 .short_name()
12820 .rsplit_once('.')
12821 .map(|(owner, _)| owner)
12822 else {
12823 return fallback.map(|unit| ResolvedTypeOwner {
12824 unit,
12825 is_forward_declaration: false,
12826 });
12827 };
12828 let owner_fqn = if code_unit.package_name().is_empty() {
12829 owner_name.to_string()
12830 } else {
12831 format!("{}.{}", code_unit.package_name(), owner_name)
12832 };
12833 match same_source_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12834 DirectOwnerResolution::UniqueFull(owner) => {
12835 return Some(ResolvedTypeOwner {
12836 unit: owner,
12837 is_forward_declaration: false,
12838 });
12839 }
12840 DirectOwnerResolution::Ambiguous => return None,
12841 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
12842 }
12843 match directly_included_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12844 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
12845 unit: owner,
12846 is_forward_declaration: false,
12847 }),
12848 DirectOwnerResolution::Ambiguous => None,
12849 DirectOwnerResolution::ForwardsOnly(forwards) => {
12850 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12851 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12852 unit: owner,
12853 is_forward_declaration: false,
12854 }),
12855 FullOwnerResolution::None => {
12856 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
12857 unit,
12858 is_forward_declaration: true,
12859 })
12860 }
12861 FullOwnerResolution::Ambiguous => None,
12862 }
12863 }
12864 DirectOwnerResolution::None => {
12865 match visible_full_cpp_owner(analyzer, code_unit, &owner_fqn, owner_name) {
12866 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
12867 unit: owner,
12868 is_forward_declaration: false,
12869 }),
12870 FullOwnerResolution::Ambiguous => None,
12871 FullOwnerResolution::None => fallback
12872 .filter(|parent| {
12873 parent.source() == code_unit.source()
12874 && parent.short_name() == owner_name
12875 && parent.package_name() == code_unit.package_name()
12876 && (!parent.is_class()
12877 || cpp_class_declaration_strength(analyzer, parent)
12878 == CppClassDeclarationStrength::Full)
12879 })
12880 .map(|unit| ResolvedTypeOwner {
12881 unit,
12882 is_forward_declaration: false,
12883 }),
12884 }
12885 }
12886 }
12887}
12888
12889fn exact_structural_type_parent(
12890 analyzer: &CppGraphSource<'_>,
12891 code_unit: &CodeUnit,
12892) -> Option<CodeUnit> {
12893 if !code_unit.is_function() && !code_unit.is_field() {
12894 return None;
12895 }
12896 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
12898 let parent = cpp.structural_parent_of(code_unit)?;
12899 (!parent.is_module()
12900 && parent.source() == code_unit.source()
12901 && parent.package_name() == code_unit.package_name()
12902 && parent.short_name() == encoded_owner)
12903 .then_some(parent)
12904}
12905
12906fn same_source_owner(
12907 analyzer: &CppGraphSource<'_>,
12908 code_unit: &CodeUnit,
12909 owner_fqn: &str,
12910 owner_name: &str,
12911) -> DirectOwnerResolution {
12912 let candidates = analyzer
12913 .global_usage_definition_index()
12914 .fqn(owner_fqn)
12915 .into_iter()
12916 .filter(|candidate| {
12917 candidate.is_class()
12918 && candidate.source() == code_unit.source()
12919 && candidate.short_name() == owner_name
12920 && candidate.package_name() == code_unit.package_name()
12921 })
12922 .collect::<Vec<_>>();
12923 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12924 classify_direct_owner_candidates(analyzer, candidates.into_iter())
12925}
12926
12927fn visible_full_cpp_owner(
12928 analyzer: &CppGraphSource<'_>,
12929 code_unit: &CodeUnit,
12930 owner_fqn: &str,
12931 owner_name: &str,
12932) -> FullOwnerResolution {
12933 let Some(cpp) = analyzer.cpp else {
12934 return FullOwnerResolution::None;
12935 };
12936 let mut visible_files = HashSet::default();
12937 collect_include_closure(
12938 analyzer,
12939 cpp.include_target_index(),
12940 code_unit.source(),
12941 &mut visible_files,
12942 None,
12943 );
12944 let candidates = analyzer
12945 .global_usage_definition_index()
12946 .fqn(owner_fqn)
12947 .into_iter()
12948 .filter(|candidate| {
12949 candidate.is_class()
12950 && candidate.short_name() == owner_name
12951 && candidate.package_name() == code_unit.package_name()
12952 && visible_files.contains(candidate.source())
12953 })
12954 .collect::<Vec<_>>();
12955 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
12956 let mut full_definition = None;
12957 for candidate in candidates {
12958 match cpp_class_declaration_strength(analyzer, candidate) {
12959 CppClassDeclarationStrength::Full if full_definition.is_some() => {
12960 return FullOwnerResolution::Ambiguous;
12961 }
12962 CppClassDeclarationStrength::Full => full_definition = Some(candidate.clone()),
12963 CppClassDeclarationStrength::Forward => {}
12964 CppClassDeclarationStrength::Unknown => return FullOwnerResolution::Ambiguous,
12965 }
12966 }
12967 full_definition.map_or(FullOwnerResolution::None, FullOwnerResolution::Unique)
12968}
12969
12970pub enum DirectOwnerResolution {
12971 None,
12972 ForwardsOnly(Vec<CodeUnit>),
12973 UniqueFull(CodeUnit),
12974 Ambiguous,
12975}
12976
12977enum FullOwnerResolution {
12978 None,
12979 Unique(CodeUnit),
12980 Ambiguous,
12981}
12982
12983#[derive(Clone, Copy, PartialEq, Eq)]
12984pub enum CppClassDeclarationStrength {
12985 Full,
12986 Forward,
12987 Unknown,
12988}
12989
12990fn directly_included_owner(
12991 analyzer: &CppGraphSource<'_>,
12992 code_unit: &CodeUnit,
12993 owner_fqn: &str,
12994 owner_name: &str,
12995) -> DirectOwnerResolution {
12996 let Some(cpp) = analyzer.cpp else {
12997 return DirectOwnerResolution::None;
12998 };
12999 let imports = analyzer.import_statements(code_unit.source());
13000 let direct_includes: HashSet<ProjectFile> = cpp_include_paths(&imports)
13001 .into_iter()
13002 .flat_map(|include| {
13003 resolve_include_targets_with_index(
13004 code_unit.source(),
13005 &include,
13006 cpp.include_target_index(),
13007 )
13008 })
13009 .collect();
13010 let candidates = analyzer
13011 .global_usage_definition_index()
13012 .fqn(owner_fqn)
13013 .into_iter()
13014 .filter(|candidate| {
13015 candidate.is_class()
13016 && candidate.short_name() == owner_name
13017 && candidate.package_name() == code_unit.package_name()
13018 && direct_includes.contains(candidate.source())
13019 })
13020 .collect::<Vec<_>>();
13021 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
13022 classify_direct_owner_candidates(analyzer, candidates.into_iter())
13023}
13024
13025fn prefer_member_declaring_owners<'a>(
13026 analyzer: &CppGraphSource<'_>,
13027 member: &CodeUnit,
13028 candidates: Vec<&'a CodeUnit>,
13029) -> Vec<&'a CodeUnit> {
13030 let matching = candidates
13031 .iter()
13032 .copied()
13033 .filter(|owner| owner_declares_member(analyzer, owner, member))
13034 .collect::<Vec<_>>();
13035 if matching.is_empty() {
13036 candidates
13037 } else {
13038 matching
13039 }
13040}
13041
13042fn owner_declares_member(
13043 analyzer: &CppGraphSource<'_>,
13044 owner: &CodeUnit,
13045 member: &CodeUnit,
13046) -> bool {
13047 analyzer.direct_children(owner).into_iter().any(|child| {
13048 child.kind() == member.kind()
13049 && child.identifier() == member.identifier()
13050 && child.signature() == member.signature()
13051 })
13052}
13053
13054fn classify_direct_owner_candidates<'a>(
13055 analyzer: &CppGraphSource<'_>,
13056 candidates: impl Iterator<Item = &'a CodeUnit>,
13057) -> DirectOwnerResolution {
13058 collapse_owner_candidates(candidates.map(|candidate| {
13059 (
13060 candidate.clone(),
13061 cpp_class_declaration_strength(analyzer, candidate),
13062 )
13063 }))
13064}
13065
13066pub fn collapse_owner_candidates(
13067 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
13068) -> DirectOwnerResolution {
13069 let mut full_definition = None;
13070 let mut forwards = Vec::new();
13071 for (candidate, strength) in candidates {
13072 match strength {
13073 CppClassDeclarationStrength::Full if full_definition.is_some() => {
13074 return DirectOwnerResolution::Ambiguous;
13075 }
13076 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
13077 CppClassDeclarationStrength::Forward => forwards.push(candidate),
13078 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
13079 }
13080 }
13081 if let Some(owner) = full_definition {
13082 DirectOwnerResolution::UniqueFull(owner)
13083 } else if !forwards.is_empty() {
13084 DirectOwnerResolution::ForwardsOnly(forwards)
13085 } else {
13086 DirectOwnerResolution::None
13087 }
13088}
13089
13090#[cfg(any(test, feature = "test-support"))]
13091pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13092 unique_logical_forward_owner(forwards)
13093}
13094
13095fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
13096 let first = forwards.pop()?;
13097 forwards
13098 .iter()
13099 .all(|forward| same_logical_symbol(forward, &first))
13100 .then_some(first)
13101}
13102
13103pub fn cpp_class_declaration_strength(
13104 analyzer: &CppGraphSource<'_>,
13105 candidate: &CodeUnit,
13106) -> CppClassDeclarationStrength {
13107 if let Some(prepared) = analyzer
13108 .cpp
13109 .and_then(|cpp| cpp.prepared_syntax(analyzer.token, candidate.source()))
13110 {
13111 return cpp_class_declaration_strength_in_tree(
13112 analyzer,
13113 candidate,
13114 prepared.source(),
13115 prepared.tree().root_node(),
13116 );
13117 }
13118 let Some(source) = analyzer.indexed_source(candidate.source()) else {
13119 return CppClassDeclarationStrength::Unknown;
13120 };
13121 #[cfg(any(test, feature = "test-support"))]
13122 if let Some(cpp) = analyzer.cpp {
13123 cpp.record_cpp_class_strength_parse_for_test();
13124 }
13125 let mut parser = Parser::new();
13126 if parser
13127 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13128 .is_err()
13129 {
13130 return CppClassDeclarationStrength::Unknown;
13131 }
13132 let Some(tree) = parser.parse(&source, None) else {
13133 return CppClassDeclarationStrength::Unknown;
13134 };
13135 cpp_class_declaration_strength_in_tree(analyzer, candidate, &source, tree.root_node())
13136}
13137
13138fn cpp_class_declaration_strength_in_tree(
13139 analyzer: &CppGraphSource<'_>,
13140 candidate: &CodeUnit,
13141 source: &str,
13142 root: Node<'_>,
13143) -> CppClassDeclarationStrength {
13144 let ranges = analyzer.ranges(candidate);
13145 let mut saw_forward = false;
13146 for range in ranges {
13147 let mut stack = vec![root];
13148 while let Some(node) = stack.pop() {
13149 if node.start_byte() == range.start_byte
13150 && recovered_fragmented_plain_class_has_body(
13151 node,
13152 source,
13153 candidate.identifier(),
13154 &range,
13155 )
13156 {
13157 return CppClassDeclarationStrength::Full;
13158 }
13159 if node.start_byte() > range.start_byte || node.end_byte() < range.start_byte {
13160 continue;
13161 }
13162 if node.start_byte() == range.start_byte && node.end_byte() == range.end_byte {
13163 if matches!(
13164 node.kind(),
13165 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13166 ) {
13167 if cpp_class_node_has_body(node) {
13168 return CppClassDeclarationStrength::Full;
13169 }
13170 saw_forward = true;
13171 } else if let Some(has_body) =
13172 recovered_exported_class_has_body(node, source, candidate.identifier())
13173 {
13174 if has_body {
13175 return CppClassDeclarationStrength::Full;
13176 }
13177 saw_forward = true;
13178 }
13179 }
13180 let mut cursor = node.walk();
13181 stack.extend(node.named_children(&mut cursor));
13182 }
13183 }
13184 if saw_forward {
13185 CppClassDeclarationStrength::Forward
13186 } else {
13187 CppClassDeclarationStrength::Unknown
13188 }
13189}
13190
13191fn cpp_class_node_has_body(node: Node<'_>) -> bool {
13192 node.child_by_field_name("body").is_some() || {
13193 let mut cursor = node.walk();
13194 node.named_children(&mut cursor).any(|child| {
13195 matches!(
13196 child.kind(),
13197 "declaration_list" | "field_declaration_list" | "enumerator_list"
13198 )
13199 })
13200 }
13201}
13202
13203pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
13204 let owner_name = code_unit
13209 .short_name()
13210 .rsplit_once('.')
13211 .map(|(owner, _)| owner)?;
13212 let owner_fqn = if code_unit.package_name().is_empty() {
13213 owner_name.to_string()
13214 } else {
13215 format!("{}.{}", code_unit.package_name(), owner_name)
13216 };
13217 ctx.analyzer
13218 .global_usage_definition_index()
13219 .fqn(&owner_fqn)
13220 .into_iter()
13221 .find(|candidate| {
13222 candidate.is_class()
13223 && ctx.visibility.is_visible(ctx.file, candidate)
13224 && candidate.short_name() == owner_name
13225 && candidate.package_name() == code_unit.package_name()
13226 })
13227 .cloned()
13228}
13229
13230pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13231 left.kind() == right.kind()
13232 && left.fq_name() == right.fq_name()
13233 && left.signature() == right.signature()
13234 && left.source() == right.source()
13235}
13236
13237pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13238 same_symbol(left, right) || same_logical_symbol(left, right)
13239}
13240
13241pub fn same_visible_global_field_symbol(
13242 analyzer: &CppGraphSource<'_>,
13243 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
13244 left: &CodeUnit,
13245 right: &CodeUnit,
13246) -> bool {
13247 if same_symbol(left, right) {
13248 return true;
13249 }
13250 if !same_logical_symbol(left, right) {
13251 return false;
13252 }
13253 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
13254 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
13255 {
13256 left.source() == right.source()
13257 } else {
13258 true
13259 }
13260}
13261
13262fn cpp_global_field_has_internal_linkage_cached(
13263 analyzer: &CppGraphSource<'_>,
13264 cache: &mut HashMap<CodeUnit, bool>,
13265 candidate: &CodeUnit,
13266) -> bool {
13267 if let Some(internal) = cache.get(candidate) {
13268 return *internal;
13269 }
13270 #[cfg(any(test, feature = "test-support"))]
13271 note_cpp_global_field_internal_linkage_classification_for_test();
13272 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
13273 cache.insert(candidate.clone(), internal);
13274 internal
13275}
13276
13277#[cfg(any(test, feature = "test-support"))]
13278thread_local! {
13279 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13280}
13281
13282#[cfg(any(test, feature = "test-support"))]
13283fn note_cpp_global_field_internal_linkage_classification_for_test() {
13284 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13285 count.set(count.get() + 1);
13286 });
13287}
13288
13289#[cfg(any(test, feature = "test-support"))]
13290pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
13291 body: impl FnOnce() -> T,
13292) -> (T, usize) {
13293 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
13294 count.set(0);
13295 let result = body();
13296 let observed = count.get();
13297 count.set(0);
13298 (result, observed)
13299 })
13300}
13301
13302pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
13303 left.kind() == right.kind()
13304 && left.fq_name() == right.fq_name()
13305 && left.signature() == right.signature()
13306}
13307
13308pub fn cpp_global_field_has_internal_linkage(
13309 analyzer: &CppGraphSource<'_>,
13310 candidate: &CodeUnit,
13311) -> bool {
13312 if !candidate.is_field() || candidate.short_name().contains('.') {
13313 return false;
13314 }
13315 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
13316 return false;
13317 };
13318 match local_linkage {
13319 CppFieldLinkage::Internal => true,
13320 CppFieldLinkage::External => false,
13321 CppFieldLinkage::InternalUnlessExternalPeer => {
13322 !cpp_global_field_linkage_peers(analyzer, candidate)
13323 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, peer))
13324 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
13325 }
13326 }
13327}
13328
13329fn cpp_global_field_linkage_peers<'a>(
13330 analyzer: &CppGraphSource<'a>,
13331 candidate: &'a CodeUnit,
13332) -> impl Iterator<Item = &'a CodeUnit> + 'a {
13333 let fq_name = candidate.fq_name();
13337 analyzer
13338 .global_usage_definition_index()
13339 .fqn(&fq_name)
13340 .into_iter()
13341 .filter(move |peer| {
13342 if *peer == candidate {
13343 return false;
13344 }
13345 #[cfg(any(test, feature = "test-support"))]
13346 note_cpp_global_field_linkage_peer_inspection_for_test();
13347 same_logical_symbol(peer, candidate)
13348 })
13349}
13350
13351#[cfg(any(test, feature = "test-support"))]
13352thread_local! {
13353 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
13354}
13355
13356#[cfg(any(test, feature = "test-support"))]
13357fn note_cpp_global_field_linkage_peer_inspection_for_test() {
13358 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13359 count.set(count.get() + 1);
13360 });
13361}
13362
13363#[cfg(any(test, feature = "test-support"))]
13364pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
13365 body: impl FnOnce() -> T,
13366) -> (T, usize) {
13367 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
13368 count.set(0);
13369 let result = body();
13370 let observed = count.get();
13371 count.set(0);
13372 (result, observed)
13373 })
13374}
13375
13376fn cpp_global_field_declaration_linkage(
13377 analyzer: &CppGraphSource<'_>,
13378 candidate: &CodeUnit,
13379) -> Option<CppFieldLinkage> {
13380 if let Some(linkage) = analyzer.cpp_field_linkage(candidate) {
13381 return Some(linkage);
13382 }
13383 let cpp = analyzer.cpp?;
13384 if let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) {
13385 return cpp_global_field_declaration_linkage_in_tree(
13386 analyzer,
13387 candidate,
13388 prepared.source(),
13389 prepared.tree().root_node(),
13390 );
13391 }
13392 let source = analyzer.indexed_source(candidate.source())?;
13393 let mut parser = Parser::new();
13394 if parser
13395 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13396 .is_err()
13397 {
13398 return None;
13399 }
13400 let tree = parser.parse(&source, None)?;
13401 cpp_global_field_declaration_linkage_in_tree(analyzer, candidate, &source, tree.root_node())
13402}
13403
13404fn cpp_global_field_declaration_linkage_in_tree(
13405 analyzer: &CppGraphSource<'_>,
13406 candidate: &CodeUnit,
13407 source: &str,
13408 root: Node<'_>,
13409) -> Option<CppFieldLinkage> {
13410 analyzer.ranges(candidate).iter().find_map(|range| {
13411 node_for_exact_range(root, range)
13412 .and_then(enclosing_cpp_field_declaration)
13413 .map(|declaration| {
13414 cpp_field_declaration_linkage(declaration, source, &ParentIndex::unindexed())
13416 })
13417 })
13418}
13419
13420fn enclosing_cpp_field_declaration(mut node: Node<'_>) -> Option<Node<'_>> {
13421 loop {
13422 if matches!(node.kind(), "declaration" | "field_declaration") {
13423 return Some(node);
13424 }
13425 node = node.parent()?;
13426 }
13427}
13428
13429#[cfg(test)]
13430mod tests {
13431 use super::*;
13432
13433 #[test]
13434 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
13435 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
13436 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
13437 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
13438 assert!(indexed_namespace_path_is_recoverable(
13439 &["cache".to_string()],
13440 &indexed,
13441 1,
13442 ));
13443 }
13444
13445 #[test]
13446 fn sort_lookup_units_totally_orders_every_identity_field() {
13447 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
13448 let base = CodeUnit::with_signature(
13449 file.clone(),
13450 CodeUnitType::Function,
13451 "scope",
13452 "value",
13453 Some("()".to_string()),
13454 false,
13455 );
13456 let different_kind = CodeUnit::with_signature(
13457 file.clone(),
13458 CodeUnitType::Field,
13459 "scope",
13460 "value",
13461 Some("()".to_string()),
13462 false,
13463 );
13464 let synthetic = base.with_synthetic(true);
13465
13466 let interner = segment_interner();
13467 let mut member_fq = FqName::new();
13468 member_fq.push(interner.intern("scope", SegmentKind::Package));
13469 member_fq.push(interner.intern("value", SegmentKind::Member));
13470 let different_package_boundary = CodeUnit::from_fq(
13471 file.clone(),
13472 CodeUnitType::Function,
13473 member_fq,
13474 0,
13475 Some("()".to_string()),
13476 false,
13477 );
13478
13479 let mut unknown_fq = FqName::new();
13480 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
13481 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
13482 let different_segment_kind = CodeUnit::from_fq(
13483 file,
13484 CodeUnitType::Function,
13485 unknown_fq,
13486 1,
13487 Some("()".to_string()),
13488 false,
13489 );
13490
13491 let input = vec![
13492 base,
13493 different_kind,
13494 synthetic,
13495 different_package_boundary,
13496 different_segment_kind,
13497 ];
13498 let mut expected = input.clone();
13499 sort_lookup_units(&mut expected);
13500 assert!(expected.windows(2).all(|pair| {
13501 let mut ordered = pair.to_vec();
13502 sort_lookup_units(&mut ordered);
13503 ordered == pair && pair[0] != pair[1]
13504 }));
13505
13506 let mut reversed = input.clone();
13507 reversed.reverse();
13508 sort_lookup_units(&mut reversed);
13509 assert_eq!(reversed, expected);
13510
13511 let mut rotated = input;
13512 rotated.rotate_left(2);
13513 sort_lookup_units(&mut rotated);
13514 assert_eq!(rotated, expected);
13515 }
13516
13517 #[test]
13518 fn displaced_preprocessor_terminator_bounds_the_real_guard() {
13519 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";
13520 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
13521 let parse = |source: &str| {
13522 let mut parser = Parser::new();
13523 parser
13524 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13525 .expect("C++ grammar");
13526 parser.parse(source, None).expect("fixture tree")
13527 };
13528
13529 let tree = parse(damaged);
13530 let root = tree.root_node();
13531 let target = damaged.find("target").expect("target byte");
13532 let declaration = root
13533 .descendant_for_byte_range(target, target + "target".len())
13534 .and_then(|mut node| {
13535 loop {
13536 if node.kind() == "declaration" {
13537 break Some(node);
13538 }
13539 node = node.parent()?;
13540 }
13541 })
13542 .expect("declaration after the displaced terminator");
13543 let conditional = declaration
13544 .parent()
13545 .filter(|node| node.kind() == "preproc_ifdef")
13546 .expect("damaged inner conditional");
13547 let outer = conditional
13548 .parent()
13549 .filter(|node| node.kind() == "preproc_ifdef")
13550 .expect("ordinary outer include guard");
13551 let terminator = cpp_displaced_preprocessor_terminator(conditional)
13552 .expect("structured displaced #endif");
13553 assert_eq!(node_text(terminator, damaged), "#endif");
13554 assert!(terminator.end_byte() <= declaration.start_byte());
13555 assert!(!preprocessor_conditional_contains_descendant(
13556 conditional,
13557 declaration
13558 ));
13559 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
13560 assert!(preprocessor_conditional_contains_descendant(
13561 outer,
13562 declaration
13563 ));
13564
13565 let tree = parse(guarded);
13566 let conditional = tree
13567 .root_node()
13568 .named_child(0)
13569 .filter(|node| node.kind() == "preproc_ifdef")
13570 .expect("ordinary conditional");
13571 let declaration = conditional
13572 .named_children(&mut conditional.walk())
13573 .find(|node| node.kind() == "declaration")
13574 .expect("guarded declaration");
13575 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13576 assert!(preprocessor_conditional_contains_descendant(
13577 conditional,
13578 declaration
13579 ));
13580
13581 let damaged_alternative = format!(
13582 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
13583 "UNUSED(value)\n".repeat(64)
13584 );
13585 let tree = parse(&damaged_alternative);
13586 let conditional = tree
13587 .root_node()
13588 .named_child(0)
13589 .filter(|node| node.kind() == "preproc_ifdef")
13590 .expect("outer conditional with an alternative");
13591 assert!(conditional.has_error());
13592 assert!(conditional.child_by_field_name("alternative").is_some());
13593 assert!(
13594 conditional
13595 .child(conditional.child_count() - 1)
13596 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
13597 );
13598 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
13599
13600 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";
13601 let tree = parse(split_declaration);
13602 let root = tree.root_node();
13603 let conditional = root
13604 .named_children(&mut root.walk())
13605 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
13606 .expect("split declaration conditional");
13607 let target = split_declaration
13608 .find("static int target")
13609 .expect("target byte");
13610 let boundary =
13611 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
13612 assert!(boundary.end_byte <= target, "{boundary:?}");
13613 assert_eq!(boundary.end_line, 9, "{boundary:?}");
13614 let target_node = root
13615 .descendant_for_byte_range(target, target + "static".len())
13616 .expect("target node");
13617 assert!(!preprocessor_conditional_contains_descendant(
13618 conditional,
13619 target_node
13620 ));
13621 }
13622
13623 #[test]
13624 fn fragmented_reference_guard_is_recovered() {
13625 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";
13626 let mut parser = Parser::new();
13627 parser
13628 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13629 .expect("C++ grammar");
13630 let tree = parser.parse(source, None).expect("fixture tree");
13631 let start = source.rfind("helper").expect("reference byte");
13632 let node = tree
13633 .root_node()
13634 .descendant_for_byte_range(start, start + "helper".len())
13635 .expect("reference node");
13636 let mut expected = HashSet::default();
13637 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
13638 vec![
13639 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
13640 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
13641 ],
13642 )));
13643 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
13644 }
13645
13646 #[test]
13647 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
13648 let source = "#if HAVE_ARM_NEON\nstatic int target(void) { return 1; }\n#endif\n#if HAVE_ARM_NEON && ENABLE_FAST_PATH\nint use(void) { return target(); }\n#endif\n";
13649 let mut parser = Parser::new();
13650 parser
13651 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13652 .expect("C++ grammar");
13653 let tree = parser.parse(source, None).expect("fixture tree");
13654 let root = tree.root_node();
13655 let definition_start = source.find("target(void)").expect("definition");
13656 let reference_start = source.rfind("target()").expect("reference");
13657 let definition = root
13658 .descendant_for_byte_range(definition_start, definition_start + "target".len())
13659 .expect("definition node");
13660 let reference = root
13661 .descendant_for_byte_range(reference_start, reference_start + "target".len())
13662 .expect("reference node");
13663 let required =
13664 preprocessor_guard_environment(definition, source).expect("definition guard");
13665 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
13666 assert!(guard_requirements_hold_at_reference(
13667 &required,
13668 Some(&active)
13669 ));
13670 }
13671
13672 #[test]
13673 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
13674 let source = "g_autoptr(FuChunkArray) self = make_array();";
13675 let mut parser = Parser::new();
13676 parser
13677 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13678 .expect("C++ grammar");
13679 let tree = parser.parse(source, None).expect("fixture tree");
13680 let statement = tree.root_node().named_child(0).expect("statement");
13681 let binding =
13682 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
13683 assert_eq!(binding.name, "self");
13684 assert_eq!(binding.type_name, "FuChunkArray");
13685 assert_eq!(binding.pointer_depth, 1);
13686
13687 let near_miss = "holder(FuChunkArray) self = make_array();";
13688 let tree = parser.parse(near_miss, None).expect("near-miss tree");
13689 let statement = tree.root_node().named_child(0).expect("statement");
13690 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
13691 }
13692
13693 #[test]
13694 fn boolean_guard_normalization_proves_equivalence_and_implication() {
13695 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
13696 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
13697 let negated_windows_branch =
13698 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
13699 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
13700 assert_eq!(negated_windows_branch, portable);
13701
13702 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
13703 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
13704 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
13705 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
13706 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
13707 assert!(fallback_branch.implies(&fallback_declaration));
13708 assert!(!fallback_declaration.implies(&fallback_branch));
13709 }
13710
13711 #[test]
13712 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
13713 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";
13714 let mut parser = Parser::new();
13715 parser
13716 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13717 .expect("C++ grammar");
13718 let tree = parser.parse(source, None).expect("fixture tree");
13719 let root = tree.root_node();
13720 let call = |marker: &str| {
13721 let start = source.find(marker).expect("call marker");
13722 let mut node = root
13723 .descendant_for_byte_range(start, start + "helper".len())
13724 .expect("call name node");
13725 loop {
13726 if node.kind() == "call_expression" {
13727 break node;
13728 }
13729 node = node.parent().expect("call expression ancestor");
13730 }
13731 };
13732 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
13733 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
13734 let keyword_call = call("helper(NULL, template); /* bound */");
13735 let keyword_arguments = keyword_call
13736 .child_by_field_name("arguments")
13737 .expect("keyword argument list");
13738 assert_eq!(
13739 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
13740 1
13741 );
13742 assert_eq!(
13743 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
13744 0
13745 );
13746
13747 let unbound_call = call("helper(NULL, template); /* unbound */");
13748 let unbound_arguments = unbound_call
13749 .child_by_field_name("arguments")
13750 .expect("unbound argument list");
13751 assert_eq!(
13752 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
13753 0
13754 );
13755 }
13756
13757 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
13758 let mut parser = Parser::new();
13759 parser
13760 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13761 .expect("C++ grammar");
13762 let tree = parser.parse(source, None).expect("C++ fixture tree");
13763 let mut stack = vec![tree.root_node()];
13764 while let Some(node) = stack.pop() {
13765 if node.kind() == "enum_specifier" {
13766 return flattened_macro_namespace_components(node, source);
13767 }
13768 let mut cursor = node.walk();
13769 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13770 stack.extend(children.into_iter().rev());
13771 }
13772 None
13773 }
13774
13775 #[test]
13776 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
13777 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13778namespace detail
13779{
13780enum class value_t { null };
13781}
13782NLOHMANN_JSON_NAMESPACE_END
13783NLOHMANN_JSON_NAMESPACE_BEGIN
13784namespace next
13785{
13786struct next_type {};
13787}
13788NLOHMANN_JSON_NAMESPACE_END
13789"#;
13790 assert_eq!(
13791 first_enum_flattened_namespace(complete),
13792 Some(vec!["detail".to_string()])
13793 );
13794
13795 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
13796 assert_eq!(
13797 first_enum_flattened_namespace(&stale_end),
13798 Some(vec!["detail".to_string()]),
13799 "a stale end marker before the begin marker must not replace the intended namespace"
13800 );
13801
13802 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
13803namespace detail
13804{
13805enum class value_t { null };
13806}
13807struct next_type {};
13808"#;
13809 assert_eq!(first_enum_flattened_namespace(incomplete), None);
13810 }
13811}