1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3#[cfg(test)]
4use crate::declarations::cpp_displaced_preprocessor_terminator;
5use crate::declarations::{
6 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
7 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
8 enclosing_cpp_namespace_nodes, node_text, normalize_cpp_whitespace, recovered_class_body_at,
9 recovered_function_like_field_declarator, recovered_pyobject_head_field,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph::syntax::{
14 function_macro_replacement_span, normalize_macro_continuations,
15 object_macro_replacement_type_references, qualified_name_parts,
16};
17use crate::graph_support::CppSource;
18use crate::imports::{
19 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
20};
21use crate::source_context::cpp_guard_set_to_runtime;
22use crate::structural::is_cpp_preprocessor_conditional;
23pub use brokk_bifrost_core::analyzer::cpp_facts::{
24 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
25};
26use brokk_bifrost_core::analyzer::cpp_facts::{
27 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
28 CppStructuredAliasTarget as StructuredAliasTarget,
29};
30use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
31use brokk_bifrost_core::analyzer::model::{
32 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
33 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
34 SignatureMetadata, StructuredTypeName,
35};
36use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
37use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
38#[cfg(test)]
39use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
40use brokk_bifrost_core::analyzer::query_token::QueryToken;
41use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
42use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
43use brokk_bifrost_core::analyzer::tree_walk::{
44 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
45 named_children_iter, node_for_exact_range, push_named_children_reversed,
46};
47use brokk_bifrost_core::analyzer::usages::common::same_node;
48use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
49use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
50use brokk_bifrost_core::cancellation::CancellationToken;
51use brokk_bifrost_core::hash::{HashMap, HashSet};
52#[cfg(test)]
53use brokk_bifrost_core::text_utils::compute_line_starts;
54use std::borrow::Cow;
55#[cfg(any(test, feature = "test-support"))]
56use std::cell::Cell;
57use std::cell::OnceCell;
58use std::cmp::Ordering as CmpOrdering;
59use std::collections::BTreeSet;
60use std::hash::Hash;
61use std::sync::atomic::{AtomicUsize, Ordering};
62use std::sync::{Arc, Mutex, OnceLock, RwLock};
63use std::time::{Duration, Instant};
64use tree_sitter::{Node, Parser, Tree};
65
66const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
67const CALLABLE_METADATA_CACHE_CAP: usize = 512;
68const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
69
70#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
71struct PreprocessorContextNodeKey {
72 id: usize,
73 start_byte: usize,
74 end_byte: usize,
75}
76
77#[derive(Default)]
86pub(crate) struct PreprocessorContextCache {
87 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
88}
89
90impl PreprocessorContextCache {
91 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
92 let key = PreprocessorContextNodeKey {
93 id: conditional.id(),
94 start_byte: conditional.start_byte(),
95 end_byte: conditional.end_byte(),
96 };
97 if let Some(boundary) = self.displaced_boundaries.get(&key) {
98 return *boundary;
99 }
100 let boundary =
101 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
102 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
103 self.displaced_boundaries.insert(key, boundary);
104 }
105 boundary
106 }
107}
108
109#[cfg(any(test, feature = "test-support"))]
110thread_local! {
111 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
112 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
113}
114
115#[derive(Clone, Copy, PartialEq, Eq)]
116pub enum TargetKind {
117 Type,
118 Constructor,
119 FreeFunction,
120 Method,
121 GlobalField,
122 MemberField,
123 Macro,
124}
125
126pub enum LexicalTypeResolution {
127 Resolved {
128 unit: CodeUnit,
129 components: Vec<String>,
130 candidates: Vec<CodeUnit>,
131 },
132 Ambiguous,
133 Missing,
134}
135
136#[derive(Clone, Copy)]
137enum TypeCandidateResolution<'a> {
138 Canonical,
139 PreserveAlias,
140 PreserveTarget(&'a CodeUnit),
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152enum TypeCandidateFailure {
153 Ambiguous,
154 Unresolvable,
155}
156
157impl TypeCandidateFailure {
158 fn lexical_resolution(self) -> LexicalTypeResolution {
159 match self {
160 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
161 Self::Unresolvable => LexicalTypeResolution::Missing,
162 }
163 }
164}
165
166pub enum LexicalCallableValueResolution {
167 Type(CodeUnit),
168 FreeFunction(CodeUnit),
169 Ambiguous,
170 Missing,
171}
172
173pub enum UsingEnumMemberResolution {
174 Resolved { owner: CodeUnit, member: CodeUnit },
175 Ambiguous,
176 Missing,
177}
178
179pub enum NamespaceValueResolution {
180 Resolved,
181 Ambiguous,
182 Missing,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186pub enum OrdinaryMacroReferenceResolution {
187 Resolved(CodeUnit),
188 Ambiguous,
189 Missing,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
193pub enum RecoveredCReferenceRanges {
194 Complete(Vec<Range>),
195 LimitExceeded,
196}
197
198pub fn resolve_namespace_value(
199 analyzer: &CppGraphSource<'_>,
200 visibility: &VisibilityIndex<'_>,
201 file: &ProjectFile,
202 namespace: &str,
203 name: &str,
204 before_byte: usize,
205) -> NamespaceValueResolution {
206 let mut matches = Vec::new();
207 for candidate in visibility.visible_identifier_candidates(file, name) {
208 if type_owner_of(analyzer, candidate).is_some()
209 || candidate.package_name() != namespace
210 || (candidate.source() == file
211 && !analyzer
212 .ranges(candidate)
213 .iter()
214 .any(|range| range.start_byte < before_byte))
215 || matches
216 .iter()
217 .any(|existing| same_visible_symbol(existing, candidate))
218 {
219 continue;
220 }
221 matches.push(candidate.clone());
222 if matches.len() > 1 {
223 return NamespaceValueResolution::Ambiguous;
224 }
225 }
226 matches
227 .pop()
228 .map(|_| NamespaceValueResolution::Resolved)
229 .unwrap_or(NamespaceValueResolution::Missing)
230}
231
232pub(crate) struct ScopedUsingEnumOwners {
233 scopes: Vec<Vec<CodeUnit>>,
234}
235
236pub(crate) struct SemanticUsingEnumOwners {
241 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
242 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
243}
244
245pub(crate) enum SemanticUsingEnumMemberResolution {
246 Class(UsingEnumMemberResolution),
247 Namespace(UsingEnumMemberResolution),
248 Missing,
249}
250
251impl SemanticUsingEnumOwners {
252 pub(crate) fn new() -> Self {
253 Self {
254 class_imports: HashMap::default(),
255 namespace_imports: HashMap::default(),
256 }
257 }
258
259 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
260 let imports = self.class_imports.entry(class).or_default();
261 if !imports
262 .iter()
263 .any(|existing| same_visible_symbol(existing, &enum_owner))
264 {
265 imports.push(enum_owner);
266 }
267 }
268
269 pub fn import_namespace(
270 &mut self,
271 namespace: Vec<String>,
272 declaration_byte: usize,
273 enum_owner: CodeUnit,
274 ) {
275 let imports = self.namespace_imports.entry(namespace).or_default();
276 if !imports
277 .iter()
278 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
279 {
280 imports.push((declaration_byte, enum_owner));
281 }
282 }
283
284 pub fn resolve_member(
285 &self,
286 visibility: &VisibilityIndex<'_>,
287 file: &ProjectFile,
288 class: Option<&CodeUnit>,
289 namespace: &[String],
290 before_byte: usize,
291 name: &str,
292 ) -> SemanticUsingEnumMemberResolution {
293 if let Some(class) = class
294 && let Some((_, imports)) = self
295 .class_imports
296 .iter()
297 .find(|(owner, _)| same_visible_symbol(owner, class))
298 {
299 let resolution =
300 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
301 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
302 return SemanticUsingEnumMemberResolution::Class(resolution);
303 }
304 }
305 for prefix_len in (0..=namespace.len()).rev() {
306 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
307 continue;
308 };
309 let owners = imports
310 .iter()
311 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
312 .map(|(_, owner)| owner);
313 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
314 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
315 return SemanticUsingEnumMemberResolution::Namespace(resolution);
316 }
317 }
318 SemanticUsingEnumMemberResolution::Missing
319 }
320}
321
322fn resolve_using_enum_member_for_owners<'a>(
323 visibility: &VisibilityIndex<'_>,
324 file: &ProjectFile,
325 owners: impl IntoIterator<Item = &'a CodeUnit>,
326 name: &str,
327) -> UsingEnumMemberResolution {
328 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
329 for owner in owners {
330 for member in visibility.visible_members_for_owner_name(file, owner, name) {
331 if !member.is_field()
332 || matches.iter().any(|(existing_owner, existing_member)| {
333 same_visible_symbol(existing_owner, owner)
334 && same_visible_symbol(existing_member, member)
335 })
336 {
337 continue;
338 }
339 matches.push((owner.clone(), member.clone()));
340 }
341 }
342 match matches.len() {
343 0 => UsingEnumMemberResolution::Missing,
344 1 => {
345 let (owner, member) = matches.pop().expect("one using-enum match");
346 UsingEnumMemberResolution::Resolved { owner, member }
347 }
348 _ => UsingEnumMemberResolution::Ambiguous,
349 }
350}
351
352impl ScopedUsingEnumOwners {
353 pub(crate) fn new() -> Self {
354 Self {
355 scopes: vec![Vec::new()],
356 }
357 }
358
359 pub fn enter_scope(&mut self) {
360 self.scopes.push(Vec::new());
361 }
362
363 pub fn exit_scope(&mut self) {
364 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
365 }
366
367 pub fn import(&mut self, owner: CodeUnit) {
368 let scope = self
369 .scopes
370 .last_mut()
371 .expect("using-enum scope stack is never empty");
372 if !scope
373 .iter()
374 .any(|existing| same_visible_symbol(existing, &owner))
375 {
376 scope.push(owner);
377 }
378 }
379
380 pub fn resolve_member(
381 &self,
382 visibility: &VisibilityIndex<'_>,
383 file: &ProjectFile,
384 name: &str,
385 ) -> UsingEnumMemberResolution {
386 for scope in self.scopes.iter().rev() {
387 let resolution =
388 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
389 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
390 return resolution;
391 }
392 }
393 UsingEnumMemberResolution::Missing
394 }
395}
396
397#[derive(Clone)]
398pub struct TargetSpec {
399 pub target: CodeUnit,
400 pub kind: TargetKind,
401 pub owner: Option<CodeUnit>,
402 pub member_name: String,
403 pub callable_arity: Option<CallableArity>,
404 pub activated_callable_arities: Vec<ActivatedCallableArity>,
405 pub param_types: Option<Vec<String>>,
406 pub enum_owner_kind: EnumOwnerKind,
407 pub owner_is_forward_declaration: bool,
408 pub callable_has_definition_body: bool,
409}
410
411#[derive(Clone, Copy)]
412pub struct ActivatedCallableArity {
413 pub activation_byte: usize,
414 pub arity: CallableArity,
415}
416
417#[derive(Debug, PartialEq, Eq, Hash)]
418pub struct TypeScanKey {
419 target: LogicalSymbolKey,
420 member_name: String,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq, Hash)]
424struct LogicalSymbolKey {
425 kind: CodeUnitType,
426 fq_name: String,
427 signature: Option<String>,
428}
429
430struct ResolvedTypeOwner {
431 unit: CodeUnit,
432 is_forward_declaration: bool,
433}
434
435impl TargetSpec {
436 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
437 (self.kind == TargetKind::Type).then(|| TypeScanKey {
438 target: logical_symbol_key(&self.target),
439 member_name: self.member_name.clone(),
440 })
441 }
442
443 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
444 if target.is_class() {
445 return Some(Self::new(
446 target.clone(),
447 TargetKind::Type,
448 Some(target.clone()),
449 target.identifier().to_string(),
450 None,
451 None,
452 ));
453 }
454
455 if target.is_field() {
456 let owner = type_owner_of(analyzer, target);
462 let kind = if owner.is_some() {
463 TargetKind::MemberField
464 } else {
465 TargetKind::GlobalField
466 };
467 let enum_owner_kind = owner
468 .as_ref()
469 .map(|owner| classify_enum_owner(analyzer, owner))
470 .unwrap_or(EnumOwnerKind::NonEnum);
471 let mut spec = Self::new(
472 target.clone(),
473 kind,
474 owner,
475 target.identifier().to_string(),
476 None,
477 None,
478 );
479 spec.enum_owner_kind = enum_owner_kind;
480 return Some(spec);
481 }
482
483 if target.is_function() {
484 let owner_resolution = target_type_owner_resolution(analyzer, target);
487 let owner_is_forward_declaration = owner_resolution
488 .as_ref()
489 .is_some_and(|owner| owner.is_forward_declaration);
490 let owner = owner_resolution.map(|owner| owner.unit);
491 let kind = if owner.as_ref().is_some_and(|owner| {
492 target.identifier() == owner.identifier()
493 || analyzer
494 .cpp
495 .and_then(|cpp| cpp.template_metadata(owner))
496 .is_some_and(|metadata| metadata.primary_name == target.identifier())
497 }) {
498 TargetKind::Constructor
499 } else if owner.is_some() {
500 TargetKind::Method
501 } else {
502 TargetKind::FreeFunction
503 };
504 let mut spec = Self::new(
505 target.clone(),
506 kind,
507 owner,
508 target.identifier().to_string(),
509 cpp_callable_arity(analyzer, target),
510 cpp_callable_parameter_types(analyzer, target),
511 );
512 spec.owner_is_forward_declaration = owner_is_forward_declaration;
513 spec.callable_has_definition_body =
514 callable_target_has_definition_body(analyzer, target);
515 return Some(spec);
516 }
517
518 if target.is_macro() {
519 return Some(Self::new(
520 target.clone(),
521 TargetKind::Macro,
522 None,
523 target.identifier().to_string(),
524 None,
525 None,
526 ));
527 }
528
529 None
530 }
531
532 pub fn with_visible_callable_arities<'a>(
533 &'a self,
534 analyzer: &CppGraphSource<'_>,
535 cpp: &dyn CppSource,
536 visibility: &VisibilityIndex<'_>,
537 file: &ProjectFile,
538 prepared: &PreparedSyntaxTree,
539 ) -> Cow<'a, Self> {
540 let macro_parameter_arity =
541 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
542 let activated_callable_arities =
543 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
544 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
545 return Cow::Borrowed(self);
546 }
547 let mut effective = self.clone();
548 if let Some(macro_parameter_arity) = macro_parameter_arity {
549 effective.callable_arity = Some(macro_parameter_arity);
550 }
551 effective.activated_callable_arities = activated_callable_arities;
552 Cow::Owned(effective)
553 }
554
555 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
556 let base = self.callable_arity?;
557 Some(
558 self.activated_callable_arities
559 .iter()
560 .filter(|candidate| candidate.activation_byte <= byte)
561 .fold(base, |arity, candidate| {
562 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
563 }),
564 )
565 }
566
567 pub fn new(
568 target: CodeUnit,
569 kind: TargetKind,
570 owner: Option<CodeUnit>,
571 member_name: String,
572 callable_arity: Option<CallableArity>,
573 param_types: Option<Vec<String>>,
574 ) -> Self {
575 Self {
576 target,
577 kind,
578 owner,
579 member_name,
580 callable_arity,
581 activated_callable_arities: Vec::new(),
582 param_types,
583 enum_owner_kind: EnumOwnerKind::NonEnum,
584 owner_is_forward_declaration: false,
585 callable_has_definition_body: false,
586 }
587 }
588}
589
590fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
591 analyzer
592 .signature_metadata(target)
593 .iter()
594 .any(|metadata| !metadata.is_declaration_only())
595}
596
597fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
598 LogicalSymbolKey {
599 kind: unit.kind(),
600 fq_name: unit.fq_name(),
601 signature: unit.signature().map(str::to_string),
602 }
603}
604
605fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
606 let Some(facts) = analyzer.declaration_source_properties(owner) else {
607 return EnumOwnerKind::NonEnum;
608 };
609 let Some(first) = facts.first() else {
610 return EnumOwnerKind::NonEnum;
611 };
612 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
613 first.enum_kind
614 } else {
615 EnumOwnerKind::NonEnum
616 }
617}
618
619#[derive(Clone, PartialEq, Eq, Hash)]
620pub struct CppScanBinding {
621 pub unit: Option<CodeUnit>,
622 pub type_name: Option<String>,
623 pub indirection: i32,
624}
625
626impl CppScanBinding {
627 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
628 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
629 }
630
631 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
632 Self {
633 type_name: Some(type_name),
634 unit,
635 indirection,
636 }
637 }
638
639 pub fn as_arg_type(&self) -> Option<CppArgType> {
640 let name = self
641 .type_name
642 .clone()
643 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
644 Some(CppArgType {
645 name,
646 unit: self.unit.clone(),
647 indirection: self.indirection,
648 pointee_const: false,
649 })
650 }
651}
652
653type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
654pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
655pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
656type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
657type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
658type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
659type MacroLexicalTemplateCache =
660 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
661
662type MacroLocalBindingTemplateCache =
663 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
664type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
665type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
666type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
667
668struct StructuredIncludeFact {
669 start_byte: usize,
670 end_byte: usize,
671 path: String,
672}
673
674#[derive(Clone, Default)]
675pub struct MacroEnvironment {
676 bindings: HashMap<String, MacroBinding>,
677 known_undefined_names: HashSet<String>,
678 build_proven_defines: HashSet<String>,
683 unknown_names: bool,
684 applied_pragma_once_files: HashSet<ProjectFile>,
685 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
686}
687
688pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
697
698struct MacroEnvironmentCheckpoint {
700 frontier: usize,
702 environment: Arc<MacroEnvironment>,
703}
704
705struct MacroEnvironmentCheckpoints {
714 checkpoints: Vec<MacroEnvironmentCheckpoint>,
715}
716
717impl MacroEnvironmentCheckpoints {
718 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
720 let index = self
721 .checkpoints
722 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
723 assert!(
724 index > 0,
725 "a checkpoint vector starts at frontier zero, which precedes every request"
726 );
727 &self.checkpoints[index - 1]
728 }
729}
730
731fn clone_or_default_file_cell<T: Default + Clone>(
732 cells: &Mutex<HashMap<ProjectFile, T>>,
733 file: &ProjectFile,
734 poison_message: &str,
735) -> T {
736 cells
737 .lock()
738 .expect(poison_message)
739 .entry(file.clone())
740 .or_default()
741 .clone()
742}
743
744impl MacroEnvironment {
745 fn binding(&self, name: &str) -> Option<&MacroBinding> {
746 self.bindings.get(name)
747 }
748
749 fn may_bind(&self, name: &str) -> bool {
750 self.bindings.contains_key(name) || self.unknown_names
751 }
752
753 fn insert(&mut self, name: String, binding: MacroBinding) {
754 self.known_undefined_names.remove(&name);
755 self.bindings.insert(name, binding);
756 }
757
758 fn remove(&mut self, name: &str) {
759 self.bindings.remove(name);
760 self.known_undefined_names.insert(name.to_string());
761 }
762
763 fn remove_known_undefined(&mut self, name: &str) {
764 self.known_undefined_names.remove(name);
765 }
766
767 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
768 for binding in self.bindings.values_mut() {
769 *binding = MacroBinding::uncertain_from(binding, source, byte);
770 }
771 self.known_undefined_names.clear();
772 self.build_proven_defines.clear();
777 self.unknown_names = true;
778 }
779
780 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
781 guards.iter().all(|guard| self.guard_may_hold(guard))
782 }
783
784 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
785 let Some(expression) = guard.as_boolean_expression() else {
786 return true;
787 };
788 self.boolean_guard_may_hold(&expression)
789 }
790
791 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
792 match expression {
793 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
794 BooleanGuardExpression::Undefined(name) => {
795 self.bindings
796 .get(name)
797 .is_none_or(|binding| !binding.is_exact())
798 && (!self.build_proven_defines.contains(name)
799 || self.known_undefined_names.contains(name))
800 }
801 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
802 BooleanGuardExpression::Opaque(_)
803 | BooleanGuardExpression::NegatedOpaque(_)
804 | BooleanGuardExpression::Constant(true) => true,
805 BooleanGuardExpression::Constant(false) => false,
806 BooleanGuardExpression::All(expressions) => expressions
807 .iter()
808 .all(|expression| self.boolean_guard_may_hold(expression)),
809 BooleanGuardExpression::Any(expressions) => expressions
810 .iter()
811 .any(|expression| self.boolean_guard_may_hold(expression)),
812 }
813 }
814}
815
816fn any_visible_source_file_matches(
817 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
818 file: &ProjectFile,
819 mut predicate: impl FnMut(&ProjectFile) -> bool,
820) -> bool {
821 match visible_source_files_by_root.get(file) {
822 Some(files) => files.iter().any(predicate),
823 None => predicate(file),
824 }
825}
826
827#[derive(Clone)]
828pub enum EffectiveUsingTarget {
829 Ordinary {
830 name: String,
831 target_components: Vec<String>,
832 global: bool,
833 },
834 Namespace {
835 namespace_components: Vec<String>,
836 global: bool,
837 },
838}
839
840#[derive(Clone)]
841pub struct OrdinaryTypeImport {
842 pub target: EffectiveUsingTarget,
843 pub source: ProjectFile,
844 pub declaration_byte: usize,
845 pub scope_start: usize,
846 pub scope_end: usize,
847 pub scope_depth: usize,
848 pub block_scope: bool,
849 pub lexical_depth: usize,
850 pub declaration_namespace: Vec<String>,
851 pub namespace_scope: Option<Vec<String>>,
852 pub resolved_target_components: Option<Vec<String>>,
853 pub required_guards: HashSet<PreprocessorGuard>,
854}
855
856#[derive(Clone)]
857pub struct ConditionalIncludeProjection {
858 pub activation_byte: usize,
859 pub required_guards: HashSet<PreprocessorGuard>,
860 pub partial_guards: HashSet<PreprocessorGuard>,
864}
865
866#[derive(Default)]
867pub struct SourceUsingIndex {
868 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
869 pub directives: Vec<OrdinaryTypeImport>,
870}
871
872#[derive(Default)]
873pub struct ProjectUsingIndex {
874 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
875 pub directives: Vec<OrdinaryTypeImport>,
876}
877
878type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
879
880pub struct EffectiveUsingIndex {
881 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
882}
883
884impl EffectiveUsingIndex {
885 fn new(_root: ProjectFile) -> Self {
886 Self {
887 projected_by_name: Mutex::new(HashMap::default()),
888 }
889 }
890
891 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
892 self.projected_by_name
893 .lock()
894 .expect("C++ effective-using projection cache poisoned")
895 .entry(name.to_string())
896 .or_default()
897 .clone()
898 }
899}
900
901pub enum OrdinaryTypeImportResolution {
902 Resolved {
903 target: CodeUnit,
904 target_components: Vec<String>,
905 lexical_depth: usize,
906 is_direct: bool,
907 },
908 Ambiguous {
909 lexical_depth: usize,
910 },
911 Missing,
912}
913
914type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
915type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
916type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
917type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
918type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
919type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
920type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
921type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
922
923struct ExtractedComparable {
928 shapes: Vec<CppComparableSlot>,
929 suffix: String,
930}
931
932const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
936
937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
959enum IncludePathAdmission {
960 Proven,
961 Compatible,
962}
963
964impl IncludePathAdmission {
965 fn admits(
966 self,
967 required: &HashSet<PreprocessorGuard>,
968 partial: &HashSet<PreprocessorGuard>,
969 reference_guards: Option<&HashSet<PreprocessorGuard>>,
970 ) -> bool {
971 match self {
972 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
973 Self::Compatible => {
974 guard_requirements_hold_at_reference(partial, reference_guards)
975 && guards_compatible_at_reference(required, reference_guards)
976 }
977 }
978 }
979}
980
981pub struct VisibilityIndex<'a> {
992 cpp: &'a dyn CppSource,
993 token: QueryToken<'a>,
998 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
999 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1000 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1001 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1002 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1003 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1004 parser_alias_target_matches:
1005 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1006 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1007 project_using_index: OnceLock<ProjectUsingIndex>,
1008 callable_reference_specs:
1009 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1010 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1011 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1012 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1013 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1014 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1015 #[cfg(any(test, feature = "test-support"))]
1016 conditional_include_projection_index_build_count: AtomicUsize,
1017 #[cfg(any(test, feature = "test-support"))]
1018 conditional_include_projection_state_count: AtomicUsize,
1019 #[cfg(any(test, feature = "test-support"))]
1020 conditional_include_target_state_count: AtomicUsize,
1021 #[cfg(any(test, feature = "test-support"))]
1022 include_activation_build_count: AtomicUsize,
1023 #[cfg(any(test, feature = "test-support"))]
1024 using_donor_activation_count: AtomicUsize,
1025 #[cfg(any(test, feature = "test-support"))]
1026 using_namespace_lookup_count: AtomicUsize,
1027 #[cfg(any(test, feature = "test-support"))]
1028 using_name_candidate_inspection_count: AtomicUsize,
1029 #[cfg(any(test, feature = "test-support"))]
1030 callable_reference_spec_build_count: AtomicUsize,
1031 #[cfg(any(test, feature = "test-support"))]
1032 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1033 #[cfg(any(test, feature = "test-support"))]
1034 visible_parser_alias_name_set_build_count: AtomicUsize,
1035 parser_alias_fallback_calls: AtomicUsize,
1036 parser_alias_fallback_files: AtomicUsize,
1037 parser_alias_source_parses: AtomicUsize,
1038 parser_alias_fallback_elapsed_micros: AtomicUsize,
1039 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1040 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1041 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1042 callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1048 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1049 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1050 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1051 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1052 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1053 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1054 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1055 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1056 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1057 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1064 macro_replacements: Mutex<MacroReplacementCache>,
1065 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1066 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1067 macro_lexical_indexes:
1068 Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1069 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1070 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1071 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1072 #[cfg(any(test, feature = "test-support"))]
1073 pub macro_replacement_parse_count: AtomicUsize,
1074 #[cfg(any(test, feature = "test-support"))]
1075 pub macro_event_application_count: AtomicUsize,
1076 #[cfg(any(test, feature = "test-support"))]
1079 pub macro_environment_checkpoint_build_count: AtomicUsize,
1080 #[cfg(any(test, feature = "test-support"))]
1083 pub macro_environment_copy_count: AtomicUsize,
1084 #[cfg(any(test, feature = "test-support"))]
1085 pub macro_environment_request_count: AtomicUsize,
1086 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1087 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1088 #[cfg(any(test, feature = "test-support"))]
1089 qualified_candidate_inspections: AtomicUsize,
1090 #[cfg(any(test, feature = "test-support"))]
1091 target_preserving_type_resolution_count: AtomicUsize,
1092 #[cfg(any(test, feature = "test-support"))]
1093 visibility_identifier_lookup_count: usize,
1094 #[cfg(any(test, feature = "test-support"))]
1095 visibility_identifier_batch_count: usize,
1096}
1097
1098impl Drop for VisibilityIndex<'_> {
1099 fn drop(&mut self) {
1100 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1101 return;
1102 }
1103 #[cfg(any(test, feature = "test-support"))]
1104 eprintln!(
1105 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1106 self.macro_environment_request_count.load(Ordering::Relaxed),
1107 self.macro_environment_copy_count.load(Ordering::Relaxed),
1108 self.macro_environment_checkpoint_build_count
1109 .load(Ordering::Relaxed),
1110 self.macro_event_application_count.load(Ordering::Relaxed),
1111 );
1112 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1113 if calls == 0 {
1114 return;
1115 }
1116 eprintln!(
1117 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1118 calls,
1119 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1120 self.parser_alias_source_parses.load(Ordering::Relaxed),
1121 self.parser_alias_fallback_elapsed_micros
1122 .load(Ordering::Relaxed)
1123 / 1_000,
1124 );
1125 }
1126}
1127
1128#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1129pub enum PreprocessorGuard {
1130 Defined(String),
1131 Undefined(String),
1132 Boolean(BooleanGuardExpression),
1133 Expression(String),
1134 NegatedExpression(String),
1135 Constant(bool),
1136}
1137
1138#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1139pub enum BooleanGuardExpression {
1140 Defined(String),
1141 Undefined(String),
1142 Truthy(String),
1143 Falsy(String),
1144 Opaque(String),
1145 NegatedOpaque(String),
1146 All(Vec<BooleanGuardExpression>),
1147 Any(Vec<BooleanGuardExpression>),
1148 Constant(bool),
1149}
1150
1151impl BooleanGuardExpression {
1152 fn negated(&self) -> Self {
1153 match self {
1154 Self::Defined(name) => Self::Undefined(name.clone()),
1155 Self::Undefined(name) => Self::Defined(name.clone()),
1156 Self::Truthy(name) => Self::Falsy(name.clone()),
1157 Self::Falsy(name) => Self::Truthy(name.clone()),
1158 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1159 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1160 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1161 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1162 Self::Constant(value) => Self::Constant(!value),
1163 }
1164 }
1165
1166 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1167 Self::normalized(expressions, true)
1168 }
1169
1170 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1171 Self::normalized(expressions, false)
1172 }
1173
1174 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1175 let mut normalized = Vec::new();
1176 for expression in expressions {
1177 match expression {
1178 Self::All(nested) if conjunction => normalized.extend(nested),
1179 Self::Any(nested) if !conjunction => normalized.extend(nested),
1180 Self::Constant(value) if value == conjunction => {}
1181 Self::Constant(value) => return Self::Constant(value),
1182 expression => normalized.push(expression),
1183 }
1184 }
1185 normalized.sort_unstable();
1186 normalized.dedup();
1187 match normalized.len() {
1188 0 => Self::Constant(conjunction),
1189 1 => normalized.pop().expect("one Boolean guard expression"),
1190 _ if conjunction => Self::All(normalized),
1191 _ => Self::Any(normalized),
1192 }
1193 }
1194
1195 fn implies(&self, required: &Self) -> bool {
1196 if self == required
1197 || matches!(self, Self::Constant(false))
1198 || matches!(required, Self::Constant(true))
1199 {
1200 return true;
1201 }
1202 if matches!(
1203 (self, required),
1204 (Self::Truthy(active), Self::Defined(required))
1205 | (Self::Undefined(active), Self::Falsy(required))
1206 if active == required
1207 ) {
1208 return true;
1209 }
1210 match self {
1211 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1212 Self::All(active) => match required {
1213 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1214 _ => active.iter().any(|expression| expression.implies(required)),
1215 },
1216 _ => match required {
1217 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1218 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1219 _ => false,
1220 },
1221 }
1222 }
1223
1224 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1225 match self {
1226 Self::Defined(name)
1227 | Self::Undefined(name)
1228 | Self::Truthy(name)
1229 | Self::Falsy(name) => name == macro_name,
1230 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1233 Self::All(expressions) | Self::Any(expressions) => expressions
1234 .iter()
1235 .any(|expression| expression.may_depend_on_macro(macro_name)),
1236 Self::Constant(_) => false,
1237 }
1238 }
1239
1240 pub fn heap_size(&self) -> usize {
1241 match self {
1242 Self::Defined(value)
1243 | Self::Undefined(value)
1244 | Self::Truthy(value)
1245 | Self::Falsy(value)
1246 | Self::Opaque(value)
1247 | Self::NegatedOpaque(value) => value.len(),
1248 Self::All(expressions) | Self::Any(expressions) => {
1249 expressions
1250 .iter()
1251 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1252 size.saturating_add(std::mem::size_of::<Self>())
1253 .saturating_add(expression.heap_size())
1254 })
1255 }
1256 Self::Constant(_) => 0,
1257 }
1258 }
1259}
1260
1261impl PreprocessorGuard {
1262 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1263 match self {
1264 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1265 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1266 Self::Boolean(expression) => Some(expression.clone()),
1267 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1268 Self::Expression(_) | Self::NegatedExpression(_) => None,
1269 }
1270 }
1271
1272 fn negated(&self) -> Self {
1273 match self {
1274 Self::Defined(name) => Self::Undefined(name.clone()),
1275 Self::Undefined(name) => Self::Defined(name.clone()),
1276 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1277 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1278 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1279 Self::Constant(value) => Self::Constant(!value),
1280 }
1281 }
1282
1283 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1284 match self {
1285 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1286 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1287 Self::Expression(_) | Self::NegatedExpression(_) => true,
1290 Self::Constant(_) => false,
1291 }
1292 }
1293}
1294
1295#[derive(Clone, PartialEq, Eq)]
1296pub enum MacroDefinition {
1297 Object {
1298 replacement: String,
1299 },
1300 Function {
1301 parameters: Vec<String>,
1302 replacement: String,
1303 },
1304 VariadicFunction {
1305 parameters: Vec<String>,
1306 replacement: String,
1307 },
1308 Unsupported,
1309}
1310
1311#[derive(Clone, Debug, PartialEq, Eq)]
1312pub enum MacroIncludeProtection {
1313 MacroGuard(String),
1314 PragmaOnce,
1315 None,
1316}
1317
1318enum ParsedMacroReplacement {
1319 Parsed { source: String, tree: Tree },
1320 Unsupported,
1321}
1322
1323const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1327
1328pub struct ParsedReplacementBody {
1337 pub source: String,
1338 pub tree: Tree,
1339 pub body_offset: usize,
1340 pub parameters: Vec<String>,
1341 original_offsets: Box<[usize]>,
1346}
1347
1348impl ParsedReplacementBody {
1349 pub fn statements(&self) -> Option<Node<'_>> {
1351 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1352 .child_by_field_name("body")
1353 }
1354
1355 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1361 assert!(
1362 node.start_byte() >= self.body_offset,
1363 "synthetic sentinel node cannot be mapped to a macro replacement"
1364 );
1365 assert!(
1366 node.end_byte() >= self.body_offset,
1367 "synthetic sentinel node cannot be mapped to a macro replacement"
1368 );
1369 let start_offset = node.start_byte() - self.body_offset;
1370 let end_offset = node.end_byte() - self.body_offset;
1371 assert!(start_offset <= end_offset);
1372 let start_origin = *self
1373 .original_offsets
1374 .get(start_offset)
1375 .expect("replacement node start must have a source mapping");
1376 let end_origin = *self
1377 .original_offsets
1378 .get(end_offset)
1379 .expect("replacement node end must have a source mapping");
1380 assert!(start_origin <= end_origin);
1381 let start = replacement_start + start_origin;
1382 let end = replacement_start + end_origin;
1383 start..end
1384 }
1385
1386 fn expands_variadic_arguments(&self) -> bool {
1393 let mut stack = vec![self.tree.root_node()];
1394 while let Some(node) = stack.pop() {
1395 if matches!(
1396 node.kind(),
1397 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1398 ) && node_text(node, &self.source) == "__VA_ARGS__"
1399 {
1400 return true;
1401 }
1402 push_named_children_reversed(node, &mut stack);
1403 }
1404 false
1405 }
1406}
1407
1408pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1410 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1411 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1412 if compact.starts_with("0x") || compact.starts_with("0X") {
1413 (16, 2, |ch| ch.is_ascii_hexdigit())
1414 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1415 (2, 2, |ch| matches!(ch, '0' | '1'))
1416 } else if compact.starts_with('0') && compact.len() > 1 {
1417 (8, 0, |ch| matches!(ch, '0'..='7'))
1418 } else {
1419 (10, 0, |ch| ch.is_ascii_digit())
1420 };
1421 let digit_len = compact[digits_start..]
1422 .chars()
1423 .take_while(|ch| digit_matches(*ch))
1424 .map(char::len_utf8)
1425 .sum::<usize>();
1426 if digit_len == 0 {
1427 return None;
1428 }
1429 let digits_end = digits_start + digit_len;
1430 if !compact[digits_end..]
1431 .chars()
1432 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1433 {
1434 return None;
1435 }
1436 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1437}
1438
1439#[derive(Clone)]
1440enum MacroLocalBindingTypeTemplate {
1441 Parameter(usize),
1442 Fixed(String),
1443}
1444
1445#[derive(Clone)]
1446struct MacroLocalBindingTemplate {
1447 name: String,
1448 declared_type: MacroLocalBindingTypeTemplate,
1449 pointer_depth: i32,
1450}
1451
1452pub struct MacroLocalBinding<'tree> {
1460 pub name: String,
1461 pub type_name: String,
1462 pub type_node: Option<Node<'tree>>,
1463 pub pointer_depth: i32,
1464 pub proven_unit: Option<CodeUnit>,
1465}
1466
1467#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1473pub enum MacroLexicalBindingKind {
1474 Parameter,
1475 Local,
1476}
1477
1478#[derive(Clone, Debug, PartialEq, Eq)]
1479pub struct MacroLexicalBinding {
1480 pub definition: ProjectFile,
1481 pub kind: MacroLexicalBindingKind,
1482 pub name: String,
1483 pub name_range: std::ops::Range<usize>,
1484 pub declaration_range: std::ops::Range<usize>,
1485}
1486
1487#[derive(Clone, Debug, Default, PartialEq, Eq)]
1491pub struct MacroLexicalReferences {
1492 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1493 pub truncated: bool,
1494 pub cancelled: bool,
1495}
1496
1497fn macro_replacement_type_parameters(
1498 body: &ParsedReplacementBody,
1499 parameters: &[String],
1500) -> Option<Vec<usize>> {
1501 let mut found = Vec::new();
1502 let mut stack = vec![body.tree.root_node()];
1503 while let Some(node) = stack.pop() {
1504 let type_position = node.kind() == "type_identifier"
1505 || (node.kind() == "identifier"
1506 && node.parent().is_some_and(|parent| {
1507 parent.kind() == "type_descriptor"
1508 && parent.child_by_field_name("type") == Some(node)
1509 }));
1510 let offsetof_type_position = node.kind() == "identifier"
1511 && node
1512 .parent()
1513 .filter(|parent| parent.kind() == "argument_list")
1514 .and_then(|arguments| arguments.parent())
1515 .is_some_and(|call| {
1516 call.kind() == "call_expression"
1517 && call
1518 .child_by_field_name("function")
1519 .is_some_and(|function| {
1520 function.kind() == "identifier"
1521 && node_text(function, &body.source) == "offsetof"
1522 })
1523 && call
1524 .child_by_field_name("arguments")
1525 .is_some_and(|arguments| {
1526 argument_children(arguments).next() == Some(node)
1527 })
1528 });
1529 if (type_position || offsetof_type_position)
1530 && let Some(index) = parameters
1531 .iter()
1532 .position(|parameter| parameter == node_text(node, &body.source))
1533 && !found.contains(&index)
1534 {
1535 found.push(index);
1536 }
1537 push_named_children_reversed(node, &mut stack);
1538 }
1539 (!found.is_empty()).then_some(found)
1540}
1541
1542fn macro_replacement_type_parameter(
1543 body: &ParsedReplacementBody,
1544 parameters: &[String],
1545) -> Option<usize> {
1546 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1547 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1548}
1549
1550pub(crate) fn macro_type_argument_node<'tree>(
1551 node: Node<'tree>,
1552 source: &str,
1553) -> Option<Node<'tree>> {
1554 match node.kind() {
1555 "type_descriptor" => {
1556 let type_child = node
1557 .child_by_field_name("type")
1558 .or_else(|| first_type_child(node))?;
1559 for index in (0..node.named_child_count()).rev() {
1560 let child = node.named_child(index)?;
1561 if child != type_child
1562 && matches!(
1563 child.kind(),
1564 "identifier"
1565 | "type_identifier"
1566 | "qualified_identifier"
1567 | "scoped_type_identifier"
1568 )
1569 {
1570 return Some(child);
1571 }
1572 }
1573 macro_type_argument_node(type_child, source)
1574 }
1575 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1576 node.child_by_field_name("name")
1577 }
1578 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1579 .next_named_sibling()
1580 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1581 .and_then(|error| error.named_child(0))
1582 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1583 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1584 }
1585}
1586
1587fn c_function_macro_argument<'tree>(
1588 node: Node<'tree>,
1589) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1590 let mut current = node;
1591 while let Some(parent) = current.parent() {
1592 if parent.kind() == "argument_list" {
1593 let call = parent.parent().filter(|call| {
1594 call.kind() == "call_expression"
1595 && call.child_by_field_name("arguments") == Some(parent)
1596 && call
1597 .child_by_field_name("function")
1598 .is_some_and(|function| {
1599 function.kind() == "identifier"
1600 && !node_range_contains(function, current)
1601 })
1602 })?;
1603 let mut actuals = argument_children(parent).enumerate();
1604 let (index, argument) = actuals.find(|(_, argument)| {
1605 node_range_contains(*argument, node)
1606 && (argument.start_byte() == current.start_byte()
1607 || argument.end_byte() == current.end_byte())
1608 })?;
1609 return Some((call, index, argument));
1610 }
1611 current = parent;
1612 }
1613 None
1614}
1615
1616fn recognized_c_macro_declarator_binding<'tree>(
1621 statement: Node<'tree>,
1622 source: &str,
1623) -> Option<MacroLocalBinding<'tree>> {
1624 let assignment = match statement.kind() {
1625 "assignment_expression" => statement,
1626 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1627 _ => return None,
1628 };
1629 if assignment.kind() != "assignment_expression" {
1630 return None;
1631 }
1632 let call = assignment.child_by_field_name("left")?;
1633 if call.kind() != "call_expression" {
1634 return None;
1635 }
1636 let function = call.child_by_field_name("function")?;
1637 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1638 return None;
1639 }
1640 let arguments = call.child_by_field_name("arguments")?;
1641 let mut actuals = argument_children(arguments);
1642 let type_node = actuals.next()?;
1643 if actuals.next().is_some()
1644 || !matches!(
1645 type_node.kind(),
1646 "identifier"
1647 | "type_identifier"
1648 | "qualified_identifier"
1649 | "scoped_type_identifier"
1650 | "template_type"
1651 )
1652 {
1653 return None;
1654 }
1655 let name_node = (0..assignment.named_child_count())
1656 .filter_map(|index| assignment.named_child(index))
1657 .filter(|child| child.kind() == "ERROR")
1658 .filter_map(|error| {
1659 (error.named_child_count() == 1)
1660 .then(|| error.named_child(0))
1661 .flatten()
1662 })
1663 .find(|node| node.kind() == "identifier")?;
1664 let name = node_text(name_node, source).trim();
1665 let type_name = node_text(type_node, source).trim();
1666 if name.is_empty() || type_name.is_empty() {
1667 return None;
1668 }
1669 Some(MacroLocalBinding {
1670 name: name.to_string(),
1671 type_name: type_name.to_string(),
1672 type_node: Some(type_node),
1673 pointer_depth: 1,
1674 proven_unit: None,
1675 })
1676}
1677
1678#[derive(Clone, PartialEq, Eq)]
1679pub struct MacroBinding {
1680 source: ProjectFile,
1681 declaration_byte: usize,
1682 definition: MacroDefinition,
1683 exact: bool,
1684}
1685
1686impl MacroBinding {
1687 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1688 Self {
1689 source: source.clone(),
1690 declaration_byte,
1691 definition: MacroDefinition::Unsupported,
1692 exact: false,
1693 }
1694 }
1695
1696 fn is_exact(&self) -> bool {
1697 self.exact
1698 }
1699
1700 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1701 Self {
1702 source: source.clone(),
1703 declaration_byte,
1704 definition: current.definition.clone(),
1705 exact: false,
1706 }
1707 }
1708}
1709
1710type OwningPreprocessorConditionals = Box<[usize]>;
1722
1723#[derive(Clone)]
1724pub enum MacroEvent {
1725 Define {
1726 name: String,
1727 binding: MacroBinding,
1728 byte: usize,
1729 conditionals: OwningPreprocessorConditionals,
1730 },
1731 Undef {
1732 name: String,
1733 byte: usize,
1734 conditionals: OwningPreprocessorConditionals,
1735 },
1736 Include {
1737 targets: Vec<ProjectFile>,
1738 byte: usize,
1739 conditionals: OwningPreprocessorConditionals,
1740 },
1741 Invalidate {
1742 byte: usize,
1743 },
1744}
1745
1746impl MacroEvent {
1747 pub fn byte(&self) -> usize {
1748 match self {
1749 Self::Define { byte, .. }
1750 | Self::Undef { byte, .. }
1751 | Self::Include { byte, .. }
1752 | Self::Invalidate { byte } => *byte,
1753 }
1754 }
1755}
1756
1757#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1758pub enum CallArityEvidence {
1759 Exact(usize),
1760 Unknown,
1761}
1762
1763impl CallArityEvidence {
1764 pub fn exact(self) -> Option<usize> {
1765 match self {
1766 Self::Exact(arity) => Some(arity),
1767 Self::Unknown => None,
1768 }
1769 }
1770
1771 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1772 self.exact().map(|arity| expected.accepts(arity))
1773 }
1774}
1775
1776struct CppAlias {
1777 name: String,
1778 target: String,
1779 namespace: Option<String>,
1780}
1781
1782type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1783
1784#[derive(Debug, Clone, PartialEq, Eq)]
1788pub enum CppTemplateResolutionError {
1789 AliasCycle { alias: CodeUnit },
1791 ArgumentBinding,
1793 Substitution,
1795 PrimarySelection,
1798 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1801}
1802
1803fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1806 let mut distinct: Vec<CodeUnit> = Vec::new();
1807 for unit in units {
1808 if !distinct
1809 .iter()
1810 .any(|existing| same_visible_symbol(existing, unit))
1811 {
1812 distinct.push(unit.clone());
1813 }
1814 }
1815 distinct
1816}
1817
1818pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1821 visibility: Factory,
1822 file: &ProjectFile,
1823 root: Node<'_>,
1824 source: &str,
1825 max_references: usize,
1826 cancelled: impl FnMut() -> bool,
1827) -> MacroLexicalReferences
1828where
1829 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1830{
1831 crate::graph::macro_lexical::all_references(
1832 visibility,
1833 file,
1834 root,
1835 source,
1836 max_references,
1837 cancelled,
1838 )
1839}
1840
1841impl<'a> VisibilityIndex<'a> {
1842 pub fn cpp(&self) -> &'a dyn CppSource {
1843 self.cpp
1844 }
1845
1846 pub fn token(&self) -> QueryToken<'a> {
1848 self.token
1849 }
1850
1851 pub(crate) fn macro_lexical_index(
1855 &self,
1856 file: &ProjectFile,
1857 root: Node<'_>,
1858 source: &str,
1859 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1860 if let Some(index) = self
1861 .macro_lexical_indexes
1862 .lock()
1863 .expect("macro lexical index cache poisoned")
1864 .get(file)
1865 .cloned()
1866 {
1867 debug_assert_eq!(
1868 index.source_len,
1869 source.len(),
1870 "macro lexical index for {file:?} was built from a different source"
1871 );
1872 return index;
1873 }
1874 let index = Arc::new(crate::graph::macro_lexical::build_index(
1875 self, file, root, source,
1876 ));
1877 let mut cache = self
1878 .macro_lexical_indexes
1879 .lock()
1880 .expect("macro lexical index cache poisoned");
1881 if let Some(existing) = cache.get(file).cloned() {
1882 return existing;
1883 }
1884 if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1885 cache.insert(file.clone(), Arc::clone(&index));
1886 }
1887 index
1888 }
1889
1890 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1896 if let Some(metadata) = self
1897 .callable_metadata
1898 .lock()
1899 .expect("C++ callable metadata cache poisoned")
1900 .get(unit)
1901 .cloned()
1902 {
1903 return metadata;
1904 }
1905 let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1906 let mut cache = self
1907 .callable_metadata
1908 .lock()
1909 .expect("C++ callable metadata cache poisoned");
1910 if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1911 cache.insert(unit.clone(), Arc::clone(&metadata));
1912 }
1913 metadata
1914 }
1915
1916 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
1918 crate::identity::cpp_callable_linkage_from_metadata(
1919 self.callable_metadata(unit).iter().cloned(),
1920 )
1921 }
1922
1923 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
1925 brokk_bifrost_core::analyzer::model::first_callable_arity(
1926 self.callable_metadata(unit).iter().cloned(),
1927 )
1928 }
1929
1930 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
1935 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
1936 self.callable_metadata(unit).iter().cloned(),
1937 )
1938 }
1939
1940 pub fn has_unresolved_include_visible_before(
1948 &self,
1949 file: &ProjectFile,
1950 before_byte: usize,
1951 ) -> bool {
1952 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1953 return false;
1954 };
1955 let facts = self.structured_include_facts(file, prepared.as_ref());
1956 has_unresolved_include_visible_before_in_prepared(
1957 file,
1958 prepared.as_ref(),
1959 self.cpp.include_target_index(),
1960 &facts,
1961 before_byte,
1962 )
1963 }
1964
1965 fn structured_include_facts(
1966 &self,
1967 file: &ProjectFile,
1968 prepared: &PreparedSyntaxTree,
1969 ) -> Arc<[StructuredIncludeFact]> {
1970 let cell = self
1971 .structured_include_fact_cells
1972 .lock()
1973 .expect("C++ structured include-fact cache poisoned")
1974 .entry(file.clone())
1975 .or_default()
1976 .clone();
1977 Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
1978 }
1979
1980 #[cfg(any(test, feature = "test-support"))]
1988 pub fn from_visible_files_for_test(
1989 cpp: &'a dyn CppSource,
1990 token: QueryToken<'a>,
1991 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1992 ) -> Self {
1993 let visible_source_files_by_root = visible_by_file
1994 .iter()
1995 .map(|(file, visible)| {
1996 (
1997 file.clone(),
1998 visible
1999 .iter()
2000 .map(|unit| unit.source().clone())
2001 .chain(std::iter::once(file.clone()))
2002 .collect(),
2003 )
2004 })
2005 .collect();
2006 let mut global_field_internal_linkage = HashMap::default();
2007 Self {
2008 cpp,
2009 token,
2010 visible_by_identifier: build_visible_identifier_index(
2011 &CppGraphSource::from_source(cpp, token),
2012 &visible_by_file,
2013 &visible_source_files_by_root,
2014 &mut global_field_internal_linkage,
2015 ),
2016 global_field_internal_linkage,
2017 visible_by_file,
2018 visible_source_files_by_root,
2019 alias_cells: Mutex::new(HashMap::default()),
2020 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2021 parser_alias_target_matches: RwLock::new(HashMap::default()),
2022 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2023 project_using_index: OnceLock::new(),
2024 callable_reference_specs: Mutex::new(HashMap::default()),
2025 structured_include_fact_cells: Mutex::new(HashMap::default()),
2026 include_activation_cells: Mutex::new(HashMap::default()),
2027 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2028 include_path_admission_cells: Mutex::new(HashMap::default()),
2029 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2030 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2031 conditional_include_projection_state_count: AtomicUsize::new(0),
2032 conditional_include_target_state_count: AtomicUsize::new(0),
2033 include_activation_build_count: AtomicUsize::new(0),
2034 using_donor_activation_count: AtomicUsize::new(0),
2035 using_namespace_lookup_count: AtomicUsize::new(0),
2036 using_name_candidate_inspection_count: AtomicUsize::new(0),
2037 callable_reference_spec_build_count: AtomicUsize::new(0),
2038 alias_source_parse_counts: Mutex::new(HashMap::default()),
2039 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2040 parser_alias_fallback_calls: AtomicUsize::new(0),
2041 parser_alias_fallback_files: AtomicUsize::new(0),
2042 parser_alias_source_parses: AtomicUsize::new(0),
2043 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2044 field_type_facts: Mutex::new(HashMap::default()),
2045 structured_alias_targets: Mutex::new(HashMap::default()),
2046 callable_comparables: Mutex::new(HashMap::default()),
2047 callable_metadata: Mutex::new(HashMap::default()),
2048 comparable_name_declarations: Mutex::new(HashMap::default()),
2049 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2050 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2051 precise_parent_cache: Mutex::new(HashMap::default()),
2052 c_tag_kind_cache: Mutex::new(HashMap::default()),
2053 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2054 macro_event_cells: Mutex::new(HashMap::default()),
2055 macro_event_name_sets: Mutex::new(HashMap::default()),
2056 macro_include_protection_cells: Mutex::new(HashMap::default()),
2057 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2058 macro_replacements: Mutex::new(HashMap::default()),
2059 macro_local_binding_templates: Mutex::new(HashMap::default()),
2060 macro_lexical_templates: Mutex::new(HashMap::default()),
2061 macro_lexical_indexes: Mutex::new(HashMap::default()),
2062 macro_replacement_bodies: Mutex::new(HashMap::default()),
2063 macro_type_parameters: Mutex::new(HashMap::default()),
2064 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2065 macro_replacement_parse_count: AtomicUsize::new(0),
2066 macro_event_application_count: AtomicUsize::new(0),
2067 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2068 macro_environment_copy_count: AtomicUsize::new(0),
2069 macro_environment_request_count: AtomicUsize::new(0),
2070 cpp_template_metadata: HashMap::default(),
2071 cpp_template_families: HashMap::default(),
2072 qualified_candidate_inspections: AtomicUsize::new(0),
2073 target_preserving_type_resolution_count: AtomicUsize::new(0),
2074 visibility_identifier_lookup_count: 0,
2075 visibility_identifier_batch_count: 0,
2076 }
2077 }
2078
2079 fn cpp_source(&self) -> CppGraphSource<'a> {
2086 CppGraphSource::from_source(self.cpp, self.token)
2087 }
2088
2089 pub fn build(
2090 cpp: &'a dyn CppSource,
2091 token: QueryToken<'a>,
2092 analyzer: &CppGraphSource<'_>,
2093 roots: &HashSet<ProjectFile>,
2094 ) -> Self {
2095 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2096 }
2097
2098 pub fn build_with_cancellation(
2099 cpp: &'a dyn CppSource,
2100 token: QueryToken<'a>,
2101 analyzer: &CppGraphSource<'_>,
2102 roots: &HashSet<ProjectFile>,
2103 cancellation: Option<&CancellationToken>,
2104 ) -> Self {
2105 let visibility_started = Instant::now();
2106 let include_targets = cpp.include_target_index();
2107 let includes_started = Instant::now();
2108 let mut include_graph = IncludeGraph::default();
2109 for root in roots {
2110 include_graph.extend_with(root, cancellation, &mut |file| {
2111 cpp.canonical_include_paths(token, file)
2112 .unwrap_or_default()
2113 .into_iter()
2114 .flat_map(|include| {
2115 resolve_include_targets_with_index(file, &include, include_targets)
2116 })
2117 .collect()
2118 });
2119 }
2120 let include_elapsed = includes_started.elapsed();
2121 let include_file_count = include_graph.files().count();
2122 let visible_source_files_by_root = roots
2123 .iter()
2124 .map(|root| {
2125 (
2126 root.clone(),
2127 include_graph.reachable_files(root, cancellation),
2128 )
2129 })
2130 .collect::<HashMap<_, _>>();
2131 let mut visibility_stats = BoundedVisibilityStats::default();
2132 let mut visible_by_file = build_bounded_visible_declarations(
2133 cpp,
2134 token,
2135 analyzer,
2136 roots,
2137 &visible_source_files_by_root,
2138 cancellation,
2139 &mut visibility_stats,
2140 );
2141 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2142 eprintln!(
2143 "BIFROST_CPP_VISIBILITY_STATS total_ms={} include_ms={} include_files={} rounds={} root_names={} identifier_lookups={} identifier_batches={} candidate_units={} candidate_sources={} declaration_reads={} declaration_units={} selected_units={} dependency_source_names={} dependency_names={} lookup_ms={} declaration_ms={} dependency_source_ms={}",
2144 visibility_started.elapsed().as_millis(),
2145 include_elapsed.as_millis(),
2146 include_file_count,
2147 visibility_stats.rounds,
2148 visibility_stats.root_names,
2149 visibility_stats.identifier_lookups,
2150 visibility_stats.identifier_batches,
2151 visibility_stats.candidate_units,
2152 visibility_stats.candidate_sources,
2153 visibility_stats.declaration_reads,
2154 visibility_stats.declaration_units,
2155 visibility_stats.selected_units,
2156 visibility_stats.dependency_source_names,
2157 visibility_stats.dependency_names,
2158 visibility_stats.lookup_elapsed.as_millis(),
2159 visibility_stats.declaration_elapsed.as_millis(),
2160 visibility_stats.dependency_source_elapsed.as_millis(),
2161 );
2162 }
2163 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2164 let finalize_started = Instant::now();
2165 if report_stats {
2166 eprintln!(
2167 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2168 visible_by_file.len(),
2169 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2170 );
2171 }
2172 let owner_started = Instant::now();
2173 if report_stats {
2174 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2175 }
2176 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2177 if report_stats {
2178 eprintln!(
2179 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2180 owner_stats.unseen_owners,
2181 owner_stats.definition_lookups,
2182 owner_stats.admitted,
2183 owner_started.elapsed().as_millis(),
2184 );
2185 }
2186 let mut global_field_internal_linkage = HashMap::default();
2187 let identifier_started = Instant::now();
2188 if report_stats {
2189 eprintln!(
2190 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2191 );
2192 }
2193 let visible_by_identifier = build_visible_identifier_index(
2194 analyzer,
2195 &visible_by_file,
2196 &visible_source_files_by_root,
2197 &mut global_field_internal_linkage,
2198 );
2199 if report_stats {
2200 eprintln!(
2201 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2202 visible_by_identifier.len(),
2203 visible_by_identifier
2204 .values()
2205 .map(HashMap::len)
2206 .sum::<usize>(),
2207 visible_by_identifier
2208 .values()
2209 .flat_map(HashMap::values)
2210 .map(Vec::len)
2211 .sum::<usize>(),
2212 identifier_started.elapsed().as_millis(),
2213 );
2214 }
2215 let mut cpp_template_metadata = HashMap::default();
2216 let metadata_started = Instant::now();
2217 let mut template_classes = 0usize;
2218 if report_stats {
2219 eprintln!(
2220 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2221 );
2222 }
2223 for unit in visible_by_file
2224 .values()
2225 .flatten()
2226 .filter(|unit| unit.is_class())
2227 {
2228 template_classes += 1;
2229 if cpp_template_metadata.contains_key(unit) {
2230 continue;
2231 }
2232 if let Some(metadata) = cpp.template_metadata(unit) {
2233 cpp_template_metadata.insert(unit.clone(), metadata);
2234 }
2235 }
2236 if report_stats {
2237 eprintln!(
2238 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2239 template_classes,
2240 cpp_template_metadata.len(),
2241 metadata_started.elapsed().as_millis(),
2242 );
2243 }
2244 let families_started = Instant::now();
2245 if report_stats {
2246 eprintln!(
2247 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2248 );
2249 }
2250 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2251 for (unit, metadata) in &cpp_template_metadata {
2252 cpp_template_families
2253 .entry(metadata.primary_fq_name.clone())
2254 .or_default()
2255 .push(unit.clone());
2256 }
2257 for family in cpp_template_families.values_mut() {
2266 sort_lookup_units(family);
2267 }
2268 if report_stats {
2269 eprintln!(
2270 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2271 cpp_template_families.len(),
2272 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2273 families_started.elapsed().as_millis(),
2274 );
2275 eprintln!(
2276 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2277 visible_by_file.len(),
2278 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2279 finalize_started.elapsed().as_millis(),
2280 visibility_started.elapsed().as_millis(),
2281 );
2282 }
2283 Self {
2284 cpp,
2285 token,
2286 visible_by_file,
2287 visible_by_identifier,
2288 global_field_internal_linkage,
2289 visible_source_files_by_root,
2290 alias_cells: Mutex::new(HashMap::default()),
2291 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2292 parser_alias_target_matches: RwLock::new(HashMap::default()),
2293 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2294 project_using_index: OnceLock::new(),
2295 callable_reference_specs: Mutex::new(HashMap::default()),
2296 structured_include_fact_cells: Mutex::new(HashMap::default()),
2297 include_activation_cells: Mutex::new(HashMap::default()),
2298 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2299 include_path_admission_cells: Mutex::new(HashMap::default()),
2300 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2301 #[cfg(any(test, feature = "test-support"))]
2302 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2303 #[cfg(any(test, feature = "test-support"))]
2304 conditional_include_projection_state_count: AtomicUsize::new(0),
2305 #[cfg(any(test, feature = "test-support"))]
2306 conditional_include_target_state_count: AtomicUsize::new(0),
2307 #[cfg(any(test, feature = "test-support"))]
2308 include_activation_build_count: AtomicUsize::new(0),
2309 #[cfg(any(test, feature = "test-support"))]
2310 using_donor_activation_count: AtomicUsize::new(0),
2311 #[cfg(any(test, feature = "test-support"))]
2312 using_namespace_lookup_count: AtomicUsize::new(0),
2313 #[cfg(any(test, feature = "test-support"))]
2314 using_name_candidate_inspection_count: AtomicUsize::new(0),
2315 #[cfg(any(test, feature = "test-support"))]
2316 callable_reference_spec_build_count: AtomicUsize::new(0),
2317 #[cfg(any(test, feature = "test-support"))]
2318 alias_source_parse_counts: Mutex::new(HashMap::default()),
2319 #[cfg(any(test, feature = "test-support"))]
2320 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2321 parser_alias_fallback_calls: AtomicUsize::new(0),
2322 parser_alias_fallback_files: AtomicUsize::new(0),
2323 parser_alias_source_parses: AtomicUsize::new(0),
2324 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2325 field_type_facts: Mutex::new(HashMap::default()),
2326 structured_alias_targets: Mutex::new(HashMap::default()),
2327 callable_comparables: Mutex::new(HashMap::default()),
2328 callable_metadata: Mutex::new(HashMap::default()),
2329 comparable_name_declarations: Mutex::new(HashMap::default()),
2330 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2331 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2332 precise_parent_cache: Mutex::new(HashMap::default()),
2333 c_tag_kind_cache: Mutex::new(HashMap::default()),
2334 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2335 macro_event_cells: Mutex::new(HashMap::default()),
2336 macro_event_name_sets: Mutex::new(HashMap::default()),
2337 macro_include_protection_cells: Mutex::new(HashMap::default()),
2338 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2339 macro_replacements: Mutex::new(HashMap::default()),
2340 macro_local_binding_templates: Mutex::new(HashMap::default()),
2341 macro_lexical_templates: Mutex::new(HashMap::default()),
2342 macro_lexical_indexes: Mutex::new(HashMap::default()),
2343 macro_replacement_bodies: Mutex::new(HashMap::default()),
2344 macro_type_parameters: Mutex::new(HashMap::default()),
2345 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2346 #[cfg(any(test, feature = "test-support"))]
2347 macro_replacement_parse_count: AtomicUsize::new(0),
2348 #[cfg(any(test, feature = "test-support"))]
2349 macro_event_application_count: AtomicUsize::new(0),
2350 #[cfg(any(test, feature = "test-support"))]
2351 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2352 #[cfg(any(test, feature = "test-support"))]
2353 macro_environment_copy_count: AtomicUsize::new(0),
2354 #[cfg(any(test, feature = "test-support"))]
2355 macro_environment_request_count: AtomicUsize::new(0),
2356 cpp_template_metadata,
2357 cpp_template_families,
2358 #[cfg(any(test, feature = "test-support"))]
2359 qualified_candidate_inspections: AtomicUsize::new(0),
2360 #[cfg(any(test, feature = "test-support"))]
2361 target_preserving_type_resolution_count: AtomicUsize::new(0),
2362 #[cfg(any(test, feature = "test-support"))]
2363 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2364 #[cfg(any(test, feature = "test-support"))]
2365 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2366 }
2367 }
2368
2369 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2370 if file == target.source() {
2371 return true;
2372 }
2373 if self.global_field_has_internal_linkage(target) {
2374 return self
2375 .visible_source_files_by_root
2376 .get(file)
2377 .is_some_and(|sources| sources.contains(target.source()));
2378 }
2379 self.visible_by_file
2380 .get(file)
2381 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2382 }
2383
2384 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2385 self.global_field_internal_linkage
2386 .get(unit)
2387 .copied()
2388 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2389 }
2390
2391 pub fn call_arity_evidence(
2392 &self,
2393 file: &ProjectFile,
2394 call: Node<'_>,
2395 source: &str,
2396 ) -> CallArityEvidence {
2397 self.call_arity_evidence_at(file, call, source, call.start_byte())
2398 }
2399
2400 pub fn call_arity_evidence_at(
2408 &self,
2409 file: &ProjectFile,
2410 call: Node<'_>,
2411 source: &str,
2412 environment_byte: usize,
2413 ) -> CallArityEvidence {
2414 let Some(arguments) = call
2415 .child_by_field_name("arguments")
2416 .or_else(|| call.child_by_field_name("parameters"))
2417 .or_else(|| call.child_by_field_name("value"))
2418 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2419 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2420 else {
2421 return CallArityEvidence::Exact(0);
2422 };
2423 let recovered_c_keyword_arguments =
2424 recovered_c_keyword_argument_count(file, call, arguments, source);
2425 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2426 let arguments = argument_children(arguments)
2427 .flat_map(|argument| {
2428 recovered_c_new_expression_arguments(argument, c_semantics)
2429 .map(Vec::from)
2430 .unwrap_or_else(|| vec![argument])
2431 })
2432 .collect::<Vec<_>>();
2433 if arguments
2434 .iter()
2435 .all(|argument| !argument_shape_may_change_arity(*argument))
2436 {
2437 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2438 }
2439 let environment = self.macro_environment(file, environment_byte);
2440 let mut stack = Vec::new();
2441 let mut total = recovered_c_keyword_arguments;
2442 for argument in arguments {
2443 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2444 return CallArityEvidence::Unknown;
2445 }
2446 let CallArityEvidence::Exact(spread) =
2447 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2448 else {
2449 return CallArityEvidence::Unknown;
2450 };
2451 total += spread;
2452 }
2453 CallArityEvidence::Exact(total)
2454 }
2455
2456 fn argument_arity_evidence(
2457 &self,
2458 argument: Node<'_>,
2459 source: &str,
2460 environment: &MacroEnvironment,
2461 stack: &mut Vec<(ProjectFile, usize)>,
2462 ) -> CallArityEvidence {
2463 let (name, invocation_arguments, function_like) = match argument.kind() {
2464 "identifier" => (node_text(argument, source), None, false),
2465 "call_expression" => {
2466 let Some(function) = argument.child_by_field_name("function") else {
2467 return CallArityEvidence::Exact(1);
2468 };
2469 if function.kind() != "identifier" {
2470 return CallArityEvidence::Exact(1);
2471 }
2472 let Some(arguments) = argument.child_by_field_name("arguments") else {
2473 return CallArityEvidence::Exact(1);
2474 };
2475 (node_text(function, source), Some(arguments), true)
2476 }
2477 _ => return CallArityEvidence::Exact(1),
2478 };
2479 let Some(binding) = environment.binding(name) else {
2480 return if environment.unknown_names {
2481 CallArityEvidence::Unknown
2482 } else {
2483 CallArityEvidence::Exact(1)
2484 };
2485 };
2486 if !binding.is_exact() {
2487 return CallArityEvidence::Unknown;
2488 }
2489 match (&binding.definition, invocation_arguments, function_like) {
2490 (MacroDefinition::Object { replacement }, None, false) => self
2491 .replacement_arity_evidence(
2492 replacement,
2493 &[],
2494 &[],
2495 source,
2496 environment,
2497 stack,
2498 binding,
2499 ),
2500 (
2501 MacroDefinition::Function {
2502 parameters,
2503 replacement,
2504 },
2505 Some(arguments),
2506 true,
2507 ) => {
2508 let actuals = argument_children(arguments).collect::<Vec<_>>();
2509 if actuals.len() != parameters.len() {
2510 CallArityEvidence::Unknown
2511 } else {
2512 self.replacement_arity_evidence(
2513 replacement,
2514 parameters,
2515 &actuals,
2516 source,
2517 environment,
2518 stack,
2519 binding,
2520 )
2521 }
2522 }
2523 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2524 _ => CallArityEvidence::Unknown,
2525 }
2526 }
2527
2528 #[allow(clippy::too_many_arguments)]
2529 fn replacement_arity_evidence(
2530 &self,
2531 replacement: &str,
2532 parameters: &[String],
2533 actuals: &[Node<'_>],
2534 actual_source: &str,
2535 environment: &MacroEnvironment,
2536 stack: &mut Vec<(ProjectFile, usize)>,
2537 binding: &MacroBinding,
2538 ) -> CallArityEvidence {
2539 let identity = (binding.source.clone(), binding.declaration_byte);
2540 if stack.contains(&identity) || replacement.trim().is_empty() {
2541 return CallArityEvidence::Unknown;
2542 }
2543 stack.push(identity);
2544 let parsed = self.parsed_macro_replacement(binding, replacement);
2545 let evidence = (|| {
2546 let ParsedMacroReplacement::Parsed {
2547 source: sentinel,
2548 tree,
2549 } = parsed.as_ref()
2550 else {
2551 return None;
2552 };
2553 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2554 let arguments = call.child_by_field_name("arguments")?;
2555 let mut total = 0usize;
2556 for argument in argument_children(arguments) {
2557 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2558 return None;
2559 }
2560 if argument.kind() == "identifier"
2561 && let Some(parameter_index) = parameters
2562 .iter()
2563 .position(|parameter| parameter == node_text(argument, sentinel))
2564 {
2565 if !macro_expansion_shape_is_safe(
2566 actuals[parameter_index],
2567 actual_source,
2568 &[],
2569 environment,
2570 ) {
2571 return None;
2572 }
2573 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2574 actuals[parameter_index],
2575 actual_source,
2576 environment,
2577 stack,
2578 ) else {
2579 return None;
2580 };
2581 total += spread;
2582 continue;
2583 }
2584 let CallArityEvidence::Exact(spread) =
2585 self.argument_arity_evidence(argument, sentinel, environment, stack)
2586 else {
2587 return None;
2588 };
2589 total += spread;
2590 }
2591 Some(CallArityEvidence::Exact(total))
2592 })()
2593 .unwrap_or(CallArityEvidence::Unknown);
2594 stack.pop();
2595 evidence
2596 }
2597
2598 fn parsed_macro_replacement(
2599 &self,
2600 binding: &MacroBinding,
2601 replacement: &str,
2602 ) -> Arc<ParsedMacroReplacement> {
2603 let key = (binding.source.clone(), binding.declaration_byte);
2604 let mut cache = self
2605 .macro_replacements
2606 .lock()
2607 .expect("C++ macro replacement cache poisoned");
2608 if let Some(parsed) = cache.get(&key) {
2609 return Arc::clone(parsed);
2610 }
2611 #[cfg(any(test, feature = "test-support"))]
2612 self.macro_replacement_parse_count
2613 .fetch_add(1, Ordering::Relaxed);
2614 let source =
2615 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2616 let mut parser = Parser::new();
2617 let parsed = parser
2618 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2619 .ok()
2620 .and_then(|()| parser.parse(&source, None))
2621 .filter(|tree| !tree.root_node().has_error())
2622 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2623 ParsedMacroReplacement::Parsed { source, tree }
2624 });
2625 let parsed = Arc::new(parsed);
2626 cache.insert(key, Arc::clone(&parsed));
2627 parsed
2628 }
2629
2630 pub fn function_macro_local_binding<'tree>(
2640 &self,
2641 file: &ProjectFile,
2642 statement: Node<'tree>,
2643 source: &str,
2644 ) -> Option<MacroLocalBinding<'tree>> {
2645 if !is_c_source_file(file) {
2646 return None;
2647 }
2648 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2649 return Some(binding);
2650 }
2651 let call = match statement.kind() {
2652 "call_expression" => statement,
2653 "expression_statement" if statement.named_child_count() == 1 => {
2654 statement.named_child(0)?
2655 }
2656 _ => return None,
2657 };
2658 if call.kind() != "call_expression" {
2659 return None;
2660 }
2661 let function = call.child_by_field_name("function")?;
2662 if function.kind() != "identifier" {
2663 return None;
2664 }
2665 let arguments = call.child_by_field_name("arguments")?;
2666 let actuals = argument_children(arguments).collect::<Vec<_>>();
2667 let environment = self.macro_environment(file, call.start_byte());
2668 let function_name = node_text(function, source);
2669 let binding = environment.binding(function_name)?;
2670 let MacroDefinition::Function {
2671 parameters,
2672 replacement,
2673 } = &binding.definition
2674 else {
2675 return None;
2676 };
2677 if actuals.len() != parameters.len() {
2678 return None;
2679 }
2680 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2681 let (type_name, type_node) = match &template.declared_type {
2682 MacroLocalBindingTypeTemplate::Parameter(index) => {
2683 let actual = *actuals.get(*index)?;
2684 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2685 return None;
2686 }
2687 (node_text(actual, source).trim().to_string(), Some(actual))
2688 }
2689 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2690 };
2691 if type_name.is_empty() {
2692 return None;
2693 }
2694 Some(MacroLocalBinding {
2695 name: template.name.clone(),
2696 type_name,
2697 type_node,
2698 pointer_depth: template.pointer_depth,
2699 proven_unit: None,
2700 })
2701 }
2702
2703 pub fn function_macro_container_binding<'tree>(
2712 &self,
2713 analyzer: &CppGraphSource<'_>,
2714 file: &ProjectFile,
2715 assignment: Node<'tree>,
2716 source: &str,
2717 ) -> Option<MacroLocalBinding<'tree>> {
2718 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2719 return None;
2720 }
2721 let name_node = assignment.child_by_field_name("left")?;
2722 if name_node.kind() != "identifier" {
2723 return None;
2724 }
2725 let call = assignment.child_by_field_name("right")?;
2726 if call.kind() != "call_expression" {
2727 return None;
2728 }
2729 let function = call.child_by_field_name("function")?;
2730 if function.kind() != "identifier" {
2731 return None;
2732 }
2733 let arguments = call.child_by_field_name("arguments")?;
2734 let actuals = argument_children(arguments).collect::<Vec<_>>();
2735 let function_name = node_text(function, source);
2736 let environment = self.macro_environment(file, call.start_byte());
2737 let binding = environment.binding(function_name)?;
2738 if !binding.is_exact() {
2739 return None;
2740 }
2741 let MacroDefinition::Function {
2742 parameters,
2743 replacement,
2744 } = &binding.definition
2745 else {
2746 return None;
2747 };
2748 if actuals.len() != parameters.len() {
2749 return None;
2750 }
2751 let body = self.parsed_macro_replacement_body(
2752 &(binding.source.clone(), binding.declaration_byte),
2753 parameters,
2754 replacement,
2755 )?;
2756 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2757 let type_argument = *actuals.get(type_parameter)?;
2758 let type_node = macro_type_argument_node(type_argument, source)?;
2759 let type_name = node_text(type_node, source).trim().to_string();
2760 if type_name.is_empty() {
2761 return None;
2762 }
2763 let explicit_tag = match node_text(type_argument, source) {
2764 "struct" => Some(CppCTagKind::Struct),
2765 "union" => Some(CppCTagKind::Union),
2766 _ => None,
2767 };
2768 let resolved_type = if let Some(tag) = explicit_tag {
2769 let candidates = self
2770 .visible_identifier_candidates(file, &type_name)
2771 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2772 .collect::<Vec<_>>();
2773 self.resolve_type_candidates(
2774 analyzer,
2775 file,
2776 &candidates,
2777 TypeCandidateResolution::Canonical,
2778 )
2779 .ok()
2780 } else {
2781 self.resolve_type_node_result(file, type_node, source)
2782 .ok()
2783 .flatten()
2784 };
2785 let proven_unit = resolved_type?;
2786 Some(MacroLocalBinding {
2787 name: node_text(name_node, source).to_string(),
2788 type_name,
2789 type_node: Some(type_node),
2790 pointer_depth: 1,
2791 proven_unit: Some(proven_unit),
2792 })
2793 }
2794
2795 pub fn macro_local_binding_at<'tree>(
2798 &self,
2799 file: &ProjectFile,
2800 root: Node<'tree>,
2801 source: &str,
2802 start_byte: usize,
2803 end_byte: usize,
2804 ) -> Option<MacroLocalBinding<'tree>> {
2805 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2806 }
2807
2808 pub fn macro_lexical_binding(
2812 &self,
2813 file: &ProjectFile,
2814 root: Node<'_>,
2815 source: &str,
2816 start_byte: usize,
2817 end_byte: usize,
2818 ) -> Option<MacroLexicalBinding> {
2819 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2820 }
2821
2822 pub fn macro_lexical_references(
2827 &self,
2828 file: &ProjectFile,
2829 root: Node<'_>,
2830 source: &str,
2831 max_references: usize,
2832 cancelled: impl FnMut() -> bool,
2833 ) -> MacroLexicalReferences {
2834 crate::graph::macro_lexical::all_references(
2835 || self,
2836 file,
2837 root,
2838 source,
2839 max_references,
2840 cancelled,
2841 )
2842 }
2843
2844 pub(crate) fn function_macro_binding_at(
2849 &self,
2850 file: &ProjectFile,
2851 name: &str,
2852 before_byte: usize,
2853 ) -> Option<(ProjectFile, usize)> {
2854 let environment = self.macro_environment(file, before_byte);
2855 let binding = environment.binding(name)?;
2856 if binding.is_exact()
2857 && matches!(
2858 binding.definition,
2859 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2860 )
2861 {
2862 return Some((binding.source.clone(), binding.declaration_byte));
2863 }
2864 if binding.source != *file {
2870 return None;
2871 }
2872 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2873 let root = prepared.tree().root_node();
2874 let source = prepared.source();
2875 let reference = root.descendant_for_byte_range(
2876 before_byte,
2877 before_byte.saturating_add(1).min(source.len()),
2878 )?;
2879 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2880 let cell = self.macro_event_cell(file);
2881 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2882 let event = events
2883 .iter()
2884 .find(|event| event.byte() == binding.declaration_byte)?;
2885 let MacroEvent::Define {
2886 name: defined_name,
2887 binding: definition,
2888 conditionals,
2889 ..
2890 } = event
2891 else {
2892 return None;
2893 };
2894 if defined_name != name
2895 || conditionals.is_empty()
2896 || !conditionals
2897 .iter()
2898 .all(|condition| reference_conditions.contains(condition))
2899 || !matches!(
2900 definition.definition,
2901 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2902 )
2903 {
2904 return None;
2905 }
2906 Some((definition.source.clone(), definition.declaration_byte))
2907 }
2908
2909 pub fn function_macro_type_argument<'tree>(
2916 &self,
2917 file: &ProjectFile,
2918 node: Node<'tree>,
2919 source: &str,
2920 ) -> Option<Node<'tree>> {
2921 if !is_c_source_file(file) {
2922 return None;
2923 }
2924 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2925 let function = call.child_by_field_name("function")?;
2926 let function_name = node_text(function, source);
2927 if function_name.is_empty() {
2928 return None;
2929 }
2930 if !self.file_defines_macro_name(file, function_name)
2931 && !self
2932 .visible_identifier_candidates(file, function_name)
2933 .any(|candidate| candidate.is_macro())
2934 {
2935 return None;
2936 }
2937 let environment = self.macro_environment(file, call.start_byte());
2938 let binding = environment.binding(function_name)?;
2939 if !binding.is_exact() {
2940 return None;
2941 }
2942 let (parameters, replacement, variadic) = match &binding.definition {
2943 MacroDefinition::Function {
2944 parameters,
2945 replacement,
2946 } => (parameters, replacement, false),
2947 MacroDefinition::VariadicFunction {
2948 parameters,
2949 replacement,
2950 } => (parameters, replacement, true),
2951 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2952 };
2953 let actuals = call
2954 .child_by_field_name("arguments")
2955 .map(argument_children)
2956 .into_iter()
2957 .flatten()
2958 .collect::<Vec<_>>();
2959 let arity_matches = if variadic {
2960 actuals.len() >= parameters.len()
2961 } else {
2962 actuals.len() == parameters.len()
2963 };
2964 let type_parameters = self.macro_type_parameter_indices(
2965 &(binding.source.clone(), binding.declaration_byte),
2966 parameters,
2967 replacement,
2968 )?;
2969 if !arity_matches || !type_parameters.contains(&argument_index) {
2970 return None;
2971 }
2972 let type_node = macro_type_argument_node(argument, source)?;
2973 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2974 return None;
2975 }
2976 Some(type_node)
2977 }
2978
2979 fn macro_local_binding_template(
2980 &self,
2981 binding: &MacroBinding,
2982 parameters: &[String],
2983 replacement: &str,
2984 ) -> Option<Arc<MacroLocalBindingTemplate>> {
2985 let key = (binding.source.clone(), binding.declaration_byte);
2986 if let Some(template) = self
2987 .macro_local_binding_templates
2988 .lock()
2989 .expect("C++ macro local-binding cache poisoned")
2990 .get(&key)
2991 {
2992 return template.clone();
2993 }
2994 let template = (|| {
2995 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
2996 let sentinel = body.source.as_str();
2997 let statements = body.statements()?;
2998 if statements.named_child_count() != 1 {
2999 return None;
3000 }
3001 let declaration = statements.named_child(0)?;
3002 if declaration.kind() != "declaration" {
3003 return None;
3004 }
3005 let type_node = declaration
3006 .child_by_field_name("type")
3007 .or_else(|| first_type_child(declaration))?;
3008 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3009 let mut cursor = declaration.walk();
3010 declaration.named_children(&mut cursor).find_map(|child| {
3011 if child.kind() == "init_declarator" {
3012 child.child_by_field_name("declarator")
3013 } else {
3014 is_declarator_node(child).then_some(child)
3015 }
3016 })
3017 })?;
3018 let name = extract_variable_name(declarator, sentinel)?;
3019 let pointer_depth =
3020 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3021 let type_text = node_text(type_node, sentinel).trim();
3022 let declared_type = parameters
3023 .iter()
3024 .position(|parameter| parameter == type_text)
3025 .map(MacroLocalBindingTypeTemplate::Parameter)
3026 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3027 Some(Arc::new(MacroLocalBindingTemplate {
3028 name,
3029 declared_type,
3030 pointer_depth,
3031 }))
3032 })();
3033 self.macro_local_binding_templates
3034 .lock()
3035 .expect("C++ macro local-binding cache poisoned")
3036 .insert(key, template.clone());
3037 template
3038 }
3039
3040 pub fn function_macro_replacement_body(
3047 &self,
3048 file: &ProjectFile,
3049 definition: Node<'_>,
3050 source: &str,
3051 ) -> Option<Arc<ParsedReplacementBody>> {
3052 debug_assert_eq!(definition.kind(), "preproc_function_def");
3053 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3054 MacroDefinition::Function {
3055 parameters,
3056 replacement,
3057 }
3058 | MacroDefinition::VariadicFunction {
3059 parameters,
3060 replacement,
3061 } => (parameters, replacement),
3062 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3063 };
3064 self.parsed_macro_replacement_body(
3065 &(file.clone(), definition.start_byte()),
3066 ¶meters,
3067 &replacement,
3068 )
3069 }
3070
3071 fn parsed_macro_replacement_body(
3079 &self,
3080 key: &(ProjectFile, usize),
3081 parameters: &[String],
3082 replacement: &str,
3083 ) -> Option<Arc<ParsedReplacementBody>> {
3084 if let Some(body) = self
3085 .macro_replacement_bodies
3086 .lock()
3087 .expect("C++ macro replacement body cache poisoned")
3088 .get(key)
3089 {
3090 return body.clone();
3091 }
3092 let body = (|| {
3093 if replacement.trim().is_empty() {
3094 return None;
3095 }
3096 let (source, tree, original_offsets) =
3097 Self::parse_macro_replacement_body(replacement, parameters)?;
3098 let body = ParsedReplacementBody {
3099 source,
3100 tree,
3101 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3102 parameters: parameters.to_vec(),
3103 original_offsets,
3104 };
3105 body.statements()?;
3106 if body.expands_variadic_arguments() {
3107 return None;
3108 }
3109 Some(Arc::new(body))
3110 })();
3111 self.macro_replacement_bodies
3112 .lock()
3113 .expect("C++ macro replacement body cache poisoned")
3114 .insert(key.clone(), body.clone());
3115 body
3116 }
3117
3118 fn parse_macro_replacement_body(
3126 replacement: &str,
3127 parameters: &[String],
3128 ) -> Option<(String, Tree, Box<[usize]>)> {
3129 let normalized = normalize_macro_continuations(replacement);
3130 let parse = |replacement: &str| {
3131 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3132 let mut parser = Parser::new();
3133 parser
3134 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3135 .ok()?;
3136 let tree = parser.parse(&source, None)?;
3137 Some((source, tree))
3138 };
3139 let (source, tree) = parse(&normalized)?;
3140 if !tree.root_node().has_error() {
3141 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3142 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3143 return Some((source, tree, original_offsets.into_boxed_slice()));
3144 }
3145
3146 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3147 let mut insertion_points = Vec::new();
3148 let mut stack = vec![tree.root_node()];
3149 while let Some(node) = stack.pop() {
3150 if matches!(
3151 node.kind(),
3152 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3153 ) && parameters
3154 .iter()
3155 .any(|parameter| parameter.as_str() == node_text(node, &source))
3156 && Self::macro_formal_needs_statement_separator(node)
3157 {
3158 let point = node.end_byte().saturating_sub(body_offset);
3159 if point <= normalized.len() && !insertion_points.contains(&point) {
3160 insertion_points.push(point);
3161 }
3162 }
3163 push_named_children_reversed(node, &mut stack);
3164 }
3165 if insertion_points.is_empty() {
3166 return None;
3167 }
3168 insertion_points.sort_unstable();
3169 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3170 let mut original_offsets =
3171 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3172 let mut next_insertion = 0;
3173 for (index, byte) in normalized.bytes().enumerate() {
3174 recovered.push(byte);
3175 original_offsets.push(index);
3176 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3177 recovered.push(b';');
3178 original_offsets.push(index + 1);
3179 next_insertion += 1;
3180 }
3181 }
3182 original_offsets.push(normalized.len());
3183 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3184 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3185 let (source, tree) = parse(&recovered)?;
3186 if tree.root_node().has_error() {
3187 return None;
3188 }
3189 Some((source, tree, original_offsets.into_boxed_slice()))
3190 }
3191
3192 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3197 offsets.extend([replacement_end; 3]);
3198 }
3199
3200 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3205 let mut current = node;
3206 let mut crossed_recovery = false;
3207 while let Some(parent) = current.parent() {
3208 if parent.is_error() {
3209 crossed_recovery = true;
3210 current = parent;
3211 continue;
3212 }
3213 if matches!(
3214 parent.kind(),
3215 "call_expression"
3216 | "argument_list"
3217 | "field_expression"
3218 | "binary_expression"
3219 | "unary_expression"
3220 | "assignment_expression"
3221 | "conditional_expression"
3222 | "parenthesized_expression"
3223 | "subscript_expression"
3224 ) {
3225 return false;
3226 }
3227 if parent.kind() == "expression_statement" {
3228 return parent.named_child_count() == 1;
3229 }
3230 if parent.kind() == "compound_statement" && current == node {
3231 return true;
3232 }
3233 if matches!(
3234 parent.kind(),
3235 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3236 ) {
3237 return crossed_recovery
3238 || parent.child_by_field_name("consequence") == Some(current);
3239 }
3240 if matches!(parent.kind(), "declaration" | "init_declarator") {
3241 return false;
3242 }
3243 current = parent;
3244 }
3245 false
3246 }
3247
3248 fn macro_type_parameter_indices(
3256 &self,
3257 key: &(ProjectFile, usize),
3258 parameters: &[String],
3259 replacement: &str,
3260 ) -> Option<Arc<[usize]>> {
3261 if let Some(indices) = self
3262 .macro_type_parameters
3263 .lock()
3264 .expect("C++ macro type-parameter cache poisoned")
3265 .get(key)
3266 {
3267 return indices.clone();
3268 }
3269 #[cfg(any(test, feature = "test-support"))]
3270 self.macro_replacement_parse_count
3271 .fetch_add(1, Ordering::Relaxed);
3272 let indices = (|| {
3273 if replacement.trim().is_empty() {
3274 return None;
3275 }
3276 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3280 let mut parser = Parser::new();
3281 parser
3282 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3283 .ok()?;
3284 let tree = parser.parse(&source, None)?;
3285 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3286 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3287 let body = ParsedReplacementBody {
3288 source,
3289 tree,
3290 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3291 parameters: parameters.to_vec(),
3292 original_offsets: original_offsets.into_boxed_slice(),
3293 };
3294 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3295 })();
3296 self.macro_type_parameters
3297 .lock()
3298 .expect("C++ macro type-parameter cache poisoned")
3299 .insert(key.clone(), indices.clone());
3300 indices
3301 }
3302
3303 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3304 let replacement = if node.kind() == "preproc_function_def" {
3305 function_macro_replacement_span(node, source)
3306 .and_then(|span| source.get(span))
3307 .map(str::to_owned)
3308 .or_else(|| {
3309 node.child_by_field_name("value")
3310 .map(|value| node_text(value, source).to_string())
3311 })
3312 .unwrap_or_default()
3313 } else {
3314 node.child_by_field_name("value")
3315 .map(|value| node_text(value, source).to_string())
3316 .unwrap_or_default()
3317 };
3318 if node.kind() == "preproc_def" {
3319 return MacroDefinition::Object { replacement };
3320 }
3321 let Some(parameters) = node.child_by_field_name("parameters") else {
3322 return MacroDefinition::Unsupported;
3323 };
3324 let variadic = (0..parameters.child_count()).any(|index| {
3325 parameters
3326 .child(index)
3327 .is_some_and(|child| child.kind() == "...")
3328 });
3329 let parameters = (0..parameters.named_child_count())
3330 .filter_map(|index| parameters.named_child(index))
3331 .map(|parameter| node_text(parameter, source).to_string())
3332 .collect::<Vec<_>>();
3333 if variadic {
3334 MacroDefinition::VariadicFunction {
3335 parameters,
3336 replacement,
3337 }
3338 } else {
3339 MacroDefinition::Function {
3340 parameters,
3341 replacement,
3342 }
3343 }
3344 }
3345
3346 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3347 clone_or_default_file_cell(
3348 &self.macro_event_cells,
3349 file,
3350 "C++ macro event cache poisoned",
3351 )
3352 }
3353
3354 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3355 if let Some(names) = self
3356 .macro_event_name_sets
3357 .lock()
3358 .expect("C++ macro event-name cache poisoned")
3359 .get(file)
3360 .cloned()
3361 {
3362 return names.contains(name);
3363 }
3364 let cell = self.macro_event_cell(file);
3365 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3366 let names = Arc::new(
3367 events
3368 .iter()
3369 .filter_map(|event| match event {
3370 MacroEvent::Define { name, .. } => Some(name.clone()),
3371 MacroEvent::Undef { .. }
3372 | MacroEvent::Include { .. }
3373 | MacroEvent::Invalidate { .. } => None,
3374 })
3375 .collect(),
3376 );
3377 self.macro_event_name_sets
3378 .lock()
3379 .expect("C++ macro event-name cache poisoned")
3380 .insert(file.clone(), Arc::clone(&names));
3381 names.contains(name)
3382 }
3383
3384 fn macro_environment_checkpoint_cell(
3385 &self,
3386 file: &ProjectFile,
3387 ) -> MacroEnvironmentCheckpointCell {
3388 clone_or_default_file_cell(
3389 &self.macro_environment_checkpoints,
3390 file,
3391 "C++ macro environment checkpoint cache poisoned",
3392 )
3393 }
3394
3395 pub fn macro_environment(
3397 &self,
3398 file: &ProjectFile,
3399 before_byte: usize,
3400 ) -> Arc<MacroEnvironment> {
3401 #[cfg(any(test, feature = "test-support"))]
3402 self.macro_environment_request_count
3403 .fetch_add(1, Ordering::Relaxed);
3404 let cell = self.macro_event_cell(file);
3405 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3406 let frontier = events.partition_point(|event| event.byte() < before_byte);
3407 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3408 let checkpoints =
3409 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3410 let checkpoint = checkpoints.at_or_before(frontier);
3411 if checkpoint.frontier == frontier {
3412 return Arc::clone(&checkpoint.environment);
3413 }
3414 #[cfg(any(test, feature = "test-support"))]
3415 self.macro_environment_copy_count
3416 .fetch_add(1, Ordering::Relaxed);
3417 let mut environment = checkpoint.environment.as_ref().clone();
3418 let mut include_stack = HashSet::from_iter([file.clone()]);
3419 for event in &events[checkpoint.frontier..frontier] {
3420 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3421 }
3422 Arc::new(environment)
3423 }
3424
3425 fn build_macro_environment_checkpoints(
3428 &self,
3429 file: &ProjectFile,
3430 events: &[MacroEvent],
3431 ) -> MacroEnvironmentCheckpoints {
3432 #[cfg(any(test, feature = "test-support"))]
3433 self.macro_environment_checkpoint_build_count
3434 .fetch_add(1, Ordering::Relaxed);
3435 let mut environment = MacroEnvironment {
3440 build_proven_defines: self
3441 .compile_proven_guards(file)
3442 .iter()
3443 .filter_map(|guard| match guard {
3444 PreprocessorGuard::Defined(name) => Some(name.clone()),
3445 _ => None,
3446 })
3447 .collect(),
3448 ..MacroEnvironment::default()
3449 };
3450 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3451 frontier: 0,
3452 environment: Arc::new(environment.clone()),
3453 }];
3454 let checkpoint_stride = events
3461 .len()
3462 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3463 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3464 let mut include_stack = HashSet::from_iter([file.clone()]);
3465 for (index, event) in events.iter().enumerate() {
3466 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3467 let frontier = index + 1;
3468 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3469 checkpoints.push(MacroEnvironmentCheckpoint {
3470 frontier,
3471 environment: Arc::new(environment.clone()),
3472 });
3473 }
3474 }
3475 MacroEnvironmentCheckpoints { checkpoints }
3476 }
3477
3478 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3487 self.macro_environment(file, before_byte)
3488 .binding(name)
3489 .is_some()
3490 }
3491
3492 pub fn macro_name_may_be_bound_at(
3493 &self,
3494 file: &ProjectFile,
3495 name: &str,
3496 before_byte: usize,
3497 ) -> bool {
3498 self.macro_environment(file, before_byte).may_bind(name)
3499 }
3500
3501 pub fn macro_binding_matches_target_at(
3505 &self,
3506 analyzer: &CppGraphSource<'_>,
3507 file: &ProjectFile,
3508 name: &str,
3509 before_byte: usize,
3510 target: &CodeUnit,
3511 ) -> bool {
3512 let ranges = analyzer.ranges(target);
3513 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3514 self.macro_binding_matches_target_declaration_at(
3515 file,
3516 name,
3517 before_byte,
3518 target.source(),
3519 &declaration_bytes,
3520 )
3521 }
3522
3523 pub(crate) fn macro_declaration_bytes(
3528 &self,
3529 target: &CodeUnit,
3530 ranges: &[Range],
3531 ) -> Vec<usize> {
3532 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3533 return Vec::new();
3534 };
3535 ranges
3536 .iter()
3537 .filter_map(|range| {
3538 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3539 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3540 node = node.parent()?;
3541 }
3542 Some(node.start_byte())
3543 })
3544 .collect()
3545 }
3546
3547 pub(crate) fn macro_binding_matches_target_declaration_at(
3548 &self,
3549 file: &ProjectFile,
3550 name: &str,
3551 before_byte: usize,
3552 target_source: &ProjectFile,
3553 target_declaration_bytes: &[usize],
3554 ) -> bool {
3555 let environment = self.macro_environment(file, before_byte);
3556 let Some(binding) = environment.binding(name) else {
3557 return false;
3558 };
3559 if binding.definition == MacroDefinition::Unsupported {
3560 return false;
3561 }
3562 if binding.source != *target_source {
3566 return false;
3567 }
3568 target_declaration_bytes.contains(&binding.declaration_byte)
3569 }
3570
3571 pub fn resolve_ordinary_macro_reference(
3578 &self,
3579 analyzer: &CppGraphSource<'_>,
3580 file: &ProjectFile,
3581 node: Node<'_>,
3582 source: &str,
3583 ) -> OrdinaryMacroReferenceResolution {
3584 if !is_ordinary_macro_reference_node(node) {
3585 return OrdinaryMacroReferenceResolution::Missing;
3586 }
3587 let name = node_text(node, source);
3588 if name.is_empty() {
3589 return OrdinaryMacroReferenceResolution::Missing;
3590 }
3591 let visible = self
3592 .visible_identifier_candidates(file, name)
3593 .filter(|candidate| candidate.is_macro())
3594 .cloned()
3595 .collect::<Vec<_>>();
3596 let mut exact = Vec::new();
3597 for candidate in &visible {
3598 if self.macro_binding_matches_target_at(
3599 analyzer,
3600 file,
3601 name,
3602 node.start_byte(),
3603 candidate,
3604 ) && !exact
3605 .iter()
3606 .any(|existing| same_visible_symbol(existing, candidate))
3607 {
3608 exact.push(candidate.clone());
3609 }
3610 }
3611 match exact.len() {
3612 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3613 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3614 0 if !visible.is_empty()
3615 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3616 {
3617 OrdinaryMacroReferenceResolution::Ambiguous
3618 }
3619 0 => OrdinaryMacroReferenceResolution::Missing,
3620 }
3621 }
3622
3623 pub fn recovered_c_reference_ranges(
3631 &self,
3632 file: &ProjectFile,
3633 root: Node<'_>,
3634 source: &str,
3635 limit: usize,
3636 ) -> RecoveredCReferenceRanges {
3637 if !is_c_source_file(file) {
3638 return RecoveredCReferenceRanges::Complete(Vec::new());
3639 }
3640 let mut ranges = Vec::new();
3641 let mut seen = HashSet::default();
3642 let mut stack = vec![(root, root.is_error())];
3643 while let Some((node, inside_error)) = stack.pop() {
3644 let inside_error = inside_error || node.is_error();
3645 if node.kind() == "preproc_arg" {
3646 let macro_value_kind = node.parent().and_then(|parent| {
3651 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3652 });
3653 if matches!(
3654 macro_value_kind,
3655 Some("preproc_def" | "preproc_function_def")
3656 ) {
3657 let name = node_text(node, source);
3658 if !name.is_empty()
3659 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3660 && !push_recovered_c_range(
3661 &mut ranges,
3662 &mut seen,
3663 node.start_byte(),
3664 node.end_byte(),
3665 node,
3666 limit,
3667 )
3668 {
3669 return RecoveredCReferenceRanges::LimitExceeded;
3670 }
3671 }
3672 if macro_value_kind == Some("preproc_def") {
3673 for reference in object_macro_replacement_type_references(node, source) {
3674 for range in reference.component_ranges {
3675 let visible = self
3676 .visible_identifier_candidates(file, &source[range.clone()])
3677 .any(|candidate| candidate.is_class() || candidate.is_module());
3678 if visible
3679 && !push_recovered_c_range(
3680 &mut ranges,
3681 &mut seen,
3682 range.start,
3683 range.end,
3684 node,
3685 limit,
3686 )
3687 {
3688 return RecoveredCReferenceRanges::LimitExceeded;
3689 }
3690 }
3691 }
3692 }
3693 }
3694 if inside_error
3695 && recovered_c_reference_node(self, file, node, source)
3696 && !push_recovered_c_range(
3697 &mut ranges,
3698 &mut seen,
3699 node.start_byte(),
3700 node.end_byte(),
3701 node,
3702 limit,
3703 )
3704 {
3705 return RecoveredCReferenceRanges::LimitExceeded;
3706 }
3707 let mut cursor = node.walk();
3708 for child in node.named_children(&mut cursor) {
3709 stack.push((child, inside_error));
3710 }
3711 }
3712 ranges.sort_unstable();
3713 RecoveredCReferenceRanges::Complete(ranges)
3714 }
3715
3716 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3722 self.visible_identifier_candidates(file, target.identifier())
3723 .filter(|candidate| candidate.is_macro())
3724 .any(|candidate| {
3725 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3726 })
3727 }
3728
3729 pub fn object_macro_replacement_at(
3730 &self,
3731 file: &ProjectFile,
3732 name: &str,
3733 before_byte: usize,
3734 ) -> Option<String> {
3735 let environment = self.macro_environment(file, before_byte);
3736 let binding = environment.binding(name)?;
3737 if !binding.exact {
3738 return None;
3739 }
3740 match &binding.definition {
3741 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3742 MacroDefinition::Function { .. }
3743 | MacroDefinition::VariadicFunction { .. }
3744 | MacroDefinition::Unsupported => None,
3745 }
3746 }
3747
3748 fn apply_macro_events(
3749 &self,
3750 file: &ProjectFile,
3751 before_byte: Option<usize>,
3752 environment: &mut MacroEnvironment,
3753 include_stack: &mut HashSet<ProjectFile>,
3754 ) {
3755 if !include_stack.insert(file.clone()) {
3756 return;
3757 }
3758 if self.cpp.prepared_syntax(self.token, file).is_none() {
3759 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3760 include_stack.remove(file);
3761 return;
3762 }
3763 match self.macro_include_protection(file) {
3764 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3765 Some(binding) if binding.is_exact() => {
3766 include_stack.remove(file);
3767 return;
3768 }
3769 Some(_) | None if environment.unknown_names => {
3770 let mut ambiguous_seen = HashSet::default();
3771 self.mark_macro_events_ambiguous(
3772 file,
3773 environment,
3774 &mut ambiguous_seen,
3775 file,
3776 before_byte.unwrap_or_default(),
3777 );
3778 include_stack.remove(file);
3779 return;
3780 }
3781 Some(_) => {
3782 let mut ambiguous_seen = HashSet::default();
3783 self.mark_macro_events_ambiguous(
3784 file,
3785 environment,
3786 &mut ambiguous_seen,
3787 file,
3788 before_byte.unwrap_or_default(),
3789 );
3790 include_stack.remove(file);
3791 return;
3792 }
3793 None => {}
3794 },
3795 MacroIncludeProtection::PragmaOnce => {
3796 if !environment.applied_pragma_once_files.insert(file.clone()) {
3797 include_stack.remove(file);
3798 return;
3799 }
3800 if environment.maybe_applied_pragma_once_files.remove(file) {
3801 let mut ambiguous_seen = HashSet::default();
3806 environment.applied_pragma_once_files.remove(file);
3807 self.mark_macro_events_ambiguous(
3808 file,
3809 environment,
3810 &mut ambiguous_seen,
3811 file,
3812 before_byte.unwrap_or_default(),
3813 );
3814 environment.maybe_applied_pragma_once_files.remove(file);
3815 environment.applied_pragma_once_files.insert(file.clone());
3816 include_stack.remove(file);
3817 return;
3818 }
3819 }
3820 MacroIncludeProtection::None => {}
3821 }
3822 let cell = self.macro_event_cell(file);
3823 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3824 for event in events {
3825 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3826 break;
3827 }
3828 self.apply_macro_event(file, event, environment, include_stack);
3829 }
3830 include_stack.remove(file);
3831 }
3832
3833 fn apply_macro_event(
3834 &self,
3835 file: &ProjectFile,
3836 event: &MacroEvent,
3837 environment: &mut MacroEnvironment,
3838 include_stack: &mut HashSet<ProjectFile>,
3839 ) {
3840 #[cfg(any(test, feature = "test-support"))]
3841 self.macro_event_application_count
3842 .fetch_add(1, Ordering::Relaxed);
3843 match event {
3844 MacroEvent::Define {
3845 name,
3846 binding,
3847 conditionals,
3848 byte,
3849 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3850 Some(true) => environment.insert(name.clone(), binding.clone()),
3851 Some(false) => {}
3852 None => Self::merge_conditional_macro_definition(
3853 environment,
3854 name,
3855 binding,
3856 file,
3857 *byte,
3858 ),
3859 },
3860 MacroEvent::Undef {
3861 name,
3862 conditionals,
3863 byte,
3864 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3865 Some(true) => environment.remove(name),
3866 Some(false) => {}
3867 None => {
3868 if environment.binding(name).is_some() {
3869 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3870 }
3871 }
3872 },
3873 MacroEvent::Include {
3874 targets,
3875 conditionals,
3876 byte,
3877 } => {
3878 let condition =
3879 self.macro_event_condition_value(file, *byte, environment, conditionals);
3880 if condition == Some(false) {
3881 return;
3882 }
3883 if targets.is_empty() {
3884 environment.mark_unknown_names(file, *byte);
3885 return;
3886 }
3887 if condition.is_none() || targets.len() > 1 {
3888 let mut ambiguous_seen = HashSet::default();
3889 for target in targets {
3890 self.mark_macro_events_ambiguous(
3891 target,
3892 environment,
3893 &mut ambiguous_seen,
3894 file,
3895 *byte,
3896 );
3897 }
3898 } else if let Some(target) = targets.first() {
3899 self.apply_macro_events(target, None, environment, include_stack);
3900 }
3901 }
3902 MacroEvent::Invalidate { byte } => {
3903 for binding in environment.bindings.values_mut() {
3904 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3905 }
3906 }
3907 }
3908 }
3909
3910 fn macro_event_condition_value(
3921 &self,
3922 file: &ProjectFile,
3923 event_byte: usize,
3924 environment: &MacroEnvironment,
3925 conditionals: &OwningPreprocessorConditionals,
3926 ) -> Option<bool> {
3927 if conditionals.is_empty() {
3928 return Some(true);
3929 }
3930 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3931 let source = prepared.source();
3932 let root = prepared.tree().root_node();
3933 let descendant = root.descendant_for_byte_range(
3934 event_byte,
3935 event_byte.saturating_add(1).min(source.len()),
3936 )?;
3937 let mut unknown = false;
3938 let mut current = descendant.parent();
3939 while let Some(conditional) = current {
3940 if matches!(
3941 conditional.kind(),
3942 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3943 ) && conditionals.contains(&conditional.start_byte())
3944 {
3945 let mut value = match conditional.kind() {
3946 "preproc_ifdef" => {
3947 let name = conditional.child_by_field_name("name")?;
3948 let defined =
3949 self.macro_name_defined_value(environment, node_text(name, source));
3950 match conditional.child(0)?.kind() {
3951 "#ifdef" => defined,
3952 "#ifndef" => defined.map(|defined| !defined),
3953 _ => None,
3954 }
3955 }
3956 "preproc_if" | "preproc_elif" => conditional
3957 .child_by_field_name("condition")
3958 .and_then(|condition| {
3959 self.preprocessor_integer_value(
3960 condition,
3961 source,
3962 environment,
3963 &mut Vec::new(),
3964 0,
3965 )
3966 })
3967 .map(|value| value != 0),
3968 _ => unreachable!(),
3969 };
3970 if conditional
3971 .child_by_field_name("alternative")
3972 .is_some_and(|alternative| {
3973 alternative.start_byte() <= descendant.start_byte()
3974 && descendant.end_byte() <= alternative.end_byte()
3975 })
3976 {
3977 value = value.map(|value| !value);
3978 }
3979 match value {
3980 Some(true) => {}
3981 Some(false) => return Some(false),
3982 None => unknown = true,
3983 }
3984 }
3985 current = conditional.parent();
3986 }
3987 (!unknown).then_some(true)
3988 }
3989
3990 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
3991 if environment.known_undefined_names.contains(name) {
3992 return Some(false);
3993 }
3994 if let Some(binding) = environment.binding(name) {
3995 return binding.is_exact().then_some(true);
3996 }
3997 environment
3998 .build_proven_defines
3999 .contains(name)
4000 .then_some(true)
4001 }
4002
4003 fn preprocessor_integer_value(
4004 &self,
4005 expression: Node<'_>,
4006 source: &str,
4007 environment: &MacroEnvironment,
4008 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4009 depth: usize,
4010 ) -> Option<i128> {
4011 if depth >= 64 {
4014 return None;
4015 }
4016 match expression.kind() {
4017 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4018 "identifier" | "type_identifier" => {
4019 let binding = environment.binding(node_text(expression, source))?;
4020 if !binding.is_exact() {
4021 return None;
4022 }
4023 let MacroDefinition::Object { replacement } = &binding.definition else {
4024 return None;
4025 };
4026 let identity = (binding.source.clone(), binding.declaration_byte);
4027 if expansion_stack.contains(&identity) {
4028 return None;
4029 }
4030 expansion_stack.push(identity);
4031 let parsed = self.parsed_macro_replacement(binding, replacement);
4032 let value = match parsed.as_ref() {
4033 ParsedMacroReplacement::Parsed {
4034 source: replacement_source,
4035 tree,
4036 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4037 .and_then(|call| call.child_by_field_name("arguments"))
4038 .and_then(|arguments| argument_children(arguments).next())
4039 .and_then(|argument| {
4040 self.preprocessor_integer_value(
4041 argument,
4042 replacement_source,
4043 environment,
4044 expansion_stack,
4045 depth + 1,
4046 )
4047 }),
4048 ParsedMacroReplacement::Unsupported => None,
4049 };
4050 expansion_stack.pop();
4051 value
4052 }
4053 "preproc_defined" => {
4054 let mut cursor = expression.walk();
4055 let name = expression
4056 .named_children(&mut cursor)
4057 .find(|child| child.kind() == "identifier")?;
4058 self.macro_name_defined_value(environment, node_text(name, source))
4059 .map(i128::from)
4060 }
4061 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4062 self.preprocessor_integer_value(
4063 child,
4064 source,
4065 environment,
4066 expansion_stack,
4067 depth + 1,
4068 )
4069 }),
4070 "unary_expression" => {
4071 let operator = expression.child_by_field_name("operator")?.kind();
4072 let argument = expression.child_by_field_name("argument")?;
4073 let value = self.preprocessor_integer_value(
4074 argument,
4075 source,
4076 environment,
4077 expansion_stack,
4078 depth + 1,
4079 )?;
4080 match operator {
4081 "+" => Some(value),
4082 "-" => value.checked_neg(),
4083 "!" => Some(i128::from(value == 0)),
4084 "~" => Some(!value),
4085 _ => None,
4086 }
4087 }
4088 "binary_expression" => {
4089 let left = self.preprocessor_integer_value(
4090 expression.child_by_field_name("left")?,
4091 source,
4092 environment,
4093 expansion_stack,
4094 depth + 1,
4095 )?;
4096 let right = self.preprocessor_integer_value(
4097 expression.child_by_field_name("right")?,
4098 source,
4099 environment,
4100 expansion_stack,
4101 depth + 1,
4102 )?;
4103 match expression.child_by_field_name("operator")?.kind() {
4104 "+" => left.checked_add(right),
4105 "-" => left.checked_sub(right),
4106 "*" => left.checked_mul(right),
4107 "/" => left.checked_div(right),
4108 "%" => left.checked_rem(right),
4109 "<<" => u32::try_from(right)
4110 .ok()
4111 .and_then(|shift| left.checked_shl(shift)),
4112 ">>" => u32::try_from(right)
4113 .ok()
4114 .and_then(|shift| left.checked_shr(shift)),
4115 "<" => Some(i128::from(left < right)),
4116 "<=" => Some(i128::from(left <= right)),
4117 ">" => Some(i128::from(left > right)),
4118 ">=" => Some(i128::from(left >= right)),
4119 "==" => Some(i128::from(left == right)),
4120 "!=" => Some(i128::from(left != right)),
4121 "&" => Some(left & right),
4122 "|" => Some(left | right),
4123 "^" => Some(left ^ right),
4124 "&&" => Some(i128::from(left != 0 && right != 0)),
4125 "||" => Some(i128::from(left != 0 || right != 0)),
4126 _ => None,
4127 }
4128 }
4129 _ => None,
4130 }
4131 }
4132
4133 fn mark_macro_events_ambiguous(
4134 &self,
4135 file: &ProjectFile,
4136 environment: &mut MacroEnvironment,
4137 include_stack: &mut HashSet<ProjectFile>,
4138 conditional_file: &ProjectFile,
4139 conditional_byte: usize,
4140 ) {
4141 if !include_stack.insert(file.clone()) {
4142 return;
4143 }
4144 if self.cpp.prepared_syntax(self.token, file).is_none() {
4145 environment.mark_unknown_names(conditional_file, conditional_byte);
4146 return;
4147 }
4148 match self.macro_include_protection(file) {
4149 MacroIncludeProtection::MacroGuard(guard) => {
4150 if environment
4151 .binding(&guard)
4152 .is_some_and(MacroBinding::is_exact)
4153 {
4154 return;
4155 }
4156 }
4157 MacroIncludeProtection::PragmaOnce => {
4158 if environment.applied_pragma_once_files.contains(file) {
4159 return;
4160 }
4161 environment
4162 .maybe_applied_pragma_once_files
4163 .insert(file.clone());
4164 }
4165 MacroIncludeProtection::None => {}
4166 }
4167 let cell = self.macro_event_cell(file);
4168 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4169 for event in events {
4170 #[cfg(any(test, feature = "test-support"))]
4171 self.macro_event_application_count
4172 .fetch_add(1, Ordering::Relaxed);
4173 match event {
4174 MacroEvent::Define { name, binding, .. } => {
4175 Self::merge_conditional_macro_definition(
4176 environment,
4177 name,
4178 binding,
4179 conditional_file,
4180 conditional_byte,
4181 );
4182 }
4183 MacroEvent::Undef { name, .. } => {
4184 if environment.binding(name).is_some() {
4185 environment.insert(
4186 name.clone(),
4187 MacroBinding::ambiguous(conditional_file, conditional_byte),
4188 );
4189 } else {
4190 environment.remove_known_undefined(name);
4191 }
4192 }
4193 MacroEvent::Include { targets, .. } => {
4194 if targets.is_empty() {
4195 environment.mark_unknown_names(conditional_file, conditional_byte);
4196 continue;
4197 }
4198 for target in targets {
4199 self.mark_macro_events_ambiguous(
4200 target,
4201 environment,
4202 include_stack,
4203 conditional_file,
4204 conditional_byte,
4205 );
4206 }
4207 }
4208 MacroEvent::Invalidate { .. } => {
4209 for binding in environment.bindings.values_mut() {
4210 *binding = MacroBinding::uncertain_from(
4211 binding,
4212 conditional_file,
4213 conditional_byte,
4214 );
4215 }
4216 }
4217 }
4218 }
4219 }
4220
4221 fn merge_conditional_macro_definition(
4222 environment: &mut MacroEnvironment,
4223 name: &str,
4224 possible_binding: &MacroBinding,
4225 conditional_file: &ProjectFile,
4226 conditional_byte: usize,
4227 ) {
4228 if environment.binding(name).is_some_and(|current| {
4233 current.definition != MacroDefinition::Unsupported
4234 && current.definition == possible_binding.definition
4235 }) {
4236 return;
4237 }
4238 environment.insert(
4239 name.to_string(),
4240 MacroBinding::ambiguous(conditional_file, conditional_byte),
4241 );
4242 }
4243
4244 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4245 let cell = self
4246 .macro_include_protection_cells
4247 .lock()
4248 .expect("C++ include protection cache poisoned")
4249 .entry(file.clone())
4250 .or_default()
4251 .clone();
4252 cell.get_or_init(|| {
4253 self.cpp.prepared_syntax(self.token, file).map_or(
4254 MacroIncludeProtection::None,
4255 |prepared| {
4256 top_level_macro_include_protection(
4257 prepared.tree().root_node(),
4258 prepared.source(),
4259 )
4260 },
4261 )
4262 })
4263 .clone()
4264 }
4265
4266 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4267 collect_macro_events(self.cpp, self.token, file)
4268 }
4269
4270 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4271 self.ordinary_type_import_cells
4272 .lock()
4273 .expect("C++ ordinary type import cache poisoned")
4274 .entry(file.clone())
4275 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4276 .clone()
4277 }
4278
4279 pub fn project_using_index(
4280 &self,
4281 build: impl FnOnce() -> ProjectUsingIndex,
4282 ) -> &ProjectUsingIndex {
4283 self.project_using_index.get_or_init(build)
4284 }
4285
4286 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4287 let mut files = self
4288 .visible_source_files_by_root
4289 .values()
4290 .flatten()
4291 .cloned()
4292 .collect::<HashSet<_>>()
4293 .into_iter()
4294 .collect::<Vec<_>>();
4295 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4296 files
4297 }
4298
4299 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4300 self.visible_source_files_by_root
4301 .get(root)
4302 .is_some_and(|files| files.contains(source))
4303 }
4304
4305 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4306 let cached = self
4307 .visible_parser_alias_name_sets
4308 .read()
4309 .expect("visible parser alias-name cache poisoned")
4310 .get(file)
4311 .cloned();
4312 let cell = if let Some(cached) = cached {
4313 cached
4314 } else {
4315 let mut cells = self
4316 .visible_parser_alias_name_sets
4317 .write()
4318 .expect("visible parser alias-name cache poisoned");
4319 Arc::clone(
4320 cells
4321 .entry(file.clone())
4322 .or_insert_with(|| Arc::new(OnceLock::new())),
4323 )
4324 };
4325 if let Some(names) = cell.get() {
4326 return names.contains(name);
4327 }
4328 let mut names = HashSet::default();
4329 let visible_files = self
4330 .visible_source_files_by_root
4331 .get(file)
4332 .cloned()
4333 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4334 for visible_file in visible_files {
4335 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4336 return false;
4337 };
4338 for alias in aliases.get().expect("published canonical aliases").iter() {
4339 names.insert(alias.name.clone());
4340 }
4341 }
4342 #[cfg(any(test, feature = "test-support"))]
4343 self.visible_parser_alias_name_set_build_count
4344 .fetch_add(1, Ordering::Relaxed);
4345 cell.get_or_init(|| names).contains(name)
4346 }
4347
4348 pub fn parser_alias_name_may_resolve_to_target(
4349 &self,
4350 file: &ProjectFile,
4351 alias_name: &str,
4352 target: &CodeUnit,
4353 ) -> bool {
4354 let started = std::time::Instant::now();
4355 self.parser_alias_fallback_calls
4356 .fetch_add(1, Ordering::Relaxed);
4357 let key = (
4358 file.clone(),
4359 alias_name.to_string(),
4360 logical_symbol_key(target),
4361 );
4362 let cached = self
4363 .parser_alias_target_matches
4364 .read()
4365 .expect("parser alias target-match cache poisoned")
4366 .get(&key)
4367 .cloned();
4368 let cell = if let Some(cached) = cached {
4369 cached
4370 } else {
4371 let mut cells = self
4372 .parser_alias_target_matches
4373 .write()
4374 .expect("parser alias target-match cache poisoned");
4375 Arc::clone(
4376 cells
4377 .entry(key)
4378 .or_insert_with(|| Arc::new(OnceLock::new())),
4379 )
4380 };
4381 let matched = *cell.get_or_init(|| {
4382 any_visible_source_file_matches(
4383 &self.visible_source_files_by_root,
4384 file,
4385 |visible_file| {
4386 self.parser_alias_fallback_files
4387 .fetch_add(1, Ordering::Relaxed);
4388 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4389 },
4390 )
4391 });
4392 self.parser_alias_fallback_elapsed_micros.fetch_add(
4393 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4394 Ordering::Relaxed,
4395 );
4396 matched
4397 }
4398
4399 fn callable_arities_for_target(
4400 &self,
4401 analyzer: &CppGraphSource<'_>,
4402 cpp: &dyn CppSource,
4403 file: &ProjectFile,
4404 prepared: &PreparedSyntaxTree,
4405 spec: &TargetSpec,
4406 ) -> Vec<ActivatedCallableArity> {
4407 let Some(signature) = spec.target.signature() else {
4408 return Vec::new();
4409 };
4410 let Some(candidates) = self
4411 .visible_by_identifier
4412 .get(file)
4413 .and_then(|by_name| by_name.get(&spec.member_name))
4414 else {
4415 return Vec::new();
4416 };
4417 let differing_candidates = candidates
4418 .iter()
4419 .filter(|candidate| {
4420 candidate.is_function()
4421 && candidate.fq_name() == spec.target.fq_name()
4422 && candidate.signature() == Some(signature)
4423 })
4424 .filter_map(|candidate| {
4425 analyzer
4426 .signature_metadata(candidate)
4427 .into_iter()
4428 .find_map(|metadata| metadata.callable_arity())
4429 .filter(|arity| Some(*arity) != spec.callable_arity)
4430 .map(|arity| (candidate, arity))
4431 })
4432 .collect::<Vec<_>>();
4433 if differing_candidates.is_empty() {
4434 return Vec::new();
4435 }
4436 let mut arities = Vec::with_capacity(differing_candidates.len());
4437 let reference = CallableReferenceContext {
4440 file,
4441 position: None,
4442 };
4443 for (candidate, candidate_arity) in differing_candidates {
4444 let declaration_activation =
4445 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4446 let Some(declaration_activation) = declaration_activation else {
4447 continue;
4448 };
4449 let activation_byte = if candidate.source() == file {
4450 Some(declaration_activation)
4451 } else {
4452 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4453 };
4454 if let Some(activation_byte) = activation_byte {
4455 arities.push(ActivatedCallableArity {
4456 activation_byte,
4457 arity: candidate_arity,
4458 });
4459 }
4460 }
4461 arities
4462 }
4463
4464 fn callable_parameter_macro_arity(
4465 &self,
4466 target: &CodeUnit,
4467 parameter_types: Option<&[String]>,
4468 ) -> Option<CallableArity> {
4469 let parameter_types = parameter_types?;
4470 let [macro_name] = parameter_types else {
4471 return None;
4472 };
4473 if macro_name.is_empty()
4474 || !macro_name
4475 .chars()
4476 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4477 {
4478 return None;
4479 }
4480 let cache_key = (target.source().clone(), macro_name.clone());
4481 if let Some(cached) = self
4482 .callable_parameter_macro_arities
4483 .lock()
4484 .expect("C++ callable parameter-macro arity cache poisoned")
4485 .get(&cache_key)
4486 .copied()
4487 {
4488 return cached;
4489 }
4490 let mut visible_files = HashSet::default();
4491 collect_include_closure(
4492 &self.cpp_source(),
4493 self.cpp.include_target_index(),
4494 target.source(),
4495 &mut visible_files,
4496 None,
4497 );
4498 let mut arities = Vec::new();
4499 for visible_file in visible_files {
4500 let cell = self.macro_event_cell(&visible_file);
4501 for event in
4502 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4503 {
4504 let MacroEvent::Define { name, binding, .. } = event else {
4505 continue;
4506 };
4507 if name != macro_name {
4508 continue;
4509 }
4510 let MacroDefinition::Object { replacement } = &binding.definition else {
4511 continue;
4512 };
4513 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4514 continue;
4515 };
4516 if !arities.contains(&arity) {
4517 arities.push(arity);
4518 }
4519 }
4520 }
4521 let resolved = (|| {
4522 let required = arities
4523 .iter()
4524 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4525 .min()?;
4526 let total = arities.iter().map(|arity| arity.total()).max()?;
4527 let repeated = arities
4528 .iter()
4529 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4530 Some(CallableArity::new(required, total, repeated))
4535 })();
4536 self.callable_parameter_macro_arities
4537 .lock()
4538 .expect("C++ callable parameter-macro arity cache poisoned")
4539 .insert(cache_key, resolved);
4540 resolved
4541 }
4542
4543 pub fn include_activation_for_source(
4544 &self,
4545 cpp: &dyn CppSource,
4546 file: &ProjectFile,
4547 prepared: &PreparedSyntaxTree,
4548 donor_source: &ProjectFile,
4549 ) -> Option<usize> {
4550 let key = (file.clone(), donor_source.clone());
4551 if let Some(cached) = self
4552 .include_activation_cells
4553 .lock()
4554 .expect("C++ include activation cache poisoned")
4555 .get(&key)
4556 .copied()
4557 {
4558 return cached;
4559 }
4560 #[cfg(any(test, feature = "test-support"))]
4561 self.include_activation_build_count
4562 .fetch_add(1, Ordering::Relaxed);
4563 let language_guard_stable = OnceCell::new();
4564 let activation =
4565 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4566 *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4567 });
4568 let mut cells = self
4569 .include_activation_cells
4570 .lock()
4571 .expect("C++ include activation cache poisoned");
4572 *cells.entry(key).or_insert(activation)
4573 }
4574
4575 pub fn conditional_include_projections_for_source(
4576 &self,
4577 file: &ProjectFile,
4578 prepared: &PreparedSyntaxTree,
4579 donor_source: &ProjectFile,
4580 ) -> Arc<[ConditionalIncludeProjection]> {
4581 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4582 let cell = self
4583 .conditional_include_projection_cells
4584 .lock()
4585 .expect("C++ conditional include projection cache poisoned")
4586 .entry(file.clone())
4587 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4588 .clone();
4589 let index = cell.get_or_build_pool_independent(|| {
4590 #[cfg(any(test, feature = "test-support"))]
4591 self.conditional_include_projection_index_build_count
4592 .fetch_add(1, Ordering::Relaxed);
4593 let language_guard_stable = OnceCell::new();
4594 find_conditional_include_projection_index(
4595 self.cpp,
4596 self.token,
4597 file,
4598 prepared,
4599 &|| {
4600 *language_guard_stable
4601 .get_or_init(|| self.include_language_guard_is_stable(file))
4602 },
4603 &|| {
4604 #[cfg(any(test, feature = "test-support"))]
4605 self.conditional_include_projection_state_count
4606 .fetch_add(1, Ordering::Relaxed);
4607 },
4608 )
4609 });
4610 index
4611 .get(donor_source)
4612 .cloned()
4613 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4614 }
4615
4616 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4617 let mut macro_events = |file: &ProjectFile| {
4618 let cell = self.macro_event_cell(file);
4619 cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4620 .clone()
4621 };
4622 include_language_guard_is_stable_with(
4623 self.cpp,
4624 self.token,
4625 file,
4626 &mut macro_events,
4627 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4628 self.structured_include_facts(file, prepared)
4629 },
4630 )
4631 }
4632
4633 #[cfg(any(test, feature = "test-support"))]
4634 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4635 (
4636 self.conditional_include_projection_index_build_count
4637 .load(Ordering::Relaxed),
4638 self.conditional_include_projection_state_count
4639 .load(Ordering::Relaxed),
4640 )
4641 }
4642
4643 #[cfg(any(test, feature = "test-support"))]
4644 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4645 self.conditional_include_target_state_count
4646 .load(Ordering::Relaxed)
4647 }
4648
4649 #[cfg(any(test, feature = "test-support"))]
4650 pub fn include_activation_build_count_for_test(&self) -> usize {
4651 self.include_activation_build_count.load(Ordering::Relaxed)
4652 }
4653
4654 #[cfg(any(test, feature = "test-support"))]
4655 pub fn note_using_donor_activation_for_test(&self) {
4656 self.using_donor_activation_count
4657 .fetch_add(1, Ordering::Relaxed);
4658 }
4659
4660 #[cfg(not(any(test, feature = "test-support")))]
4661 pub fn note_using_donor_activation_for_test(&self) {}
4662
4663 #[cfg(any(test, feature = "test-support"))]
4664 pub fn note_using_namespace_lookup_for_test(&self) {
4665 self.using_namespace_lookup_count
4666 .fetch_add(1, Ordering::Relaxed);
4667 }
4668
4669 #[cfg(not(any(test, feature = "test-support")))]
4670 pub fn note_using_namespace_lookup_for_test(&self) {}
4671
4672 #[cfg(any(test, feature = "test-support"))]
4673 pub fn note_using_name_candidate_inspection_for_test(&self) {
4674 self.using_name_candidate_inspection_count
4675 .fetch_add(1, Ordering::Relaxed);
4676 }
4677
4678 #[cfg(not(any(test, feature = "test-support")))]
4679 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4680
4681 #[cfg(any(test, feature = "test-support"))]
4682 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4683 (
4684 self.using_donor_activation_count.load(Ordering::Relaxed),
4685 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4686 self.callable_reference_spec_build_count
4687 .load(Ordering::Relaxed),
4688 self.using_name_candidate_inspection_count
4689 .load(Ordering::Relaxed),
4690 )
4691 }
4692
4693 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4694 file == target.source()
4695 || self
4696 .visible_by_file
4697 .get(file)
4698 .is_some_and(|visible| visible.contains(target))
4699 }
4700
4701 pub fn declaration_visible_at(
4713 &self,
4714 analyzer: &CppGraphSource<'_>,
4715 file: &ProjectFile,
4716 declaration: &CodeUnit,
4717 reference_byte: usize,
4718 ) -> bool {
4719 let reference_guards = OnceCell::new();
4720 self.visible_identifier_candidates(file, declaration.identifier())
4721 .filter(|candidate| {
4722 self.same_logical_callable(analyzer, candidate, declaration)
4723 || flattened_macro_namespace_declaration_matches(
4724 analyzer,
4725 self.cpp,
4726 file,
4727 candidate,
4728 declaration,
4729 reference_byte,
4730 )
4731 })
4732 .any(|candidate| {
4733 self.physical_declaration_visible_at(
4734 analyzer,
4735 file,
4736 candidate,
4737 reference_byte,
4738 &reference_guards,
4739 )
4740 })
4741 }
4742
4743 pub fn declaration_visible_at_reference(
4750 &self,
4751 analyzer: &CppGraphSource<'_>,
4752 file: &ProjectFile,
4753 declaration: &CodeUnit,
4754 reference: Node<'_>,
4755 ) -> bool {
4756 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4757 return false;
4758 };
4759 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4760 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4761 if !declaration_guards.iter().any(|(_, required)| {
4762 guards_compatible_at_reference(required, reference_guards.as_ref())
4763 }) {
4764 return false;
4765 }
4766 if declaration.source() == file
4767 && !analyzer.reference_uses_c_semantics(file)
4768 && (declaration.is_field() || declaration.is_callable())
4769 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4770 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4771 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4772 })
4773 {
4774 return true;
4778 }
4779 if declaration.is_field() && declaration.source() == file {
4780 let reference_byte = reference.start_byte();
4781 let reference_function =
4782 real_function_definition_ancestor(reference, prepared.source());
4783 let guards = OnceCell::new();
4784 let field_reference = CallableReferenceContext {
4785 file,
4786 position: Some(CallableReferencePosition {
4787 prepared: prepared.as_ref(),
4788 byte: reference_byte,
4789 guards: &guards,
4790 }),
4791 };
4792 let mut has_local_declaration = false;
4793 let mut local_declaration_visible = false;
4794 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4795 {
4796 let Some(declaration_function) =
4797 real_function_definition_ancestor(declaration, prepared.source())
4798 else {
4799 continue;
4800 };
4801 has_local_declaration = true;
4802 if reference_function.is_some_and(|reference_function| {
4803 reference_function.start_byte() == declaration_function.start_byte()
4804 && reference_function.end_byte() == declaration_function.end_byte()
4805 }) && callable_preprocessor_context_is_visible_for_reference(
4806 declaration,
4807 prepared.source(),
4808 &field_reference,
4809 ) && declaration.end_byte() < reference_byte
4810 {
4811 local_declaration_visible = true;
4812 break;
4813 }
4814 }
4815 if has_local_declaration {
4816 return local_declaration_visible;
4817 }
4818 }
4819 let guards = OnceCell::new();
4820 self.physical_declaration_visible_at(
4821 analyzer,
4822 file,
4823 declaration,
4824 reference.start_byte(),
4825 &guards,
4826 )
4827 }
4828
4829 pub fn declaration_visible_for_c_forward_call(
4835 &self,
4836 analyzer: &CppGraphSource<'_>,
4837 file: &ProjectFile,
4838 declaration: &CodeUnit,
4839 reference_byte: usize,
4840 ) -> bool {
4841 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4842 return true;
4843 }
4844 if declaration.source() != file {
4845 return false;
4846 }
4847 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4848 return false;
4849 };
4850 let reference_guards = prepared
4851 .tree()
4852 .root_node()
4853 .descendant_for_byte_range(reference_byte, reference_byte)
4854 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4855 declaration_guard_requirements(analyzer, self.cpp, declaration)
4856 .into_iter()
4857 .any(|(_, required)| {
4858 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4859 })
4860 }
4861
4862 pub fn callable_arity_at_reference(
4863 &self,
4864 analyzer: &CppGraphSource<'_>,
4865 file: &ProjectFile,
4866 candidate: &CodeUnit,
4867 reference_byte: usize,
4868 ) -> Option<CallableArity> {
4869 let key = (file.clone(), logical_symbol_key(candidate));
4870 let cell = self
4871 .callable_reference_specs
4872 .lock()
4873 .expect("C++ callable reference-spec cache poisoned")
4874 .entry(key)
4875 .or_default()
4876 .clone();
4877 let spec = cell.get_or_init(|| {
4878 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4879 let spec = TargetSpec::from_target(analyzer, candidate)?;
4880 let spec = spec
4881 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4882 .into_owned();
4883 #[cfg(any(test, feature = "test-support"))]
4884 self.callable_reference_spec_build_count
4885 .fetch_add(1, Ordering::Relaxed);
4886 Some(spec)
4887 });
4888 spec.as_ref()?.callable_arity_at(reference_byte)
4889 }
4890
4891 fn physical_declaration_visible_at(
4892 &self,
4893 analyzer: &CppGraphSource<'_>,
4894 file: &ProjectFile,
4895 declaration: &CodeUnit,
4896 reference_byte: usize,
4897 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4898 ) -> bool {
4899 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4900 return false;
4901 };
4902 let reference = CallableReferenceContext {
4903 file,
4904 position: Some(CallableReferencePosition {
4905 prepared: prepared.as_ref(),
4906 byte: reference_byte,
4907 guards: reference_guards,
4908 }),
4909 };
4910 if declaration.source() == file {
4911 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4912 .or_else(|| {
4913 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4914 })
4915 .is_some_and(|activation| activation < reference_byte);
4916 }
4917 if self
4918 .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4919 .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4920 .is_none()
4921 {
4922 return false;
4923 }
4924 declaration_guard_requirements(analyzer, self.cpp, declaration)
4925 .into_iter()
4926 .any(|(_, declaration_guards)| {
4927 self.foreign_declaration_reachable_at_reference(
4928 file,
4929 prepared.as_ref(),
4930 declaration.source(),
4931 &declaration_guards,
4932 reference.guards(),
4933 reference_byte,
4934 )
4935 })
4936 }
4937
4938 fn foreign_callable_declaration_activation(
4953 &self,
4954 analyzer: &CppGraphSource<'_>,
4955 declaration: &CodeUnit,
4956 reference: &CallableReferenceContext<'_>,
4957 ) -> Option<usize> {
4958 let build_decides = !self.compile_context_is_absent(reference.file);
4959 let proven = self.compile_proven_guards(reference.file);
4960 let augmented;
4961 let active = match reference.guards() {
4962 Some(active) if !proven.is_empty() => {
4963 augmented = active.union(&proven).cloned().collect();
4964 Some(&augmented)
4965 }
4966 other => other,
4967 };
4968 analyzer
4969 .declaration_source_occurrences(declaration)?
4970 .into_iter()
4971 .filter_map(|occurrence| {
4972 let activation = occurrence.fact.callable_activation?;
4973 let mut required =
4974 cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4975 if required.iter().any(|guard| match guard {
4976 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4977 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4978 !reference.is_c()
4979 }
4980 _ => false,
4981 }) {
4982 return None;
4983 }
4984 required.retain(|guard| {
4985 !matches!(guard,
4986 PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
4987 if name == "__cplusplus")
4988 });
4989 let visible = required.is_empty()
4990 || if build_decides {
4991 guard_requirements_hold_at_reference(&required, active)
4992 } else {
4993 guards_compatible_at_reference(&required, reference.guards())
4994 };
4995 visible.then_some(activation)
4996 })
4997 .min()
4998 }
4999
5000 pub fn external_type_candidate_visible_at(
5001 &self,
5002 file: &ProjectFile,
5003 candidate: &CodeUnit,
5004 reference_byte: usize,
5005 ) -> bool {
5006 if candidate.source() == file {
5007 return true;
5008 }
5009 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5010 return false;
5011 };
5012 self.visible_identifier_candidates(file, candidate.identifier())
5013 .filter(|peer| same_logical_symbol(candidate, peer))
5014 .any(|peer| {
5015 peer.source() == file
5016 || self
5017 .include_activation_for_source(
5018 self.cpp,
5019 file,
5020 prepared.as_ref(),
5021 peer.source(),
5022 )
5023 .is_some_and(|activation| activation <= reference_byte)
5024 })
5025 }
5026
5027 pub fn external_type_declaration_visible_at(
5028 &self,
5029 file: &ProjectFile,
5030 candidate: &CodeUnit,
5031 reference_byte: usize,
5032 ) -> bool {
5033 if candidate.source() == file {
5034 return true;
5035 }
5036 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5037 return false;
5038 };
5039 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5040 .is_some_and(|activation| activation <= reference_byte)
5041 }
5042
5043 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5062 if let Some(cached) = self
5063 .compile_proven_guard_cells
5064 .lock()
5065 .expect("C++ compile-proven guard cache poisoned")
5066 .get(file)
5067 {
5068 return Arc::clone(cached);
5069 }
5070 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5071 Some(names) => names,
5072 None => {
5073 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5074 let seed = translation_units.next().and_then(|translation_unit| {
5075 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5076 });
5077 match seed {
5078 None => HashSet::default(),
5079 Some(mut names) => {
5080 for translation_unit in translation_units {
5081 let Some(reached) = context_fact_names(
5082 self.cpp.compile_contexts_for(&translation_unit),
5083 ) else {
5084 names.clear();
5085 break;
5086 };
5087 names.retain(|name| reached.contains(name));
5088 if names.is_empty() {
5089 break;
5090 }
5091 }
5092 names
5093 }
5094 }
5095 }
5096 };
5097 let proven = Arc::new(
5098 names
5099 .into_iter()
5100 .map(PreprocessorGuard::Defined)
5101 .collect::<HashSet<_>>(),
5102 );
5103 self.compile_proven_guard_cells
5104 .lock()
5105 .expect("C++ compile-proven guard cache poisoned")
5106 .insert(file.clone(), Arc::clone(&proven));
5107 proven
5108 }
5109
5110 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5120 if let Some(cached) = self
5121 .include_path_admission_cells
5122 .lock()
5123 .expect("C++ include-path admission cache poisoned")
5124 .get(file)
5125 .copied()
5126 {
5127 return cached;
5128 }
5129 let admission = if self.compile_context_is_absent(file) {
5130 IncludePathAdmission::Compatible
5131 } else {
5132 IncludePathAdmission::Proven
5133 };
5134 self.include_path_admission_cells
5135 .lock()
5136 .expect("C++ include-path admission cache poisoned")
5137 .insert(file.clone(), admission);
5138 admission
5139 }
5140
5141 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5148 if !self.cpp.compile_contexts_for(file).is_empty() {
5149 return false;
5150 }
5151 let translation_units = self.cpp.reaching_translation_units(file);
5152 translation_units.is_empty()
5153 || translation_units
5154 .iter()
5155 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5156 }
5157
5158 pub fn miss_requires_compile_context(
5170 &self,
5171 file: &ProjectFile,
5172 identifier: &str,
5173 reference: Node<'_>,
5174 ) -> bool {
5175 if !self.compile_context_is_absent(file) {
5176 return false;
5177 }
5178 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5179 return false;
5180 };
5181 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5182 let reference_byte = reference.start_byte();
5183 let mut sources = self
5184 .visible_identifier_candidates(file, identifier)
5185 .map(CodeUnit::source)
5186 .filter(|source| *source != file)
5187 .collect::<Vec<_>>();
5188 sources.sort();
5189 sources.dedup();
5190 sources.into_iter().any(|declaration_source| {
5191 self.conditional_include_projections_for_source(
5192 file,
5193 prepared.as_ref(),
5194 declaration_source,
5195 )
5196 .iter()
5197 .any(|projection| {
5198 projection.activation_byte <= reference_byte
5199 && !guard_requirements_hold_at_reference(
5200 &projection.required_guards,
5201 reference_guards.as_ref(),
5202 )
5203 && guards_compatible_at_reference(
5204 &projection.required_guards,
5205 reference_guards.as_ref(),
5206 )
5207 })
5208 })
5209 }
5210
5211 fn foreign_declaration_reachable_at_reference(
5223 &self,
5224 file: &ProjectFile,
5225 prepared: &PreparedSyntaxTree,
5226 declaration_source: &ProjectFile,
5227 declaration_guards: &HashSet<PreprocessorGuard>,
5228 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5229 reference_byte: usize,
5230 ) -> bool {
5231 let proven = self.compile_proven_guards(file);
5237 let augmented;
5238 let reference_guards = match reference_guards {
5239 Some(active) if !proven.is_empty() => {
5240 augmented = active.union(&proven).cloned().collect();
5241 Some(&augmented)
5242 }
5243 other => other,
5244 };
5245 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5246 eprintln!(
5247 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5248 declaration_source.rel_path().display(),
5249 );
5250 }
5251 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5252 return false;
5253 }
5254 if self
5255 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5256 .is_some_and(|activation| activation <= reference_byte)
5257 {
5258 return true;
5259 }
5260 let projections =
5261 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5262 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5263 eprintln!(
5264 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5265 declaration_source.rel_path().display(),
5266 declaration_guards.len(),
5267 proven.len(),
5268 projections.len(),
5269 );
5270 }
5271 let admission = self.include_path_admission(file);
5272 projections.iter().any(|projection| {
5273 projection.activation_byte <= reference_byte
5274 && admission.admits(
5275 &projection.required_guards,
5276 &projection.partial_guards,
5277 reference_guards,
5278 )
5279 && self.preprocessor_guards_stable_between(
5280 file,
5281 projection.activation_byte,
5282 reference_byte,
5283 &projection.required_guards,
5284 )
5285 })
5286 }
5287
5288 fn foreign_declaration_may_be_reachable_from_raw_guards(
5289 &self,
5290 file: &ProjectFile,
5291 prepared: &PreparedSyntaxTree,
5292 declaration_source: &ProjectFile,
5293 declaration_guards: &HashSet<PreprocessorGuard>,
5294 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5295 reference_byte: usize,
5296 ) -> bool {
5297 let proven = self.compile_proven_guards(file);
5298 let augmented;
5299 let reference_guards = match reference_guards {
5300 Some(active) if !proven.is_empty() => {
5301 augmented = active.union(&proven).cloned().collect();
5302 Some(&augmented)
5303 }
5304 other => other,
5305 };
5306 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5307 return false;
5308 }
5309 if self
5310 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5311 .is_some_and(|activation| activation <= reference_byte)
5312 {
5313 return true;
5314 }
5315 let language_guard_stable = OnceCell::new();
5316 let reachable = find_conditional_include_projection_for_source(
5317 self.cpp,
5318 self.token,
5319 file,
5320 prepared,
5321 declaration_source,
5322 self.include_path_admission(file),
5323 reference_guards,
5324 reference_byte,
5325 &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5326 &|| {
5327 #[cfg(any(test, feature = "test-support"))]
5328 self.conditional_include_target_state_count
5329 .fetch_add(1, Ordering::Relaxed);
5330 },
5331 );
5332 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5333 eprintln!(
5334 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5335 declaration_source.rel_path().display(),
5336 declaration_guards.len(),
5337 proven.len(),
5338 reference_guards.map_or(0, HashSet::len),
5339 );
5340 }
5341 reachable
5342 }
5343
5344 fn foreign_declaration_reachable_from_compile_proven_guards(
5345 &self,
5346 file: &ProjectFile,
5347 prepared: &PreparedSyntaxTree,
5348 declaration_source: &ProjectFile,
5349 declaration_guards: &HashSet<PreprocessorGuard>,
5350 reference_byte: usize,
5351 ) -> bool {
5352 let proven = self.compile_proven_guards(file);
5353 if proven.is_empty()
5354 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5355 {
5356 return false;
5357 }
5358 let projections =
5359 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5360 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5361 eprintln!(
5362 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5363 declaration_source.rel_path().display(),
5364 declaration_guards.len(),
5365 proven.len(),
5366 projections.len(),
5367 );
5368 }
5369 projections.iter().any(|projection| {
5370 projection.activation_byte <= reference_byte
5371 && guard_requirements_hold_at_reference(
5372 &projection.required_guards,
5373 Some(proven.as_ref()),
5374 )
5375 && self.preprocessor_guards_stable_between(
5380 file,
5381 0,
5382 projection.activation_byte,
5383 &projection.required_guards,
5384 )
5385 })
5386 }
5387
5388 pub fn external_type_candidate_visible_in_context(
5389 &self,
5390 analyzer: &CppGraphSource<'_>,
5391 file: &ProjectFile,
5392 candidate: &CodeUnit,
5393 reference: Node<'_>,
5394 ) -> bool {
5395 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5396 if report_stats {
5397 eprintln!(
5398 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5399 candidate.fq_name(),
5400 candidate.source().rel_path().display(),
5401 file.rel_path().display(),
5402 reference.start_byte(),
5403 );
5404 }
5405 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5406 return false;
5407 };
5408 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5409 let reference_guards = OnceCell::new();
5410 let reference_guards_at_site = || {
5411 reference_guards.get_or_init(|| {
5412 let started = Instant::now();
5413 if report_stats {
5414 eprintln!(
5415 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5416 file.rel_path().display(),
5417 reference.start_byte(),
5418 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5419 );
5420 }
5421 let macro_environment = self.macro_environment(file, reference.start_byte());
5422 let filtered = raw_reference_guards
5423 .clone()
5424 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5425 if report_stats {
5426 eprintln!(
5427 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5428 filtered.is_some(),
5429 started.elapsed().as_millis(),
5430 );
5431 }
5432 filtered
5433 })
5434 };
5435
5436 let peers = self
5437 .visible_identifier_candidates(file, candidate.identifier())
5438 .filter(|peer| same_logical_symbol(candidate, peer))
5439 .collect::<Vec<_>>();
5440 if report_stats {
5441 let peer_sources = peers
5442 .iter()
5443 .map(|peer| peer.source().rel_path().display().to_string())
5444 .collect::<Vec<_>>();
5445 eprintln!(
5446 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5447 candidate.fq_name(),
5448 );
5449 }
5450 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5451 declaration_guard_requirements(analyzer, self.cpp, peer)
5452 .into_iter()
5453 .any(|(declaration_byte, declaration_guards)| {
5454 if peer.source() == file {
5455 let visible = declaration_byte < reference.start_byte()
5456 && declaration_guards.is_empty();
5457 if report_stats {
5458 eprintln!(
5459 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5460 peer.source().rel_path().display(),
5461 declaration_guards.len(),
5462 );
5463 }
5464 return visible;
5465 }
5466 let direct = declaration_guards.is_empty()
5467 && self
5468 .include_activation_for_source(
5469 self.cpp,
5470 file,
5471 prepared.as_ref(),
5472 peer.source(),
5473 )
5474 .is_some_and(|activation| activation <= reference.start_byte());
5475 let compile_proven = !direct
5476 && self.foreign_declaration_reachable_from_compile_proven_guards(
5477 file,
5478 prepared.as_ref(),
5479 peer.source(),
5480 &declaration_guards,
5481 reference.start_byte(),
5482 );
5483 if report_stats {
5484 eprintln!(
5485 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5486 peer.source().rel_path().display(),
5487 declaration_guards.len(),
5488 );
5489 }
5490 direct || compile_proven
5491 })
5492 });
5493 if directly_visible_without_reference_environment {
5494 if report_stats {
5495 eprintln!(
5496 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5497 candidate.fq_name(),
5498 );
5499 }
5500 return true;
5501 }
5502 let directly_visible = peers.iter().any(|peer| {
5503 declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5504 .into_iter()
5505 .any(|site| {
5506 let declaration_byte = site.byte;
5507 let declaration_guards = site.guards;
5508 if peer.source() == file {
5509 if declaration_byte >= reference.start_byte() {
5510 return false;
5511 }
5512 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5517 guard_requirements_hold_at_reference(&declaration_guards, guards)
5518 || (site.completed_family_before_reference
5519 && guards_compatible_at_reference(&declaration_guards, guards))
5520 };
5521 if !active(raw_reference_guards.as_ref()) {
5522 return false;
5523 }
5524 return active(reference_guards_at_site().as_ref())
5525 && self.preprocessor_guards_stable_between(
5526 file,
5527 declaration_byte,
5528 reference.start_byte(),
5529 &declaration_guards,
5530 );
5531 }
5532 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5533 file,
5534 prepared.as_ref(),
5535 peer.source(),
5536 &declaration_guards,
5537 raw_reference_guards.as_ref(),
5538 reference.start_byte(),
5539 );
5540 if report_stats {
5541 eprintln!(
5542 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5543 peer.source().rel_path().display(),
5544 declaration_guards.len(),
5545 );
5546 }
5547 if !raw_feasible {
5548 return false;
5549 }
5550 self.foreign_declaration_reachable_at_reference(
5551 file,
5552 prepared.as_ref(),
5553 peer.source(),
5554 &declaration_guards,
5555 reference_guards_at_site().as_ref(),
5556 reference.start_byte(),
5557 )
5558 })
5559 });
5560 if directly_visible {
5561 if report_stats {
5562 eprintln!(
5563 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5564 candidate.fq_name(),
5565 );
5566 }
5567 return true;
5568 }
5569 let complementary = self
5570 .visible_identifier_candidates(file, candidate.identifier())
5571 .filter(|peer| {
5572 peer.kind() == candidate.kind()
5573 && peer.fq_name() == candidate.fq_name()
5574 && peer.source() == candidate.source()
5575 })
5576 .collect::<Vec<_>>();
5577 let complementary_family =
5582 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5583 let raw_candidate_branch_compatible = complementary_family
5584 && raw_reference_guards.as_ref().is_some_and(|active| {
5585 declaration_guard_requirements(analyzer, self.cpp, candidate)
5586 .iter()
5587 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5588 });
5589 if report_stats {
5590 eprintln!(
5591 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5592 candidate.fq_name(),
5593 complementary.len(),
5594 complementary_family,
5595 raw_candidate_branch_compatible,
5596 );
5597 }
5598 let candidate_branch_compatible = raw_candidate_branch_compatible
5599 && reference_guards_at_site().as_ref().is_some_and(|active| {
5600 declaration_guard_requirements(analyzer, self.cpp, candidate)
5601 .iter()
5602 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5603 });
5604 let complementary_visible = candidate_branch_compatible
5605 && if candidate.source() == file {
5606 declaration_guard_requirements(analyzer, self.cpp, candidate)
5607 .iter()
5608 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5609 } else {
5610 self.include_activation_for_source(
5611 self.cpp,
5612 file,
5613 prepared.as_ref(),
5614 candidate.source(),
5615 )
5616 .is_some_and(|activation| activation <= reference.start_byte())
5617 };
5618 if report_stats {
5619 eprintln!(
5620 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5621 if complementary_visible {
5622 "complementary"
5623 } else {
5624 "missing"
5625 },
5626 candidate.fq_name(),
5627 );
5628 }
5629 complementary_visible
5630 }
5631
5632 pub fn is_exhaustive_same_fqn_type_declaration_family(
5633 &self,
5634 analyzer: &CppGraphSource<'_>,
5635 file: &ProjectFile,
5636 candidate: &CodeUnit,
5637 ) -> bool {
5638 let candidates = self
5639 .visible_identifier_candidates(file, candidate.identifier())
5640 .filter(|peer| {
5641 peer.kind() == candidate.kind()
5642 && peer.fq_name() == candidate.fq_name()
5643 && peer.source() == candidate.source()
5644 })
5645 .collect::<Vec<_>>();
5646 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5647 }
5648
5649 pub fn dependent_member_pointer_alias_visible_in_context(
5664 &self,
5665 analyzer: &CppGraphSource<'_>,
5666 file: &ProjectFile,
5667 candidate: &CodeUnit,
5668 owner_components: &[String],
5669 reference: Node<'_>,
5670 ) -> bool {
5671 if !analyzer
5672 .type_alias_provider()
5673 .is_some_and(|provider| provider.is_type_alias(candidate))
5674 {
5675 return false;
5676 }
5677 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5678 return false;
5679 };
5680 if terminal != candidate.identifier()
5681 || canonical_cpp_scope_components(candidate) != owner_components
5682 {
5683 return false;
5684 }
5685 let Some(expected_parent_fq_name) =
5686 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5687 else {
5688 return false;
5689 };
5690 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5691 return false;
5692 };
5693 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5694 || parent_anchor.source() != candidate.source()
5695 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5696 {
5697 return false;
5698 }
5699
5700 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5706 || candidate.source() == file
5707 && !analyzer
5708 .ranges(candidate)
5709 .iter()
5710 .any(|range| range.start_byte < reference.start_byte())
5711 {
5712 return false;
5713 }
5714
5715 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5716 if candidate_guards.is_empty() {
5717 return false;
5718 }
5719 let same_guard_sets =
5720 |left: &[(usize, HashSet<PreprocessorGuard>)],
5721 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5722 left.iter().all(|(_, left_guards)| {
5723 right
5724 .iter()
5725 .any(|(_, right_guards)| left_guards == right_guards)
5726 })
5727 };
5728 let parent_candidates = self
5729 .visible_identifier_candidates(file, parent_anchor.identifier())
5730 .filter(|peer| {
5731 peer.kind() == parent_anchor.kind()
5732 && peer.fq_name() == expected_parent_fq_name.as_str()
5733 && peer.source() == parent_anchor.source()
5734 && canonical_cpp_scope_components(peer) == owner_prefix
5735 })
5736 .filter_map(|peer| {
5737 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5738 (candidate_guards.len() == parent_guards.len()
5739 && same_guard_sets(&candidate_guards, &parent_guards)
5740 && same_guard_sets(&parent_guards, &candidate_guards))
5741 .then(|| (peer.clone(), parent_guards))
5742 })
5743 .collect::<Vec<_>>();
5744 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5745 return false;
5746 };
5747
5748 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5749 return false;
5750 };
5751 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5752 else {
5753 return false;
5754 };
5755 if !candidate_guards.iter().any(|(_, target_guards)| {
5760 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5761 && (candidate.source() != file
5762 || self.preprocessor_guards_stable_between(
5763 file,
5764 0,
5765 reference.start_byte(),
5766 target_guards,
5767 ))
5768 }) {
5769 return false;
5770 }
5771
5772 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5773 }
5774
5775 pub fn external_type_candidate_guard_compatible_in_context(
5785 &self,
5786 analyzer: &CppGraphSource<'_>,
5787 file: &ProjectFile,
5788 candidate: &CodeUnit,
5789 reference: Node<'_>,
5790 ) -> bool {
5791 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5792 return false;
5793 };
5794 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5795
5796 self.visible_identifier_candidates(file, candidate.identifier())
5797 .filter(|peer| same_logical_symbol(candidate, peer))
5798 .any(|peer| {
5799 declaration_guard_requirements(analyzer, self.cpp, peer)
5800 .into_iter()
5801 .any(|(declaration_byte, declaration_guards)| {
5802 if peer.source() == file {
5803 let (start, end) = if declaration_byte <= reference.start_byte() {
5804 (declaration_byte, reference.start_byte())
5805 } else {
5806 (reference.start_byte(), declaration_byte)
5807 };
5808 return guard_requirements_hold_at_reference(
5809 &declaration_guards,
5810 reference_guards.as_ref(),
5811 ) && self.preprocessor_guards_stable_between(
5812 file,
5813 start,
5814 end,
5815 &declaration_guards,
5816 );
5817 }
5818 self.foreign_declaration_reachable_at_reference(
5819 file,
5820 prepared.as_ref(),
5821 peer.source(),
5822 &declaration_guards,
5823 reference_guards.as_ref(),
5824 reference.start_byte(),
5825 )
5826 })
5827 })
5828 }
5829
5830 pub fn same_file_callable_guard_compatible_ignoring_order(
5838 &self,
5839 analyzer: &CppGraphSource<'_>,
5840 file: &ProjectFile,
5841 candidate: &CodeUnit,
5842 reference: Node<'_>,
5843 ) -> bool {
5844 if candidate.source() != file || !candidate.is_callable() {
5845 return false;
5846 }
5847 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5848 return false;
5849 };
5850 let guards = OnceCell::new();
5851 let context = CallableReferenceContext {
5852 file,
5853 position: Some(CallableReferencePosition {
5854 prepared: prepared.as_ref(),
5855 byte: reference.start_byte(),
5856 guards: &guards,
5857 }),
5858 };
5859 analyzer
5860 .declaration_source_occurrences(candidate)
5861 .into_iter()
5862 .flatten()
5863 .filter_map(|occurrence| {
5864 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5865 Some(callable_guard_requirements_are_visible(
5866 &guards,
5867 occurrence.fact.callable_guard_completion_byte,
5868 &context,
5869 ))
5870 })
5871 .any(|visible| visible)
5872 }
5873
5874 pub fn type_candidate_may_be_visible_before_reference(
5875 &self,
5876 analyzer: &CppGraphSource<'_>,
5877 file: &ProjectFile,
5878 candidate: &CodeUnit,
5879 reference_byte: usize,
5880 ) -> bool {
5881 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5882 return false;
5883 };
5884 let root = prepared.tree().root_node();
5885 let end_byte = reference_byte
5886 .saturating_add(1)
5887 .min(prepared.source().len());
5888 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5889 return false;
5890 };
5891 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5892 }
5893
5894 pub fn preprocessor_guards_stable_between(
5895 &self,
5896 file: &ProjectFile,
5897 start_byte: usize,
5898 end_byte: usize,
5899 guards: &HashSet<PreprocessorGuard>,
5900 ) -> bool {
5901 if guards.is_empty() || start_byte >= end_byte {
5902 return true;
5903 }
5904 let cell = self.macro_event_cell(file);
5905 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5906 let mut visited = HashSet::from_iter([file.clone()]);
5907 !events.iter().any(|event| {
5908 event.byte() >= start_byte
5909 && event.byte() < end_byte
5910 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5911 })
5912 }
5913
5914 fn macro_event_may_mutate_guards(
5915 &self,
5916 event: &MacroEvent,
5917 guards: &HashSet<PreprocessorGuard>,
5918 visited: &mut HashSet<ProjectFile>,
5919 ) -> bool {
5920 match event {
5921 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5922 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5923 }
5924 MacroEvent::Include { targets, .. } => {
5925 targets.is_empty()
5926 || targets
5927 .iter()
5928 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5929 }
5930 MacroEvent::Invalidate { .. } => true,
5931 }
5932 }
5933
5934 fn source_may_mutate_guards(
5935 &self,
5936 file: &ProjectFile,
5937 guards: &HashSet<PreprocessorGuard>,
5938 visited: &mut HashSet<ProjectFile>,
5939 ) -> bool {
5940 if !visited.insert(file.clone()) {
5941 return false;
5942 }
5943 let cell = self.macro_event_cell(file);
5944 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5945 events
5946 .iter()
5947 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5948 }
5949
5950 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5951 let normalized = normalize_reference_name(raw_name)?;
5952 self.type_candidates(file, &normalized)
5953 .into_iter()
5954 .next()
5955 .cloned()
5956 }
5957
5958 pub fn unique_visible_parameter_type_fallback(
5967 &self,
5968 analyzer: &CppGraphSource<'_>,
5969 file: &ProjectFile,
5970 node: Node<'_>,
5971 source: &str,
5972 ) -> Option<CodeUnit> {
5973 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5974 return None;
5975 }
5976 let name = node_text(node, source);
5977 let candidates = self
5978 .visible_identifier_candidates(file, name)
5979 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5980 .filter(|candidate| {
5981 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
5982 })
5983 .collect::<Vec<_>>();
5984 self.unique_canonical_type_candidate(analyzer, file, &candidates)
5985 }
5986
5987 pub fn resolve_type_node_result(
5988 &self,
5989 file: &ProjectFile,
5990 node: Node<'_>,
5991 source: &str,
5992 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
5993 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
5994 return Ok(None);
5995 };
5996 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
5997 return Ok(Some(primary));
5998 };
5999 self.resolve_template_arguments(file, primary, &arguments)
6000 .map(Some)
6001 }
6002
6003 pub fn resolve_type_node_primary(
6004 &self,
6005 file: &ProjectFile,
6006 node: Node<'_>,
6007 source: &str,
6008 ) -> Option<CodeUnit> {
6009 let components = cpp_type_name_components(node, source)?;
6010 self.resolve_type(file, &components.join("::"))
6011 }
6012
6013 pub fn resolve_template_arguments(
6014 &self,
6015 file: &ProjectFile,
6016 primary: CodeUnit,
6017 arguments: &[CppTemplateExpression],
6018 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6019 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6020 }
6021
6022 fn resolve_template_arguments_inner(
6023 &self,
6024 file: &ProjectFile,
6025 primary: CodeUnit,
6026 arguments: &[CppTemplateExpression],
6027 seen_aliases: &mut HashSet<CodeUnit>,
6028 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6029 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6030 && let Some(alias_target) = &metadata.alias_target
6031 {
6032 if !seen_aliases.insert(primary.clone()) {
6033 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6034 }
6035 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6036 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6037 let target_name = alias_target.components.join("::");
6038 let target_primary = if alias_target.global {
6039 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6040 } else {
6041 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6042 };
6043 let Some(target_primary) = target_primary else {
6044 return Ok(primary);
6048 };
6049 let Some(target_arguments) = &alias_target.arguments else {
6050 return Ok(target_primary);
6051 };
6052 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6053 .ok_or(CppTemplateResolutionError::Substitution)?;
6054 return self.resolve_template_arguments_inner(
6055 file,
6056 target_primary,
6057 &target_arguments,
6058 seen_aliases,
6059 );
6060 }
6061
6062 let primary_fq_name = self
6063 .cpp_template_metadata
6064 .get(&primary)
6065 .map(|metadata| metadata.primary_fq_name.clone())
6066 .unwrap_or_else(|| primary.fq_name());
6067 let has_specialization_metadata = self
6068 .cpp_template_families
6069 .get(&primary_fq_name)
6070 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6071 if !has_specialization_metadata {
6072 return Ok(primary);
6073 }
6074 self.select_template_specialization(file, &primary, arguments)
6075 }
6076
6077 fn select_template_specialization(
6078 &self,
6079 file: &ProjectFile,
6080 resolved: &CodeUnit,
6081 explicit_arguments: &[CppTemplateExpression],
6082 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6083 let primary_fq_name = self
6084 .cpp_template_metadata
6085 .get(resolved)
6086 .map(|metadata| metadata.primary_fq_name.clone())
6087 .unwrap_or_else(|| resolved.fq_name());
6088 let family = self
6089 .cpp_template_families
6090 .get(&primary_fq_name)
6091 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6092 let primary_candidates = family
6093 .iter()
6094 .filter_map(|unit| {
6095 let metadata = self.cpp_template_metadata.get(unit)?;
6096 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6097 })
6098 .collect::<Vec<_>>();
6099 let primary_unit = primary_candidates
6100 .iter()
6101 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6102 .or_else(|| {
6103 primary_candidates
6104 .iter()
6105 .map(|(unit, _)| *unit)
6106 .min_by_key(|unit| {
6107 (
6108 unit.source().to_string(),
6109 unit.signature().unwrap_or_default(),
6110 )
6111 })
6112 })
6113 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6114 let primary_parameters =
6115 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6116 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6117 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6118 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6119
6120 let mut applicable = Vec::new();
6121 for unit in family {
6122 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6123 continue;
6124 };
6125 if metadata.is_primary() || !self.is_visible(file, unit) {
6126 continue;
6127 }
6128 if !cpp_specialization_matches(metadata, &expanded) {
6129 continue;
6130 }
6131 applicable.push((unit, metadata));
6132 }
6133 if applicable.is_empty() {
6134 return Ok(primary_unit.clone());
6135 }
6136
6137 let winners = applicable
6142 .iter()
6143 .filter(|(candidate, candidate_metadata)| {
6144 applicable.iter().all(|(other, other_metadata)| {
6145 same_visible_symbol(candidate, other)
6146 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6147 })
6148 })
6149 .copied()
6150 .collect::<Vec<_>>();
6151 let Some((selected, _)) = winners.first() else {
6152 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6155 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6156 });
6157 };
6158 if winners
6159 .iter()
6160 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6161 {
6162 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6163 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6164 });
6165 }
6166 Ok((*selected).clone())
6167 }
6168
6169 pub fn resolve_type_components_lexically(
6170 &self,
6171 analyzer: &CppGraphSource<'_>,
6172 file: &ProjectFile,
6173 components: &[String],
6174 global: bool,
6175 lexical_scope: &[String],
6176 ) -> LexicalTypeResolution {
6177 self.resolve_type_components_lexically_inner(
6178 analyzer,
6179 file,
6180 components,
6181 global,
6182 lexical_scope,
6183 TypeCandidateResolution::Canonical,
6184 )
6185 }
6186
6187 pub fn resolve_type_components_lexically_for_forward(
6188 &self,
6189 analyzer: &CppGraphSource<'_>,
6190 file: &ProjectFile,
6191 components: &[String],
6192 global: bool,
6193 lexical_scope: &[String],
6194 ) -> LexicalTypeResolution {
6195 self.resolve_type_components_lexically_inner(
6196 analyzer,
6197 file,
6198 components,
6199 global,
6200 lexical_scope,
6201 TypeCandidateResolution::PreserveAlias,
6202 )
6203 }
6204
6205 pub fn resolve_type_components_lexically_for_target(
6206 &self,
6207 analyzer: &CppGraphSource<'_>,
6208 file: &ProjectFile,
6209 components: &[String],
6210 global: bool,
6211 lexical_scope: &[String],
6212 target: &CodeUnit,
6213 ) -> LexicalTypeResolution {
6214 #[cfg(any(test, feature = "test-support"))]
6215 self.target_preserving_type_resolution_count
6216 .fetch_add(1, Ordering::Relaxed);
6217 self.resolve_type_components_lexically_inner(
6218 analyzer,
6219 file,
6220 components,
6221 global,
6222 lexical_scope,
6223 TypeCandidateResolution::PreserveTarget(target),
6224 )
6225 }
6226
6227 pub fn coarse_unqualified_type_reference_may_resolve(
6228 &self,
6229 file: &ProjectFile,
6230 name: &str,
6231 ) -> bool {
6232 if name.is_empty() {
6233 return true;
6234 }
6235 self.visible_identifier_candidates(file, name)
6236 .any(|candidate| {
6237 candidate.kind() == CodeUnitType::Class
6238 || declared_type_alias(&self.cpp_source(), candidate)
6239 })
6240 || self.visible_parser_alias_name_is_visible(file, name)
6241 }
6242
6243 #[allow(clippy::too_many_arguments)]
6244 pub fn structured_type_reference_may_resolve_to_target(
6245 &self,
6246 analyzer: &CppGraphSource<'_>,
6247 file: &ProjectFile,
6248 components: &[String],
6249 global: bool,
6250 lexical_scope: &[String],
6251 target: &CodeUnit,
6252 ) -> bool {
6253 if components.is_empty() {
6254 return true;
6255 }
6256 let Some(terminal) = components.last() else {
6257 return true;
6258 };
6259 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6260 .map(|qualified| qualified.join("::"))
6261 .collect::<Vec<_>>();
6262 let target_name = cpp_name_for(target);
6263 if qualified_tiers
6264 .iter()
6265 .any(|qualified| qualified == &target_name)
6266 {
6267 return true;
6268 }
6269
6270 let mut saw_shape_candidate = false;
6271 for candidate in self.visible_identifier_candidates(file, terminal) {
6272 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6273 {
6274 continue;
6275 }
6276 let candidate_name = cpp_name_for(candidate);
6277 let shape_matches = if global || components.len() > 1 {
6278 qualified_tiers
6279 .iter()
6280 .any(|qualified| qualified == &candidate_name)
6281 } else {
6282 true
6283 };
6284 if !shape_matches {
6285 continue;
6286 }
6287 saw_shape_candidate = true;
6288 if same_visible_symbol(candidate, target)
6289 || self.c_tag_declaration_family_matches_target(
6290 analyzer,
6291 file,
6292 std::slice::from_ref(&candidate),
6293 target,
6294 )
6295 || self.compatible_primary_template_redeclarations(candidate, target)
6296 || (declared_type_alias(analyzer, candidate)
6297 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6298 {
6299 return true;
6300 }
6301 }
6302
6303 !saw_shape_candidate
6304 }
6305
6306 fn cached_c_tag_kind(
6313 &self,
6314 analyzer: &CppGraphSource<'_>,
6315 candidate: &CodeUnit,
6316 ) -> Option<CppCTagKind> {
6317 if let Some(kind) = self
6318 .c_tag_kind_cache
6319 .lock()
6320 .expect("C tag kind cache poisoned")
6321 .get(candidate)
6322 {
6323 return *kind;
6324 }
6325 let kind = indexed_c_tag_kind(analyzer, candidate);
6326 self.c_tag_kind_cache
6327 .lock()
6328 .expect("C tag kind cache poisoned")
6329 .insert(candidate.clone(), kind);
6330 kind
6331 }
6332
6333 fn cached_unique_c_tag_complete_definition(
6334 &self,
6335 analyzer: &CppGraphSource<'_>,
6336 target: &CodeUnit,
6337 target_tag: CppCTagKind,
6338 ) -> Option<CodeUnit> {
6339 if let Some(definition) = self
6340 .c_tag_complete_definition_cache
6341 .lock()
6342 .expect("C tag complete-definition cache poisoned")
6343 .get(target)
6344 {
6345 return definition.clone();
6346 }
6347 let complete_definitions = analyzer
6348 .definitions(&target.fq_name())
6349 .filter(|candidate| {
6350 candidate.is_class()
6351 && !declared_type_alias(analyzer, candidate)
6352 && is_c_source_file(candidate.source())
6353 && analyzer.parent_of(candidate).is_none()
6354 && cpp_class_declaration_strength(analyzer, candidate)
6355 == CppClassDeclarationStrength::Full
6356 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6357 })
6358 .collect::<HashSet<_>>();
6359 let definition = (complete_definitions.len() == 1)
6360 .then(|| complete_definitions.into_iter().next())
6361 .flatten()
6362 .filter(|candidate| same_visible_symbol(candidate, target));
6363 self.c_tag_complete_definition_cache
6364 .lock()
6365 .expect("C tag complete-definition cache poisoned")
6366 .insert(target.clone(), definition.clone());
6367 definition
6368 }
6369
6370 pub fn c_tag_declaration_family_matches_target(
6371 &self,
6372 analyzer: &CppGraphSource<'_>,
6373 visible_from: &ProjectFile,
6374 candidates: &[&CodeUnit],
6375 target: &CodeUnit,
6376 ) -> bool {
6377 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6378 let candidate_evidence = candidates
6379 .iter()
6380 .map(|candidate| {
6381 (
6382 candidate.fq_name(),
6383 candidate.source().rel_path().to_path_buf(),
6384 cpp_class_declaration_strength(analyzer, candidate),
6385 indexed_c_tag_kind(analyzer, candidate),
6386 self.is_physically_visible(visible_from, candidate),
6387 )
6388 })
6389 .collect::<Vec<_>>();
6390 eprintln!(
6391 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6392 visible_from.rel_path().display(),
6393 target.fq_name(),
6394 target.source().rel_path().display(),
6395 cpp_class_declaration_strength(analyzer, target),
6396 indexed_c_tag_kind(analyzer, target),
6397 );
6398 }
6399 if candidates.is_empty()
6400 || !target.is_class()
6401 || declared_type_alias(analyzer, target)
6402 || !is_c_source_file(target.source())
6403 || analyzer.parent_of(target).is_some()
6404 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6405 {
6406 return false;
6407 }
6408 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6409 return false;
6410 };
6411 if self
6412 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6413 .is_none()
6414 {
6415 return false;
6416 }
6417 let mut saw_visible_forward = false;
6418 for candidate in candidates.iter().copied() {
6419 if candidate == target {
6420 continue;
6421 }
6422 if !candidate.is_class()
6423 || declared_type_alias(analyzer, candidate)
6424 || candidate.fq_name() != target.fq_name()
6425 || analyzer.parent_of(candidate).is_some()
6426 || cpp_class_declaration_strength(analyzer, candidate)
6427 != CppClassDeclarationStrength::Forward
6428 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6429 || !self.is_physically_visible(visible_from, candidate)
6430 {
6431 return false;
6432 }
6433 saw_visible_forward = true;
6434 }
6435 saw_visible_forward
6436 }
6437
6438 fn unique_c_tag_declaration_family(
6443 &self,
6444 analyzer: &CppGraphSource<'_>,
6445 visible_from: &ProjectFile,
6446 candidates: &[&CodeUnit],
6447 ) -> Option<CodeUnit> {
6448 let first = candidates.first()?;
6449 let target_fq_name = first.fq_name();
6450 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6451 let mut full = None;
6452 let mut saw_forward = false;
6453 for candidate in candidates.iter().copied() {
6454 if !candidate.is_class()
6455 || declared_type_alias(analyzer, candidate)
6456 || candidate.fq_name() != target_fq_name
6457 || analyzer.parent_of(candidate).is_some()
6458 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6459 {
6460 return None;
6461 }
6462 match cpp_class_declaration_strength(analyzer, candidate) {
6463 CppClassDeclarationStrength::Full
6464 if is_c_source_file(candidate.source())
6465 && full.replace(candidate.clone()).is_none() => {}
6466 CppClassDeclarationStrength::Forward
6467 if self.is_physically_visible(visible_from, candidate) =>
6468 {
6469 saw_forward = true
6470 }
6471 _ => return None,
6472 }
6473 }
6474 if saw_forward { full } else { None }
6475 }
6476
6477 pub fn target_preserving_reference_namespace(
6478 &self,
6479 analyzer: &CppGraphSource<'_>,
6480 file: &ProjectFile,
6481 identifier: &str,
6482 target: &CodeUnit,
6483 ) -> Option<Vec<String>> {
6484 let mut namespace = None;
6485 for candidate in self.visible_identifier_candidates(file, identifier) {
6486 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6487 {
6488 continue;
6489 }
6490 if !(same_visible_symbol(candidate, target)
6491 || self.compatible_primary_template_redeclarations(candidate, target)
6492 || declared_type_alias(analyzer, candidate)
6493 && self.structured_alias_primary_preserves_target(
6494 analyzer, file, candidate, target,
6495 ))
6496 {
6497 continue;
6498 }
6499 if namespace
6500 .as_ref()
6501 .is_some_and(|existing| existing != candidate.package_name())
6502 {
6503 return None;
6504 }
6505 namespace = Some(candidate.package_name().to_string());
6506 }
6507 let namespace = namespace?;
6508 Some(
6509 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6510 brokk_bifrost_core::analyzer::Language::Cpp,
6511 &namespace,
6512 ),
6513 )
6514 }
6515
6516 pub fn resolve_imported_type_candidate(
6517 &self,
6518 analyzer: &CppGraphSource<'_>,
6519 file: &ProjectFile,
6520 target: &CodeUnit,
6521 target_components: &[String],
6522 direct_target: Option<&CodeUnit>,
6523 preserve_alias: bool,
6524 ) -> LexicalTypeResolution {
6525 let candidates = [target];
6526 let resolution = if preserve_alias {
6527 TypeCandidateResolution::PreserveAlias
6528 } else {
6529 direct_target.map_or(
6530 TypeCandidateResolution::Canonical,
6531 TypeCandidateResolution::PreserveTarget,
6532 )
6533 };
6534 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6538 Ok(unit) => LexicalTypeResolution::Resolved {
6539 unit,
6540 components: target_components.to_vec(),
6541 candidates: vec![target.clone()],
6542 },
6543 Err(failure) => failure.lexical_resolution(),
6544 }
6545 }
6546
6547 fn resolve_type_components_lexically_inner(
6548 &self,
6549 analyzer: &CppGraphSource<'_>,
6550 file: &ProjectFile,
6551 components: &[String],
6552 global: bool,
6553 lexical_scope: &[String],
6554 resolution: TypeCandidateResolution<'_>,
6555 ) -> LexicalTypeResolution {
6556 if components.is_empty() {
6557 return LexicalTypeResolution::Missing;
6558 }
6559 let mut injected = self.resolve_injected_class_name(
6569 analyzer,
6570 file,
6571 components,
6572 global,
6573 lexical_scope,
6574 resolution,
6575 );
6576 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6577 let prefix_len = qualified.len().saturating_sub(components.len());
6578 if injected
6579 .as_ref()
6580 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6581 {
6582 return injected
6583 .take()
6584 .expect("injected class resolution was just present")
6585 .1;
6586 }
6587 let qualified_name = qualified.join("::");
6588 let candidates = self
6589 .type_candidates(file, &qualified_name)
6590 .into_iter()
6591 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6592 .collect::<Vec<_>>();
6593 if candidates.is_empty() {
6594 if !global && components.len() == 1 {
6595 match self.resolve_inherited_type_for_lexical_scope(
6596 analyzer,
6597 file,
6598 &qualified[..prefix_len],
6599 &components[0],
6600 resolution,
6601 ) {
6602 LexicalTypeResolution::Missing => {}
6603 inherited => return inherited,
6604 }
6605 }
6606 continue;
6607 }
6608 let candidates =
6609 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6610 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6611 Ok(unit) => unit,
6612 Err(failure) => return failure.lexical_resolution(),
6613 };
6614 return LexicalTypeResolution::Resolved {
6615 unit,
6616 components: qualified,
6617 candidates: candidates.into_iter().cloned().collect(),
6618 };
6619 }
6620 LexicalTypeResolution::Missing
6621 }
6622
6623 fn resolve_injected_class_name(
6624 &self,
6625 analyzer: &CppGraphSource<'_>,
6626 file: &ProjectFile,
6627 components: &[String],
6628 global: bool,
6629 lexical_scope: &[String],
6630 resolution: TypeCandidateResolution<'_>,
6631 ) -> Option<(usize, LexicalTypeResolution)> {
6632 if global
6633 || components.len() != 1
6634 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6635 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6636 {
6637 return None;
6638 }
6639 let name = components.first()?;
6640 let mut matches: Vec<&CodeUnit> = Vec::new();
6641 let mut owner_len = 0;
6642 for candidate in self.visible_identifier_candidates(file, name) {
6643 if !candidate.is_class()
6644 || declared_type_alias(analyzer, candidate)
6645 || candidate.identifier() != name
6646 {
6647 continue;
6648 }
6649 let candidate_scope = canonical_cpp_scope_components(candidate);
6650 if candidate_scope.len() > lexical_scope.len()
6651 || !lexical_scope.starts_with(&candidate_scope)
6652 || candidate_scope.last().is_none_or(|last| last != name)
6653 {
6654 continue;
6655 }
6656 if candidate_scope.len() > owner_len {
6657 owner_len = candidate_scope.len();
6658 matches.clear();
6659 }
6660 if candidate_scope.len() == owner_len
6661 && !matches
6662 .iter()
6663 .any(|existing| same_logical_symbol(existing, candidate))
6664 {
6665 matches.push(candidate);
6666 }
6667 }
6668 if matches.is_empty() {
6669 return None;
6670 }
6671 let owner_components = lexical_scope[..owner_len].to_vec();
6678 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6679 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6680 Ok(unit) => LexicalTypeResolution::Resolved {
6681 unit,
6682 components: owner_components,
6683 candidates: matches.into_iter().cloned().collect(),
6684 },
6685 Err(failure) => failure.lexical_resolution(),
6686 };
6687 Some((owner_len, resolution))
6688 }
6689
6690 fn resolve_inherited_type_for_lexical_scope(
6691 &self,
6692 analyzer: &CppGraphSource<'_>,
6693 file: &ProjectFile,
6694 lexical_scope: &[String],
6695 name: &str,
6696 resolution: TypeCandidateResolution<'_>,
6697 ) -> LexicalTypeResolution {
6698 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6699 return LexicalTypeResolution::Missing;
6700 };
6701 let lexical_owner_name = lexical_scope.join("::");
6702 if lexical_owner_name.is_empty() {
6703 return LexicalTypeResolution::Missing;
6704 }
6705 let owner_candidates = self
6706 .type_candidates(file, &lexical_owner_name)
6707 .into_iter()
6708 .filter(|candidate| {
6709 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6710 && !declared_type_alias(analyzer, candidate)
6711 })
6712 .collect::<Vec<_>>();
6713 if owner_candidates.is_empty() {
6714 return LexicalTypeResolution::Missing;
6715 }
6716 let physical_owner_candidates = owner_candidates
6721 .iter()
6722 .copied()
6723 .filter(|candidate| candidate.source() == file)
6724 .collect::<Vec<_>>();
6725 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6726 owner_candidates
6727 } else {
6728 physical_owner_candidates
6729 };
6730 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6731 return LexicalTypeResolution::Ambiguous;
6732 };
6733
6734 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6735 let mut visited_owners = HashSet::default();
6736 while !frontier.is_empty() {
6737 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6738 let mut next_frontier = Vec::new();
6739 for owner in frontier {
6740 if !visited_owners.insert(owner.fq_name()) {
6741 continue;
6742 }
6743 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6744 let candidates = self
6745 .type_candidates(file, &qualified_name)
6746 .into_iter()
6747 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6748 .collect::<Vec<_>>();
6749 if candidates.is_empty() {
6750 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6751 if !next_frontier
6752 .iter()
6753 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6754 {
6755 next_frontier.push(ancestor);
6756 }
6757 }
6758 continue;
6759 }
6760 let candidates =
6761 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6762 let unit =
6763 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6764 Ok(unit) => unit,
6765 Err(failure) => return failure.lexical_resolution(),
6766 };
6767 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6768 }
6769 if let Some((unit, candidates)) = level_matches.first().cloned() {
6770 let Some(first_declaration) = candidates.first() else {
6771 return LexicalTypeResolution::Ambiguous;
6772 };
6773 if !level_matches.iter().all(|(_, declarations)| {
6774 declarations
6775 .iter()
6776 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6777 }) {
6778 return LexicalTypeResolution::Ambiguous;
6779 }
6780 let mut components = lexical_scope.to_vec();
6781 components.push(name.to_string());
6782 return LexicalTypeResolution::Resolved {
6783 unit,
6784 components,
6785 candidates,
6786 };
6787 }
6788 frontier = next_frontier;
6789 }
6790 LexicalTypeResolution::Missing
6791 }
6792
6793 pub fn inherited_injected_class_owner(
6810 &self,
6811 analyzer: &CppGraphSource<'_>,
6812 file: &ProjectFile,
6813 enclosing_owner: &CodeUnit,
6814 injected_name: &str,
6815 ) -> Option<CodeUnit> {
6816 let hierarchy = analyzer.type_hierarchy_provider()?;
6817 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6818 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6819 while !frontier.is_empty() {
6820 let mut level_matches = Vec::new();
6821 let mut next_frontier = Vec::new();
6822 for raw_owner in frontier {
6823 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6824 else {
6825 if raw_owner.identifier() == injected_name {
6826 return None;
6827 }
6828 continue;
6829 };
6830 let propagated = propagated_counts.entry(owner.clone()).or_default();
6831 if *propagated == 2 {
6832 continue;
6833 }
6834 *propagated += 1;
6835 if owner.identifier() == injected_name {
6836 level_matches.push(owner.clone());
6837 }
6838 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6839 }
6840 match level_matches.as_slice() {
6841 [owner] => return Some(owner.clone()),
6842 [_, ..] => return None,
6843 [] => {}
6844 }
6845 frontier = next_frontier;
6846 }
6847 None
6848 }
6849
6850 fn resolve_type_candidates(
6855 &self,
6856 analyzer: &CppGraphSource<'_>,
6857 file: &ProjectFile,
6858 candidates: &[&CodeUnit],
6859 resolution: TypeCandidateResolution<'_>,
6860 ) -> Result<CodeUnit, TypeCandidateFailure> {
6861 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6862 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6863 {
6864 return Ok(unit);
6865 }
6866 match resolution {
6867 TypeCandidateResolution::Canonical => {
6868 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6869 }
6870 TypeCandidateResolution::PreserveAlias => {
6871 let same_fqn_alias_family = candidates.len() > 1
6878 && candidates.iter().all(|candidate| {
6879 declared_type_alias(analyzer, candidate)
6880 && same_logical_symbol(candidates[0], candidate)
6881 })
6882 && candidates
6883 .iter()
6884 .any(|candidate| candidate.source() != candidates[0].source());
6885 if same_fqn_alias_family {
6886 let physically_visible = candidates
6887 .iter()
6888 .copied()
6889 .filter(|candidate| self.is_physically_visible(file, candidate))
6890 .collect::<Vec<_>>();
6891 if let Some(alias) = select_alias_spelling_with_agreed_targets(
6901 &physically_visible,
6902 |candidate| self.structured_alias_target(analyzer, candidate),
6903 ) {
6904 return Ok(alias.clone());
6905 }
6906 }
6907 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6908 .ok_or(TypeCandidateFailure::Ambiguous)
6909 }
6910 TypeCandidateResolution::PreserveTarget(target) => self
6911 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6912 .ok_or(TypeCandidateFailure::Ambiguous),
6913 }
6914 }
6915
6916 fn candidates_for_type_resolution<'b>(
6917 &self,
6918 analyzer: &CppGraphSource<'_>,
6919 file: &ProjectFile,
6920 candidates: &[&'b CodeUnit],
6921 resolution: TypeCandidateResolution<'_>,
6922 ) -> Vec<&'b CodeUnit> {
6923 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6924 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6925 if compile_proven.len() == 1 {
6926 return compile_proven;
6927 }
6928 }
6929 candidates.to_vec()
6930 }
6931
6932 fn compile_proven_type_candidates<'b>(
6939 &self,
6940 analyzer: &CppGraphSource<'_>,
6941 file: &ProjectFile,
6942 candidates: &[&'b CodeUnit],
6943 ) -> Vec<&'b CodeUnit> {
6944 let proven = self.compile_proven_guards(file);
6945 if proven.is_empty() {
6946 return Vec::new();
6947 }
6948 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6949 return Vec::new();
6950 };
6951 candidates
6952 .iter()
6953 .copied()
6954 .filter(|candidate| {
6955 let declaration_guards =
6956 declaration_guard_requirements(analyzer, self.cpp, candidate);
6957 if declaration_guards.is_empty() {
6958 return false;
6959 }
6960 if candidate.source() == file {
6961 return declaration_guards.iter().any(|(_, required)| {
6962 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6963 });
6964 }
6965 declaration_guards.iter().any(|(_, required)| {
6966 self.foreign_declaration_reachable_from_compile_proven_guards(
6967 file,
6968 prepared.as_ref(),
6969 candidate.source(),
6970 required,
6971 usize::MAX,
6972 )
6973 })
6974 })
6975 .collect()
6976 }
6977
6978 pub fn resolve_callable_value_components_lexically(
6979 &self,
6980 analyzer: &CppGraphSource<'_>,
6981 file: &ProjectFile,
6982 owner_components: &[String],
6983 member_name: &str,
6984 global: bool,
6985 lexical_scope: &[String],
6986 ) -> LexicalCallableValueResolution {
6987 if owner_components.is_empty() || member_name.is_empty() {
6988 return LexicalCallableValueResolution::Missing;
6989 }
6990 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
6991 let owner_name = qualified_owner.join("::");
6992 let type_candidates = self
6993 .type_candidates(file, &owner_name)
6994 .into_iter()
6995 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
6996 .collect::<Vec<_>>();
6997 let resolved_type = if type_candidates.is_empty() {
6998 None
6999 } else {
7000 let Some(unit) =
7001 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7002 else {
7003 return LexicalCallableValueResolution::Ambiguous;
7004 };
7005 Some(unit)
7006 };
7007
7008 let mut qualified_callable = qualified_owner;
7009 qualified_callable.push(member_name.to_string());
7010 let callable_name = qualified_callable.join("::");
7011 let free_function = self
7012 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7013 .into_iter()
7014 .find(|candidate| {
7015 canonical_cpp_name_matches(candidate, &callable_name)
7016 && type_owner_of(analyzer, candidate).is_none()
7017 })
7018 .cloned();
7019
7020 match (resolved_type, free_function) {
7021 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7022 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7023 (None, Some(function)) => {
7024 return LexicalCallableValueResolution::FreeFunction(function);
7025 }
7026 (None, None) => {}
7027 }
7028 }
7029 LexicalCallableValueResolution::Missing
7030 }
7031
7032 fn resolve_type_for_declaration(
7033 &self,
7034 visible_from: &ProjectFile,
7035 declaration: &CodeUnit,
7036 raw_name: &str,
7037 ) -> Option<CodeUnit> {
7038 let normalized = normalize_reference_name(raw_name)?;
7039 if !normalized.contains("::")
7040 && let Some(namespace) = cpp_namespace_for(declaration)
7041 {
7042 for prefix in namespace_prefixes(&namespace) {
7043 let qualified = format!("{prefix}::{normalized}");
7044 if let Some(unit) = self
7045 .type_candidates(visible_from, &qualified)
7046 .into_iter()
7047 .next()
7048 {
7049 return Some(unit.clone());
7050 }
7051 }
7052 }
7053 self.resolve_type(visible_from, raw_name)
7054 }
7055
7056 fn resolve_unique_canonical_type_for_declaration(
7057 &self,
7058 analyzer: &CppGraphSource<'_>,
7059 visible_from: &ProjectFile,
7060 declaration: &CodeUnit,
7061 raw_name: &str,
7062 ) -> Option<CodeUnit> {
7063 let current = self.resolve_defining_type_for_declaration(
7064 analyzer,
7065 visible_from,
7066 declaration,
7067 raw_name,
7068 )?;
7069 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7070 .ok()
7071 }
7072
7073 pub fn canonical_type_unit(
7074 &self,
7075 analyzer: &CppGraphSource<'_>,
7076 visible_from: &ProjectFile,
7077 unit: &CodeUnit,
7078 ) -> Option<CodeUnit> {
7079 self.canonical_type_resolution(analyzer, visible_from, unit)
7080 .ok()
7081 }
7082
7083 pub fn canonical_type_unit_in_context(
7091 &self,
7092 analyzer: &CppGraphSource<'_>,
7093 visible_from: &ProjectFile,
7094 reference: Node<'_>,
7095 unit: &CodeUnit,
7096 ) -> Option<CodeUnit> {
7097 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7098 {
7099 return None;
7100 }
7101 self.canonical_type_resolution(analyzer, visible_from, unit)
7102 .ok()
7103 }
7104
7105 fn canonical_type_resolution(
7113 &self,
7114 analyzer: &CppGraphSource<'_>,
7115 visible_from: &ProjectFile,
7116 unit: &CodeUnit,
7117 ) -> Result<CodeUnit, TypeCandidateFailure> {
7118 let mut current = unit.clone();
7119 let mut seen_aliases = HashSet::default();
7120 loop {
7121 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7122 return current
7123 .is_class()
7124 .then_some(current)
7125 .ok_or(TypeCandidateFailure::Unresolvable);
7126 };
7127 if matches!(target, StructuredAliasTarget::Builtin) {
7128 return current
7129 .is_class()
7130 .then_some(current)
7131 .ok_or(TypeCandidateFailure::Unresolvable);
7132 }
7133 if !seen_aliases.insert(current.clone()) {
7134 return Err(TypeCandidateFailure::Unresolvable);
7135 }
7136 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7137 }
7138 }
7139
7140 pub fn canonical_visible_full_type_unit(
7141 &self,
7142 analyzer: &CppGraphSource<'_>,
7143 visible_from: &ProjectFile,
7144 unit: &CodeUnit,
7145 ) -> Option<CodeUnit> {
7146 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7147 if cpp_class_declaration_strength(analyzer, &canonical)
7148 != CppClassDeclarationStrength::Forward
7149 {
7150 return Some(canonical);
7151 }
7152 let mut full = Vec::new();
7153 for candidate in self
7154 .visible_identifier_candidates(visible_from, canonical.identifier())
7155 .filter(|candidate| {
7156 candidate.is_class()
7157 && candidate.fq_name() == canonical.fq_name()
7158 && cpp_class_declaration_strength(analyzer, candidate)
7159 == CppClassDeclarationStrength::Full
7160 })
7161 {
7162 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7163 full.push(candidate.clone());
7164 }
7165 }
7166 match full.len() {
7167 0 => Some(canonical),
7168 1 => full.pop(),
7169 _ => None,
7170 }
7171 }
7172
7173 fn resolve_structured_alias_target(
7174 &self,
7175 visible_from: &ProjectFile,
7176 declaration: &CodeUnit,
7177 target: &StructuredAliasTarget,
7178 ) -> Option<CodeUnit> {
7179 self.structured_alias_target_resolution(visible_from, declaration, target)
7180 .ok()
7181 }
7182
7183 fn structured_alias_target_resolution(
7184 &self,
7185 visible_from: &ProjectFile,
7186 declaration: &CodeUnit,
7187 target: &StructuredAliasTarget,
7188 ) -> Result<CodeUnit, TypeCandidateFailure> {
7189 let primary =
7190 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7191 let StructuredAliasTarget::Named { arguments, .. } = target else {
7192 return Err(TypeCandidateFailure::Unresolvable);
7193 };
7194 match arguments {
7195 Some(arguments) => self
7196 .resolve_template_arguments(visible_from, primary, arguments)
7197 .map_err(|error| match error {
7198 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7199 TypeCandidateFailure::Ambiguous
7200 }
7201 _ => TypeCandidateFailure::Unresolvable,
7202 }),
7203 None => Ok(primary),
7204 }
7205 }
7206
7207 fn resolve_structured_alias_primary(
7208 &self,
7209 visible_from: &ProjectFile,
7210 declaration: &CodeUnit,
7211 target: &StructuredAliasTarget,
7212 ) -> Option<CodeUnit> {
7213 self.structured_alias_primary_resolution(visible_from, declaration, target)
7214 .ok()
7215 }
7216
7217 fn structured_alias_primary_resolution(
7218 &self,
7219 visible_from: &ProjectFile,
7220 declaration: &CodeUnit,
7221 target: &StructuredAliasTarget,
7222 ) -> Result<CodeUnit, TypeCandidateFailure> {
7223 let StructuredAliasTarget::Named {
7224 components, global, ..
7225 } = target
7226 else {
7227 return Err(TypeCandidateFailure::Unresolvable);
7228 };
7229 let qualified = components.join("::");
7230 let candidates = if *global {
7231 let mut candidates = self.type_candidates(visible_from, &qualified);
7238 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7239 candidates
7240 } else {
7241 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7242 };
7243 logical_type_candidate(candidates)
7244 }
7245
7246 pub fn structured_alias_primary_preserves_target(
7247 &self,
7248 analyzer: &CppGraphSource<'_>,
7249 visible_from: &ProjectFile,
7250 candidate: &CodeUnit,
7251 target: &CodeUnit,
7252 ) -> bool {
7253 let mut current = candidate.clone();
7254 let mut seen = HashSet::default();
7255 let mut matched_target = false;
7256 loop {
7257 if same_visible_symbol(¤t, target)
7258 || self.compatible_primary_template_redeclarations(¤t, target)
7259 {
7260 matched_target = true;
7261 }
7262 if !seen.insert(current.clone()) {
7263 return false;
7264 }
7265 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7266 return matched_target;
7267 };
7268 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7269 return matched_target;
7270 };
7271 let Some(primary) =
7272 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7273 else {
7274 return matched_target;
7280 };
7281 current = primary;
7282 }
7283 }
7284
7285 fn lexical_type_candidates_match_target(
7286 &self,
7287 analyzer: &CppGraphSource<'_>,
7288 unit: &CodeUnit,
7289 candidates: &[CodeUnit],
7290 target: &CodeUnit,
7291 ) -> bool {
7292 same_visible_symbol(unit, target)
7293 || self.same_template_member_identity(analyzer, unit, target)
7294 || candidates.iter().any(|candidate| {
7295 same_visible_symbol(candidate, target)
7296 || self.same_template_member_identity(analyzer, candidate, target)
7297 })
7298 }
7299
7300 pub fn structured_class_alias_resolves_to_target(
7301 &self,
7302 analyzer: &CppGraphSource<'_>,
7303 visible_from: &ProjectFile,
7304 alias: &CodeUnit,
7305 target: &CodeUnit,
7306 ) -> bool {
7307 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7308 return false;
7309 };
7310 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7311 return false;
7312 };
7313 let StructuredAliasTarget::Named {
7314 components, global, ..
7315 } = &alias_target
7316 else {
7317 return false;
7318 };
7319 let lexical_scope = canonical_cpp_scope_components(&owner);
7320 match self.resolve_type_components_lexically_for_target(
7321 analyzer,
7322 visible_from,
7323 components,
7324 *global,
7325 &lexical_scope,
7326 target,
7327 ) {
7328 LexicalTypeResolution::Resolved {
7329 unit, candidates, ..
7330 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7331 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7332 self.structured_alias_primary_preserves_target(
7333 analyzer,
7334 visible_from,
7335 alias,
7336 target,
7337 ) || self.flattened_macro_namespace_alias_target_matches(
7338 analyzer,
7339 visible_from,
7340 alias,
7341 &alias_target,
7342 target,
7343 )
7344 }
7345 }
7346 }
7347
7348 pub fn structured_class_alias_path_preserves_target(
7356 &self,
7357 analyzer: &CppGraphSource<'_>,
7358 visible_from: &ProjectFile,
7359 alias: &CodeUnit,
7360 target: &CodeUnit,
7361 ) -> bool {
7362 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7363 return false;
7364 };
7365 let Some(StructuredAliasTarget::Named {
7366 components, global, ..
7367 }) = self.structured_alias_target(analyzer, alias)
7368 else {
7369 return false;
7370 };
7371 let lexical_scope = canonical_cpp_scope_components(&owner);
7372 (1..components.len()).rev().any(|component_count| {
7373 matches!(
7374 self.resolve_type_components_lexically_for_target(
7375 analyzer,
7376 visible_from,
7377 &components[..component_count],
7378 global,
7379 &lexical_scope,
7380 target,
7381 ),
7382 LexicalTypeResolution::Resolved {
7383 ref unit,
7384 ref candidates,
7385 ..
7386 } if self.lexical_type_candidates_match_target(
7387 analyzer,
7388 unit,
7389 candidates,
7390 target,
7391 )
7392 )
7393 })
7394 }
7395
7396 fn flattened_macro_namespace_alias_target_matches(
7397 &self,
7398 analyzer: &CppGraphSource<'_>,
7399 visible_from: &ProjectFile,
7400 alias: &CodeUnit,
7401 alias_target: &StructuredAliasTarget,
7402 target: &CodeUnit,
7403 ) -> bool {
7404 let StructuredAliasTarget::Named {
7405 components,
7406 global: false,
7407 arguments: None,
7408 } = alias_target
7409 else {
7410 return false;
7411 };
7412 let Some((target_name, namespace_components)) = components.split_last() else {
7413 return false;
7414 };
7415 if namespace_components.is_empty()
7416 || target_name != target.identifier()
7417 || alias.source() != target.source()
7418 || alias.source() != visible_from
7419 || !target.is_class()
7420 || declared_type_alias(analyzer, target)
7421 {
7422 return false;
7423 }
7424 if self
7425 .resolve_structured_alias_target(visible_from, alias, alias_target)
7426 .is_some()
7427 {
7428 return false;
7429 }
7430
7431 let Some(alias_start) = analyzer
7432 .declaration_source_occurrences(alias)
7433 .into_iter()
7434 .flatten()
7435 .map(|occurrence| occurrence.range.start_byte)
7436 .min()
7437 else {
7438 return false;
7439 };
7440 let has_matching_declaration = analyzer
7441 .declaration_source_occurrences(target)
7442 .into_iter()
7443 .flatten()
7444 .any(|occurrence| {
7445 occurrence.range.end_byte <= alias_start
7446 && occurrence.fact.flattened_macro_namespace.as_deref()
7447 == Some(namespace_components)
7448 });
7449 if !has_matching_declaration {
7450 return false;
7451 }
7452
7453 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7454 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7455 guard_requirement_sets_match(&alias_guards, &target_guards)
7456 }
7457
7458 pub fn template_alias_arguments_preserve_target(
7459 &self,
7460 analyzer: &CppGraphSource<'_>,
7461 visible_from: &ProjectFile,
7462 alias: &CodeUnit,
7463 arguments: &[CppTemplateExpression],
7464 target: &CodeUnit,
7465 ) -> bool {
7466 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7467 return false;
7468 };
7469 if metadata.alias_target.is_none()
7470 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7471 {
7472 return false;
7473 }
7474 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7475 }
7476
7477 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7478 self.cpp_template_metadata
7479 .get(unit)
7480 .is_some_and(CppTemplateMetadata::is_primary)
7481 }
7482
7483 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7484 self.cpp_template_metadata
7485 .get(unit)
7486 .is_some_and(CppTemplateMetadata::is_specialization)
7487 }
7488
7489 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7490 same_visible_symbol(left, right)
7491 || self.compatible_primary_template_redeclarations(left, right)
7492 }
7493
7494 pub fn same_template_member_identity(
7495 &self,
7496 analyzer: &CppGraphSource<'_>,
7497 left: &CodeUnit,
7498 right: &CodeUnit,
7499 ) -> bool {
7500 if same_visible_symbol(left, right) {
7501 return true;
7502 }
7503 if left.kind() != right.kind()
7504 || left.identifier() != right.identifier()
7505 || left.signature() != right.signature()
7506 {
7507 return false;
7508 }
7509 let (Some(left_owner), Some(right_owner)) =
7510 (analyzer.parent_of(left), analyzer.parent_of(right))
7511 else {
7512 return false;
7513 };
7514 left_owner.is_class()
7515 && right_owner.is_class()
7516 && self.same_template_owner_identity(&left_owner, &right_owner)
7517 }
7518
7519 fn unique_canonical_type_candidate(
7520 &self,
7521 analyzer: &CppGraphSource<'_>,
7522 visible_from: &ProjectFile,
7523 candidates: &[&CodeUnit],
7524 ) -> Option<CodeUnit> {
7525 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7526 .ok()
7527 }
7528
7529 fn canonical_type_candidate_resolution(
7530 &self,
7531 analyzer: &CppGraphSource<'_>,
7532 visible_from: &ProjectFile,
7533 candidates: &[&CodeUnit],
7534 ) -> Result<CodeUnit, TypeCandidateFailure> {
7535 let mut canonical = Vec::new();
7536 for candidate in candidates {
7537 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7538 if canonical
7539 .iter()
7540 .any(|existing| same_visible_symbol(existing, &resolved))
7541 {
7542 continue;
7543 }
7544 if let Some(existing) = canonical.iter_mut().find(|existing| {
7545 self.compatible_primary_template_redeclarations(existing, &resolved)
7546 }) {
7547 if matches!(
7556 (
7557 cpp_class_declaration_strength(analyzer, existing),
7558 cpp_class_declaration_strength(analyzer, &resolved),
7559 ),
7560 (
7561 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7562 CppClassDeclarationStrength::Full,
7563 ) | (
7564 CppClassDeclarationStrength::Unknown,
7565 CppClassDeclarationStrength::Forward,
7566 )
7567 ) {
7568 *existing = resolved;
7569 }
7570 continue;
7571 }
7572 canonical.push(resolved);
7573 if canonical.len() > 1 {
7574 return Err(TypeCandidateFailure::Ambiguous);
7575 }
7576 }
7577 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7578 }
7579
7580 pub fn unique_type_candidate_preserving_target(
7581 &self,
7582 analyzer: &CppGraphSource<'_>,
7583 visible_from: &ProjectFile,
7584 candidates: &[&CodeUnit],
7585 target: &CodeUnit,
7586 ) -> Option<CodeUnit> {
7587 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7598 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7599 {
7600 return Some(target.clone());
7601 }
7602 let mut resolved_candidates = Vec::new();
7603 for candidate in candidates {
7604 let Some(resolved) =
7610 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7611 else {
7612 continue;
7613 };
7614 if resolved_candidates
7615 .iter()
7616 .any(|existing| same_visible_symbol(existing, &resolved))
7617 {
7618 continue;
7619 }
7620 resolved_candidates.push(resolved);
7621 }
7622 match resolved_candidates.as_slice() {
7623 [] => None,
7624 [single] => Some(single.clone()),
7625 _ => self
7630 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7631 .map(|_| target.clone()),
7632 }
7633 }
7634
7635 pub fn same_fqn_type_spelling_for_target<'b>(
7652 &self,
7653 analyzer: &CppGraphSource<'_>,
7654 visible_from: &ProjectFile,
7655 candidates: &[&'b CodeUnit],
7656 target: &CodeUnit,
7657 ) -> Option<&'b CodeUnit> {
7658 let [first, rest @ ..] = candidates else {
7659 return None;
7660 };
7661 if rest.is_empty()
7662 || !rest.iter().all(|candidate| {
7663 candidate.kind() == first.kind()
7664 && candidate.fq_name() == first.fq_name()
7665 && candidate.source() == first.source()
7666 })
7667 {
7668 return None;
7669 }
7670 candidates
7671 .iter()
7672 .copied()
7673 .find(|candidate| same_symbol(candidate, target))
7674 .or_else(|| {
7675 candidates.iter().copied().find(|candidate| {
7676 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7677 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7678 })
7679 })
7680 }
7681
7682 pub fn alternate_same_fqn_type_declarations(
7683 &self,
7684 analyzer: &CppGraphSource<'_>,
7685 candidates: &[&CodeUnit],
7686 target: &CodeUnit,
7687 ) -> bool {
7688 let Some(first) = candidates.first() else {
7689 return false;
7690 };
7691 let same_api = first.kind() == target.kind()
7692 && first.fq_name() == target.fq_name()
7693 && first.source() == target.source()
7694 && candidates.iter().all(|candidate| {
7695 candidate.kind() == target.kind()
7696 && candidate.fq_name() == target.fq_name()
7697 && candidate.source() == target.source()
7698 })
7699 && candidates
7700 .iter()
7701 .any(|candidate| same_symbol(candidate, target))
7702 && candidates
7703 .iter()
7704 .any(|candidate| !same_logical_symbol(candidate, target));
7705 if !same_api {
7706 return false;
7707 }
7708
7709 let requirements = candidates
7710 .iter()
7711 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7712 .collect::<Vec<_>>();
7713 requirements.len() > 1
7714 && requirements
7715 .iter()
7716 .all(|requirement| !requirement.is_empty())
7717 && requirements.iter().enumerate().all(|(index, left)| {
7718 requirements[index + 1..].iter().all(|right| {
7719 left.iter().all(|(_, left_guards)| {
7720 right.iter().all(|(_, right_guards)| {
7721 merge_preprocessor_guards(left_guards, right_guards).is_none()
7722 })
7723 })
7724 })
7725 })
7726 }
7727
7728 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7729 let mut pending = vec![terms.to_vec()];
7730 while let Some(branch_terms) = pending.pop() {
7731 let mut normalized = Vec::new();
7732 let mut covers_branch = false;
7733 for term in branch_terms {
7734 if term.iter().any(|guard| term.contains(&guard.negated())) {
7735 continue;
7736 }
7737 if term.is_empty() {
7738 covers_branch = true;
7739 break;
7740 }
7741 if !normalized.iter().any(|existing| existing == &term) {
7742 normalized.push(term);
7743 }
7744 }
7745 if covers_branch {
7746 continue;
7747 }
7748 let Some(split_guard) = normalized
7749 .iter()
7750 .flat_map(|term| term.iter())
7751 .next()
7752 .cloned()
7753 else {
7754 return false;
7755 };
7756 let negated_guard = split_guard.negated();
7757 let mut when_defined = Vec::new();
7758 let mut when_undefined = Vec::new();
7759 for term in normalized {
7760 if term.contains(&negated_guard) {
7761 } else if term.contains(&split_guard) {
7763 let mut reduced = term.clone();
7764 reduced.remove(&split_guard);
7765 when_defined.push(reduced);
7766 } else {
7767 when_defined.push(term.clone());
7768 }
7769 if term.contains(&split_guard) {
7770 } else if term.contains(&negated_guard) {
7772 let mut reduced = term;
7773 reduced.remove(&negated_guard);
7774 when_undefined.push(reduced);
7775 } else {
7776 when_undefined.push(term);
7777 }
7778 }
7779 pending.push(when_defined);
7780 pending.push(when_undefined);
7781 }
7782 true
7783 }
7784
7785 fn declarations_share_exhaustive_conditional_family(
7794 &self,
7795 analyzer: &CppGraphSource<'_>,
7796 candidates: &[&CodeUnit],
7797 ) -> Option<(usize, usize)> {
7798 let mut family_range = None;
7799 for candidate in candidates {
7800 let mut candidate_family = None;
7801 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7802 let key = occurrence.exhaustive_family?;
7803 if candidate_family.is_some_and(|existing| existing != key) {
7804 return None;
7805 }
7806 candidate_family = Some(key);
7807 }
7808 let candidate_family = candidate_family?;
7809 if family_range.is_some_and(|existing| existing != candidate_family) {
7810 return None;
7811 }
7812 family_range = Some(candidate_family);
7813 }
7814 family_range
7815 }
7816
7817 pub fn complementary_same_fqn_type_declarations(
7818 &self,
7819 analyzer: &CppGraphSource<'_>,
7820 candidates: &[&CodeUnit],
7821 target: &CodeUnit,
7822 ) -> bool {
7823 if candidates.len() < 2
7824 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7825 || self
7826 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7827 .is_none()
7828 {
7829 return false;
7830 }
7831 Self::preprocessor_guard_terms_cover_all_paths(
7832 &self.declaration_family_guard_terms(analyzer, candidates),
7833 )
7834 }
7835
7836 fn declaration_family_guard_terms(
7837 &self,
7838 analyzer: &CppGraphSource<'_>,
7839 candidates: &[&CodeUnit],
7840 ) -> Vec<HashSet<PreprocessorGuard>> {
7841 candidates
7842 .iter()
7843 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7844 .map(|(_, guards)| guards)
7845 .collect()
7846 }
7847
7848 fn exhaustive_guard_family_activation(
7864 &self,
7865 analyzer: &CppGraphSource<'_>,
7866 candidate: &CodeUnit,
7867 reference: &CallableReferenceContext<'_>,
7868 ) -> Option<usize> {
7869 if !analyzer
7872 .declaration_source_occurrences(candidate)?
7873 .iter()
7874 .any(|occurrence| occurrence.fact.callable_activation.is_some())
7875 {
7876 return None;
7877 }
7878 let family = self
7879 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7880 .filter(|peer| {
7881 peer.kind() == candidate.kind()
7882 && peer.fq_name() == candidate.fq_name()
7883 && peer.source() == candidate.source()
7884 })
7885 .collect::<Vec<_>>();
7886 let (_, family_end) =
7887 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7888 if !Self::preprocessor_guard_terms_cover_all_paths(
7889 &self.declaration_family_guard_terms(analyzer, &family),
7890 ) {
7891 return None;
7892 }
7893 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7897 .iter()
7898 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7899 {
7900 return None;
7901 }
7902 (first_declaration_byte(analyzer, candidate)?
7903 == family
7904 .iter()
7905 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7906 .min()?)
7907 .then_some(family_end)
7908 }
7909
7910 fn type_candidate_preserving_target(
7911 &self,
7912 analyzer: &CppGraphSource<'_>,
7913 visible_from: &ProjectFile,
7914 candidate: &CodeUnit,
7915 target: &CodeUnit,
7916 ) -> Option<CodeUnit> {
7917 let mut current = candidate.clone();
7918 let mut matched_target = same_visible_symbol(¤t, target)
7919 || self.compatible_primary_template_redeclarations(¤t, target);
7920 let mut seen = HashSet::default();
7921 loop {
7922 if !seen.insert(current.clone()) {
7923 return None;
7924 }
7925 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7926 return matched_target
7927 .then(|| target.clone())
7928 .or_else(|| current.is_class().then_some(current));
7929 };
7930 if self.flattened_macro_namespace_alias_target_matches(
7931 analyzer,
7932 visible_from,
7933 ¤t,
7934 &alias_target,
7935 target,
7936 ) {
7937 return Some(target.clone());
7938 }
7939 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7940 return matched_target
7941 .then(|| target.clone())
7942 .or_else(|| current.is_class().then_some(current));
7943 }
7944 if !self.cpp_template_metadata.contains_key(¤t)
7952 && let Some(primary) =
7953 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7954 && (same_visible_symbol(&primary, target)
7955 || self.compatible_primary_template_redeclarations(&primary, target))
7956 {
7957 return Some(target.clone());
7958 }
7959 if same_visible_symbol(¤t, target) {
7960 return Some(target.clone());
7961 }
7962 if self.cpp_template_metadata.contains_key(¤t) {
7963 return None;
7964 }
7965 let Some(next) =
7966 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7967 else {
7968 return matched_target.then(|| target.clone());
7969 };
7970 current = next;
7971 matched_target |= same_visible_symbol(¤t, target)
7972 || self.compatible_primary_template_redeclarations(¤t, target);
7973 }
7974 }
7975
7976 fn compatible_primary_template_redeclarations(
7977 &self,
7978 left: &CodeUnit,
7979 right: &CodeUnit,
7980 ) -> bool {
7981 let (Some(left_metadata), Some(right_metadata)) = (
7982 self.cpp_template_metadata.get(left),
7983 self.cpp_template_metadata.get(right),
7984 ) else {
7985 return false;
7986 };
7987 left_metadata.primary_fq_name == right_metadata.primary_fq_name
7988 && left_metadata.is_primary()
7989 && right_metadata.is_primary()
7990 && cpp_reconcile_primary_template_parameters(
7991 &[(left, left_metadata), (right, right_metadata)],
7992 right,
7993 )
7994 .is_some()
7995 }
7996
7997 fn alias_candidate_may_preserve_target(
7998 &self,
7999 analyzer: &CppGraphSource<'_>,
8000 visible_from: &ProjectFile,
8001 candidate: &CodeUnit,
8002 target: &CodeUnit,
8003 ) -> bool {
8004 let mut current = candidate.clone();
8005 let mut seen = HashSet::default();
8006 loop {
8007 if same_visible_symbol(¤t, target)
8008 || self.compatible_primary_template_redeclarations(¤t, target)
8009 {
8010 return true;
8011 }
8012 if self.cpp_template_metadata.contains_key(¤t) {
8013 return true;
8014 }
8015 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8016 return false;
8017 };
8018 let StructuredAliasTarget::Named {
8019 components,
8020 global,
8021 arguments,
8022 } = alias_target
8023 else {
8024 return false;
8025 };
8026 if arguments.is_some() || !seen.insert(current.clone()) {
8027 return true;
8028 }
8029 let qualified = components.join("::");
8030 let next = if global {
8031 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8032 } else {
8033 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8034 };
8035 let Some(next) = next else {
8036 return true;
8037 };
8038 current = next;
8039 }
8040 }
8041
8042 fn alias_candidate_structurally_reaches_target(
8043 &self,
8044 analyzer: &CppGraphSource<'_>,
8045 visible_from: &ProjectFile,
8046 candidate: &CodeUnit,
8047 target: &CodeUnit,
8048 ) -> bool {
8049 let mut current = candidate.clone();
8050 let mut seen = HashSet::default();
8051 loop {
8052 if same_visible_symbol(¤t, target)
8053 || self.compatible_primary_template_redeclarations(¤t, target)
8054 || self
8055 .cpp_template_metadata
8056 .get(¤t)
8057 .zip(self.cpp_template_metadata.get(target))
8058 .is_some_and(|(current, target)| {
8059 current.primary_fq_name == target.primary_fq_name
8060 })
8061 {
8062 return true;
8063 }
8064 if !seen.insert(current.clone()) {
8065 return false;
8066 }
8067 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8068 return false;
8069 };
8070 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8071 && components[..components.len().saturating_sub(1)]
8072 .iter()
8073 .any(|component| {
8074 self.type_reference_component_directly_names_target(component, target)
8075 })
8076 && self.structured_class_alias_path_preserves_target(
8077 analyzer,
8078 visible_from,
8079 ¤t,
8080 target,
8081 )
8082 {
8083 return true;
8084 }
8085 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8086 return false;
8087 }
8088 let Some(primary) =
8089 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8090 else {
8091 return false;
8092 };
8093 current = primary;
8094 }
8095 }
8096
8097 pub fn qualified_alias_reference_may_reach_target(
8109 &self,
8110 analyzer: &CppGraphSource<'_>,
8111 file: &ProjectFile,
8112 components: &[String],
8113 global: bool,
8114 target: &CodeUnit,
8115 ) -> bool {
8116 for end in 0..components.len() {
8117 let spelled = &components[..=end];
8118 for candidate in self
8119 .visible_identifier_candidates(file, &components[end])
8120 .filter(|candidate| declared_type_alias(analyzer, candidate))
8121 {
8122 let candidate_components = canonical_cpp_scope_components(candidate);
8123 let shape_matches = if global {
8124 candidate_components == spelled
8125 } else {
8126 candidate_components.ends_with(spelled)
8127 };
8128 if shape_matches
8129 && self.alias_candidate_structurally_reaches_target(
8130 analyzer, file, candidate, target,
8131 )
8132 {
8133 return true;
8134 }
8135 }
8136 }
8137 false
8138 }
8139
8140 fn type_candidates_for_declaration<'b>(
8144 &'b self,
8145 visible_from: &ProjectFile,
8146 declaration: &CodeUnit,
8147 raw_name: &str,
8148 ) -> Vec<&'b CodeUnit> {
8149 let Some(normalized) = normalize_reference_name(raw_name) else {
8150 return Vec::new();
8151 };
8152 if let Some(namespace) = cpp_namespace_for(declaration) {
8153 for prefix in namespace_prefixes(&namespace) {
8154 let qualified = format!("{prefix}::{normalized}");
8155 let candidates = self.type_candidates(visible_from, &qualified);
8156 if !candidates.is_empty() {
8157 return candidates;
8158 }
8159 }
8160 }
8161 self.type_candidates(visible_from, &normalized)
8162 }
8163
8164 fn resolve_unique_type_for_declaration(
8165 &self,
8166 visible_from: &ProjectFile,
8167 declaration: &CodeUnit,
8168 raw_name: &str,
8169 ) -> Option<CodeUnit> {
8170 unique_logical_type_candidate(self.type_candidates_for_declaration(
8171 visible_from,
8172 declaration,
8173 raw_name,
8174 ))
8175 }
8176
8177 fn resolve_defining_type_for_declaration(
8195 &self,
8196 analyzer: &CppGraphSource<'_>,
8197 visible_from: &ProjectFile,
8198 declaration: &CodeUnit,
8199 raw_name: &str,
8200 ) -> Option<CodeUnit> {
8201 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8202 let logical = unique_logical_type_candidate(candidates.clone())?;
8203 let mut defining = candidates.into_iter().filter(|candidate| {
8204 candidate.is_class()
8205 && cpp_class_declaration_strength(analyzer, candidate)
8206 == CppClassDeclarationStrength::Full
8207 });
8208 match (defining.next(), defining.next()) {
8209 (Some(unique_definition), None) => Some(unique_definition.clone()),
8210 _ => Some(logical),
8211 }
8212 }
8213
8214 pub fn resolves_to_type(
8215 &self,
8216 analyzer: &CppGraphSource<'_>,
8217 file: &ProjectFile,
8218 raw_name: &str,
8219 target: &CodeUnit,
8220 ) -> bool {
8221 let Some(normalized) = normalize_reference_name(raw_name) else {
8222 return false;
8223 };
8224 let candidates = self.type_candidates(file, &normalized);
8225 if candidates.is_empty() {
8226 return self.parser_alias_resolves_to_type(file, raw_name, target);
8227 }
8228 let Some(resolved) =
8229 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8230 else {
8231 return false;
8232 };
8233 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8234 }
8235
8236 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8237 let mut current = alias.clone();
8238 let mut visited = HashSet::default();
8239 while visited.insert(current.clone()) {
8240 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8241 let resolved =
8242 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8243 if resolved.is_class() {
8244 return Some(resolved);
8245 }
8246 if !declared_type_alias(&self.cpp_source(), &resolved) {
8247 return None;
8248 }
8249 current = resolved;
8250 }
8251 None
8252 }
8253
8254 pub fn same_logical_callable(
8269 &self,
8270 analyzer: &CppGraphSource<'_>,
8271 left: &CodeUnit,
8272 right: &CodeUnit,
8273 ) -> bool {
8274 if same_logical_symbol(left, right) {
8275 return true;
8276 }
8277 if left.kind() != right.kind()
8278 || !left.is_callable()
8279 || !right.is_callable()
8280 || left.fq_name() != right.fq_name()
8281 {
8282 return false;
8283 }
8284 if self.callable_is_template_declaration(analyzer, left)
8290 || self.callable_is_template_declaration(analyzer, right)
8291 {
8292 return false;
8293 }
8294 let (Some(left_comparable), Some(right_comparable)) = (
8295 self.callable_comparable(analyzer, left),
8296 self.callable_comparable(analyzer, right),
8297 ) else {
8298 return false;
8299 };
8300 if left_comparable.suffix != right_comparable.suffix
8305 || left_comparable.shapes.len() != right_comparable.shapes.len()
8306 {
8307 return false;
8308 }
8309 left_comparable
8310 .shapes
8311 .iter()
8312 .zip(right_comparable.shapes.iter())
8313 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8314 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8315 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8316 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8317 }
8318 _ => false,
8322 })
8323 }
8324
8325 fn comparable_shapes_agree(
8331 &self,
8332 analyzer: &CppGraphSource<'_>,
8333 left: &CppComparableParameter,
8334 right: &CppComparableParameter,
8335 ) -> bool {
8336 let mut stack = vec![(left.root(), right.root())];
8337 while let Some((left_index, right_index)) = stack.pop() {
8338 match (left.node(left_index), right.node(right_index)) {
8339 (
8340 CppComparableNode::Named {
8341 name: left_name,
8342 primitive: left_primitive,
8343 konst: left_konst,
8344 volatil: left_volatil,
8345 },
8346 CppComparableNode::Named {
8347 name: right_name,
8348 primitive: right_primitive,
8349 konst: right_konst,
8350 volatil: right_volatil,
8351 },
8352 ) => {
8353 if left_konst != right_konst
8354 || left_volatil != right_volatil
8355 || left_primitive != right_primitive
8356 || !self.comparable_names_agree(
8357 analyzer,
8358 left_name,
8359 right_name,
8360 *left_primitive,
8361 )
8362 {
8363 return false;
8364 }
8365 }
8366 (
8367 CppComparableNode::Pointer {
8368 inner: left_inner,
8369 konst: left_konst,
8370 volatil: left_volatil,
8371 },
8372 CppComparableNode::Pointer {
8373 inner: right_inner,
8374 konst: right_konst,
8375 volatil: right_volatil,
8376 },
8377 ) => {
8378 if left_konst != right_konst || left_volatil != right_volatil {
8379 return false;
8380 }
8381 stack.push((*left_inner, *right_inner));
8382 }
8383 (
8384 CppComparableNode::Reference { inner: left_inner },
8385 CppComparableNode::Reference { inner: right_inner },
8386 )
8387 | (
8388 CppComparableNode::Array { inner: left_inner },
8389 CppComparableNode::Array { inner: right_inner },
8390 ) => stack.push((*left_inner, *right_inner)),
8391 (
8392 CppComparableNode::Generic {
8393 base: left_base,
8394 arguments: left_arguments,
8395 },
8396 CppComparableNode::Generic {
8397 base: right_base,
8398 arguments: right_arguments,
8399 },
8400 ) => {
8401 if left_arguments.len() != right_arguments.len() {
8402 return false;
8403 }
8404 stack.push((*left_base, *right_base));
8405 stack.extend(
8406 left_arguments.iter().zip(right_arguments.iter()).map(
8407 |(left_argument, right_argument)| (*left_argument, *right_argument),
8408 ),
8409 );
8410 }
8411 _ => return false,
8412 }
8413 }
8414 true
8415 }
8416
8417 fn comparable_names_agree(
8427 &self,
8428 analyzer: &CppGraphSource<'_>,
8429 left: &StructuredTypeName,
8430 right: &StructuredTypeName,
8431 primitive: bool,
8432 ) -> bool {
8433 if primitive {
8434 return left.path() == right.path();
8435 }
8436 match (
8437 self.comparable_name_terminal(analyzer, left),
8438 self.comparable_name_terminal(analyzer, right),
8439 ) {
8440 (Some(left_terminal), Some(right_terminal)) => {
8441 same_logical_symbol(&left_terminal, &right_terminal)
8442 }
8443 (None, None) => {
8444 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8445 }
8446 _ => false,
8447 }
8448 }
8449
8450 fn comparable_name_terminal(
8460 &self,
8461 analyzer: &CppGraphSource<'_>,
8462 name: &StructuredTypeName,
8463 ) -> Option<CodeUnit> {
8464 let mut current = self.comparable_name_declaration(analyzer, name)?;
8465 let mut visited = HashSet::default();
8466 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8467 if !declared_type_alias(analyzer, ¤t) {
8474 return current.is_class().then_some(current);
8475 }
8476 if !visited.insert(current.clone()) {
8477 return None;
8478 }
8479 let facts = analyzer.declaration_source_properties(¤t)?;
8480 if facts.iter().any(|fact| fact.adds_indirection) {
8481 return None;
8482 }
8483 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
8484 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8485 }
8486 None
8487 }
8488
8489 fn comparable_alias_target(
8500 &self,
8501 analyzer: &CppGraphSource<'_>,
8502 alias: &CodeUnit,
8503 raw_target: &str,
8504 ) -> Option<CodeUnit> {
8505 let absolute = raw_target.trim_start().starts_with("::");
8510 let normalized = normalize_reference_name(raw_target)?;
8511 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8512 brokk_bifrost_core::analyzer::Language::Cpp,
8513 &normalized,
8514 );
8515 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8516 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8517 brokk_bifrost_core::analyzer::Language::Cpp,
8518 &namespace,
8519 )
8520 });
8521 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8522 self.comparable_name_declaration(analyzer, &name)
8523 }
8524
8525 fn comparable_name_declaration(
8533 &self,
8534 analyzer: &CppGraphSource<'_>,
8535 name: &StructuredTypeName,
8536 ) -> Option<CodeUnit> {
8537 let mut cache = self
8544 .comparable_name_declarations
8545 .lock()
8546 .expect("C++ comparable name declaration cache poisoned");
8547 if let Some(cached) = cache.get(name) {
8548 return cached.clone();
8549 }
8550 let interner = segment_interner();
8551 let identifier = name.path().last()?;
8552 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8553 let first_depth = if name.is_absolute() {
8554 0
8555 } else {
8556 name.lexical_scope().len()
8557 };
8558 let mut resolved = None;
8559 for depth in (0..=first_depth).rev() {
8560 let mut structured = FqName::new();
8561 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8562 structured.push(interner.intern(component, SegmentKind::Unknown));
8563 }
8564 let mut candidates = candidates_by_identifier
8565 .iter()
8566 .filter(|unit| unit.fq().same_segment_texts(&structured))
8567 .filter(|unit| {
8568 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8569 })
8570 .cloned();
8571 let Some(first) = candidates.next() else {
8572 continue;
8573 };
8574 resolved = candidates
8575 .all(|unit| same_logical_symbol(&unit, &first))
8576 .then_some(first);
8577 break;
8578 }
8579 cache.insert(name.clone(), resolved.clone());
8580 resolved
8581 }
8582
8583 fn callable_comparable(
8589 &self,
8590 analyzer: &CppGraphSource<'_>,
8591 unit: &CodeUnit,
8592 ) -> Option<Arc<ExtractedComparable>> {
8593 if let Some(cached) = self
8594 .callable_comparables
8595 .lock()
8596 .expect("C++ callable comparable cache poisoned")
8597 .get(unit)
8598 .cloned()
8599 {
8600 return cached;
8601 }
8602 let extracted = self
8603 .extract_callable_comparable(analyzer, unit)
8604 .map(Arc::new);
8605 self.callable_comparables
8606 .lock()
8607 .expect("C++ callable comparable cache poisoned")
8608 .insert(unit.clone(), extracted.clone());
8609 extracted
8610 }
8611
8612 fn extract_callable_comparable(
8613 &self,
8614 analyzer: &CppGraphSource<'_>,
8615 unit: &CodeUnit,
8616 ) -> Option<ExtractedComparable> {
8617 let fact = analyzer
8618 .declaration_source_properties(unit)?
8619 .into_iter()
8620 .find(|fact| fact.callable_comparable_shapes.is_some())?;
8621 Some(ExtractedComparable {
8622 shapes: fact.callable_comparable_shapes?,
8623 suffix: fact.callable_identity_suffix?,
8624 })
8625 }
8626
8627 pub fn canonical_type_for_reference(
8628 &self,
8629 file: &ProjectFile,
8630 raw_name: &str,
8631 ) -> Option<CodeUnit> {
8632 let resolved = self.resolve_type(file, raw_name)?;
8633 self.alias_target(&resolved).or(Some(resolved))
8634 }
8635
8636 pub fn parser_alias_resolves_to_type(
8637 &self,
8638 file: &ProjectFile,
8639 raw_name: &str,
8640 target: &CodeUnit,
8641 ) -> bool {
8642 let Some(alias_name) = normalize_reference_name(raw_name) else {
8643 return false;
8644 };
8645 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8646 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8647 })
8648 }
8649
8650 fn file_alias_matches(
8651 &self,
8652 cpp: &dyn CppSource,
8653 file: &ProjectFile,
8654 alias_name: &str,
8655 target: &CodeUnit,
8656 ) -> bool {
8657 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8658 cell.get()
8659 .expect("published canonical aliases")
8660 .iter()
8661 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8662 })
8663 }
8664
8665 fn canonical_alias_cell(
8666 &self,
8667 cpp: &dyn CppSource,
8668 file: &ProjectFile,
8669 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8670 let cell = {
8671 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8672 Arc::clone(
8673 cells
8674 .entry(file.clone())
8675 .or_insert_with(|| Arc::new(OnceLock::new())),
8676 )
8677 };
8678 if cell.get().is_none() {
8679 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8680 cell.get_or_init(|| {
8681 self.parser_alias_source_parses
8682 .fetch_add(1, Ordering::Relaxed);
8683 #[cfg(any(test, feature = "test-support"))]
8684 {
8685 *self
8686 .alias_source_parse_counts
8687 .lock()
8688 .expect("alias source parse count lock")
8689 .entry(file.clone())
8690 .or_default() += 1;
8691 }
8692 aliases.into_boxed_slice()
8693 });
8694 }
8695 Some(cell)
8696 }
8697
8698 #[cfg(any(test, feature = "test-support"))]
8699 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8700 self.visible_source_files_by_root
8701 .get(file)
8702 .cloned()
8703 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8704 }
8705
8706 #[cfg(any(test, feature = "test-support"))]
8707 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8708 self.alias_source_parse_counts
8709 .lock()
8710 .expect("alias source parse count lock")
8711 .get(file)
8712 .copied()
8713 .unwrap_or(0)
8714 }
8715
8716 #[cfg(any(test, feature = "test-support"))]
8717 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8718 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8719 }
8720
8721 pub fn resolve_named(
8722 &self,
8723 file: &ProjectFile,
8724 raw_name: &str,
8725 kind: TargetKind,
8726 ) -> Option<CodeUnit> {
8727 let normalized = normalize_reference_name(raw_name)?;
8728 self.named_candidates_for_normalized(file, &normalized, kind)
8729 .into_iter()
8730 .next()
8731 .cloned()
8732 }
8733
8734 pub fn contains_named_symbol(
8735 &self,
8736 file: &ProjectFile,
8737 raw_name: &str,
8738 kind: TargetKind,
8739 target: &CodeUnit,
8740 ) -> bool {
8741 let Some(normalized) = normalize_reference_name(raw_name) else {
8742 return false;
8743 };
8744 self.named_candidates_for_normalized(file, &normalized, kind)
8745 .into_iter()
8746 .any(|unit| {
8747 matches_kind_for_lookup(unit, kind)
8748 && reference_matches_unit(&normalized, unit)
8749 && same_visible_symbol(unit, target)
8750 })
8751 }
8752
8753 pub fn named_candidates(
8754 &self,
8755 file: &ProjectFile,
8756 raw_name: &str,
8757 kind: TargetKind,
8758 ) -> Vec<CodeUnit> {
8759 let Some(normalized) = normalize_reference_name(raw_name) else {
8760 return Vec::new();
8761 };
8762 self.named_candidates_for_normalized(file, &normalized, kind)
8763 .into_iter()
8764 .cloned()
8765 .collect()
8766 }
8767
8768 pub fn resolve_known_non_target(
8769 &self,
8770 file: &ProjectFile,
8771 raw_name: &str,
8772 kind: TargetKind,
8773 target: &CodeUnit,
8774 ) -> bool {
8775 let Some(normalized) = normalize_reference_name(raw_name) else {
8776 return false;
8777 };
8778 normalized.contains("::")
8779 && self
8780 .named_candidates_for_normalized(file, &normalized, kind)
8781 .into_iter()
8782 .any(|unit| {
8783 matches_kind_for_lookup(unit, kind)
8784 && reference_matches_unit(&normalized, unit)
8785 && !same_visible_symbol(unit, target)
8786 })
8787 }
8788
8789 pub fn resolve_call_return_binding(
8790 &self,
8791 analyzer: &CppGraphSource<'_>,
8792 file: &ProjectFile,
8793 raw_name: &str,
8794 arity: usize,
8795 lexical_namespace: Option<&str>,
8796 direct_type: Option<&CodeUnit>,
8797 ) -> Option<CppScanBinding> {
8798 let normalized = normalize_reference_name(raw_name)?;
8799 let mut candidates = Vec::new();
8800 for function in
8801 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8802 {
8803 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8804 && !direct_type.is_some_and(|direct_type| {
8805 self.callable_is_constructor_declaration(analyzer, function)
8806 && type_owner_of(analyzer, function)
8807 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8808 })
8809 {
8810 candidates.push(function.clone());
8811 }
8812 }
8813 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8814 unanimous_return_binding(analyzer, self, file, &candidates)
8815 }
8816
8817 pub fn resolve_call_return_binding_without_arity(
8818 &self,
8819 analyzer: &CppGraphSource<'_>,
8820 file: &ProjectFile,
8821 raw_name: &str,
8822 lexical_namespace: Option<&str>,
8823 direct_type: Option<&CodeUnit>,
8824 ) -> (bool, Option<CppScanBinding>) {
8825 let Some(normalized) = normalize_reference_name(raw_name) else {
8826 return (false, None);
8827 };
8828 let mut candidates = self
8829 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8830 .into_iter()
8831 .filter(|function| {
8832 function.is_function()
8833 && !direct_type.is_some_and(|direct_type| {
8834 self.callable_is_constructor_declaration(analyzer, function)
8835 && type_owner_of(analyzer, function)
8836 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8837 })
8838 })
8839 .cloned()
8840 .collect::<Vec<_>>();
8841 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8842 let has_candidates = !candidates.is_empty();
8843 (
8844 has_candidates,
8845 unanimous_return_binding(analyzer, self, file, &candidates),
8846 )
8847 }
8848
8849 pub fn visible_identifier_candidates<'b>(
8850 &'b self,
8851 file: &ProjectFile,
8852 identifier: &str,
8853 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8854 self.visible_by_identifier
8855 .get(file)
8856 .and_then(|by_name| by_name.get(identifier))
8857 .into_iter()
8858 .flatten()
8859 }
8860
8861 pub fn type_reference_component_directly_names_target(
8867 &self,
8868 component: &str,
8869 target: &CodeUnit,
8870 ) -> bool {
8871 component == target.identifier()
8872 || self
8873 .cpp_template_metadata
8874 .get(target)
8875 .is_some_and(|metadata| component == metadata.primary_name)
8876 }
8877
8878 pub fn visible_type_reference_component_names_for_target(
8886 &self,
8887 analyzer: &CppGraphSource<'_>,
8888 file: &ProjectFile,
8889 target: &CodeUnit,
8890 ) -> HashSet<String> {
8891 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8892 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8893 names.insert(metadata.primary_name.clone());
8894 }
8895
8896 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8897 for (identifier, candidates) in by_identifier {
8898 if candidates.iter().any(|candidate| {
8899 (candidate.is_class()
8900 && (same_visible_symbol(candidate, target)
8901 || self.compatible_primary_template_redeclarations(candidate, target)))
8902 || (declared_type_alias(analyzer, candidate)
8903 && self.alias_candidate_structurally_reaches_target(
8904 analyzer, file, candidate, target,
8905 ))
8906 }) {
8907 names.insert(identifier.clone());
8908 }
8909 }
8910 }
8911
8912 names
8913 }
8914
8915 pub fn indexed_structural_class_scope(
8916 &self,
8917 file: &ProjectFile,
8918 class: Node<'_>,
8919 source: &str,
8920 ) -> Option<Vec<String>> {
8921 let key = (file.clone(), class.start_byte(), class.end_byte());
8922 if let Some(cached) = self
8923 .indexed_structural_class_scopes
8924 .lock()
8925 .expect("C++ indexed structural-class scope cache poisoned")
8926 .get(&key)
8927 .cloned()
8928 {
8929 return cached;
8930 }
8931 let resolved = (|| {
8932 let name = class.child_by_field_name("name")?;
8933 let identifier = if name.kind() == "template_type" {
8934 node_text(name.child_by_field_name("name")?, source).to_string()
8935 } else {
8936 let mut components = Vec::new();
8937 append_cpp_name_components(name, source, &mut components)?;
8938 components.last()?.clone()
8939 };
8940 let visible = self
8941 .visible_identifier_candidates(file, &identifier)
8942 .cloned()
8943 .collect::<Vec<_>>();
8944 let mut visible = visible;
8945 for candidate in
8946 self.visible_by_file
8947 .get(file)
8948 .into_iter()
8949 .flatten()
8950 .filter(|candidate| {
8951 self.cpp_template_metadata
8952 .get(candidate)
8953 .is_some_and(|metadata| metadata.primary_name == identifier)
8954 })
8955 {
8956 if !visible
8957 .iter()
8958 .any(|existing| same_logical_symbol(existing, candidate))
8959 {
8960 visible.push(candidate.clone());
8961 }
8962 }
8963 let cpp_source = self.cpp_source();
8966 let candidates = visible
8967 .iter()
8968 .filter(|candidate| {
8969 candidate.source() == file
8970 && candidate.is_class()
8971 && !declared_type_alias(&cpp_source, candidate)
8972 && self.cpp.ranges(candidate).iter().any(|range| {
8973 range.start_byte <= class.start_byte()
8974 && class.end_byte() <= range.end_byte
8975 })
8976 })
8977 .collect::<Vec<_>>();
8978 let owner = if name.kind() == "template_type" {
8979 let expected = normalize_cpp_whitespace(node_text(name, source));
8980 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
8981 let exact = candidates
8982 .iter()
8983 .copied()
8984 .filter(|candidate| {
8985 candidate
8986 .fq()
8987 .segments()
8988 .iter()
8989 .rev()
8990 .find_map(|&segment| {
8991 let (text, kind) = interner.resolve(segment);
8992 matches!(
8993 kind,
8994 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
8995 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
8996 )
8997 .then_some(text)
8998 })
8999 .is_some_and(|text| text == expected)
9000 })
9001 .collect::<Vec<_>>();
9002 unique_logical_type_candidate(exact)
9003 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9004 } else {
9005 unique_logical_type_candidate(candidates)?
9006 };
9007 Some(canonical_cpp_scope_components(&owner))
9008 })();
9009 self.indexed_structural_class_scopes
9010 .lock()
9011 .expect("C++ indexed structural-class scope cache poisoned")
9012 .insert(key, resolved.clone());
9013 resolved
9014 }
9015
9016 pub fn indexed_enclosing_owner_scope(
9017 &self,
9018 analyzer: &CppGraphSource<'_>,
9019 file: &ProjectFile,
9020 node: Node<'_>,
9021 ) -> Option<Vec<String>> {
9022 let anchor = std::iter::successors(Some(node), |current| current.parent())
9023 .find(|current| {
9024 matches!(
9025 current.kind(),
9026 "function_definition"
9027 | "class_specifier"
9028 | "struct_specifier"
9029 | "union_specifier"
9030 )
9031 })
9032 .unwrap_or(node);
9033 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9034 if let Some(cached) = self
9035 .indexed_enclosing_owner_scopes
9036 .lock()
9037 .expect("C++ indexed enclosing-owner scope cache poisoned")
9038 .get(&key)
9039 .cloned()
9040 {
9041 return cached;
9042 }
9043 let resolved = (|| {
9044 let range = Range {
9045 start_byte: node.start_byte(),
9046 end_byte: node.end_byte(),
9047 start_line: node.start_position().row,
9048 end_line: node.end_position().row,
9049 };
9050 let start = analyzer.enclosing_code_unit(file, &range)?;
9051 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9052 start,
9053 |unit| self.cached_precise_parent_of(analyzer, unit),
9054 )
9055 .find(|unit| {
9056 unit.is_class()
9057 && !analyzer
9058 .type_alias_provider()
9059 .is_some_and(|provider| provider.is_type_alias(unit))
9060 })?;
9061 Some(canonical_cpp_scope_components(&owner))
9062 })();
9063 self.indexed_enclosing_owner_scopes
9064 .lock()
9065 .expect("C++ indexed enclosing-owner scope cache poisoned")
9066 .insert(key, resolved.clone());
9067 resolved
9068 }
9069
9070 fn cached_precise_parent_of(
9071 &self,
9072 analyzer: &CppGraphSource<'_>,
9073 code_unit: &CodeUnit,
9074 ) -> Option<CodeUnit> {
9075 if let Some(cached) = self
9076 .precise_parent_cache
9077 .lock()
9078 .expect("C++ precise-parent cache poisoned")
9079 .get(code_unit)
9080 .cloned()
9081 {
9082 return cached;
9083 }
9084 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9085 self.precise_parent_cache
9086 .lock()
9087 .expect("C++ precise-parent cache poisoned")
9088 .insert(code_unit.clone(), resolved.clone());
9089 resolved
9090 }
9091
9092 pub fn callable_is_constructor_declaration(
9093 &self,
9094 analyzer: &CppGraphSource<'_>,
9095 candidate: &CodeUnit,
9096 ) -> bool {
9097 if !candidate.is_function() {
9098 return false;
9099 }
9100 if analyzer
9101 .declaration_source_properties(candidate)
9102 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9103 {
9104 return true;
9105 }
9106 let metadata = analyzer.signature_metadata(candidate);
9107 !metadata.is_empty()
9108 && metadata
9109 .iter()
9110 .all(|signature| signature.return_type_text().is_none())
9111 }
9112
9113 pub fn callable_is_deduction_guide_declaration(
9121 &self,
9122 analyzer: &CppGraphSource<'_>,
9123 candidate: &CodeUnit,
9124 ) -> bool {
9125 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9126 }
9127
9128 pub fn callable_is_template_declaration(
9132 &self,
9133 analyzer: &CppGraphSource<'_>,
9134 candidate: &CodeUnit,
9135 ) -> bool {
9136 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9137 }
9138
9139 pub fn type_name_candidates<'b>(
9140 &'b self,
9141 file: &ProjectFile,
9142 normalized: &str,
9143 ) -> Vec<&'b CodeUnit> {
9144 self.candidate_units(file, normalized, TargetKind::Type)
9145 }
9146
9147 pub fn visible_members_for_owner_name<'b>(
9148 &'b self,
9149 file: &ProjectFile,
9150 owner: &CodeUnit,
9151 name: &str,
9152 ) -> Vec<&'b CodeUnit> {
9153 self.visible_identifier_candidates(file, name)
9154 .filter(|unit| {
9155 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9159 .is_some_and(|parent| parent == owner.fq_name())
9160 })
9161 .collect()
9162 }
9163
9164 pub fn visible_member_for_owner_name(
9165 &self,
9166 file: &ProjectFile,
9167 owner: &CodeUnit,
9168 name: &str,
9169 ) -> VisibleMemberResolution {
9170 let candidates = self.visible_members_for_owner_name(file, owner, name);
9171 let mut callables = Vec::new();
9172 let mut non_callable = None;
9173 for candidate in candidates {
9174 if candidate.is_function() {
9175 callables.push(candidate.clone());
9176 } else if non_callable.is_none() {
9177 non_callable = Some(candidate.clone());
9178 }
9179 }
9180 match (callables.is_empty(), non_callable) {
9181 (false, None) => VisibleMemberResolution::Callable(callables),
9182 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9183 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9184 (true, None) => VisibleMemberResolution::Missing,
9185 }
9186 }
9187
9188 fn field_declared_type_fact(
9189 &self,
9190 analyzer: &CppGraphSource<'_>,
9191 field: &CodeUnit,
9192 ) -> Option<DeclaredFieldTypeFact> {
9193 if let Some(cached) = self
9194 .field_type_facts
9195 .lock()
9196 .expect("C++ field type fact cache poisoned")
9197 .get(field)
9198 .cloned()
9199 {
9200 return cached;
9201 }
9202 let decoded = decode_field_declared_type_fact(analyzer, field);
9203 self.field_type_facts
9204 .lock()
9205 .expect("C++ field type fact cache poisoned")
9206 .insert(field.clone(), decoded.clone());
9207 decoded
9208 }
9209
9210 fn structured_alias_target(
9211 &self,
9212 analyzer: &CppGraphSource<'_>,
9213 unit: &CodeUnit,
9214 ) -> Option<StructuredAliasTarget> {
9215 if let Some(cached) = self
9216 .structured_alias_targets
9217 .lock()
9218 .expect("C++ structured alias target cache poisoned")
9219 .get(unit)
9220 .cloned()
9221 {
9222 return cached;
9223 }
9224 let decoded = decode_structured_alias_target(analyzer, unit);
9225 self.structured_alias_targets
9226 .lock()
9227 .expect("C++ structured alias target cache poisoned")
9228 .insert(unit.clone(), decoded.clone());
9229 decoded
9230 }
9231
9232 pub fn type_candidates<'b>(
9233 &'b self,
9234 file: &ProjectFile,
9235 normalized: &str,
9236 ) -> Vec<&'b CodeUnit> {
9237 let mut candidates = self
9238 .candidate_units(file, normalized, TargetKind::Type)
9239 .into_iter()
9240 .filter(|unit| {
9241 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9242 })
9243 .collect::<Vec<_>>();
9244 dedup_unit_refs(&mut candidates);
9245 candidates
9246 }
9247
9248 pub fn named_candidates_for_normalized<'b>(
9249 &'b self,
9250 file: &ProjectFile,
9251 normalized: &str,
9252 kind: TargetKind,
9253 ) -> Vec<&'b CodeUnit> {
9254 let mut candidates = self
9255 .candidate_units(file, normalized, kind)
9256 .into_iter()
9257 .filter(|unit| {
9258 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9259 })
9260 .collect::<Vec<_>>();
9261 dedup_unit_refs(&mut candidates);
9262 candidates
9263 }
9264
9265 pub fn candidate_units<'b>(
9266 &'b self,
9267 file: &ProjectFile,
9268 normalized: &str,
9269 kind: TargetKind,
9270 ) -> Vec<&'b CodeUnit> {
9271 if normalized.contains("::") {
9272 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9281 brokk_bifrost_core::analyzer::Language::Cpp,
9282 normalized,
9283 );
9284 let Some(identifier) = parts.last() else {
9285 return Vec::new();
9286 };
9287 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9288 return self
9289 .visible_identifier_candidates(file, identifier)
9290 .filter(|unit| {
9291 #[cfg(any(test, feature = "test-support"))]
9292 self.qualified_candidate_inspections
9293 .fetch_add(1, Ordering::Relaxed);
9294 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9295 || canonical_cpp_name_matches(unit, normalized)
9296 })
9297 .collect();
9298 }
9299 self.visible_identifier_candidates(file, normalized)
9300 .collect()
9301 }
9302
9303 #[cfg(any(test, feature = "test-support"))]
9304 pub fn reset_qualified_candidate_inspections(&self) {
9305 self.qualified_candidate_inspections
9306 .store(0, Ordering::Relaxed);
9307 }
9308
9309 #[cfg(any(test, feature = "test-support"))]
9310 pub fn qualified_candidate_inspections(&self) -> usize {
9311 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9312 }
9313
9314 #[cfg(any(test, feature = "test-support"))]
9315 pub fn visibility_identifier_lookup_count(&self) -> usize {
9316 self.visibility_identifier_lookup_count
9317 }
9318
9319 #[cfg(any(test, feature = "test-support"))]
9320 pub fn visibility_identifier_batch_count(&self) -> usize {
9321 self.visibility_identifier_batch_count
9322 }
9323
9324 #[cfg(any(test, feature = "test-support"))]
9325 pub fn reset_target_preserving_type_resolution_count(&self) {
9326 self.target_preserving_type_resolution_count
9327 .store(0, Ordering::Relaxed);
9328 }
9329
9330 #[cfg(any(test, feature = "test-support"))]
9331 pub fn target_preserving_type_resolution_count(&self) -> usize {
9332 self.target_preserving_type_resolution_count
9333 .load(Ordering::Relaxed)
9334 }
9335
9336 #[cfg(any(test, feature = "test-support"))]
9337 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9338 self.visible_parser_alias_name_set_build_count
9339 .load(Ordering::Relaxed)
9340 }
9341}
9342
9343fn callable_declaration_has_fact(
9344 analyzer: &CppGraphSource<'_>,
9345 candidate: &CodeUnit,
9346 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9347) -> bool {
9348 candidate.is_function()
9349 && analyzer
9350 .declaration_source_properties(candidate)
9351 .is_some_and(|facts| facts.iter().any(matches))
9352}
9353
9354#[derive(Default)]
9355struct IncludeGraph {
9356 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9357}
9358
9359impl IncludeGraph {
9360 fn extend_with<F>(
9361 &mut self,
9362 root: &ProjectFile,
9363 cancellation: Option<&CancellationToken>,
9364 targets_for: &mut F,
9365 ) where
9366 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9367 {
9368 let mut stack = vec![root.clone()];
9369 while let Some(file) = stack.pop() {
9370 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9371 break;
9372 }
9373 if self.targets_by_file.contains_key(&file) {
9374 continue;
9375 }
9376 let targets = targets_for(&file);
9377 stack.extend(targets.iter().cloned());
9378 self.targets_by_file.insert(file, targets);
9379 }
9380 }
9381
9382 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9383 self.targets_by_file.keys()
9384 }
9385
9386 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9387 self.targets_by_file
9388 .get(file)
9389 .map(Vec::as_slice)
9390 .unwrap_or_default()
9391 }
9392
9393 fn reachable_files(
9394 &self,
9395 root: &ProjectFile,
9396 cancellation: Option<&CancellationToken>,
9397 ) -> HashSet<ProjectFile> {
9398 let mut pending = vec![root.clone()];
9399 let mut visited = HashSet::default();
9400 while let Some(file) = pending.pop() {
9401 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9402 break;
9403 }
9404 if visited.insert(file.clone()) {
9405 pending.extend(self.targets(&file).iter().cloned());
9406 }
9407 }
9408 visited
9409 }
9410}
9411
9412fn build_bounded_visible_declarations(
9413 cpp: &dyn CppSource,
9414 token: QueryToken<'_>,
9415 analyzer: &CppGraphSource<'_>,
9416 roots: &HashSet<ProjectFile>,
9417 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9418 cancellation: Option<&CancellationToken>,
9419 stats: &mut BoundedVisibilityStats,
9420) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9421 let mut candidates_by_identifier = HashMap::default();
9422 let mut declarations_by_source_and_reading: HashMap<
9423 (ProjectFile, bool),
9424 BoundedVisibilityDeclarations,
9425 > = HashMap::default();
9426 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9427 roots
9428 .iter()
9429 .map(|root| {
9430 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9431 let root_declarations = declarations_by_source_and_reading
9432 .entry((root.clone(), reading_is_c))
9433 .or_insert_with(|| {
9434 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9435 });
9436 let mut visible = root_declarations
9437 .all
9438 .iter()
9439 .cloned()
9440 .collect::<HashSet<_>>();
9441 let mut pending_names = HashSet::default();
9442 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9443 let mut cursor = prepared.tree().walk();
9448 let mut pending_nodes = vec![prepared.tree().root_node()];
9449 while let Some(node) = pending_nodes.pop() {
9450 if matches!(
9451 node.kind(),
9452 "identifier"
9453 | "type_identifier"
9454 | "field_identifier"
9455 | "namespace_identifier"
9456 ) {
9457 pending_names.insert(node_text(node, prepared.source()).to_string());
9458 }
9459 if node.kind() == "preproc_arg" {
9460 for reference in
9461 object_macro_replacement_type_references(node, prepared.source())
9462 {
9463 pending_names.extend(reference.components);
9464 }
9465 }
9466 pending_nodes.extend(node.named_children(&mut cursor));
9467 }
9468 }
9469 stats.root_names += pending_names.len();
9470 let mut completed_names = HashSet::default();
9471 while !pending_names.is_empty() {
9472 stats.rounds += 1;
9473 let round_names = std::mem::take(&mut pending_names);
9474 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9475 HashMap::default();
9476 let mut identifiers = Vec::new();
9477 for identifier in round_names {
9478 if !completed_names.insert(identifier.clone())
9479 || cancellation.is_some_and(CancellationToken::is_cancelled)
9480 {
9481 continue;
9482 }
9483 identifiers.push(identifier);
9484 }
9485 let missing_identifiers = identifiers
9486 .iter()
9487 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9488 .cloned()
9489 .collect::<HashSet<_>>();
9490 if !missing_identifiers.is_empty() {
9491 let lookup_started = Instant::now();
9492 let mut candidates = cpp
9493 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9494 stats.lookup_elapsed += lookup_started.elapsed();
9495 stats.identifier_lookups += missing_identifiers.len();
9496 stats.identifier_batches += 1;
9497 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9498 break;
9499 }
9500 for identifier in missing_identifiers {
9501 let units = candidates.remove(&identifier).unwrap_or_default();
9502 let candidate_count = units.len();
9503 let candidate_sources = units
9504 .into_iter()
9505 .map(|unit| unit.source().clone())
9506 .collect::<HashSet<_>>();
9507 candidates_by_identifier
9508 .insert(identifier, (candidate_sources, candidate_count));
9509 }
9510 }
9511 for identifier in identifiers {
9512 let (candidate_sources, candidate_count) = candidates_by_identifier
9513 .get(&identifier)
9514 .expect("every missing identifier was inserted after the batch lookup");
9515 stats.candidate_units += *candidate_count;
9516 for source in candidate_sources.iter().cloned() {
9517 if source != *root
9518 && visible_sources
9519 .get(root)
9520 .is_some_and(|files| files.contains(&source))
9521 {
9522 requested_names_by_source
9523 .entry(source)
9524 .or_default()
9525 .insert(identifier.clone());
9526 }
9527 }
9528 }
9529 stats.candidate_sources += requested_names_by_source.len();
9530 for (source, requested_names) in requested_names_by_source {
9531 let declarations = declarations_by_source_and_reading
9532 .entry((source.clone(), reading_is_c))
9533 .or_insert_with(|| {
9534 bounded_visibility_declarations(
9535 cpp,
9536 analyzer,
9537 &source,
9538 reading_is_c,
9539 stats,
9540 )
9541 });
9542 let mut selected = HashSet::default();
9543 for name in requested_names {
9544 if let Some(units) = declarations.by_identifier.get(&name) {
9545 selected.extend(units.iter().cloned());
9546 }
9547 }
9548 for unit in selected {
9549 stats.selected_units += 1;
9550 let dependency_names = dependency_names_by_unit
9551 .entry(unit.clone())
9552 .or_insert_with(|| {
9553 let facts_started = Instant::now();
9554 let mut dependency_names = HashSet::default();
9555 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9556 for fact in facts {
9557 for name in fact.dependency_type_names {
9558 stats.dependency_source_names += 1;
9559 #[cfg(any(test, feature = "test-support"))]
9560 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9561 .with(|count| count.set(count.get() + 1));
9562 dependency_names.insert(name);
9563 }
9564 }
9565 stats.dependency_source_elapsed += facts_started.elapsed();
9566 }
9567 dependency_names
9568 });
9569 for name in dependency_names.iter() {
9570 if !completed_names.contains(name) && pending_names.insert(name.clone())
9571 {
9572 stats.dependency_names += 1;
9573 }
9574 }
9575 visible.insert(unit);
9576 }
9577 }
9578 }
9579 (root.clone(), visible)
9580 })
9581 .collect()
9582}
9583
9584struct BoundedVisibilityDeclarations {
9585 all: Vec<CodeUnit>,
9586 by_identifier: HashMap<String, Vec<CodeUnit>>,
9587}
9588
9589fn bounded_visibility_declarations(
9590 cpp: &dyn CppSource,
9591 analyzer: &CppGraphSource<'_>,
9592 file: &ProjectFile,
9593 c_semantics: bool,
9594 stats: &mut BoundedVisibilityStats,
9595) -> BoundedVisibilityDeclarations {
9596 let declarations_started = Instant::now();
9597 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9598 .into_iter()
9599 .collect::<Vec<_>>();
9600 stats.declaration_elapsed += declarations_started.elapsed();
9601 stats.declaration_reads += 1;
9602 stats.declaration_units += all.len();
9603
9604 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9605 for unit in &all {
9606 by_identifier
9607 .entry(unit.identifier().to_string())
9608 .or_default()
9609 .push(unit.clone());
9610 if unit.is_class()
9611 && let Some(metadata) = cpp.template_metadata(unit)
9612 && metadata.primary_name != unit.identifier()
9613 {
9614 by_identifier
9615 .entry(metadata.primary_name)
9616 .or_default()
9617 .push(unit.clone());
9618 }
9619 }
9620 BoundedVisibilityDeclarations { all, by_identifier }
9621}
9622
9623#[derive(Default)]
9624struct BoundedVisibilityStats {
9625 rounds: usize,
9626 root_names: usize,
9627 identifier_lookups: usize,
9628 identifier_batches: usize,
9629 candidate_units: usize,
9630 candidate_sources: usize,
9631 declaration_reads: usize,
9632 declaration_units: usize,
9633 selected_units: usize,
9634 dependency_source_names: usize,
9635 dependency_names: usize,
9636 lookup_elapsed: Duration,
9637 declaration_elapsed: Duration,
9638 dependency_source_elapsed: Duration,
9639}
9640
9641fn bounded_visibility_declarations_in_reading(
9642 analyzer: &CppGraphSource<'_>,
9643 file: &ProjectFile,
9644 c_semantics: bool,
9645) -> BTreeSet<CodeUnit> {
9646 #[cfg(any(test, feature = "test-support"))]
9647 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9648 analyzer.declarations_in_reading(file, c_semantics)
9649}
9650
9651#[cfg(any(test, feature = "test-support"))]
9652pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9653 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9654}
9655
9656#[cfg(any(test, feature = "test-support"))]
9657pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9658 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9659}
9660
9661#[cfg(any(test, feature = "test-support"))]
9662pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9663 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9664}
9665
9666#[cfg(any(test, feature = "test-support"))]
9667pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9668 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9669}
9670
9671pub struct VisibilityData {
9672 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9673 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9674}
9675
9676pub fn build_visibility_data<F, R, D>(
9686 roots: &HashSet<ProjectFile>,
9687 cancellation: Option<&CancellationToken>,
9688 mut targets_for: F,
9689 mut reading_is_c_for: R,
9690 mut declarations_for: D,
9691) -> VisibilityData
9692where
9693 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9694 R: FnMut(&ProjectFile) -> bool,
9695 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9696{
9697 let mut include_graph = IncludeGraph::default();
9698 for file in roots {
9699 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9700 break;
9701 }
9702 include_graph.extend_with(file, cancellation, &mut targets_for);
9703 }
9704 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9705 .files()
9706 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9707 .map(|file| (file.clone(), declarations_for(file, false)))
9708 .collect();
9709 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9710 let mut visible_by_file = HashMap::default();
9711 let mut visible_source_files_by_root = HashMap::default();
9712 for file in roots {
9713 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9714 break;
9715 }
9716 let mut visited = HashSet::default();
9717 let mut visible = HashSet::default();
9718 let declarations_by_file = if reading_is_c_for(file) {
9719 for reached in cpp_declarations_by_file.keys() {
9720 if !c_declarations_by_file.contains_key(reached) {
9721 let declarations = declarations_for(reached, true);
9722 c_declarations_by_file.insert(reached.clone(), declarations);
9723 }
9724 }
9725 &c_declarations_by_file
9726 } else {
9727 &cpp_declarations_by_file
9728 };
9729 collect_visible_declarations(
9730 &include_graph,
9731 declarations_by_file,
9732 file,
9733 &mut visited,
9734 &mut visible,
9735 cancellation,
9736 );
9737 visible_by_file.insert(file.clone(), visible);
9738 visible_source_files_by_root.insert(file.clone(), visited);
9739 }
9740 VisibilityData {
9741 visible_by_file,
9742 visible_source_files_by_root,
9743 }
9744}
9745
9746#[derive(Default)]
9765struct OutOfLineOwnerBindingStats {
9766 unseen_owners: usize,
9767 definition_lookups: usize,
9768 admitted: usize,
9769}
9770
9771fn extend_with_out_of_line_owner_bindings(
9772 cpp: &dyn CppSource,
9773 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9774) -> OutOfLineOwnerBindingStats {
9775 let mut stats = OutOfLineOwnerBindingStats::default();
9776 for (file, visible) in visible_by_file.iter_mut() {
9777 let mut unseen_owners: HashSet<String> = visible
9781 .iter()
9782 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9783 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9784 .collect();
9785 if unseen_owners.is_empty() {
9786 continue;
9787 }
9788 for unit in visible.iter().filter(|unit| unit.is_class()) {
9789 unseen_owners.remove(&unit.fq_name());
9790 }
9791 stats.unseen_owners += unseen_owners.len();
9792 stats.definition_lookups += unseen_owners.len();
9793 let admitted = unseen_owners
9794 .iter()
9795 .flat_map(|owner| cpp.definitions(owner))
9796 .filter(CodeUnit::is_class)
9797 .collect::<Vec<_>>();
9798 stats.admitted += admitted.len();
9799 visible.extend(admitted);
9800 }
9801 stats
9802}
9803
9804pub enum VisibleMemberResolution {
9805 Callable(Vec<CodeUnit>),
9806 NonCallable,
9807 AmbiguousKind,
9808 Missing,
9809}
9810
9811#[derive(Clone)]
9812pub enum EnclosingMemberOwnerResolution {
9813 Owner(CodeUnit),
9814 Ambiguous,
9815 Missing,
9816}
9817
9818pub fn resolve_declaring_member_owner(
9819 analyzer: &CppGraphSource<'_>,
9820 visibility: &VisibilityIndex<'_>,
9821 file: &ProjectFile,
9822 receiver_owner: &CodeUnit,
9823 member_name: &str,
9824) -> EnclosingMemberOwnerResolution {
9825 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9826 return EnclosingMemberOwnerResolution::Missing;
9827 };
9828 let Some(receiver_owner) =
9829 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9830 else {
9831 return EnclosingMemberOwnerResolution::Ambiguous;
9832 };
9833 let resolve_level = |frontier: &[CodeUnit]| {
9834 let mut member_owners = Vec::new();
9835 for raw_owner in frontier {
9836 let Some(owner) =
9837 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9838 else {
9839 return EnclosingMemberOwnerResolution::Ambiguous;
9840 };
9841 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9842 if !member.is_field() && !member.is_function() {
9847 continue;
9848 }
9849 let Some(member_owner) = type_owner_of(analyzer, member) else {
9850 return EnclosingMemberOwnerResolution::Ambiguous;
9851 };
9852 if !member_owners
9853 .iter()
9854 .any(|existing| same_visible_symbol(existing, &member_owner))
9855 {
9856 member_owners.push(member_owner);
9857 }
9858 }
9859 }
9860 match member_owners.len() {
9861 0 => EnclosingMemberOwnerResolution::Missing,
9862 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9863 _ => EnclosingMemberOwnerResolution::Ambiguous,
9864 }
9865 };
9866 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9870 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9871 return direct;
9872 }
9873 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9874 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9875 let mut path_matches = Vec::new();
9876 while let Some(raw_owner) = stack.pop() {
9877 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9878 else {
9879 return EnclosingMemberOwnerResolution::Ambiguous;
9880 };
9881 let propagated = propagated_counts.entry(owner.clone()).or_default();
9885 if *propagated == 2 {
9886 continue;
9887 }
9888 *propagated += 1;
9889 match resolve_level(std::slice::from_ref(&owner)) {
9890 EnclosingMemberOwnerResolution::Owner(owner) => {
9891 path_matches.push(owner);
9892 if path_matches.len() == 2 {
9893 return EnclosingMemberOwnerResolution::Ambiguous;
9894 }
9895 }
9896 EnclosingMemberOwnerResolution::Ambiguous => {
9897 return EnclosingMemberOwnerResolution::Ambiguous;
9898 }
9899 EnclosingMemberOwnerResolution::Missing => {
9900 stack.extend(hierarchy.get_direct_ancestors(&owner));
9901 }
9902 }
9903 }
9904 match path_matches.len() {
9905 0 => EnclosingMemberOwnerResolution::Missing,
9906 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9907 _ => unreachable!("base-path matches are capped at one before returning"),
9908 }
9909}
9910
9911pub fn resolve_declaring_callable_owner(
9926 analyzer: &CppGraphSource<'_>,
9927 visibility: &VisibilityIndex<'_>,
9928 file: &ProjectFile,
9929 ordinary: EnclosingMemberOwnerResolution,
9930 member_name: &str,
9931 call_arity: usize,
9932) -> EnclosingMemberOwnerResolution {
9933 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9934 return ordinary;
9935 };
9936 if visibility
9937 .visible_members_for_owner_name(file, ordinary_owner, member_name)
9938 .into_iter()
9939 .any(|unit| {
9940 unit.is_function()
9941 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9942 })
9943 {
9944 return ordinary;
9945 }
9946
9947 let mut pending = match member_using_declaration_bases(
9948 analyzer,
9949 visibility,
9950 file,
9951 ordinary_owner,
9952 member_name,
9953 ) {
9954 Ok(bases) => bases,
9955 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9956 };
9957 let mut visited = HashSet::default();
9958 let mut introduced_owners = Vec::new();
9959 while let Some(owner) = pending.pop() {
9960 if !visited.insert(owner.clone()) {
9961 continue;
9962 }
9963 let accepts_arity = visibility
9964 .visible_members_for_owner_name(file, &owner, member_name)
9965 .into_iter()
9966 .any(|unit| {
9967 unit.is_function()
9968 && cpp_callable_arity(analyzer, unit)
9969 .is_none_or(|known| known.accepts(call_arity))
9970 });
9971 if accepts_arity {
9972 if !introduced_owners
9973 .iter()
9974 .any(|existing| same_visible_symbol(existing, &owner))
9975 {
9976 introduced_owners.push(owner);
9977 }
9978 continue;
9979 }
9980 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
9981 Ok(bases) => pending.extend(bases),
9982 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9983 }
9984 }
9985 match introduced_owners.as_slice() {
9986 [] => ordinary,
9987 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
9988 _ => EnclosingMemberOwnerResolution::Ambiguous,
9989 }
9990}
9991
9992fn member_using_declaration_bases(
9993 analyzer: &CppGraphSource<'_>,
9994 visibility: &VisibilityIndex<'_>,
9995 file: &ProjectFile,
9996 owner: &CodeUnit,
9997 member_name: &str,
9998) -> Result<Vec<CodeUnit>, ()> {
9999 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10000 return Err(());
10001 };
10002 let scopes: Vec<_> = facts
10003 .iter()
10004 .flat_map(|fact| &fact.member_usings)
10005 .filter(|using| using.member == member_name)
10006 .map(|using| using.scope.join("::"))
10007 .collect();
10008 if scopes.is_empty() {
10009 return Ok(Vec::new());
10010 }
10011 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10012 return Ok(Vec::new());
10013 };
10014 let mut bases = Vec::new();
10015 for raw_ancestor in hierarchy.get_ancestors(owner) {
10016 let Some(ancestor) =
10017 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10018 else {
10019 return Err(());
10020 };
10021 let qualified = cpp_name_for(&ancestor);
10022 if scopes
10023 .iter()
10024 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10025 && !bases
10026 .iter()
10027 .any(|existing| same_visible_symbol(existing, &ancestor))
10028 {
10029 bases.push(ancestor);
10030 }
10031 }
10032 Ok(bases)
10033}
10034
10035pub fn lexical_component_tiers<'a>(
10036 components: &'a [String],
10037 global: bool,
10038 lexical_scope: &'a [String],
10039) -> impl Iterator<Item = Vec<String>> + 'a {
10040 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10041 (0..=first_prefix_len).rev().map(move |prefix_len| {
10042 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10043 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10044 qualified.extend_from_slice(components);
10045 qualified
10046 })
10047}
10048
10049pub fn build_visible_identifier_index(
10050 analyzer: &CppGraphSource<'_>,
10051 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10052 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10053 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10054) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10055 let mut out = HashMap::default();
10056 for (file, visible) in visible_by_file {
10057 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10058 for unit in visible {
10059 if unit.is_field()
10060 && !visible_source_files_by_root
10061 .get(file)
10062 .is_some_and(|sources| sources.contains(unit.source()))
10063 && cpp_global_field_has_internal_linkage_cached(
10064 analyzer,
10065 global_field_internal_linkage,
10066 unit,
10067 )
10068 {
10069 continue;
10070 }
10071 by_identifier
10072 .entry(unit.identifier().to_string())
10073 .or_default()
10074 .push(unit.clone());
10075 }
10076 for units in by_identifier.values_mut() {
10077 sort_lookup_units(units);
10078 units.dedup();
10079 }
10080 out.insert(file.clone(), by_identifier);
10081 }
10082 out
10083}
10084
10085fn sort_lookup_units(units: &mut [CodeUnit]) {
10086 units.sort_by(|left, right| {
10087 left.fq_name()
10088 .cmp(&right.fq_name())
10089 .then_with(|| left.signature().cmp(&right.signature()))
10090 .then_with(|| left.source().cmp(right.source()))
10091 .then_with(|| left.kind().cmp(&right.kind()))
10092 .then_with(|| {
10093 left.package_segment_count()
10094 .cmp(&right.package_segment_count())
10095 })
10096 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10097 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10098 });
10099}
10100
10101fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10102 let interner = segment_interner();
10103 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10104 let (left_text, left_kind) = interner.resolve(left_id);
10105 let (right_text, right_kind) = interner.resolve(right_id);
10106 let order = left_text
10107 .cmp(right_text)
10108 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10109 if order != CmpOrdering::Equal {
10110 return order;
10111 }
10112 }
10113 left.len().cmp(&right.len())
10114}
10115
10116const fn segment_kind_order(kind: SegmentKind) -> u8 {
10117 match kind {
10118 SegmentKind::Path => 0,
10119 SegmentKind::Package => 1,
10120 SegmentKind::Type => 2,
10121 SegmentKind::Companion => 3,
10122 SegmentKind::Nested => 4,
10123 SegmentKind::Member => 5,
10124 SegmentKind::Unknown => 6,
10125 }
10126}
10127
10128fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10129 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10130 std::mem::take(units),
10131 );
10132}
10133
10134pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10135 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10139 brokk_bifrost_core::analyzer::Language::Cpp,
10140 reference,
10141 );
10142 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10143}
10144
10145fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10146 if parts.is_empty() {
10147 return Vec::new();
10148 }
10149
10150 let mut candidates = Vec::new();
10151 for package_len in 0..parts.len() {
10152 let package = parts[..package_len].join("::");
10153 let rest = &parts[package_len..];
10154 if rest.is_empty() {
10155 continue;
10156 }
10157 match kind {
10158 TargetKind::Type | TargetKind::Constructor => {
10159 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10160 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10161 }
10162 TargetKind::FreeFunction
10163 | TargetKind::Method
10164 | TargetKind::GlobalField
10165 | TargetKind::MemberField
10166 | TargetKind::Macro => {
10167 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10168 if rest.len() > 1 {
10169 let owner = rest[..rest.len() - 1].join("$");
10170 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10171 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10172 }
10173 }
10174 }
10175 }
10176 candidates
10177}
10178
10179fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10180 let fqn = if package.is_empty() {
10181 short.to_string()
10182 } else {
10183 format!("{package}.{short}")
10184 };
10185 if !out.contains(&fqn) {
10186 out.push(fqn);
10187 }
10188}
10189
10190pub fn infer_cpp_initializer_type(
10191 analyzer: &CppGraphSource<'_>,
10192 visibility: &VisibilityIndex<'_>,
10193 file: &ProjectFile,
10194 source: &str,
10195 node: Node<'_>,
10196) -> Option<CodeUnit> {
10197 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10198 .and_then(|binding| binding.unit)
10199}
10200
10201pub fn infer_cpp_initializer_binding(
10202 analyzer: &CppGraphSource<'_>,
10203 visibility: &VisibilityIndex<'_>,
10204 file: &ProjectFile,
10205 source: &str,
10206 node: Node<'_>,
10207 receiver_resolver: Option<&ReceiverResolver<'_>>,
10208) -> Option<CppScanBinding> {
10209 match node.kind() {
10210 "new_expression" => {
10211 let type_node = cpp_new_expression_type_node(node)?;
10212 let type_text = node_text(type_node, source);
10213 let name = normalize_cpp_type_name(type_text);
10214 Some(CppScanBinding::from_type_name(
10215 name.clone(),
10216 visibility.resolve_type(file, &name),
10217 1,
10218 ))
10219 }
10220 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10221 if function.kind() == "field_expression" {
10229 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10230 return resolve_field_method_call_return_binding(
10231 analyzer,
10232 visibility,
10233 file,
10234 source,
10235 function,
10236 arity,
10237 receiver_resolver,
10238 );
10239 }
10240 let function_text = node_text(function, source);
10241 let direct_type_binding = visibility
10242 .resolve_type(file, function_text)
10243 .map(|unit| CppScanBinding::from_unit(unit, 0));
10244 if function.kind() == "template_function" && direct_type_binding.is_some() {
10245 let lexical_namespace = enclosing_namespace_context(node, source);
10246 let arity = visibility.call_arity_evidence(file, node, source).exact();
10247 if let Some(arity) = arity
10248 && let Some(binding) = visibility.resolve_call_return_binding(
10249 analyzer,
10250 file,
10251 function_text,
10252 arity,
10253 lexical_namespace.as_deref(),
10254 direct_type_binding
10255 .as_ref()
10256 .and_then(|binding| binding.unit.as_ref()),
10257 )
10258 {
10259 return Some(binding);
10260 }
10261 let (has_callable, callable_binding) = visibility
10262 .resolve_call_return_binding_without_arity(
10263 analyzer,
10264 file,
10265 function_text,
10266 lexical_namespace.as_deref(),
10267 direct_type_binding
10268 .as_ref()
10269 .and_then(|binding| binding.unit.as_ref()),
10270 );
10271 if let Some(binding) = callable_binding {
10272 return Some(binding);
10273 }
10274 if has_callable {
10275 return None;
10276 }
10277 return direct_type_binding;
10278 }
10279 let arity = visibility.call_arity_evidence(file, node, source).exact();
10284 if let Some(arity) = arity {
10285 let direct_type_binding_for_call = direct_type_binding.clone();
10286 if let Some(binding) = resolve_static_method_call_return_binding(
10287 analyzer, visibility, file, source, function, arity,
10288 )
10289 .or_else(|| {
10290 visibility.resolve_call_return_binding(
10295 analyzer,
10296 file,
10297 function_text,
10298 arity,
10299 enclosing_namespace_context(node, source).as_deref(),
10300 direct_type_binding_for_call
10301 .as_ref()
10302 .and_then(|binding| binding.unit.as_ref()),
10303 )
10304 }) {
10305 return Some(binding);
10306 }
10307 }
10308 direct_type_binding
10309 }),
10310 _ => None,
10311 }
10312}
10313
10314pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10320 debug_assert_eq!(expression.kind(), "new_expression");
10321 if let Some(type_node) = expression.child_by_field_name("type") {
10322 return Some(type_node);
10323 }
10324
10325 let mut pending = Vec::new();
10326 let mut cursor = expression.walk();
10327 pending.extend(
10328 expression
10329 .named_children(&mut cursor)
10330 .filter(|child| {
10331 !matches!(
10332 child.kind(),
10333 "argument_list" | "initializer_list" | "new_declarator"
10334 )
10335 })
10336 .collect::<Vec<_>>()
10337 .into_iter()
10338 .rev(),
10339 );
10340 while let Some(node) = pending.pop() {
10341 if is_cpp_type_specifier_node(node) {
10342 return Some(node);
10343 }
10344 if node.kind() == "ERROR" {
10345 let mut cursor = node.walk();
10346 pending.extend(
10347 node.named_children(&mut cursor)
10348 .collect::<Vec<_>>()
10349 .into_iter()
10350 .rev(),
10351 );
10352 }
10353 }
10354 None
10355}
10356
10357fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10358 matches!(
10359 node.kind(),
10360 "class_specifier"
10361 | "decltype"
10362 | "dependent_type"
10363 | "enum_specifier"
10364 | "placeholder_type_specifier"
10365 | "primitive_type"
10366 | "qualified_identifier"
10367 | "sized_type_specifier"
10368 | "struct_specifier"
10369 | "template_type"
10370 | "type_identifier"
10371 | "union_specifier"
10372 )
10373}
10374
10375fn resolve_static_method_call_return_binding(
10376 analyzer: &CppGraphSource<'_>,
10377 visibility: &VisibilityIndex<'_>,
10378 file: &ProjectFile,
10379 source: &str,
10380 function: Node<'_>,
10381 arity: usize,
10382) -> Option<CppScanBinding> {
10383 if function.kind() != "qualified_identifier" {
10384 return None;
10385 }
10386 let qualified = normalize_cpp_reference_text(node_text(function, source));
10387 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10394 brokk_bifrost_core::analyzer::Language::Cpp,
10395 &qualified,
10396 );
10397 let (owner_text, member_name) = match parts.split_last() {
10398 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10399 (owner_parts.join("::"), member.clone())
10400 }
10401 _ => {
10402 let scope = function.child_by_field_name("scope")?;
10403 let name = function.child_by_field_name("name")?;
10404 (
10405 node_text(scope, source).to_string(),
10406 node_text(name, source).to_string(),
10407 )
10408 }
10409 };
10410 let owner = visibility.resolve_type(file, &owner_text)?;
10411 let candidates =
10412 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10413 unanimous_return_binding(analyzer, visibility, file, &candidates)
10414}
10415
10416fn resolve_field_method_call_return_binding(
10417 analyzer: &CppGraphSource<'_>,
10418 visibility: &VisibilityIndex<'_>,
10419 file: &ProjectFile,
10420 source: &str,
10421 function: Node<'_>,
10422 arity: usize,
10423 receiver_resolver: Option<&ReceiverResolver<'_>>,
10424) -> Option<CppScanBinding> {
10425 debug_assert_eq!(
10426 function.kind(),
10427 "field_expression",
10428 "the member-call return binding answers only for a field-expression callee"
10429 );
10430 let receiver_resolver = receiver_resolver?;
10431 let field = function.child_by_field_name("field")?;
10432 let member_name = node_text(function_terminal_node(field), source);
10433 let receiver = function
10434 .child_by_field_name("argument")
10435 .or_else(|| function.named_child(0))?;
10436 let owners = receiver_resolver(receiver, source);
10437 let mut candidates = Vec::new();
10438 for owner in owners {
10439 let declaring_owner =
10440 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10441 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10442 EnclosingMemberOwnerResolution::Missing => continue,
10443 EnclosingMemberOwnerResolution::Ambiguous => return None,
10444 };
10445 candidates.extend(callable_members_accepting_arity(
10446 analyzer,
10447 visibility,
10448 file,
10449 &declaring_owner,
10450 member_name,
10451 arity,
10452 ));
10453 }
10454 unanimous_return_binding(analyzer, visibility, file, &candidates)
10455}
10456
10457fn callable_members_accepting_arity(
10458 analyzer: &CppGraphSource<'_>,
10459 visibility: &VisibilityIndex<'_>,
10460 file: &ProjectFile,
10461 owner: &CodeUnit,
10462 member_name: &str,
10463 arity: usize,
10464) -> Vec<CodeUnit> {
10465 visibility
10466 .visible_members_for_owner_name(file, owner, member_name)
10467 .into_iter()
10468 .filter(|unit| {
10469 unit.is_function()
10470 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10471 })
10472 .cloned()
10473 .collect()
10474}
10475
10476fn unanimous_return_binding(
10477 analyzer: &CppGraphSource<'_>,
10478 visibility: &VisibilityIndex<'_>,
10479 file: &ProjectFile,
10480 candidates: &[CodeUnit],
10481) -> Option<CppScanBinding> {
10482 let mut resolved_return: Option<CppScanBinding> = None;
10483 for function in candidates {
10484 let metadata = analyzer.signature_metadata(function);
10485 let return_types = if metadata.is_empty() {
10486 vec![cpp_function_return_type_text(analyzer, function)?]
10487 } else {
10488 metadata
10489 .iter()
10490 .map(|metadata| metadata.return_type_text().map(str::to_string))
10491 .collect::<Option<Vec<_>>>()?
10492 };
10493 for return_text in return_types {
10494 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10495 let name = normalize_cpp_type_name(&return_text);
10496 let binding = CppScanBinding::from_type_name(
10497 name.clone(),
10498 visibility
10499 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10500 indirection,
10501 );
10502 if let Some(existing) = resolved_return.as_ref()
10503 && (existing.indirection != binding.indirection
10504 || match (&existing.unit, &binding.unit) {
10505 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10506 (None, None) => existing.type_name != binding.type_name,
10507 (Some(_), None) | (None, Some(_)) => true,
10508 })
10509 {
10510 return None;
10511 }
10512 resolved_return = Some(binding);
10513 }
10514 }
10515 resolved_return
10516}
10517
10518fn aliases_from_canonical_source(
10519 cpp: &dyn CppSource,
10520 token: QueryToken<'_>,
10521 file: &ProjectFile,
10522) -> Option<Vec<CppAlias>> {
10523 let source = cpp.declaration_source_facts(token, file)?;
10524 Some(
10525 source
10526 .facts
10527 .declarations
10528 .iter()
10529 .filter_map(|fact| {
10530 let alias = fact.file_scope_alias.as_ref()?;
10531 Some(CppAlias {
10532 name: alias.name.clone(),
10533 target: alias.target.clone(),
10534 namespace: alias.namespace.clone(),
10535 })
10536 })
10537 .collect(),
10538 )
10539}
10540
10541pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10542 let mut current = node.parent();
10543 while let Some(parent) = current {
10544 match parent.kind() {
10545 "translation_unit"
10546 | "namespace_definition"
10547 | "declaration_list"
10548 | "linkage_specification" => current = parent.parent(),
10549 "template_declaration" => current = parent.parent(),
10550 _ => return false,
10551 }
10552 }
10553 true
10554}
10555
10556pub fn collect_include_closure(
10557 analyzer: &CppGraphSource<'_>,
10558 include_targets: &IncludeTargetIndex,
10559 file: &ProjectFile,
10560 out: &mut HashSet<ProjectFile>,
10561 cancellation: Option<&CancellationToken>,
10562) {
10563 let mut stack = vec![file.clone()];
10564 while let Some(file) = stack.pop() {
10565 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10566 break;
10567 }
10568 if !out.insert(file.clone()) {
10569 continue;
10570 }
10571 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10572 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10573 stack.push(target);
10574 }
10575 }
10576 }
10577}
10578
10579fn collect_visible_declarations(
10580 include_graph: &IncludeGraph,
10581 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10582 file: &ProjectFile,
10583 visited: &mut HashSet<ProjectFile>,
10584 out: &mut HashSet<CodeUnit>,
10585 cancellation: Option<&CancellationToken>,
10586) {
10587 let mut stack = vec![file.clone()];
10588 while let Some(file) = stack.pop() {
10589 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10590 break;
10591 }
10592 if !visited.insert(file.clone()) {
10593 continue;
10594 }
10595 if let Some(declarations) = declarations_by_file.get(&file) {
10596 out.extend(declarations.iter().cloned());
10597 }
10598 stack.extend(include_graph.targets(&file).iter().cloned());
10599 }
10600}
10601
10602fn parse_macro_parameter_list<T>(
10603 replacement: &str,
10604 read: impl FnOnce(Node<'_>, &str) -> T,
10605) -> Option<T> {
10606 let source = format!("void __bifrost_macro_parameters({replacement});");
10607 let mut parser = Parser::new();
10608 parser
10609 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10610 .ok()?;
10611 let tree = parser.parse(&source, None)?;
10612 let root = tree.root_node();
10613 if root.has_error() {
10614 return None;
10615 }
10616 let declaration = root.named_child(0)?;
10617 let declarator = declaration.child_by_field_name("declarator")?;
10618 let parameters = declarator.child_by_field_name("parameters")?;
10619 Some(read(parameters, &source))
10620}
10621
10622fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10623 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10624}
10625
10626pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10629 parse_macro_parameter_list(
10630 replacement,
10631 crate::declarations::cpp_callable_parameter_types,
10632 )
10633}
10634
10635pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10636 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10637}
10638
10639pub fn cpp_callable_parameter_types(
10640 analyzer: &CppGraphSource<'_>,
10641 unit: &CodeUnit,
10642) -> Option<Vec<String>> {
10643 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10644 analyzer.signature_metadata(unit),
10645 )
10646}
10647
10648fn merge_compatible_callable_arities(
10649 left: CallableArity,
10650 right: CallableArity,
10651) -> Option<CallableArity> {
10652 let total = left.total();
10653 let left_repeated = left.accepts(total.saturating_add(1));
10654 let right_repeated = right.accepts(right.total().saturating_add(1));
10655 if total != right.total() || left_repeated != right_repeated {
10656 return None;
10657 }
10658 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10659 Some(CallableArity::new(required, total, left_repeated))
10660}
10661
10662fn collect_macro_events(
10663 cpp: &dyn CppSource,
10664 token: QueryToken<'_>,
10665 file: &ProjectFile,
10666) -> Vec<MacroEvent> {
10667 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10668 return Vec::new();
10669 };
10670 let source = prepared.source();
10671 let mut events = Vec::new();
10672 let root = prepared.tree().root_node();
10673 let mut stack = vec![root];
10674 while let Some(node) = stack.pop() {
10675 match node.kind() {
10676 "preproc_def" | "preproc_function_def" => {
10677 let Some(name) = node.child_by_field_name("name") else {
10678 continue;
10679 };
10680 let name = node_text(name, source).to_string();
10681 events.push(MacroEvent::Define {
10682 name,
10683 binding: MacroBinding {
10684 source: file.clone(),
10685 declaration_byte: node.start_byte(),
10686 definition: VisibilityIndex::decode_macro_definition(node, source),
10687 exact: true,
10688 },
10689 byte: node.start_byte(),
10690 conditionals: owning_preprocessor_conditionals(root, node, source),
10691 });
10692 continue;
10693 }
10694 "preproc_include" => {
10695 let Some(path) = node.child_by_field_name("path") else {
10696 events.push(MacroEvent::Include {
10697 targets: Vec::new(),
10698 byte: node.start_byte(),
10699 conditionals: owning_preprocessor_conditionals(root, node, source),
10700 });
10701 continue;
10702 };
10703 let include = crate::imports::include_path_literal_value(path, source);
10704 let targets = include.map_or_else(Vec::new, |include| {
10705 resolve_include_targets_with_index(file, include, cpp.include_target_index())
10706 });
10707 if targets.is_empty()
10718 && include.is_some_and(|include| {
10719 !cpp.include_target_index().names_indexed_file(include)
10720 })
10721 {
10722 continue;
10723 }
10724 events.push(MacroEvent::Include {
10725 targets,
10726 byte: node.start_byte(),
10727 conditionals: owning_preprocessor_conditionals(root, node, source),
10728 });
10729 continue;
10730 }
10731 "preproc_call" => {
10732 let Some(directive) = node.child_by_field_name("directive") else {
10733 continue;
10734 };
10735 if node_text(directive, source) != "#undef" {
10736 continue;
10737 }
10738 let name = node
10739 .child_by_field_name("argument")
10740 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10741 if let Some(name) = name {
10742 events.push(MacroEvent::Undef {
10743 name,
10744 byte: node.start_byte(),
10745 conditionals: owning_preprocessor_conditionals(root, node, source),
10746 });
10747 } else {
10748 events.push(MacroEvent::Invalidate {
10749 byte: node.start_byte(),
10750 });
10751 }
10752 continue;
10753 }
10754 _ => {}
10755 }
10756 push_named_children_reversed(node, &mut stack);
10757 }
10758 events.sort_by_key(MacroEvent::byte);
10759 events
10760}
10761
10762fn find_include_activation(
10763 cpp: &dyn CppSource,
10764 token: QueryToken<'_>,
10765 file: &ProjectFile,
10766 prepared: &PreparedSyntaxTree,
10767 donor_source: &ProjectFile,
10768 language_guard_is_stable: &dyn Fn() -> bool,
10769) -> Option<usize> {
10770 let include_targets = cpp.include_target_index();
10771 let reference_is_c = file
10772 .rel_path()
10773 .extension()
10774 .and_then(|extension| extension.to_str())
10775 == Some("c");
10776 let mut direct_includes = Vec::new();
10777 let mut nodes = vec![prepared.tree().root_node()];
10778 while let Some(node) = nodes.pop() {
10781 if node.kind() == "preproc_include" {
10782 let Some(required_guards) = include_directive_guard_requirements(
10783 node,
10784 prepared.source(),
10785 reference_is_c,
10786 language_guard_is_stable,
10787 ) else {
10788 continue;
10789 };
10790 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10791 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10792 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10793 file,
10794 include,
10795 include_targets,
10796 )) {
10797 direct_includes.push((node.end_byte(), target));
10798 }
10799 }
10800 }
10801 continue;
10802 }
10803 push_named_children_reversed(node, &mut nodes);
10804 }
10805 direct_includes.sort_by_key(|(activation, _)| *activation);
10806 let mut known_missing = HashSet::default();
10807 direct_includes
10808 .into_iter()
10809 .find(|(_, direct)| {
10810 unconditional_include_reaches(
10811 cpp,
10812 token,
10813 direct,
10814 donor_source,
10815 file,
10816 &mut known_missing,
10817 language_guard_is_stable,
10818 )
10819 })
10820 .map(|(activation, _)| activation)
10821}
10822
10823fn find_conditional_include_projection_index(
10824 cpp: &dyn CppSource,
10825 token: QueryToken<'_>,
10826 file: &ProjectFile,
10827 prepared: &PreparedSyntaxTree,
10828 language_guard_is_stable: &dyn Fn() -> bool,
10829 on_state: &dyn Fn(),
10830) -> ConditionalIncludeProjectionIndex {
10831 let reference_is_c = reference_uses_c_semantics(cpp, file);
10832 let include_targets = cpp.include_target_index();
10833 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10834 HashMap::default();
10835 let mut pending = Vec::new();
10836 let mut nodes = vec![prepared.tree().root_node()];
10837 while let Some(node) = nodes.pop() {
10838 if node.kind() == "preproc_include" {
10839 let Some(required_guards) = include_directive_guard_requirements(
10840 node,
10841 prepared.source(),
10842 reference_is_c,
10843 language_guard_is_stable,
10844 ) else {
10845 continue;
10846 };
10847 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10848 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10849 file,
10850 include,
10851 include_targets,
10852 )) else {
10853 continue;
10854 };
10855 pending.push((target, node.end_byte(), required_guards.clone()));
10856 }
10857 continue;
10858 }
10859 push_named_children_reversed(node, &mut nodes);
10860 }
10861
10862 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10874 HashMap::default();
10875 while let Some((current_file, activation_byte, path)) = pending.pop() {
10876 let guard_sets = expanded
10877 .entry((current_file.clone(), activation_byte))
10878 .or_default();
10879 if guard_sets
10883 .iter()
10884 .any(|existing| existing.is_subset(&path.all))
10885 {
10886 continue;
10887 }
10888 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10889 .drain(..)
10890 .partition(|existing| path.all.is_subset(existing));
10891 *guard_sets = kept;
10892 guard_sets.push(path.all.clone());
10893 if !evicted.is_empty()
10894 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10895 {
10896 projections.retain(|projection| {
10897 projection.activation_byte != activation_byte
10898 || !evicted.contains(&projection.required_guards)
10899 });
10900 }
10901 on_state();
10902
10903 projections_by_source
10906 .entry(current_file.clone())
10907 .or_default()
10908 .push(ConditionalIncludeProjection {
10909 activation_byte,
10910 required_guards: path.all.clone(),
10911 partial_guards: path.partial.clone(),
10912 });
10913
10914 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10915 continue;
10916 };
10917 let mut nodes = vec![current_prepared.tree().root_node()];
10918 while let Some(node) = nodes.pop() {
10919 if node.kind() == "preproc_include" {
10920 let Some(include_guards) = include_directive_guard_requirements(
10921 node,
10922 current_prepared.source(),
10923 reference_is_c,
10924 language_guard_is_stable,
10925 ) else {
10926 continue;
10927 };
10928 let Some(reached) = path.merged(&include_guards) else {
10929 continue;
10930 };
10931 for (include, _) in
10932 include_path_from_node(node, current_prepared.source()).into_iter()
10933 {
10934 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10935 ¤t_file,
10936 include,
10937 include_targets,
10938 )) else {
10939 continue;
10940 };
10941 pending.push((target, activation_byte, reached.clone()));
10942 }
10943 continue;
10944 }
10945 push_named_children_reversed(node, &mut nodes);
10946 }
10947 }
10948
10949 projections_by_source
10950 .into_iter()
10951 .map(|(source, mut projections)| {
10952 projections.sort_by_key(|projection| projection.activation_byte);
10953 (source, Arc::from(projections))
10954 })
10955 .collect()
10956}
10957
10958#[allow(clippy::too_many_arguments)]
10964fn find_conditional_include_projection_for_source(
10965 cpp: &dyn CppSource,
10966 token: QueryToken<'_>,
10967 file: &ProjectFile,
10968 prepared: &PreparedSyntaxTree,
10969 donor_source: &ProjectFile,
10970 admission: IncludePathAdmission,
10971 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10972 reference_byte: usize,
10973 language_guard_is_stable: &dyn Fn() -> bool,
10974 on_state: &dyn Fn(),
10975) -> bool {
10976 let Some(reference_guards) = reference_guards else {
10977 return false;
10978 };
10979 let reference_is_c = reference_uses_c_semantics(cpp, file);
10980 let include_targets = cpp.include_target_index();
10981 let mut pending = Vec::new();
10982 let mut nodes = vec![prepared.tree().root_node()];
10983 while let Some(node) = nodes.pop() {
10984 if node.kind() == "preproc_include" {
10985 let Some(required_guards) = include_directive_guard_requirements(
10986 node,
10987 prepared.source(),
10988 reference_is_c,
10989 language_guard_is_stable,
10990 ) else {
10991 continue;
10992 };
10993 if node.end_byte() > reference_byte
10994 || !admission.admits(
10995 &required_guards.all,
10996 &required_guards.partial,
10997 Some(reference_guards),
10998 )
10999 {
11000 continue;
11001 }
11002 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11003 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11004 file,
11005 include,
11006 include_targets,
11007 )) else {
11008 continue;
11009 };
11010 if &target == donor_source {
11011 return true;
11012 }
11013 pending.push((target, required_guards.clone()));
11014 }
11015 continue;
11016 }
11017 push_named_children_reversed(node, &mut nodes);
11018 }
11019
11020 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11021 while let Some((current_file, path)) = pending.pop() {
11022 let guard_sets = expanded.entry(current_file.clone()).or_default();
11023 if guard_sets.contains(&path.all) {
11024 continue;
11025 }
11026 guard_sets.push(path.all.clone());
11027 on_state();
11028
11029 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11030 continue;
11031 };
11032 let mut nodes = vec![current_prepared.tree().root_node()];
11033 while let Some(node) = nodes.pop() {
11034 if node.kind() == "preproc_include" {
11035 let Some(include_guards) = include_directive_guard_requirements(
11036 node,
11037 current_prepared.source(),
11038 reference_is_c,
11039 language_guard_is_stable,
11040 ) else {
11041 continue;
11042 };
11043 let Some(reached) = path.merged(&include_guards) else {
11044 continue;
11045 };
11046 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11047 continue;
11048 }
11049 for (include, _) in
11050 include_path_from_node(node, current_prepared.source()).into_iter()
11051 {
11052 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11053 ¤t_file,
11054 include,
11055 include_targets,
11056 )) else {
11057 continue;
11058 };
11059 if &target == donor_source {
11060 return true;
11061 }
11062 pending.push((target, reached.clone()));
11063 }
11064 continue;
11065 }
11066 push_named_children_reversed(node, &mut nodes);
11067 }
11068 }
11069 false
11070}
11071
11072pub fn cpp_include_closure_reaches(
11085 cpp: &dyn CppSource,
11086 token: QueryToken<'_>,
11087 translation_unit: &ProjectFile,
11088 header: &ProjectFile,
11089) -> bool {
11090 let language_guard_stable = OnceCell::new();
11091 unconditional_include_reaches(
11092 cpp,
11093 token,
11094 translation_unit,
11095 header,
11096 translation_unit,
11097 &mut HashSet::default(),
11098 &|| {
11099 *language_guard_stable.get_or_init(|| {
11100 let mut macro_events =
11101 |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11102 include_language_guard_is_stable_with(
11103 cpp,
11104 token,
11105 translation_unit,
11106 &mut macro_events,
11107 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11108 collect_structured_include_facts(prepared)
11109 },
11110 )
11111 })
11112 },
11113 )
11114}
11115
11116fn unconditional_include_reaches(
11117 cpp: &dyn CppSource,
11118 token: QueryToken<'_>,
11119 first: &ProjectFile,
11120 donor_source: &ProjectFile,
11121 reference_file: &ProjectFile,
11122 known_missing: &mut HashSet<ProjectFile>,
11123 language_guard_is_stable: &dyn Fn() -> bool,
11124) -> bool {
11125 if first == donor_source {
11126 return true;
11127 }
11128 if known_missing.contains(first) {
11129 return false;
11130 }
11131 let reference_is_c = reference_file
11132 .rel_path()
11133 .extension()
11134 .and_then(|extension| extension.to_str())
11135 == Some("c");
11136 if let Some(reaches) =
11137 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11138 {
11139 return reaches;
11140 }
11141 let mut visited = HashSet::default();
11142 let mut files = vec![first.clone()];
11143 let include_targets = cpp.include_target_index();
11144 while let Some(file) = files.pop() {
11145 if file == *donor_source {
11146 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11147 return true;
11148 }
11149 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11150 continue;
11151 }
11152 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11153 continue;
11154 };
11155 let mut cursor = prepared.tree().walk();
11157 let mut nodes = vec![prepared.tree().root_node()];
11158 while let Some(node) = nodes.pop() {
11159 if node.kind() == "preproc_include" {
11160 let Some(required_guards) = include_directive_guard_requirements(
11161 node,
11162 prepared.source(),
11163 reference_is_c,
11164 language_guard_is_stable,
11165 ) else {
11166 continue;
11167 };
11168 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11169 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11170 {
11171 if let Some(target) = unique_include_target(
11172 resolve_include_targets_with_index(&file, include, include_targets),
11173 ) {
11174 files.push(target);
11175 }
11176 }
11177 }
11178 continue;
11179 }
11180 let first_pushed = nodes.len();
11181 nodes.extend(node.named_children(&mut cursor));
11182 nodes[first_pushed..].reverse();
11183 }
11184 }
11185 known_missing.extend(visited);
11186 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11187 false
11188}
11189
11190fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11191 cpp: &dyn CppSource,
11192 token: QueryToken<'_>,
11193 file: &ProjectFile,
11194 mut macro_events: MacroEvents,
11195 mut include_facts: IncludeFacts,
11196) -> bool
11197where
11198 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11199 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11200{
11201 if !crate::imports::is_cpp_translation_unit(file) {
11205 return false;
11206 }
11207 if !cpp.compile_contexts_for(file).is_empty()
11210 || cpp
11211 .reaching_translation_units(file)
11212 .iter()
11213 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11214 {
11215 return false;
11216 }
11217 let mut pending = vec![file.clone()];
11218 let mut visited = HashSet::default();
11219 while let Some(file) = pending.pop() {
11220 if !visited.insert(file.clone()) {
11221 continue;
11222 }
11223 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11224 return false;
11225 };
11226 if macro_events(&file).iter().any(|event| match event {
11227 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11228 name == "__cplusplus"
11229 }
11230 MacroEvent::Invalidate { .. } => true,
11231 MacroEvent::Include { targets, .. } => targets.is_empty(),
11232 }) {
11233 return false;
11234 }
11235 for fact in include_facts(&file, prepared.as_ref()).iter() {
11238 let targets =
11239 resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11240 if targets.is_empty() {
11241 return false;
11242 }
11243 pending.extend(targets);
11244 }
11245 }
11246 true
11247}
11248
11249fn declaration_guard_requirements(
11250 analyzer: &CppGraphSource<'_>,
11251 cpp: &dyn CppSource,
11252 candidate: &CodeUnit,
11253) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11254 declaration_guard_sites(analyzer, cpp, candidate, None)
11255 .into_iter()
11256 .map(|site| (site.byte, site.guards))
11257 .collect()
11258}
11259
11260struct DeclarationGuardSite {
11263 byte: usize,
11265 guards: HashSet<PreprocessorGuard>,
11267 completed_family_before_reference: bool,
11271}
11272
11273fn declaration_guard_sites(
11276 analyzer: &CppGraphSource<'_>,
11277 cpp: &dyn CppSource,
11278 candidate: &CodeUnit,
11279 reference_byte: Option<usize>,
11280) -> Vec<DeclarationGuardSite> {
11281 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11282 return Vec::new();
11283 };
11284 let root = prepared.tree().root_node();
11285 analyzer
11286 .declaration_source_occurrences(candidate)
11287 .into_iter()
11288 .flatten()
11289 .filter_map(|occurrence| {
11290 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11291 Some(DeclarationGuardSite {
11292 byte: occurrence.range.start_byte,
11293 guards,
11294 completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11295 root.descendant_for_byte_range(
11296 occurrence.range.start_byte,
11297 occurrence.range.end_byte,
11298 )
11299 .is_some_and(|node| {
11300 declaration_follows_completed_family_before_reference(
11301 node,
11302 prepared.source(),
11303 reference_byte,
11304 )
11305 })
11306 }),
11307 })
11308 })
11309 .collect()
11310}
11311
11312fn declaration_follows_completed_family_before_reference(
11331 node: Node<'_>,
11332 source: &str,
11333 reference_byte: usize,
11334) -> bool {
11335 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11336}
11337
11338pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11341 let mut cache = PreprocessorContextCache::default();
11342 callable_guard_completion_byte_with_cache(node, source, &mut cache)
11343}
11344
11345pub(crate) fn callable_guard_completion_byte_with_cache(
11346 node: Node<'_>,
11347 source: &str,
11348 cache: &mut PreprocessorContextCache,
11349) -> Option<usize> {
11350 let mut completion_byte = 0;
11351 let mut ancestor = node.parent();
11352 while let Some(conditional) = ancestor {
11353 if matches!(
11354 conditional.kind(),
11355 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11356 ) && !is_file_covering_include_guard(conditional, source)
11357 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11358 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11359 {
11360 let family = preprocessor_conditional_family_root(conditional);
11361 if !preprocessor_conditional_family_has_terminal_else(family) {
11362 return None;
11363 }
11364 completion_byte = completion_byte.max(family.end_byte());
11365 }
11366 ancestor = conditional.parent();
11367 }
11368 Some(completion_byte)
11369}
11370
11371fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11372 analyzer
11373 .ranges(candidate)
11374 .into_iter()
11375 .map(|range| range.start_byte)
11376 .min()
11377}
11378
11379fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11385 let (first, rest) = contexts.split_first()?;
11386 Some(
11387 first
11388 .defined_macros
11389 .iter()
11390 .filter(|name| {
11391 rest.iter()
11392 .all(|context| context.defined_macros.contains(*name))
11393 })
11394 .cloned()
11395 .collect(),
11396 )
11397}
11398
11399pub fn guard_requirements_hold_at_reference(
11400 required: &HashSet<PreprocessorGuard>,
11401 reference: Option<&HashSet<PreprocessorGuard>>,
11402) -> bool {
11403 reference.is_some_and(|active| {
11404 required
11405 .iter()
11406 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11407 })
11408}
11409
11410fn preprocessor_guard_holds_at_reference(
11411 required: &PreprocessorGuard,
11412 active: &HashSet<PreprocessorGuard>,
11413) -> bool {
11414 if active.contains(required) {
11415 return true;
11416 }
11417 let active_expression = BooleanGuardExpression::all(
11418 active
11419 .iter()
11420 .filter_map(PreprocessorGuard::as_boolean_expression),
11421 );
11422 required
11423 .as_boolean_expression()
11424 .is_some_and(|required| active_expression.implies(&required))
11425}
11426
11427fn guards_compatible_at_reference(
11432 declaration: &HashSet<PreprocessorGuard>,
11433 reference: Option<&HashSet<PreprocessorGuard>>,
11434) -> bool {
11435 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11436}
11437
11438pub fn preprocessor_conditional_family_range(
11447 root: Node<'_>,
11448 start_byte: usize,
11449 end_byte: usize,
11450) -> Option<(usize, usize)> {
11451 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11452 preprocessor_conditional_family_node(node)
11453 .map(|family| (family.start_byte(), family.end_byte()))
11454}
11455
11456pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11457 let mut ancestor = Some(node);
11458 while let Some(current) = ancestor {
11459 if is_cpp_preprocessor_conditional(current)
11460 && preprocessor_conditional_contains_descendant(current, node)
11461 {
11462 return Some(preprocessor_conditional_family_root(current));
11463 }
11464 ancestor = current.parent();
11465 }
11466 None
11467}
11468
11469pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11470 node: Node<'tree>,
11471 cache: &mut PreprocessorContextCache,
11472) -> Option<Node<'tree>> {
11473 let mut ancestor = node.parent();
11474 while let Some(current) = ancestor {
11475 if is_cpp_preprocessor_conditional(current)
11476 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11477 {
11478 let family = preprocessor_conditional_family_root(current);
11479 if preprocessor_conditional_family_has_terminal_else(family) {
11480 return Some(family);
11481 }
11482 }
11483 ancestor = current.parent();
11484 }
11485 None
11486}
11487
11488fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11489 while let Some(parent) = conditional.parent() {
11490 let is_alternative = parent
11491 .child_by_field_name("alternative")
11492 .is_some_and(|alternative| {
11493 alternative.start_byte() == conditional.start_byte()
11494 && alternative.end_byte() == conditional.end_byte()
11495 });
11496 if !is_alternative {
11497 break;
11498 }
11499 conditional = parent;
11500 }
11501 conditional
11502}
11503
11504fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11505 loop {
11506 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11507 return false;
11508 };
11509 match alternative.kind() {
11510 "preproc_else" => return true,
11511 "preproc_elif" => conditional = alternative,
11512 _ => return false,
11513 }
11514 }
11515}
11516
11517fn include_directive_guard_requirements(
11530 node: Node<'_>,
11531 source: &str,
11532 reference_is_c: bool,
11533 language_guard_is_stable: &dyn Fn() -> bool,
11534) -> Option<PreprocessorGuardEnvironment> {
11535 let mut required = preprocessor_guard_environment_by_family(node, source)?;
11536 let excluded_by_language = required
11537 .all
11538 .iter()
11539 .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11540 if excluded_by_language {
11541 return None;
11542 }
11543 let is_language_guard = |guard: &PreprocessorGuard| {
11544 matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11545 if name == "__cplusplus")
11546 };
11547 if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11550 for guards in [&mut required.all, &mut required.partial] {
11551 guards.retain(|guard| !is_language_guard(guard));
11552 }
11553 }
11554 Some(required)
11555}
11556
11557fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11558 match guard {
11559 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11560 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11561 _ => false,
11562 }
11563}
11564
11565pub fn preprocessor_guard_environment(
11566 node: Node<'_>,
11567 source: &str,
11568) -> Option<HashSet<PreprocessorGuard>> {
11569 let mut cache = PreprocessorContextCache::default();
11570 preprocessor_guard_environment_with_cache(node, source, &mut cache)
11571}
11572
11573pub(crate) fn preprocessor_guard_environment_with_cache(
11574 node: Node<'_>,
11575 source: &str,
11576 cache: &mut PreprocessorContextCache,
11577) -> Option<HashSet<PreprocessorGuard>> {
11578 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11579 .map(|environment| environment.all)
11580}
11581
11582#[derive(Clone)]
11593struct PreprocessorGuardEnvironment {
11594 all: HashSet<PreprocessorGuard>,
11595 partial: HashSet<PreprocessorGuard>,
11596}
11597
11598impl PreprocessorGuardEnvironment {
11599 fn merged(&self, other: &Self) -> Option<Self> {
11603 Some(Self {
11604 all: merge_preprocessor_guards(&self.all, &other.all)?,
11605 partial: self.partial.union(&other.partial).cloned().collect(),
11606 })
11607 }
11608}
11609
11610fn preprocessor_guard_environment_by_family(
11611 node: Node<'_>,
11612 source: &str,
11613) -> Option<PreprocessorGuardEnvironment> {
11614 let mut cache = PreprocessorContextCache::default();
11615 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11616}
11617
11618fn preprocessor_guard_environment_by_family_with_cache(
11619 node: Node<'_>,
11620 source: &str,
11621 cache: &mut PreprocessorContextCache,
11622) -> Option<PreprocessorGuardEnvironment> {
11623 let mut all = HashSet::default();
11624 let mut partial = HashSet::default();
11625 let mut ancestor = node.parent();
11626 while let Some(conditional) = ancestor {
11627 if matches!(
11628 conditional.kind(),
11629 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11630 ) && !is_file_covering_include_guard(conditional, source)
11631 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11632 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11633 {
11634 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11635 match guard {
11636 PreprocessorGuard::Constant(true) => {
11637 ancestor = conditional.parent();
11638 continue;
11639 }
11640 PreprocessorGuard::Constant(false) => return None,
11641 _ => {}
11642 }
11643 if all.contains(&guard.negated()) {
11644 return None;
11645 }
11646 if !preprocessor_conditional_family_has_terminal_else(
11647 preprocessor_conditional_family_root(conditional),
11648 ) {
11649 partial.insert(guard.clone());
11650 }
11651 all.insert(guard);
11652 }
11653 ancestor = conditional.parent();
11654 }
11655 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11656 match guard {
11657 PreprocessorGuard::Constant(true) => {}
11658 PreprocessorGuard::Constant(false) => return None,
11659 _ => {
11660 if all.contains(&guard.negated()) {
11661 return None;
11662 }
11663 partial.insert(guard.clone());
11667 all.insert(guard);
11668 }
11669 }
11670 }
11671 Some(PreprocessorGuardEnvironment { all, partial })
11672}
11673
11674pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11675 node: Node<'_>,
11676 source: &str,
11677 cache: &mut PreprocessorContextCache,
11678) -> Option<HashSet<PreprocessorGuard>> {
11679 let mut guards = HashSet::default();
11680 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11681 match guard {
11682 PreprocessorGuard::Constant(true) => {}
11683 PreprocessorGuard::Constant(false) => return None,
11684 _ => {
11685 if guards.contains(&guard.negated()) {
11686 return None;
11687 }
11688 guards.insert(guard);
11689 }
11690 }
11691 }
11692 Some(guards)
11693}
11694
11695fn callable_preprocessor_ancestor_guards(
11696 node: Node<'_>,
11697 source: &str,
11698) -> Option<Vec<PreprocessorGuard>> {
11699 let mut cache = PreprocessorContextCache::default();
11700 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11701}
11702
11703fn callable_preprocessor_ancestor_guards_with_cache(
11704 node: Node<'_>,
11705 source: &str,
11706 cache: &mut PreprocessorContextCache,
11707) -> Option<Vec<PreprocessorGuard>> {
11708 let mut guards = Vec::new();
11709 let mut ancestor = node.parent();
11710 while let Some(conditional) = ancestor {
11711 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11712 && !is_file_covering_include_guard(conditional, source)
11713 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11714 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11715 {
11716 guards.push(preprocessor_guard_for_descendant(
11717 conditional,
11718 node,
11719 source,
11720 )?);
11721 }
11722 ancestor = conditional.parent();
11723 }
11724 Some(guards)
11725}
11726
11727fn fragmented_statement_preprocessor_guard(
11728 descendant: Node<'_>,
11729 source: &str,
11730) -> Option<PreprocessorGuard> {
11731 let mut ancestor = descendant.parent();
11737 while let Some(statement) = ancestor {
11738 if statement.kind() == "if_statement"
11739 && let (Some(consequence), Some(alternative)) = (
11740 statement.child_by_field_name("consequence"),
11741 statement.child_by_field_name("alternative"),
11742 )
11743 && alternative.start_byte() <= descendant.start_byte()
11744 && descendant.end_byte() <= alternative.end_byte()
11745 {
11746 let mut cursor = consequence.walk();
11747 let openers = consequence
11748 .named_children(&mut cursor)
11749 .filter(|child| {
11750 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11751 && child
11752 .child(child.child_count().saturating_sub(1))
11753 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11754 })
11755 .collect::<Vec<_>>();
11756 if openers.len() != 1 {
11757 ancestor = statement.parent();
11758 continue;
11759 }
11760
11761 let mut terminators = Vec::new();
11762 let mut stack = vec![alternative];
11763 while let Some(node) = stack.pop() {
11764 if node.kind() == "preproc_call"
11765 && node.start_byte() >= descendant.end_byte()
11766 && node
11767 .child_by_field_name("directive")
11768 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11769 {
11770 terminators.push(node);
11771 continue;
11772 }
11773 push_named_children_reversed(node, &mut stack);
11774 }
11775 if terminators.len() == 1 {
11776 return simple_preprocessor_guard(openers[0], source);
11777 }
11778 }
11779 ancestor = statement.parent();
11780 }
11781 None
11782}
11783
11784pub(crate) fn preprocessor_guard_for_descendant(
11785 conditional: Node<'_>,
11786 descendant: Node<'_>,
11787 source: &str,
11788) -> Option<PreprocessorGuard> {
11789 let mut guard = simple_preprocessor_guard(conditional, source)?;
11790 if conditional
11791 .child_by_field_name("alternative")
11792 .is_some_and(|alternative| {
11793 alternative.start_byte() <= descendant.start_byte()
11794 && descendant.end_byte() <= alternative.end_byte()
11795 })
11796 {
11797 let alternative = conditional.child_by_field_name("alternative")?;
11798 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11802 return None;
11803 }
11804 guard = guard.negated();
11805 }
11806 Some(guard)
11807}
11808
11809pub(crate) fn preprocessor_conditional_contains_descendant(
11810 conditional: Node<'_>,
11811 descendant: Node<'_>,
11812) -> bool {
11813 cpp_displaced_preprocessor_boundary(conditional)
11814 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11815}
11816
11817fn preprocessor_conditional_contains_descendant_with_cache(
11818 conditional: Node<'_>,
11819 descendant: Node<'_>,
11820 cache: &mut PreprocessorContextCache,
11821) -> bool {
11822 cache
11823 .displaced_boundary_end(conditional)
11824 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11825}
11826
11827pub fn merge_preprocessor_guards(
11828 left: &HashSet<PreprocessorGuard>,
11829 right: &HashSet<PreprocessorGuard>,
11830) -> Option<HashSet<PreprocessorGuard>> {
11831 let mut merged = left.clone();
11832 for guard in right {
11833 let boolean_negation = guard
11834 .as_boolean_expression()
11835 .map(|expression| expression.negated());
11836 if merged.contains(&guard.negated())
11837 || boolean_negation.is_some_and(|negated| {
11838 merged
11839 .iter()
11840 .filter_map(PreprocessorGuard::as_boolean_expression)
11841 .any(|existing| existing == negated)
11842 })
11843 {
11844 return None;
11845 }
11846 merged.insert(guard.clone());
11847 }
11848 Some(merged)
11849}
11850
11851fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11852 if conditional.kind() == "preproc_ifdef" {
11853 let name = conditional.child_by_field_name("name")?;
11854 let name = node_text(name, source).to_string();
11855 return match conditional.child(0)?.kind() {
11856 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11857 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11858 _ => None,
11859 };
11860 }
11861 let condition = conditional.child_by_field_name("condition")?;
11862 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11863 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11864 node_text(condition, source),
11865 )))
11866 })
11867}
11868
11869fn simple_preprocessor_expression_guard(
11870 expression: Node<'_>,
11871 source: &str,
11872) -> Option<PreprocessorGuard> {
11873 match expression.kind() {
11874 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11875 node_text(expression, source).to_string(),
11876 ))),
11877 "number_literal" => match node_text(expression, source).trim() {
11878 "0" => Some(PreprocessorGuard::Constant(false)),
11879 "1" => Some(PreprocessorGuard::Constant(true)),
11880 _ => None,
11881 },
11882 "preproc_defined" => {
11883 let identifier = (0..expression.named_child_count())
11884 .filter_map(|index| expression.named_child(index))
11885 .find(|child| child.kind() == "identifier")?;
11886 Some(PreprocessorGuard::Defined(
11887 node_text(identifier, source).to_string(),
11888 ))
11889 }
11890 "unary_expression"
11891 if expression
11892 .child_by_field_name("operator")
11893 .is_some_and(|operator| operator.kind() == "!") =>
11894 {
11895 simple_preprocessor_expression_guard(
11896 expression.child_by_field_name("argument")?,
11897 source,
11898 )
11899 .map(|guard| guard.negated())
11900 }
11901 "parenthesized_expression" => (0..expression.named_child_count())
11902 .filter_map(|index| expression.named_child(index))
11903 .next()
11904 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11905 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11906 expression, source,
11907 ))),
11908 _ => None,
11909 }
11910}
11911
11912fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11913 match expression.kind() {
11914 "number_literal" => match node_text(expression, source).trim() {
11915 "0" => BooleanGuardExpression::Constant(false),
11916 "1" => BooleanGuardExpression::Constant(true),
11917 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11918 expression, source,
11919 ))),
11920 },
11921 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11922 "preproc_defined" => {
11923 let identifier = (0..expression.named_child_count())
11924 .filter_map(|index| expression.named_child(index))
11925 .find(|child| child.kind() == "identifier");
11926 identifier.map_or_else(
11927 || {
11928 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11929 expression, source,
11930 )))
11931 },
11932 |identifier| {
11933 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11934 },
11935 )
11936 }
11937 "unary_expression"
11938 if expression
11939 .child_by_field_name("operator")
11940 .is_some_and(|operator| operator.kind() == "!") =>
11941 {
11942 expression.child_by_field_name("argument").map_or_else(
11943 || {
11944 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11945 expression, source,
11946 )))
11947 },
11948 |argument| boolean_preprocessor_expression(argument, source).negated(),
11949 )
11950 }
11951 "parenthesized_expression" => (0..expression.named_child_count())
11952 .filter_map(|index| expression.named_child(index))
11953 .next()
11954 .map_or_else(
11955 || {
11956 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11957 expression, source,
11958 )))
11959 },
11960 |child| boolean_preprocessor_expression(child, source),
11961 ),
11962 "binary_expression" => {
11963 let operands = || {
11964 Some((
11965 boolean_preprocessor_expression(
11966 expression.child_by_field_name("left")?,
11967 source,
11968 ),
11969 boolean_preprocessor_expression(
11970 expression.child_by_field_name("right")?,
11971 source,
11972 ),
11973 ))
11974 };
11975 match expression
11976 .child_by_field_name("operator")
11977 .map(|operator| operator.kind())
11978 {
11979 Some("&&") => operands().map_or_else(
11980 || {
11981 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11982 expression, source,
11983 )))
11984 },
11985 |(left, right)| BooleanGuardExpression::all([left, right]),
11986 ),
11987 Some("||") => operands().map_or_else(
11988 || {
11989 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11990 expression, source,
11991 )))
11992 },
11993 |(left, right)| BooleanGuardExpression::any([left, right]),
11994 ),
11995 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11996 expression, source,
11997 ))),
11998 }
11999 }
12000 _ => {
12001 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12002 }
12003 }
12004}
12005
12006fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12007 if targets.len() == 1 {
12008 targets.pop()
12009 } else {
12010 None
12011 }
12012}
12013
12014fn callable_declaration_nodes<'tree>(
12015 analyzer: &CppGraphSource<'_>,
12016 prepared: &'tree PreparedSyntaxTree,
12017 candidate: &CodeUnit,
12018) -> Vec<Node<'tree>> {
12019 let root = prepared.tree().root_node();
12020 analyzer
12021 .ranges(candidate)
12022 .into_iter()
12023 .filter_map(|range| {
12024 let mut declaration =
12025 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12026 while !matches!(
12039 declaration.kind(),
12040 "declaration" | "field_declaration" | "function_definition"
12041 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12042 declaration,
12043 prepared.source(),
12044 ) {
12045 let Some(parent) = declaration.parent() else {
12046 break;
12047 };
12048 declaration = parent;
12049 }
12050 Some(declaration)
12051 })
12052 .collect()
12053}
12054
12055fn real_function_definition_ancestor<'tree>(
12056 node: Node<'tree>,
12057 source: &str,
12058) -> Option<Node<'tree>> {
12059 let mut ancestor = node.parent();
12060 while let Some(node) = ancestor {
12061 if node.kind() == "function_definition"
12062 && !is_recovered_declaration_scope_container(node, source)
12063 {
12064 return Some(node);
12065 }
12066 ancestor = node.parent();
12067 }
12068 None
12069}
12070
12071fn callable_declaration_activation_in_file(
12072 analyzer: &CppGraphSource<'_>,
12073 candidate: &CodeUnit,
12074 reference: &CallableReferenceContext<'_>,
12075) -> Option<usize> {
12076 analyzer
12077 .declaration_source_occurrences(candidate)?
12078 .into_iter()
12079 .filter_map(|occurrence| {
12080 let activation = occurrence.fact.callable_activation?;
12081 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12082 callable_guard_requirements_are_visible(
12083 &guards,
12084 occurrence.fact.callable_guard_completion_byte,
12085 reference,
12086 )
12087 .then_some(activation)
12088 })
12089 .min()
12090}
12091
12092fn callable_guard_requirements_are_visible(
12093 required: &HashSet<PreprocessorGuard>,
12094 completion_byte: Option<usize>,
12095 reference: &CallableReferenceContext<'_>,
12096) -> bool {
12097 if required.iter().all(|guard| match guard {
12098 PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12099 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12100 guard => reference
12101 .guards()
12102 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12103 }) {
12104 return true;
12105 }
12106 if required.iter().any(|guard| match guard {
12107 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12108 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12109 _ => false,
12110 }) {
12111 return false;
12112 }
12113 let undecided: HashSet<_> = required
12114 .iter()
12115 .filter(|guard| {
12116 !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12117 if name == "__cplusplus")
12118 })
12119 .cloned()
12120 .collect();
12121 reference.position.as_ref().is_some_and(|position| {
12122 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12123 && guards_compatible_at_reference(&undecided, reference.guards())
12124 })
12125}
12126
12127struct CallableReferenceContext<'a> {
12133 file: &'a ProjectFile,
12134 position: Option<CallableReferencePosition<'a>>,
12135}
12136
12137struct CallableReferencePosition<'a> {
12141 prepared: &'a PreparedSyntaxTree,
12142 byte: usize,
12143 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12144}
12145
12146impl CallableReferenceContext<'_> {
12147 fn is_c(&self) -> bool {
12148 self.file
12149 .rel_path()
12150 .extension()
12151 .and_then(|extension| extension.to_str())
12152 == Some("c")
12153 }
12154
12155 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12156 let position = self.position.as_ref()?;
12157 position
12158 .guards
12159 .get_or_init(|| {
12160 position
12161 .prepared
12162 .tree()
12163 .root_node()
12164 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12165 .and_then(|node| {
12166 preprocessor_guard_environment(node, position.prepared.source())
12167 })
12168 })
12169 .as_ref()
12170 }
12171}
12172
12173fn callable_declaration_guard_requirements(
12185 node: Node<'_>,
12186 source: &str,
12187 reference: &CallableReferenceContext<'_>,
12188) -> Option<HashSet<PreprocessorGuard>> {
12189 let reference_is_c = reference.is_c();
12190 let mut required = HashSet::default();
12191 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12192 match guard {
12193 PreprocessorGuard::Constant(true) => {}
12194 PreprocessorGuard::Constant(false) => return None,
12195 guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12196 return None;
12197 }
12198 guard => {
12199 required.insert(guard);
12200 }
12201 }
12202 }
12203 Some(required)
12204}
12205
12206fn callable_preprocessor_context_is_visible_for_reference(
12217 node: Node<'_>,
12218 source: &str,
12219 reference: &CallableReferenceContext<'_>,
12220) -> bool {
12221 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12222 return false;
12223 };
12224 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12225 return true;
12226 }
12227 reference.position.as_ref().is_some_and(|position| {
12228 guards_compatible_at_reference(&required, reference.guards())
12229 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12230 })
12231}
12232
12233fn flattened_macro_namespace_declaration_matches(
12234 analyzer: &CppGraphSource<'_>,
12235 _cpp: &dyn CppSource,
12236 reference_file: &ProjectFile,
12237 visible_declaration: &CodeUnit,
12238 qualified_candidate: &CodeUnit,
12239 reference_byte: usize,
12240) -> bool {
12241 if visible_declaration.kind() != qualified_candidate.kind()
12247 || visible_declaration.identifier() != qualified_candidate.identifier()
12248 || visible_declaration.signature() != qualified_candidate.signature()
12249 || !visible_declaration.package_name().is_empty()
12250 || qualified_candidate.package_name().is_empty()
12251 {
12252 return false;
12253 }
12254
12255 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12256 return false;
12257 };
12258 occurrences.into_iter().any(|occurrence| {
12259 occurrence
12260 .displaced_namespace_closing_brace
12261 .is_some_and(|closing_brace| {
12262 visible_declaration.source() != reference_file || closing_brace < reference_byte
12263 })
12264 })
12265}
12266
12267pub(crate) fn flattened_macro_namespace_components(
12268 declaration: Node<'_>,
12269 source: &str,
12270) -> Option<Vec<String>> {
12271 flattened_macro_function_namespace_components(declaration, source)
12272 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12273}
12274
12275fn flattened_macro_function_namespace_components(
12276 declaration: Node<'_>,
12277 source: &str,
12278) -> Option<Vec<String>> {
12279 let body = declaration
12280 .parent()
12281 .filter(|parent| parent.kind() == "compound_statement")?;
12282 let function = body.parent()?;
12283 if function.child_by_field_name("body") != Some(body) {
12284 return None;
12285 }
12286 let namespace_name = recovered_macro_namespace_name(function, source)?;
12287 let mut components = enclosing_namespace_components(declaration, source)?;
12288 components.push(namespace_name);
12289 Some(components)
12290}
12291
12292fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12303 if function.kind() != "function_definition" || !function.has_error() {
12304 return None;
12305 }
12306 let body = function
12307 .child_by_field_name("body")
12308 .filter(|body| body.kind() == "compound_statement")?;
12309 let mut cursor = function.walk();
12310 let prefix = function
12311 .named_children(&mut cursor)
12312 .take_while(|child| child.start_byte() < body.start_byte())
12313 .filter(|child| child.kind() != "comment")
12314 .collect::<Vec<_>>();
12315 let begin_index = prefix.iter().rposition(|child| {
12316 flattened_macro_sentinel_name(*child, source)
12317 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12318 })?;
12319 let mut identifiers = Vec::new();
12320 let mut stack = prefix[begin_index + 1..]
12321 .iter()
12322 .rev()
12323 .copied()
12324 .collect::<Vec<_>>();
12325 while let Some(current) = stack.pop() {
12326 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12327 identifiers.push(identifier);
12328 continue;
12329 }
12330 let mut cursor = current.walk();
12331 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12332 stack.extend(children.into_iter().rev());
12333 }
12334 let [keyword, namespace_name] = identifiers.as_slice() else {
12335 return None;
12336 };
12337 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12338 {
12339 return None;
12340 }
12341 let mut next = function.next_named_sibling();
12342 let next = loop {
12343 let candidate = next?;
12344 next = candidate.next_named_sibling();
12345 if candidate.kind() != "comment" {
12346 break candidate;
12347 }
12348 };
12349 flattened_macro_sentinel_name(next, source)
12350 .is_some_and(|name| is_namespace_end_sentinel(&name))
12351 .then(|| namespace_name.clone())
12352}
12353
12354pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12359 crate::declarations::is_recovered_exported_class_container(node, source)
12360 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12361 node, source,
12362 )
12363 || recovered_macro_namespace_name(node, source).is_some()
12364}
12365
12366fn flattened_macro_error_namespace_components(
12367 declaration: Node<'_>,
12368 source: &str,
12369) -> Option<Vec<String>> {
12370 let parent = declaration
12371 .parent()
12372 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12373 let mut cursor = parent.walk();
12374 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12375 let declaration_index = siblings
12376 .iter()
12377 .position(|candidate| same_node(*candidate, declaration))?;
12378 let begin_index = (0..declaration_index).rev().find(|index| {
12379 flattened_macro_sentinel_name(siblings[*index], source)
12380 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12381 })?;
12382
12383 let significant = siblings[begin_index + 1..declaration_index]
12384 .iter()
12385 .copied()
12386 .filter(|node| node.kind() != "comment")
12387 .collect::<Vec<_>>();
12388 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12389 return None;
12390 };
12391 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12392 return None;
12393 }
12394 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12395 if significant[2..].iter().any(|node| {
12396 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12397 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12398 })
12399 }) {
12400 return None;
12401 }
12402
12403 let mut saw_namespace_close = false;
12404 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12405 if sibling.kind() == "comment" {
12406 continue;
12407 }
12408 if !saw_namespace_close {
12409 if direct_unmatched_closing_brace(sibling) {
12410 saw_namespace_close = true;
12411 continue;
12412 }
12413 if flattened_macro_sentinel_name(sibling, source).is_some() {
12414 return None;
12415 }
12416 continue;
12417 }
12418 if !flattened_macro_sentinel_name(sibling, source)
12419 .is_some_and(|name| is_namespace_end_sentinel(&name))
12420 {
12421 return None;
12422 }
12423 let mut components = enclosing_namespace_components(declaration, source)?;
12424 components.push(namespace_name);
12425 return Some(components);
12426 }
12427 None
12428}
12429
12430fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12431 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12435 node.named_child(0)?
12436 } else {
12437 node
12438 };
12439 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12440 node.child_by_field_name("type")
12441 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12442 })?;
12443 (cpp_export_macro_token(&candidate)
12444 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12445 .then_some(candidate)
12446}
12447
12448#[derive(Clone, Copy, PartialEq, Eq)]
12451enum NamespaceSentinel {
12452 Begin,
12453 End,
12454}
12455
12456fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12457 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12458 Some(NamespaceSentinel::Begin)
12459 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12460 Some(NamespaceSentinel::End)
12461 } else {
12462 None
12463 }
12464}
12465
12466fn is_namespace_begin_sentinel(name: &str) -> bool {
12467 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12468}
12469
12470fn is_namespace_end_sentinel(name: &str) -> bool {
12471 namespace_sentinel(name) == Some(NamespaceSentinel::End)
12472}
12473
12474fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12475 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12476 return None;
12477 }
12478 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12479 (!cpp_export_macro_token(&name)).then_some(name)
12480}
12481
12482fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12483 if !matches!(
12484 node.kind(),
12485 "identifier" | "namespace_identifier" | "type_identifier"
12486 ) {
12487 return None;
12488 }
12489 let name = normalize_cpp_whitespace(node_text(node, source));
12490 (!name.is_empty()).then_some(name)
12491}
12492
12493fn guard_requirement_sets_match(
12494 left: &[(usize, HashSet<PreprocessorGuard>)],
12495 right: &[(usize, HashSet<PreprocessorGuard>)],
12496) -> bool {
12497 left.len() == right.len()
12498 && left.iter().all(|(_, left_guards)| {
12499 right
12500 .iter()
12501 .any(|(_, right_guards)| left_guards == right_guards)
12502 })
12503 && right.iter().all(|(_, right_guards)| {
12504 left.iter()
12505 .any(|(_, left_guards)| right_guards == left_guards)
12506 })
12507}
12508
12509pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12510 node.kind() == "ERROR"
12511 && (0..node.child_count())
12512 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12513}
12514
12515pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12516 let mut ancestor = node.parent();
12517 while let Some(parent) = ancestor {
12518 if is_cpp_preprocessor_conditional(parent)
12519 && !is_file_covering_include_guard(parent, source)
12520 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12521 {
12522 return false;
12523 }
12524 ancestor = parent.parent();
12525 }
12526 true
12527}
12528
12529fn is_split_cpp_language_linkage_wrapper(
12530 conditional: Node<'_>,
12531 descendant: Node<'_>,
12532 source: &str,
12533) -> bool {
12534 if conditional.child_by_field_name("alternative").is_some()
12535 || !matches!(
12536 simple_preprocessor_guard(conditional, source),
12537 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12538 )
12539 {
12540 return false;
12541 }
12542 let mut current = descendant.parent();
12543 let linkage = loop {
12544 let Some(node) = current else {
12545 return false;
12546 };
12547 if node == conditional {
12548 return false;
12549 }
12550 if node.kind() == "linkage_specification" {
12551 break node;
12552 }
12553 current = node.parent();
12554 };
12555 if linkage
12556 .child_by_field_name("value")
12557 .is_none_or(|value| node_text(value, source) != "\"C\"")
12558 {
12559 return false;
12560 }
12561 let Some(body) = linkage.child_by_field_name("body") else {
12562 return false;
12563 };
12564 let mut cursor = body.walk();
12569 let closes_opening_branch = body
12570 .named_children(&mut cursor)
12571 .take_while(|child| child.end_byte() <= descendant.start_byte())
12572 .any(|child| {
12573 child.kind() == "preproc_call"
12574 && child
12575 .child_by_field_name("directive")
12576 .is_some_and(|directive| node_text(directive, source) == "#endif")
12577 });
12578 let mut cursor = body.walk();
12579 let reopens_for_closing_brace = body
12580 .named_children(&mut cursor)
12581 .skip_while(|child| child.start_byte() < descendant.end_byte())
12582 .any(|child| {
12583 matches!(
12584 simple_preprocessor_guard(child, source),
12585 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12586 ) && {
12587 let mut tokens = child.walk();
12588 child
12589 .children(&mut tokens)
12590 .any(|token| token.kind() == "#endif" && token.is_missing())
12591 }
12592 });
12593 closes_opening_branch && reopens_for_closing_brace
12594}
12595
12596pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12600 node.child_by_field_name("arguments")
12601 .or_else(|| node.child_by_field_name("parameters"))
12602 .or_else(|| node.child_by_field_name("value"))
12603 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12604 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12605}
12606
12607pub fn call_arity(node: Node<'_>) -> usize {
12608 call_arguments_node(node)
12609 .map(|args| argument_children(args).count())
12610 .unwrap_or(0)
12611}
12612
12613pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12614 let recovered_block_arguments = recovered_block_literal_arguments(node);
12615 (0..node.child_count())
12616 .filter_map(move |index| node.child(index))
12617 .filter(|child| child.is_named() && !child.is_extra())
12618 .flat_map(move |child| {
12619 if let Some((raw, left, right)) = recovered_block_arguments
12620 && child == raw
12621 {
12622 [Some(left), Some(right)]
12623 } else {
12624 [Some(child), None]
12625 }
12626 })
12627 .flatten()
12628}
12629
12630pub fn recovered_c_new_expression_arguments(
12639 node: Node<'_>,
12640 uses_c_semantics: bool,
12641) -> Option<[Node<'_>; 2]> {
12642 if !uses_c_semantics || node.kind() != "new_expression" {
12643 return None;
12644 }
12645 let parent = node.parent()?;
12646 if parent.kind() != "argument_list" {
12647 return None;
12648 }
12649 let keyword = node.child(0)?;
12650 let error = node.child(1)?;
12651 let trailing = node.child(2)?;
12652 if node.child(3).is_some()
12653 || keyword.kind() != "new"
12654 || keyword.is_named()
12655 || keyword.child_count() != 0
12656 || error.kind() != "ERROR"
12657 || !error.is_extra()
12658 || error.child_count() != 1
12659 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12660 || node.child_by_field_name("type") != Some(trailing)
12661 || trailing.kind() != "type_identifier"
12662 {
12663 return None;
12664 }
12665 Some([keyword, trailing])
12666}
12667
12668pub fn recovered_c_new_expression_argument_at(
12671 mut node: Node<'_>,
12672 start_byte: usize,
12673 end_byte: usize,
12674 uses_c_semantics: bool,
12675) -> Option<Node<'_>> {
12676 if !uses_c_semantics {
12682 return None;
12683 }
12684 loop {
12685 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12686 return arguments.into_iter().find(|argument| {
12687 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12688 });
12689 }
12690 node = node.parent()?;
12691 }
12692}
12693
12694fn recovered_c_keyword_argument_count(
12695 file: &ProjectFile,
12696 call: Node<'_>,
12697 arguments: Node<'_>,
12698 source: &str,
12699) -> usize {
12700 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12706 return 0;
12707 }
12708 let mut ancestor = Some(call);
12709 let function = loop {
12710 let Some(current) = ancestor else {
12711 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12712 .unwrap_or(0);
12713 };
12714 if current.kind() == "function_definition" {
12715 break current;
12716 }
12717 ancestor = current.parent();
12718 };
12719 let Some(parameters) = function
12720 .child_by_field_name("declarator")
12721 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12722 else {
12723 return 0;
12724 };
12725 let displaced_parameter_keywords = (0..parameters.child_count())
12726 .filter_map(|index| parameters.child(index))
12727 .filter(|error| error.kind() == "ERROR")
12728 .filter_map(|error| {
12729 let parameter = error.prev_named_sibling()?;
12730 if parameter.kind() != "parameter_declaration"
12731 || parameter.end_byte() != error.start_byte()
12732 || extract_variable_name(parameter, source).is_some()
12733 {
12734 return None;
12735 }
12736 let mut children = (0..error.child_count())
12737 .filter_map(|index| error.child(index))
12738 .filter(|child| !child.is_extra() && !child.is_missing());
12739 let keyword = children.next()?;
12740 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12741 .then_some(keyword)
12742 })
12743 .collect::<Vec<_>>();
12744 if displaced_parameter_keywords.is_empty() {
12745 return 0;
12746 }
12747
12748 (0..arguments.child_count())
12749 .filter_map(|index| arguments.child(index))
12750 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12751 .filter(|error| {
12752 let mut children = (0..error.child_count())
12753 .filter_map(|index| error.child(index))
12754 .filter(|child| !child.is_extra() && !child.is_missing());
12755 let Some(comma) = children.next() else {
12756 return false;
12757 };
12758 let Some(keyword) = children.next() else {
12759 return false;
12760 };
12761 children.next().is_none()
12762 && comma.kind() == ","
12763 && !keyword.is_named()
12764 && keyword.child_count() == 0
12765 && displaced_parameter_keywords
12766 .iter()
12767 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12768 })
12769 .count()
12770}
12771
12772fn recovered_c_keyword_argument_count_from_collapsed_function(
12778 call: Node<'_>,
12779 arguments: Node<'_>,
12780) -> Option<usize> {
12781 let argument_keywords = children_iter(arguments)
12782 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12783 .filter_map(|error| {
12784 let mut children =
12785 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12786 let comma = children.next()?;
12787 let keyword = children.next()?;
12788 (children.next().is_none()
12789 && comma.kind() == ","
12790 && !keyword.is_named()
12791 && keyword.child_count() == 0)
12792 .then_some(keyword)
12793 })
12794 .collect::<Vec<_>>();
12795 if argument_keywords.is_empty() {
12796 return Some(0);
12797 }
12798
12799 let mut ancestor = call.parent();
12804 let body = loop {
12805 let current = ancestor?;
12806 if matches!(current.kind(), "function_definition" | "lambda_expression") {
12807 return None;
12808 }
12809 if current.kind() == "compound_statement"
12810 && current
12811 .parent()
12812 .is_some_and(|parent| parent.kind() == "ERROR")
12813 {
12814 break current;
12815 }
12816 ancestor = current.parent();
12817 };
12818 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12819 let function_error = parameter_error
12820 .parent()
12821 .filter(|parent| parent.kind() == "ERROR")?;
12822 let function_children = children_iter(function_error).collect::<Vec<_>>();
12823 let parameter_error_index = function_children
12824 .iter()
12825 .position(|child| same_node(*child, parameter_error))?;
12826 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
12829 .iter()
12830 .enumerate()
12831 .any(|(index, child)| {
12832 function_error.field_name_for_child(index as u32) == Some("type")
12833 && !child.is_extra()
12834 && !child.is_missing()
12835 && matches!(
12836 child.kind(),
12837 "primitive_type" | "sized_type_specifier" | "type_identifier"
12838 )
12839 });
12840 if parameter_error_index < 2
12841 || !has_return_type
12842 || function_children[parameter_error_index - 1].kind() != "("
12843 || function_children[parameter_error_index - 2].kind() != "identifier"
12844 {
12845 return Some(0);
12846 }
12847
12848 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
12849 let body_index = parameter_children
12850 .iter()
12851 .position(|child| same_node(*child, body))?;
12852 let close_parenthesis_index = parameter_children[..body_index]
12853 .iter()
12854 .position(|child| child.kind() == ")")?;
12855 let parameter_keywords = parameter_children[..close_parenthesis_index]
12856 .iter()
12857 .enumerate()
12858 .filter(|(index, child)| {
12859 !child.is_named()
12860 && !child.is_extra()
12861 && !child.is_missing()
12862 && child.child_count() == 0
12863 && argument_keywords
12864 .iter()
12865 .any(|keyword| keyword.kind_id() == child.kind_id())
12866 && collapsed_c_parameter_name_position(¶meter_children, *index)
12867 })
12868 .map(|(_, child)| *child)
12869 .collect::<Vec<_>>();
12870 Some(
12871 argument_keywords
12872 .iter()
12873 .filter(|keyword| {
12874 parameter_keywords
12875 .iter()
12876 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12877 })
12878 .count(),
12879 )
12880}
12881
12882fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
12887 let segment_start = children[..keyword_index]
12888 .iter()
12889 .rposition(|child| matches!(child.kind(), "(" | ","))
12890 .map_or(0, |index| index + 1);
12891 let mut saw_type = false;
12892 let mut saw_pointer = false;
12893 for child in &children[segment_start..keyword_index] {
12894 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
12895 saw_pointer = true;
12896 continue;
12897 }
12898 if saw_pointer {
12899 return false;
12900 }
12901 if !child.is_named() || child.is_extra() || child.is_missing() {
12902 return false;
12903 }
12904 if child.kind() == "type_qualifier" {
12905 continue;
12906 }
12907 if matches!(
12908 child.kind(),
12909 "class_specifier"
12910 | "dependent_type"
12911 | "enum_specifier"
12912 | "identifier"
12913 | "primitive_type"
12914 | "qualified_identifier"
12915 | "scoped_identifier"
12916 | "struct_specifier"
12917 | "template_type"
12918 | "type_identifier"
12919 | "union_specifier"
12920 ) {
12921 saw_type = true;
12922 continue;
12923 }
12924 return false;
12925 }
12926 saw_type && saw_pointer
12927}
12928
12929fn recovered_block_literal_arguments<'tree>(
12930 arguments: Node<'tree>,
12931) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12932 if arguments.kind() != "argument_list" {
12933 return None;
12934 }
12935 let mut raw_arguments = (0..arguments.child_count())
12936 .filter_map(|index| arguments.child(index))
12937 .filter(|child| child.is_named() && !child.is_extra());
12938 let raw = raw_arguments.next()?;
12939 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12940 return None;
12941 }
12942
12943 let left = raw.child_by_field_name("left")?;
12944 if left.is_missing() || left.start_byte() == left.end_byte() {
12945 return None;
12946 }
12947 let right = raw.child_by_field_name("right")?;
12948 if right.kind() != "compound_literal_expression"
12949 || right.is_missing()
12950 || right
12951 .child_by_field_name("type")
12952 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12953 || right
12954 .child_by_field_name("value")
12955 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12956 {
12957 return None;
12958 }
12959 let has_intervening_error = (0..raw.child_count())
12960 .filter_map(|index| raw.child(index))
12961 .any(|child| {
12962 child.kind() == "ERROR"
12963 && !child.is_missing()
12964 && child.start_byte() >= left.end_byte()
12965 && child.end_byte() <= right.start_byte()
12966 });
12967 has_intervening_error.then_some((raw, left, right))
12968}
12969
12970pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12971 match node.kind() {
12972 "new_expression" => node
12973 .child_by_field_name("type")
12974 .or_else(|| node.named_child(0)),
12975 "compound_literal_expression" => node.child_by_field_name("type"),
12976 "call_expression" => node.child_by_field_name("function"),
12977 _ => None,
12978 }
12979}
12980
12981pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
12987 if node.kind() != "cast_expression" {
12988 return None;
12989 }
12990 let descriptor = node.child_by_field_name("type")?;
12991 if descriptor.kind() == "type_descriptor" {
12992 descriptor.child_by_field_name("type")
12993 } else {
12994 Some(descriptor)
12995 }
12996}
12997
12998pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13013 debug_assert_eq!(field.kind(), "field_expression");
13014 let operator = field.child_by_field_name("operator")?;
13015 let mut cursor = field.walk();
13016 let receiver = field
13017 .named_children(&mut cursor)
13018 .filter(|child| child.end_byte() <= operator.start_byte())
13019 .last()?;
13020 if receiver.kind() != "ERROR" {
13021 return Some(receiver);
13022 }
13023 (receiver.named_child_count() == 1)
13024 .then(|| receiver.named_child(0))
13025 .flatten()
13026}
13027
13028pub fn field_initializer_constructs_target(
13029 node: Node<'_>,
13030 ctx: &ScanCtx<'_>,
13031 owner: &CodeUnit,
13032) -> bool {
13033 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13042 return qualified_base_initializer_constructs_target(node, ctx, owner);
13043 }
13044 let Some(name) = node
13045 .child_by_field_name("name")
13046 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13047 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13048 else {
13049 return false;
13050 };
13051 let field_name = node_text(name, ctx.source);
13052 ctx.visibility
13053 .visible_identifier_candidates(ctx.file, field_name)
13054 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13055 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13056}
13057
13058fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13068 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13069}
13070
13071fn qualified_base_initializer_constructs_target(
13072 node: Node<'_>,
13073 ctx: &ScanCtx<'_>,
13074 owner: &CodeUnit,
13075) -> bool {
13076 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13077 return false;
13078 };
13079 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13080 return false;
13081 };
13082 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13083 return false;
13084 };
13085 let resolves_target = |components: &[String]| {
13086 matches!(
13087 ctx.visibility.resolve_type_components_lexically_for_target(
13088 &ctx.analyzer,
13089 ctx.file,
13090 components,
13091 is_globally_qualified_cpp_name(qualified),
13092 &lexical_scope,
13093 owner,
13094 ),
13095 LexicalTypeResolution::Resolved { unit, .. }
13096 if same_visible_symbol(&unit, owner)
13097 )
13098 };
13099 if resolves_target(&components) {
13100 return true;
13101 }
13102
13103 components
13109 .last()
13110 .is_some_and(|terminal| terminal == owner.identifier())
13111 && resolves_target(&components[..components.len() - 1])
13112}
13113
13114fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13115 ctx.visibility
13116 .field_declared_type_fact(&ctx.analyzer, unit)
13117 .is_some_and(|fact| {
13118 ctx.visibility
13119 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13120 || ctx.visibility.resolves_to_type(
13121 &ctx.analyzer,
13122 ctx.file,
13123 &normalize_field_type_text(&fact.type_text),
13124 owner,
13125 )
13126 })
13127}
13128
13129pub fn field_declared_binding(
13130 analyzer: &CppGraphSource<'_>,
13131 visibility: &VisibilityIndex<'_>,
13132 visible_from: &ProjectFile,
13133 field: &CodeUnit,
13134) -> Option<CppScanBinding> {
13135 let (normalized, resolved, indirection) =
13136 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13137 let resolved = resolved
13138 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13139 Some(CppScanBinding::from_type_name(
13140 normalized,
13141 resolved,
13142 indirection,
13143 ))
13144}
13145
13146pub fn field_initializer_holds_subobject(
13153 analyzer: &CppGraphSource<'_>,
13154 visibility: &VisibilityIndex<'_>,
13155 field: &CodeUnit,
13156) -> bool {
13157 visibility
13158 .field_declared_type_fact(analyzer, field)
13159 .is_none_or(|fact| !fact.binds_indirectly)
13160}
13161
13162fn anonymous_aggregate_field_owner(
13169 analyzer: &CppGraphSource<'_>,
13170 visibility: &VisibilityIndex<'_>,
13171 visible_from: &ProjectFile,
13172 field: &CodeUnit,
13173) -> Option<CodeUnit> {
13174 let owner = type_owner_of(analyzer, field)?;
13175 if !owner.is_class() {
13176 return None;
13177 }
13178 let declaration = analyzer.get_source(field, false)?;
13179 let mut parser = Parser::new();
13180 parser
13181 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13182 .ok()?;
13183 let tree = parser.parse(&declaration, None)?;
13184 let mut stack = vec![tree.root_node()];
13185 while let Some(node) = stack.pop() {
13186 if matches!(node.kind(), "declaration" | "field_declaration")
13187 && let Some(type_node) = node
13188 .child_by_field_name("type")
13189 .or_else(|| first_type_child(node))
13190 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13191 && type_node.child_by_field_name("name").is_none()
13192 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13193 {
13194 let matches = visibility
13195 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13196 .into_iter()
13197 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13198 .collect::<Vec<_>>();
13199 return match matches.as_slice() {
13200 [child] => Some((*child).clone()),
13201 _ => None,
13202 };
13203 }
13204 let mut cursor = node.walk();
13205 stack.extend(node.named_children(&mut cursor));
13206 }
13207 None
13208}
13209
13210pub fn anonymous_aggregate_owner(
13217 analyzer: &CppGraphSource<'_>,
13218 file: &ProjectFile,
13219 node: Node<'_>,
13220) -> Option<CodeUnit> {
13221 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13222 || node.child_by_field_name("name").is_some()
13223 {
13224 return None;
13225 }
13226 let mut candidates = analyzer
13227 .declarations(file)
13228 .into_iter()
13229 .filter(|candidate| {
13230 candidate.is_class()
13231 && analyzer.ranges(candidate).into_iter().any(|range| {
13232 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13233 })
13234 })
13235 .collect::<Vec<_>>();
13236 candidates.sort_by_key(|candidate| candidate.fq_name());
13237 candidates.dedup();
13238 match candidates.as_slice() {
13239 [candidate] => Some(candidate.clone()),
13240 _ => None,
13241 }
13242}
13243
13244fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13246 let Some(first) = candidates.first() else {
13247 return Err(TypeCandidateFailure::Unresolvable);
13248 };
13249 if candidates
13250 .iter()
13251 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13252 {
13253 Ok((*first).clone())
13254 } else {
13255 Err(TypeCandidateFailure::Ambiguous)
13256 }
13257}
13258
13259fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13260 logical_type_candidate(candidates).ok()
13261}
13262
13263fn unique_type_candidate_preserving_alias(
13264 analyzer: &CppGraphSource<'_>,
13265 file: &ProjectFile,
13266 candidates: &[&CodeUnit],
13267) -> Option<CodeUnit> {
13268 let first = *candidates.first()?;
13269 if declared_type_alias(analyzer, first) {
13270 return candidates
13271 .iter()
13272 .all(|candidate| {
13273 declared_type_alias(analyzer, candidate)
13274 && candidate.kind() == first.kind()
13275 && candidate.fq_name() == first.fq_name()
13276 && candidate.source() == first.source()
13277 })
13278 .then(|| first.clone());
13279 }
13280 if analyzer.reference_uses_c_semantics(file)
13281 && first.is_class()
13282 && indexed_c_tag_kind(analyzer, first).is_some()
13283 {
13284 let mut full_source = None;
13285 let mut tag_kind = None;
13286 for candidate in candidates.iter().copied() {
13287 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13288 if tag_kind
13289 .replace(candidate_tag_kind)
13290 .is_some_and(|existing| existing != candidate_tag_kind)
13291 {
13292 return None;
13293 }
13294 if cpp_class_declaration_strength(analyzer, candidate)
13295 == CppClassDeclarationStrength::Full
13296 && full_source
13297 .replace(candidate.source())
13298 .is_some_and(|existing| existing != candidate.source())
13299 {
13300 return None;
13301 }
13302 }
13303 }
13304 candidates
13305 .iter()
13306 .all(|candidate| {
13307 !declared_type_alias(analyzer, candidate)
13308 && candidate.kind() == first.kind()
13309 && candidate.fq_name() == first.fq_name()
13310 })
13311 .then(|| first.clone())
13312}
13313
13314pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13315 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13316 analyzer.type_alias_provider(),
13317 unit,
13318 )
13319}
13320
13321pub fn field_declared_type_binding(
13322 analyzer: &CppGraphSource<'_>,
13323 visibility: &VisibilityIndex<'_>,
13324 visible_from: &ProjectFile,
13325 field: &CodeUnit,
13326) -> Option<(String, Option<CodeUnit>, i32)> {
13327 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13328 let normalized = normalize_field_type_text(&fact.type_text);
13329 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13330 analyzer,
13331 visible_from,
13332 field,
13333 &normalized,
13334 );
13335 let resolved = match (primary, fact.template_arguments.as_deref()) {
13336 (Some(primary), Some(arguments)) => visibility
13337 .resolve_template_arguments(visible_from, primary, arguments)
13338 .ok(),
13339 (resolved, None) => resolved,
13340 (None, Some(_)) => None,
13341 };
13342 Some((normalized, resolved, fact.indirection))
13343}
13344
13345fn decode_field_declared_type_fact(
13346 analyzer: &CppGraphSource<'_>,
13347 field: &CodeUnit,
13348) -> Option<DeclaredFieldTypeFact> {
13349 if let Some(facts) = analyzer.declaration_source_properties(field)
13350 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13351 && facts
13352 .iter()
13353 .all(|fact| fact.field_type.as_ref() == Some(first))
13354 {
13355 return Some(first.clone());
13356 }
13357 let Some(declaration) = analyzer.get_source(field, false) else {
13358 return decode_indexed_field_declared_type_fact(analyzer, field);
13359 };
13360 let mut parser = Parser::new();
13361 parser
13362 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13363 .ok()?;
13364 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13368 let contextual_tree = parser.parse(&contextual_declaration, None)?;
13369 let mut stack = vec![contextual_tree.root_node()];
13370 while let Some(node) = stack.pop() {
13371 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13372 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13373 {
13374 return Some(DeclaredFieldTypeFact {
13375 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13376 indirection: recovered.pointer_depth(),
13377 binds_indirectly: recovered.pointer_depth() > 0,
13378 template_arguments: None,
13379 });
13380 }
13381 if let Some(recovered) =
13382 recovered_function_like_field_declarator(node, &contextual_declaration)
13383 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13384 {
13385 let type_node = node
13386 .child_by_field_name("type")
13387 .or_else(|| first_type_child(node))?;
13388 return Some(DeclaredFieldTypeFact {
13389 type_text: node_text(type_node, &contextual_declaration).to_string(),
13390 indirection: recovered.pointer_depth(),
13391 binds_indirectly: true,
13394 template_arguments: cpp_template_reference_arguments(
13395 type_node,
13396 &contextual_declaration,
13397 ),
13398 });
13399 }
13400 if let Some(fact) =
13401 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13402 {
13403 return Some(fact);
13404 }
13405 let mut cursor = node.walk();
13406 stack.extend(node.named_children(&mut cursor));
13407 }
13408 let tree = parser.parse(&declaration, None)?;
13409 let mut stack = vec![tree.root_node()];
13410 while let Some(node) = stack.pop() {
13411 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13412 {
13413 return Some(fact);
13414 }
13415 let mut cursor = node.walk();
13416 stack.extend(node.named_children(&mut cursor));
13417 }
13418 None
13419}
13420
13421fn decode_indexed_field_declared_type_fact(
13426 analyzer: &CppGraphSource<'_>,
13427 field: &CodeUnit,
13428) -> Option<DeclaredFieldTypeFact> {
13429 let cpp = analyzer.cpp?;
13430 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13431 let source = prepared.source();
13432 let root = prepared.tree().root_node();
13433 for range in analyzer.ranges(field) {
13434 let end = range.start_byte.saturating_add(1).min(source.len());
13435 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13436 while let Some(node) = current {
13437 if matches!(node.kind(), "declaration" | "field_declaration")
13438 && let Some(fact) =
13439 decode_declared_field_type_node(node, field.identifier(), source)
13440 {
13441 return Some(fact);
13442 }
13443 current = node.parent();
13444 }
13445 }
13446 None
13447}
13448
13449fn decode_declared_field_type_node(
13450 node: Node<'_>,
13451 field_name: &str,
13452 source: &str,
13453) -> Option<DeclaredFieldTypeFact> {
13454 if !matches!(node.kind(), "declaration" | "field_declaration") {
13455 return None;
13456 }
13457 let type_node = node
13458 .child_by_field_name("type")
13459 .or_else(|| first_type_child(node))?;
13460 cpp_declared_field_type_fact(node, type_node, field_name, source)
13461}
13462
13463pub(crate) fn cpp_declared_field_type_fact(
13464 node: Node<'_>,
13465 type_node: Node<'_>,
13466 field_name: &str,
13467 source: &str,
13468) -> Option<DeclaredFieldTypeFact> {
13469 let binding = declared_name_binding(node, type_node, field_name, source)?;
13470 let declared_type = if matches!(
13471 type_node.kind(),
13472 "class_specifier" | "struct_specifier" | "union_specifier"
13473 ) {
13474 type_node.child_by_field_name("name")
13475 } else {
13476 Some(type_node)
13477 };
13478 Some(DeclaredFieldTypeFact {
13479 type_text: declared_type.map_or_else(
13480 || field_name.to_string(),
13481 |declared_type| node_text(declared_type, source).to_string(),
13482 ),
13483 indirection: binding.pointer_depth,
13484 binds_indirectly: binding.indirect,
13485 template_arguments: declared_type
13486 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13487 })
13488}
13489
13490pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13504 let mut parser = Parser::new();
13505 parser
13506 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13507 .ok()?;
13508 let tree = parser.parse(declaration, None)?;
13509 let mut stack = vec![tree.root_node()];
13510 while let Some(node) = stack.pop() {
13511 let type_node = match node.kind() {
13512 "type_definition" => {
13513 let mut cursor = node.walk();
13514 if node
13515 .children_by_field_name("declarator", &mut cursor)
13516 .any(declarator_names_function_type)
13517 {
13518 return None;
13519 }
13520 node.child_by_field_name("type")?
13521 }
13522 "alias_declaration" => {
13523 let type_node = node.child_by_field_name("type")?;
13524 if type_node
13525 .child_by_field_name("declarator")
13526 .is_some_and(declarator_names_function_type)
13527 {
13528 return None;
13529 }
13530 type_node
13531 }
13532 _ => {
13533 let mut cursor = node.walk();
13534 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13535 stack.extend(children.into_iter().rev());
13536 continue;
13537 }
13538 };
13539 return Some(node_text(type_node, declaration).to_string());
13540 }
13541 None
13542}
13543
13544pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13550 let mut current = Some(declarator);
13551 while let Some(node) = current {
13552 match node.kind() {
13553 "function_declarator" | "abstract_function_declarator" => return true,
13554 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13555 current = node.named_child(0);
13556 }
13557 _ => current = node.child_by_field_name("declarator"),
13558 }
13559 }
13560 false
13561}
13562
13563fn decode_structured_alias_target(
13564 analyzer: &CppGraphSource<'_>,
13565 unit: &CodeUnit,
13566) -> Option<StructuredAliasTarget> {
13567 let facts = analyzer.declaration_source_properties(unit)?;
13568 let first = facts.first()?.alias_target.as_ref()?;
13569 facts
13570 .iter()
13571 .all(|fact| fact.alias_target.as_ref() == Some(first))
13572 .then(|| first.clone())
13573}
13574
13575pub(crate) fn structured_alias_type_target(
13576 mut type_node: Node<'_>,
13577 source: &str,
13578) -> Option<StructuredAliasTarget> {
13579 while type_node.kind() == "type_descriptor" {
13580 type_node = type_node.child_by_field_name("type")?;
13581 }
13582 if type_node.kind() == "primitive_type" {
13583 return Some(StructuredAliasTarget::Builtin);
13584 }
13585 if matches!(
13586 type_node.kind(),
13587 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13588 ) {
13589 type_node = type_node.child_by_field_name("name")?;
13590 }
13591 let global = type_node.child_by_field_name("scope").is_none()
13592 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13593 let mut components = Vec::new();
13594 append_structured_type_components(type_node, source, &mut components)?;
13595 let arguments = cpp_template_reference_arguments(type_node, source);
13596 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13597 components,
13598 global,
13599 arguments,
13600 })
13601}
13602
13603fn append_structured_type_components(
13604 node: Node<'_>,
13605 source: &str,
13606 out: &mut Vec<String>,
13607) -> Option<()> {
13608 match node.kind() {
13609 "identifier" | "namespace_identifier" | "type_identifier" => {
13610 out.push(node_text(node, source).to_string());
13611 Some(())
13612 }
13613 "template_type" | "template_function" => {
13614 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13615 }
13616 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13617 let (scope, name) = qualified_name_parts(node)?;
13618 if let Some(scope) = scope {
13619 append_structured_type_components(scope, source, out)?;
13620 }
13621 append_structured_type_components(name, source, out)
13622 }
13623 _ => None,
13624 }
13625}
13626
13627pub(crate) struct DeclaredNameBinding {
13630 pub pointer_depth: i32,
13632 pub indirect: bool,
13636}
13637
13638pub(crate) fn declared_name_binding(
13639 declaration: Node<'_>,
13640 type_node: Node<'_>,
13641 field_name: &str,
13642 source: &str,
13643) -> Option<DeclaredNameBinding> {
13644 let mut stack = Vec::new();
13645 let mut cursor = declaration.walk();
13646 stack.extend(
13647 declaration
13648 .named_children(&mut cursor)
13649 .filter(|child| !same_node(*child, type_node)),
13650 );
13651 while let Some(node) = stack.pop() {
13652 if matches!(node.kind(), "identifier" | "field_identifier")
13653 && node_text(node, source) == field_name
13654 {
13655 let mut binding = DeclaredNameBinding {
13656 pointer_depth: 0,
13657 indirect: false,
13658 };
13659 let mut current = node.parent();
13660 while let Some(parent) = current {
13661 if same_node(parent, declaration) {
13662 return Some(binding);
13663 }
13664 match parent.kind() {
13665 "pointer_declarator" => {
13666 binding.pointer_depth += 1;
13667 binding.indirect = true;
13668 }
13669 "reference_declarator" => binding.indirect = true,
13670 _ => {}
13671 }
13672 current = parent.parent();
13673 }
13674 return None;
13675 }
13676 let mut cursor = node.walk();
13677 stack.extend(node.named_children(&mut cursor));
13678 }
13679 None
13680}
13681
13682fn normalize_field_type_text(type_text: &str) -> String {
13683 const FIELD_SPECIFIERS: [&str; 8] = [
13684 "extern ",
13685 "static ",
13686 "mutable ",
13687 "constexpr ",
13688 "constinit ",
13689 "inline ",
13690 "volatile ",
13691 "const ",
13692 ];
13693
13694 let mut normalized = normalize_type_text(type_text);
13695 loop {
13696 let Some(stripped) = FIELD_SPECIFIERS
13697 .iter()
13698 .find_map(|specifier| normalized.strip_prefix(specifier))
13699 else {
13700 return normalized;
13701 };
13702 normalized = normalize_type_text(stripped);
13703 }
13704}
13705
13706pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13707 let Some(type_node) = node.child_by_field_name("type") else {
13708 return false;
13709 };
13710 ctx.visibility.resolves_to_type(
13711 &ctx.analyzer,
13712 ctx.file,
13713 node_text(type_node, ctx.source),
13714 owner,
13715 )
13716}
13717
13718pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13719 !ctx.analyzer
13720 .declarations(ctx.file)
13721 .into_iter()
13722 .filter(|unit| unit.is_function())
13723 .any(|unit| {
13724 ctx.analyzer.ranges(&unit).iter().any(|range| {
13725 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13726 })
13727 })
13728}
13729
13730pub enum DeclarationConstructorInitializer<'tree> {
13732 Arguments(Node<'tree>),
13735 Expression(Node<'tree>),
13738 Empty,
13740}
13741
13742pub fn declaration_constructor_initializer(
13743 node: Node<'_>,
13744) -> DeclarationConstructorInitializer<'_> {
13745 let mut cursor = node.walk();
13746 for child in node.named_children(&mut cursor) {
13747 if child.kind() == "init_declarator" {
13748 let Some(value) = child
13749 .child_by_field_name("value")
13750 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13751 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13752 else {
13753 return DeclarationConstructorInitializer::Empty;
13754 };
13755 return match value.kind() {
13756 "argument_list" | "initializer_list" => {
13757 DeclarationConstructorInitializer::Arguments(value)
13758 }
13759 "compound_literal_expression" => call_arguments_node(value)
13760 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13761 DeclarationConstructorInitializer::Arguments(arguments)
13762 }),
13763 _ => DeclarationConstructorInitializer::Expression(value),
13764 };
13765 }
13766 if let Some(declarator) = declaration_declarator(node, child) {
13767 return declarator_parameters(declarator)
13768 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13769 DeclarationConstructorInitializer::Arguments(parameters)
13770 });
13771 }
13772 }
13773 DeclarationConstructorInitializer::Empty
13774}
13775
13776pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13777 match declaration_constructor_initializer(node) {
13778 DeclarationConstructorInitializer::Arguments(arguments) => {
13779 argument_children(arguments).count()
13780 }
13781 DeclarationConstructorInitializer::Expression(_) => 1,
13782 DeclarationConstructorInitializer::Empty => 0,
13783 }
13784}
13785
13786fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13790 let mut current = node;
13791 loop {
13792 if let Some(parameters) = current.child_by_field_name("parameters") {
13793 return Some(parameters);
13794 }
13795 current = current.child_by_field_name("declarator")?;
13796 }
13797}
13798
13799fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13800 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13801}
13802
13803fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13804 if node.kind() == "identifier" {
13805 return true;
13806 }
13807 if node.kind() == "parenthesized_expression" {
13808 return false;
13809 }
13810 if node.kind() == "call_expression" {
13811 return node
13812 .child_by_field_name("function")
13813 .is_some_and(|function| function.kind() == "identifier");
13814 }
13815 let mut stack = vec![node];
13816 while let Some(descendant) = stack.pop() {
13817 if descendant != node && descendant.kind() == "parenthesized_expression" {
13818 continue;
13819 }
13820 if descendant.kind() == "identifier" {
13821 return true;
13822 }
13823 if descendant.kind() == "call_expression" {
13824 if descendant
13825 .child_by_field_name("function")
13826 .is_some_and(|function| function.kind() == "identifier")
13827 {
13828 return true;
13829 }
13830 continue;
13831 }
13832 push_named_children_reversed(descendant, &mut stack);
13833 }
13834 false
13835}
13836
13837fn macro_expansion_shape_is_safe(
13838 node: Node<'_>,
13839 source: &str,
13840 parameters: &[String],
13841 environment: &MacroEnvironment,
13842) -> bool {
13843 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13844 return true;
13845 }
13846 if node.kind() == "call_expression" {
13847 let Some(function) = node.child_by_field_name("function") else {
13848 return true;
13849 };
13850 if function.kind() != "identifier" {
13851 return true;
13852 }
13853 let function_name = node_text(function, source);
13854 if parameters
13855 .iter()
13856 .any(|parameter| parameter == function_name)
13857 {
13858 return false;
13859 }
13860 if !environment.may_bind(function_name) {
13861 return true;
13862 }
13863 let Some(arguments) = node.child_by_field_name("arguments") else {
13864 return false;
13865 };
13866 return argument_children(arguments).all(|argument| {
13867 if argument.kind() == "identifier"
13868 && parameters
13869 .iter()
13870 .any(|parameter| parameter == node_text(argument, source))
13871 {
13872 return false;
13873 }
13874 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13875 });
13876 }
13877 let mut stack = vec![node];
13878 while let Some(descendant) = stack.pop() {
13879 if descendant != node {
13880 if descendant.kind() == "parenthesized_expression" {
13881 continue;
13882 }
13883 if descendant.kind() == "call_expression" {
13884 let expands = descendant
13885 .child_by_field_name("function")
13886 .filter(|function| function.kind() == "identifier")
13887 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13888 if expands {
13889 return false;
13890 }
13891 continue;
13892 }
13893 }
13894 if descendant.kind() == "identifier" {
13895 let identifier = node_text(descendant, source);
13896 if parameters.iter().any(|parameter| parameter == identifier)
13897 || environment.may_bind(identifier)
13898 {
13899 return false;
13900 }
13901 }
13902 push_named_children_reversed(descendant, &mut stack);
13903 }
13904 true
13905}
13906
13907fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13908 let source = prepared.source();
13909 let mut facts = Vec::new();
13910 let mut nodes = vec![prepared.tree().root_node()];
13911 while let Some(node) = nodes.pop() {
13912 if node.kind() == "preproc_include" {
13913 let Some(path) = node
13914 .child_by_field_name("path")
13915 .and_then(|path| crate::imports::include_path_literal_value(path, source))
13916 .map(str::to_owned)
13917 else {
13918 continue;
13919 };
13920 facts.push(StructuredIncludeFact {
13921 start_byte: node.start_byte(),
13922 end_byte: node.end_byte(),
13923 path,
13924 });
13925 continue;
13926 }
13927 push_named_children_reversed(node, &mut nodes);
13928 }
13929 Arc::from(facts.into_boxed_slice())
13930}
13931
13932fn has_unresolved_include_visible_before_in_prepared(
13933 file: &ProjectFile,
13934 prepared: &PreparedSyntaxTree,
13935 include_targets: &IncludeTargetIndex,
13936 facts: &[StructuredIncludeFact],
13937 before_byte: usize,
13938) -> bool {
13939 let guards = OnceCell::new();
13940 let reference = CallableReferenceContext {
13941 file,
13942 position: Some(CallableReferencePosition {
13943 prepared,
13944 byte: before_byte,
13945 guards: &guards,
13946 }),
13947 };
13948 let root = prepared.tree().root_node();
13949 facts
13950 .iter()
13951 .filter(|fact| fact.end_byte <= before_byte)
13952 .any(|fact| {
13953 let node = root
13954 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13955 .expect("structured include fact range must be in prepared tree");
13956 assert_eq!(
13957 node.kind(),
13958 "preproc_include",
13959 "structured include fact range must identify its include node"
13960 );
13961 callable_preprocessor_context_is_visible_for_reference(
13962 node,
13963 prepared.source(),
13964 &reference,
13965 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13966 })
13967}
13968
13969fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13970 let descendant = node;
13971 while let Some(parent) = node.parent() {
13972 if is_cpp_preprocessor_conditional(parent)
13973 && !is_file_covering_include_guard(parent, source)
13974 && preprocessor_conditional_contains_descendant(parent, descendant)
13975 {
13976 return true;
13977 }
13978 node = parent;
13979 }
13980 false
13981}
13982
13983fn owning_preprocessor_conditionals(
13995 root: Node<'_>,
13996 event: Node<'_>,
13997 source: &str,
13998) -> OwningPreprocessorConditionals {
13999 if !has_preprocessor_conditional_ancestor(event, source) {
14000 return OwningPreprocessorConditionals::default();
14001 }
14002 let start = event.start_byte();
14003 let descendant = root
14004 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14005 .expect("a byte inside the parsed tree names a descendant");
14006 let mut owners = Vec::new();
14007 let mut current = descendant.parent();
14008 while let Some(conditional) = current {
14009 if is_cpp_preprocessor_conditional(conditional)
14010 && !is_file_covering_include_guard(conditional, source)
14011 && preprocessor_conditional_contains_descendant(conditional, descendant)
14012 {
14013 owners.push(conditional.start_byte());
14014 }
14015 current = conditional.parent();
14016 }
14017 owners.into_boxed_slice()
14018}
14019
14020fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14021 node.parent()
14022 .filter(|parent| parent.kind() == "translation_unit")
14023 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14024 && is_canonical_include_guard(node, source)
14025}
14026
14027fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14028 if node.kind() != "preproc_ifdef"
14029 || node
14030 .child(0)
14031 .is_none_or(|directive| directive.kind() != "#ifndef")
14032 || node.child_by_field_name("alternative").is_some()
14033 {
14034 return false;
14035 }
14036 let Some(guard_name) = node.child_by_field_name("name") else {
14037 return false;
14038 };
14039 let mut cursor = node.walk();
14040 node.named_children(&mut cursor)
14041 .find(|child| *child != guard_name && child.kind() != "comment")
14042 .filter(|child| child.kind() == "preproc_def")
14043 .and_then(|definition| definition.child_by_field_name("name"))
14044 .is_some_and(|defined_name| {
14045 node_text(defined_name, source) == node_text(guard_name, source)
14046 })
14047}
14048
14049fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14050 let mut guard = None;
14051 for child in named_children_iter(root) {
14052 if child.kind() == "comment" || is_pragma_once(child, source) {
14053 continue;
14054 }
14055 if guard.is_none() && is_canonical_include_guard(child, source) {
14056 guard = Some(child);
14057 } else {
14058 return None;
14059 }
14060 }
14061 guard
14062 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14063 .map(|name| node_text(name, source).to_string())
14064}
14065
14066fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14067 if (0..root.named_child_count())
14068 .filter_map(|index| root.named_child(index))
14069 .any(|child| is_pragma_once(child, source))
14070 {
14071 return MacroIncludeProtection::PragmaOnce;
14072 }
14073 top_level_canonical_include_guard_name(root, source)
14074 .map(MacroIncludeProtection::MacroGuard)
14075 .unwrap_or(MacroIncludeProtection::None)
14076}
14077
14078fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14079 node.kind() == "preproc_call"
14080 && node
14081 .child_by_field_name("directive")
14082 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14083 && node
14084 .child_by_field_name("argument")
14085 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14086}
14087
14088fn parse_preproc_identifier(argument: &str) -> Option<String> {
14089 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14090 let mut parser = Parser::new();
14091 parser
14092 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14093 .ok()?;
14094 let tree = parser.parse(&sentinel, None)?;
14095 if tree.root_node().has_error() {
14096 return None;
14097 }
14098 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14099 let identifier = statement.named_child(0)?;
14100 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14101 .then(|| node_text(identifier, &sentinel).to_string())
14102}
14103
14104pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14105 match node.kind() {
14106 "identifier" | "field_identifier" => {
14107 let name = node_text(node, source).trim();
14108 (!name.is_empty()).then(|| name.to_string())
14109 }
14110 "abstract_array_declarator"
14111 | "abstract_function_declarator"
14112 | "abstract_parenthesized_declarator"
14113 | "abstract_pointer_declarator"
14114 | "abstract_reference_declarator" => None,
14115 "function_declarator" => node
14116 .child_by_field_name("declarator")
14117 .or_else(|| node.child_by_field_name("name"))
14118 .and_then(|child| extract_variable_name(child, source)),
14119 _ => node
14120 .child_by_field_name("declarator")
14121 .or_else(|| node.child_by_field_name("name"))
14122 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14123 .and_then(|child| extract_variable_name(child, source)),
14124 }
14125}
14126
14127pub fn is_c_source_file(file: &ProjectFile) -> bool {
14138 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14139}
14140
14141pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14148 if !is_c_source_file(file) || node.kind() != "identifier" {
14149 return false;
14150 }
14151 let mut operand = node;
14152 while let Some(parent) = operand.parent().filter(|parent| {
14153 parent.kind() == "parenthesized_expression"
14154 && parent.named_child_count() == 1
14155 && parent.named_child(0) == Some(operand)
14156 }) {
14157 operand = parent;
14158 }
14159 operand.parent().is_some_and(|parent| {
14160 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14161 })
14162}
14163
14164pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14173 if !is_c_offsetof_member_node(node) {
14174 return None;
14175 }
14176 let expression = node.parent()?;
14177 if expression.has_error() {
14178 return None;
14179 }
14180 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14181 if closing.kind() != ")" || closing.is_missing() {
14182 return None;
14183 }
14184 let type_descriptor = expression.child_by_field_name("type")?;
14185 if type_descriptor.kind() != "type_descriptor"
14186 || type_descriptor.is_missing()
14187 || type_descriptor.has_error()
14188 {
14189 return None;
14190 }
14191 let type_specifier = type_descriptor.child_by_field_name("type")?;
14192 if type_specifier.is_missing() || type_specifier.has_error() {
14193 return None;
14194 }
14195 let type_reference = match type_specifier.kind() {
14196 "class_specifier" | "struct_specifier" | "union_specifier" => {
14197 type_specifier.child_by_field_name("name")?
14198 }
14199 _ => type_specifier,
14200 };
14201 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14202}
14203
14204pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14209 node.kind() == "field_identifier"
14210 && node.parent().is_some_and(|parent| {
14211 parent.kind() == "offsetof_expression"
14212 && parent.child_by_field_name("member") == Some(node)
14213 })
14214}
14215
14216pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14220 if node.kind() != "template_function" {
14221 return false;
14222 }
14223 let Some(arguments) = node
14224 .parent()
14225 .filter(|parent| parent.kind() == "template_argument_list")
14226 else {
14227 return false;
14228 };
14229 let Some(owner) = arguments.parent().filter(|parent| {
14230 parent.kind() == "template_type"
14231 && parent.child_by_field_name("arguments") == Some(arguments)
14232 }) else {
14233 return false;
14234 };
14235
14236 let mut current = owner;
14237 while let Some(parent) = current.parent() {
14238 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14239 {
14240 return true;
14241 }
14242 if parent.kind() == "qualified_identifier"
14243 && parent.child_by_field_name("name") == Some(current)
14244 {
14245 current = parent;
14246 continue;
14247 }
14248 return false;
14249 }
14250 false
14251}
14252
14253pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14269 if node.kind() != "type_identifier" {
14270 return false;
14271 }
14272 let Some(descriptor) = node
14273 .parent()
14274 .filter(|parent| parent.kind() == "type_descriptor")
14275 else {
14276 return false;
14277 };
14278 if descriptor.child_by_field_name("type") != Some(node) {
14279 return false;
14280 }
14281 let Some(arguments) = descriptor
14282 .parent()
14283 .filter(|parent| parent.kind() == "template_argument_list")
14284 else {
14285 return false;
14286 };
14287 arguments.parent().is_some_and(|owner| {
14288 matches!(
14289 owner.kind(),
14290 "template_type" | "template_function" | "template_method"
14291 ) && owner.child_by_field_name("arguments") == Some(arguments)
14292 })
14293}
14294
14295pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14308 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14309}
14310
14311pub fn is_declarator_node(node: Node<'_>) -> bool {
14312 matches!(
14313 node.kind(),
14314 "identifier"
14315 | "field_identifier"
14316 | "qualified_identifier"
14317 | "scoped_identifier"
14318 | "pointer_declarator"
14319 | "reference_declarator"
14320 | "array_declarator"
14321 | "parenthesized_declarator"
14322 | "function_declarator"
14323 )
14324}
14325
14326pub fn declaration_declarator<'tree>(
14335 declaration: Node<'tree>,
14336 child: Node<'tree>,
14337) -> Option<Node<'tree>> {
14338 if !matches!(
14339 declaration.kind(),
14340 "declaration"
14341 | "field_declaration"
14342 | "parameter_declaration"
14343 | "optional_parameter_declaration"
14344 | "function_definition"
14345 | "type_definition"
14346 | "alias_declaration"
14347 | "template_instantiation"
14348 ) {
14349 return None;
14350 }
14351 if declaration
14352 .child_by_field_name("type")
14353 .is_some_and(|type_node| same_node(type_node, child))
14354 {
14355 return None;
14356 }
14357 if child.kind() == "init_declarator" {
14358 return child.child_by_field_name("declarator");
14359 }
14360 let field = field_name_in_parent(declaration, child);
14361 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14362 || (declaration.kind() == "type_definition"
14363 && child.kind() == "type_identifier"
14364 && matches!(field, Some("declarator") | None))
14365 {
14366 Some(child)
14367 } else {
14368 None
14369 }
14370}
14371
14372pub fn cpp_binding_parts<'tree>(
14374 node: Node<'tree>,
14375 source: &str,
14376) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14377 let declarator = node.child_by_field_name("declarator")?;
14378 let name = extract_variable_name(declarator, source)?;
14379 let type_node = node
14380 .child_by_field_name("type")
14381 .or_else(|| first_type_child(node));
14382 Some((name, declarator, type_node))
14383}
14384
14385#[derive(Clone, Debug, PartialEq, Eq)]
14388pub struct RecoveredNamespaceRegion {
14389 pub start: usize,
14391 pub end: usize,
14393 pub components: Vec<String>,
14396 pub component_ranges: Vec<Range>,
14407 pub component_name_ranges: Vec<Range>,
14413 pub component_closes: Vec<Option<Range>>,
14419}
14420
14421#[derive(Clone, Debug, Default)]
14486pub struct OrphanedNamespaceScopeIndex {
14487 regions: Vec<RecoveredNamespaceRegion>,
14488 brace_closes: HashMap<usize, Range>,
14491}
14492
14493impl OrphanedNamespaceScopeIndex {
14494 pub fn build(root: Node<'_>, source: &str) -> Self {
14495 if !root.has_error() {
14496 return Self::default();
14497 }
14498 #[derive(Clone, Copy)]
14504 struct OpenHead {
14505 head: Range,
14506 open: usize,
14507 }
14508 struct PendingRegion {
14511 start: usize,
14512 end: usize,
14513 components: Vec<String>,
14514 heads: Vec<OpenHead>,
14515 name_ranges: Vec<Range>,
14516 }
14517 struct Frame<'tree> {
14518 node: Node<'tree>,
14519 children: Vec<Node<'tree>>,
14520 next: usize,
14521 parsed_scope: Vec<String>,
14522 run: Option<PendingRegion>,
14523 }
14524 fn frame<'tree>(
14525 node: Node<'tree>,
14526 mut parsed_scope: Vec<String>,
14527 source: &str,
14528 ) -> Frame<'tree> {
14529 if node.kind() == "namespace_definition"
14530 && let Some(name) = node.child_by_field_name("name")
14531 {
14532 let mut components = Vec::new();
14533 if append_cpp_name_components(name, source, &mut components).is_some() {
14534 parsed_scope.extend(components);
14535 }
14536 }
14537 let mut cursor = node.walk();
14538 Frame {
14539 node,
14540 children: node.children(&mut cursor).collect(),
14541 next: 0,
14542 parsed_scope,
14543 run: None,
14544 }
14545 }
14546 let mut regions = Vec::new();
14547 let mut brace_closes = HashMap::default();
14548 let mut namespace_brace_proofs = Vec::new();
14555 let mut unpaired_closes: Vec<Range> = Vec::new();
14561 let mut open = Vec::new();
14565 let mut lexical_scope = Vec::new();
14566 let mut lexical_heads: Vec<OpenHead> = Vec::new();
14571 let mut lexical_name_ranges: Vec<Range> = Vec::new();
14574 let mut frames = vec![frame(root, Vec::new(), source)];
14575 while !frames.is_empty() {
14580 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14581 let current = frames.last_mut().expect("frames is non-empty");
14582 if current.next == current.children.len() {
14583 regions.extend(frames.pop().expect("the frame just borrowed").run);
14584 continue;
14585 }
14586 let child = current.children[current.next];
14587 current.next += 1;
14588 match child.kind() {
14589 "{" if !child.is_missing() => {
14590 regions.extend(current.run.take());
14591 if let Some(namespace) = parent_node.filter(|parent| {
14592 parent.kind() == "namespace_definition"
14593 && parent.child_by_field_name("body") == Some(current.node)
14594 && child.start_byte() == current.node.start_byte()
14595 }) && let Some(proof) = namespace_body_brace_proof(current.node)
14596 {
14597 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14598 }
14599 let mut named = parent_node.and_then(|parent| {
14604 namespace_body_name_components(parent, current.node, source)
14605 });
14606 if named.is_none() {
14607 named = recovered_namespace_open_components(
14608 ¤t.children[..current.next - 1],
14609 source,
14610 );
14611 }
14612 open.push((child.start_byte(), lexical_scope.len()));
14613 if let Some((written_at, components)) = named {
14614 let head = OpenHead {
14615 head: Range {
14616 start_byte: written_at.start_byte(),
14617 end_byte: child.end_byte(),
14618 start_line: written_at.start_position().row + 1,
14619 end_line: child.end_position().row + 1,
14620 },
14621 open: child.start_byte(),
14622 };
14623 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14624 lexical_name_ranges
14625 .extend(components.iter().map(|(_, name_range)| *name_range));
14626 lexical_scope
14627 .extend(components.into_iter().map(|(component, _)| component));
14628 }
14629 continue;
14630 }
14631 "}" if !child.is_missing() => {
14632 regions.extend(current.run.take());
14633 let close = Range {
14634 start_byte: child.start_byte(),
14635 end_byte: child.end_byte(),
14636 start_line: child.start_position().row + 1,
14637 end_line: child.end_position().row + 1,
14638 };
14639 match open.pop() {
14640 Some((start, namespace_len)) => {
14641 debug_assert!(
14642 start < close.start_byte,
14643 "a close pairs with an open before it: {start} then {close:?}"
14644 );
14645 lexical_scope.truncate(namespace_len);
14646 lexical_heads.truncate(namespace_len);
14647 lexical_name_ranges.truncate(namespace_len);
14648 brace_closes.insert(start, close);
14649 }
14650 None => unpaired_closes.push(close),
14651 }
14652 continue;
14653 }
14654 _ => {}
14655 }
14656 if current.node.kind() != "namespace_definition"
14660 && lexical_scope != current.parsed_scope
14661 {
14662 match &mut current.run {
14663 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14664 run => {
14665 regions.extend(run.take());
14666 *run = Some(PendingRegion {
14667 start: child.start_byte(),
14668 end: child.end_byte(),
14669 components: lexical_scope.clone(),
14670 heads: lexical_heads.clone(),
14671 name_ranges: lexical_name_ranges.clone(),
14672 });
14673 }
14674 }
14675 } else {
14676 regions.extend(current.run.take());
14677 }
14678 if child.has_error() {
14682 let parsed_scope = current.parsed_scope.clone();
14683 frames.push(frame(child, parsed_scope, source));
14684 }
14685 }
14686 if !unpaired_closes.is_empty() {
14695 brace_closes.clear();
14696 } else {
14697 let missing_namespaces = namespace_brace_proofs
14703 .iter()
14704 .filter_map(|(_, namespace, proof)| {
14705 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14706 })
14707 .collect::<HashSet<_>>();
14708 for (open, namespace, proof) in namespace_brace_proofs {
14709 if !std::iter::successors(Some(namespace), |node| node.parent())
14710 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14711 {
14712 continue;
14713 }
14714 match proof {
14715 NamespaceBodyBraceProof::Real(close) => {
14716 brace_closes.insert(open, close);
14717 }
14718 NamespaceBodyBraceProof::Missing => {
14719 brace_closes.remove(&open);
14720 }
14721 }
14722 }
14723 }
14724 let regions = regions
14729 .into_iter()
14730 .map(|region| {
14731 debug_assert_eq!(
14732 region.components.len(),
14733 region.heads.len(),
14734 "every recovered namespace level carries the head that writes it: {:?}",
14735 region.components
14736 );
14737 debug_assert_eq!(
14738 region.components.len(),
14739 region.name_ranges.len(),
14740 "every recovered namespace level carries the name token that writes it: {:?}",
14741 region.components
14742 );
14743 let component_closes: Vec<_> = region
14744 .heads
14745 .iter()
14746 .map(|level| brace_closes.get(&level.open).copied())
14747 .collect();
14748 let component_ranges = region
14749 .heads
14750 .iter()
14751 .zip(&component_closes)
14752 .map(|(level, close)| match close {
14753 Some(close) => Range {
14754 start_byte: level.head.start_byte,
14755 end_byte: close.end_byte,
14756 start_line: level.head.start_line,
14757 end_line: close.end_line,
14758 },
14759 None => level.head,
14760 })
14761 .collect();
14762 RecoveredNamespaceRegion {
14763 start: region.start,
14764 end: region.end,
14765 components: region.components,
14766 component_ranges,
14767 component_name_ranges: region.name_ranges,
14768 component_closes,
14769 }
14770 })
14771 .collect();
14772 Self {
14773 regions,
14774 brace_closes,
14775 }
14776 }
14777
14778 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14785 self.brace_closes.get(&open).copied()
14786 }
14787
14788 pub fn is_empty(&self) -> bool {
14789 self.regions.is_empty()
14790 }
14791
14792 pub fn approximate_size(&self) -> usize {
14794 self.regions.iter().fold(
14795 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14796 |total, region| {
14797 total
14798 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14799 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14800 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14801 .saturating_add(
14802 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14803 )
14804 .saturating_add(
14805 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14806 )
14807 },
14808 )
14809 }
14810
14811 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14813 self.regions
14814 .iter()
14815 .filter(|region| region.start <= byte && byte < region.end)
14816 .min_by_key(|region| region.end - region.start)
14817 }
14818
14819 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14823 self.enclosing_namespace_levels(node, source)
14824 .into_iter()
14825 .map(|(component, ..)| component)
14826 .collect()
14827 }
14828
14829 pub fn enclosing_namespace_levels(
14838 &self,
14839 node: Node<'_>,
14840 source: &str,
14841 ) -> Vec<(String, Range, Range)> {
14842 let mut parsed = Vec::new();
14843 let mut current = node.parent();
14844 while let Some(parent) = current {
14845 if parent.kind() == "namespace_definition"
14846 && let Some(name) = parent.child_by_field_name("name")
14847 && let Some(components) = cpp_name_component_ranges(name, source)
14848 {
14849 let range = node_range(parent);
14850 parsed.push((
14851 parent.start_byte(),
14852 components
14853 .into_iter()
14854 .map(|(component, name_range)| (component, range, name_range))
14855 .collect(),
14856 namespace_definition_has_real_braces(parent),
14857 ));
14858 }
14859 current = parent.parent();
14860 }
14861 parsed.reverse();
14862 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14863 region
14864 .components
14865 .iter()
14866 .take(active_components)
14867 .cloned()
14868 .zip(
14869 region
14870 .component_ranges
14871 .iter()
14872 .take(active_components)
14873 .copied(),
14874 )
14875 .zip(
14876 region
14877 .component_name_ranges
14878 .iter()
14879 .take(active_components)
14880 .copied(),
14881 )
14882 .map(|((component, range), name_range)| (component, range, name_range))
14883 .collect()
14884 })
14885 }
14886
14887 pub fn restore_enclosing_namespaces(
14893 &self,
14894 parsed: Vec<(usize, Vec<String>, bool)>,
14895 node_start: usize,
14896 ) -> Vec<String> {
14897 self.splice_recovered_path(parsed, node_start, |region, active_components| {
14898 region
14899 .components
14900 .iter()
14901 .take(active_components)
14902 .cloned()
14903 .collect()
14904 })
14905 }
14906
14907 fn splice_recovered_path<T>(
14913 &self,
14914 parsed: Vec<(usize, Vec<T>, bool)>,
14915 node_start: usize,
14916 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14917 ) -> Vec<T> {
14918 let Some(region) = self.region_at(node_start) else {
14919 return parsed
14920 .into_iter()
14921 .flat_map(|(_, levels, _)| levels)
14922 .collect();
14923 };
14924 debug_assert_eq!(
14925 region.components.len(),
14926 region.component_closes.len(),
14927 "every recovered namespace component carries its close proof: {region:?}"
14928 );
14929 debug_assert_eq!(
14930 region.components.len(),
14931 region.component_name_ranges.len(),
14932 "every recovered namespace component carries its name token: {region:?}"
14933 );
14934 if region.components.is_empty() {
14935 return parsed
14936 .into_iter()
14937 .filter(|(_, _, complete)| *complete)
14938 .flat_map(|(_, levels, _)| levels)
14939 .collect();
14940 }
14941 let active_components = region
14942 .component_closes
14943 .iter()
14944 .take_while(|close| {
14945 close
14946 .as_ref()
14947 .is_none_or(|close| node_start < close.end_byte)
14948 })
14949 .count();
14950 recovered(region, active_components)
14951 .into_iter()
14952 .chain(
14953 parsed
14954 .into_iter()
14955 .filter(|(start, _, _)| *start >= region.start)
14956 .flat_map(|(_, levels, _)| levels),
14957 )
14958 .collect()
14959 }
14960}
14961
14962pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14966 debug_assert_eq!(namespace.kind(), "namespace_definition");
14967 let Some(body) = namespace.child_by_field_name("body") else {
14968 return false;
14969 };
14970 let mut opening = false;
14971 let mut closing = false;
14972 let mut cursor = body.walk();
14973 for child in body.children(&mut cursor) {
14974 match child.kind() {
14975 "{" if !child.is_missing() => opening = true,
14976 "}" if !child.is_missing() => closing = true,
14977 _ => {}
14978 }
14979 }
14980 opening && closing
14981}
14982
14983fn recovered_namespace_open_components<'tree>(
14999 preceding: &[Node<'tree>],
15000 source: &str,
15001) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15002 let mut head = Vec::new();
15003 for &sibling in preceding.iter().rev() {
15004 if sibling.kind() != "comment" {
15005 head.push(sibling);
15006 if head.len() == 2 {
15007 break;
15008 }
15009 }
15010 }
15011 let [name, keyword] = head[..] else {
15012 return None;
15013 };
15014 if keyword.kind() != "namespace" {
15015 return None;
15016 }
15017 let components = cpp_name_component_ranges(name, source)?;
15018 (!components.is_empty()).then_some((keyword, components))
15019}
15020
15021fn namespace_body_name_components<'tree>(
15025 parent: Node<'tree>,
15026 body: Node<'tree>,
15027 source: &str,
15028) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15029 if body.kind() != "declaration_list"
15030 || parent.kind() != "namespace_definition"
15031 || parent.child_by_field_name("body") != Some(body)
15032 {
15033 return None;
15034 }
15035 let name = parent.child_by_field_name("name")?;
15036 let components = cpp_name_component_ranges(name, source)?;
15037 (!components.is_empty()).then_some((parent, components))
15038}
15039
15040#[derive(Clone, Copy)]
15041enum NamespaceBodyBraceProof {
15042 Real(Range),
15043 Missing,
15044}
15045
15046fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15052 if body.kind() != "declaration_list" {
15053 return None;
15054 }
15055 let mut cursor = body.walk();
15056 let children = body.children(&mut cursor).collect::<Vec<_>>();
15057 let close_index = children
15058 .iter()
15059 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15060 let close = children[close_index];
15061 if close.is_missing() {
15062 return Some(NamespaceBodyBraceProof::Missing);
15063 }
15064 let namespace_terminator_sibling = body
15065 .parent()
15066 .filter(|namespace| namespace.kind() == "namespace_definition")
15067 .and_then(|namespace| namespace.next_sibling())
15068 .is_some_and(|sibling| {
15069 sibling.kind() == ";"
15070 || (sibling.kind() == "expression_statement"
15071 && sibling.child_count() == 1
15072 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15073 });
15074 if namespace_terminator_sibling
15079 || children
15080 .get(close_index + 1)
15081 .is_some_and(|sibling| sibling.kind() == ";")
15082 || children[..close_index]
15083 .iter()
15084 .rev()
15085 .find(|child| !child.is_extra() && child.kind() != "{")
15086 .is_some_and(|previous| {
15087 matches!(
15088 previous.kind(),
15089 "expression_statement" | "labeled_statement"
15090 )
15091 })
15092 {
15093 return None;
15094 }
15095 Some(NamespaceBodyBraceProof::Real(node_range(close)))
15096}
15097
15098#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15099pub enum RecoveredDeclaratorTypeContext {
15100 Declaration,
15101 FunctionDefinition,
15102 Parameter,
15103}
15104
15105pub fn recovered_macro_decorated_declarator_type(
15120 node: Node<'_>,
15121) -> Option<RecoveredDeclaratorTypeContext> {
15122 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15123}
15124
15125pub fn recovered_macro_decorated_type_node(
15130 node: Node<'_>,
15131) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15132 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15133 return None;
15134 }
15135 let qualified = node.parent()?;
15136 if qualified.kind() != "qualified_identifier"
15137 || qualified.child_by_field_name("scope") != Some(node)
15138 || !(0..qualified.child_count())
15139 .filter_map(|index| qualified.child(index))
15140 .any(|child| child.kind() == "::" && child.is_missing())
15141 {
15142 return None;
15143 }
15144 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15145 return None;
15146 }
15147
15148 let (declaration, context) = recovered_declarator_container(qualified)?;
15149 let type_node = declaration
15150 .child_by_field_name("type")
15151 .filter(|type_node| {
15152 *type_node != qualified
15153 && !type_node.is_missing()
15154 && type_node.start_byte() != type_node.end_byte()
15155 })?;
15156 Some((type_node, context))
15157}
15158
15159fn recovered_declarator_container(
15160 mut declarator: Node<'_>,
15161) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15162 loop {
15163 let parent = declarator.parent()?;
15164 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15165 return Some((
15166 parent
15167 .parent()
15168 .filter(|declaration| declaration.kind() == "declaration")?,
15169 RecoveredDeclaratorTypeContext::Declaration,
15170 ));
15171 }
15172 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15173 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15174 }
15175 if parent.kind() == "function_definition"
15176 && has_field_child(parent, "declarator", declarator)
15177 {
15178 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15179 }
15180 if matches!(
15186 parent.kind(),
15187 "parameter_declaration" | "optional_parameter_declaration"
15188 ) && has_field_child(parent, "declarator", declarator)
15189 {
15190 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15191 }
15192 if !matches!(
15193 parent.kind(),
15194 "array_declarator"
15195 | "function_declarator"
15196 | "parenthesized_declarator"
15197 | "pointer_declarator"
15198 | "pointer_type_declarator"
15199 | "reference_declarator"
15200 ) || !has_field_child(parent, "declarator", declarator)
15201 {
15202 return None;
15203 }
15204 declarator = parent;
15205 }
15206}
15207
15208fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15209 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15210}
15211
15212fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15213 loop {
15214 if node.is_missing() || node.start_byte() == node.end_byte() {
15215 return false;
15216 }
15217 match node.kind() {
15218 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15219 return true;
15220 }
15221 "array_declarator"
15222 | "function_declarator"
15223 | "parenthesized_declarator"
15224 | "pointer_declarator"
15225 | "pointer_type_declarator"
15226 | "reference_declarator" => {
15227 let Some(declarator) = node.child_by_field_name("declarator") else {
15228 return false;
15229 };
15230 node = declarator;
15231 }
15232 _ => return false,
15233 }
15234 }
15235}
15236
15237pub enum DesignatedInitializerOwner {
15239 Resolved(CodeUnit),
15240 Unresolved,
15241}
15242
15243enum InitializerOwnerStep {
15244 Field(String),
15245 AggregateWrapper,
15246}
15247
15248pub fn designated_initializer_owner(
15258 analyzer: &CppGraphSource<'_>,
15259 visibility: &VisibilityIndex<'_>,
15260 file: &ProjectFile,
15261 source: &str,
15262 node: Node<'_>,
15263) -> Option<DesignatedInitializerOwner> {
15264 if let Some(designator) = node
15265 .parent()
15266 .filter(|parent| parent.kind() == "field_designator")
15267 {
15268 let pair = designator.parent()?;
15269 if pair.kind() != "initializer_pair" {
15270 return None;
15271 }
15272 let mut cursor = pair.walk();
15273 let designators = pair
15274 .children_by_field_name("designator", &mut cursor)
15275 .collect::<Vec<_>>();
15276 let position = designators
15277 .iter()
15278 .position(|candidate| same_node(*candidate, designator))?;
15279 let initializer = pair.parent()?;
15280 if initializer.kind() != "initializer_list" {
15281 return None;
15282 }
15283 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15284 for prior in &designators[..position] {
15285 let field = prior
15286 .child_by_field_name("field")
15287 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15288 owner = owner.and_then(|owner| {
15289 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15290 });
15291 }
15292 return Some(classified_designated_owner(owner));
15293 }
15294
15295 let init_declarator = node.parent()?;
15296 if init_declarator.child_by_field_name("declarator") != Some(node)
15297 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15298 {
15299 return None;
15300 }
15301 Some(classified_designated_owner(declaration_owner(
15302 analyzer,
15303 visibility,
15304 file,
15305 source,
15306 init_declarator.parent()?,
15307 )))
15308}
15309
15310fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15311 owner.map_or(
15312 DesignatedInitializerOwner::Unresolved,
15313 DesignatedInitializerOwner::Resolved,
15314 )
15315}
15316
15317fn initializer_list_owner(
15318 analyzer: &CppGraphSource<'_>,
15319 visibility: &VisibilityIndex<'_>,
15320 file: &ProjectFile,
15321 source: &str,
15322 initializer: Node<'_>,
15323) -> Option<CodeUnit> {
15324 let mut current = initializer;
15325 let mut steps = Vec::new();
15326 loop {
15327 let parent = current.parent()?;
15328 match parent.kind() {
15329 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15330 let designator = parent.child_by_field_name("designator")?;
15331 let step = designator
15332 .child_by_field_name("field")
15333 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15334 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15335 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15336 steps.push(step);
15337 current = parent.parent()?;
15338 }
15339 "initializer_list" => {
15340 current = parent;
15341 }
15342 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15343 let declaration = parent.parent()?;
15344 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15345 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15346 }
15347 "compound_literal_expression"
15348 if parent.child_by_field_name("value") == Some(current) =>
15349 {
15350 let type_node = parent.child_by_field_name("type")?;
15351 let owner =
15352 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15353 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15354 }
15355 "ERROR" => current = parent,
15356 _ => return None,
15357 }
15358 }
15359}
15360
15361fn apply_initializer_owner_steps(
15362 analyzer: &CppGraphSource<'_>,
15363 visibility: &VisibilityIndex<'_>,
15364 file: &ProjectFile,
15365 mut owner: CodeUnit,
15366 steps: Vec<InitializerOwnerStep>,
15367) -> Option<CodeUnit> {
15368 for step in steps.into_iter().rev() {
15369 if let InitializerOwnerStep::Field(field_name) = step {
15370 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15371 }
15372 }
15373 Some(owner)
15374}
15375
15376fn initializer_field_owner(
15377 analyzer: &CppGraphSource<'_>,
15378 visibility: &VisibilityIndex<'_>,
15379 file: &ProjectFile,
15380 owner: CodeUnit,
15381 field_name: &str,
15382) -> Option<CodeUnit> {
15383 let fields = visibility
15384 .visible_members_for_owner_name(file, &owner, field_name)
15385 .into_iter()
15386 .filter(|field| field.is_field())
15387 .collect::<Vec<_>>();
15388 let field = match fields.as_slice() {
15389 [field] => *field,
15390 _ => return None,
15391 };
15392 field_declared_binding(analyzer, visibility, file, field)?.unit
15393}
15394
15395fn declaration_owner(
15396 analyzer: &CppGraphSource<'_>,
15397 visibility: &VisibilityIndex<'_>,
15398 file: &ProjectFile,
15399 source: &str,
15400 declaration: Node<'_>,
15401) -> Option<CodeUnit> {
15402 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15403 return None;
15404 }
15405 let type_node = declaration
15406 .child_by_field_name("type")
15407 .or_else(|| first_type_child(declaration))?;
15408 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15409}
15410
15411fn resolve_designated_owner_type(
15412 analyzer: &CppGraphSource<'_>,
15413 visibility: &VisibilityIndex<'_>,
15414 file: &ProjectFile,
15415 source: &str,
15416 type_node: Node<'_>,
15417) -> Option<CodeUnit> {
15418 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15419 return Some(owner);
15420 }
15421 let type_name = normalize_type_text(node_text(type_node, source));
15422 visibility
15423 .resolve_type(file, &type_name)
15424 .filter(CodeUnit::is_class)
15425}
15426
15427pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15428 let mut cursor = node.walk();
15429 node.named_children(&mut cursor).find(|child| {
15430 matches!(
15431 child.kind(),
15432 "type_identifier"
15433 | "primitive_type"
15434 | "qualified_identifier"
15435 | "scoped_type_identifier"
15436 | "struct_specifier"
15437 | "union_specifier"
15438 | "enum_specifier"
15439 )
15440 })
15441}
15442
15443pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15444 visibility: &VisibilityIndex<'_>,
15445 file: &ProjectFile,
15446 source: &str,
15447 declarator: Node<'_>,
15448 type_text: Option<&str>,
15449 bindings: &LocalInferenceEngine<T>,
15450) -> bool {
15451 if !has_ancestor_kind(declarator, "compound_statement") {
15452 return false;
15453 }
15454 if declarator
15455 .child_by_field_name("declarator")
15456 .is_none_or(|declarator| declarator.kind() != "identifier")
15457 {
15458 return false;
15459 }
15460 if !type_text
15461 .and_then(|text| visibility.resolve_type(file, text))
15462 .is_some_and(|unit| unit.is_class())
15463 {
15464 return false;
15465 }
15466 declarator
15467 .child_by_field_name("parameters")
15468 .is_some_and(|parameters| {
15469 constructor_parameters_look_like_expressions(parameters, source, bindings)
15470 })
15471}
15472
15473fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15474 parameters: Node<'_>,
15475 source: &str,
15476 bindings: &LocalInferenceEngine<T>,
15477) -> bool {
15478 let mut cursor = parameters.walk();
15479 parameters.named_children(&mut cursor).any(|parameter| {
15480 !matches!(
15481 parameter.kind(),
15482 "parameter_declaration" | "optional_parameter_declaration"
15483 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15484 })
15485}
15486
15487fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15488 parameter: Node<'_>,
15489 source: &str,
15490 bindings: &LocalInferenceEngine<T>,
15491) -> bool {
15492 let text = node_text(parameter, source).trim();
15493 if text
15494 .chars()
15495 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15496 && bindings.is_shadowed(text)
15497 {
15498 return true;
15499 }
15500
15501 let Some(base) = parameter
15502 .child_by_field_name("type")
15503 .filter(|base| base.kind() == "type_identifier")
15504 else {
15505 return false;
15506 };
15507 let Some(subscript) = parameter
15508 .child_by_field_name("declarator")
15509 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15510 else {
15511 return false;
15512 };
15513 subscript.child_by_field_name("size").is_some()
15514 && bindings.is_shadowed(node_text(base, source).trim())
15515}
15516
15517pub fn is_declaration_name(node: Node<'_>) -> bool {
15518 let Some(parent) = node.parent() else {
15519 return false;
15520 };
15521 if parent
15522 .child_by_field_name("name")
15523 .is_some_and(|name| same_node(name, node))
15524 {
15525 if matches!(
15526 parent.kind(),
15527 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15528 ) {
15529 return cpp_tag_specifier_declares_name(parent);
15530 }
15531 if matches!(
15532 parent.kind(),
15533 "namespace_definition"
15534 | "namespace_alias_definition"
15535 | "alias_declaration"
15536 | "enumerator"
15537 ) {
15538 return true;
15539 }
15540 }
15541
15542 let mut current = Some(parent);
15543 while let Some(ancestor) = current {
15544 let type_definition = ancestor.kind() == "type_definition";
15545 let mut child_cursor = ancestor.walk();
15546 if ancestor.named_children(&mut child_cursor).any(|child| {
15547 declaration_declarator(ancestor, child).is_some_and(|declarator| {
15548 declarator_name_path_contains(declarator, node, type_definition)
15549 })
15550 }) {
15551 return true;
15552 }
15553 if matches!(
15554 ancestor.kind(),
15555 "declaration"
15556 | "field_declaration"
15557 | "parameter_declaration"
15558 | "optional_parameter_declaration"
15559 | "function_definition"
15560 | "type_definition"
15561 | "alias_declaration"
15562 | "template_instantiation"
15563 | "class_specifier"
15564 | "struct_specifier"
15565 | "union_specifier"
15566 | "enum_specifier"
15567 ) {
15568 return false;
15569 }
15570 current = ancestor.parent();
15571 }
15572 false
15573}
15574
15575pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15584 if !matches!(
15585 node.kind(),
15586 "qualified_identifier" | "scoped_type_identifier"
15587 ) {
15588 return false;
15589 }
15590 let Some(declaration) = node
15591 .parent()
15592 .filter(|parent| parent.kind() == "declaration")
15593 else {
15594 return false;
15595 };
15596 if declaration.child_by_field_name("declarator") != Some(node)
15597 || !declaration
15598 .child_by_field_name("type")
15599 .is_some_and(|friend| {
15600 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15601 })
15602 {
15603 return false;
15604 }
15605 let mut cursor = declaration.walk();
15606 let mut errors = declaration
15607 .named_children(&mut cursor)
15608 .filter(|child| child.kind() == "ERROR");
15609 let Some(error) = errors.next() else {
15610 return false;
15611 };
15612 errors.next().is_none()
15613 && error.named_child_count() == 1
15614 && error.named_child(0).is_some_and(|class| {
15615 class.kind() == "identifier" && node_text(class, source) == "class"
15616 })
15617}
15618
15619pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15620 if !matches!(node.kind(), "identifier" | "field_identifier") {
15621 return false;
15622 }
15623 if let Some(parent) = node.parent() {
15624 if parent.kind() == "call_expression"
15625 && parent.child_by_field_name("function") == Some(node)
15626 {
15627 return false;
15628 }
15629 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15630 && parent.child_by_field_name("label") == Some(node)
15631 {
15632 return false;
15633 }
15634 }
15635 if is_declaration_name(node) {
15636 return false;
15637 }
15638 let mut current = node.parent();
15639 while let Some(ancestor) = current {
15640 match ancestor.kind() {
15641 "preproc_ifdef" | "preproc_ifndef" => {
15642 if ancestor
15643 .child_by_field_name("name")
15644 .is_some_and(|name| node_range_contains(name, node))
15645 {
15646 return false;
15647 }
15648 }
15649 "preproc_if" | "preproc_elif" => {
15650 if ancestor
15651 .child_by_field_name("condition")
15652 .is_some_and(|condition| node_range_contains(condition, node))
15653 {
15654 return false;
15655 }
15656 }
15657 "preproc_else" => {}
15658 kind if kind.starts_with("preproc_") => return false,
15659 _ => {}
15660 }
15661 if matches!(
15662 ancestor.kind(),
15663 "translation_unit" | "function_definition" | "compound_statement"
15664 ) {
15665 break;
15666 }
15667 current = ancestor.parent();
15668 }
15669 true
15670}
15671
15672fn recovered_c_reference_node(
15673 visibility: &VisibilityIndex<'_>,
15674 file: &ProjectFile,
15675 node: Node<'_>,
15676 source: &str,
15677) -> bool {
15678 if node.start_byte() >= node.end_byte()
15679 || node.is_error()
15680 || node.is_missing()
15681 || !matches!(
15682 node.kind(),
15683 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15684 )
15685 || recovered_c_macro_binding_role(node)
15686 || recovered_c_label_role(node)
15687 {
15688 return false;
15689 }
15690 let name = node_text(node, source);
15698 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15699 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15700 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15701 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15702 if is_declaration_name(node)
15703 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15704 && !recovered_function_call
15705 && !recovered_macro_call
15706 && !recovered_parenthesized_reference
15707 {
15708 return false;
15709 }
15710
15711 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15712 return true;
15713 }
15714 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15715 return true;
15716 }
15717 if recovered_parenthesized_reference {
15718 return true;
15719 }
15720 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15721 if recovered_function_call {
15722 return true;
15723 }
15724 return visibility
15725 .visible_identifier_candidates(file, name)
15726 .any(|candidate| candidate.is_class() || candidate.is_module());
15727 }
15728 let visible = visibility
15729 .visible_identifier_candidates(file, name)
15730 .next()
15731 .is_some();
15732 visible
15733 && (recovered_c_reference_anchor(node)
15734 || recovered_c_error_expression_leaf(node)
15735 || recovered_function_call)
15736}
15737
15738fn push_recovered_c_range(
15739 ranges: &mut Vec<Range>,
15740 seen: &mut HashSet<(usize, usize)>,
15741 start_byte: usize,
15742 end_byte: usize,
15743 node: Node<'_>,
15744 limit: usize,
15745) -> bool {
15746 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15747 return true;
15748 }
15749 if ranges.len() >= limit {
15750 return false;
15751 }
15752 ranges.push(Range {
15753 start_byte,
15754 end_byte,
15755 start_line: node.start_position().row,
15756 end_line: node.end_position().row,
15757 });
15758 true
15759}
15760
15761fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15766 let mut current = node.parent();
15767 while let Some(parent) = current {
15768 if parent.is_error() {
15769 let Some(anchor) = parent.parent() else {
15770 return false;
15771 };
15772 return anchor.kind().ends_with("_expression")
15773 || matches!(
15774 anchor.kind(),
15775 "argument_list"
15776 | "return_statement"
15777 | "expression_statement"
15778 | "case_statement"
15779 | "initializer_list"
15780 | "field_designator"
15781 | "enumerator"
15782 );
15783 }
15784 if matches!(
15785 parent.kind(),
15786 "translation_unit" | "function_definition" | "compound_statement"
15787 ) {
15788 return false;
15789 }
15790 current = parent.parent();
15791 }
15792 false
15793}
15794
15795fn recovered_c_function_call(
15802 visibility: &VisibilityIndex<'_>,
15803 file: &ProjectFile,
15804 node: Node<'_>,
15805 name: &str,
15806) -> bool {
15807 if !matches!(
15808 node.kind(),
15809 "identifier" | "field_identifier" | "type_identifier"
15810 ) {
15811 return false;
15812 }
15813 let error_call_prefix = node.parent().is_some_and(|error| {
15818 error.is_error()
15819 && error
15820 .parent()
15821 .is_some_and(|parent| parent.kind() == "compound_statement")
15822 }) && node
15823 .prev_sibling()
15824 .is_none_or(|previous| previous.kind() == ";")
15825 && node.next_sibling().is_some_and(|open| {
15826 open.kind() == "("
15827 && open.next_named_sibling().is_some_and(|argument| {
15828 argument.kind() == "parameter_declaration" && !argument.has_error()
15829 })
15830 });
15831 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15832 && visibility
15833 .visible_identifier_candidates(file, name)
15834 .any(CodeUnit::is_function)
15835}
15836
15837fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15847 let mut function_declarator = if node.parent().is_some_and(|parent| {
15848 parent.kind() == "function_declarator"
15849 && parent.child_by_field_name("declarator") == Some(node)
15850 }) {
15851 node.parent().expect("checked function declarator parent")
15852 } else {
15853 let Some(parameter) = node.parent().filter(|parent| {
15854 parent.kind() == "parameter_declaration"
15855 && parent.child_by_field_name("type") == Some(node)
15856 }) else {
15857 return false;
15858 };
15859 if !parameter
15860 .child_by_field_name("declarator")
15861 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15862 {
15863 return false;
15864 }
15865 let Some(parameters) = parameter
15866 .parent()
15867 .filter(|parent| parent.kind() == "parameter_list")
15868 else {
15869 return false;
15870 };
15871 let Some(function_declarator) = parameters
15872 .parent()
15873 .filter(|parent| parent.kind() == "function_declarator")
15874 else {
15875 return false;
15876 };
15877 function_declarator
15878 };
15879
15880 while let Some(parent) = function_declarator.parent().filter(|parent| {
15883 parent.kind() == "function_declarator"
15884 && parent.child_by_field_name("declarator") == Some(function_declarator)
15885 }) {
15886 function_declarator = parent;
15887 }
15888 let Some(mut current) = function_declarator
15889 .parent()
15890 .filter(|parent| parent.is_error())
15891 else {
15892 return false;
15893 };
15894 loop {
15895 let Some(parent) = current.parent() else {
15896 return false;
15897 };
15898 if matches!(
15899 parent.kind(),
15900 "translation_unit"
15901 | "compound_statement"
15902 | "preproc_if"
15903 | "preproc_ifdef"
15904 | "preproc_ifndef"
15905 | "preproc_else"
15906 | "preproc_elif"
15907 ) {
15908 return true;
15909 }
15910 if parent.kind() == "function_definition"
15911 && parent.child_by_field_name("declarator") == Some(current)
15912 && parent.named_child(0) == Some(current)
15913 && parent.child_by_field_name("body").is_some()
15914 {
15915 return true;
15916 }
15917 if parent.is_error()
15918 || matches!(
15919 parent.kind(),
15920 "parameter_declaration"
15921 | "parameter_list"
15922 | "function_declarator"
15923 | "abstract_function_declarator"
15924 | "parenthesized_declarator"
15925 )
15926 {
15927 current = parent;
15928 continue;
15929 }
15930 return false;
15931 }
15932}
15933
15934fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
15940 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
15941 return false;
15942 };
15943 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
15944 return false;
15945 }
15946 let Some(declarator) = error
15947 .parent()
15948 .filter(|parent| parent.kind() == "parenthesized_declarator")
15949 else {
15950 return false;
15951 };
15952 let Some(declaration) = declarator
15953 .parent()
15954 .filter(|parent| parent.kind() == "declaration")
15955 else {
15956 return false;
15957 };
15958 if declaration.child_by_field_name("declarator") != Some(declarator) {
15959 return false;
15960 }
15961 let Some(type_node) = declaration.child_by_field_name("type") else {
15962 return false;
15963 };
15964 type_node.kind() == "dependent_type"
15965 && type_node
15966 .child(0)
15967 .is_some_and(|keyword| keyword.kind() == "typename")
15968}
15969
15970fn recovered_c_explicit_assignment_callee(
15971 visibility: &VisibilityIndex<'_>,
15972 file: &ProjectFile,
15973 node: Node<'_>,
15974 name: &str,
15975) -> bool {
15976 let mut current = node;
15977 let error = loop {
15978 let Some(parent) = current.parent() else {
15979 return false;
15980 };
15981 if parent.is_error() {
15982 break parent;
15983 }
15984 current = parent;
15985 };
15986 let mut cursor = error.walk();
15987 let explicit_recovery_precedes_callee = error
15988 .named_children(&mut cursor)
15989 .take_while(|child| child.start_byte() < node.start_byte())
15990 .any(|child| child.kind() == "explicit_function_specifier");
15991 if !explicit_recovery_precedes_callee {
15992 return false;
15993 }
15994 visibility
15995 .visible_identifier_candidates(file, name)
15996 .any(CodeUnit::is_function)
15997}
15998
15999fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16000 while let Some(parent) = node.parent() {
16001 if matches!(
16002 parent.kind(),
16003 "preproc_def" | "preproc_function_def" | "preproc_params"
16004 ) {
16005 return true;
16006 }
16007 if parent.is_error()
16008 || matches!(
16009 parent.kind(),
16010 "translation_unit" | "function_definition" | "compound_statement"
16011 )
16012 {
16013 return false;
16014 }
16015 node = parent;
16016 }
16017 false
16018}
16019
16020fn recovered_c_label_role(node: Node<'_>) -> bool {
16021 node.parent().is_some_and(|parent| {
16022 matches!(parent.kind(), "labeled_statement" | "goto_statement")
16023 && parent.child_by_field_name("label") == Some(node)
16024 })
16025}
16026
16027fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16028 while let Some(parent) = node.parent() {
16029 if parent.is_error() {
16030 return false;
16031 }
16032 if parent.kind() == "optional_parameter_declaration"
16037 && parent
16038 .child_by_field_name("default_value")
16039 .is_some_and(|value| node_range_contains(value, node))
16040 {
16041 return true;
16042 }
16043 if parent.kind().ends_with("_expression")
16044 || matches!(
16045 parent.kind(),
16046 "argument_list"
16047 | "return_statement"
16048 | "expression_statement"
16049 | "case_statement"
16050 | "initializer_list"
16051 | "init_declarator"
16052 | "array_declarator"
16053 | "field_designator"
16054 | "enumerator"
16055 )
16056 {
16057 return true;
16058 }
16059 if matches!(
16060 parent.kind(),
16061 "translation_unit"
16062 | "function_definition"
16063 | "compound_statement"
16064 | "declaration"
16065 | "field_declaration"
16066 | "parameter_declaration"
16067 ) {
16068 return false;
16069 }
16070 node = parent;
16071 }
16072 false
16073}
16074
16075pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16083 let mut current = parameter.parent();
16084 while let Some(ancestor) = current {
16085 if ancestor.kind() == "lambda_expression" {
16086 return ancestor
16087 .child_by_field_name("declarator")
16088 .is_some_and(|declarator| {
16089 declarator.start_byte() <= parameter.start_byte()
16090 && parameter.end_byte() <= declarator.end_byte()
16091 });
16092 }
16093 if ancestor.kind() == "function_definition" {
16094 return ancestor
16095 .child_by_field_name("declarator")
16096 .is_some_and(|declarator| {
16097 declarator.start_byte() <= parameter.start_byte()
16098 && parameter.end_byte() <= declarator.end_byte()
16099 });
16100 }
16101 current = ancestor.parent();
16102 }
16103 false
16104}
16105
16106pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16107 let mut current = node.parent();
16108 while let Some(ancestor) = current {
16109 if matches!(
16110 ancestor.kind(),
16111 "parameter_declaration" | "optional_parameter_declaration"
16112 ) {
16113 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16114 }
16115 if matches!(
16116 ancestor.kind(),
16117 "function_definition" | "lambda_expression" | "compound_statement"
16118 ) {
16119 return false;
16120 }
16121 current = ancestor.parent();
16122 }
16123 false
16124}
16125
16126pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16130 specifier: Node<'_>,
16131) -> impl Iterator<Item = Node<'_>> {
16132 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16133 !matches!(
16134 ancestor.kind(),
16135 "type_descriptor"
16136 | "parameter_declaration"
16137 | "optional_parameter_declaration"
16138 | "template_argument_list"
16139 | "cast_expression"
16140 )
16141 })
16142}
16143
16144pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16145 if specifier.child_by_field_name("body").is_some() {
16146 return true;
16147 }
16148 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16149 match ancestor.kind() {
16150 "declaration" | "field_declaration" => {
16151 let mut cursor = ancestor.walk();
16152 return ancestor
16153 .children_by_field_name("declarator", &mut cursor)
16154 .next()
16155 .is_none();
16156 }
16157 "translation_unit" => return true,
16158 _ => {}
16159 }
16160 }
16161 false
16162}
16163
16164pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16165 match node.kind() {
16166 "identifier"
16167 | "field_identifier"
16168 | "qualified_identifier"
16169 | "scoped_identifier"
16170 | "operator_name"
16171 | "destructor_name"
16172 | "literal_operator_name" => Some(node),
16173 "reference_declarator" | "parenthesized_declarator" => {
16174 node.named_child(0).and_then(declarator_name_node)
16175 }
16176 _ => node
16177 .child_by_field_name("declarator")
16178 .or_else(|| node.child_by_field_name("name"))
16179 .or_else(|| node.child_by_field_name("field"))
16180 .and_then(declarator_name_node),
16181 }
16182}
16183
16184pub fn declarator_path_contains_before_name(
16186 declarator: Node<'_>,
16187 name: Node<'_>,
16188 candidate: Node<'_>,
16189) -> bool {
16190 let mut current = Some(declarator);
16191 while let Some(node) = current {
16192 if same_node(node, candidate) {
16193 return true;
16194 }
16195 if same_node(node, name) {
16196 return false;
16197 }
16198 current = node
16199 .child_by_field_name("declarator")
16200 .or_else(|| node.child_by_field_name("name"))
16201 .or_else(|| node.child_by_field_name("field"));
16202 }
16203 false
16204}
16205
16206fn declarator_name_path_contains(
16207 declarator: Node<'_>,
16208 candidate: Node<'_>,
16209 allow_type_identifier: bool,
16210) -> bool {
16211 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16212 return false;
16213 };
16214 declarator_path_contains_before_name(declarator, name, candidate)
16215}
16216
16217fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16218 match node.kind() {
16219 "identifier"
16220 | "field_identifier"
16221 | "operator_name"
16222 | "destructor_name"
16223 | "literal_operator_name" => Some(node),
16224 "type_identifier" if allow_type_identifier => Some(node),
16225 _ => node
16226 .child_by_field_name("declarator")
16227 .or_else(|| node.child_by_field_name("name"))
16228 .or_else(|| node.child_by_field_name("field"))
16229 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16230 }
16231}
16232
16233pub fn is_nested_type_node(node: Node<'_>) -> bool {
16236 node.parent().is_some_and(|parent| {
16237 matches!(
16238 parent.kind(),
16239 "qualified_identifier" | "scoped_type_identifier" | "template_type"
16240 )
16241 })
16242}
16243
16244pub struct OutOfLineMemberDefinitionOwners<'tree> {
16245 pub owners: Vec<(Node<'tree>, CodeUnit)>,
16246 innermost: Option<(Node<'tree>, CodeUnit)>,
16247}
16248
16249impl OutOfLineMemberDefinitionOwners<'_> {
16250 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16251 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16252 }
16253}
16254
16255pub struct QualifiedOwnerComponents<'tree> {
16256 pub nodes: Vec<Node<'tree>>,
16257 pub names: Vec<String>,
16258 pub global: bool,
16259}
16260
16261pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16266 let mut stack = vec![node];
16267 let mut found_separator = false;
16268 while let Some(current) = stack.pop() {
16269 if !matches!(
16270 current.kind(),
16271 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16272 ) {
16273 continue;
16274 }
16275 let mut current_has_separator = false;
16276 for child in children_iter(current) {
16277 if child.kind() == "::" {
16278 if child.is_missing() {
16279 return false;
16280 }
16281 current_has_separator = true;
16282 found_separator = true;
16283 }
16284 }
16285 if !current_has_separator {
16286 return false;
16287 }
16288 for field in ["scope", "name"] {
16289 if let Some(child) = current.child_by_field_name(field)
16290 && matches!(
16291 child.kind(),
16292 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16293 )
16294 {
16295 stack.push(child);
16296 }
16297 }
16298 }
16299 found_separator
16300}
16301
16302pub fn qualified_owner_components<'tree>(
16303 node: Node<'tree>,
16304 source: &str,
16305) -> Option<QualifiedOwnerComponents<'tree>> {
16306 if !qualified_name_has_concrete_scope_separators(node) {
16307 return None;
16308 }
16309 let mut nodes = cpp_name_component_nodes(node)?;
16310 nodes.pop()?;
16311 if nodes.is_empty() {
16312 return None;
16313 }
16314 let names = nodes
16315 .iter()
16316 .map(|component| node_text(*component, source).to_string())
16317 .collect();
16318 Some(QualifiedOwnerComponents {
16319 nodes,
16320 names,
16321 global: is_globally_qualified_cpp_name(node),
16322 })
16323}
16324
16325pub fn out_of_line_member_definition_owner<'tree>(
16326 analyzer: &CppGraphSource<'_>,
16327 visibility: &VisibilityIndex<'_>,
16328 file: &ProjectFile,
16329 source: &str,
16330 node: Node<'tree>,
16331) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16332 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16333 || !has_ancestor_kind(node, "function_definition")
16334 || !is_function_declarator_name_root(node)
16335 {
16336 return None;
16337 }
16338 let qualified = qualified_owner_components(node, source)?;
16339 let lexical_scope = enclosing_namespace_components(node, source)?;
16340 let mut owners = Vec::new();
16341 let mut innermost = None;
16342
16343 for component_count in 1..=qualified.names.len() {
16344 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16345 .resolve_type_components_lexically(
16346 analyzer,
16347 file,
16348 &qualified.names[..component_count],
16349 qualified.global,
16350 &lexical_scope,
16351 )
16352 && !owners
16353 .iter()
16354 .any(|(_, existing)| same_visible_symbol(existing, &unit))
16355 {
16356 if component_count == qualified.names.len() {
16357 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16358 }
16359 owners.push((qualified.nodes[component_count - 1], unit));
16360 }
16361 }
16362
16363 if innermost.is_none() {
16373 let indexed_owner_components = visibility
16374 .indexed_enclosing_owner_scope(analyzer, file, node)
16375 .or_else(|| {
16376 if qualified.names.len() <= 1 {
16381 return None;
16382 }
16383 let range = Range {
16384 start_byte: node.start_byte(),
16385 end_byte: node.end_byte(),
16386 start_line: node.start_position().row,
16387 end_line: node.end_position().row,
16388 };
16389 let start = analyzer.enclosing_code_unit(file, &range)?;
16390 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16391 brokk_bifrost_core::analyzer::Language::Cpp,
16392 &cpp_name_for(&start),
16393 );
16394 components.pop();
16395 Some(components)
16396 });
16397 if let Some(indexed_owner_components) = indexed_owner_components
16398 && indexed_owner_components.len() > qualified.names.len()
16399 && indexed_owner_components.ends_with(&qualified.names)
16400 && indexed_namespace_path_is_recoverable(
16401 &lexical_scope,
16402 &indexed_owner_components,
16403 qualified.names.len(),
16404 )
16405 && (qualified.names.len() > 1 || !qualified.global)
16410 {
16411 let namespace_count = indexed_owner_components.len() - qualified.names.len();
16412 for component_count in 1..=qualified.names.len() {
16413 let expected = &indexed_owner_components[..namespace_count + component_count];
16414 let owner_node = qualified.nodes[component_count - 1];
16415 for owner in visibility
16416 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16417 .filter(|candidate| candidate.is_class())
16418 .filter(|candidate| {
16419 canonical_cpp_scope_components(candidate) == expected
16420 && visibility.external_type_candidate_visible_in_context(
16421 analyzer, file, candidate, node,
16422 )
16423 })
16424 {
16425 if component_count == qualified.names.len() && innermost.is_none() {
16426 innermost = Some((owner_node, owner.clone()));
16427 }
16428 if !owners
16429 .iter()
16430 .any(|(_, existing)| same_symbol(existing, owner))
16431 {
16432 owners.push((owner_node, owner.clone()));
16433 }
16434 }
16435 }
16436 }
16437 }
16438 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16439}
16440
16441fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16442 let mut current = node;
16443 while let Some(parent) = current.parent() {
16444 if parent.kind() == "function_declarator" {
16445 return parent.child_by_field_name("declarator") == Some(current);
16446 }
16447 if matches!(
16448 parent.kind(),
16449 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16450 ) && parent.child_by_field_name("declarator") == Some(current)
16451 {
16452 current = parent;
16453 continue;
16454 }
16455 return false;
16456 }
16457 false
16458}
16459
16460pub fn append_cpp_name_components(
16461 node: Node<'_>,
16462 source: &str,
16463 out: &mut Vec<String>,
16464) -> Option<()> {
16465 out.extend(
16466 cpp_name_component_nodes(node)?
16467 .into_iter()
16468 .map(|component| node_text(component, source).to_string()),
16469 );
16470 Some(())
16471}
16472
16473fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16476 cpp_name_component_nodes(node).map(|components| {
16477 components
16478 .into_iter()
16479 .map(|component| {
16480 (
16481 node_text(component, source).to_string(),
16482 node_range(component),
16483 )
16484 })
16485 .collect()
16486 })
16487}
16488
16489pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16490 let mut components = Vec::new();
16491 append_cpp_name_components(node, source, &mut components)?;
16492 Some(components)
16493}
16494
16495pub fn unique_macro_replacement_type_candidate(
16504 analyzer: &CppGraphSource<'_>,
16505 visibility: &VisibilityIndex<'_>,
16506 file: &ProjectFile,
16507 components: &[String],
16508) -> Option<CodeUnit> {
16509 let terminal = components.last()?;
16510 let mut candidates = Vec::new();
16511 for candidate in visibility
16512 .visible_identifier_candidates(file, terminal)
16513 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16514 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16515 {
16516 if !candidates
16517 .iter()
16518 .any(|existing| same_logical_symbol(existing, candidate))
16519 {
16520 candidates.push(candidate.clone());
16521 }
16522 }
16523 (candidates.len() == 1).then(|| candidates.remove(0))
16524}
16525
16526pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16534 let mut parser = Parser::new();
16535 if parser
16536 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16537 .is_err()
16538 {
16539 return Vec::new();
16540 }
16541 let Some(tree) = parser.parse(source, None) else {
16542 return Vec::new();
16543 };
16544 let mut scopes = Vec::new();
16545 let mut pending = vec![tree.root_node()];
16546 while let Some(node) = pending.pop() {
16547 if node.kind() == "using_declaration" {
16548 let Some(imported) = node.named_child(0) else {
16549 continue;
16550 };
16551 let Some(mut components) = cpp_type_name_components(imported, source) else {
16552 continue;
16553 };
16554 if components.pop().as_deref() == Some(member) && !components.is_empty() {
16555 scopes.push(components.join("::"));
16556 }
16557 continue;
16558 }
16559 push_named_children_reversed(node, &mut pending);
16560 }
16561 scopes
16562}
16563
16564pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16568 qualified == scope
16569 || qualified
16570 .strip_suffix(scope)
16571 .is_some_and(|prefix| prefix.ends_with("::"))
16572}
16573
16574pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16579 let Some(type_descriptor) = node.parent() else {
16580 return false;
16581 };
16582 if type_descriptor.kind() != "type_descriptor"
16583 || type_descriptor.child_by_field_name("type") != Some(node)
16584 {
16585 return false;
16586 }
16587 let Some(arguments) = type_descriptor.parent() else {
16588 return false;
16589 };
16590 if arguments.kind() != "template_argument_list" {
16591 return false;
16592 }
16593 arguments.parent().is_some_and(|parent| {
16594 matches!(parent.kind(), "template_type" | "template_function")
16595 && parent.child_by_field_name("arguments") == Some(arguments)
16596 })
16597}
16598
16599pub fn cpp_template_reference_arguments(
16600 mut node: Node<'_>,
16601 source: &str,
16602) -> Option<Vec<CppTemplateExpression>> {
16603 loop {
16604 match node.kind() {
16605 "template_type" | "template_function" => {
16606 let arguments = node.child_by_field_name("arguments")?;
16607 let mut cursor = arguments.walk();
16608 return Some(
16609 arguments
16610 .named_children(&mut cursor)
16611 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16612 .map(|argument| CppTemplateExpression {
16613 text: normalize_cpp_whitespace(node_text(argument, source)),
16614 term: cpp_template_term(
16616 argument,
16617 source,
16618 &[],
16619 &ParentIndex::unindexed(),
16620 ),
16621 })
16622 .collect(),
16623 );
16624 }
16625 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16626 node = node
16627 .child_by_field_name("name")
16628 .or_else(|| node.child_by_field_name("type"))?;
16629 }
16630 _ => return None,
16631 }
16632 }
16633}
16634
16635fn cpp_reconcile_primary_template_parameters(
16636 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16637 preferred: &CodeUnit,
16638) -> Option<Vec<CppTemplateParameterMetadata>> {
16639 let canonical = candidates
16640 .iter()
16641 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16642 let mut merged = canonical
16643 .parameters
16644 .iter()
16645 .map(|parameter| CppTemplateParameterMetadata {
16646 name: parameter.name.clone(),
16647 kind: parameter.kind,
16648 variadic: parameter.variadic,
16649 default: None,
16650 })
16651 .collect::<Vec<_>>();
16652
16653 for (_, metadata) in candidates {
16654 if metadata.parameters.len() != merged.len() {
16655 return None;
16656 }
16657 let rename_bindings = metadata
16658 .parameters
16659 .iter()
16660 .zip(&merged)
16661 .map(|(parameter, canonical)| {
16662 (
16663 parameter.name.clone(),
16664 CppTemplateTerm::Parameter(canonical.name.clone()),
16665 )
16666 })
16667 .collect::<HashMap<_, _>>();
16668 for ((parameter, canonical), merged_parameter) in metadata
16669 .parameters
16670 .iter()
16671 .zip(&canonical.parameters)
16672 .zip(&mut merged)
16673 {
16674 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16675 return None;
16676 }
16677 let Some(default) = ¶meter.default else {
16678 continue;
16679 };
16680 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16681 if let Some(existing) = &merged_parameter.default {
16682 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16683 return None;
16684 }
16685 } else {
16686 merged_parameter.default = Some(CppTemplateExpression {
16687 text: default.text.clone(),
16688 term: normalized_term,
16689 });
16690 }
16691 }
16692 }
16693 Some(merged)
16694}
16695
16696pub fn cpp_bind_template_arguments(
16697 parameters: &[CppTemplateParameterMetadata],
16698 explicit_arguments: &[CppTemplateExpression],
16699) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16700 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16701 if variadic_index.is_some_and(|index| {
16702 index + 1 != parameters.len()
16703 || parameters[index + 1..]
16704 .iter()
16705 .any(|parameter| parameter.variadic)
16706 }) {
16707 return None;
16708 }
16709 let fixed_count = variadic_index.unwrap_or(parameters.len());
16710 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16711 return None;
16712 }
16713 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16714 let mut expanded = explicit_arguments[..explicit_fixed_count]
16715 .iter()
16716 .map(cpp_clone_template_expression_iterative)
16717 .collect::<Vec<_>>();
16718 let mut bindings = HashMap::default();
16719 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16720 bindings.insert(
16721 parameter.name.clone(),
16722 cpp_clone_template_term_iterative(&argument.term),
16723 );
16724 }
16725 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
16726 let default = parameter.default.as_ref()?;
16727 let term = cpp_substitute_template_term(&default.term, &bindings)?;
16728 bindings.insert(parameter.name.clone(), term.clone());
16729 expanded.push(CppTemplateExpression {
16730 text: default.text.clone(),
16731 term,
16732 });
16733 }
16734 if let Some(index) = variadic_index {
16735 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16736 expanded.extend(
16737 packed_arguments
16738 .iter()
16739 .map(cpp_clone_template_expression_iterative),
16740 );
16741 bindings.insert(
16742 parameters[index].name.clone(),
16743 CppTemplateTerm::Node {
16744 kind: "parameter_pack".to_string(),
16745 children: packed_arguments
16746 .iter()
16747 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16748 .collect(),
16749 },
16750 );
16751 }
16752 Some((expanded, bindings))
16753}
16754
16755fn cpp_specialization_matches(
16756 metadata: &CppTemplateMetadata,
16757 arguments: &[CppTemplateExpression],
16758) -> bool {
16759 cpp_specialization_terms_match(
16760 &metadata.parameters,
16761 &metadata.specialization_arguments,
16762 arguments,
16763 )
16764}
16765
16766fn cpp_specialization_terms_match(
16767 parameters: &[CppTemplateParameterMetadata],
16768 patterns: &[CppTemplateExpression],
16769 arguments: &[CppTemplateExpression],
16770) -> bool {
16771 if patterns.len() != arguments.len() {
16772 return false;
16773 }
16774 let parameter_names = parameters
16775 .iter()
16776 .map(|parameter| parameter.name.as_str())
16777 .collect::<HashSet<_>>();
16778 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16779 patterns.iter().zip(arguments).all(|(pattern, argument)| {
16780 cpp_unify_template_term(
16781 &pattern.term,
16782 &argument.term,
16783 ¶meter_names,
16784 &mut bindings,
16785 )
16786 })
16787}
16788
16789fn cpp_specialization_more_specialized(
16790 candidate: &CppTemplateMetadata,
16791 other: &CppTemplateMetadata,
16792) -> bool {
16793 cpp_specialization_pattern_accepts(other, candidate)
16794 && !cpp_specialization_pattern_accepts(candidate, other)
16795}
16796
16797fn cpp_specialization_pattern_accepts(
16798 broader: &CppTemplateMetadata,
16799 narrower: &CppTemplateMetadata,
16800) -> bool {
16801 cpp_specialization_terms_match(
16802 &broader.parameters,
16803 &broader.specialization_arguments,
16804 &narrower.specialization_arguments,
16805 )
16806}
16807
16808pub fn cpp_substitute_template_term(
16809 term: &CppTemplateTerm,
16810 bindings: &HashMap<String, CppTemplateTerm>,
16811) -> Option<CppTemplateTerm> {
16812 enum Work<'a> {
16813 Visit(&'a CppTemplateTerm),
16814 Build { kind: String, child_count: usize },
16815 }
16816
16817 let mut work = vec![Work::Visit(term)];
16818 let mut substituted = Vec::new();
16819 while let Some(next) = work.pop() {
16820 match next {
16821 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16822 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16823 }
16824 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16825 substituted.push(CppTemplateTerm::Atom {
16826 kind: kind.clone(),
16827 text: text.clone(),
16828 });
16829 }
16830 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16831 work.push(Work::Build {
16832 kind: kind.clone(),
16833 child_count: children.len(),
16834 });
16835 work.extend(children.iter().rev().map(Work::Visit));
16836 }
16837 Work::Build { kind, child_count } => {
16838 let children = substituted.split_off(substituted.len() - child_count);
16839 substituted.push(CppTemplateTerm::Node { kind, children });
16840 }
16841 }
16842 }
16843 substituted.pop()
16844}
16845
16846pub fn cpp_substitute_template_arguments(
16847 arguments: &[CppTemplateExpression],
16848 bindings: &HashMap<String, CppTemplateTerm>,
16849) -> Option<Vec<CppTemplateExpression>> {
16850 let mut substituted = Vec::new();
16851 for argument in arguments {
16852 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16853 substituted.push(CppTemplateExpression {
16854 text: argument.text.clone(),
16855 term: cpp_substitute_template_term(&argument.term, bindings)?,
16856 });
16857 continue;
16858 };
16859 if kind != "parameter_pack_expansion" {
16860 substituted.push(CppTemplateExpression {
16861 text: argument.text.clone(),
16862 term: cpp_substitute_template_term(&argument.term, bindings)?,
16863 });
16864 continue;
16865 }
16866 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16867 return None;
16868 };
16869 if ellipsis != "..." {
16870 return None;
16871 }
16872
16873 let mut pack_names = Vec::new();
16874 let mut work = vec![pattern];
16875 while let Some(term) = work.pop() {
16876 match term {
16877 CppTemplateTerm::Parameter(name)
16878 if matches!(
16879 bindings.get(name),
16880 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16881 ) =>
16882 {
16883 if !pack_names.contains(name) {
16884 pack_names.push(name.clone());
16885 }
16886 }
16887 CppTemplateTerm::Node { children, .. } => work.extend(children),
16888 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16889 }
16890 }
16891 let first_pack = pack_names.first()?;
16892 let CppTemplateTerm::Node {
16893 children: first_elements,
16894 ..
16895 } = bindings.get(first_pack)?
16896 else {
16897 return None;
16898 };
16899 let pack_len = first_elements.len();
16900 for pack_name in &pack_names {
16901 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16902 return None;
16903 };
16904 if children.len() != pack_len {
16905 return None;
16906 }
16907 }
16908 for index in 0..pack_len {
16909 let mut element_bindings = bindings.clone();
16910 for pack_name in &pack_names {
16911 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16912 return None;
16913 };
16914 element_bindings.insert(
16915 pack_name.clone(),
16916 cpp_clone_template_term_iterative(&children[index]),
16917 );
16918 }
16919 substituted.push(CppTemplateExpression {
16920 text: argument.text.clone(),
16921 term: cpp_substitute_template_term(pattern, &element_bindings)?,
16922 });
16923 }
16924 }
16925 Some(substituted)
16926}
16927
16928fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
16929 enum Work<'a> {
16930 Visit(&'a CppTemplateTerm),
16931 Build { kind: String, child_count: usize },
16932 }
16933
16934 let mut work = vec![Work::Visit(term)];
16935 let mut cloned = Vec::new();
16936 while let Some(next) = work.pop() {
16937 match next {
16938 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16939 cloned.push(CppTemplateTerm::Parameter(name.clone()));
16940 }
16941 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16942 cloned.push(CppTemplateTerm::Atom {
16943 kind: kind.clone(),
16944 text: text.clone(),
16945 });
16946 }
16947 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16948 work.push(Work::Build {
16949 kind: kind.clone(),
16950 child_count: children.len(),
16951 });
16952 work.extend(children.iter().rev().map(Work::Visit));
16953 }
16954 Work::Build { kind, child_count } => {
16955 let children = cloned.split_off(cloned.len() - child_count);
16956 cloned.push(CppTemplateTerm::Node { kind, children });
16957 }
16958 }
16959 }
16960 cloned
16961 .pop()
16962 .expect("template term traversal emits one root")
16963}
16964
16965fn cpp_clone_template_expression_iterative(
16966 expression: &CppTemplateExpression,
16967) -> CppTemplateExpression {
16968 CppTemplateExpression {
16969 text: expression.text.clone(),
16970 term: cpp_clone_template_term_iterative(&expression.term),
16971 }
16972}
16973
16974pub fn cpp_unify_template_term(
16975 pattern: &CppTemplateTerm,
16976 argument: &CppTemplateTerm,
16977 parameters: &HashSet<&str>,
16978 bindings: &mut HashMap<String, CppTemplateTerm>,
16979) -> bool {
16980 let mut work = vec![(pattern, argument)];
16981 while let Some((pattern, argument)) = work.pop() {
16982 match pattern {
16983 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
16984 if let Some(bound) = bindings.get(name) {
16985 if !cpp_template_terms_equal(bound, argument) {
16986 return false;
16987 }
16988 } else {
16989 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
16990 }
16991 }
16992 CppTemplateTerm::Atom {
16993 kind: pattern_kind,
16994 text: pattern_text,
16995 } => {
16996 if !matches!(
16997 argument,
16998 CppTemplateTerm::Atom { kind, text }
16999 if kind == pattern_kind && text == pattern_text
17000 ) {
17001 return false;
17002 }
17003 }
17004 CppTemplateTerm::Node {
17005 kind: pattern_kind,
17006 children: pattern_children,
17007 } => {
17008 let CppTemplateTerm::Node { kind, children } = argument else {
17009 return false;
17010 };
17011 if kind != pattern_kind || children.len() != pattern_children.len() {
17012 return false;
17013 }
17014 work.extend(pattern_children.iter().zip(children).rev());
17015 }
17016 CppTemplateTerm::Parameter(_) => return false,
17017 }
17018 }
17019 true
17020}
17021
17022fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17023 let mut work = vec![(left, right)];
17024 while let Some((left, right)) = work.pop() {
17025 match (left, right) {
17026 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17027 if left != right {
17028 return false;
17029 }
17030 }
17031 (
17032 CppTemplateTerm::Atom {
17033 kind: left_kind,
17034 text: left_text,
17035 },
17036 CppTemplateTerm::Atom {
17037 kind: right_kind,
17038 text: right_text,
17039 },
17040 ) => {
17041 if left_kind != right_kind || left_text != right_text {
17042 return false;
17043 }
17044 }
17045 (
17046 CppTemplateTerm::Node {
17047 kind: left_kind,
17048 children: left_children,
17049 },
17050 CppTemplateTerm::Node {
17051 kind: right_kind,
17052 children: right_children,
17053 },
17054 ) => {
17055 if left_kind != right_kind || left_children.len() != right_children.len() {
17056 return false;
17057 }
17058 work.extend(left_children.iter().zip(right_children).rev());
17059 }
17060 _ => return false,
17061 }
17062 }
17063 true
17064}
17065
17066pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17067 let mut components = Vec::new();
17068 let mut stack = vec![node];
17069 while let Some(current) = stack.pop() {
17070 match current.kind() {
17071 "identifier"
17072 | "field_identifier"
17073 | "namespace_identifier"
17074 | "type_identifier"
17075 | "operator_name"
17076 | "destructor_name" => components.push(current),
17077 "template_type" | "template_function" => {
17078 stack.push(current.child_by_field_name("name")?);
17079 }
17080 "dependent_name" => stack.push(current.named_child(0)?),
17081 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17082 stack.push(current.child_by_field_name("name")?);
17083 if let Some(scope) = current.child_by_field_name("scope") {
17084 stack.push(scope);
17085 }
17086 }
17087 "nested_namespace_specifier" => {
17088 for index in (0..current.named_child_count()).rev() {
17089 stack.push(current.named_child(index)?);
17090 }
17091 }
17092 _ => return None,
17093 }
17094 }
17095 Some(components)
17096}
17097
17098pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17099 node.child_by_field_name("scope").is_none()
17100 && node.child(0).is_some_and(|child| child.kind() == "::")
17101}
17102
17103fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17104 let mut namespaces = Vec::new();
17105 for parent in enclosing_cpp_namespace_nodes(node) {
17106 if let Some(name) = parent.child_by_field_name("name") {
17107 let mut components = Vec::new();
17108 append_cpp_name_components(name, source, &mut components)?;
17109 namespaces.push(components);
17110 }
17111 }
17112 Some(namespaces.into_iter().flatten().collect())
17113}
17114
17115fn indexed_namespace_path_is_recoverable(
17126 lexical_scope: &[String],
17127 indexed_owner_scope: &[String],
17128 explicit_owner_component_count: usize,
17129) -> bool {
17130 if lexical_scope.is_empty() {
17131 return explicit_owner_component_count > 1;
17132 }
17133 if lexical_scope.len() >= indexed_owner_scope.len() {
17134 return false;
17135 }
17136 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17137}
17138
17139pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17140
17141pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17147 let mut current = Some(node);
17148 while let Some(candidate) = current {
17149 if candidate.kind() == "declaration" {
17150 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17151 return false;
17152 }
17153 let mut cursor = candidate.walk();
17154 return candidate.named_children(&mut cursor).any(|child| {
17155 child.kind() == "init_declarator"
17156 && child
17157 .child_by_field_name("value")
17158 .is_some_and(|value| value.kind() == "cast_expression")
17159 });
17160 }
17161 current = candidate.parent();
17162 }
17163 false
17164}
17165
17166#[derive(Clone, Copy, PartialEq, Eq)]
17167pub(crate) enum QualifiedAliasReferenceKind {
17168 Ordinary,
17169 ConstructorWithExpressionArgument,
17170 ExhaustiveTemplate,
17171}
17172
17173pub(crate) fn qualified_alias_reference_preserves_target(
17180 node: Node<'_>,
17181 target: &CodeUnit,
17182 analyzer: &CppGraphSource<'_>,
17183 visibility: &VisibilityIndex<'_>,
17184 file: &ProjectFile,
17185 source: &str,
17186) -> Option<QualifiedAliasReferenceKind> {
17187 if !matches!(
17188 node.kind(),
17189 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17190 ) {
17191 return None;
17192 }
17193 let components = cpp_type_name_components(node, source)?;
17194 let name = components.last()?;
17195 analyzer.type_alias_provider().and_then(|provider| {
17196 visibility
17197 .visible_identifier_candidates(file, name)
17198 .find_map(|candidate| {
17199 let proof = provider.is_type_alias(candidate)
17200 && canonical_cpp_scope_components(candidate) == components
17201 && visibility.external_type_candidate_visible_in_context(
17202 analyzer, file, candidate, node,
17203 )
17204 && match cpp_template_reference_arguments(node, source) {
17205 Some(arguments) => visibility.template_alias_arguments_preserve_target(
17206 analyzer, file, candidate, &arguments, target,
17207 ),
17208 None => visibility.structured_alias_primary_preserves_target(
17209 analyzer, file, candidate, target,
17210 ),
17211 };
17212 proof.then(|| {
17213 if cpp_template_reference_arguments(node, source).is_some()
17214 && visibility.is_exhaustive_same_fqn_type_declaration_family(
17215 analyzer, file, candidate,
17216 )
17217 {
17218 QualifiedAliasReferenceKind::ExhaustiveTemplate
17219 } else if qualified_alias_constructor_has_expression_argument(node)
17220 || qualified_alias_local_constructor_declaration(node)
17221 {
17222 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17223 } else {
17224 QualifiedAliasReferenceKind::Ordinary
17225 }
17226 })
17227 })
17228 })
17229}
17230
17231pub(crate) fn qualified_alias_reference_requires_terminal(
17232 reference: Option<QualifiedAliasReferenceKind>,
17233) -> bool {
17234 matches!(
17235 reference,
17236 Some(
17237 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17238 | QualifiedAliasReferenceKind::ExhaustiveTemplate
17239 )
17240 )
17241}
17242
17243fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17244 let Some(declaration) = node.parent().filter(|parent| {
17245 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17246 }) else {
17247 return false;
17248 };
17249 let mut cursor = declaration.walk();
17250 declaration.named_children(&mut cursor).any(|child| {
17251 child.kind() == "init_declarator"
17252 && child
17253 .child_by_field_name("value")
17254 .filter(|value| value.kind() == "argument_list")
17255 .is_some_and(|arguments| {
17256 let mut cursor = arguments.walk();
17257 arguments.named_children(&mut cursor).any(|argument| {
17258 let is_parameter = matches!(
17259 argument.kind(),
17260 "parameter_declaration" | "optional_parameter_declaration"
17261 );
17262 if is_parameter {
17263 argument
17264 .child_by_field_name("type")
17265 .is_some_and(|type_node| {
17266 type_node.kind() == "type_identifier"
17267 && argument.child_by_field_name("declarator").is_none()
17268 })
17269 } else {
17270 !argument.kind().ends_with("_literal")
17271 && !matches!(argument.kind(), "true" | "false" | "nullptr")
17272 }
17273 })
17274 })
17275 })
17276}
17277
17278fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17283 let Some(declaration) = node.parent().filter(|parent| {
17284 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17285 }) else {
17286 return false;
17287 };
17288 if declaration
17289 .parent()
17290 .is_none_or(|parent| parent.kind() != "compound_statement")
17291 {
17292 return false;
17293 }
17294 let mut cursor = declaration.walk();
17295 declaration
17296 .named_children(&mut cursor)
17297 .any(|child| child.kind() == "function_declarator")
17298}
17299
17300pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17306 loop {
17307 let next = match node.kind() {
17308 "qualified_identifier"
17309 | "scoped_identifier"
17310 | "template_method"
17311 | "template_function"
17312 | "template_type" => node.child_by_field_name("name"),
17313 "field_expression" => node.child_by_field_name("field"),
17314 _ => None,
17315 };
17316 let Some(next) = next else {
17317 return node;
17318 };
17319 node = next;
17320 }
17321}
17322
17323#[derive(Clone, Copy)]
17324pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17325 pub receiver: Node<'tree>,
17326 pub member: Node<'tree>,
17327 pub arity: usize,
17328}
17329
17330pub fn recovered_relational_template_member_call(
17338 field: Node<'_>,
17339) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17340 if field.kind() != "field_expression" {
17341 return None;
17342 }
17343 let receiver = field
17344 .child_by_field_name("argument")
17345 .or_else(|| field.child_by_field_name("object"))?;
17346 let member = field.child_by_field_name("field")?;
17347 let less = field.parent()?;
17348 if less.kind() != "binary_expression"
17349 || less.child_by_field_name("left") != Some(field)
17350 || less
17351 .child_by_field_name("operator")
17352 .is_none_or(|operator| operator.kind() != "<")
17353 || less.child_by_field_name("right").is_none()
17354 {
17355 return None;
17356 }
17357 let greater = less.parent()?;
17358 if greater.kind() != "binary_expression"
17359 || greater.child_by_field_name("left") != Some(less)
17360 || greater
17361 .child_by_field_name("operator")
17362 .is_none_or(|operator| operator.kind() != ">")
17363 {
17364 return None;
17365 }
17366 let arguments = greater.child_by_field_name("right")?;
17367 if arguments.kind() != "parenthesized_expression" {
17368 return None;
17369 }
17370 let arity = parenthesized_call_argument_arity(arguments)?;
17371 Some(RecoveredRelationalTemplateMemberCall {
17372 receiver,
17373 member,
17374 arity,
17375 })
17376}
17377
17378fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17379 let expression = arguments.named_child(0)?;
17380 if expression.kind() != "comma_expression" {
17381 return Some(1);
17382 }
17383 let mut arity = 0usize;
17384 let mut stack = vec![expression];
17385 while let Some(node) = stack.pop() {
17386 if node.kind() == "comma_expression" {
17387 stack.push(node.child_by_field_name("right")?);
17388 stack.push(node.child_by_field_name("left")?);
17389 } else {
17390 arity += 1;
17391 }
17392 }
17393 Some(arity)
17394}
17395
17396pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17399 while let Some(parent) = node.parent() {
17400 match parent.kind() {
17401 "call_expression" => {
17402 return parent
17403 .child_by_field_name("function")
17404 .or_else(|| parent.named_child(0))
17405 == Some(node);
17406 }
17407 "qualified_identifier"
17408 | "scoped_identifier"
17409 | "template_function"
17410 | "template_type"
17411 | "field_expression" => node = parent,
17412 _ => return false,
17413 }
17414 }
17415 false
17416}
17417
17418pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17422 loop {
17423 let Some(parent) = node.parent() else {
17424 return node;
17425 };
17426 if parent.kind() == "template_type"
17427 && parent
17428 .child_by_field_name("name")
17429 .is_some_and(|name| same_node(name, node))
17430 {
17431 node = parent;
17432 continue;
17433 }
17434 if matches!(
17435 parent.kind(),
17436 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17437 ) && (parent
17438 .child_by_field_name("scope")
17439 .is_some_and(|scope| same_node(scope, node))
17440 || parent
17441 .child_by_field_name("name")
17442 .is_some_and(|name| same_node(name, node)))
17443 && parent.end_byte() == node.end_byte()
17444 {
17445 node = parent;
17446 continue;
17447 }
17448 return node;
17449 }
17450}
17451
17452pub fn normalize_type_text(value: &str) -> String {
17453 strip_tag_type_prefix(
17454 normalize_cpp_whitespace(value)
17455 .trim_start_matches("const ")
17456 .trim_end_matches('*')
17457 .trim_end_matches('&')
17458 .trim(),
17459 )
17460 .to_string()
17461}
17462
17463use crate::declarations::strip_tag_type_prefix;
17464
17465pub fn normalize_reference_name(value: &str) -> Option<String> {
17466 let normalized = normalize_cpp_reference_text(value);
17467 (!normalized.is_empty()).then_some(normalized)
17468}
17469
17470pub fn normalize_cpp_reference_text(value: &str) -> String {
17471 let mut text = normalize_cpp_whitespace(value)
17472 .trim_start_matches("new ")
17473 .trim()
17474 .to_string();
17475 if let Some(index) = text.find(['(', '{']) {
17476 text.truncate(index);
17477 }
17478 if let Some(index) = text.find('<') {
17479 text.truncate(index);
17480 }
17481 let normalized = text
17482 .trim()
17483 .trim_start_matches("const ")
17484 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17485 .trim_matches(':')
17486 .trim();
17487 strip_tag_type_prefix(normalized).to_string()
17488}
17489
17490pub fn cpp_name_for(unit: &CodeUnit) -> String {
17491 let short = unit.short_name().replace(['.', '$'], "::");
17492 if unit.package_name().is_empty() {
17493 short
17494 } else {
17495 format!("{}::{}", unit.package_name(), short)
17496 }
17497}
17498
17499fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17503 let fq = unit.fq();
17504 if fq.is_empty() {
17505 return None;
17506 }
17507 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17508 Some(
17509 fq.segments()
17510 .iter()
17511 .map(|&segment| interner.resolve(segment).0)
17512 .collect::<Vec<_>>()
17513 .join("::"),
17514 )
17515}
17516
17517fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17518 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17519 || unit.fq().is_empty() && cpp_name_for(unit) == expected
17520}
17521
17522pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17531 let fq = unit.fq();
17532 if !fq.is_empty() {
17533 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17534 let scope = fq
17535 .segments()
17536 .iter()
17537 .filter_map(|&segment| {
17538 let (text, kind) = interner.resolve(segment);
17539 matches!(
17540 kind,
17541 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17542 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17543 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17544 )
17545 .then(|| text.to_string())
17546 })
17547 .collect();
17548 return scope;
17549 }
17550 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17551 brokk_bifrost_core::analyzer::Language::Cpp,
17552 &cpp_name_for(unit),
17553 )
17554}
17555
17556pub fn terminal_name(value: &str) -> &str {
17567 value
17568 .rsplit("::")
17569 .next()
17570 .unwrap_or(value)
17571 .rsplit(['.', '-', '>'])
17572 .next()
17573 .unwrap_or(value)
17574 .trim()
17575}
17576
17577pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17578 terminal_name(&normalize_cpp_reference_text(value)) == expected
17579}
17580
17581pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17582 name_matches_terminal(value, expected)
17583 || expected.starts_with("operator")
17584 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17585}
17586
17587pub fn name_mentions(value: &str, expected: &str) -> bool {
17588 normalize_cpp_reference_text(value)
17589 .split("::")
17590 .any(|part| part == expected)
17591}
17592
17593pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17594 let cpp_name = cpp_name_for(unit);
17595 if reference.contains("::") {
17596 return reference == cpp_name;
17597 }
17598 reference == cpp_name
17599 || terminal_name(reference) == unit.identifier()
17600 && (unit.package_name().is_empty() || reference == unit.identifier())
17601}
17602
17603pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17604 match kind {
17605 TargetKind::Type
17606 | TargetKind::Constructor
17607 | TargetKind::Method
17608 | TargetKind::MemberField => true,
17609 TargetKind::FreeFunction => unit.is_function(),
17610 TargetKind::GlobalField => unit.is_field(),
17611 TargetKind::Macro => unit.is_macro(),
17612 }
17613}
17614
17615fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17616 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17617 let target_name = cpp_name_for(target);
17618 if normalized.contains("::") {
17619 return normalized == target_name;
17620 }
17621 if let Some(namespace) = alias.namespace.as_deref() {
17622 return namespace_prefixes(namespace)
17623 .into_iter()
17624 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17625 }
17626 target.package_name().is_empty() && normalized == target.identifier()
17627}
17628
17629pub fn cpp_function_return_type_text(
17632 analyzer: &CppGraphSource<'_>,
17633 function: &CodeUnit,
17634) -> Option<String> {
17635 let metadata = analyzer.signature_metadata(function);
17636 let first = metadata.first()?.return_type_text()?;
17637 metadata
17638 .iter()
17639 .all(|metadata| metadata.return_type_text() == Some(first))
17640 .then(|| first.to_owned())
17641}
17642
17643pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17644 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17654 namespace
17655 .strip_prefix("anonymous_namespace::")
17656 .unwrap_or(namespace)
17657 .to_string()
17658 })
17659}
17660
17661pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
17663 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17669 brokk_bifrost_core::analyzer::Language::Cpp,
17670 namespace,
17671 );
17672 let mut prefixes = Vec::new();
17673 while !parts.is_empty() {
17674 prefixes.push(parts.join("::"));
17675 parts.pop();
17676 }
17677 prefixes
17678}
17679
17680fn nearest_namespace_candidates(
17681 candidates: Vec<CodeUnit>,
17682 normalized: &str,
17683 lexical_namespace: Option<&str>,
17684) -> Vec<CodeUnit> {
17685 if normalized.contains("::") {
17686 return candidates;
17687 }
17688 if let Some(namespace) = lexical_namespace {
17689 for prefix in namespace_prefixes(namespace) {
17690 let scoped = candidates
17691 .iter()
17692 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17693 .cloned()
17694 .collect::<Vec<_>>();
17695 if !scoped.is_empty() {
17696 return scoped;
17697 }
17698 }
17699 }
17700 candidates
17701 .into_iter()
17702 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17703 .collect()
17704}
17705
17706pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17707 let mut namespaces = Vec::new();
17708 for parent in enclosing_cpp_namespace_nodes(node) {
17709 if let Some(name) = parent.child_by_field_name("name") {
17710 let namespace = normalize_cpp_reference_text(node_text(name, source));
17711 if !namespace.is_empty() {
17712 namespaces.push(namespace);
17713 }
17714 }
17715 }
17716 if namespaces.is_empty() {
17717 None
17718 } else {
17719 Some(namespaces.join("::"))
17720 }
17721}
17722
17723pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17727 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17728}
17729
17730fn type_owner_resolution(
17731 analyzer: &CppGraphSource<'_>,
17732 code_unit: &CodeUnit,
17733) -> Option<ResolvedTypeOwner> {
17734 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17735}
17736
17737fn target_type_owner_resolution(
17738 analyzer: &CppGraphSource<'_>,
17739 code_unit: &CodeUnit,
17740) -> Option<ResolvedTypeOwner> {
17741 match type_owner_resolution(analyzer, code_unit) {
17742 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17743 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17744 }
17745}
17746
17747fn target_forward_owner_resolution(
17759 analyzer: &CppGraphSource<'_>,
17760 code_unit: &CodeUnit,
17761) -> Option<ResolvedTypeOwner> {
17762 if !code_unit.is_function() {
17763 return None;
17764 }
17765 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17771 let cpp = analyzer.cpp?;
17772 let mut visible_files = HashSet::default();
17773 collect_include_closure(
17774 analyzer,
17775 cpp.include_target_index(),
17776 code_unit.source(),
17777 &mut visible_files,
17778 None,
17779 );
17780 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17781 let visible_candidates = candidates
17782 .iter()
17783 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17784 .cloned()
17785 .collect::<Vec<_>>();
17786 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17787 DirectOwnerResolution::UniqueFull(unit) => {
17788 return Some(ResolvedTypeOwner {
17789 unit,
17790 is_forward_declaration: false,
17791 });
17792 }
17793 DirectOwnerResolution::ForwardsOnly(forwards) => {
17794 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17795 unit: forwards.into_iter().next().unwrap(),
17796 is_forward_declaration: true,
17797 });
17798 }
17799 DirectOwnerResolution::Ambiguous => return None,
17800 DirectOwnerResolution::None => {}
17801 }
17802
17803 let candidates = candidates
17804 .into_iter()
17805 .filter(|candidate| candidate.is_class())
17806 .collect::<Vec<_>>();
17807 let (unit, is_forward_declaration) =
17808 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17809 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17810 DirectOwnerResolution::ForwardsOnly(forwards) => {
17811 (unique_logical_forward_owner(forwards)?, true)
17812 }
17813 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17814 };
17815 Some(ResolvedTypeOwner {
17816 unit,
17817 is_forward_declaration,
17818 })
17819}
17820
17821pub fn precise_parent_of(
17822 analyzer: &CppGraphSource<'_>,
17823 visibility: &VisibilityIndex<'_>,
17824 code_unit: &CodeUnit,
17825) -> Option<CodeUnit> {
17826 visibility.cached_precise_parent_of(analyzer, code_unit)
17827}
17828
17829fn precise_parent_resolution(
17830 analyzer: &CppGraphSource<'_>,
17831 code_unit: &CodeUnit,
17832) -> Option<ResolvedTypeOwner> {
17833 #[cfg(any(test, feature = "test-support"))]
17834 if let Some(cpp) = analyzer.cpp {
17835 cpp.record_cpp_parent_resolution_for_test();
17836 }
17837 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17838 return Some(ResolvedTypeOwner {
17839 unit,
17840 is_forward_declaration: false,
17841 });
17842 }
17843 let fallback = analyzer.parent_of(code_unit);
17844 if !code_unit.owner_is_type_scope() {
17845 return fallback.map(|unit| ResolvedTypeOwner {
17846 unit,
17847 is_forward_declaration: false,
17848 });
17849 }
17850 let owner_fq = code_unit
17851 .fq()
17852 .parent()
17853 .expect("a unit with an owner identifier has a structured parent");
17854 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17855 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17856 DirectOwnerResolution::UniqueFull(owner) => {
17857 return Some(ResolvedTypeOwner {
17858 unit: owner,
17859 is_forward_declaration: false,
17860 });
17861 }
17862 DirectOwnerResolution::Ambiguous => return None,
17863 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17864 }
17865 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17866 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17867 unit: owner,
17868 is_forward_declaration: false,
17869 }),
17870 DirectOwnerResolution::Ambiguous => None,
17871 DirectOwnerResolution::ForwardsOnly(forwards) => {
17872 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17873 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17874 unit: owner,
17875 is_forward_declaration: false,
17876 }),
17877 FullOwnerResolution::None => {
17878 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17879 unit,
17880 is_forward_declaration: true,
17881 })
17882 }
17883 FullOwnerResolution::Ambiguous => None,
17884 }
17885 }
17886 DirectOwnerResolution::None => {
17887 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17888 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17889 unit: owner,
17890 is_forward_declaration: false,
17891 }),
17892 FullOwnerResolution::Ambiguous => None,
17893 FullOwnerResolution::None => fallback
17894 .filter(|parent| {
17895 parent.source() == code_unit.source()
17896 && parent.fq() == &owner_fq
17897 && (!parent.is_class()
17898 || cpp_class_declaration_strength(analyzer, parent)
17899 == CppClassDeclarationStrength::Full)
17900 })
17901 .map(|unit| ResolvedTypeOwner {
17902 unit,
17903 is_forward_declaration: false,
17904 }),
17905 }
17906 }
17907 }
17908}
17909
17910fn exact_structural_type_parent(
17911 analyzer: &CppGraphSource<'_>,
17912 code_unit: &CodeUnit,
17913) -> Option<CodeUnit> {
17914 if !code_unit.is_function() && !code_unit.is_field() {
17915 return None;
17916 }
17917 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17919 let parent = cpp.structural_parent_of(code_unit)?;
17920 (!parent.is_module()
17921 && parent.source() == code_unit.source()
17922 && parent.package_name() == code_unit.package_name()
17923 && parent.short_name() == encoded_owner)
17924 .then_some(parent)
17925}
17926
17927fn same_source_owner(
17928 analyzer: &CppGraphSource<'_>,
17929 code_unit: &CodeUnit,
17930 owner_candidates: &[CodeUnit],
17931) -> DirectOwnerResolution {
17932 let candidates = owner_candidates
17933 .iter()
17934 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
17935 .cloned()
17936 .collect::<Vec<_>>();
17937 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17938 classify_direct_owner_candidates(analyzer, candidates.into_iter())
17939}
17940
17941fn visible_full_cpp_owner(
17942 analyzer: &CppGraphSource<'_>,
17943 code_unit: &CodeUnit,
17944 owner_candidates: &[CodeUnit],
17945) -> FullOwnerResolution {
17946 let Some(cpp) = analyzer.cpp else {
17947 return FullOwnerResolution::None;
17948 };
17949 let mut visible_files = HashSet::default();
17950 collect_include_closure(
17951 analyzer,
17952 cpp.include_target_index(),
17953 code_unit.source(),
17954 &mut visible_files,
17955 None,
17956 );
17957 let candidates = owner_candidates
17958 .iter()
17959 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17960 .cloned()
17961 .collect::<Vec<_>>();
17962 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
17963 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
17964 let strength = cpp_class_declaration_strength(analyzer, &candidate);
17965 (candidate, strength)
17966 })) {
17967 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
17968 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
17969 FullOwnerResolution::None
17970 }
17971 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
17972 }
17973}
17974
17975pub enum DirectOwnerResolution {
17976 None,
17977 ForwardsOnly(Vec<CodeUnit>),
17978 UniqueFull(CodeUnit),
17979 Ambiguous,
17980}
17981
17982enum FullOwnerResolution {
17983 None,
17984 Unique(CodeUnit),
17985 Ambiguous,
17986}
17987
17988fn directly_included_owner(
17989 analyzer: &CppGraphSource<'_>,
17990 code_unit: &CodeUnit,
17991 owner_candidates: &[CodeUnit],
17992) -> DirectOwnerResolution {
17993 let Some(cpp) = analyzer.cpp else {
17994 return DirectOwnerResolution::None;
17995 };
17996 let direct_includes: HashSet<ProjectFile> = analyzer
17997 .canonical_include_paths(code_unit.source())
17998 .unwrap_or_default()
17999 .into_iter()
18000 .flat_map(|include| {
18001 resolve_include_targets_with_index(
18002 code_unit.source(),
18003 &include,
18004 cpp.include_target_index(),
18005 )
18006 })
18007 .collect();
18008 let candidates = owner_candidates
18009 .iter()
18010 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18011 .cloned()
18012 .collect::<Vec<_>>();
18013 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18014 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18015}
18016
18017fn prefer_member_declaring_owners(
18018 analyzer: &CppGraphSource<'_>,
18019 member: &CodeUnit,
18020 candidates: Vec<CodeUnit>,
18021) -> Vec<CodeUnit> {
18022 let matching = candidates
18023 .iter()
18024 .filter(|owner| owner_declares_member(analyzer, owner, member))
18025 .cloned()
18026 .collect::<Vec<_>>();
18027 if matching.is_empty() {
18028 candidates
18029 } else {
18030 matching
18031 }
18032}
18033
18034fn owner_declares_member(
18035 analyzer: &CppGraphSource<'_>,
18036 owner: &CodeUnit,
18037 member: &CodeUnit,
18038) -> bool {
18039 analyzer.direct_children(owner).into_iter().any(|child| {
18040 child.kind() == member.kind()
18041 && child.identifier() == member.identifier()
18042 && child.signature() == member.signature()
18043 })
18044}
18045
18046fn classify_direct_owner_candidates(
18047 analyzer: &CppGraphSource<'_>,
18048 candidates: impl Iterator<Item = CodeUnit>,
18049) -> DirectOwnerResolution {
18050 collapse_owner_candidates(candidates.map(|candidate| {
18051 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18052 (candidate, strength)
18053 }))
18054}
18055
18056pub fn collapse_owner_candidates(
18057 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18058) -> DirectOwnerResolution {
18059 let mut full_definition = None;
18060 let mut forwards = Vec::new();
18061 for (candidate, strength) in candidates {
18062 match strength {
18063 CppClassDeclarationStrength::Full if full_definition.is_some() => {
18064 return DirectOwnerResolution::Ambiguous;
18065 }
18066 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18067 CppClassDeclarationStrength::Forward => forwards.push(candidate),
18068 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18069 }
18070 }
18071 if let Some(owner) = full_definition {
18072 DirectOwnerResolution::UniqueFull(owner)
18073 } else if !forwards.is_empty() {
18074 DirectOwnerResolution::ForwardsOnly(forwards)
18075 } else {
18076 DirectOwnerResolution::None
18077 }
18078}
18079
18080#[cfg(any(test, feature = "test-support"))]
18081pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18082 unique_logical_forward_owner(forwards)
18083}
18084
18085fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18086 let first = forwards.pop()?;
18087 forwards
18088 .iter()
18089 .all(|forward| same_logical_symbol(forward, &first))
18090 .then_some(first)
18091}
18092
18093pub fn cpp_class_declaration_strength(
18094 analyzer: &CppGraphSource<'_>,
18095 candidate: &CodeUnit,
18096) -> CppClassDeclarationStrength {
18097 let Some(cpp) = analyzer.cpp else {
18105 return uncached_cpp_class_declaration_strength(analyzer, candidate);
18106 };
18107 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18108 return strength;
18109 }
18110 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18111 cpp.cache_class_declaration_strength(candidate, strength);
18112 strength
18113}
18114
18115fn uncached_cpp_class_declaration_strength(
18116 analyzer: &CppGraphSource<'_>,
18117 candidate: &CodeUnit,
18118) -> CppClassDeclarationStrength {
18119 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18120 if facts
18121 .iter()
18122 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18123 {
18124 return CppClassDeclarationStrength::Full;
18125 }
18126 if facts
18127 .iter()
18128 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18129 {
18130 return CppClassDeclarationStrength::Forward;
18131 }
18132 }
18133 if let Some(cpp) = analyzer.cpp
18134 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18135 {
18136 return cpp_class_declaration_strength_in_tree(
18137 analyzer,
18138 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18139 candidate,
18140 prepared.source(),
18141 prepared.tree().root_node(),
18142 );
18143 }
18144 let Some(source) = analyzer.indexed_source(candidate.source()) else {
18145 return CppClassDeclarationStrength::Unknown;
18146 };
18147 let mut parser = Parser::new();
18148 if parser
18149 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18150 .is_err()
18151 {
18152 return CppClassDeclarationStrength::Unknown;
18153 }
18154 let Some(tree) = parser.parse(&source, None) else {
18155 return CppClassDeclarationStrength::Unknown;
18156 };
18157 let recovered_export_classes =
18160 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18161 cpp_class_declaration_strength_in_tree(
18162 analyzer,
18163 &recovered_export_classes,
18164 candidate,
18165 &source,
18166 tree.root_node(),
18167 )
18168}
18169
18170fn cpp_class_declaration_strength_in_tree(
18171 analyzer: &CppGraphSource<'_>,
18172 recovered_export_classes: &CppRecoveredExportClassIndex,
18173 candidate: &CodeUnit,
18174 source: &str,
18175 root: Node<'_>,
18176) -> CppClassDeclarationStrength {
18177 let ranges = analyzer.ranges(candidate);
18178 let mut saw_forward = false;
18179 for range in ranges {
18180 match recovered_class_body_at(
18183 recovered_export_classes,
18184 root,
18185 source,
18186 candidate.identifier(),
18187 &range,
18188 ) {
18189 Some(true) => return CppClassDeclarationStrength::Full,
18190 Some(false) => {
18191 saw_forward = true;
18192 continue;
18193 }
18194 None => {}
18195 }
18196 let covers_range_start = |node: &Node<'_>| {
18203 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18204 };
18205 let mut stack = Vec::new();
18206 if covers_range_start(&root) {
18207 stack.push(root);
18208 }
18209 while let Some(node) = stack.pop() {
18210 if node.start_byte() == range.start_byte
18211 && node.end_byte() == range.end_byte
18212 && matches!(
18213 node.kind(),
18214 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18215 )
18216 {
18217 if cpp_class_node_has_body(node) {
18218 return CppClassDeclarationStrength::Full;
18219 }
18220 saw_forward = true;
18221 }
18222 let mut cursor = node.walk();
18223 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18224 }
18225 }
18226 if saw_forward {
18227 CppClassDeclarationStrength::Forward
18228 } else {
18229 CppClassDeclarationStrength::Unknown
18230 }
18231}
18232
18233fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18234 child_by_field_or_named_child_of_kinds(
18235 node,
18236 "body",
18237 &[
18238 "declaration_list",
18239 "field_declaration_list",
18240 "enumerator_list",
18241 ],
18242 )
18243 .is_some()
18244}
18245
18246#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18247enum CppCTagKind {
18248 Struct,
18249 Union,
18250}
18251
18252fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18253 let declaration = analyzer.get_source(code_unit, false)?;
18254 let mut parser = Parser::new();
18255 parser
18256 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18257 .ok()?;
18258 let tree = parser.parse(&declaration, None)?;
18259 let mut stack = vec![tree.root_node()];
18260 while let Some(node) = stack.pop() {
18261 let kind = match node.kind() {
18262 "struct_specifier" => CppCTagKind::Struct,
18263 "union_specifier" => CppCTagKind::Union,
18264 _ => {
18265 let mut cursor = node.walk();
18266 stack.extend(node.named_children(&mut cursor));
18267 continue;
18268 }
18269 };
18270 if node
18271 .child_by_field_name("name")
18272 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18273 {
18274 return Some(kind);
18275 }
18276 let mut cursor = node.walk();
18277 stack.extend(node.named_children(&mut cursor));
18278 }
18279 None
18280}
18281
18282pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18283 if !code_unit.owner_is_type_scope() {
18284 return None;
18285 }
18286 let owner_fq = code_unit.fq().parent()?;
18287 ctx.analyzer
18288 .workspace_definitions()
18289 .exact(&owner_fq)
18290 .into_iter()
18291 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18292}
18293
18294pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18295 same_logical_symbol(left, right) && left.source() == right.source()
18296}
18297
18298pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18299 same_symbol(left, right) || same_logical_symbol(left, right)
18300}
18301
18302pub fn same_visible_global_field_symbol(
18303 analyzer: &CppGraphSource<'_>,
18304 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18305 left: &CodeUnit,
18306 right: &CodeUnit,
18307) -> bool {
18308 if same_symbol(left, right) {
18309 return true;
18310 }
18311 if !same_logical_symbol(left, right) {
18312 return false;
18313 }
18314 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18315 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18316 {
18317 left.source() == right.source()
18318 } else {
18319 true
18320 }
18321}
18322
18323fn cpp_global_field_has_internal_linkage_cached(
18324 analyzer: &CppGraphSource<'_>,
18325 cache: &mut HashMap<CodeUnit, bool>,
18326 candidate: &CodeUnit,
18327) -> bool {
18328 if let Some(internal) = cache.get(candidate) {
18329 return *internal;
18330 }
18331 #[cfg(any(test, feature = "test-support"))]
18332 note_cpp_global_field_internal_linkage_classification_for_test();
18333 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18334 cache.insert(candidate.clone(), internal);
18335 internal
18336}
18337
18338#[cfg(any(test, feature = "test-support"))]
18339thread_local! {
18340 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18341}
18342
18343#[cfg(any(test, feature = "test-support"))]
18344fn note_cpp_global_field_internal_linkage_classification_for_test() {
18345 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18346 count.set(count.get() + 1);
18347 });
18348}
18349
18350#[cfg(any(test, feature = "test-support"))]
18351pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18352 body: impl FnOnce() -> T,
18353) -> (T, usize) {
18354 with_thread_local_counter_for_test(
18355 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18356 body,
18357 )
18358}
18359
18360#[cfg(any(test, feature = "test-support"))]
18361fn with_thread_local_counter_for_test<T>(
18362 counter: &'static std::thread::LocalKey<Cell<usize>>,
18363 body: impl FnOnce() -> T,
18364) -> (T, usize) {
18365 counter.with(|count| {
18366 count.set(0);
18367 let result = body();
18368 let observed = count.get();
18369 count.set(0);
18370 (result, observed)
18371 })
18372}
18373
18374pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18375 left.kind() == right.kind()
18376 && left.fq_name() == right.fq_name()
18377 && left.signature() == right.signature()
18378}
18379
18380pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18383 candidates: &[&'a CodeUnit],
18384 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18385) -> Option<&'a CodeUnit> {
18386 let first = *candidates.first()?;
18387 if candidates.len() == 1 {
18388 return Some(first);
18389 }
18390 let first_target = structured_target(first)?;
18391 candidates
18392 .iter()
18393 .skip(1)
18394 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18395 .then_some(first)
18396}
18397
18398pub fn cpp_global_field_has_internal_linkage(
18399 analyzer: &CppGraphSource<'_>,
18400 candidate: &CodeUnit,
18401) -> bool {
18402 if !candidate.is_field() || candidate.short_name().contains('.') {
18403 return false;
18404 }
18405 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18406 return false;
18407 };
18408 match local_linkage {
18409 CppFieldLinkage::Internal => true,
18410 CppFieldLinkage::External => false,
18411 CppFieldLinkage::InternalUnlessExternalPeer => {
18412 !cpp_global_field_linkage_peers(analyzer, candidate)
18413 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18414 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18415 }
18416 }
18417}
18418
18419fn cpp_global_field_linkage_peers<'a>(
18420 analyzer: &CppGraphSource<'a>,
18421 candidate: &'a CodeUnit,
18422) -> impl Iterator<Item = CodeUnit> + 'a {
18423 let name = candidate.fq().clone();
18424 analyzer
18425 .workspace_definitions()
18426 .exact(&name)
18427 .into_iter()
18428 .filter(move |peer| {
18429 if peer == candidate {
18430 return false;
18431 }
18432 #[cfg(any(test, feature = "test-support"))]
18433 note_cpp_global_field_linkage_peer_inspection_for_test();
18434 same_logical_symbol(peer, candidate)
18435 })
18436}
18437
18438#[cfg(any(test, feature = "test-support"))]
18439thread_local! {
18440 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18441}
18442
18443#[cfg(any(test, feature = "test-support"))]
18444fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18445 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18446 count.set(count.get() + 1);
18447 });
18448}
18449
18450#[cfg(any(test, feature = "test-support"))]
18451pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18452 body: impl FnOnce() -> T,
18453) -> (T, usize) {
18454 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18455}
18456
18457fn cpp_global_field_declaration_linkage(
18458 analyzer: &CppGraphSource<'_>,
18459 candidate: &CodeUnit,
18460) -> Option<CppFieldLinkage> {
18461 analyzer.cpp_field_linkage(candidate)
18462}
18463
18464pub fn cpp_canonical_alias_target_text(
18466 analyzer: &CppGraphSource<'_>,
18467 unit: &CodeUnit,
18468) -> Option<String> {
18469 let facts = analyzer.declaration_source_properties(unit)?;
18470 let first = facts.first()?.alias_target_text.as_ref()?;
18471 facts
18472 .iter()
18473 .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18474 .then(|| first.clone())
18475}
18476
18477#[cfg(test)]
18478mod tests {
18479 #[test]
18480 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18481 fn issue_3089_statement_formal_at_end_of_replacement() {
18482 let parameters = vec!["handle".to_owned(), "block".to_owned()];
18483 for replacement in [
18484 "do { header_event_t* event; if ((handle)->active) block } while (0)",
18485 "do { header_event_t* event; block } while (0)",
18486 ] {
18487 assert!(
18488 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
18489 .is_some(),
18490 "{replacement}"
18491 );
18492 }
18493 }
18494 use super::*;
18495 use brokk_bifrost_test_macros::hardwon;
18496
18497 #[test]
18498 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18499 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18500 let source = "int size(void) { return sizeof(((Payload))); }\n";
18501 let mut parser = Parser::new();
18502 parser
18503 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18504 .expect("C++ grammar");
18505 let tree = parser.parse(source, None).expect("fixture tree");
18506 let start = source.find("Payload").expect("sizeof operand");
18507 let node = tree
18508 .root_node()
18509 .named_descendant_for_byte_range(start, start + "Payload".len())
18510 .expect("focused operand");
18511 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18512 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18513
18514 assert_eq!(node.kind(), "identifier");
18515 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18516 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18517 }
18518
18519 fn parse_cpp(source: &str) -> Tree {
18520 crate::diagnostics::parse_cpp_tree(source)
18521 }
18522
18523 #[test]
18524 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18525 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18526 let source = "auto value = new ns::Box<int> [ 3 ];";
18527 let tree = parse_cpp(source);
18528 let mut pending = vec![tree.root_node()];
18529 let allocation = loop {
18530 let node = pending.pop().expect("new expression in fixture");
18531 if node.kind() == "new_expression" {
18532 break node;
18533 }
18534 let mut cursor = node.walk();
18535 pending.extend(
18536 node.named_children(&mut cursor)
18537 .collect::<Vec<_>>()
18538 .into_iter()
18539 .rev(),
18540 );
18541 };
18542
18543 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18544 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18545 assert_eq!(node_text(type_node, source), "ns::Box<int>");
18546 assert_eq!(
18547 allocation
18548 .child_by_field_name("declarator")
18549 .expect("array declarator")
18550 .kind(),
18551 "new_declarator"
18552 );
18553
18554 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18555 let old_split = old_split
18556 .strip_prefix("new ")
18557 .unwrap_or(old_split.as_str())
18558 .split(['(', '{'])
18559 .next()
18560 .expect("old split result");
18561 assert_ne!(old_split.trim(), node_text(type_node, source));
18562 }
18563
18564 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18565 let start = source.find(needle).expect("fixture needle");
18566 tree.root_node()
18567 .named_descendant_for_byte_range(start, start + needle.len())
18568 .expect("node at needle")
18569 }
18570
18571 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18572 let tree = crate::diagnostics::parse_cpp_tree(source);
18573 PreparedSyntaxTree::new(
18574 PreparedSyntaxSource::Exact(Arc::from(source)),
18575 tree,
18576 compute_line_starts(source),
18577 LanguageDialect::Standard(Language::Cpp),
18578 PreparedSourceOrigin::Disk,
18579 None,
18580 )
18581 }
18582
18583 fn unresolved_include_before(source: &str, reference: &str) -> bool {
18584 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18585 let prepared = prepared_cpp(source);
18586 let facts = collect_structured_include_facts(&prepared);
18587 let include_targets = IncludeTargetIndex::build([&file]);
18588 has_unresolved_include_visible_before_in_prepared(
18589 &file,
18590 &prepared,
18591 &include_targets,
18592 &facts,
18593 source.find(reference).expect("reference fixture"),
18594 )
18595 }
18596
18597 #[test]
18598 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18599 fn unresolved_include_before_reference_is_visible() {
18600 let source = "#include \"missing.h\"\nint use = Missing;\n";
18601 assert!(unresolved_include_before(source, "Missing"));
18602 }
18603
18604 #[test]
18605 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18606 fn unresolved_include_after_reference_is_not_visible() {
18607 let source = "int use = Missing;\n#include \"missing.h\"\n";
18608 assert!(!unresolved_include_before(source, "Missing"));
18609 }
18610
18611 #[test]
18612 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18613 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18614 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18615 assert!(!unresolved_include_before(source, "Missing"));
18616 }
18617
18618 #[test]
18619 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18620 fn unresolved_include_in_current_branch_is_visible() {
18621 let source =
18622 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18623 assert!(unresolved_include_before(source, "Missing"));
18624 }
18625
18626 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18631namespace matchers {
18632 namespace detail {
18633 class API [[nodiscard]] First {
18634 public:
18635 int value() const { return count_ + 1; }
18636 private:
18637 int count_;
18638 };
18639 class API [[nodiscard]] Second {
18640 public:
18641 int value() const { return count_ + 2; }
18642 private:
18643 int count_;
18644 };
18645 } // namespace detail
18646
18647 template <typename T>
18648 void tail_function(MatcherBase<T> const& value);
18649
18650 class TailClass {};
18651} // namespace matchers
18652} // namespace app
18653
18654struct AfterAll {};
18655"#;
18656
18657 #[test]
18658 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18659 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18660 let source = STOLEN_BRACE_CASCADE;
18661 let tree = parse_cpp(source);
18662 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18663
18664 let tail_class = named_node_at(&tree, source, "TailClass");
18665 assert!(
18666 !has_ancestor_kind(tail_class, "namespace_definition"),
18667 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18668 );
18669 let displaced = named_node_at(&tree, source, "Second");
18670 assert_eq!(
18671 enclosing_namespace_components(displaced, source),
18672 Some(vec!["app".to_string(), "matchers".to_string()]),
18673 "the fixture must displace the second class out of detail"
18674 );
18675
18676 let components = |needle: &str| {
18677 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18678 };
18679 assert_eq!(components("First"), ["app", "matchers", "detail"]);
18680 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18681 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18682 assert_eq!(components("tail_function"), ["app", "matchers"]);
18683 assert_eq!(components("TailClass"), ["app", "matchers"]);
18684 assert!(components("AfterAll").is_empty());
18685 }
18686
18687 #[test]
18688 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18689 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18690 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18691 let tree = parse_cpp(clean);
18692 assert!(!tree.root_node().has_error());
18693 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18694
18695 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18698 let tree = parse_cpp(damaged);
18699 assert!(tree.root_node().has_error());
18700 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18701 assert_eq!(
18702 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18703 ["a"]
18704 );
18705 }
18706
18707 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18714
18715 class Target {
18716 int value_;
18717 };
18718
18719 template <typename T>
18720 class Holder {
18721 public:
18722 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18723
18724#define HOLDER_DEFINE_OP( id, op ) \
18725 template <typename U> \
18726 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
18727 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
18728 return Target{}; \
18729 }
18730
18731 HOLDER_DEFINE_OP( equal, == )
18732#undef HOLDER_DEFINE_OP
18733 T m_lhs;
18734 };
18735
18736 class Tail {};
18737}
18738"#;
18739
18740 #[test]
18741 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
18742 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18743 let source = COLLAPSED_NAMESPACE_HEAD;
18744 let tree = parse_cpp(source);
18745 let target = named_node_at(&tree, source, "class Target");
18746
18747 assert!(
18748 !has_ancestor_kind(target, "namespace_definition"),
18749 "the fixture must reproduce the collapse: the class has no namespace ancestor"
18750 );
18751 let head = target.parent().expect("the collapsed namespace envelope");
18752 assert_eq!(
18753 head.kind(),
18754 "ERROR",
18755 "the fixture must keep the namespace head in an ERROR node"
18756 );
18757
18758 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18759 assert_eq!(
18760 index.enclosing_namespace_components(target, source),
18761 ["app"]
18762 );
18763 assert_eq!(
18764 index.enclosing_namespace_levels(target, source),
18765 vec![(
18766 "app".to_string(),
18767 Range {
18768 start_byte: 0,
18769 end_byte: source.len() - 1,
18770 start_line: 1,
18771 end_line: 25,
18772 },
18773 Range {
18774 start_byte: 10,
18775 end_byte: 13,
18776 start_line: 1,
18777 end_line: 1,
18778 },
18779 )],
18780 "the recovered namespace owns its full construct, not its first member or just its header"
18781 );
18782 }
18783
18784 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
18793
18794namespace app {
18795namespace detail {
18796
18797bool check(bool active) {
18798 APP_IF_ACTIVE
18799 return true;
18800 }
18801 return false;
18802}
18803
18804class Tail {};
18805
18806} // namespace detail
18807} // namespace app
18808"#;
18809
18810 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
18817namespace compile_time {
18818namespace number_parsing {
18819consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
18820 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
18821 auto digit = [](uint8_t c) -> uint32_t {
18822 }
18823 }
18824 if (a) {
18825 while (a) {
18826 }
18827 }
18828 switch (c) {
18829 case '{': {
18830 }
18831 }
18832 }
18833 while (a) {
18834 cursor += field.second;
18835 if (a) {
18836 case '{': {
18837 if (a) {
18838 }
18839 }
18840 case 'n': {
18841 if (a) {
18842 }
18843 }
18844 }
18845 }
18846 if (a) {
18847 simdjson_consteval_error("Expected '}'");
18848 }
18849}
18850}
18851}
18852"#;
18853
18854 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
18856 let mut stack = vec![root];
18857 let mut counts = (0, 0);
18858 while let Some(node) = stack.pop() {
18859 match (node.kind(), node.is_missing()) {
18860 ("{", false) => counts.0 += 1,
18861 ("}", false) => counts.1 += 1,
18862 _ => {}
18863 }
18864 let mut cursor = node.walk();
18865 stack.extend(node.children(&mut cursor));
18866 }
18867 counts
18868 }
18869
18870 fn namespace_open_byte(source: &str, head: &str) -> usize {
18872 let at = source.find(head).expect("fixture namespace head");
18873 at + head.len() - 1
18874 }
18875
18876 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
18877 let index = OrphanedNamespaceScopeIndex::build(root, source);
18878 for head in heads {
18879 assert_eq!(
18880 index.matching_close_brace(namespace_open_byte(source, head)),
18881 None,
18882 "{head} must not claim a close belonging to an inner scope"
18883 );
18884 }
18885 }
18886
18887 #[test]
18888 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
18889 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
18890 let source = MACRO_OPENED_SCOPE;
18891 let tree = parse_cpp(source);
18892 let root = tree.root_node();
18893 assert_eq!(
18894 real_brace_token_counts(root),
18895 (4, 5),
18896 "the fixture must reproduce the loss: one scope the source opens \
18897 has no `{{` token, so the stream holds an extra real close"
18898 );
18899
18900 assert_namespace_heads_have_no_matching_close(
18901 root,
18902 source,
18903 &["namespace app {", "namespace detail {"],
18904 );
18905 }
18906
18907 #[test]
18908 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
18909 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
18910 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
18914 let tree = parse_cpp(&source);
18915 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
18916 let after_all = named_node_at(&tree, &source, "struct AfterAll");
18917 assert!(
18918 index
18919 .enclosing_namespace_components(after_all, &source)
18920 .is_empty()
18921 );
18922 }
18923
18924 #[test]
18925 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
18926 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
18927 let source = REFLECTION_CASE_LABEL_RECOVERY;
18928 let tree = parse_cpp(source);
18929 let root = tree.root_node();
18930 let mut stack = vec![root];
18931 let mut mis_lexed_opens = Vec::new();
18932 while let Some(node) = stack.pop() {
18933 if node.kind() == "character"
18934 && &source[node.start_byte()..node.end_byte()] == "{"
18935 && node
18936 .parent()
18937 .is_some_and(|parent| parent.kind() != "char_literal")
18938 {
18939 mis_lexed_opens.push(node.start_position().row + 1);
18940 }
18941 let mut cursor = node.walk();
18942 stack.extend(node.children(&mut cursor));
18943 }
18944 assert!(
18945 mis_lexed_opens.is_empty(),
18946 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
18947 the old ABI14 mis-lex is no longer a valid recovery witness"
18948 );
18949 assert_eq!(
18950 real_brace_token_counts(root),
18951 (17, 17),
18952 "the improved parse retains balanced real brace tokens"
18953 );
18954 assert_eq!(root.kind(), "translation_unit");
18955 assert!(root.has_error(), "the malformed body remains incomplete");
18956 let index = OrphanedNamespaceScopeIndex::build(root, source);
18957 assert_eq!(
18958 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
18959 None,
18960 "a missing outer close must not borrow an inner namespace's real close"
18961 );
18962 for (head, close) in [
18963 ("namespace compile_time {", source.len() - 2),
18964 ("namespace number_parsing {", source.len() - 4),
18965 ] {
18966 assert_eq!(
18967 index
18968 .matching_close_brace(namespace_open_byte(source, head))
18969 .map(|range| range.start_byte),
18970 Some(close),
18971 "{head} retains its own real close"
18972 );
18973 }
18974 }
18975
18976 #[test]
18977 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
18978 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
18979 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
18980 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
18981 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
18982 assert!(indexed_namespace_path_is_recoverable(
18983 &["cache".to_string()],
18984 &indexed,
18985 1,
18986 ));
18987 }
18988
18989 #[test]
18990 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
18991 fn sort_lookup_units_totally_orders_every_identity_field() {
18992 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
18993 let base = CodeUnit::with_signature(
18994 file.clone(),
18995 CodeUnitType::Function,
18996 "scope",
18997 "value",
18998 Some("()".to_string()),
18999 false,
19000 );
19001 let different_kind = CodeUnit::with_signature(
19002 file.clone(),
19003 CodeUnitType::Field,
19004 "scope",
19005 "value",
19006 Some("()".to_string()),
19007 false,
19008 );
19009 let synthetic = base.with_synthetic(true);
19010
19011 let interner = segment_interner();
19012 let mut member_fq = FqName::new();
19013 member_fq.push(interner.intern("scope", SegmentKind::Package));
19014 member_fq.push(interner.intern("value", SegmentKind::Member));
19015 let different_package_boundary = CodeUnit::from_fq(
19016 file.clone(),
19017 CodeUnitType::Function,
19018 member_fq,
19019 0,
19020 Some("()".to_string()),
19021 false,
19022 );
19023
19024 let mut unknown_fq = FqName::new();
19025 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19026 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19027 let different_segment_kind = CodeUnit::from_fq(
19028 file,
19029 CodeUnitType::Function,
19030 unknown_fq,
19031 1,
19032 Some("()".to_string()),
19033 false,
19034 );
19035
19036 let input = vec![
19037 base,
19038 different_kind,
19039 synthetic,
19040 different_package_boundary,
19041 different_segment_kind,
19042 ];
19043 let mut expected = input.clone();
19044 sort_lookup_units(&mut expected);
19045 assert!(expected.windows(2).all(|pair| {
19046 let mut ordered = pair.to_vec();
19047 sort_lookup_units(&mut ordered);
19048 ordered == pair && pair[0] != pair[1]
19049 }));
19050
19051 let mut reversed = input.clone();
19052 reversed.reverse();
19053 sort_lookup_units(&mut reversed);
19054 assert_eq!(reversed, expected);
19055
19056 let mut rotated = input;
19057 rotated.rotate_left(2);
19058 sort_lookup_units(&mut rotated);
19059 assert_eq!(rotated, expected);
19060 }
19061
19062 #[test]
19063 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19064 fn structured_conditional_array_bound_preserves_the_real_guard() {
19065 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";
19066 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19067 let parse = |source: &str| {
19068 let mut parser = Parser::new();
19069 parser
19070 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19071 .expect("C++ grammar");
19072 parser.parse(source, None).expect("fixture tree")
19073 };
19074
19075 let tree = parse(damaged);
19076 let root = tree.root_node();
19077 assert!(
19078 !root.has_error(),
19079 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19080 root.to_sexp()
19081 );
19082 let target = damaged.find("target").expect("target byte");
19083 let declaration = root
19084 .descendant_for_byte_range(target, target + "target".len())
19085 .and_then(|mut node| {
19086 loop {
19087 if node.kind() == "declaration" {
19088 break Some(node);
19089 }
19090 node = node.parent()?;
19091 }
19092 })
19093 .expect("declaration after the displaced terminator");
19094 let outer = declaration
19095 .parent()
19096 .filter(|node| node.kind() == "preproc_ifdef")
19097 .expect("ordinary outer include guard");
19098 let bound = outer
19099 .named_children(&mut outer.walk())
19100 .filter(|node| node.kind() == "declaration")
19101 .filter_map(|node| node.child_by_field_name("declarator"))
19102 .find(|node| node.kind() == "array_declarator")
19103 .and_then(|node| node.child_by_field_name("size"))
19104 .expect("structured conditional array bound");
19105 assert_eq!(bound.kind(), "preproc_array_size");
19106 let conditional = bound
19107 .named_children(&mut bound.walk())
19108 .find(|node| node.kind() == "preproc_ifdef")
19109 .expect("array-size conditional");
19110 assert!(conditional.end_byte() <= declaration.start_byte());
19111 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19112 assert!(
19113 !std::iter::successors(Some(declaration), |node| node.parent())
19114 .any(|ancestor| same_node(ancestor, conditional)),
19115 "the declaration must not inherit the array-size feature guard"
19116 );
19117 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19118 assert!(preprocessor_conditional_contains_descendant(
19119 outer,
19120 declaration
19121 ));
19122
19123 let tree = parse(guarded);
19124 let conditional = tree
19125 .root_node()
19126 .named_child(0)
19127 .filter(|node| node.kind() == "preproc_ifdef")
19128 .expect("ordinary conditional");
19129 let declaration = conditional
19130 .named_children(&mut conditional.walk())
19131 .find(|node| node.kind() == "declaration")
19132 .expect("guarded declaration");
19133 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19134 assert!(preprocessor_conditional_contains_descendant(
19135 conditional,
19136 declaration
19137 ));
19138
19139 let damaged_alternative = format!(
19140 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19141 "UNUSED(value)\n".repeat(64)
19142 );
19143 let tree = parse(&damaged_alternative);
19144 let conditional = tree
19145 .root_node()
19146 .named_child(0)
19147 .filter(|node| node.kind() == "preproc_ifdef")
19148 .expect("outer conditional with an alternative");
19149 assert!(conditional.has_error());
19150 assert!(conditional.child_by_field_name("alternative").is_some());
19151 assert!(
19152 conditional
19153 .child(conditional.child_count() - 1)
19154 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19155 );
19156 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19157
19158 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";
19159 let tree = parse(split_declaration);
19160 let root = tree.root_node();
19161 let conditional = root
19162 .named_children(&mut root.walk())
19163 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
19164 .expect("split declaration conditional");
19165 let target = split_declaration
19166 .find("static int target")
19167 .expect("target byte");
19168 let boundary =
19169 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
19170 assert!(boundary.end_byte <= target, "{boundary:?}");
19171 assert_eq!(boundary.end_line, 9, "{boundary:?}");
19172 let target_node = root
19173 .descendant_for_byte_range(target, target + "static".len())
19174 .expect("target node");
19175 assert!(!preprocessor_conditional_contains_descendant(
19176 conditional,
19177 target_node
19178 ));
19179 }
19180
19181 #[test]
19182 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19183 fn fragmented_reference_guard_is_recovered() {
19184 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";
19185 let mut parser = Parser::new();
19186 parser
19187 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19188 .expect("C++ grammar");
19189 let tree = parser.parse(source, None).expect("fixture tree");
19190 let start = source.rfind("helper").expect("reference byte");
19191 let node = tree
19192 .root_node()
19193 .descendant_for_byte_range(start, start + "helper".len())
19194 .expect("reference node");
19195 let mut expected = HashSet::default();
19196 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19197 vec![
19198 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19199 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19200 ],
19201 )));
19202 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19203 }
19204
19205 #[test]
19206 fn expression_defined_and_ifndef_guards_are_incompatible() {
19207 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19208 let mut parser = Parser::new();
19209 parser
19210 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19211 .expect("C++ grammar");
19212 let tree = parser.parse(source, None).expect("fixture tree");
19213 let root = tree.root_node();
19214 let selected_start = source.find("selected").expect("selected declaration");
19215 let rejected_start = source.find("rejected").expect("rejected declaration");
19216 let selected = root
19217 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19218 .expect("selected node");
19219 let rejected = root
19220 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19221 .expect("rejected node");
19222 let selected_guards =
19223 preprocessor_guard_environment(selected, source).expect("selected guards");
19224 let rejected_guards =
19225 preprocessor_guard_environment(rejected, source).expect("rejected guards");
19226
19227 assert!(
19228 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19229 "opposite spellings of one macro guard must contradict"
19230 );
19231 }
19232
19233 #[test]
19234 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19235 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19236 let source = r#"#ifdef _WIN32
19237#if defined(__cplusplus)
19238extern "C"
19239#endif
19240int platform_api(void);
19241#endif
19242
19243#ifdef _WIN32
19244static int entropy_target(void) { return 0; }
19245#else
19246#ifdef HAVE_COMMON_RANDOM
19247static int other_target(void) { return 0; }
19248#elif defined(HAVE_GETENTROPY)
19249static int entropy_target(void) { return 1; }
19250static int use_entropy(void) { return entropy_target(); }
19251#endif
19252#endif
19253"#;
19254 let mut parser = Parser::new();
19255 parser
19256 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19257 .expect("C++ grammar");
19258 let tree = parser.parse(source, None).expect("fixture tree");
19259 let start = source.rfind("entropy_target()").expect("reference");
19260 let node = tree
19261 .root_node()
19262 .descendant_for_byte_range(start, start + "entropy_target".len())
19263 .expect("reference node");
19264 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19265 assert!(
19266 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19267 "{guards:#?}"
19268 );
19269 assert!(
19270 guards.contains(&PreprocessorGuard::Undefined(
19271 "HAVE_COMMON_RANDOM".to_string()
19272 )),
19273 "{guards:#?}"
19274 );
19275 assert!(
19276 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19277 "{guards:#?}"
19278 );
19279 assert!(
19280 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
19281 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
19282 );
19283 }
19284
19285 #[test]
19286 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
19287 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
19288 let source = "#define KEY 42\n#ifdef ENABLE_KEYS\nint classify(int value) {\n switch (value) {\n case KEY: return 1;\n default: return 0;\n }\n}\n#endif\n";
19289 let mut parser = Parser::new();
19290 parser
19291 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19292 .expect("C++ grammar");
19293 let tree = parser.parse(source, None).expect("fixture tree");
19294 let root = tree.root_node();
19295 let node_at = |text: &str, start: usize| {
19296 root.descendant_for_byte_range(start, start + text.len())
19297 .expect("token node")
19298 };
19299
19300 let key_start = source.find("case KEY").expect("case label") + "case ".len();
19301 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
19302 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
19303 assert!(!is_ordinary_macro_reference_node(node_at(
19304 "ENABLE_KEYS",
19305 guard_start,
19306 )));
19307 }
19308
19309 #[test]
19310 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19311 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
19312 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";
19313 let mut parser = Parser::new();
19314 parser
19315 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19316 .expect("C++ grammar");
19317 let tree = parser.parse(source, None).expect("fixture tree");
19318 let root = tree.root_node();
19319 let definition_start = source.find("target(void)").expect("definition");
19320 let reference_start = source.rfind("target()").expect("reference");
19321 let definition = root
19322 .descendant_for_byte_range(definition_start, definition_start + "target".len())
19323 .expect("definition node");
19324 let reference = root
19325 .descendant_for_byte_range(reference_start, reference_start + "target".len())
19326 .expect("reference node");
19327 let required =
19328 preprocessor_guard_environment(definition, source).expect("definition guard");
19329 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19330 assert!(guard_requirements_hold_at_reference(
19331 &required,
19332 Some(&active)
19333 ));
19334 }
19335
19336 #[test]
19337 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19338 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19339 let source = "g_autoptr(FuChunkArray) self = make_array();";
19340 let mut parser = Parser::new();
19341 parser
19342 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19343 .expect("C++ grammar");
19344 let tree = parser.parse(source, None).expect("fixture tree");
19345 let statement = tree.root_node().named_child(0).expect("statement");
19346 let binding =
19347 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19348 assert_eq!(binding.name, "self");
19349 assert_eq!(binding.type_name, "FuChunkArray");
19350 assert_eq!(binding.pointer_depth, 1);
19351
19352 let near_miss = "holder(FuChunkArray) self = make_array();";
19353 let tree = parser.parse(near_miss, None).expect("near-miss tree");
19354 let statement = tree.root_node().named_child(0).expect("statement");
19355 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19356 }
19357
19358 #[test]
19359 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
19360 fn boolean_guard_normalization_proves_equivalence_and_implication() {
19361 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19362 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19363 let negated_windows_branch =
19364 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19365 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19366 assert_eq!(negated_windows_branch, portable);
19367
19368 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19369 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19370 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19371 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19372 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19373 assert!(fallback_branch.implies(&fallback_declaration));
19374 assert!(
19375 BooleanGuardExpression::Truthy("FEATURE".to_string())
19376 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19377 );
19378 assert!(
19379 BooleanGuardExpression::Undefined("FEATURE".to_string())
19380 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19381 );
19382 assert!(
19383 !BooleanGuardExpression::Defined("FEATURE".to_string())
19384 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19385 );
19386 assert!(!fallback_declaration.implies(&fallback_branch));
19387 }
19388
19389 #[test]
19390 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
19391 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19392 let source = "static int helper(const char *left, wchar_t *right) { return 0; }\nint caller(wchar_t *template) {\n if (1) {\n return helper(NULL, template); /* nested bound */\n }\n return helper(NULL, template); /* bound */\n}\nint lambda_scope(wchar_t *template) {\n return [=]() { return helper(NULL, template); }(); /* lambda scope */\n}\nint unbound(void) {\n return helper(NULL, template); /* unbound */\n}\n";
19393 let mut parser = Parser::new();
19394 parser
19395 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19396 .expect("C++ grammar");
19397 let tree = parser.parse(source, None).expect("fixture tree");
19398 let root = tree.root_node();
19399 let call = |marker: &str| {
19400 let start = source.find(marker).expect("call marker");
19401 let mut node = root
19402 .descendant_for_byte_range(start, start + "helper".len())
19403 .expect("call name node");
19404 loop {
19405 if node.kind() == "call_expression" {
19406 break node;
19407 }
19408 node = node.parent().expect("call expression ancestor");
19409 }
19410 };
19411 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19412 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19413 let keyword_call = call("helper(NULL, template); /* bound */");
19414 let keyword_arguments = keyword_call
19415 .child_by_field_name("arguments")
19416 .expect("keyword argument list");
19417 assert_eq!(
19418 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19419 1
19420 );
19421
19422 let nested_call = call("helper(NULL, template); /* nested bound */");
19423 let nested_arguments = nested_call
19424 .child_by_field_name("arguments")
19425 .expect("nested keyword argument list");
19426 assert_eq!(
19427 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
19428 1
19429 );
19430
19431 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
19432 let lambda_arguments = lambda_call
19433 .child_by_field_name("arguments")
19434 .expect("lambda keyword argument list");
19435 assert_eq!(
19436 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
19437 0,
19438 "a lambda body must not borrow the enclosing collapsed C parameter"
19439 );
19440
19441 assert_eq!(
19442 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19443 0
19444 );
19445
19446 let unbound_call = call("helper(NULL, template); /* unbound */");
19447 let unbound_arguments = unbound_call
19448 .child_by_field_name("arguments")
19449 .expect("unbound argument list");
19450 assert_eq!(
19451 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19452 0
19453 );
19454 }
19455
19456 #[test]
19457 #[brokk_bifrost_test_macros::hardwon(
19458 "#4407: collapsed C keyword parameters need structural position proof"
19459 )]
19460 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
19461 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
19462int type_keyword(template *value) {
19463 return helper(NULL, template); /* type */
19464}
19465int qualifier_keyword(const *template) {
19466 return helper(NULL, template); /* qualifier */
19467}
19468int annotation_keyword(wchar_t *value __attribute__((template))) {
19469 return helper(NULL, template); /* annotation */
19470}
19471int default_keyword(wchar_t *value = template) {
19472 return helper(NULL, template); /* default */
19473}
19474int nested_expression(wchar_t *value, (template)) {
19475 return helper(NULL, template); /* nested */
19476}
19477int malformed_head = (wchar_t *template) {
19478 return helper(NULL, template); /* malformed */
19479};
19480"#;
19481 let tree = parse_cpp(source);
19482 let root = tree.root_node();
19483 let call = |marker: &str| {
19484 let start = source.find(marker).expect("call marker");
19485 let mut node = root
19486 .descendant_for_byte_range(start, start + "helper".len())
19487 .expect("call name node");
19488 loop {
19489 if node.kind() == "call_expression" {
19490 break node;
19491 }
19492 node = node.parent().expect("call expression ancestor");
19493 }
19494 };
19495 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
19496 for marker in [
19497 "helper(NULL, template); /* type */",
19498 "helper(NULL, template); /* qualifier */",
19499 "helper(NULL, template); /* annotation */",
19500 "helper(NULL, template); /* default */",
19501 "helper(NULL, template); /* nested */",
19502 "helper(NULL, template); /* malformed */",
19503 ] {
19504 let keyword_call = call(marker);
19505 let keyword_arguments = keyword_call
19506 .child_by_field_name("arguments")
19507 .expect("keyword argument list");
19508 assert_eq!(
19509 recovered_c_keyword_argument_count(
19510 &c_file,
19511 keyword_call,
19512 keyword_arguments,
19513 source,
19514 ),
19515 0,
19516 "keyword in {marker} must not prove a parameter binding"
19517 );
19518 }
19519 }
19520
19521 #[test]
19522 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19523 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19524 let source = r#"#define MAKE(type) type *value
19525MAKE(int *);
19526typedef struct Item Item;
19527struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19528void gen_op_movl(void *s, int first, int second) { }
19529const char *strZ(const char *value) { return value; }
19530int body(void *s) {
19531 MAKE(int *);
19532 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19533 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19534 execvp(strZ(value), UNCONSTIFY(char **, args));
19535}
19536 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19537 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19538int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19539 if (d->units) {
19540 switch (target->nodetype) {
19541 case 1:
19542 case 2:
19543 break;
19544 default:
19545 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19546 }
19547 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19548 lysdict_remove(ctx, ((struct Item *)target)->units);
19549 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19550 }
19551 return 0;
19552 }
19553STATIC EFI_STATUS Encode () { return 0; }
19554"#;
19555 let tree = parse_cpp(source);
19556 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19557 let top_macro = tree
19558 .root_node()
19559 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19560 .expect("top macro node");
19561 let body_macro_start = source
19562 .match_indices("MAKE(int *);")
19563 .nth(1)
19564 .expect("body macro")
19565 .0;
19566 let body_macro = tree
19567 .root_node()
19568 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19569 .expect("body macro node");
19570 let function_call_start = source
19571 .find("gen_op_movl(s, offsetof(CPUX86State")
19572 .expect("function call");
19573 let function_call = tree
19574 .root_node()
19575 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19576 .expect("function call node");
19577 let strz_start = source.find("strZ(value)").expect("nested function call");
19578 let strz = tree
19579 .root_node()
19580 .named_descendant_for_byte_range(strz_start, strz_start + 4)
19581 .expect("nested function call node");
19582 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19583 let recovered_call = tree
19584 .root_node()
19585 .named_descendant_for_byte_range(
19586 recovered_call_start,
19587 recovered_call_start + "lysdict_remove".len(),
19588 )
19589 .expect("recovered call node");
19590 let binder_start = source.find("Encode").expect("binder");
19591 let binder = tree
19592 .root_node()
19593 .named_descendant_for_byte_range(binder_start, binder_start + 6)
19594 .expect("binder node");
19595
19596 assert!(recovered_c_function_declarator_invocation(top_macro));
19597 assert!(recovered_c_function_declarator_invocation(body_macro));
19598 assert!(recovered_c_function_declarator_invocation(function_call));
19599 assert!(recovered_c_function_declarator_invocation(strz));
19600 assert!(recovered_c_function_declarator_invocation(recovered_call));
19601 assert!(!recovered_c_function_declarator_invocation(binder));
19602 }
19603
19604 #[test]
19605 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19606 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19607 let source = r#"typedef int krb5_context;
19608int helper(int first, int second) { return first + second; }
19609static krb5_context ctx;
19610int main(int argc, char **argv) {
19611 int ccinitial;
19612 const char *collection_name, *typename;
19613 typename = helper(ctx, ccinitial);
19614 return 0;
19615}
19616"#;
19617 let tree = parse_cpp(source);
19618 let ctx = tree
19619 .root_node()
19620 .descendant_for_byte_range(
19621 source.find("ctx, ccinitial").expect("ctx argument"),
19622 source.find("ctx, ccinitial").expect("ctx argument") + 3,
19623 )
19624 .expect("ctx node");
19625 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19626 let ccinitial = tree
19627 .root_node()
19628 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19629 .expect("sibling node");
19630 let typename = named_node_at(&tree, source, "typename = helper");
19631 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19632
19633 assert_eq!(ctx.kind(), "identifier");
19634 assert!(recovered_c_parenthesized_declarator_reference(ctx));
19635 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19636 assert!(!recovered_c_parenthesized_declarator_reference(typename));
19637 assert!(!recovered_c_parenthesized_declarator_reference(helper));
19638 }
19639
19640 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19641 let mut parser = Parser::new();
19642 parser
19643 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19644 .expect("C++ grammar");
19645 let tree = parser.parse(source, None).expect("C++ fixture tree");
19646 let mut stack = vec![tree.root_node()];
19647 while let Some(node) = stack.pop() {
19648 if node.kind() == "enum_specifier" {
19649 return flattened_macro_namespace_components(node, source);
19650 }
19651 let mut cursor = node.walk();
19652 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19653 stack.extend(children.into_iter().rev());
19654 }
19655 None
19656 }
19657
19658 #[test]
19659 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
19660 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19661 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19662namespace detail
19663{
19664enum class value_t { null };
19665}
19666NLOHMANN_JSON_NAMESPACE_END
19667NLOHMANN_JSON_NAMESPACE_BEGIN
19668namespace next
19669{
19670struct next_type {};
19671}
19672NLOHMANN_JSON_NAMESPACE_END
19673"#;
19674 assert_eq!(
19675 first_enum_flattened_namespace(complete),
19676 Some(vec!["detail".to_string()])
19677 );
19678
19679 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19680 assert_eq!(
19681 first_enum_flattened_namespace(&stale_end),
19682 Some(vec!["detail".to_string()]),
19683 "a stale end marker before the begin marker must not replace the intended namespace"
19684 );
19685
19686 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19687namespace detail
19688{
19689enum class value_t { null };
19690}
19691struct next_type {};
19692"#;
19693 assert_eq!(first_enum_flattened_namespace(incomplete), None);
19694 }
19695}
19696
19697#[cfg(test)]
19713mod lookup_order_properties {
19714 use super::*;
19715 use proptest::prelude::*;
19716
19717 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19721 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19722 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19726 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19727 const KINDS: [CodeUnitType; 6] = [
19728 CodeUnitType::Class,
19729 CodeUnitType::Function,
19730 CodeUnitType::Field,
19731 CodeUnitType::Module,
19732 CodeUnitType::Macro,
19733 CodeUnitType::FileScope,
19734 ];
19735
19736 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19739 enum ProbedOrder {
19740 Before,
19741 Tied,
19742 After,
19743 Contradictory,
19746 }
19747
19748 impl ProbedOrder {
19749 fn mirror(self) -> Self {
19750 match self {
19751 ProbedOrder::Before => ProbedOrder::After,
19752 ProbedOrder::After => ProbedOrder::Before,
19753 other => other,
19754 }
19755 }
19756
19757 fn signum(self) -> i8 {
19759 match self {
19760 ProbedOrder::Before => -1,
19761 ProbedOrder::Tied => 0,
19762 ProbedOrder::After => 1,
19763 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19764 }
19765 }
19766 }
19767
19768 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
19776 if left == right {
19777 return ProbedOrder::Tied;
19780 }
19781 let mut forward = vec![left.clone(), right.clone()];
19782 sort_lookup_units(&mut forward);
19783 let mut backward = vec![right.clone(), left.clone()];
19784 sort_lookup_units(&mut backward);
19785 let left_first = backward[0] == *left;
19786 let right_first = forward[0] == *right;
19787 match (left_first, right_first) {
19788 (true, true) => ProbedOrder::Contradictory,
19789 (true, false) => ProbedOrder::Before,
19790 (false, true) => ProbedOrder::After,
19791 (false, false) => ProbedOrder::Tied,
19792 }
19793 }
19794
19795 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
19796 (
19797 0..ROOT_NAMES.len(),
19798 0..REL_PATHS.len(),
19799 0..KINDS.len(),
19800 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
19801 0..3usize,
19802 0..SIGNATURES.len(),
19803 any::<bool>(),
19804 )
19805 .prop_map(
19806 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
19807 let source = ProjectFile::new(
19808 std::env::temp_dir().join(ROOT_NAMES[root]),
19809 REL_PATHS[rel_path],
19810 );
19811 let interner = segment_interner();
19812 let mut fq = FqName::new();
19813 for (atom, segment_kind) in &segments {
19814 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
19815 }
19816 let package_segment_count = package_prefix % fq.len();
19818 CodeUnit::from_fq(
19819 source,
19820 KINDS[kind],
19821 fq,
19822 package_segment_count,
19823 SIGNATURES[signature].map(str::to_string),
19824 synthetic,
19825 )
19826 },
19827 )
19828 }
19829
19830 proptest! {
19831 #![proptest_config(ProptestConfig::with_cases(256))]
19832
19833 #[test]
19836 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19837 fn lookup_order_is_reflexive_and_dual(
19838 left in code_unit_strategy(),
19839 right in code_unit_strategy(),
19840 ) {
19841 prop_assert_eq!(
19842 probe_order(&left, &left),
19843 ProbedOrder::Tied,
19844 "a unit must tie with itself: {:?}",
19845 left
19846 );
19847 let forward = probe_order(&left, &right);
19848 prop_assert_ne!(
19849 forward,
19850 ProbedOrder::Contradictory,
19851 "comparator put each of these strictly first: left={:?} right={:?}",
19852 left,
19853 right
19854 );
19855 prop_assert_eq!(
19856 probe_order(&right, &left),
19857 forward.mirror(),
19858 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
19859 left,
19860 right
19861 );
19862 }
19863
19864 #[test]
19866 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19867 fn lookup_order_is_transitive(
19868 a in code_unit_strategy(),
19869 b in code_unit_strategy(),
19870 c in code_unit_strategy(),
19871 ) {
19872 let ab = probe_order(&a, &b);
19873 let bc = probe_order(&b, &c);
19874 let ac = probe_order(&a, &c);
19875 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
19876 prop_assert_ne!(
19877 probed,
19878 ProbedOrder::Contradictory,
19879 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
19880 pair,
19881 a,
19882 b,
19883 c
19884 );
19885 }
19886 if ab.signum() <= 0 && bc.signum() <= 0 {
19887 prop_assert!(
19888 ac.signum() <= 0,
19889 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
19890 a={:?} b={:?} c={:?}",
19891 ab,
19892 bc,
19893 ac,
19894 a,
19895 b,
19896 c
19897 );
19898 }
19899 }
19900
19901 #[test]
19904 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19905 fn lookup_order_separates_distinct_identities(
19906 left in code_unit_strategy(),
19907 right in code_unit_strategy(),
19908 ) {
19909 if probe_order(&left, &right) == ProbedOrder::Tied {
19910 prop_assert_eq!(
19911 &left,
19912 &right,
19913 "distinct identities tied, so their order is whatever order they \
19914 arrived in: left_segments={:?} right_segments={:?}",
19915 left.fq_segments_debug(),
19916 right.fq_segments_debug()
19917 );
19918 }
19919 }
19920
19921 #[test]
19924 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
19925 fn lookup_sort_is_permutation_invariant(
19926 units in prop::collection::vec(code_unit_strategy(), 1..=8),
19927 ) {
19928 let mut sorted = units.clone();
19929 sort_lookup_units(&mut sorted);
19930 for rotation in 0..units.len() {
19931 for reversed in [false, true] {
19932 let mut permuted = units.clone();
19933 permuted.rotate_left(rotation);
19934 if reversed {
19935 permuted.reverse();
19936 }
19937 sort_lookup_units(&mut permuted);
19938 prop_assert_eq!(
19939 &permuted,
19940 &sorted,
19941 "sorting a permutation gave a different list \
19942 (rotation={}, reversed={}): input={:?}",
19943 rotation,
19944 reversed,
19945 units
19946 );
19947 }
19948 }
19949 }
19950 }
19951}