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