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