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