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