1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3use crate::declarations::cpp_displaced_preprocessor_branch;
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
8 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
9 enclosing_cpp_namespace_nodes, macro_modifier_identifier, node_text, normalize_cpp_whitespace,
10 recovered_class_body_at, recovered_function_like_field_declarator,
11 recovered_pyobject_head_field,
12};
13use crate::graph::CppGraphSource;
14use crate::graph::extractor::ScanCtx;
15use crate::graph::syntax::{
16 SentinelInput, function_macro_replacement_span, object_macro_replacement_type_references,
17 parse_sentinel, qualified_name_parts,
18};
19use crate::graph_support::CppSource;
20use crate::imports::{
21 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
22};
23use crate::source_context::cpp_guard_set_to_runtime;
24use crate::structural::is_cpp_preprocessor_conditional;
25use crate::template_heads::{
26 CppTemplateBinding, CppTemplateContext, CppTemplateReconciliation,
27 cpp_declaration_template_context,
28};
29pub use brokk_bifrost_core::analyzer::cpp_facts::{
30 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
31};
32use brokk_bifrost_core::analyzer::cpp_facts::{
33 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
34 CppStructuredAliasTarget as StructuredAliasTarget,
35};
36use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
37use brokk_bifrost_core::analyzer::model::{
38 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
39 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
40 SignatureMetadata, StructuredTypeName,
41};
42use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
43use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
44#[cfg(test)]
45use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
46use brokk_bifrost_core::analyzer::query_token::QueryToken;
47use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
48use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
49use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
50use brokk_bifrost_core::analyzer::tree_walk::{
51 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
52 named_children_iter, node_for_exact_range, push_named_children_reversed,
53 single_named_child_matching,
54};
55use brokk_bifrost_core::analyzer::usages::common::same_node;
56use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
57use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
58use brokk_bifrost_core::cancellation::CancellationToken;
59use brokk_bifrost_core::hash::{HashMap, HashSet};
60#[cfg(test)]
61use brokk_bifrost_core::text_utils::compute_line_starts;
62use std::borrow::Cow;
63#[cfg(any(test, feature = "test-support"))]
64use std::cell::Cell;
65use std::cell::OnceCell;
66use std::cmp::Ordering as CmpOrdering;
67use std::collections::BTreeSet;
68use std::hash::Hash;
69use std::sync::atomic::{AtomicUsize, Ordering};
70use std::sync::{Arc, Mutex, OnceLock, RwLock};
71use std::time::{Duration, Instant};
72use tree_sitter::{Node, Parser, Tree};
73
74const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
75const CALLABLE_METADATA_CACHE_CAP: usize = 512;
76const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
77
78#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
79struct PreprocessorContextNodeKey {
80 id: usize,
81 start_byte: usize,
82 end_byte: usize,
83}
84
85#[derive(Default)]
94pub(crate) struct PreprocessorContextCache {
95 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
96 displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
97}
98
99#[derive(Clone, Copy)]
102struct DisplacedBranchDirective {
103 end_byte: usize,
104 is_else: bool,
105}
106
107impl PreprocessorContextCache {
108 fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
109 let key = PreprocessorContextNodeKey {
110 id: conditional.id(),
111 start_byte: conditional.start_byte(),
112 end_byte: conditional.end_byte(),
113 };
114 if let Some(directive) = self.displaced_branches.get(&key) {
115 return *directive;
116 }
117 let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
118 DisplacedBranchDirective {
119 end_byte: directive.end_byte(),
120 is_else: directive.kind() == "#else",
121 }
122 });
123 if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
124 self.displaced_branches.insert(key, directive);
125 }
126 directive
127 }
128
129 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
130 let key = PreprocessorContextNodeKey {
131 id: conditional.id(),
132 start_byte: conditional.start_byte(),
133 end_byte: conditional.end_byte(),
134 };
135 if let Some(boundary) = self.displaced_boundaries.get(&key) {
136 return *boundary;
137 }
138 let boundary =
139 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
140 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
141 self.displaced_boundaries.insert(key, boundary);
142 }
143 boundary
144 }
145}
146
147#[cfg(any(test, feature = "test-support"))]
148thread_local! {
149 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
150 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
151}
152
153#[derive(Clone, Copy, PartialEq, Eq)]
154pub enum TargetKind {
155 Type,
156 Constructor,
157 FreeFunction,
158 Method,
159 GlobalField,
160 MemberField,
161 Macro,
162}
163
164pub enum LexicalTypeResolution {
165 Resolved {
166 unit: CodeUnit,
167 components: Vec<String>,
168 candidates: Vec<CodeUnit>,
169 },
170 Ambiguous,
171 Missing,
172}
173
174#[derive(Clone, Copy)]
175enum TypeCandidateResolution<'a> {
176 Canonical,
177 PreserveAlias,
178 PreserveTarget(&'a CodeUnit),
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190enum TypeCandidateFailure {
191 Ambiguous,
192 Unresolvable,
193}
194
195impl TypeCandidateFailure {
196 fn lexical_resolution(self) -> LexicalTypeResolution {
197 match self {
198 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
199 Self::Unresolvable => LexicalTypeResolution::Missing,
200 }
201 }
202}
203
204pub enum LexicalCallableValueResolution {
205 Type(CodeUnit),
206 FreeFunction(CodeUnit),
207 Ambiguous,
208 Missing,
209}
210
211pub enum UsingEnumMemberResolution {
212 Resolved { owner: CodeUnit, member: CodeUnit },
213 Ambiguous,
214 Missing,
215}
216
217pub enum NamespaceValueResolution {
218 Resolved,
219 Ambiguous,
220 Missing,
221}
222
223#[derive(Clone, Debug, PartialEq, Eq)]
224pub enum OrdinaryMacroReferenceResolution {
225 Resolved(CodeUnit),
226 Ambiguous,
227 Missing,
228}
229
230#[derive(Clone, Debug, PartialEq, Eq)]
231pub enum RecoveredCReferenceRanges {
232 Complete(Vec<Range>),
233 LimitExceeded,
234}
235
236pub fn resolve_namespace_value(
237 analyzer: &CppGraphSource<'_>,
238 visibility: &VisibilityIndex<'_>,
239 file: &ProjectFile,
240 namespace: &str,
241 name: &str,
242 before_byte: usize,
243) -> NamespaceValueResolution {
244 let mut matches = Vec::new();
245 for candidate in visibility.visible_identifier_candidates(file, name) {
246 if type_owner_of(analyzer, candidate).is_some()
247 || candidate.package_name() != namespace
248 || (candidate.source() == file
249 && !analyzer
250 .ranges(candidate)
251 .iter()
252 .any(|range| range.start_byte < before_byte))
253 || matches
254 .iter()
255 .any(|existing| same_visible_symbol(existing, candidate))
256 {
257 continue;
258 }
259 matches.push(candidate.clone());
260 if matches.len() > 1 {
261 return NamespaceValueResolution::Ambiguous;
262 }
263 }
264 matches
265 .pop()
266 .map(|_| NamespaceValueResolution::Resolved)
267 .unwrap_or(NamespaceValueResolution::Missing)
268}
269
270pub(crate) struct ScopedUsingEnumOwners {
271 scopes: Vec<Vec<CodeUnit>>,
272}
273
274pub(crate) struct SemanticUsingEnumOwners {
279 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
280 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
281}
282
283pub(crate) enum SemanticUsingEnumMemberResolution {
284 Class(UsingEnumMemberResolution),
285 Namespace(UsingEnumMemberResolution),
286 Missing,
287}
288
289impl SemanticUsingEnumOwners {
290 pub(crate) fn new() -> Self {
291 Self {
292 class_imports: HashMap::default(),
293 namespace_imports: HashMap::default(),
294 }
295 }
296
297 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
298 let imports = self.class_imports.entry(class).or_default();
299 if !imports
300 .iter()
301 .any(|existing| same_visible_symbol(existing, &enum_owner))
302 {
303 imports.push(enum_owner);
304 }
305 }
306
307 pub fn import_namespace(
308 &mut self,
309 namespace: Vec<String>,
310 declaration_byte: usize,
311 enum_owner: CodeUnit,
312 ) {
313 let imports = self.namespace_imports.entry(namespace).or_default();
314 if !imports
315 .iter()
316 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
317 {
318 imports.push((declaration_byte, enum_owner));
319 }
320 }
321
322 pub fn resolve_member(
323 &self,
324 visibility: &VisibilityIndex<'_>,
325 file: &ProjectFile,
326 class: Option<&CodeUnit>,
327 namespace: &[String],
328 before_byte: usize,
329 name: &str,
330 ) -> SemanticUsingEnumMemberResolution {
331 if let Some(class) = class
332 && let Some((_, imports)) = self
333 .class_imports
334 .iter()
335 .find(|(owner, _)| same_visible_symbol(owner, class))
336 {
337 let resolution =
338 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
339 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
340 return SemanticUsingEnumMemberResolution::Class(resolution);
341 }
342 }
343 for prefix_len in (0..=namespace.len()).rev() {
344 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
345 continue;
346 };
347 let owners = imports
348 .iter()
349 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
350 .map(|(_, owner)| owner);
351 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
352 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
353 return SemanticUsingEnumMemberResolution::Namespace(resolution);
354 }
355 }
356 SemanticUsingEnumMemberResolution::Missing
357 }
358}
359
360fn resolve_using_enum_member_for_owners<'a>(
361 visibility: &VisibilityIndex<'_>,
362 file: &ProjectFile,
363 owners: impl IntoIterator<Item = &'a CodeUnit>,
364 name: &str,
365) -> UsingEnumMemberResolution {
366 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
367 for owner in owners {
368 for member in visibility.visible_members_for_owner_name(file, owner, name) {
369 if !member.is_field()
370 || matches.iter().any(|(existing_owner, existing_member)| {
371 same_visible_symbol(existing_owner, owner)
372 && same_visible_symbol(existing_member, member)
373 })
374 {
375 continue;
376 }
377 matches.push((owner.clone(), member.clone()));
378 }
379 }
380 match matches.len() {
381 0 => UsingEnumMemberResolution::Missing,
382 1 => {
383 let (owner, member) = matches.pop().expect("one using-enum match");
384 UsingEnumMemberResolution::Resolved { owner, member }
385 }
386 _ => UsingEnumMemberResolution::Ambiguous,
387 }
388}
389
390impl ScopedUsingEnumOwners {
391 pub(crate) fn new() -> Self {
392 Self {
393 scopes: vec![Vec::new()],
394 }
395 }
396
397 pub fn enter_scope(&mut self) {
398 self.scopes.push(Vec::new());
399 }
400
401 pub fn exit_scope(&mut self) {
402 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
403 }
404
405 pub fn import(&mut self, owner: CodeUnit) {
406 let scope = self
407 .scopes
408 .last_mut()
409 .expect("using-enum scope stack is never empty");
410 if !scope
411 .iter()
412 .any(|existing| same_visible_symbol(existing, &owner))
413 {
414 scope.push(owner);
415 }
416 }
417
418 pub fn resolve_member(
419 &self,
420 visibility: &VisibilityIndex<'_>,
421 file: &ProjectFile,
422 name: &str,
423 ) -> UsingEnumMemberResolution {
424 for scope in self.scopes.iter().rev() {
425 let resolution =
426 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
427 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
428 return resolution;
429 }
430 }
431 UsingEnumMemberResolution::Missing
432 }
433}
434
435#[derive(Clone)]
436pub struct TargetSpec {
437 pub target: CodeUnit,
438 pub kind: TargetKind,
439 pub owner: Option<CodeUnit>,
440 pub member_name: String,
441 pub callable_arity: Option<CallableArity>,
442 pub activated_callable_arities: Vec<ActivatedCallableArity>,
443 pub param_types: Option<Vec<String>>,
444 pub enum_owner_kind: EnumOwnerKind,
445 pub owner_is_forward_declaration: bool,
446 pub callable_has_definition_body: bool,
447}
448
449#[derive(Clone, Copy)]
450pub struct ActivatedCallableArity {
451 pub activation_byte: usize,
452 pub arity: CallableArity,
453}
454
455#[derive(Debug, PartialEq, Eq, Hash)]
456pub struct TypeScanKey {
457 target: LogicalSymbolKey,
458 member_name: String,
459}
460
461#[derive(Clone, Debug, PartialEq, Eq, Hash)]
462struct LogicalSymbolKey {
463 kind: CodeUnitType,
464 fq_name: String,
465 signature: Option<String>,
466}
467
468struct ResolvedTypeOwner {
469 unit: CodeUnit,
470 is_forward_declaration: bool,
471}
472
473impl TargetSpec {
474 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
475 (self.kind == TargetKind::Type).then(|| TypeScanKey {
476 target: logical_symbol_key(&self.target),
477 member_name: self.member_name.clone(),
478 })
479 }
480
481 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
482 if target.is_class() {
483 return Some(Self::new(
484 target.clone(),
485 TargetKind::Type,
486 Some(target.clone()),
487 target.identifier().to_string(),
488 None,
489 None,
490 ));
491 }
492
493 if target.is_field() {
494 let owner = type_owner_of(analyzer, target);
500 let kind = if owner.is_some() {
501 TargetKind::MemberField
502 } else {
503 TargetKind::GlobalField
504 };
505 let enum_owner_kind = owner
506 .as_ref()
507 .map(|owner| classify_enum_owner(analyzer, owner))
508 .unwrap_or(EnumOwnerKind::NonEnum);
509 let mut spec = Self::new(
510 target.clone(),
511 kind,
512 owner,
513 target.identifier().to_string(),
514 None,
515 None,
516 );
517 spec.enum_owner_kind = enum_owner_kind;
518 return Some(spec);
519 }
520
521 if target.is_function() {
522 let owner_resolution = target_type_owner_resolution(analyzer, target);
525 let owner_is_forward_declaration = owner_resolution
526 .as_ref()
527 .is_some_and(|owner| owner.is_forward_declaration);
528 let owner = owner_resolution.map(|owner| owner.unit);
529 let kind = if owner.as_ref().is_some_and(|owner| {
530 target.identifier() == owner.identifier()
531 || analyzer
532 .cpp
533 .and_then(|cpp| cpp.template_metadata(owner))
534 .is_some_and(|metadata| metadata.primary_name == target.identifier())
535 }) {
536 TargetKind::Constructor
537 } else if owner.is_some() {
538 TargetKind::Method
539 } else {
540 TargetKind::FreeFunction
541 };
542 let mut spec = Self::new(
543 target.clone(),
544 kind,
545 owner,
546 target.identifier().to_string(),
547 cpp_callable_arity(analyzer, target),
548 cpp_callable_parameter_types(analyzer, target),
549 );
550 spec.owner_is_forward_declaration = owner_is_forward_declaration;
551 spec.callable_has_definition_body =
552 callable_target_has_definition_body(analyzer, target);
553 return Some(spec);
554 }
555
556 if target.is_macro() {
557 return Some(Self::new(
558 target.clone(),
559 TargetKind::Macro,
560 None,
561 target.identifier().to_string(),
562 None,
563 None,
564 ));
565 }
566
567 None
568 }
569
570 pub fn with_visible_callable_arities<'a>(
571 &'a self,
572 analyzer: &CppGraphSource<'_>,
573 cpp: &dyn CppSource,
574 visibility: &VisibilityIndex<'_>,
575 file: &ProjectFile,
576 prepared: &PreparedSyntaxTree,
577 ) -> Cow<'a, Self> {
578 let macro_parameter_arity =
579 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
580 let activated_callable_arities =
581 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
582 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
583 return Cow::Borrowed(self);
584 }
585 let mut effective = self.clone();
586 if let Some(macro_parameter_arity) = macro_parameter_arity {
587 effective.callable_arity = Some(macro_parameter_arity);
588 }
589 effective.activated_callable_arities = activated_callable_arities;
590 Cow::Owned(effective)
591 }
592
593 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
594 let base = self.callable_arity?;
595 Some(
596 self.activated_callable_arities
597 .iter()
598 .filter(|candidate| candidate.activation_byte <= byte)
599 .fold(base, |arity, candidate| {
600 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
601 }),
602 )
603 }
604
605 pub fn new(
606 target: CodeUnit,
607 kind: TargetKind,
608 owner: Option<CodeUnit>,
609 member_name: String,
610 callable_arity: Option<CallableArity>,
611 param_types: Option<Vec<String>>,
612 ) -> Self {
613 Self {
614 target,
615 kind,
616 owner,
617 member_name,
618 callable_arity,
619 activated_callable_arities: Vec::new(),
620 param_types,
621 enum_owner_kind: EnumOwnerKind::NonEnum,
622 owner_is_forward_declaration: false,
623 callable_has_definition_body: false,
624 }
625 }
626}
627
628fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
629 analyzer
630 .signature_metadata(target)
631 .iter()
632 .any(|metadata| !metadata.is_declaration_only())
633}
634
635fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
636 LogicalSymbolKey {
637 kind: unit.kind(),
638 fq_name: unit.fq_name(),
639 signature: unit.signature().map(str::to_string),
640 }
641}
642
643fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
644 let Some(facts) = analyzer.declaration_source_properties(owner) else {
645 return EnumOwnerKind::NonEnum;
646 };
647 let Some(first) = facts.first() else {
648 return EnumOwnerKind::NonEnum;
649 };
650 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
651 first.enum_kind
652 } else {
653 EnumOwnerKind::NonEnum
654 }
655}
656
657#[derive(Clone, PartialEq, Eq, Hash)]
658pub struct CppScanBinding {
659 pub unit: Option<CodeUnit>,
660 pub type_name: Option<String>,
661 pub indirection: i32,
662}
663
664impl CppScanBinding {
665 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
666 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
667 }
668
669 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
670 Self {
671 type_name: Some(type_name),
672 unit,
673 indirection,
674 }
675 }
676
677 pub fn as_arg_type(&self) -> Option<CppArgType> {
678 let name = self
679 .type_name
680 .clone()
681 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
682 Some(CppArgType {
683 name,
684 unit: self.unit.clone(),
685 indirection: self.indirection,
686 pointee_const: false,
687 })
688 }
689}
690
691type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
692pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
693pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
694type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
695type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
696type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
697type MacroLexicalTemplateCache =
698 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
699
700type MacroLocalBindingTemplateCache =
701 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
702type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
703type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
704type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
705
706struct StructuredIncludeFact {
707 start_byte: usize,
708 end_byte: usize,
709 path: String,
710}
711
712#[derive(Clone, Default)]
713pub struct MacroEnvironment {
714 bindings: HashMap<String, MacroBinding>,
715 known_undefined_names: HashSet<String>,
716 build_proven_defines: HashSet<String>,
721 unknown_names: bool,
722 applied_pragma_once_files: HashSet<ProjectFile>,
723 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
724}
725
726pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
735
736struct MacroEnvironmentCheckpoint {
738 frontier: usize,
740 environment: Arc<MacroEnvironment>,
741}
742
743struct MacroEnvironmentCheckpoints {
752 checkpoints: Vec<MacroEnvironmentCheckpoint>,
753}
754
755impl MacroEnvironmentCheckpoints {
756 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
758 let index = self
759 .checkpoints
760 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
761 assert!(
762 index > 0,
763 "a checkpoint vector starts at frontier zero, which precedes every request"
764 );
765 &self.checkpoints[index - 1]
766 }
767}
768
769fn clone_or_default_file_cell<T: Default + Clone>(
770 cells: &Mutex<HashMap<ProjectFile, T>>,
771 file: &ProjectFile,
772 poison_message: &str,
773) -> T {
774 cells
775 .lock()
776 .expect(poison_message)
777 .entry(file.clone())
778 .or_default()
779 .clone()
780}
781
782impl MacroEnvironment {
783 fn binding(&self, name: &str) -> Option<&MacroBinding> {
784 self.bindings.get(name)
785 }
786
787 fn may_bind(&self, name: &str) -> bool {
788 self.bindings.contains_key(name) || self.unknown_names
789 }
790
791 fn insert(&mut self, name: String, binding: MacroBinding) {
792 self.known_undefined_names.remove(&name);
793 self.bindings.insert(name, binding);
794 }
795
796 fn remove(&mut self, name: &str) {
797 self.bindings.remove(name);
798 self.known_undefined_names.insert(name.to_string());
799 }
800
801 fn remove_known_undefined(&mut self, name: &str) {
802 self.known_undefined_names.remove(name);
803 }
804
805 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
806 for binding in self.bindings.values_mut() {
807 *binding = MacroBinding::uncertain_from(binding, source, byte);
808 }
809 self.known_undefined_names.clear();
810 self.build_proven_defines.clear();
815 self.unknown_names = true;
816 }
817
818 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
819 guards.iter().all(|guard| self.guard_may_hold(guard))
820 }
821
822 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
823 self.boolean_guard_may_hold(&guard.as_boolean_expression())
824 }
825
826 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
827 match expression {
828 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
829 BooleanGuardExpression::Undefined(name) => {
830 self.bindings
831 .get(name)
832 .is_none_or(|binding| !binding.is_exact())
833 && (!self.build_proven_defines.contains(name)
834 || self.known_undefined_names.contains(name))
835 }
836 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
837 BooleanGuardExpression::Opaque(_)
838 | BooleanGuardExpression::NegatedOpaque(_)
839 | BooleanGuardExpression::Constant(true) => true,
840 BooleanGuardExpression::Constant(false) => false,
841 BooleanGuardExpression::All(expressions) => expressions
842 .iter()
843 .all(|expression| self.boolean_guard_may_hold(expression)),
844 BooleanGuardExpression::Any(expressions) => expressions
845 .iter()
846 .any(|expression| self.boolean_guard_may_hold(expression)),
847 }
848 }
849}
850
851fn any_visible_source_file_matches(
852 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
853 file: &ProjectFile,
854 mut predicate: impl FnMut(&ProjectFile) -> bool,
855) -> bool {
856 match visible_source_files_by_root.get(file) {
857 Some(files) => files.iter().any(predicate),
858 None => predicate(file),
859 }
860}
861
862#[derive(Clone)]
863pub enum EffectiveUsingTarget {
864 Ordinary {
865 name: String,
866 target_components: Vec<String>,
867 global: bool,
868 },
869 Namespace {
870 namespace_components: Vec<String>,
871 global: bool,
872 },
873}
874
875#[derive(Clone)]
876pub struct OrdinaryTypeImport {
877 pub target: EffectiveUsingTarget,
878 pub source: ProjectFile,
879 pub declaration_byte: usize,
880 pub scope_start: usize,
881 pub scope_end: usize,
882 pub scope_depth: usize,
883 pub block_scope: bool,
884 pub lexical_depth: usize,
885 pub declaration_namespace: Vec<String>,
886 pub namespace_scope: Option<Vec<String>>,
887 pub resolved_target_components: Option<Vec<String>>,
888 pub required_guards: HashSet<PreprocessorGuard>,
889}
890
891#[derive(Clone)]
892pub struct ConditionalIncludeProjection {
893 pub activation_byte: usize,
894 pub required_guards: HashSet<PreprocessorGuard>,
895 pub partial_guards: HashSet<PreprocessorGuard>,
899}
900
901#[derive(Default)]
902pub struct SourceUsingIndex {
903 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
904 pub directives: Vec<OrdinaryTypeImport>,
905}
906
907#[derive(Default)]
908pub struct ProjectUsingIndex {
909 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
910 pub directives: Vec<OrdinaryTypeImport>,
911}
912
913type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
914
915pub struct EffectiveUsingIndex {
916 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
917}
918
919impl EffectiveUsingIndex {
920 fn new(_root: ProjectFile) -> Self {
921 Self {
922 projected_by_name: Mutex::new(HashMap::default()),
923 }
924 }
925
926 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
927 self.projected_by_name
928 .lock()
929 .expect("C++ effective-using projection cache poisoned")
930 .entry(name.to_string())
931 .or_default()
932 .clone()
933 }
934}
935
936pub enum OrdinaryTypeImportResolution {
937 Resolved {
938 target: CodeUnit,
939 target_components: Vec<String>,
940 lexical_depth: usize,
941 is_direct: bool,
942 },
943 Ambiguous {
944 lexical_depth: usize,
945 },
946 Missing,
947}
948
949type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
950type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
951type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
952type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
953type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
954type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
955type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
956type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
957
958struct ExtractedComparable {
963 shapes: Vec<CppComparableSlot>,
964 suffix: String,
965 template: Option<CppTemplateContext>,
968}
969
970const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
974
975#[derive(Clone, Copy, Debug, PartialEq, Eq)]
984enum IncludePathAdmission {
985 Proven,
986 Compatible,
987}
988
989impl IncludePathAdmission {
990 fn admits(
991 self,
992 required: &HashSet<PreprocessorGuard>,
993 partial: &HashSet<PreprocessorGuard>,
994 reference_guards: Option<&HashSet<PreprocessorGuard>>,
995 ) -> bool {
996 match self {
997 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
998 Self::Compatible => {
999 guard_requirements_hold_at_reference(partial, reference_guards)
1000 && guards_compatible_at_reference(required, reference_guards)
1001 }
1002 }
1003 }
1004}
1005
1006pub struct VisibilityIndex<'a> {
1010 cpp: &'a dyn CppSource,
1011 token: QueryToken<'a>,
1012 data: Arc<VisibilityIndexData>,
1013}
1014
1015impl std::ops::Deref for VisibilityIndex<'_> {
1016 type Target = VisibilityIndexData;
1017
1018 fn deref(&self) -> &Self::Target {
1019 &self.data
1020 }
1021}
1022
1023pub struct VisibilityIndexData {
1026 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1027 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1028 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1029 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1030 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1031 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1032 parser_alias_target_matches:
1033 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1034 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1035 project_using_index: OnceLock<ProjectUsingIndex>,
1036 namespace_alias_sites: Mutex<HashMap<ProjectFile, super::namespace_alias::NamespaceAliasCell>>,
1037 callable_reference_specs:
1038 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1039 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1040 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1041 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1042 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1043 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1044 #[cfg(any(test, feature = "test-support"))]
1045 conditional_include_projection_index_build_count: AtomicUsize,
1046 #[cfg(any(test, feature = "test-support"))]
1047 conditional_include_projection_state_count: AtomicUsize,
1048 #[cfg(any(test, feature = "test-support"))]
1049 conditional_include_target_state_count: AtomicUsize,
1050 #[cfg(any(test, feature = "test-support"))]
1051 include_activation_build_count: AtomicUsize,
1052 #[cfg(any(test, feature = "test-support"))]
1053 using_donor_activation_count: AtomicUsize,
1054 #[cfg(any(test, feature = "test-support"))]
1055 using_namespace_lookup_count: AtomicUsize,
1056 #[cfg(any(test, feature = "test-support"))]
1057 using_name_candidate_inspection_count: AtomicUsize,
1058 #[cfg(any(test, feature = "test-support"))]
1059 callable_reference_spec_build_count: AtomicUsize,
1060 #[cfg(any(test, feature = "test-support"))]
1061 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1062 #[cfg(any(test, feature = "test-support"))]
1063 visible_parser_alias_name_set_build_count: AtomicUsize,
1064 parser_alias_fallback_calls: AtomicUsize,
1065 parser_alias_fallback_files: AtomicUsize,
1066 parser_alias_source_parses: AtomicUsize,
1067 parser_alias_fallback_elapsed_micros: AtomicUsize,
1068 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1069 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1070 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1071 class_definition_agreements: Mutex<HashMap<(CodeUnit, CodeUnit), bool>>,
1072 callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1078 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1079 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1080 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1081 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1082 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1083 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1084 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1085 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1086 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1087 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1094 macro_replacements: Mutex<MacroReplacementCache>,
1095 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1096 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1097 macro_lexical_indexes:
1098 Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1099 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1100 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1101 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1102 #[cfg(any(test, feature = "test-support"))]
1103 pub macro_replacement_parse_count: AtomicUsize,
1104 #[cfg(any(test, feature = "test-support"))]
1105 pub macro_event_application_count: AtomicUsize,
1106 #[cfg(any(test, feature = "test-support"))]
1109 pub macro_environment_checkpoint_build_count: AtomicUsize,
1110 #[cfg(any(test, feature = "test-support"))]
1113 pub macro_environment_copy_count: AtomicUsize,
1114 #[cfg(any(test, feature = "test-support"))]
1115 pub macro_environment_request_count: AtomicUsize,
1116 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1117 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1118 #[cfg(any(test, feature = "test-support"))]
1119 qualified_candidate_inspections: AtomicUsize,
1120 #[cfg(any(test, feature = "test-support"))]
1121 target_preserving_type_resolution_count: AtomicUsize,
1122 #[cfg(any(test, feature = "test-support"))]
1123 visibility_identifier_lookup_count: usize,
1124 #[cfg(any(test, feature = "test-support"))]
1125 visibility_identifier_batch_count: usize,
1126}
1127
1128impl Drop for VisibilityIndexData {
1129 fn drop(&mut self) {
1130 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1131 return;
1132 }
1133 #[cfg(any(test, feature = "test-support"))]
1134 eprintln!(
1135 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1136 self.macro_environment_request_count.load(Ordering::Relaxed),
1137 self.macro_environment_copy_count.load(Ordering::Relaxed),
1138 self.macro_environment_checkpoint_build_count
1139 .load(Ordering::Relaxed),
1140 self.macro_event_application_count.load(Ordering::Relaxed),
1141 );
1142 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1143 if calls == 0 {
1144 return;
1145 }
1146 eprintln!(
1147 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1148 calls,
1149 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1150 self.parser_alias_source_parses.load(Ordering::Relaxed),
1151 self.parser_alias_fallback_elapsed_micros
1152 .load(Ordering::Relaxed)
1153 / 1_000,
1154 );
1155 }
1156}
1157
1158#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1159pub enum PreprocessorGuard {
1160 Defined(String),
1161 Undefined(String),
1162 Boolean(BooleanGuardExpression),
1163 Expression(String),
1164 NegatedExpression(String),
1165 Constant(bool),
1166}
1167
1168#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1169pub enum BooleanGuardExpression {
1170 Defined(String),
1171 Undefined(String),
1172 Truthy(String),
1173 Falsy(String),
1174 Opaque(String),
1175 NegatedOpaque(String),
1176 All(Vec<BooleanGuardExpression>),
1177 Any(Vec<BooleanGuardExpression>),
1178 Constant(bool),
1179}
1180
1181impl BooleanGuardExpression {
1182 fn negated(&self) -> Self {
1183 match self {
1184 Self::Defined(name) => Self::Undefined(name.clone()),
1185 Self::Undefined(name) => Self::Defined(name.clone()),
1186 Self::Truthy(name) => Self::Falsy(name.clone()),
1187 Self::Falsy(name) => Self::Truthy(name.clone()),
1188 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1189 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1190 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1191 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1192 Self::Constant(value) => Self::Constant(!value),
1193 }
1194 }
1195
1196 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1197 Self::normalized(expressions, true)
1198 }
1199
1200 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1201 Self::normalized(expressions, false)
1202 }
1203
1204 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1205 let mut normalized = Vec::new();
1206 for expression in expressions {
1207 match expression {
1208 Self::All(nested) if conjunction => normalized.extend(nested),
1209 Self::Any(nested) if !conjunction => normalized.extend(nested),
1210 Self::Constant(value) if value == conjunction => {}
1211 Self::Constant(value) => return Self::Constant(value),
1212 expression => normalized.push(expression),
1213 }
1214 }
1215 normalized.sort_unstable();
1216 normalized.dedup();
1217 match normalized.len() {
1218 0 => Self::Constant(conjunction),
1219 1 => normalized.pop().expect("one Boolean guard expression"),
1220 _ if conjunction => Self::All(normalized),
1221 _ => Self::Any(normalized),
1222 }
1223 }
1224
1225 fn implies(&self, required: &Self) -> bool {
1226 if self == required
1227 || matches!(self, Self::Constant(false))
1228 || matches!(required, Self::Constant(true))
1229 {
1230 return true;
1231 }
1232 if matches!(
1233 (self, required),
1234 (Self::Truthy(active), Self::Defined(required))
1235 | (Self::Undefined(active), Self::Falsy(required))
1236 if active == required
1237 ) {
1238 return true;
1239 }
1240 match self {
1241 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1242 Self::All(active) => match required {
1243 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1244 _ => active.iter().any(|expression| expression.implies(required)),
1245 },
1246 _ => match required {
1247 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1248 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1249 _ => false,
1250 },
1251 }
1252 }
1253
1254 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1255 match self {
1256 Self::Defined(name)
1257 | Self::Undefined(name)
1258 | Self::Truthy(name)
1259 | Self::Falsy(name) => name == macro_name,
1260 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1263 Self::All(expressions) | Self::Any(expressions) => expressions
1264 .iter()
1265 .any(|expression| expression.may_depend_on_macro(macro_name)),
1266 Self::Constant(_) => false,
1267 }
1268 }
1269
1270 pub fn heap_size(&self) -> usize {
1271 match self {
1272 Self::Defined(value)
1273 | Self::Undefined(value)
1274 | Self::Truthy(value)
1275 | Self::Falsy(value)
1276 | Self::Opaque(value)
1277 | Self::NegatedOpaque(value) => value.len(),
1278 Self::All(expressions) | Self::Any(expressions) => {
1279 expressions
1280 .iter()
1281 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1282 size.saturating_add(std::mem::size_of::<Self>())
1283 .saturating_add(expression.heap_size())
1284 })
1285 }
1286 Self::Constant(_) => 0,
1287 }
1288 }
1289}
1290
1291impl PreprocessorGuard {
1292 fn as_boolean_expression(&self) -> BooleanGuardExpression {
1293 match self {
1294 Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1295 Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1296 Self::Boolean(expression) => expression.clone(),
1297 Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1298 Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1299 Self::NegatedExpression(expression) => {
1300 BooleanGuardExpression::NegatedOpaque(expression.clone())
1301 }
1302 }
1303 }
1304
1305 fn negated(&self) -> Self {
1306 match self {
1307 Self::Defined(name) => Self::Undefined(name.clone()),
1308 Self::Undefined(name) => Self::Defined(name.clone()),
1309 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1310 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1311 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1312 Self::Constant(value) => Self::Constant(!value),
1313 }
1314 }
1315
1316 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1317 match self {
1318 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1319 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1320 Self::Expression(_) | Self::NegatedExpression(_) => true,
1323 Self::Constant(_) => false,
1324 }
1325 }
1326}
1327
1328#[derive(Clone, PartialEq, Eq)]
1329pub enum MacroDefinition {
1330 Object {
1331 replacement: String,
1332 },
1333 Function {
1334 parameters: Vec<String>,
1335 replacement: String,
1336 },
1337 VariadicFunction {
1338 parameters: Vec<String>,
1339 replacement: String,
1340 },
1341 Unsupported,
1342}
1343
1344#[derive(Clone, Debug, PartialEq, Eq)]
1345pub enum MacroIncludeProtection {
1346 MacroGuard(String),
1347 PragmaOnce,
1348 None,
1349}
1350
1351enum ParsedMacroReplacement {
1352 Parsed { source: String, tree: Tree },
1353 Unsupported,
1354}
1355
1356const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1360const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1361
1362type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1365
1366pub struct ParsedReplacementBody {
1375 pub source: String,
1376 pub tree: Tree,
1377 pub body_offset: usize,
1378 pub parameters: Vec<String>,
1379 original_offsets: MacroOriginMap,
1383}
1384
1385impl ParsedReplacementBody {
1386 pub fn statements(&self) -> Option<Node<'_>> {
1388 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1389 .child_by_field_name("body")
1390 }
1391
1392 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1398 assert!(
1399 node.start_byte() >= self.body_offset,
1400 "synthetic sentinel node cannot be mapped to a macro replacement"
1401 );
1402 assert!(
1403 node.end_byte() >= self.body_offset,
1404 "synthetic sentinel node cannot be mapped to a macro replacement"
1405 );
1406 let start_offset = node.start_byte() - self.body_offset;
1407 let end_offset = node.end_byte() - self.body_offset;
1408 assert!(start_offset <= end_offset);
1409 let start_origin = self
1410 .original_offsets
1411 .get(start_offset)
1412 .expect("replacement node start must have a source mapping")
1413 .start;
1414 let end_origin = if start_offset == end_offset {
1415 start_origin
1416 } else {
1417 self.original_offsets
1418 .get(end_offset - 1)
1419 .expect("replacement node end must have a source mapping")
1420 .end
1421 };
1422 assert!(start_origin <= end_origin);
1423 let start = replacement_start + start_origin;
1424 let end = replacement_start + end_origin;
1425 start..end
1426 }
1427
1428 fn expands_variadic_arguments(&self) -> bool {
1435 let mut stack = vec![self.tree.root_node()];
1436 while let Some(node) = stack.pop() {
1437 if matches!(
1438 node.kind(),
1439 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1440 ) && node_text(node, &self.source) == "__VA_ARGS__"
1441 {
1442 return true;
1443 }
1444 push_named_children_reversed(node, &mut stack);
1445 }
1446 false
1447 }
1448}
1449
1450pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1452 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1453 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1454 if compact.starts_with("0x") || compact.starts_with("0X") {
1455 (16, 2, |ch| ch.is_ascii_hexdigit())
1456 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1457 (2, 2, |ch| matches!(ch, '0' | '1'))
1458 } else if compact.starts_with('0') && compact.len() > 1 {
1459 (8, 0, |ch| matches!(ch, '0'..='7'))
1460 } else {
1461 (10, 0, |ch| ch.is_ascii_digit())
1462 };
1463 let digit_len = compact[digits_start..]
1464 .chars()
1465 .take_while(|ch| digit_matches(*ch))
1466 .map(char::len_utf8)
1467 .sum::<usize>();
1468 if digit_len == 0 {
1469 return None;
1470 }
1471 let digits_end = digits_start + digit_len;
1472 if !compact[digits_end..]
1473 .chars()
1474 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1475 {
1476 return None;
1477 }
1478 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1479}
1480
1481#[derive(Clone)]
1482enum MacroLocalBindingTypeTemplate {
1483 Parameter(usize),
1484 Fixed(String),
1485}
1486
1487#[derive(Clone)]
1488struct MacroLocalBindingTemplate {
1489 name: String,
1490 declared_type: MacroLocalBindingTypeTemplate,
1491 pointer_depth: i32,
1492}
1493
1494pub struct MacroLocalBinding<'tree> {
1502 pub name: String,
1503 pub type_name: String,
1504 pub type_node: Option<Node<'tree>>,
1505 pub pointer_depth: i32,
1506 pub proven_unit: Option<CodeUnit>,
1507}
1508
1509#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1515pub enum MacroLexicalBindingKind {
1516 Parameter,
1517 Local,
1518}
1519
1520#[derive(Clone, Debug, PartialEq, Eq)]
1521pub struct MacroLexicalBinding {
1522 pub definition: ProjectFile,
1523 pub kind: MacroLexicalBindingKind,
1524 pub name: String,
1525 pub name_range: std::ops::Range<usize>,
1526 pub declaration_range: std::ops::Range<usize>,
1527}
1528
1529#[derive(Clone, Debug, Default, PartialEq, Eq)]
1533pub struct MacroLexicalReferences {
1534 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1535 pub truncated: bool,
1536 pub cancelled: bool,
1537}
1538
1539fn macro_replacement_type_parameters(
1540 body: &ParsedReplacementBody,
1541 parameters: &[String],
1542) -> Option<Vec<usize>> {
1543 let mut found = Vec::new();
1544 let mut stack = vec![body.tree.root_node()];
1545 while let Some(node) = stack.pop() {
1546 let type_position = node.kind() == "type_identifier"
1547 || (node.kind() == "identifier"
1548 && node.parent().is_some_and(|parent| {
1549 parent.kind() == "type_descriptor"
1550 && parent.child_by_field_name("type") == Some(node)
1551 }));
1552 let offsetof_type_position = node.kind() == "identifier"
1553 && node
1554 .parent()
1555 .filter(|parent| parent.kind() == "argument_list")
1556 .and_then(|arguments| arguments.parent())
1557 .is_some_and(|call| {
1558 call.kind() == "call_expression"
1559 && call
1560 .child_by_field_name("function")
1561 .is_some_and(|function| {
1562 function.kind() == "identifier"
1563 && node_text(function, &body.source) == "offsetof"
1564 })
1565 && call
1566 .child_by_field_name("arguments")
1567 .is_some_and(|arguments| {
1568 argument_children(arguments).next() == Some(node)
1569 })
1570 });
1571 if (type_position || offsetof_type_position)
1572 && let Some(index) = parameters
1573 .iter()
1574 .position(|parameter| parameter == node_text(node, &body.source))
1575 && !found.contains(&index)
1576 {
1577 found.push(index);
1578 }
1579 push_named_children_reversed(node, &mut stack);
1580 }
1581 (!found.is_empty()).then_some(found)
1582}
1583
1584fn macro_replacement_type_parameter(
1585 body: &ParsedReplacementBody,
1586 parameters: &[String],
1587) -> Option<usize> {
1588 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1589 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1590}
1591
1592pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1596 let (name, arguments) = match node.kind() {
1597 "call_expression" => (
1598 node.child_by_field_name("function")?,
1599 argument_children(node.child_by_field_name("arguments")?).collect(),
1600 ),
1601 "macro_type_specifier" => (
1602 node.child_by_field_name("name")?,
1603 vec![node.child_by_field_name("type")?],
1604 ),
1605 _ => return None,
1606 };
1607 (name.kind() == "identifier").then_some((name, arguments))
1608}
1609
1610pub(crate) fn macro_type_argument_node<'tree>(
1611 node: Node<'tree>,
1612 source: &str,
1613) -> Option<Node<'tree>> {
1614 match node.kind() {
1615 "type_descriptor" => {
1616 let type_child = node
1617 .child_by_field_name("type")
1618 .or_else(|| first_type_child(node))?;
1619 for index in (0..node.named_child_count()).rev() {
1620 let child = node.named_child(index)?;
1621 if child != type_child
1622 && matches!(
1623 child.kind(),
1624 "identifier"
1625 | "type_identifier"
1626 | "qualified_identifier"
1627 | "scoped_type_identifier"
1628 )
1629 {
1630 return Some(child);
1631 }
1632 }
1633 macro_type_argument_node(type_child, source)
1634 }
1635 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1636 node.child_by_field_name("name")
1637 }
1638 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1639 .next_named_sibling()
1640 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1641 .and_then(|error| error.named_child(0))
1642 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1643 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1644 }
1645}
1646
1647fn c_function_macro_argument<'tree>(
1648 node: Node<'tree>,
1649) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1650 let mut current = node;
1651 while let Some(parent) = current.parent() {
1652 if parent.kind() == "argument_list" {
1653 let call = parent.parent().filter(|call| {
1654 call.kind() == "call_expression"
1655 && call.child_by_field_name("arguments") == Some(parent)
1656 && call
1657 .child_by_field_name("function")
1658 .is_some_and(|function| {
1659 function.kind() == "identifier"
1660 && !node_range_contains(function, current)
1661 })
1662 })?;
1663 let mut actuals = argument_children(parent).enumerate();
1664 let (index, argument) = actuals.find(|(_, argument)| {
1665 node_range_contains(*argument, node)
1666 && (argument.start_byte() == current.start_byte()
1667 || argument.end_byte() == current.end_byte())
1668 })?;
1669 return Some((call, index, argument));
1670 }
1671 current = parent;
1672 }
1673 None
1674}
1675
1676fn recognized_c_macro_declarator_binding<'tree>(
1684 statement: Node<'tree>,
1685 source: &str,
1686) -> Option<MacroLocalBinding<'tree>> {
1687 if statement.kind() == "declaration" {
1688 let specifier = statement
1689 .child_by_field_name("type")
1690 .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1691 let macro_name = specifier
1692 .child_by_field_name("name")
1693 .filter(|name| name.kind() == "identifier")?;
1694 if node_text(macro_name, source) != "g_autoptr" {
1695 return None;
1696 }
1697 let type_node = specifier
1698 .child_by_field_name("type")
1699 .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1700 let declarator = statement
1701 .child_by_field_name("declarator")
1702 .filter(|declarator| declarator.kind() == "init_declarator")?;
1703 let name_node = declarator
1704 .child_by_field_name("declarator")
1705 .filter(|name| name.kind() == "identifier")?;
1706 declarator.child_by_field_name("value")?;
1707 let name = node_text(name_node, source).trim();
1708 let type_name = node_text(type_node, source).trim();
1709 if name.is_empty() || type_name.is_empty() {
1710 return None;
1711 }
1712 return Some(MacroLocalBinding {
1713 name: name.to_string(),
1714 type_name: type_name.to_string(),
1715 type_node: Some(type_node),
1716 pointer_depth: 1,
1717 proven_unit: None,
1718 });
1719 }
1720 let assignment = match statement.kind() {
1721 "assignment_expression" => statement,
1722 "expression_statement" => {
1723 single_named_child_matching(statement, |child| !child.is_extra())?
1724 }
1725 _ => return None,
1726 };
1727 if assignment.kind() != "assignment_expression" {
1728 return None;
1729 }
1730 let call = assignment.child_by_field_name("left")?;
1731 if call.kind() != "call_expression" {
1732 return None;
1733 }
1734 let function = call.child_by_field_name("function")?;
1735 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1736 return None;
1737 }
1738 let arguments = call.child_by_field_name("arguments")?;
1739 let mut actuals = argument_children(arguments);
1740 let type_node = actuals.next()?;
1741 if actuals.next().is_some()
1742 || !matches!(
1743 type_node.kind(),
1744 "identifier"
1745 | "type_identifier"
1746 | "qualified_identifier"
1747 | "scoped_type_identifier"
1748 | "template_type"
1749 )
1750 {
1751 return None;
1752 }
1753 let name_node = (0..assignment.named_child_count())
1754 .filter_map(|index| assignment.named_child(index))
1755 .filter(|child| child.kind() == "ERROR")
1756 .filter_map(|error| {
1757 (error.named_child_count() == 1)
1758 .then(|| error.named_child(0))
1759 .flatten()
1760 })
1761 .find(|node| node.kind() == "identifier")?;
1762 let name = node_text(name_node, source).trim();
1763 let type_name = node_text(type_node, source).trim();
1764 if name.is_empty() || type_name.is_empty() {
1765 return None;
1766 }
1767 Some(MacroLocalBinding {
1768 name: name.to_string(),
1769 type_name: type_name.to_string(),
1770 type_node: Some(type_node),
1771 pointer_depth: 1,
1772 proven_unit: None,
1773 })
1774}
1775
1776#[derive(Clone, PartialEq, Eq)]
1777pub struct MacroBinding {
1778 source: ProjectFile,
1779 declaration_byte: usize,
1780 definition: MacroDefinition,
1781 exact: bool,
1782}
1783
1784impl MacroBinding {
1785 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1786 Self {
1787 source: source.clone(),
1788 declaration_byte,
1789 definition: MacroDefinition::Unsupported,
1790 exact: false,
1791 }
1792 }
1793
1794 fn is_exact(&self) -> bool {
1795 self.exact
1796 }
1797
1798 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1799 Self {
1800 source: source.clone(),
1801 declaration_byte,
1802 definition: current.definition.clone(),
1803 exact: false,
1804 }
1805 }
1806}
1807
1808type OwningPreprocessorConditionals = Box<[usize]>;
1820
1821#[derive(Clone)]
1822pub enum MacroEvent {
1823 Define {
1824 name: String,
1825 binding: MacroBinding,
1826 byte: usize,
1827 conditionals: OwningPreprocessorConditionals,
1828 },
1829 Undef {
1830 name: String,
1831 byte: usize,
1832 conditionals: OwningPreprocessorConditionals,
1833 },
1834 Include {
1835 targets: Vec<ProjectFile>,
1836 byte: usize,
1837 conditionals: OwningPreprocessorConditionals,
1838 },
1839 Invalidate {
1840 byte: usize,
1841 },
1842}
1843
1844impl MacroEvent {
1845 pub fn byte(&self) -> usize {
1846 match self {
1847 Self::Define { byte, .. }
1848 | Self::Undef { byte, .. }
1849 | Self::Include { byte, .. }
1850 | Self::Invalidate { byte } => *byte,
1851 }
1852 }
1853}
1854
1855#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1856pub enum CallArityEvidence {
1857 Exact(usize),
1858 Unknown,
1859}
1860
1861impl CallArityEvidence {
1862 pub fn exact(self) -> Option<usize> {
1863 match self {
1864 Self::Exact(arity) => Some(arity),
1865 Self::Unknown => None,
1866 }
1867 }
1868
1869 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1870 self.exact().map(|arity| expected.accepts(arity))
1871 }
1872}
1873
1874struct CppAlias {
1875 name: String,
1876 target: String,
1877 namespace: Option<String>,
1878}
1879
1880type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1881
1882#[derive(Debug, Clone, PartialEq, Eq)]
1886pub enum CppTemplateResolutionError {
1887 AliasCycle { alias: CodeUnit },
1889 ArgumentBinding,
1891 Substitution,
1893 PrimarySelection,
1896 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1899}
1900
1901fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1904 let mut distinct: Vec<CodeUnit> = Vec::new();
1905 for unit in units {
1906 if !distinct
1907 .iter()
1908 .any(|existing| same_visible_symbol(existing, unit))
1909 {
1910 distinct.push(unit.clone());
1911 }
1912 }
1913 distinct
1914}
1915
1916pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1919 visibility: Factory,
1920 file: &ProjectFile,
1921 root: Node<'_>,
1922 source: &str,
1923 max_references: usize,
1924 cancelled: impl FnMut() -> bool,
1925) -> MacroLexicalReferences
1926where
1927 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1928{
1929 crate::graph::macro_lexical::all_references(
1930 visibility,
1931 file,
1932 root,
1933 source,
1934 max_references,
1935 cancelled,
1936 )
1937}
1938
1939impl<'a> VisibilityIndex<'a> {
1940 pub fn data(&self) -> Arc<VisibilityIndexData> {
1941 Arc::clone(&self.data)
1942 }
1943
1944 pub fn from_data(
1945 cpp: &'a dyn CppSource,
1946 token: QueryToken<'a>,
1947 data: Arc<VisibilityIndexData>,
1948 ) -> Self {
1949 Self { cpp, token, data }
1950 }
1951
1952 pub fn cpp(&self) -> &'a dyn CppSource {
1953 self.cpp
1954 }
1955
1956 pub fn token(&self) -> QueryToken<'a> {
1958 self.token
1959 }
1960
1961 pub(crate) fn macro_lexical_index(
1965 &self,
1966 file: &ProjectFile,
1967 root: Node<'_>,
1968 source: &str,
1969 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1970 if let Some(index) = self
1971 .macro_lexical_indexes
1972 .lock()
1973 .expect("macro lexical index cache poisoned")
1974 .get(file)
1975 .cloned()
1976 {
1977 debug_assert_eq!(
1978 index.source_len,
1979 source.len(),
1980 "macro lexical index for {file:?} was built from a different source"
1981 );
1982 return index;
1983 }
1984 let index = Arc::new(crate::graph::macro_lexical::build_index(
1985 self, file, root, source,
1986 ));
1987 let mut cache = self
1988 .macro_lexical_indexes
1989 .lock()
1990 .expect("macro lexical index cache poisoned");
1991 if let Some(existing) = cache.get(file).cloned() {
1992 return existing;
1993 }
1994 if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1995 cache.insert(file.clone(), Arc::clone(&index));
1996 }
1997 index
1998 }
1999
2000 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2006 if let Some(metadata) = self
2007 .callable_metadata
2008 .lock()
2009 .expect("C++ callable metadata cache poisoned")
2010 .get(unit)
2011 .cloned()
2012 {
2013 return metadata;
2014 }
2015 let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
2016 let mut cache = self
2017 .callable_metadata
2018 .lock()
2019 .expect("C++ callable metadata cache poisoned");
2020 if cache.len() < CALLABLE_METADATA_CACHE_CAP {
2021 cache.insert(unit.clone(), Arc::clone(&metadata));
2022 }
2023 metadata
2024 }
2025
2026 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2028 crate::identity::cpp_callable_linkage_from_metadata(
2029 self.callable_metadata(unit).iter().cloned(),
2030 )
2031 }
2032
2033 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2035 brokk_bifrost_core::analyzer::model::first_callable_arity(
2036 self.callable_metadata(unit).iter().cloned(),
2037 )
2038 }
2039
2040 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2045 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2046 self.callable_metadata(unit).iter().cloned(),
2047 )
2048 }
2049
2050 pub fn has_unresolved_include_visible_before(
2058 &self,
2059 file: &ProjectFile,
2060 before_byte: usize,
2061 ) -> bool {
2062 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2063 return false;
2064 };
2065 let facts = self.structured_include_facts(file, prepared.as_ref());
2066 has_unresolved_include_visible_before_in_prepared(
2067 file,
2068 prepared.as_ref(),
2069 self.cpp.include_target_index(),
2070 &facts,
2071 before_byte,
2072 )
2073 }
2074
2075 fn structured_include_facts(
2076 &self,
2077 file: &ProjectFile,
2078 prepared: &PreparedSyntaxTree,
2079 ) -> Arc<[StructuredIncludeFact]> {
2080 let cell = self
2081 .structured_include_fact_cells
2082 .lock()
2083 .expect("C++ structured include-fact cache poisoned")
2084 .entry(file.clone())
2085 .or_default()
2086 .clone();
2087 Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2088 }
2089
2090 #[cfg(any(test, feature = "test-support"))]
2098 pub fn from_visible_files_for_test(
2099 cpp: &'a dyn CppSource,
2100 token: QueryToken<'a>,
2101 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2102 ) -> Self {
2103 let visible_source_files_by_root = visible_by_file
2104 .iter()
2105 .map(|(file, visible)| {
2106 (
2107 file.clone(),
2108 visible
2109 .iter()
2110 .map(|unit| unit.source().clone())
2111 .chain(std::iter::once(file.clone()))
2112 .collect(),
2113 )
2114 })
2115 .collect();
2116 let mut global_field_internal_linkage = HashMap::default();
2117 Self {
2118 cpp,
2119 token,
2120 data: Arc::new(VisibilityIndexData {
2121 visible_by_identifier: build_visible_identifier_index(
2122 &CppGraphSource::from_source(cpp, token),
2123 &visible_by_file,
2124 &visible_source_files_by_root,
2125 &mut global_field_internal_linkage,
2126 ),
2127 global_field_internal_linkage,
2128 visible_by_file,
2129 visible_source_files_by_root,
2130 alias_cells: Mutex::new(HashMap::default()),
2131 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2132 parser_alias_target_matches: RwLock::new(HashMap::default()),
2133 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2134 project_using_index: OnceLock::new(),
2135 namespace_alias_sites: Mutex::new(HashMap::default()),
2136 callable_reference_specs: Mutex::new(HashMap::default()),
2137 structured_include_fact_cells: Mutex::new(HashMap::default()),
2138 include_activation_cells: Mutex::new(HashMap::default()),
2139 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2140 include_path_admission_cells: Mutex::new(HashMap::default()),
2141 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2142 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2143 conditional_include_projection_state_count: AtomicUsize::new(0),
2144 conditional_include_target_state_count: AtomicUsize::new(0),
2145 include_activation_build_count: AtomicUsize::new(0),
2146 using_donor_activation_count: AtomicUsize::new(0),
2147 using_namespace_lookup_count: AtomicUsize::new(0),
2148 using_name_candidate_inspection_count: AtomicUsize::new(0),
2149 callable_reference_spec_build_count: AtomicUsize::new(0),
2150 alias_source_parse_counts: Mutex::new(HashMap::default()),
2151 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2152 parser_alias_fallback_calls: AtomicUsize::new(0),
2153 parser_alias_fallback_files: AtomicUsize::new(0),
2154 parser_alias_source_parses: AtomicUsize::new(0),
2155 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2156 field_type_facts: Mutex::new(HashMap::default()),
2157 structured_alias_targets: Mutex::new(HashMap::default()),
2158 callable_comparables: Mutex::new(HashMap::default()),
2159 class_definition_agreements: Mutex::new(HashMap::default()),
2160 callable_metadata: Mutex::new(HashMap::default()),
2161 comparable_name_declarations: Mutex::new(HashMap::default()),
2162 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2163 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2164 precise_parent_cache: Mutex::new(HashMap::default()),
2165 c_tag_kind_cache: Mutex::new(HashMap::default()),
2166 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2167 macro_event_cells: Mutex::new(HashMap::default()),
2168 macro_event_name_sets: Mutex::new(HashMap::default()),
2169 macro_include_protection_cells: Mutex::new(HashMap::default()),
2170 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2171 macro_replacements: Mutex::new(HashMap::default()),
2172 macro_local_binding_templates: Mutex::new(HashMap::default()),
2173 macro_lexical_templates: Mutex::new(HashMap::default()),
2174 macro_lexical_indexes: Mutex::new(HashMap::default()),
2175 macro_replacement_bodies: Mutex::new(HashMap::default()),
2176 macro_type_parameters: Mutex::new(HashMap::default()),
2177 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2178 macro_replacement_parse_count: AtomicUsize::new(0),
2179 macro_event_application_count: AtomicUsize::new(0),
2180 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2181 macro_environment_copy_count: AtomicUsize::new(0),
2182 macro_environment_request_count: AtomicUsize::new(0),
2183 cpp_template_metadata: HashMap::default(),
2184 cpp_template_families: HashMap::default(),
2185 qualified_candidate_inspections: AtomicUsize::new(0),
2186 target_preserving_type_resolution_count: AtomicUsize::new(0),
2187 visibility_identifier_lookup_count: 0,
2188 visibility_identifier_batch_count: 0,
2189 }),
2190 }
2191 }
2192
2193 fn cpp_source(&self) -> CppGraphSource<'a> {
2200 CppGraphSource::from_source(self.cpp, self.token)
2201 }
2202
2203 pub fn build(
2204 cpp: &'a dyn CppSource,
2205 token: QueryToken<'a>,
2206 analyzer: &CppGraphSource<'_>,
2207 roots: &HashSet<ProjectFile>,
2208 ) -> Self {
2209 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2210 }
2211
2212 pub fn build_with_cancellation(
2213 cpp: &'a dyn CppSource,
2214 token: QueryToken<'a>,
2215 analyzer: &CppGraphSource<'_>,
2216 roots: &HashSet<ProjectFile>,
2217 cancellation: Option<&CancellationToken>,
2218 ) -> Self {
2219 let visibility_started = Instant::now();
2220 let include_targets = cpp.include_target_index();
2221 let includes_started = Instant::now();
2222 let mut include_graph = IncludeGraph::default();
2223 for root in roots {
2224 include_graph.extend_with(root, cancellation, &mut |file| {
2225 cpp.canonical_include_paths(token, file)
2226 .unwrap_or_default()
2227 .into_iter()
2228 .flat_map(|include| {
2229 resolve_include_targets_with_index(file, &include, include_targets)
2230 })
2231 .collect()
2232 });
2233 }
2234 let include_elapsed = includes_started.elapsed();
2235 let include_file_count = include_graph.files().count();
2236 let visible_source_files_by_root = roots
2237 .iter()
2238 .map(|root| {
2239 (
2240 root.clone(),
2241 include_graph.reachable_files(root, cancellation),
2242 )
2243 })
2244 .collect::<HashMap<_, _>>();
2245 let mut visibility_stats = BoundedVisibilityStats::default();
2246 let mut visible_by_file = build_bounded_visible_declarations(
2247 cpp,
2248 token,
2249 analyzer,
2250 roots,
2251 &visible_source_files_by_root,
2252 cancellation,
2253 &mut visibility_stats,
2254 );
2255 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2256 eprintln!(
2257 "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={}",
2258 visibility_started.elapsed().as_millis(),
2259 include_elapsed.as_millis(),
2260 include_file_count,
2261 visibility_stats.rounds,
2262 visibility_stats.root_names,
2263 visibility_stats.identifier_lookups,
2264 visibility_stats.identifier_batches,
2265 visibility_stats.candidate_units,
2266 visibility_stats.candidate_sources,
2267 visibility_stats.declaration_reads,
2268 visibility_stats.declaration_units,
2269 visibility_stats.selected_units,
2270 visibility_stats.dependency_source_names,
2271 visibility_stats.dependency_names,
2272 visibility_stats.lookup_elapsed.as_millis(),
2273 visibility_stats.declaration_elapsed.as_millis(),
2274 visibility_stats.dependency_source_elapsed.as_millis(),
2275 );
2276 }
2277 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2278 let finalize_started = Instant::now();
2279 if report_stats {
2280 eprintln!(
2281 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2282 visible_by_file.len(),
2283 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2284 );
2285 }
2286 let owner_started = Instant::now();
2287 if report_stats {
2288 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2289 }
2290 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2291 if report_stats {
2292 eprintln!(
2293 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2294 owner_stats.unseen_owners,
2295 owner_stats.definition_lookups,
2296 owner_stats.admitted,
2297 owner_started.elapsed().as_millis(),
2298 );
2299 }
2300 let mut global_field_internal_linkage = HashMap::default();
2301 let identifier_started = Instant::now();
2302 if report_stats {
2303 eprintln!(
2304 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2305 );
2306 }
2307 let visible_by_identifier = build_visible_identifier_index(
2308 analyzer,
2309 &visible_by_file,
2310 &visible_source_files_by_root,
2311 &mut global_field_internal_linkage,
2312 );
2313 if report_stats {
2314 eprintln!(
2315 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2316 visible_by_identifier.len(),
2317 visible_by_identifier
2318 .values()
2319 .map(HashMap::len)
2320 .sum::<usize>(),
2321 visible_by_identifier
2322 .values()
2323 .flat_map(HashMap::values)
2324 .map(Vec::len)
2325 .sum::<usize>(),
2326 identifier_started.elapsed().as_millis(),
2327 );
2328 }
2329 let mut cpp_template_metadata = HashMap::default();
2330 let metadata_started = Instant::now();
2331 let mut template_classes = 0usize;
2332 if report_stats {
2333 eprintln!(
2334 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2335 );
2336 }
2337 for unit in visible_by_file
2338 .values()
2339 .flatten()
2340 .filter(|unit| unit.is_class())
2341 {
2342 template_classes += 1;
2343 if cpp_template_metadata.contains_key(unit) {
2344 continue;
2345 }
2346 if let Some(metadata) = cpp.template_metadata(unit) {
2347 cpp_template_metadata.insert(unit.clone(), metadata);
2348 }
2349 }
2350 if report_stats {
2351 eprintln!(
2352 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2353 template_classes,
2354 cpp_template_metadata.len(),
2355 metadata_started.elapsed().as_millis(),
2356 );
2357 }
2358 let families_started = Instant::now();
2359 if report_stats {
2360 eprintln!(
2361 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2362 );
2363 }
2364 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2365 for (unit, metadata) in &cpp_template_metadata {
2366 cpp_template_families
2367 .entry(metadata.primary_fq_name.clone())
2368 .or_default()
2369 .push(unit.clone());
2370 }
2371 for family in cpp_template_families.values_mut() {
2380 sort_lookup_units(family);
2381 }
2382 if report_stats {
2383 eprintln!(
2384 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2385 cpp_template_families.len(),
2386 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2387 families_started.elapsed().as_millis(),
2388 );
2389 eprintln!(
2390 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2391 visible_by_file.len(),
2392 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2393 finalize_started.elapsed().as_millis(),
2394 visibility_started.elapsed().as_millis(),
2395 );
2396 }
2397 Self {
2398 cpp,
2399 token,
2400 data: Arc::new(VisibilityIndexData {
2401 visible_by_file,
2402 visible_by_identifier,
2403 global_field_internal_linkage,
2404 visible_source_files_by_root,
2405 alias_cells: Mutex::new(HashMap::default()),
2406 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2407 parser_alias_target_matches: RwLock::new(HashMap::default()),
2408 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2409 project_using_index: OnceLock::new(),
2410 namespace_alias_sites: Mutex::new(HashMap::default()),
2411 callable_reference_specs: Mutex::new(HashMap::default()),
2412 structured_include_fact_cells: Mutex::new(HashMap::default()),
2413 include_activation_cells: Mutex::new(HashMap::default()),
2414 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2415 include_path_admission_cells: Mutex::new(HashMap::default()),
2416 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2417 #[cfg(any(test, feature = "test-support"))]
2418 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2419 #[cfg(any(test, feature = "test-support"))]
2420 conditional_include_projection_state_count: AtomicUsize::new(0),
2421 #[cfg(any(test, feature = "test-support"))]
2422 conditional_include_target_state_count: AtomicUsize::new(0),
2423 #[cfg(any(test, feature = "test-support"))]
2424 include_activation_build_count: AtomicUsize::new(0),
2425 #[cfg(any(test, feature = "test-support"))]
2426 using_donor_activation_count: AtomicUsize::new(0),
2427 #[cfg(any(test, feature = "test-support"))]
2428 using_namespace_lookup_count: AtomicUsize::new(0),
2429 #[cfg(any(test, feature = "test-support"))]
2430 using_name_candidate_inspection_count: AtomicUsize::new(0),
2431 #[cfg(any(test, feature = "test-support"))]
2432 callable_reference_spec_build_count: AtomicUsize::new(0),
2433 #[cfg(any(test, feature = "test-support"))]
2434 alias_source_parse_counts: Mutex::new(HashMap::default()),
2435 #[cfg(any(test, feature = "test-support"))]
2436 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2437 parser_alias_fallback_calls: AtomicUsize::new(0),
2438 parser_alias_fallback_files: AtomicUsize::new(0),
2439 parser_alias_source_parses: AtomicUsize::new(0),
2440 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2441 field_type_facts: Mutex::new(HashMap::default()),
2442 structured_alias_targets: Mutex::new(HashMap::default()),
2443 callable_comparables: Mutex::new(HashMap::default()),
2444 class_definition_agreements: Mutex::new(HashMap::default()),
2445 callable_metadata: Mutex::new(HashMap::default()),
2446 comparable_name_declarations: Mutex::new(HashMap::default()),
2447 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2448 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2449 precise_parent_cache: Mutex::new(HashMap::default()),
2450 c_tag_kind_cache: Mutex::new(HashMap::default()),
2451 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2452 macro_event_cells: Mutex::new(HashMap::default()),
2453 macro_event_name_sets: Mutex::new(HashMap::default()),
2454 macro_include_protection_cells: Mutex::new(HashMap::default()),
2455 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2456 macro_replacements: Mutex::new(HashMap::default()),
2457 macro_local_binding_templates: Mutex::new(HashMap::default()),
2458 macro_lexical_templates: Mutex::new(HashMap::default()),
2459 macro_lexical_indexes: Mutex::new(HashMap::default()),
2460 macro_replacement_bodies: Mutex::new(HashMap::default()),
2461 macro_type_parameters: Mutex::new(HashMap::default()),
2462 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2463 #[cfg(any(test, feature = "test-support"))]
2464 macro_replacement_parse_count: AtomicUsize::new(0),
2465 #[cfg(any(test, feature = "test-support"))]
2466 macro_event_application_count: AtomicUsize::new(0),
2467 #[cfg(any(test, feature = "test-support"))]
2468 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2469 #[cfg(any(test, feature = "test-support"))]
2470 macro_environment_copy_count: AtomicUsize::new(0),
2471 #[cfg(any(test, feature = "test-support"))]
2472 macro_environment_request_count: AtomicUsize::new(0),
2473 cpp_template_metadata,
2474 cpp_template_families,
2475 #[cfg(any(test, feature = "test-support"))]
2476 qualified_candidate_inspections: AtomicUsize::new(0),
2477 #[cfg(any(test, feature = "test-support"))]
2478 target_preserving_type_resolution_count: AtomicUsize::new(0),
2479 #[cfg(any(test, feature = "test-support"))]
2480 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2481 #[cfg(any(test, feature = "test-support"))]
2482 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2483 }),
2484 }
2485 }
2486
2487 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2488 if file == target.source() {
2489 return true;
2490 }
2491 if self.global_field_has_internal_linkage(target) {
2492 return self
2493 .visible_source_files_by_root
2494 .get(file)
2495 .is_some_and(|sources| sources.contains(target.source()));
2496 }
2497 self.visible_by_file
2498 .get(file)
2499 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2500 }
2501
2502 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2503 self.global_field_internal_linkage
2504 .get(unit)
2505 .copied()
2506 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2507 }
2508
2509 pub fn call_arity_evidence(
2510 &self,
2511 file: &ProjectFile,
2512 call: Node<'_>,
2513 source: &str,
2514 ) -> CallArityEvidence {
2515 self.call_arity_evidence_at(file, call, source, call.start_byte())
2516 }
2517
2518 pub fn call_arity_evidence_at(
2526 &self,
2527 file: &ProjectFile,
2528 call: Node<'_>,
2529 source: &str,
2530 environment_byte: usize,
2531 ) -> CallArityEvidence {
2532 let Some(arguments) = call
2533 .child_by_field_name("arguments")
2534 .or_else(|| call.child_by_field_name("parameters"))
2535 .or_else(|| call.child_by_field_name("value"))
2536 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2537 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2538 else {
2539 return CallArityEvidence::Exact(0);
2540 };
2541 let recovered_c_keyword_arguments =
2542 recovered_c_keyword_argument_count(file, call, arguments, source);
2543 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2544 let arguments = argument_children(arguments)
2545 .flat_map(|argument| {
2546 recovered_c_new_expression_arguments(argument, c_semantics)
2547 .map(Vec::from)
2548 .unwrap_or_else(|| vec![argument])
2549 })
2550 .collect::<Vec<_>>();
2551 if arguments
2552 .iter()
2553 .all(|argument| !argument_shape_may_change_arity(*argument))
2554 {
2555 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2556 }
2557 let environment = self.macro_environment(file, environment_byte);
2558 let mut stack = Vec::new();
2559 let mut total = recovered_c_keyword_arguments;
2560 for argument in arguments {
2561 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2562 return CallArityEvidence::Unknown;
2563 }
2564 let CallArityEvidence::Exact(spread) =
2565 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2566 else {
2567 return CallArityEvidence::Unknown;
2568 };
2569 total += spread;
2570 }
2571 CallArityEvidence::Exact(total)
2572 }
2573
2574 fn argument_arity_evidence(
2575 &self,
2576 argument: Node<'_>,
2577 source: &str,
2578 environment: &MacroEnvironment,
2579 stack: &mut Vec<(ProjectFile, usize)>,
2580 ) -> CallArityEvidence {
2581 let (name, invocation_arguments, function_like) = match argument.kind() {
2582 "identifier" => (node_text(argument, source), None, false),
2583 "call_expression" => {
2584 let Some(function) = argument.child_by_field_name("function") else {
2585 return CallArityEvidence::Exact(1);
2586 };
2587 if function.kind() != "identifier" {
2588 return CallArityEvidence::Exact(1);
2589 }
2590 let Some(arguments) = argument.child_by_field_name("arguments") else {
2591 return CallArityEvidence::Exact(1);
2592 };
2593 (node_text(function, source), Some(arguments), true)
2594 }
2595 _ => return CallArityEvidence::Exact(1),
2596 };
2597 let Some(binding) = environment.binding(name) else {
2598 return if environment.unknown_names {
2599 CallArityEvidence::Unknown
2600 } else {
2601 CallArityEvidence::Exact(1)
2602 };
2603 };
2604 if !binding.is_exact() {
2605 return CallArityEvidence::Unknown;
2606 }
2607 match (&binding.definition, invocation_arguments, function_like) {
2608 (MacroDefinition::Object { replacement }, None, false) => self
2609 .replacement_arity_evidence(
2610 replacement,
2611 &[],
2612 &[],
2613 source,
2614 environment,
2615 stack,
2616 binding,
2617 ),
2618 (
2619 MacroDefinition::Function {
2620 parameters,
2621 replacement,
2622 },
2623 Some(arguments),
2624 true,
2625 ) => {
2626 let actuals = argument_children(arguments).collect::<Vec<_>>();
2627 if actuals.len() != parameters.len() {
2628 CallArityEvidence::Unknown
2629 } else {
2630 self.replacement_arity_evidence(
2631 replacement,
2632 parameters,
2633 &actuals,
2634 source,
2635 environment,
2636 stack,
2637 binding,
2638 )
2639 }
2640 }
2641 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2642 _ => CallArityEvidence::Unknown,
2643 }
2644 }
2645
2646 #[allow(clippy::too_many_arguments)]
2647 fn replacement_arity_evidence(
2648 &self,
2649 replacement: &str,
2650 parameters: &[String],
2651 actuals: &[Node<'_>],
2652 actual_source: &str,
2653 environment: &MacroEnvironment,
2654 stack: &mut Vec<(ProjectFile, usize)>,
2655 binding: &MacroBinding,
2656 ) -> CallArityEvidence {
2657 let identity = (binding.source.clone(), binding.declaration_byte);
2658 if stack.contains(&identity) || replacement.trim().is_empty() {
2659 return CallArityEvidence::Unknown;
2660 }
2661 stack.push(identity);
2662 let parsed = self.parsed_macro_replacement(binding, replacement);
2663 let evidence = (|| {
2664 let ParsedMacroReplacement::Parsed {
2665 source: sentinel,
2666 tree,
2667 } = parsed.as_ref()
2668 else {
2669 return None;
2670 };
2671 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2672 let arguments = call.child_by_field_name("arguments")?;
2673 let mut total = 0usize;
2674 for argument in argument_children(arguments) {
2675 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2676 return None;
2677 }
2678 if argument.kind() == "identifier"
2679 && let Some(parameter_index) = parameters
2680 .iter()
2681 .position(|parameter| parameter == node_text(argument, sentinel))
2682 {
2683 if !macro_expansion_shape_is_safe(
2684 actuals[parameter_index],
2685 actual_source,
2686 &[],
2687 environment,
2688 ) {
2689 return None;
2690 }
2691 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2692 actuals[parameter_index],
2693 actual_source,
2694 environment,
2695 stack,
2696 ) else {
2697 return None;
2698 };
2699 total += spread;
2700 continue;
2701 }
2702 let CallArityEvidence::Exact(spread) =
2703 self.argument_arity_evidence(argument, sentinel, environment, stack)
2704 else {
2705 return None;
2706 };
2707 total += spread;
2708 }
2709 Some(CallArityEvidence::Exact(total))
2710 })()
2711 .unwrap_or(CallArityEvidence::Unknown);
2712 stack.pop();
2713 evidence
2714 }
2715
2716 fn parsed_macro_replacement(
2717 &self,
2718 binding: &MacroBinding,
2719 replacement: &str,
2720 ) -> Arc<ParsedMacroReplacement> {
2721 let key = (binding.source.clone(), binding.declaration_byte);
2722 let mut cache = self
2723 .macro_replacements
2724 .lock()
2725 .expect("C++ macro replacement cache poisoned");
2726 if let Some(parsed) = cache.get(&key) {
2727 return Arc::clone(parsed);
2728 }
2729 #[cfg(any(test, feature = "test-support"))]
2730 self.macro_replacement_parse_count
2731 .fetch_add(1, Ordering::Relaxed);
2732 let parsed = parse_sentinel(
2733 "void __bifrost_macro_arity() { __bifrost_macro_call(",
2734 SentinelInput::replacement(replacement),
2735 "); }",
2736 );
2737 let parsed = if parsed.tree.root_node().has_error() {
2738 ParsedMacroReplacement::Unsupported
2739 } else {
2740 ParsedMacroReplacement::Parsed {
2741 source: parsed.source,
2742 tree: parsed.tree,
2743 }
2744 };
2745 let parsed = Arc::new(parsed);
2746 cache.insert(key, Arc::clone(&parsed));
2747 parsed
2748 }
2749
2750 pub fn function_macro_local_binding<'tree>(
2760 &self,
2761 file: &ProjectFile,
2762 statement: Node<'tree>,
2763 source: &str,
2764 ) -> Option<MacroLocalBinding<'tree>> {
2765 if !is_c_source_file(file) {
2766 return None;
2767 }
2768 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2769 return Some(binding);
2770 }
2771 let call = match statement.kind() {
2772 "expression_statement" => {
2773 single_named_child_matching(statement, |child| !child.is_extra())?
2774 }
2775 _ => statement,
2776 };
2777 let (function, actuals) = function_macro_invocation_parts(call)?;
2778 let environment = self.macro_environment(file, call.start_byte());
2779 let function_name = node_text(function, source);
2780 let binding = environment.binding(function_name)?;
2781 let MacroDefinition::Function {
2782 parameters,
2783 replacement,
2784 } = &binding.definition
2785 else {
2786 return None;
2787 };
2788 if actuals.len() != parameters.len() {
2789 return None;
2790 }
2791 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2792 let (type_name, type_node) = match &template.declared_type {
2793 MacroLocalBindingTypeTemplate::Parameter(index) => {
2794 let actual = *actuals.get(*index)?;
2795 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2796 return None;
2797 }
2798 let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2799 (
2800 node_text(actual, source).trim().to_string(),
2801 Some(type_node),
2802 )
2803 }
2804 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2805 };
2806 if type_name.is_empty() {
2807 return None;
2808 }
2809 Some(MacroLocalBinding {
2810 name: template.name.clone(),
2811 type_name,
2812 type_node,
2813 pointer_depth: template.pointer_depth,
2814 proven_unit: None,
2815 })
2816 }
2817
2818 pub fn function_macro_container_binding<'tree>(
2827 &self,
2828 analyzer: &CppGraphSource<'_>,
2829 file: &ProjectFile,
2830 assignment: Node<'tree>,
2831 source: &str,
2832 ) -> Option<MacroLocalBinding<'tree>> {
2833 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2834 return None;
2835 }
2836 let name_node = assignment.child_by_field_name("left")?;
2837 if name_node.kind() != "identifier" {
2838 return None;
2839 }
2840 let call = assignment.child_by_field_name("right")?;
2841 if call.kind() != "call_expression" {
2842 return None;
2843 }
2844 let function = call.child_by_field_name("function")?;
2845 if function.kind() != "identifier" {
2846 return None;
2847 }
2848 let arguments = call.child_by_field_name("arguments")?;
2849 let actuals = argument_children(arguments).collect::<Vec<_>>();
2850 let function_name = node_text(function, source);
2851 let environment = self.macro_environment(file, call.start_byte());
2852 let binding = environment.binding(function_name)?;
2853 if !binding.is_exact() {
2854 return None;
2855 }
2856 let MacroDefinition::Function {
2857 parameters,
2858 replacement,
2859 } = &binding.definition
2860 else {
2861 return None;
2862 };
2863 if actuals.len() != parameters.len() {
2864 return None;
2865 }
2866 let body = self.parsed_macro_replacement_body(
2867 &(binding.source.clone(), binding.declaration_byte),
2868 parameters,
2869 replacement,
2870 )?;
2871 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2872 let type_argument = *actuals.get(type_parameter)?;
2873 let type_node = macro_type_argument_node(type_argument, source)?;
2874 let type_name = node_text(type_node, source).trim().to_string();
2875 if type_name.is_empty() {
2876 return None;
2877 }
2878 let explicit_tag = match node_text(type_argument, source) {
2879 "struct" => Some(CppCTagKind::Struct),
2880 "union" => Some(CppCTagKind::Union),
2881 _ => None,
2882 };
2883 let resolved_type = if let Some(tag) = explicit_tag {
2884 let candidates = self
2885 .visible_identifier_candidates(file, &type_name)
2886 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2887 .collect::<Vec<_>>();
2888 self.resolve_type_candidates(
2889 analyzer,
2890 file,
2891 &candidates,
2892 TypeCandidateResolution::Canonical,
2893 )
2894 .ok()
2895 } else {
2896 self.resolve_type_node_result(file, type_node, source)
2897 .ok()
2898 .flatten()
2899 };
2900 let proven_unit = resolved_type?;
2901 Some(MacroLocalBinding {
2902 name: node_text(name_node, source).to_string(),
2903 type_name,
2904 type_node: Some(type_node),
2905 pointer_depth: 1,
2906 proven_unit: Some(proven_unit),
2907 })
2908 }
2909
2910 pub fn macro_local_binding_at<'tree>(
2913 &self,
2914 file: &ProjectFile,
2915 root: Node<'tree>,
2916 source: &str,
2917 start_byte: usize,
2918 end_byte: usize,
2919 ) -> Option<MacroLocalBinding<'tree>> {
2920 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2921 }
2922
2923 pub fn macro_lexical_binding(
2927 &self,
2928 file: &ProjectFile,
2929 root: Node<'_>,
2930 source: &str,
2931 start_byte: usize,
2932 end_byte: usize,
2933 ) -> Option<MacroLexicalBinding> {
2934 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2935 }
2936
2937 pub fn macro_lexical_references(
2942 &self,
2943 file: &ProjectFile,
2944 root: Node<'_>,
2945 source: &str,
2946 max_references: usize,
2947 cancelled: impl FnMut() -> bool,
2948 ) -> MacroLexicalReferences {
2949 crate::graph::macro_lexical::all_references(
2950 || self,
2951 file,
2952 root,
2953 source,
2954 max_references,
2955 cancelled,
2956 )
2957 }
2958
2959 pub(crate) fn function_macro_binding_at(
2964 &self,
2965 file: &ProjectFile,
2966 name: &str,
2967 before_byte: usize,
2968 ) -> Option<(ProjectFile, usize)> {
2969 let environment = self.macro_environment(file, before_byte);
2970 let binding = environment.binding(name)?;
2971 if binding.is_exact()
2972 && matches!(
2973 binding.definition,
2974 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2975 )
2976 {
2977 return Some((binding.source.clone(), binding.declaration_byte));
2978 }
2979 if binding.source != *file {
2985 return None;
2986 }
2987 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2988 let root = prepared.tree().root_node();
2989 let source = prepared.source();
2990 let reference = root.descendant_for_byte_range(
2991 before_byte,
2992 before_byte.saturating_add(1).min(source.len()),
2993 )?;
2994 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2995 let cell = self.macro_event_cell(file);
2996 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2997 let event = events
2998 .iter()
2999 .find(|event| event.byte() == binding.declaration_byte)?;
3000 let MacroEvent::Define {
3001 name: defined_name,
3002 binding: definition,
3003 conditionals,
3004 ..
3005 } = event
3006 else {
3007 return None;
3008 };
3009 if defined_name != name
3010 || conditionals.is_empty()
3011 || !conditionals
3012 .iter()
3013 .all(|condition| reference_conditions.contains(condition))
3014 || !matches!(
3015 definition.definition,
3016 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3017 )
3018 {
3019 return None;
3020 }
3021 Some((definition.source.clone(), definition.declaration_byte))
3022 }
3023
3024 pub fn function_macro_type_argument<'tree>(
3031 &self,
3032 file: &ProjectFile,
3033 node: Node<'tree>,
3034 source: &str,
3035 ) -> Option<Node<'tree>> {
3036 if !is_c_source_file(file) {
3037 return None;
3038 }
3039 let (call, argument_index, argument) = c_function_macro_argument(node)?;
3040 let function = call.child_by_field_name("function")?;
3041 let function_name = node_text(function, source);
3042 if function_name.is_empty() {
3043 return None;
3044 }
3045 if !self.file_defines_macro_name(file, function_name)
3046 && !self
3047 .visible_identifier_candidates(file, function_name)
3048 .any(|candidate| candidate.is_macro())
3049 {
3050 return None;
3051 }
3052 let environment = self.macro_environment(file, call.start_byte());
3053 let binding = environment.binding(function_name)?;
3054 if !binding.is_exact() {
3055 return None;
3056 }
3057 let (parameters, replacement, variadic) = match &binding.definition {
3058 MacroDefinition::Function {
3059 parameters,
3060 replacement,
3061 } => (parameters, replacement, false),
3062 MacroDefinition::VariadicFunction {
3063 parameters,
3064 replacement,
3065 } => (parameters, replacement, true),
3066 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3067 };
3068 let actuals = call
3069 .child_by_field_name("arguments")
3070 .map(argument_children)
3071 .into_iter()
3072 .flatten()
3073 .collect::<Vec<_>>();
3074 let arity_matches = if variadic {
3075 actuals.len() >= parameters.len()
3076 } else {
3077 actuals.len() == parameters.len()
3078 };
3079 let type_parameters = self.macro_type_parameter_indices(
3080 &(binding.source.clone(), binding.declaration_byte),
3081 parameters,
3082 replacement,
3083 )?;
3084 if !arity_matches || !type_parameters.contains(&argument_index) {
3085 return None;
3086 }
3087 let type_node = macro_type_argument_node(argument, source)?;
3088 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3089 return None;
3090 }
3091 Some(type_node)
3092 }
3093
3094 fn macro_local_binding_template(
3095 &self,
3096 binding: &MacroBinding,
3097 parameters: &[String],
3098 replacement: &str,
3099 ) -> Option<Arc<MacroLocalBindingTemplate>> {
3100 let key = (binding.source.clone(), binding.declaration_byte);
3101 if let Some(template) = self
3102 .macro_local_binding_templates
3103 .lock()
3104 .expect("C++ macro local-binding cache poisoned")
3105 .get(&key)
3106 {
3107 return template.clone();
3108 }
3109 let template = (|| {
3110 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3111 let sentinel = body.source.as_str();
3112 let statements = body.statements()?;
3113 let declaration = single_named_child_matching(statements, |child| !child.is_extra())?;
3114 if declaration.kind() != "declaration" {
3115 return None;
3116 }
3117 let type_node = declaration
3118 .child_by_field_name("type")
3119 .or_else(|| first_type_child(declaration))?;
3120 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3121 let mut cursor = declaration.walk();
3122 declaration.named_children(&mut cursor).find_map(|child| {
3123 if child.kind() == "init_declarator" {
3124 child.child_by_field_name("declarator")
3125 } else {
3126 is_declarator_node(child).then_some(child)
3127 }
3128 })
3129 })?;
3130 let name = extract_variable_name(declarator, sentinel)?;
3131 let pointer_depth =
3132 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3133 let type_text = node_text(type_node, sentinel).trim();
3134 let declared_type = parameters
3135 .iter()
3136 .position(|parameter| parameter == type_text)
3137 .map(MacroLocalBindingTypeTemplate::Parameter)
3138 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3139 Some(Arc::new(MacroLocalBindingTemplate {
3140 name,
3141 declared_type,
3142 pointer_depth,
3143 }))
3144 })();
3145 self.macro_local_binding_templates
3146 .lock()
3147 .expect("C++ macro local-binding cache poisoned")
3148 .insert(key, template.clone());
3149 template
3150 }
3151
3152 pub fn function_macro_replacement_body(
3159 &self,
3160 file: &ProjectFile,
3161 definition: Node<'_>,
3162 source: &str,
3163 ) -> Option<Arc<ParsedReplacementBody>> {
3164 debug_assert_eq!(definition.kind(), "preproc_function_def");
3165 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3166 MacroDefinition::Function {
3167 parameters,
3168 replacement,
3169 }
3170 | MacroDefinition::VariadicFunction {
3171 parameters,
3172 replacement,
3173 } => (parameters, replacement),
3174 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3175 };
3176 self.parsed_macro_replacement_body(
3177 &(file.clone(), definition.start_byte()),
3178 ¶meters,
3179 &replacement,
3180 )
3181 }
3182
3183 fn parsed_macro_replacement_body(
3191 &self,
3192 key: &(ProjectFile, usize),
3193 parameters: &[String],
3194 replacement: &str,
3195 ) -> Option<Arc<ParsedReplacementBody>> {
3196 if let Some(body) = self
3197 .macro_replacement_bodies
3198 .lock()
3199 .expect("C++ macro replacement body cache poisoned")
3200 .get(key)
3201 {
3202 return body.clone();
3203 }
3204 let body = (|| {
3205 if replacement.trim().is_empty() {
3206 return None;
3207 }
3208 let (source, tree, original_offsets) =
3209 Self::parse_macro_replacement_body(replacement, parameters)?;
3210 let body = ParsedReplacementBody {
3211 source,
3212 tree,
3213 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3214 parameters: parameters.to_vec(),
3215 original_offsets,
3216 };
3217 body.statements()?;
3218 if body.expands_variadic_arguments() {
3219 return None;
3220 }
3221 Some(Arc::new(body))
3222 })();
3223 self.macro_replacement_bodies
3224 .lock()
3225 .expect("C++ macro replacement body cache poisoned")
3226 .insert(key.clone(), body.clone());
3227 body
3228 }
3229
3230 fn parse_macro_replacement_body(
3238 replacement: &str,
3239 parameters: &[String],
3240 ) -> Option<(String, Tree, MacroOriginMap)> {
3241 let parsed = parse_sentinel(
3242 MACRO_BODY_SENTINEL_PREFIX,
3243 SentinelInput::replacement(replacement),
3244 MACRO_BODY_SENTINEL_SUFFIX,
3245 );
3246 let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3247 let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3248 let normalized_offsets = &parsed.original_offsets;
3249 let source = parsed.source.as_str();
3250 let tree = &parsed.tree;
3251 if !tree.root_node().has_error() {
3252 return Some((
3253 parsed.source,
3254 parsed.tree,
3255 parsed.original_offsets.into_boxed_slice(),
3256 ));
3257 }
3258
3259 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3260 let mut insertion_points = Vec::new();
3261 let mut stack = vec![tree.root_node()];
3262 while let Some(node) = stack.pop() {
3263 if matches!(
3264 node.kind(),
3265 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3266 ) && parameters
3267 .iter()
3268 .any(|parameter| parameter.as_str() == node_text(node, source))
3269 && Self::macro_formal_needs_statement_separator(node)
3270 {
3271 let point = node.end_byte().saturating_sub(body_offset);
3272 if point <= normalized.len() && !insertion_points.contains(&point) {
3273 insertion_points.push(point);
3274 }
3275 }
3276 push_named_children_reversed(node, &mut stack);
3277 }
3278 if insertion_points.is_empty() {
3279 return None;
3280 }
3281 insertion_points.sort_unstable();
3282 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3283 let mut original_offsets =
3284 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3285 let mut next_insertion = 0;
3286 for (index, byte) in normalized.bytes().enumerate() {
3287 recovered.push(byte);
3288 original_offsets.push(normalized_offsets[index].clone());
3289 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3290 recovered.push(b';');
3291 let boundary = normalized_offsets[index].end;
3292 original_offsets.push(boundary..boundary);
3293 next_insertion += 1;
3294 }
3295 }
3296 original_offsets.push(replacement.len()..replacement.len());
3297 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3298 let parsed = parse_sentinel(
3299 MACRO_BODY_SENTINEL_PREFIX,
3300 SentinelInput {
3301 source: recovered,
3302 original_offsets,
3303 },
3304 MACRO_BODY_SENTINEL_SUFFIX,
3305 );
3306 let (source, tree, original_offsets) =
3307 (parsed.source, parsed.tree, parsed.original_offsets);
3308 if tree.root_node().has_error() {
3309 return None;
3310 }
3311 Some((source, tree, original_offsets.into_boxed_slice()))
3312 }
3313
3314 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3319 let mut current = node;
3320 let mut crossed_recovery = false;
3321 while let Some(parent) = current.parent() {
3322 if parent.is_error() {
3323 crossed_recovery = true;
3324 current = parent;
3325 continue;
3326 }
3327 if matches!(
3328 parent.kind(),
3329 "call_expression"
3330 | "argument_list"
3331 | "field_expression"
3332 | "binary_expression"
3333 | "unary_expression"
3334 | "assignment_expression"
3335 | "conditional_expression"
3336 | "parenthesized_expression"
3337 | "subscript_expression"
3338 ) {
3339 return false;
3340 }
3341 if parent.kind() == "expression_statement" {
3342 return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3343 }
3344 if parent.kind() == "compound_statement" && current == node {
3345 return true;
3346 }
3347 if matches!(
3348 parent.kind(),
3349 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3350 ) {
3351 return crossed_recovery
3352 || parent.child_by_field_name("consequence") == Some(current);
3353 }
3354 if matches!(parent.kind(), "declaration" | "init_declarator") {
3355 return false;
3356 }
3357 current = parent;
3358 }
3359 false
3360 }
3361
3362 fn macro_type_parameter_indices(
3370 &self,
3371 key: &(ProjectFile, usize),
3372 parameters: &[String],
3373 replacement: &str,
3374 ) -> Option<Arc<[usize]>> {
3375 if let Some(indices) = self
3376 .macro_type_parameters
3377 .lock()
3378 .expect("C++ macro type-parameter cache poisoned")
3379 .get(key)
3380 {
3381 return indices.clone();
3382 }
3383 #[cfg(any(test, feature = "test-support"))]
3384 self.macro_replacement_parse_count
3385 .fetch_add(1, Ordering::Relaxed);
3386 let indices = (|| {
3387 if replacement.trim().is_empty() {
3388 return None;
3389 }
3390 let parsed = parse_sentinel(
3394 MACRO_BODY_SENTINEL_PREFIX,
3395 SentinelInput::replacement(replacement),
3396 MACRO_BODY_SENTINEL_SUFFIX,
3397 );
3398 let (source, tree, original_offsets) =
3399 (parsed.source, parsed.tree, parsed.original_offsets);
3400 let body = ParsedReplacementBody {
3401 source,
3402 tree,
3403 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3404 parameters: parameters.to_vec(),
3405 original_offsets: original_offsets.into_boxed_slice(),
3406 };
3407 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3408 })();
3409 self.macro_type_parameters
3410 .lock()
3411 .expect("C++ macro type-parameter cache poisoned")
3412 .insert(key.clone(), indices.clone());
3413 indices
3414 }
3415
3416 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3417 let replacement = if node.kind() == "preproc_function_def" {
3418 function_macro_replacement_span(node, source)
3419 .and_then(|span| source.get(span))
3420 .map(str::to_owned)
3421 .or_else(|| {
3422 node.child_by_field_name("value")
3423 .map(|value| node_text(value, source).to_string())
3424 })
3425 .unwrap_or_default()
3426 } else {
3427 node.child_by_field_name("value")
3428 .map(|value| node_text(value, source).to_string())
3429 .unwrap_or_default()
3430 };
3431 if node.kind() == "preproc_def" {
3432 return MacroDefinition::Object { replacement };
3433 }
3434 let Some(parameters) = node.child_by_field_name("parameters") else {
3435 return MacroDefinition::Unsupported;
3436 };
3437 let variadic = (0..parameters.child_count()).any(|index| {
3438 parameters
3439 .child(index)
3440 .is_some_and(|child| child.kind() == "...")
3441 });
3442 let parameters = (0..parameters.named_child_count())
3443 .filter_map(|index| parameters.named_child(index))
3444 .map(|parameter| node_text(parameter, source).to_string())
3445 .collect::<Vec<_>>();
3446 if variadic {
3447 MacroDefinition::VariadicFunction {
3448 parameters,
3449 replacement,
3450 }
3451 } else {
3452 MacroDefinition::Function {
3453 parameters,
3454 replacement,
3455 }
3456 }
3457 }
3458
3459 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3460 clone_or_default_file_cell(
3461 &self.macro_event_cells,
3462 file,
3463 "C++ macro event cache poisoned",
3464 )
3465 }
3466
3467 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3468 if let Some(names) = self
3469 .macro_event_name_sets
3470 .lock()
3471 .expect("C++ macro event-name cache poisoned")
3472 .get(file)
3473 .cloned()
3474 {
3475 return names.contains(name);
3476 }
3477 let cell = self.macro_event_cell(file);
3478 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3479 let names = Arc::new(
3480 events
3481 .iter()
3482 .filter_map(|event| match event {
3483 MacroEvent::Define { name, .. } => Some(name.clone()),
3484 MacroEvent::Undef { .. }
3485 | MacroEvent::Include { .. }
3486 | MacroEvent::Invalidate { .. } => None,
3487 })
3488 .collect(),
3489 );
3490 self.macro_event_name_sets
3491 .lock()
3492 .expect("C++ macro event-name cache poisoned")
3493 .insert(file.clone(), Arc::clone(&names));
3494 names.contains(name)
3495 }
3496
3497 fn macro_environment_checkpoint_cell(
3498 &self,
3499 file: &ProjectFile,
3500 ) -> MacroEnvironmentCheckpointCell {
3501 clone_or_default_file_cell(
3502 &self.macro_environment_checkpoints,
3503 file,
3504 "C++ macro environment checkpoint cache poisoned",
3505 )
3506 }
3507
3508 pub fn macro_environment(
3510 &self,
3511 file: &ProjectFile,
3512 before_byte: usize,
3513 ) -> Arc<MacroEnvironment> {
3514 #[cfg(any(test, feature = "test-support"))]
3515 self.macro_environment_request_count
3516 .fetch_add(1, Ordering::Relaxed);
3517 let cell = self.macro_event_cell(file);
3518 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3519 let frontier = events.partition_point(|event| event.byte() < before_byte);
3520 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3521 let checkpoints =
3522 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3523 let checkpoint = checkpoints.at_or_before(frontier);
3524 if checkpoint.frontier == frontier {
3525 return Arc::clone(&checkpoint.environment);
3526 }
3527 #[cfg(any(test, feature = "test-support"))]
3528 self.macro_environment_copy_count
3529 .fetch_add(1, Ordering::Relaxed);
3530 let mut environment = checkpoint.environment.as_ref().clone();
3531 let mut include_stack = HashSet::from_iter([file.clone()]);
3532 for event in &events[checkpoint.frontier..frontier] {
3533 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3534 }
3535 Arc::new(environment)
3536 }
3537
3538 fn build_macro_environment_checkpoints(
3541 &self,
3542 file: &ProjectFile,
3543 events: &[MacroEvent],
3544 ) -> MacroEnvironmentCheckpoints {
3545 #[cfg(any(test, feature = "test-support"))]
3546 self.macro_environment_checkpoint_build_count
3547 .fetch_add(1, Ordering::Relaxed);
3548 let mut environment = MacroEnvironment {
3553 build_proven_defines: self
3554 .compile_proven_guards(file)
3555 .iter()
3556 .filter_map(|guard| match guard {
3557 PreprocessorGuard::Defined(name) => Some(name.clone()),
3558 _ => None,
3559 })
3560 .collect(),
3561 ..MacroEnvironment::default()
3562 };
3563 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3564 frontier: 0,
3565 environment: Arc::new(environment.clone()),
3566 }];
3567 let checkpoint_stride = events
3574 .len()
3575 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3576 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3577 let mut include_stack = HashSet::from_iter([file.clone()]);
3578 for (index, event) in events.iter().enumerate() {
3579 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3580 let frontier = index + 1;
3581 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3582 checkpoints.push(MacroEnvironmentCheckpoint {
3583 frontier,
3584 environment: Arc::new(environment.clone()),
3585 });
3586 }
3587 }
3588 MacroEnvironmentCheckpoints { checkpoints }
3589 }
3590
3591 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3600 self.macro_environment(file, before_byte)
3601 .binding(name)
3602 .is_some()
3603 }
3604
3605 pub fn macro_name_may_be_bound_at(
3606 &self,
3607 file: &ProjectFile,
3608 name: &str,
3609 before_byte: usize,
3610 ) -> bool {
3611 self.macro_environment(file, before_byte).may_bind(name)
3612 }
3613
3614 pub fn macro_binding_matches_target_at(
3618 &self,
3619 analyzer: &CppGraphSource<'_>,
3620 file: &ProjectFile,
3621 name: &str,
3622 before_byte: usize,
3623 target: &CodeUnit,
3624 ) -> bool {
3625 let ranges = analyzer.ranges(target);
3626 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3627 self.macro_binding_matches_target_declaration_at(
3628 file,
3629 name,
3630 before_byte,
3631 target.source(),
3632 &declaration_bytes,
3633 )
3634 }
3635
3636 pub(crate) fn macro_declaration_bytes(
3641 &self,
3642 target: &CodeUnit,
3643 ranges: &[Range],
3644 ) -> Vec<usize> {
3645 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3646 return Vec::new();
3647 };
3648 ranges
3649 .iter()
3650 .filter_map(|range| {
3651 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3652 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3653 node = node.parent()?;
3654 }
3655 Some(node.start_byte())
3656 })
3657 .collect()
3658 }
3659
3660 pub(crate) fn macro_binding_matches_target_declaration_at(
3661 &self,
3662 file: &ProjectFile,
3663 name: &str,
3664 before_byte: usize,
3665 target_source: &ProjectFile,
3666 target_declaration_bytes: &[usize],
3667 ) -> bool {
3668 let environment = self.macro_environment(file, before_byte);
3669 let Some(binding) = environment.binding(name) else {
3670 return false;
3671 };
3672 if binding.definition == MacroDefinition::Unsupported {
3673 return false;
3674 }
3675 if binding.source != *target_source {
3679 return false;
3680 }
3681 target_declaration_bytes.contains(&binding.declaration_byte)
3682 }
3683
3684 pub fn resolve_ordinary_macro_reference(
3691 &self,
3692 analyzer: &CppGraphSource<'_>,
3693 file: &ProjectFile,
3694 node: Node<'_>,
3695 source: &str,
3696 ) -> OrdinaryMacroReferenceResolution {
3697 if !is_ordinary_macro_reference_node(node) {
3698 return OrdinaryMacroReferenceResolution::Missing;
3699 }
3700 let name = node_text(node, source);
3701 if name.is_empty() {
3702 return OrdinaryMacroReferenceResolution::Missing;
3703 }
3704 let visible = self
3705 .visible_identifier_candidates(file, name)
3706 .filter(|candidate| candidate.is_macro())
3707 .cloned()
3708 .collect::<Vec<_>>();
3709 let mut exact = Vec::new();
3710 for candidate in &visible {
3711 if self.macro_binding_matches_target_at(
3712 analyzer,
3713 file,
3714 name,
3715 node.start_byte(),
3716 candidate,
3717 ) && !exact
3718 .iter()
3719 .any(|existing| same_visible_symbol(existing, candidate))
3720 {
3721 exact.push(candidate.clone());
3722 }
3723 }
3724 match exact.len() {
3725 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3726 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3727 0 if !visible.is_empty()
3728 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3729 {
3730 OrdinaryMacroReferenceResolution::Ambiguous
3731 }
3732 0 => OrdinaryMacroReferenceResolution::Missing,
3733 }
3734 }
3735
3736 pub fn recovered_c_reference_ranges(
3744 &self,
3745 file: &ProjectFile,
3746 root: Node<'_>,
3747 source: &str,
3748 limit: usize,
3749 ) -> RecoveredCReferenceRanges {
3750 if !is_c_source_file(file) {
3751 return RecoveredCReferenceRanges::Complete(Vec::new());
3752 }
3753 let mut ranges = Vec::new();
3754 let mut seen = HashSet::default();
3755 let mut stack = vec![(root, root.is_error())];
3756 while let Some((node, inside_error)) = stack.pop() {
3757 let inside_error =
3762 inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3763 if node.kind() == "preproc_arg" {
3764 let macro_value_kind = node.parent().and_then(|parent| {
3769 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3770 });
3771 if matches!(
3772 macro_value_kind,
3773 Some("preproc_def" | "preproc_function_def")
3774 ) {
3775 let name = node_text(node, source);
3776 if !name.is_empty()
3777 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3778 && !push_recovered_c_range(
3779 &mut ranges,
3780 &mut seen,
3781 node.start_byte(),
3782 node.end_byte(),
3783 node,
3784 limit,
3785 )
3786 {
3787 return RecoveredCReferenceRanges::LimitExceeded;
3788 }
3789 }
3790 if macro_value_kind == Some("preproc_def") {
3791 for reference in object_macro_replacement_type_references(node, source) {
3792 for range in reference.component_ranges {
3793 let visible = self
3794 .visible_identifier_candidates(file, &source[range.clone()])
3795 .any(|candidate| candidate.is_class() || candidate.is_module());
3796 if visible
3797 && !push_recovered_c_range(
3798 &mut ranges,
3799 &mut seen,
3800 range.start,
3801 range.end,
3802 node,
3803 limit,
3804 )
3805 {
3806 return RecoveredCReferenceRanges::LimitExceeded;
3807 }
3808 }
3809 }
3810 }
3811 }
3812 if inside_error
3813 && recovered_c_reference_node(self, file, node, source)
3814 && !push_recovered_c_range(
3815 &mut ranges,
3816 &mut seen,
3817 node.start_byte(),
3818 node.end_byte(),
3819 node,
3820 limit,
3821 )
3822 {
3823 return RecoveredCReferenceRanges::LimitExceeded;
3824 }
3825 let mut cursor = node.walk();
3826 for child in node.named_children(&mut cursor) {
3827 stack.push((child, inside_error));
3828 }
3829 }
3830 ranges.sort_unstable();
3831 RecoveredCReferenceRanges::Complete(ranges)
3832 }
3833
3834 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3840 self.visible_identifier_candidates(file, target.identifier())
3841 .filter(|candidate| candidate.is_macro())
3842 .any(|candidate| {
3843 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3844 })
3845 }
3846
3847 pub fn object_macro_replacement_at(
3848 &self,
3849 file: &ProjectFile,
3850 name: &str,
3851 before_byte: usize,
3852 ) -> Option<String> {
3853 let environment = self.macro_environment(file, before_byte);
3854 let binding = environment.binding(name)?;
3855 if !binding.exact {
3856 return None;
3857 }
3858 match &binding.definition {
3859 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3860 MacroDefinition::Function { .. }
3861 | MacroDefinition::VariadicFunction { .. }
3862 | MacroDefinition::Unsupported => None,
3863 }
3864 }
3865
3866 fn apply_macro_events(
3867 &self,
3868 file: &ProjectFile,
3869 environment: &mut MacroEnvironment,
3870 include_stack: &mut HashSet<ProjectFile>,
3871 ) {
3872 if !include_stack.insert(file.clone()) {
3873 return;
3874 }
3875 if self.cpp.prepared_syntax(self.token, file).is_none() {
3876 environment.mark_unknown_names(file, 0);
3877 include_stack.remove(file);
3878 return;
3879 }
3880 match self.macro_include_protection(file) {
3881 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3882 Some(binding) if binding.is_exact() => {
3883 include_stack.remove(file);
3884 return;
3885 }
3886 Some(_) | None if environment.unknown_names => {
3887 let mut ambiguous_seen = HashSet::default();
3888 self.mark_macro_events_ambiguous(
3889 file,
3890 environment,
3891 &mut ambiguous_seen,
3892 file,
3893 0,
3894 );
3895 include_stack.remove(file);
3896 return;
3897 }
3898 Some(_) => {
3899 let mut ambiguous_seen = HashSet::default();
3900 self.mark_macro_events_ambiguous(
3901 file,
3902 environment,
3903 &mut ambiguous_seen,
3904 file,
3905 0,
3906 );
3907 include_stack.remove(file);
3908 return;
3909 }
3910 None => {}
3911 },
3912 MacroIncludeProtection::PragmaOnce => {
3913 if !environment.applied_pragma_once_files.insert(file.clone()) {
3914 include_stack.remove(file);
3915 return;
3916 }
3917 if environment.maybe_applied_pragma_once_files.remove(file) {
3918 let mut ambiguous_seen = HashSet::default();
3923 environment.applied_pragma_once_files.remove(file);
3924 self.mark_macro_events_ambiguous(
3925 file,
3926 environment,
3927 &mut ambiguous_seen,
3928 file,
3929 0,
3930 );
3931 environment.maybe_applied_pragma_once_files.remove(file);
3932 environment.applied_pragma_once_files.insert(file.clone());
3933 include_stack.remove(file);
3934 return;
3935 }
3936 }
3937 MacroIncludeProtection::None => {}
3938 }
3939 let cell = self.macro_event_cell(file);
3940 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3941 for event in events {
3942 self.apply_macro_event(file, event, environment, include_stack);
3943 }
3944 include_stack.remove(file);
3945 }
3946
3947 fn apply_macro_event(
3948 &self,
3949 file: &ProjectFile,
3950 event: &MacroEvent,
3951 environment: &mut MacroEnvironment,
3952 include_stack: &mut HashSet<ProjectFile>,
3953 ) {
3954 #[cfg(any(test, feature = "test-support"))]
3955 self.macro_event_application_count
3956 .fetch_add(1, Ordering::Relaxed);
3957 match event {
3958 MacroEvent::Define {
3959 name,
3960 binding,
3961 conditionals,
3962 byte,
3963 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3964 Some(true) => environment.insert(name.clone(), binding.clone()),
3965 Some(false) => {}
3966 None => Self::merge_conditional_macro_definition(
3967 environment,
3968 name,
3969 binding,
3970 file,
3971 *byte,
3972 ),
3973 },
3974 MacroEvent::Undef {
3975 name,
3976 conditionals,
3977 byte,
3978 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3979 Some(true) => environment.remove(name),
3980 Some(false) => {}
3981 None => {
3982 if environment.binding(name).is_some() {
3983 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3984 }
3985 }
3986 },
3987 MacroEvent::Include {
3988 targets,
3989 conditionals,
3990 byte,
3991 } => {
3992 let condition =
3993 self.macro_event_condition_value(file, *byte, environment, conditionals);
3994 if condition == Some(false) {
3995 return;
3996 }
3997 if targets.is_empty() {
3998 environment.mark_unknown_names(file, *byte);
3999 return;
4000 }
4001 if condition.is_none() || targets.len() > 1 {
4002 let mut ambiguous_seen = HashSet::default();
4003 for target in targets {
4004 self.mark_macro_events_ambiguous(
4005 target,
4006 environment,
4007 &mut ambiguous_seen,
4008 file,
4009 *byte,
4010 );
4011 }
4012 } else if let Some(target) = targets.first() {
4013 self.apply_macro_events(target, environment, include_stack);
4014 }
4015 }
4016 MacroEvent::Invalidate { byte } => {
4017 for binding in environment.bindings.values_mut() {
4018 *binding = MacroBinding::uncertain_from(binding, file, *byte);
4019 }
4020 }
4021 }
4022 }
4023
4024 fn macro_event_condition_value(
4035 &self,
4036 file: &ProjectFile,
4037 event_byte: usize,
4038 environment: &MacroEnvironment,
4039 conditionals: &OwningPreprocessorConditionals,
4040 ) -> Option<bool> {
4041 if conditionals.is_empty() {
4042 return Some(true);
4043 }
4044 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4045 let source = prepared.source();
4046 let root = prepared.tree().root_node();
4047 let descendant = root.descendant_for_byte_range(
4048 event_byte,
4049 event_byte.saturating_add(1).min(source.len()),
4050 )?;
4051 let mut unknown = false;
4052 let mut current = descendant.parent();
4053 while let Some(conditional) = current {
4054 if matches!(
4055 conditional.kind(),
4056 "preproc_if" | "preproc_ifdef" | "preproc_elif"
4057 ) && conditionals.contains(&conditional.start_byte())
4058 {
4059 let mut value = match conditional.kind() {
4060 "preproc_ifdef" => {
4061 let name = conditional.child_by_field_name("name")?;
4062 let defined =
4063 self.macro_name_defined_value(environment, node_text(name, source));
4064 match conditional.child(0)?.kind() {
4065 "#ifdef" => defined,
4066 "#ifndef" => defined.map(|defined| !defined),
4067 _ => None,
4068 }
4069 }
4070 "preproc_if" | "preproc_elif" => conditional
4071 .child_by_field_name("condition")
4072 .and_then(|condition| {
4073 self.preprocessor_integer_value(
4074 condition,
4075 source,
4076 environment,
4077 &mut Vec::new(),
4078 0,
4079 )
4080 })
4081 .map(|value| value != 0),
4082 _ => unreachable!(),
4083 };
4084 match preprocessor_branch_of_descendant(
4085 conditional,
4086 descendant,
4087 &mut PreprocessorContextCache::default(),
4088 ) {
4089 PreprocessorBranch::Consequence => {}
4090 PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4091 value = value.map(|value| !value);
4092 }
4093 PreprocessorBranch::Unreadable => value = None,
4094 }
4095 match value {
4096 Some(true) => {}
4097 Some(false) => return Some(false),
4098 None => unknown = true,
4099 }
4100 }
4101 current = conditional.parent();
4102 }
4103 (!unknown).then_some(true)
4104 }
4105
4106 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4107 if environment.known_undefined_names.contains(name) {
4108 return Some(false);
4109 }
4110 if let Some(binding) = environment.binding(name) {
4111 return binding.is_exact().then_some(true);
4112 }
4113 environment
4114 .build_proven_defines
4115 .contains(name)
4116 .then_some(true)
4117 }
4118
4119 fn preprocessor_integer_value(
4120 &self,
4121 expression: Node<'_>,
4122 source: &str,
4123 environment: &MacroEnvironment,
4124 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4125 depth: usize,
4126 ) -> Option<i128> {
4127 if depth >= 64 {
4130 return None;
4131 }
4132 match expression.kind() {
4133 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4134 "identifier" | "type_identifier" => {
4135 let binding = environment.binding(node_text(expression, source))?;
4136 if !binding.is_exact() {
4137 return None;
4138 }
4139 let MacroDefinition::Object { replacement } = &binding.definition else {
4140 return None;
4141 };
4142 let identity = (binding.source.clone(), binding.declaration_byte);
4143 if expansion_stack.contains(&identity) {
4144 return None;
4145 }
4146 expansion_stack.push(identity);
4147 let parsed = self.parsed_macro_replacement(binding, replacement);
4148 let value = match parsed.as_ref() {
4149 ParsedMacroReplacement::Parsed {
4150 source: replacement_source,
4151 tree,
4152 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4153 .and_then(|call| call.child_by_field_name("arguments"))
4154 .and_then(|arguments| argument_children(arguments).next())
4155 .and_then(|argument| {
4156 self.preprocessor_integer_value(
4157 argument,
4158 replacement_source,
4159 environment,
4160 expansion_stack,
4161 depth + 1,
4162 )
4163 }),
4164 ParsedMacroReplacement::Unsupported => None,
4165 };
4166 expansion_stack.pop();
4167 value
4168 }
4169 "preproc_defined" => {
4170 let mut cursor = expression.walk();
4171 let name = expression
4172 .named_children(&mut cursor)
4173 .find(|child| child.kind() == "identifier")?;
4174 self.macro_name_defined_value(environment, node_text(name, source))
4175 .map(i128::from)
4176 }
4177 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4178 self.preprocessor_integer_value(
4179 child,
4180 source,
4181 environment,
4182 expansion_stack,
4183 depth + 1,
4184 )
4185 }),
4186 "unary_expression" => {
4187 let operator = expression.child_by_field_name("operator")?.kind();
4188 let argument = expression.child_by_field_name("argument")?;
4189 let value = self.preprocessor_integer_value(
4190 argument,
4191 source,
4192 environment,
4193 expansion_stack,
4194 depth + 1,
4195 )?;
4196 match operator {
4197 "+" => Some(value),
4198 "-" => value.checked_neg(),
4199 "!" => Some(i128::from(value == 0)),
4200 "~" => Some(!value),
4201 _ => None,
4202 }
4203 }
4204 "binary_expression" => {
4205 let left = self.preprocessor_integer_value(
4206 expression.child_by_field_name("left")?,
4207 source,
4208 environment,
4209 expansion_stack,
4210 depth + 1,
4211 )?;
4212 let right = self.preprocessor_integer_value(
4213 expression.child_by_field_name("right")?,
4214 source,
4215 environment,
4216 expansion_stack,
4217 depth + 1,
4218 )?;
4219 match expression.child_by_field_name("operator")?.kind() {
4220 "+" => left.checked_add(right),
4221 "-" => left.checked_sub(right),
4222 "*" => left.checked_mul(right),
4223 "/" => left.checked_div(right),
4224 "%" => left.checked_rem(right),
4225 "<<" => u32::try_from(right)
4226 .ok()
4227 .and_then(|shift| left.checked_shl(shift)),
4228 ">>" => u32::try_from(right)
4229 .ok()
4230 .and_then(|shift| left.checked_shr(shift)),
4231 "<" => Some(i128::from(left < right)),
4232 "<=" => Some(i128::from(left <= right)),
4233 ">" => Some(i128::from(left > right)),
4234 ">=" => Some(i128::from(left >= right)),
4235 "==" => Some(i128::from(left == right)),
4236 "!=" => Some(i128::from(left != right)),
4237 "&" => Some(left & right),
4238 "|" => Some(left | right),
4239 "^" => Some(left ^ right),
4240 "&&" => Some(i128::from(left != 0 && right != 0)),
4241 "||" => Some(i128::from(left != 0 || right != 0)),
4242 _ => None,
4243 }
4244 }
4245 _ => None,
4246 }
4247 }
4248
4249 fn mark_macro_events_ambiguous(
4250 &self,
4251 file: &ProjectFile,
4252 environment: &mut MacroEnvironment,
4253 include_stack: &mut HashSet<ProjectFile>,
4254 conditional_file: &ProjectFile,
4255 conditional_byte: usize,
4256 ) {
4257 if !include_stack.insert(file.clone()) {
4258 return;
4259 }
4260 if self.cpp.prepared_syntax(self.token, file).is_none() {
4261 environment.mark_unknown_names(conditional_file, conditional_byte);
4262 return;
4263 }
4264 match self.macro_include_protection(file) {
4265 MacroIncludeProtection::MacroGuard(guard) => {
4266 if environment
4267 .binding(&guard)
4268 .is_some_and(MacroBinding::is_exact)
4269 {
4270 return;
4271 }
4272 }
4273 MacroIncludeProtection::PragmaOnce => {
4274 if environment.applied_pragma_once_files.contains(file) {
4275 return;
4276 }
4277 environment
4278 .maybe_applied_pragma_once_files
4279 .insert(file.clone());
4280 }
4281 MacroIncludeProtection::None => {}
4282 }
4283 let cell = self.macro_event_cell(file);
4284 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4285 for event in events {
4286 #[cfg(any(test, feature = "test-support"))]
4287 self.macro_event_application_count
4288 .fetch_add(1, Ordering::Relaxed);
4289 match event {
4290 MacroEvent::Define { name, binding, .. } => {
4291 Self::merge_conditional_macro_definition(
4292 environment,
4293 name,
4294 binding,
4295 conditional_file,
4296 conditional_byte,
4297 );
4298 }
4299 MacroEvent::Undef { name, .. } => {
4300 if environment.binding(name).is_some() {
4301 environment.insert(
4302 name.clone(),
4303 MacroBinding::ambiguous(conditional_file, conditional_byte),
4304 );
4305 } else {
4306 environment.remove_known_undefined(name);
4307 }
4308 }
4309 MacroEvent::Include { targets, .. } => {
4310 if targets.is_empty() {
4311 environment.mark_unknown_names(conditional_file, conditional_byte);
4312 continue;
4313 }
4314 for target in targets {
4315 self.mark_macro_events_ambiguous(
4316 target,
4317 environment,
4318 include_stack,
4319 conditional_file,
4320 conditional_byte,
4321 );
4322 }
4323 }
4324 MacroEvent::Invalidate { .. } => {
4325 for binding in environment.bindings.values_mut() {
4326 *binding = MacroBinding::uncertain_from(
4327 binding,
4328 conditional_file,
4329 conditional_byte,
4330 );
4331 }
4332 }
4333 }
4334 }
4335 }
4336
4337 fn merge_conditional_macro_definition(
4338 environment: &mut MacroEnvironment,
4339 name: &str,
4340 possible_binding: &MacroBinding,
4341 conditional_file: &ProjectFile,
4342 conditional_byte: usize,
4343 ) {
4344 if environment.binding(name).is_some_and(|current| {
4349 current.definition != MacroDefinition::Unsupported
4350 && current.definition == possible_binding.definition
4351 }) {
4352 return;
4353 }
4354 environment.insert(
4355 name.to_string(),
4356 MacroBinding::ambiguous(conditional_file, conditional_byte),
4357 );
4358 }
4359
4360 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4361 let cell = self
4362 .macro_include_protection_cells
4363 .lock()
4364 .expect("C++ include protection cache poisoned")
4365 .entry(file.clone())
4366 .or_default()
4367 .clone();
4368 cell.get_or_init(|| {
4369 self.cpp.prepared_syntax(self.token, file).map_or(
4370 MacroIncludeProtection::None,
4371 |prepared| {
4372 top_level_macro_include_protection(
4373 prepared.tree().root_node(),
4374 prepared.source(),
4375 )
4376 },
4377 )
4378 })
4379 .clone()
4380 }
4381
4382 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4383 collect_macro_events(self.cpp, self.token, file)
4384 }
4385
4386 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4387 self.ordinary_type_import_cells
4388 .lock()
4389 .expect("C++ ordinary type import cache poisoned")
4390 .entry(file.clone())
4391 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4392 .clone()
4393 }
4394
4395 pub fn project_using_index(
4396 &self,
4397 build: impl FnOnce() -> ProjectUsingIndex,
4398 ) -> &ProjectUsingIndex {
4399 self.project_using_index.get_or_init(build)
4400 }
4401
4402 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4403 let mut files = self
4404 .visible_source_files_by_root
4405 .values()
4406 .flatten()
4407 .cloned()
4408 .collect::<HashSet<_>>()
4409 .into_iter()
4410 .collect::<Vec<_>>();
4411 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4412 files
4413 }
4414
4415 pub(super) fn namespace_alias_cell(
4417 &self,
4418 root: &ProjectFile,
4419 ) -> super::namespace_alias::NamespaceAliasCell {
4420 self.namespace_alias_sites
4421 .lock()
4422 .expect("C++ namespace alias cache poisoned")
4423 .entry(root.clone())
4424 .or_default()
4425 .clone()
4426 }
4427
4428 pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4430 self.visible_source_files_by_root.get(root).map_or_else(
4431 || vec![root.clone()],
4432 |files| {
4433 files
4434 .iter()
4435 .chain([root])
4436 .cloned()
4437 .collect::<HashSet<_>>()
4438 .into_iter()
4439 .collect()
4440 },
4441 )
4442 }
4443
4444 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4445 self.visible_source_files_by_root
4446 .get(root)
4447 .is_some_and(|files| files.contains(source))
4448 }
4449
4450 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4451 let cached = self
4452 .visible_parser_alias_name_sets
4453 .read()
4454 .expect("visible parser alias-name cache poisoned")
4455 .get(file)
4456 .cloned();
4457 let cell = if let Some(cached) = cached {
4458 cached
4459 } else {
4460 let mut cells = self
4461 .visible_parser_alias_name_sets
4462 .write()
4463 .expect("visible parser alias-name cache poisoned");
4464 Arc::clone(
4465 cells
4466 .entry(file.clone())
4467 .or_insert_with(|| Arc::new(OnceLock::new())),
4468 )
4469 };
4470 if let Some(names) = cell.get() {
4471 return names.contains(name);
4472 }
4473 let mut names = HashSet::default();
4474 let visible_files = self
4475 .visible_source_files_by_root
4476 .get(file)
4477 .cloned()
4478 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4479 for visible_file in visible_files {
4480 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4481 return false;
4482 };
4483 for alias in aliases.get().expect("published canonical aliases").iter() {
4484 names.insert(alias.name.clone());
4485 }
4486 }
4487 #[cfg(any(test, feature = "test-support"))]
4488 self.visible_parser_alias_name_set_build_count
4489 .fetch_add(1, Ordering::Relaxed);
4490 cell.get_or_init(|| names).contains(name)
4491 }
4492
4493 pub fn parser_alias_name_may_resolve_to_target(
4494 &self,
4495 file: &ProjectFile,
4496 alias_name: &str,
4497 target: &CodeUnit,
4498 ) -> bool {
4499 let started = std::time::Instant::now();
4500 self.parser_alias_fallback_calls
4501 .fetch_add(1, Ordering::Relaxed);
4502 let key = (
4503 file.clone(),
4504 alias_name.to_string(),
4505 logical_symbol_key(target),
4506 );
4507 let cached = self
4508 .parser_alias_target_matches
4509 .read()
4510 .expect("parser alias target-match cache poisoned")
4511 .get(&key)
4512 .cloned();
4513 let cell = if let Some(cached) = cached {
4514 cached
4515 } else {
4516 let mut cells = self
4517 .parser_alias_target_matches
4518 .write()
4519 .expect("parser alias target-match cache poisoned");
4520 Arc::clone(
4521 cells
4522 .entry(key)
4523 .or_insert_with(|| Arc::new(OnceLock::new())),
4524 )
4525 };
4526 let matched = *cell.get_or_init(|| {
4527 any_visible_source_file_matches(
4528 &self.visible_source_files_by_root,
4529 file,
4530 |visible_file| {
4531 self.parser_alias_fallback_files
4532 .fetch_add(1, Ordering::Relaxed);
4533 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4534 },
4535 )
4536 });
4537 self.parser_alias_fallback_elapsed_micros.fetch_add(
4538 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4539 Ordering::Relaxed,
4540 );
4541 matched
4542 }
4543
4544 fn callable_arities_for_target(
4545 &self,
4546 analyzer: &CppGraphSource<'_>,
4547 cpp: &dyn CppSource,
4548 file: &ProjectFile,
4549 prepared: &PreparedSyntaxTree,
4550 spec: &TargetSpec,
4551 ) -> Vec<ActivatedCallableArity> {
4552 let Some(signature) = spec.target.signature() else {
4553 return Vec::new();
4554 };
4555 let Some(candidates) = self
4556 .visible_by_identifier
4557 .get(file)
4558 .and_then(|by_name| by_name.get(&spec.member_name))
4559 else {
4560 return Vec::new();
4561 };
4562 let differing_candidates = candidates
4563 .iter()
4564 .filter(|candidate| {
4565 candidate.is_function()
4566 && candidate.fq_name() == spec.target.fq_name()
4567 && candidate.signature() == Some(signature)
4568 })
4569 .filter_map(|candidate| {
4570 analyzer
4571 .signature_metadata(candidate)
4572 .into_iter()
4573 .find_map(|metadata| metadata.callable_arity())
4574 .filter(|arity| Some(*arity) != spec.callable_arity)
4575 .map(|arity| (candidate, arity))
4576 })
4577 .collect::<Vec<_>>();
4578 if differing_candidates.is_empty() {
4579 return Vec::new();
4580 }
4581 let mut arities = Vec::with_capacity(differing_candidates.len());
4582 let reference = CallableReferenceContext {
4585 file,
4586 position: None,
4587 };
4588 for (candidate, candidate_arity) in differing_candidates {
4589 let declaration_activation =
4590 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4591 let Some(declaration_activation) = declaration_activation else {
4592 continue;
4593 };
4594 let activation_byte = if candidate.source() == file {
4595 Some(declaration_activation)
4596 } else {
4597 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4598 };
4599 if let Some(activation_byte) = activation_byte {
4600 arities.push(ActivatedCallableArity {
4601 activation_byte,
4602 arity: candidate_arity,
4603 });
4604 }
4605 }
4606 arities
4607 }
4608
4609 fn callable_parameter_macro_arity(
4610 &self,
4611 target: &CodeUnit,
4612 parameter_types: Option<&[String]>,
4613 ) -> Option<CallableArity> {
4614 let parameter_types = parameter_types?;
4615 let [macro_name] = parameter_types else {
4616 return None;
4617 };
4618 if macro_name.is_empty()
4619 || !macro_name
4620 .chars()
4621 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4622 {
4623 return None;
4624 }
4625 let cache_key = (target.source().clone(), macro_name.clone());
4626 if let Some(cached) = self
4627 .callable_parameter_macro_arities
4628 .lock()
4629 .expect("C++ callable parameter-macro arity cache poisoned")
4630 .get(&cache_key)
4631 .copied()
4632 {
4633 return cached;
4634 }
4635 let mut visible_files = HashSet::default();
4636 collect_include_closure(
4637 &self.cpp_source(),
4638 self.cpp.include_target_index(),
4639 target.source(),
4640 &mut visible_files,
4641 None,
4642 );
4643 let mut arities = Vec::new();
4644 for visible_file in visible_files {
4645 let cell = self.macro_event_cell(&visible_file);
4646 for event in
4647 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4648 {
4649 let MacroEvent::Define { name, binding, .. } = event else {
4650 continue;
4651 };
4652 if name != macro_name {
4653 continue;
4654 }
4655 let MacroDefinition::Object { replacement } = &binding.definition else {
4656 continue;
4657 };
4658 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4659 continue;
4660 };
4661 if !arities.contains(&arity) {
4662 arities.push(arity);
4663 }
4664 }
4665 }
4666 let resolved = (|| {
4667 let required = arities
4668 .iter()
4669 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4670 .min()?;
4671 let total = arities.iter().map(|arity| arity.total()).max()?;
4672 let repeated = arities
4673 .iter()
4674 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4675 Some(CallableArity::new(required, total, repeated))
4680 })();
4681 self.callable_parameter_macro_arities
4682 .lock()
4683 .expect("C++ callable parameter-macro arity cache poisoned")
4684 .insert(cache_key, resolved);
4685 resolved
4686 }
4687
4688 pub fn include_activation_for_source(
4689 &self,
4690 cpp: &dyn CppSource,
4691 file: &ProjectFile,
4692 prepared: &PreparedSyntaxTree,
4693 donor_source: &ProjectFile,
4694 ) -> Option<usize> {
4695 let key = (file.clone(), donor_source.clone());
4696 if let Some(cached) = self
4697 .include_activation_cells
4698 .lock()
4699 .expect("C++ include activation cache poisoned")
4700 .get(&key)
4701 .copied()
4702 {
4703 return cached;
4704 }
4705 #[cfg(any(test, feature = "test-support"))]
4706 self.include_activation_build_count
4707 .fetch_add(1, Ordering::Relaxed);
4708 let language_guard_stable = OnceCell::new();
4709 let activation =
4710 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4711 *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4712 });
4713 let mut cells = self
4714 .include_activation_cells
4715 .lock()
4716 .expect("C++ include activation cache poisoned");
4717 *cells.entry(key).or_insert(activation)
4718 }
4719
4720 pub fn conditional_include_projections_for_source(
4721 &self,
4722 file: &ProjectFile,
4723 prepared: &PreparedSyntaxTree,
4724 donor_source: &ProjectFile,
4725 ) -> Arc<[ConditionalIncludeProjection]> {
4726 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4727 let cell = self
4728 .conditional_include_projection_cells
4729 .lock()
4730 .expect("C++ conditional include projection cache poisoned")
4731 .entry(file.clone())
4732 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4733 .clone();
4734 let index = cell.get_or_build_pool_independent(|| {
4735 #[cfg(any(test, feature = "test-support"))]
4736 self.conditional_include_projection_index_build_count
4737 .fetch_add(1, Ordering::Relaxed);
4738 let language_guard_stable = OnceCell::new();
4739 find_conditional_include_projection_index(
4740 self.cpp,
4741 self.token,
4742 file,
4743 prepared,
4744 &|| {
4745 *language_guard_stable
4746 .get_or_init(|| self.include_language_guard_is_stable(file))
4747 },
4748 &|| {
4749 #[cfg(any(test, feature = "test-support"))]
4750 self.conditional_include_projection_state_count
4751 .fetch_add(1, Ordering::Relaxed);
4752 },
4753 )
4754 });
4755 index
4756 .get(donor_source)
4757 .cloned()
4758 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4759 }
4760
4761 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4762 let mut macro_events = |file: &ProjectFile| {
4763 let cell = self.macro_event_cell(file);
4764 cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4765 .clone()
4766 };
4767 include_language_guard_is_stable_with(
4768 self.cpp,
4769 self.token,
4770 file,
4771 &mut macro_events,
4772 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4773 self.structured_include_facts(file, prepared)
4774 },
4775 )
4776 }
4777
4778 #[cfg(any(test, feature = "test-support"))]
4779 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4780 (
4781 self.conditional_include_projection_index_build_count
4782 .load(Ordering::Relaxed),
4783 self.conditional_include_projection_state_count
4784 .load(Ordering::Relaxed),
4785 )
4786 }
4787
4788 #[cfg(any(test, feature = "test-support"))]
4789 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4790 self.conditional_include_target_state_count
4791 .load(Ordering::Relaxed)
4792 }
4793
4794 #[cfg(any(test, feature = "test-support"))]
4795 pub fn include_activation_build_count_for_test(&self) -> usize {
4796 self.include_activation_build_count.load(Ordering::Relaxed)
4797 }
4798
4799 #[cfg(any(test, feature = "test-support"))]
4800 pub fn note_using_donor_activation_for_test(&self) {
4801 self.using_donor_activation_count
4802 .fetch_add(1, Ordering::Relaxed);
4803 }
4804
4805 #[cfg(not(any(test, feature = "test-support")))]
4806 pub fn note_using_donor_activation_for_test(&self) {}
4807
4808 #[cfg(any(test, feature = "test-support"))]
4809 pub fn note_using_namespace_lookup_for_test(&self) {
4810 self.using_namespace_lookup_count
4811 .fetch_add(1, Ordering::Relaxed);
4812 }
4813
4814 #[cfg(not(any(test, feature = "test-support")))]
4815 pub fn note_using_namespace_lookup_for_test(&self) {}
4816
4817 #[cfg(any(test, feature = "test-support"))]
4818 pub fn note_using_name_candidate_inspection_for_test(&self) {
4819 self.using_name_candidate_inspection_count
4820 .fetch_add(1, Ordering::Relaxed);
4821 }
4822
4823 #[cfg(not(any(test, feature = "test-support")))]
4824 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4825
4826 #[cfg(any(test, feature = "test-support"))]
4827 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4828 (
4829 self.using_donor_activation_count.load(Ordering::Relaxed),
4830 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4831 self.callable_reference_spec_build_count
4832 .load(Ordering::Relaxed),
4833 self.using_name_candidate_inspection_count
4834 .load(Ordering::Relaxed),
4835 )
4836 }
4837
4838 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4839 file == target.source()
4840 || self
4841 .visible_by_file
4842 .get(file)
4843 .is_some_and(|visible| visible.contains(target))
4844 }
4845
4846 pub fn declaration_visible_at(
4858 &self,
4859 analyzer: &CppGraphSource<'_>,
4860 file: &ProjectFile,
4861 declaration: &CodeUnit,
4862 reference_byte: usize,
4863 ) -> bool {
4864 let reference_guards = OnceCell::new();
4865 if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4868 {
4869 return self.physical_declaration_visible_at(
4870 analyzer,
4871 file,
4872 declaration,
4873 reference_byte,
4874 &reference_guards,
4875 );
4876 }
4877 self.visible_identifier_candidates(file, declaration.identifier())
4878 .filter(|candidate| {
4879 (if declaration.is_callable()
4884 && reference_uses_c_semantics(self.cpp, file)
4885 != reference_uses_c_semantics(self.cpp, declaration.source())
4886 {
4887 crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4888 analyzer,
4889 self,
4890 candidate,
4891 declaration,
4892 |left, right| {
4893 crate::identity::cpp_header_body_files_are_related(
4894 self.cpp, self.token, left, right,
4895 )
4896 },
4897 )
4898 } else {
4899 self.same_logical_callable(analyzer, candidate, declaration)
4900 })
4901 || flattened_macro_namespace_declaration_matches(
4902 analyzer,
4903 self.cpp,
4904 file,
4905 candidate,
4906 declaration,
4907 reference_byte,
4908 )
4909 })
4910 .any(|candidate| {
4911 self.physical_declaration_visible_at(
4912 analyzer,
4913 file,
4914 candidate,
4915 reference_byte,
4916 &reference_guards,
4917 )
4918 })
4919 }
4920
4921 pub(crate) fn declaration_guards_compatible_at_reference(
4925 &self,
4926 analyzer: &CppGraphSource<'_>,
4927 file: &ProjectFile,
4928 declaration: &CodeUnit,
4929 reference: Node<'_>,
4930 ) -> bool {
4931 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4932 return false;
4933 };
4934 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4935 declaration_guard_requirements(analyzer, self.cpp, declaration)
4936 .iter()
4937 .any(|(_, required)| {
4938 guards_compatible_at_reference(required, reference_guards.as_ref())
4939 })
4940 }
4941
4942 pub fn declaration_visible_at_reference(
4949 &self,
4950 analyzer: &CppGraphSource<'_>,
4951 file: &ProjectFile,
4952 declaration: &CodeUnit,
4953 reference: Node<'_>,
4954 ) -> bool {
4955 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4956 return false;
4957 };
4958 if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
4959 {
4960 return false;
4961 }
4962 if declaration.source() == file
4963 && !analyzer.reference_uses_c_semantics(file)
4964 && (declaration.is_field() || declaration.is_callable())
4965 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4966 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4967 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4968 })
4969 {
4970 return true;
4974 }
4975 if declaration.is_field() && declaration.source() == file {
4976 let reference_byte = reference.start_byte();
4977 let reference_function =
4978 real_function_definition_ancestor(reference, prepared.source());
4979 let guards = OnceCell::new();
4980 let field_reference = CallableReferenceContext {
4981 file,
4982 position: Some(CallableReferencePosition {
4983 prepared: prepared.as_ref(),
4984 byte: reference_byte,
4985 guards: &guards,
4986 }),
4987 };
4988 let mut has_local_declaration = false;
4989 let mut local_declaration_visible = false;
4990 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4991 {
4992 let Some(declaration_function) =
4993 real_function_definition_ancestor(declaration, prepared.source())
4994 else {
4995 continue;
4996 };
4997 has_local_declaration = true;
4998 if reference_function.is_some_and(|reference_function| {
4999 reference_function.start_byte() == declaration_function.start_byte()
5000 && reference_function.end_byte() == declaration_function.end_byte()
5001 }) && callable_preprocessor_context_is_visible_for_reference(
5002 declaration,
5003 prepared.source(),
5004 &field_reference,
5005 ) && declaration.end_byte() < reference_byte
5006 {
5007 local_declaration_visible = true;
5008 break;
5009 }
5010 }
5011 if has_local_declaration {
5012 return local_declaration_visible;
5013 }
5014 }
5015 let guards = OnceCell::new();
5016 self.physical_declaration_visible_at(
5017 analyzer,
5018 file,
5019 declaration,
5020 reference.start_byte(),
5021 &guards,
5022 )
5023 }
5024
5025 pub fn declaration_visible_for_c_forward_call(
5031 &self,
5032 analyzer: &CppGraphSource<'_>,
5033 file: &ProjectFile,
5034 declaration: &CodeUnit,
5035 reference_byte: usize,
5036 ) -> bool {
5037 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5038 return true;
5039 }
5040 if declaration.source() != file {
5041 return false;
5042 }
5043 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5044 return false;
5045 };
5046 let reference_guards = prepared
5047 .tree()
5048 .root_node()
5049 .descendant_for_byte_range(reference_byte, reference_byte)
5050 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5051 declaration_guard_requirements(analyzer, self.cpp, declaration)
5052 .into_iter()
5053 .any(|(_, required)| {
5054 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5055 })
5056 }
5057
5058 pub fn callable_arity_at_reference(
5059 &self,
5060 analyzer: &CppGraphSource<'_>,
5061 file: &ProjectFile,
5062 candidate: &CodeUnit,
5063 reference_byte: usize,
5064 ) -> Option<CallableArity> {
5065 let key = (file.clone(), logical_symbol_key(candidate));
5066 let cell = self
5067 .callable_reference_specs
5068 .lock()
5069 .expect("C++ callable reference-spec cache poisoned")
5070 .entry(key)
5071 .or_default()
5072 .clone();
5073 let spec = cell.get_or_init(|| {
5074 let prepared = self.cpp.prepared_syntax(self.token, file)?;
5075 let spec = TargetSpec::from_target(analyzer, candidate)?;
5076 let spec = spec
5077 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
5078 .into_owned();
5079 #[cfg(any(test, feature = "test-support"))]
5080 self.callable_reference_spec_build_count
5081 .fetch_add(1, Ordering::Relaxed);
5082 Some(spec)
5083 });
5084 spec.as_ref()?.callable_arity_at(reference_byte)
5085 }
5086
5087 fn physical_declaration_visible_at(
5088 &self,
5089 analyzer: &CppGraphSource<'_>,
5090 file: &ProjectFile,
5091 declaration: &CodeUnit,
5092 reference_byte: usize,
5093 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5094 ) -> bool {
5095 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5096 return false;
5097 };
5098 let reference = CallableReferenceContext {
5099 file,
5100 position: Some(CallableReferencePosition {
5101 prepared: prepared.as_ref(),
5102 byte: reference_byte,
5103 guards: reference_guards,
5104 }),
5105 };
5106 if declaration.source() == file {
5107 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5108 .or_else(|| {
5109 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5110 })
5111 .is_some_and(|activation| activation < reference_byte);
5112 }
5113 if self
5114 .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5115 .is_some()
5116 {
5117 return self.foreign_declaration_reachable_at_reference(
5120 file,
5121 prepared.as_ref(),
5122 declaration.source(),
5123 &HashSet::default(),
5124 reference.guards(),
5125 reference_byte,
5126 );
5127 }
5128 if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5129 return false;
5130 }
5131 declaration_guard_requirements(analyzer, self.cpp, declaration)
5132 .into_iter()
5133 .any(|(_, declaration_guards)| {
5134 self.foreign_declaration_reachable_at_reference(
5135 file,
5136 prepared.as_ref(),
5137 declaration.source(),
5138 &declaration_guards,
5139 reference.guards(),
5140 reference_byte,
5141 )
5142 })
5143 }
5144
5145 fn foreign_callable_declaration_is_active(
5150 &self,
5151 analyzer: &CppGraphSource<'_>,
5152 declaration: &CodeUnit,
5153 reference: &CallableReferenceContext<'_>,
5154 ) -> bool {
5155 let build_decides = !self.compile_context_is_absent(reference.file);
5156 let proven = self.compile_proven_guards(reference.file);
5157 let augmented;
5158 let active = match reference.guards() {
5159 Some(active) if !proven.is_empty() => {
5160 augmented = active.union(&proven).cloned().collect();
5161 Some(&augmented)
5162 }
5163 other => other,
5164 };
5165 analyzer
5166 .declaration_source_occurrences(declaration)
5167 .into_iter()
5168 .flatten()
5169 .any(|occurrence| {
5170 if occurrence.fact.callable_activation.is_none() {
5171 return false;
5172 }
5173 let Some(mut required) = occurrence
5174 .fact
5175 .callable_guards
5176 .as_ref()
5177 .and_then(cpp_guard_set_to_runtime)
5178 else {
5179 return false;
5180 };
5181 if required.iter().any(|guard| match guard {
5182 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
5183 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
5184 !reference.is_c()
5185 }
5186 _ => false,
5187 }) {
5188 return false;
5189 }
5190 required.retain(|guard| {
5191 !matches!(guard,
5192 PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5193 if name == "__cplusplus")
5194 });
5195 required.is_empty()
5196 || if build_decides {
5197 guard_requirements_hold_at_reference(&required, active)
5198 } else {
5199 guards_compatible_at_reference(&required, reference.guards())
5200 }
5201 })
5202 }
5203
5204 pub fn external_type_candidate_visible_at(
5205 &self,
5206 file: &ProjectFile,
5207 candidate: &CodeUnit,
5208 reference_byte: usize,
5209 ) -> bool {
5210 if candidate.source() == file {
5211 return true;
5212 }
5213 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5214 return false;
5215 };
5216 self.visible_identifier_candidates(file, candidate.identifier())
5217 .filter(|peer| same_logical_symbol(candidate, peer))
5218 .any(|peer| {
5219 peer.source() == file
5220 || self
5221 .include_activation_for_source(
5222 self.cpp,
5223 file,
5224 prepared.as_ref(),
5225 peer.source(),
5226 )
5227 .is_some_and(|activation| activation <= reference_byte)
5228 })
5229 }
5230
5231 pub fn external_type_declaration_visible_at(
5232 &self,
5233 file: &ProjectFile,
5234 candidate: &CodeUnit,
5235 reference_byte: usize,
5236 ) -> bool {
5237 if candidate.source() == file {
5238 return true;
5239 }
5240 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5241 return false;
5242 };
5243 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5244 .is_some_and(|activation| activation <= reference_byte)
5245 }
5246
5247 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5266 if let Some(cached) = self
5267 .compile_proven_guard_cells
5268 .lock()
5269 .expect("C++ compile-proven guard cache poisoned")
5270 .get(file)
5271 {
5272 return Arc::clone(cached);
5273 }
5274 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5275 Some(names) => names,
5276 None => {
5277 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5278 let seed = translation_units.next().and_then(|translation_unit| {
5279 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5280 });
5281 match seed {
5282 None => HashSet::default(),
5283 Some(mut names) => {
5284 for translation_unit in translation_units {
5285 let Some(reached) = context_fact_names(
5286 self.cpp.compile_contexts_for(&translation_unit),
5287 ) else {
5288 names.clear();
5289 break;
5290 };
5291 names.retain(|name| reached.contains(name));
5292 if names.is_empty() {
5293 break;
5294 }
5295 }
5296 names
5297 }
5298 }
5299 }
5300 };
5301 let proven = Arc::new(
5302 names
5303 .into_iter()
5304 .map(PreprocessorGuard::Defined)
5305 .collect::<HashSet<_>>(),
5306 );
5307 self.compile_proven_guard_cells
5308 .lock()
5309 .expect("C++ compile-proven guard cache poisoned")
5310 .insert(file.clone(), Arc::clone(&proven));
5311 proven
5312 }
5313
5314 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5324 if let Some(cached) = self
5325 .include_path_admission_cells
5326 .lock()
5327 .expect("C++ include-path admission cache poisoned")
5328 .get(file)
5329 .copied()
5330 {
5331 return cached;
5332 }
5333 let admission = if self.compile_context_is_absent(file) {
5334 IncludePathAdmission::Compatible
5335 } else {
5336 IncludePathAdmission::Proven
5337 };
5338 self.include_path_admission_cells
5339 .lock()
5340 .expect("C++ include-path admission cache poisoned")
5341 .insert(file.clone(), admission);
5342 admission
5343 }
5344
5345 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5352 if !self.cpp.compile_contexts_for(file).is_empty() {
5353 return false;
5354 }
5355 let translation_units = self.cpp.reaching_translation_units(file);
5356 translation_units.is_empty()
5357 || translation_units
5358 .iter()
5359 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5360 }
5361
5362 pub fn miss_requires_compile_context(
5374 &self,
5375 file: &ProjectFile,
5376 identifier: &str,
5377 reference: Node<'_>,
5378 ) -> bool {
5379 if !self.compile_context_is_absent(file) {
5380 return false;
5381 }
5382 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5383 return false;
5384 };
5385 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5386 let reference_byte = reference.start_byte();
5387 let mut sources = self
5388 .visible_identifier_candidates(file, identifier)
5389 .map(CodeUnit::source)
5390 .filter(|source| *source != file)
5391 .collect::<Vec<_>>();
5392 sources.sort();
5393 sources.dedup();
5394 sources.into_iter().any(|declaration_source| {
5395 self.conditional_include_projections_for_source(
5396 file,
5397 prepared.as_ref(),
5398 declaration_source,
5399 )
5400 .iter()
5401 .any(|projection| {
5402 projection.activation_byte <= reference_byte
5403 && !guard_requirements_hold_at_reference(
5404 &projection.required_guards,
5405 reference_guards.as_ref(),
5406 )
5407 && guards_compatible_at_reference(
5408 &projection.required_guards,
5409 reference_guards.as_ref(),
5410 )
5411 })
5412 })
5413 }
5414
5415 fn effective_reference_guards(
5422 &self,
5423 file: &ProjectFile,
5424 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5425 ) -> Option<HashSet<PreprocessorGuard>> {
5426 let mut effective = reference_guards.cloned()?;
5427 effective.extend(self.compile_proven_guards(file).iter().cloned());
5428 if self.include_language_guard_is_stable(file) {
5429 effective.insert(if reference_uses_c_semantics(self.cpp, file) {
5430 PreprocessorGuard::Undefined("__cplusplus".to_string())
5431 } else {
5432 PreprocessorGuard::Defined("__cplusplus".to_string())
5433 });
5434 }
5435 Some(effective)
5436 }
5437
5438 fn foreign_declaration_reachable_at_reference(
5446 &self,
5447 file: &ProjectFile,
5448 prepared: &PreparedSyntaxTree,
5449 declaration_source: &ProjectFile,
5450 declaration_guards: &HashSet<PreprocessorGuard>,
5451 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5452 reference_byte: usize,
5453 ) -> bool {
5454 let effective = self.effective_reference_guards(file, reference_guards);
5455 let reference_guards = effective.as_ref();
5456 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5457 eprintln!(
5458 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5459 declaration_source.rel_path().display(),
5460 );
5461 }
5462 if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5463 return false;
5464 }
5465 if self
5466 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5467 .is_some_and(|activation| activation <= reference_byte)
5468 {
5469 return true;
5470 }
5471 let projections =
5472 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5473 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5474 eprintln!(
5475 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5476 declaration_source.rel_path().display(),
5477 declaration_guards.len(),
5478 self.compile_proven_guards(file).len(),
5479 projections.len(),
5480 );
5481 }
5482 let admission = IncludePathAdmission::Proven;
5483 projections.iter().any(|projection| {
5484 projection.activation_byte <= reference_byte
5485 && admission.admits(
5486 &projection.required_guards,
5487 &projection.partial_guards,
5488 reference_guards,
5489 )
5490 && self.preprocessor_guards_stable_between(
5491 file,
5492 projection.activation_byte,
5493 reference_byte,
5494 &projection.required_guards,
5495 )
5496 })
5497 }
5498
5499 fn foreign_declaration_may_be_reachable_from_raw_guards(
5500 &self,
5501 file: &ProjectFile,
5502 prepared: &PreparedSyntaxTree,
5503 declaration_source: &ProjectFile,
5504 declaration_guards: &HashSet<PreprocessorGuard>,
5505 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5506 reference_byte: usize,
5507 ) -> bool {
5508 let proven = self.compile_proven_guards(file);
5509 let augmented;
5510 let reference_guards = match reference_guards {
5511 Some(active) if !proven.is_empty() => {
5512 augmented = active.union(&proven).cloned().collect();
5513 Some(&augmented)
5514 }
5515 other => other,
5516 };
5517 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5518 return false;
5519 }
5520 if self
5521 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5522 .is_some_and(|activation| activation <= reference_byte)
5523 {
5524 return true;
5525 }
5526 let language_guard_stable = OnceCell::new();
5527 let reachable = find_conditional_include_projection_for_source(
5528 self.cpp,
5529 self.token,
5530 file,
5531 prepared,
5532 declaration_source,
5533 self.include_path_admission(file),
5534 reference_guards,
5535 reference_byte,
5536 &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5537 &|| {
5538 #[cfg(any(test, feature = "test-support"))]
5539 self.conditional_include_target_state_count
5540 .fetch_add(1, Ordering::Relaxed);
5541 },
5542 );
5543 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5544 eprintln!(
5545 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5546 declaration_source.rel_path().display(),
5547 declaration_guards.len(),
5548 proven.len(),
5549 reference_guards.map_or(0, HashSet::len),
5550 );
5551 }
5552 reachable
5553 }
5554
5555 fn foreign_declaration_reachable_from_compile_proven_guards(
5556 &self,
5557 file: &ProjectFile,
5558 prepared: &PreparedSyntaxTree,
5559 declaration_source: &ProjectFile,
5560 declaration_guards: &HashSet<PreprocessorGuard>,
5561 reference_byte: usize,
5562 ) -> bool {
5563 let proven = self.compile_proven_guards(file);
5564 if proven.is_empty()
5565 || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5566 {
5567 return false;
5568 }
5569 let projections =
5570 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5571 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5572 eprintln!(
5573 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5574 declaration_source.rel_path().display(),
5575 declaration_guards.len(),
5576 proven.len(),
5577 projections.len(),
5578 );
5579 }
5580 projections.iter().any(|projection| {
5581 projection.activation_byte <= reference_byte
5582 && guard_requirements_hold_at_reference(
5583 &projection.required_guards,
5584 Some(proven.as_ref()),
5585 )
5586 && self.preprocessor_guards_stable_between(
5591 file,
5592 0,
5593 projection.activation_byte,
5594 &projection.required_guards,
5595 )
5596 })
5597 }
5598
5599 fn declaration_site_visible_without_reference_environment(
5605 &self,
5606 file: &ProjectFile,
5607 prepared: &PreparedSyntaxTree,
5608 site_source: &ProjectFile,
5609 site_byte: usize,
5610 site_guards: &HashSet<PreprocessorGuard>,
5611 reference_byte: usize,
5612 ) -> bool {
5613 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5614 if site_source == file {
5615 let visible = site_byte < reference_byte && site_guards.is_empty();
5616 if report_stats {
5617 eprintln!(
5618 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5619 site_source.rel_path().display(),
5620 site_guards.len(),
5621 );
5622 }
5623 return visible;
5624 }
5625 let direct = site_guards.is_empty()
5626 && self
5627 .include_activation_for_source(self.cpp, file, prepared, site_source)
5628 .is_some_and(|activation| activation <= reference_byte);
5629 let compile_proven = !direct
5630 && self.foreign_declaration_reachable_from_compile_proven_guards(
5631 file,
5632 prepared,
5633 site_source,
5634 site_guards,
5635 reference_byte,
5636 );
5637 if report_stats {
5638 eprintln!(
5639 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5640 site_source.rel_path().display(),
5641 site_guards.len(),
5642 );
5643 }
5644 direct || compile_proven
5645 }
5646
5647 #[allow(clippy::too_many_arguments)]
5652 fn declaration_site_visible_at_reference<'g>(
5653 &self,
5654 file: &ProjectFile,
5655 prepared: &PreparedSyntaxTree,
5656 site_source: &ProjectFile,
5657 site: &DeclarationGuardSite,
5658 reference_byte: usize,
5659 raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5660 reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5661 ) -> bool {
5662 if site_source == file {
5663 if site.byte >= reference_byte {
5664 return false;
5665 }
5666 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5671 guard_requirements_hold_at_reference(&site.guards, guards)
5672 || (site.completed_family_before_reference
5673 && guards_compatible_at_reference(&site.guards, guards))
5674 };
5675 if !active(raw_reference_guards) {
5676 return false;
5677 }
5678 return active(reference_guards_at_site())
5679 && self.preprocessor_guards_stable_between(
5680 file,
5681 site.byte,
5682 reference_byte,
5683 &site.guards,
5684 );
5685 }
5686 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5687 file,
5688 prepared,
5689 site_source,
5690 &site.guards,
5691 raw_reference_guards,
5692 reference_byte,
5693 );
5694 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5695 eprintln!(
5696 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5697 site_source.rel_path().display(),
5698 site.guards.len(),
5699 );
5700 }
5701 raw_feasible
5702 && self.foreign_declaration_reachable_at_reference(
5703 file,
5704 prepared,
5705 site_source,
5706 &site.guards,
5707 reference_guards_at_site(),
5708 reference_byte,
5709 )
5710 }
5711
5712 pub fn binding_site_visible_at_reference(
5717 &self,
5718 file: &ProjectFile,
5719 site_source: &ProjectFile,
5720 site_byte: usize,
5721 site_guards: &HashSet<PreprocessorGuard>,
5722 reference: Node<'_>,
5723 ) -> bool {
5724 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5725 return false;
5726 };
5727 if self.declaration_site_visible_without_reference_environment(
5728 file,
5729 prepared.as_ref(),
5730 site_source,
5731 site_byte,
5732 site_guards,
5733 reference.start_byte(),
5734 ) {
5735 return true;
5736 }
5737 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5738 let reference_guards = OnceCell::new();
5739 self.declaration_site_visible_at_reference(
5740 file,
5741 prepared.as_ref(),
5742 site_source,
5743 &DeclarationGuardSite {
5744 byte: site_byte,
5745 guards: site_guards.clone(),
5746 completed_family_before_reference: false,
5747 },
5748 reference.start_byte(),
5749 raw_reference_guards.as_ref(),
5750 || {
5751 reference_guards
5752 .get_or_init(|| {
5753 let environment = self.macro_environment(file, reference.start_byte());
5754 raw_reference_guards
5755 .clone()
5756 .filter(|guards| environment.guard_requirements_may_hold(guards))
5757 })
5758 .as_ref()
5759 },
5760 )
5761 }
5762
5763 pub fn external_type_candidate_visible_in_context(
5764 &self,
5765 analyzer: &CppGraphSource<'_>,
5766 file: &ProjectFile,
5767 candidate: &CodeUnit,
5768 reference: Node<'_>,
5769 ) -> bool {
5770 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5771 if report_stats {
5772 eprintln!(
5773 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5774 candidate.fq_name(),
5775 candidate.source().rel_path().display(),
5776 file.rel_path().display(),
5777 reference.start_byte(),
5778 );
5779 }
5780 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5781 return false;
5782 };
5783 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5784 let reference_guards = OnceCell::new();
5785 let reference_guards_at_site = || {
5786 reference_guards.get_or_init(|| {
5787 let started = Instant::now();
5788 if report_stats {
5789 eprintln!(
5790 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5791 file.rel_path().display(),
5792 reference.start_byte(),
5793 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5794 );
5795 }
5796 let macro_environment = self.macro_environment(file, reference.start_byte());
5797 let filtered = raw_reference_guards
5798 .clone()
5799 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5800 if report_stats {
5801 eprintln!(
5802 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5803 filtered.is_some(),
5804 started.elapsed().as_millis(),
5805 );
5806 }
5807 filtered
5808 })
5809 };
5810
5811 let peers = self
5812 .visible_identifier_candidates(file, candidate.identifier())
5813 .filter(|peer| same_logical_symbol(candidate, peer))
5814 .collect::<Vec<_>>();
5815 if report_stats {
5816 let peer_sources = peers
5817 .iter()
5818 .map(|peer| peer.source().rel_path().display().to_string())
5819 .collect::<Vec<_>>();
5820 eprintln!(
5821 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5822 candidate.fq_name(),
5823 );
5824 }
5825 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5826 declaration_guard_requirements(analyzer, self.cpp, peer)
5827 .into_iter()
5828 .any(|(declaration_byte, declaration_guards)| {
5829 self.declaration_site_visible_without_reference_environment(
5830 file,
5831 prepared.as_ref(),
5832 peer.source(),
5833 declaration_byte,
5834 &declaration_guards,
5835 reference.start_byte(),
5836 )
5837 })
5838 });
5839 if directly_visible_without_reference_environment {
5840 if report_stats {
5841 eprintln!(
5842 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5843 candidate.fq_name(),
5844 );
5845 }
5846 return true;
5847 }
5848 let directly_visible = peers.iter().any(|peer| {
5849 declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5850 .into_iter()
5851 .any(|site| {
5852 self.declaration_site_visible_at_reference(
5853 file,
5854 prepared.as_ref(),
5855 peer.source(),
5856 &site,
5857 reference.start_byte(),
5858 raw_reference_guards.as_ref(),
5859 || reference_guards_at_site().as_ref(),
5860 )
5861 })
5862 });
5863 if directly_visible {
5864 if report_stats {
5865 eprintln!(
5866 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5867 candidate.fq_name(),
5868 );
5869 }
5870 return true;
5871 }
5872 let complementary = self
5873 .visible_identifier_candidates(file, candidate.identifier())
5874 .filter(|peer| {
5875 peer.kind() == candidate.kind()
5876 && peer.fq_name() == candidate.fq_name()
5877 && peer.source() == candidate.source()
5878 })
5879 .collect::<Vec<_>>();
5880 let complementary_family =
5885 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5886 let raw_candidate_branch_compatible = complementary_family
5887 && raw_reference_guards.as_ref().is_some_and(|active| {
5888 declaration_guard_requirements(analyzer, self.cpp, candidate)
5889 .iter()
5890 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5891 });
5892 if report_stats {
5893 eprintln!(
5894 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5895 candidate.fq_name(),
5896 complementary.len(),
5897 complementary_family,
5898 raw_candidate_branch_compatible,
5899 );
5900 }
5901 let candidate_branch_compatible = raw_candidate_branch_compatible
5902 && reference_guards_at_site().as_ref().is_some_and(|active| {
5903 declaration_guard_requirements(analyzer, self.cpp, candidate)
5904 .iter()
5905 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5906 });
5907 let complementary_visible = candidate_branch_compatible
5908 && if candidate.source() == file {
5909 declaration_guard_requirements(analyzer, self.cpp, candidate)
5910 .iter()
5911 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5912 } else {
5913 self.include_activation_for_source(
5914 self.cpp,
5915 file,
5916 prepared.as_ref(),
5917 candidate.source(),
5918 )
5919 .is_some_and(|activation| activation <= reference.start_byte())
5920 };
5921 if report_stats {
5922 eprintln!(
5923 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5924 if complementary_visible {
5925 "complementary"
5926 } else {
5927 "missing"
5928 },
5929 candidate.fq_name(),
5930 );
5931 }
5932 complementary_visible
5933 }
5934
5935 pub fn is_exhaustive_same_fqn_type_declaration_family(
5936 &self,
5937 analyzer: &CppGraphSource<'_>,
5938 file: &ProjectFile,
5939 candidate: &CodeUnit,
5940 ) -> bool {
5941 let candidates = self
5942 .visible_identifier_candidates(file, candidate.identifier())
5943 .filter(|peer| {
5944 peer.kind() == candidate.kind()
5945 && peer.fq_name() == candidate.fq_name()
5946 && peer.source() == candidate.source()
5947 })
5948 .collect::<Vec<_>>();
5949 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5950 }
5951
5952 pub fn dependent_member_pointer_alias_visible_in_context(
5967 &self,
5968 analyzer: &CppGraphSource<'_>,
5969 file: &ProjectFile,
5970 candidate: &CodeUnit,
5971 owner_components: &[String],
5972 reference: Node<'_>,
5973 ) -> bool {
5974 if !analyzer
5975 .type_alias_provider()
5976 .is_some_and(|provider| provider.is_type_alias(candidate))
5977 {
5978 return false;
5979 }
5980 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5981 return false;
5982 };
5983 if terminal != candidate.identifier()
5984 || canonical_cpp_scope_components(candidate) != owner_components
5985 {
5986 return false;
5987 }
5988 let Some(expected_parent_fq_name) =
5989 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5990 else {
5991 return false;
5992 };
5993 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5994 return false;
5995 };
5996 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5997 || parent_anchor.source() != candidate.source()
5998 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5999 {
6000 return false;
6001 }
6002
6003 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6009 || candidate.source() == file
6010 && !analyzer
6011 .ranges(candidate)
6012 .iter()
6013 .any(|range| range.start_byte < reference.start_byte())
6014 {
6015 return false;
6016 }
6017
6018 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6019 if candidate_guards.is_empty() {
6020 return false;
6021 }
6022 let same_guard_sets =
6023 |left: &[(usize, HashSet<PreprocessorGuard>)],
6024 right: &[(usize, HashSet<PreprocessorGuard>)]| {
6025 left.iter().all(|(_, left_guards)| {
6026 right
6027 .iter()
6028 .any(|(_, right_guards)| left_guards == right_guards)
6029 })
6030 };
6031 let parent_candidates = self
6032 .visible_identifier_candidates(file, parent_anchor.identifier())
6033 .filter(|peer| {
6034 peer.kind() == parent_anchor.kind()
6035 && peer.fq_name() == expected_parent_fq_name.as_str()
6036 && peer.source() == parent_anchor.source()
6037 && canonical_cpp_scope_components(peer) == owner_prefix
6038 })
6039 .filter_map(|peer| {
6040 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6041 (candidate_guards.len() == parent_guards.len()
6042 && same_guard_sets(&candidate_guards, &parent_guards)
6043 && same_guard_sets(&parent_guards, &candidate_guards))
6044 .then(|| (peer.clone(), parent_guards))
6045 })
6046 .collect::<Vec<_>>();
6047 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6048 return false;
6049 };
6050
6051 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6052 return false;
6053 };
6054 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
6055 else {
6056 return false;
6057 };
6058 if !candidate_guards.iter().any(|(_, target_guards)| {
6063 guards_compatible_at_reference(target_guards, Some(&reference_guards))
6064 && (candidate.source() != file
6065 || self.preprocessor_guards_stable_between(
6066 file,
6067 0,
6068 reference.start_byte(),
6069 target_guards,
6070 ))
6071 }) {
6072 return false;
6073 }
6074
6075 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6076 }
6077
6078 pub fn external_type_candidate_guard_compatible_in_context(
6088 &self,
6089 analyzer: &CppGraphSource<'_>,
6090 file: &ProjectFile,
6091 candidate: &CodeUnit,
6092 reference: Node<'_>,
6093 ) -> bool {
6094 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6095 return false;
6096 };
6097 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6098
6099 self.visible_identifier_candidates(file, candidate.identifier())
6100 .filter(|peer| same_logical_symbol(candidate, peer))
6101 .any(|peer| {
6102 declaration_guard_requirements(analyzer, self.cpp, peer)
6103 .into_iter()
6104 .any(|(declaration_byte, declaration_guards)| {
6105 if peer.source() == file {
6106 let (start, end) = if declaration_byte <= reference.start_byte() {
6107 (declaration_byte, reference.start_byte())
6108 } else {
6109 (reference.start_byte(), declaration_byte)
6110 };
6111 return guard_requirements_hold_at_reference(
6112 &declaration_guards,
6113 reference_guards.as_ref(),
6114 ) && self.preprocessor_guards_stable_between(
6115 file,
6116 start,
6117 end,
6118 &declaration_guards,
6119 );
6120 }
6121 self.foreign_declaration_reachable_at_reference(
6122 file,
6123 prepared.as_ref(),
6124 peer.source(),
6125 &declaration_guards,
6126 reference_guards.as_ref(),
6127 reference.start_byte(),
6128 )
6129 })
6130 })
6131 }
6132
6133 pub fn same_file_callable_guard_compatible_ignoring_order(
6141 &self,
6142 analyzer: &CppGraphSource<'_>,
6143 file: &ProjectFile,
6144 candidate: &CodeUnit,
6145 reference: Node<'_>,
6146 ) -> bool {
6147 if candidate.source() != file || !candidate.is_callable() {
6148 return false;
6149 }
6150 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6151 return false;
6152 };
6153 let guards = OnceCell::new();
6154 let context = CallableReferenceContext {
6155 file,
6156 position: Some(CallableReferencePosition {
6157 prepared: prepared.as_ref(),
6158 byte: reference.start_byte(),
6159 guards: &guards,
6160 }),
6161 };
6162 analyzer
6163 .declaration_source_occurrences(candidate)
6164 .into_iter()
6165 .flatten()
6166 .filter_map(|occurrence| {
6167 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
6168 Some(callable_guard_requirements_are_visible(
6169 &guards,
6170 occurrence.fact.callable_guard_completion_byte,
6171 &context,
6172 ))
6173 })
6174 .any(|visible| visible)
6175 }
6176
6177 pub fn type_candidate_may_be_visible_before_reference(
6178 &self,
6179 analyzer: &CppGraphSource<'_>,
6180 file: &ProjectFile,
6181 candidate: &CodeUnit,
6182 reference_byte: usize,
6183 ) -> bool {
6184 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6185 return false;
6186 };
6187 let root = prepared.tree().root_node();
6188 let end_byte = reference_byte
6189 .saturating_add(1)
6190 .min(prepared.source().len());
6191 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
6192 return false;
6193 };
6194 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
6195 }
6196
6197 pub fn preprocessor_guards_stable_between(
6198 &self,
6199 file: &ProjectFile,
6200 start_byte: usize,
6201 end_byte: usize,
6202 guards: &HashSet<PreprocessorGuard>,
6203 ) -> bool {
6204 if guards.is_empty() || start_byte >= end_byte {
6205 return true;
6206 }
6207 let cell = self.macro_event_cell(file);
6208 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6209 let mut visited = HashSet::from_iter([file.clone()]);
6210 !events.iter().any(|event| {
6211 event.byte() >= start_byte
6212 && event.byte() < end_byte
6213 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6214 })
6215 }
6216
6217 fn macro_event_may_mutate_guards(
6218 &self,
6219 event: &MacroEvent,
6220 guards: &HashSet<PreprocessorGuard>,
6221 visited: &mut HashSet<ProjectFile>,
6222 ) -> bool {
6223 match event {
6224 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6225 guards.iter().any(|guard| guard.may_depend_on_macro(name))
6226 }
6227 MacroEvent::Include { targets, .. } => {
6228 targets.is_empty()
6229 || targets
6230 .iter()
6231 .any(|target| self.source_may_mutate_guards(target, guards, visited))
6232 }
6233 MacroEvent::Invalidate { .. } => true,
6234 }
6235 }
6236
6237 fn source_may_mutate_guards(
6238 &self,
6239 file: &ProjectFile,
6240 guards: &HashSet<PreprocessorGuard>,
6241 visited: &mut HashSet<ProjectFile>,
6242 ) -> bool {
6243 if !visited.insert(file.clone()) {
6244 return false;
6245 }
6246 let cell = self.macro_event_cell(file);
6247 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6248 events
6249 .iter()
6250 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6251 }
6252
6253 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6254 let normalized = normalize_reference_name(raw_name)?;
6255 self.type_candidates(file, &normalized)
6256 .into_iter()
6257 .next()
6258 .cloned()
6259 }
6260
6261 pub fn unique_visible_parameter_type_fallback(
6270 &self,
6271 analyzer: &CppGraphSource<'_>,
6272 file: &ProjectFile,
6273 node: Node<'_>,
6274 source: &str,
6275 ) -> Option<CodeUnit> {
6276 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6277 return None;
6278 }
6279 let name = node_text(node, source);
6280 let candidates = self
6281 .visible_identifier_candidates(file, name)
6282 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6283 .filter(|candidate| {
6284 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6285 })
6286 .collect::<Vec<_>>();
6287 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6288 }
6289
6290 pub fn resolve_type_node_result(
6291 &self,
6292 file: &ProjectFile,
6293 node: Node<'_>,
6294 source: &str,
6295 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6296 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6297 return Ok(None);
6298 };
6299 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6300 return Ok(Some(primary));
6301 };
6302 self.resolve_template_arguments(file, primary, &arguments)
6303 .map(Some)
6304 }
6305
6306 pub fn resolve_type_node_primary(
6307 &self,
6308 file: &ProjectFile,
6309 node: Node<'_>,
6310 source: &str,
6311 ) -> Option<CodeUnit> {
6312 let components = cpp_type_name_components(node, source)?;
6313 self.resolve_type(file, &components.join("::"))
6314 }
6315
6316 pub fn resolve_template_arguments(
6317 &self,
6318 file: &ProjectFile,
6319 primary: CodeUnit,
6320 arguments: &[CppTemplateExpression],
6321 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6322 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6323 }
6324
6325 fn resolve_template_arguments_inner(
6326 &self,
6327 file: &ProjectFile,
6328 primary: CodeUnit,
6329 arguments: &[CppTemplateExpression],
6330 seen_aliases: &mut HashSet<CodeUnit>,
6331 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6332 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6333 && let Some(alias_target) = &metadata.alias_target
6334 {
6335 if !seen_aliases.insert(primary.clone()) {
6336 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6337 }
6338 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6339 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6340 let target_name = alias_target.components.join("::");
6341 let target_primary = if alias_target.global {
6342 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6343 } else {
6344 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6345 };
6346 let Some(target_primary) = target_primary else {
6347 return Ok(primary);
6351 };
6352 let Some(target_arguments) = &alias_target.arguments else {
6353 return Ok(target_primary);
6354 };
6355 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6356 .ok_or(CppTemplateResolutionError::Substitution)?;
6357 return self.resolve_template_arguments_inner(
6358 file,
6359 target_primary,
6360 &target_arguments,
6361 seen_aliases,
6362 );
6363 }
6364
6365 let primary_fq_name = self
6366 .cpp_template_metadata
6367 .get(&primary)
6368 .map(|metadata| metadata.primary_fq_name.clone())
6369 .unwrap_or_else(|| primary.fq_name());
6370 let has_specialization_metadata = self
6371 .cpp_template_families
6372 .get(&primary_fq_name)
6373 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6374 if !has_specialization_metadata {
6375 return Ok(primary);
6376 }
6377 self.select_template_specialization(file, &primary, arguments)
6378 }
6379
6380 fn select_template_specialization(
6381 &self,
6382 file: &ProjectFile,
6383 resolved: &CodeUnit,
6384 explicit_arguments: &[CppTemplateExpression],
6385 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6386 let primary_fq_name = self
6387 .cpp_template_metadata
6388 .get(resolved)
6389 .map(|metadata| metadata.primary_fq_name.clone())
6390 .unwrap_or_else(|| resolved.fq_name());
6391 let family = self
6392 .cpp_template_families
6393 .get(&primary_fq_name)
6394 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6395 let primary_candidates = family
6396 .iter()
6397 .filter_map(|unit| {
6398 let metadata = self.cpp_template_metadata.get(unit)?;
6399 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6400 })
6401 .collect::<Vec<_>>();
6402 let primary_unit = primary_candidates
6403 .iter()
6404 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6405 .or_else(|| {
6406 primary_candidates
6407 .iter()
6408 .map(|(unit, _)| *unit)
6409 .min_by_key(|unit| {
6410 (
6411 unit.source().to_string(),
6412 unit.signature().unwrap_or_default(),
6413 )
6414 })
6415 })
6416 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6417 let primary_parameters =
6418 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6419 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6420 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6421 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6422
6423 let mut applicable = Vec::new();
6424 for unit in family {
6425 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6426 continue;
6427 };
6428 if metadata.is_primary() || !self.is_visible(file, unit) {
6429 continue;
6430 }
6431 if !cpp_specialization_matches(metadata, &expanded) {
6432 continue;
6433 }
6434 applicable.push((unit, metadata));
6435 }
6436 if applicable.is_empty() {
6437 return Ok(primary_unit.clone());
6438 }
6439
6440 let winners = applicable
6445 .iter()
6446 .filter(|(candidate, candidate_metadata)| {
6447 applicable.iter().all(|(other, other_metadata)| {
6448 same_visible_symbol(candidate, other)
6449 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6450 })
6451 })
6452 .copied()
6453 .collect::<Vec<_>>();
6454 let Some((selected, _)) = winners.first() else {
6455 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6458 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6459 });
6460 };
6461 if winners
6462 .iter()
6463 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6464 {
6465 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6466 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6467 });
6468 }
6469 Ok((*selected).clone())
6470 }
6471
6472 pub fn resolve_type_components_lexically(
6473 &self,
6474 analyzer: &CppGraphSource<'_>,
6475 file: &ProjectFile,
6476 components: &[String],
6477 global: bool,
6478 lexical_scope: &[String],
6479 ) -> LexicalTypeResolution {
6480 self.resolve_type_components_lexically_inner(
6481 analyzer,
6482 file,
6483 components,
6484 global,
6485 lexical_scope,
6486 TypeCandidateResolution::Canonical,
6487 )
6488 }
6489
6490 pub fn resolve_type_components_lexically_for_forward(
6491 &self,
6492 analyzer: &CppGraphSource<'_>,
6493 file: &ProjectFile,
6494 components: &[String],
6495 global: bool,
6496 lexical_scope: &[String],
6497 ) -> LexicalTypeResolution {
6498 self.resolve_type_components_lexically_inner(
6499 analyzer,
6500 file,
6501 components,
6502 global,
6503 lexical_scope,
6504 TypeCandidateResolution::PreserveAlias,
6505 )
6506 }
6507
6508 pub fn resolve_type_components_lexically_for_target(
6509 &self,
6510 analyzer: &CppGraphSource<'_>,
6511 file: &ProjectFile,
6512 components: &[String],
6513 global: bool,
6514 lexical_scope: &[String],
6515 target: &CodeUnit,
6516 ) -> LexicalTypeResolution {
6517 #[cfg(any(test, feature = "test-support"))]
6518 self.target_preserving_type_resolution_count
6519 .fetch_add(1, Ordering::Relaxed);
6520 self.resolve_type_components_lexically_inner(
6521 analyzer,
6522 file,
6523 components,
6524 global,
6525 lexical_scope,
6526 TypeCandidateResolution::PreserveTarget(target),
6527 )
6528 }
6529
6530 pub fn coarse_unqualified_type_reference_may_resolve(
6531 &self,
6532 file: &ProjectFile,
6533 name: &str,
6534 ) -> bool {
6535 if name.is_empty() {
6536 return true;
6537 }
6538 self.visible_identifier_candidates(file, name)
6539 .any(|candidate| {
6540 candidate.kind() == CodeUnitType::Class
6541 || declared_type_alias(&self.cpp_source(), candidate)
6542 })
6543 || self.visible_parser_alias_name_is_visible(file, name)
6544 }
6545
6546 #[allow(clippy::too_many_arguments)]
6547 pub fn structured_type_reference_may_resolve_to_target(
6548 &self,
6549 analyzer: &CppGraphSource<'_>,
6550 file: &ProjectFile,
6551 components: &[String],
6552 global: bool,
6553 lexical_scope: &[String],
6554 target: &CodeUnit,
6555 ) -> bool {
6556 if components.is_empty() {
6557 return true;
6558 }
6559 let Some(terminal) = components.last() else {
6560 return true;
6561 };
6562 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6563 .map(|qualified| qualified.join("::"))
6564 .collect::<Vec<_>>();
6565 let target_name = cpp_name_for(target);
6566 if qualified_tiers
6567 .iter()
6568 .any(|qualified| qualified == &target_name)
6569 {
6570 return true;
6571 }
6572
6573 let mut saw_shape_candidate = false;
6574 for candidate in self.visible_identifier_candidates(file, terminal) {
6575 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6576 {
6577 continue;
6578 }
6579 let candidate_name = cpp_name_for(candidate);
6580 let shape_matches = if global || components.len() > 1 {
6581 qualified_tiers
6582 .iter()
6583 .any(|qualified| qualified == &candidate_name)
6584 } else {
6585 true
6586 };
6587 if !shape_matches {
6588 continue;
6589 }
6590 saw_shape_candidate = true;
6591 if same_visible_symbol(candidate, target)
6592 || self.c_tag_declaration_family_matches_target(
6593 analyzer,
6594 file,
6595 std::slice::from_ref(&candidate),
6596 target,
6597 )
6598 || self.compatible_primary_template_redeclarations(candidate, target)
6599 || (declared_type_alias(analyzer, candidate)
6600 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6601 {
6602 return true;
6603 }
6604 }
6605
6606 !saw_shape_candidate
6607 }
6608
6609 fn cached_c_tag_kind(
6616 &self,
6617 analyzer: &CppGraphSource<'_>,
6618 candidate: &CodeUnit,
6619 ) -> Option<CppCTagKind> {
6620 if let Some(kind) = self
6621 .c_tag_kind_cache
6622 .lock()
6623 .expect("C tag kind cache poisoned")
6624 .get(candidate)
6625 {
6626 return *kind;
6627 }
6628 let kind = indexed_c_tag_kind(analyzer, candidate);
6629 self.c_tag_kind_cache
6630 .lock()
6631 .expect("C tag kind cache poisoned")
6632 .insert(candidate.clone(), kind);
6633 kind
6634 }
6635
6636 fn cached_unique_c_tag_complete_definition(
6637 &self,
6638 analyzer: &CppGraphSource<'_>,
6639 target: &CodeUnit,
6640 target_tag: CppCTagKind,
6641 ) -> Option<CodeUnit> {
6642 if let Some(definition) = self
6643 .c_tag_complete_definition_cache
6644 .lock()
6645 .expect("C tag complete-definition cache poisoned")
6646 .get(target)
6647 {
6648 return definition.clone();
6649 }
6650 let complete_definitions = analyzer
6651 .definitions(&target.fq_name())
6652 .filter(|candidate| {
6653 candidate.is_class()
6654 && !declared_type_alias(analyzer, candidate)
6655 && is_c_source_file(candidate.source())
6656 && analyzer.parent_of(candidate).is_none()
6657 && cpp_class_declaration_strength(analyzer, candidate)
6658 == CppClassDeclarationStrength::Full
6659 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6660 })
6661 .collect::<HashSet<_>>();
6662 let definition = (complete_definitions.len() == 1)
6663 .then(|| complete_definitions.into_iter().next())
6664 .flatten()
6665 .filter(|candidate| same_visible_symbol(candidate, target));
6666 self.c_tag_complete_definition_cache
6667 .lock()
6668 .expect("C tag complete-definition cache poisoned")
6669 .insert(target.clone(), definition.clone());
6670 definition
6671 }
6672
6673 pub fn c_tag_declaration_family_matches_target(
6674 &self,
6675 analyzer: &CppGraphSource<'_>,
6676 visible_from: &ProjectFile,
6677 candidates: &[&CodeUnit],
6678 target: &CodeUnit,
6679 ) -> bool {
6680 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6681 let candidate_evidence = candidates
6682 .iter()
6683 .map(|candidate| {
6684 (
6685 candidate.fq_name(),
6686 candidate.source().rel_path().to_path_buf(),
6687 cpp_class_declaration_strength(analyzer, candidate),
6688 indexed_c_tag_kind(analyzer, candidate),
6689 self.is_physically_visible(visible_from, candidate),
6690 )
6691 })
6692 .collect::<Vec<_>>();
6693 eprintln!(
6694 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6695 visible_from.rel_path().display(),
6696 target.fq_name(),
6697 target.source().rel_path().display(),
6698 cpp_class_declaration_strength(analyzer, target),
6699 indexed_c_tag_kind(analyzer, target),
6700 );
6701 }
6702 if candidates.is_empty()
6703 || !target.is_class()
6704 || declared_type_alias(analyzer, target)
6705 || !is_c_source_file(target.source())
6706 || analyzer.parent_of(target).is_some()
6707 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6708 {
6709 return false;
6710 }
6711 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6712 return false;
6713 };
6714 if self
6715 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6716 .is_none()
6717 {
6718 return false;
6719 }
6720 let mut saw_visible_forward = false;
6721 for candidate in candidates.iter().copied() {
6722 if candidate == target {
6723 continue;
6724 }
6725 if !candidate.is_class()
6726 || declared_type_alias(analyzer, candidate)
6727 || candidate.fq_name() != target.fq_name()
6728 || analyzer.parent_of(candidate).is_some()
6729 || cpp_class_declaration_strength(analyzer, candidate)
6730 != CppClassDeclarationStrength::Forward
6731 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6732 || !self.is_physically_visible(visible_from, candidate)
6733 {
6734 return false;
6735 }
6736 saw_visible_forward = true;
6737 }
6738 saw_visible_forward
6739 }
6740
6741 fn unique_c_tag_declaration_family(
6746 &self,
6747 analyzer: &CppGraphSource<'_>,
6748 visible_from: &ProjectFile,
6749 candidates: &[&CodeUnit],
6750 ) -> Option<CodeUnit> {
6751 let first = candidates.first()?;
6752 let target_fq_name = first.fq_name();
6753 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6754 let mut full = None;
6755 let mut saw_forward = false;
6756 for candidate in candidates.iter().copied() {
6757 if !candidate.is_class()
6758 || declared_type_alias(analyzer, candidate)
6759 || candidate.fq_name() != target_fq_name
6760 || analyzer.parent_of(candidate).is_some()
6761 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6762 {
6763 return None;
6764 }
6765 match cpp_class_declaration_strength(analyzer, candidate) {
6766 CppClassDeclarationStrength::Full
6767 if is_c_source_file(candidate.source())
6768 && full.replace(candidate.clone()).is_none() => {}
6769 CppClassDeclarationStrength::Forward
6770 if self.is_physically_visible(visible_from, candidate) =>
6771 {
6772 saw_forward = true
6773 }
6774 _ => return None,
6775 }
6776 }
6777 if saw_forward { full } else { None }
6778 }
6779
6780 pub fn target_preserving_reference_namespace(
6781 &self,
6782 analyzer: &CppGraphSource<'_>,
6783 file: &ProjectFile,
6784 identifier: &str,
6785 target: &CodeUnit,
6786 ) -> Option<Vec<String>> {
6787 let mut namespace = None;
6788 for candidate in self.visible_identifier_candidates(file, identifier) {
6789 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6790 {
6791 continue;
6792 }
6793 if !(same_visible_symbol(candidate, target)
6794 || self.compatible_primary_template_redeclarations(candidate, target)
6795 || declared_type_alias(analyzer, candidate)
6796 && self.structured_alias_primary_preserves_target(
6797 analyzer, file, candidate, target,
6798 ))
6799 {
6800 continue;
6801 }
6802 if namespace
6803 .as_ref()
6804 .is_some_and(|existing| existing != candidate.package_name())
6805 {
6806 return None;
6807 }
6808 namespace = Some(candidate.package_name().to_string());
6809 }
6810 let namespace = namespace?;
6811 Some(
6812 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6813 brokk_bifrost_core::analyzer::Language::Cpp,
6814 &namespace,
6815 ),
6816 )
6817 }
6818
6819 pub fn resolve_imported_type_candidate(
6820 &self,
6821 analyzer: &CppGraphSource<'_>,
6822 file: &ProjectFile,
6823 target: &CodeUnit,
6824 target_components: &[String],
6825 direct_target: Option<&CodeUnit>,
6826 preserve_alias: bool,
6827 ) -> LexicalTypeResolution {
6828 let candidates = [target];
6829 let resolution = if preserve_alias {
6830 TypeCandidateResolution::PreserveAlias
6831 } else {
6832 direct_target.map_or(
6833 TypeCandidateResolution::Canonical,
6834 TypeCandidateResolution::PreserveTarget,
6835 )
6836 };
6837 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6841 Ok(unit) => LexicalTypeResolution::Resolved {
6842 unit,
6843 components: target_components.to_vec(),
6844 candidates: vec![target.clone()],
6845 },
6846 Err(failure) => failure.lexical_resolution(),
6847 }
6848 }
6849
6850 fn resolve_type_components_lexically_inner(
6851 &self,
6852 analyzer: &CppGraphSource<'_>,
6853 file: &ProjectFile,
6854 components: &[String],
6855 global: bool,
6856 lexical_scope: &[String],
6857 resolution: TypeCandidateResolution<'_>,
6858 ) -> LexicalTypeResolution {
6859 if components.is_empty() {
6860 return LexicalTypeResolution::Missing;
6861 }
6862 let mut injected = self.resolve_injected_class_name(
6872 analyzer,
6873 file,
6874 components,
6875 global,
6876 lexical_scope,
6877 resolution,
6878 );
6879 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6880 let prefix_len = qualified.len().saturating_sub(components.len());
6881 if injected
6882 .as_ref()
6883 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6884 {
6885 return injected
6886 .take()
6887 .expect("injected class resolution was just present")
6888 .1;
6889 }
6890 let qualified_name = qualified.join("::");
6891 let candidates = self
6892 .type_candidates(file, &qualified_name)
6893 .into_iter()
6894 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6895 .collect::<Vec<_>>();
6896 if candidates.is_empty() {
6897 if !global && components.len() == 1 {
6898 match self.resolve_inherited_type_for_lexical_scope(
6899 analyzer,
6900 file,
6901 &qualified[..prefix_len],
6902 &components[0],
6903 resolution,
6904 ) {
6905 LexicalTypeResolution::Missing => {}
6906 inherited => return inherited,
6907 }
6908 }
6909 continue;
6910 }
6911 let candidates =
6912 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6913 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6914 Ok(unit) => unit,
6915 Err(failure) => return failure.lexical_resolution(),
6916 };
6917 return LexicalTypeResolution::Resolved {
6918 unit,
6919 components: qualified,
6920 candidates: candidates.into_iter().cloned().collect(),
6921 };
6922 }
6923 LexicalTypeResolution::Missing
6924 }
6925
6926 fn resolve_injected_class_name(
6927 &self,
6928 analyzer: &CppGraphSource<'_>,
6929 file: &ProjectFile,
6930 components: &[String],
6931 global: bool,
6932 lexical_scope: &[String],
6933 resolution: TypeCandidateResolution<'_>,
6934 ) -> Option<(usize, LexicalTypeResolution)> {
6935 if global
6936 || components.len() != 1
6937 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6938 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6939 {
6940 return None;
6941 }
6942 let name = components.first()?;
6943 let mut matches: Vec<&CodeUnit> = Vec::new();
6944 let mut owner_len = 0;
6945 for candidate in self.visible_identifier_candidates(file, name) {
6946 if !candidate.is_class()
6947 || declared_type_alias(analyzer, candidate)
6948 || candidate.identifier() != name
6949 {
6950 continue;
6951 }
6952 let candidate_scope = canonical_cpp_scope_components(candidate);
6953 if candidate_scope.len() > lexical_scope.len()
6954 || !lexical_scope.starts_with(&candidate_scope)
6955 || candidate_scope.last().is_none_or(|last| last != name)
6956 {
6957 continue;
6958 }
6959 if candidate_scope.len() > owner_len {
6960 owner_len = candidate_scope.len();
6961 matches.clear();
6962 }
6963 if candidate_scope.len() == owner_len
6964 && !matches
6965 .iter()
6966 .any(|existing| same_logical_symbol(existing, candidate))
6967 {
6968 matches.push(candidate);
6969 }
6970 }
6971 if matches.is_empty() {
6972 return None;
6973 }
6974 let owner_components = lexical_scope[..owner_len].to_vec();
6981 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6982 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6983 Ok(unit) => LexicalTypeResolution::Resolved {
6984 unit,
6985 components: owner_components,
6986 candidates: matches.into_iter().cloned().collect(),
6987 },
6988 Err(failure) => failure.lexical_resolution(),
6989 };
6990 Some((owner_len, resolution))
6991 }
6992
6993 fn resolve_inherited_type_for_lexical_scope(
6994 &self,
6995 analyzer: &CppGraphSource<'_>,
6996 file: &ProjectFile,
6997 lexical_scope: &[String],
6998 name: &str,
6999 resolution: TypeCandidateResolution<'_>,
7000 ) -> LexicalTypeResolution {
7001 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7002 return LexicalTypeResolution::Missing;
7003 };
7004 let lexical_owner_name = lexical_scope.join("::");
7005 if lexical_owner_name.is_empty() {
7006 return LexicalTypeResolution::Missing;
7007 }
7008 let owner_candidates = self
7009 .type_candidates(file, &lexical_owner_name)
7010 .into_iter()
7011 .filter(|candidate| {
7012 canonical_cpp_name_matches(candidate, &lexical_owner_name)
7013 && !declared_type_alias(analyzer, candidate)
7014 })
7015 .collect::<Vec<_>>();
7016 if owner_candidates.is_empty() {
7017 return LexicalTypeResolution::Missing;
7018 }
7019 let physical_owner_candidates = owner_candidates
7024 .iter()
7025 .copied()
7026 .filter(|candidate| candidate.source() == file)
7027 .collect::<Vec<_>>();
7028 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7029 owner_candidates
7030 } else {
7031 physical_owner_candidates
7032 };
7033 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7034 return LexicalTypeResolution::Ambiguous;
7035 };
7036
7037 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
7038 let mut visited_owners = HashSet::default();
7039 while !frontier.is_empty() {
7040 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7041 let mut next_frontier = Vec::new();
7042 for owner in frontier {
7043 if !visited_owners.insert(owner.fq_name()) {
7044 continue;
7045 }
7046 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7047 let candidates = self
7048 .type_candidates(file, &qualified_name)
7049 .into_iter()
7050 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7051 .collect::<Vec<_>>();
7052 if candidates.is_empty() {
7053 for ancestor in hierarchy.get_direct_ancestors(&owner) {
7054 if !next_frontier
7055 .iter()
7056 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7057 {
7058 next_frontier.push(ancestor);
7059 }
7060 }
7061 continue;
7062 }
7063 let candidates =
7064 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7065 let unit =
7066 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7067 Ok(unit) => unit,
7068 Err(failure) => return failure.lexical_resolution(),
7069 };
7070 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7071 }
7072 if let Some((unit, candidates)) = level_matches.first().cloned() {
7073 let Some(first_declaration) = candidates.first() else {
7074 return LexicalTypeResolution::Ambiguous;
7075 };
7076 if !level_matches.iter().all(|(_, declarations)| {
7077 declarations
7078 .iter()
7079 .all(|declaration| same_logical_symbol(first_declaration, declaration))
7080 }) {
7081 return LexicalTypeResolution::Ambiguous;
7082 }
7083 let mut components = lexical_scope.to_vec();
7084 components.push(name.to_string());
7085 return LexicalTypeResolution::Resolved {
7086 unit,
7087 components,
7088 candidates,
7089 };
7090 }
7091 frontier = next_frontier;
7092 }
7093 LexicalTypeResolution::Missing
7094 }
7095
7096 pub fn inherited_injected_class_owner(
7113 &self,
7114 analyzer: &CppGraphSource<'_>,
7115 file: &ProjectFile,
7116 enclosing_owner: &CodeUnit,
7117 injected_name: &str,
7118 ) -> Option<CodeUnit> {
7119 let hierarchy = analyzer.type_hierarchy_provider()?;
7120 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
7121 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7122 while !frontier.is_empty() {
7123 let mut level_matches = Vec::new();
7124 let mut next_frontier = Vec::new();
7125 for raw_owner in frontier {
7126 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7127 else {
7128 if raw_owner.identifier() == injected_name {
7129 return None;
7130 }
7131 continue;
7132 };
7133 let propagated = propagated_counts.entry(owner.clone()).or_default();
7134 if *propagated == 2 {
7135 continue;
7136 }
7137 *propagated += 1;
7138 if owner.identifier() == injected_name {
7139 level_matches.push(owner.clone());
7140 }
7141 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
7142 }
7143 match level_matches.as_slice() {
7144 [owner] => return Some(owner.clone()),
7145 [_, ..] => return None,
7146 [] => {}
7147 }
7148 frontier = next_frontier;
7149 }
7150 None
7151 }
7152
7153 fn resolve_type_candidates(
7158 &self,
7159 analyzer: &CppGraphSource<'_>,
7160 file: &ProjectFile,
7161 candidates: &[&CodeUnit],
7162 resolution: TypeCandidateResolution<'_>,
7163 ) -> Result<CodeUnit, TypeCandidateFailure> {
7164 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7165 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7166 {
7167 return Ok(unit);
7168 }
7169 match resolution {
7170 TypeCandidateResolution::Canonical => {
7171 self.canonical_type_candidate_resolution(analyzer, file, candidates)
7172 }
7173 TypeCandidateResolution::PreserveAlias => {
7174 let same_fqn_alias_family = candidates.len() > 1
7181 && candidates.iter().all(|candidate| {
7182 declared_type_alias(analyzer, candidate)
7183 && same_logical_symbol(candidates[0], candidate)
7184 })
7185 && candidates
7186 .iter()
7187 .any(|candidate| candidate.source() != candidates[0].source());
7188 if same_fqn_alias_family {
7189 let physically_visible = candidates
7190 .iter()
7191 .copied()
7192 .filter(|candidate| self.is_physically_visible(file, candidate))
7193 .collect::<Vec<_>>();
7194 if let Some(alias) = select_alias_spelling_with_agreed_targets(
7204 &physically_visible,
7205 |candidate| self.structured_alias_target(analyzer, candidate),
7206 ) {
7207 return Ok(alias.clone());
7208 }
7209 }
7210 unique_type_candidate_preserving_alias(analyzer, file, candidates)
7211 .ok_or(TypeCandidateFailure::Ambiguous)
7212 }
7213 TypeCandidateResolution::PreserveTarget(target) => self
7214 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7215 .ok_or(TypeCandidateFailure::Ambiguous),
7216 }
7217 }
7218
7219 fn candidates_for_type_resolution<'b>(
7220 &self,
7221 analyzer: &CppGraphSource<'_>,
7222 file: &ProjectFile,
7223 candidates: &[&'b CodeUnit],
7224 resolution: TypeCandidateResolution<'_>,
7225 ) -> Vec<&'b CodeUnit> {
7226 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7227 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7228 if compile_proven.len() == 1 {
7229 return compile_proven;
7230 }
7231 }
7232 candidates.to_vec()
7233 }
7234
7235 fn compile_proven_type_candidates<'b>(
7242 &self,
7243 analyzer: &CppGraphSource<'_>,
7244 file: &ProjectFile,
7245 candidates: &[&'b CodeUnit],
7246 ) -> Vec<&'b CodeUnit> {
7247 let proven = self.compile_proven_guards(file);
7248 if proven.is_empty() {
7249 return Vec::new();
7250 }
7251 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7252 return Vec::new();
7253 };
7254 candidates
7255 .iter()
7256 .copied()
7257 .filter(|candidate| {
7258 let declaration_guards =
7259 declaration_guard_requirements(analyzer, self.cpp, candidate);
7260 if declaration_guards.is_empty() {
7261 return false;
7262 }
7263 if candidate.source() == file {
7264 return declaration_guards.iter().any(|(_, required)| {
7265 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7266 });
7267 }
7268 declaration_guards.iter().any(|(_, required)| {
7269 self.foreign_declaration_reachable_from_compile_proven_guards(
7270 file,
7271 prepared.as_ref(),
7272 candidate.source(),
7273 required,
7274 usize::MAX,
7275 )
7276 })
7277 })
7278 .collect()
7279 }
7280
7281 pub fn resolve_callable_value_components_lexically(
7282 &self,
7283 analyzer: &CppGraphSource<'_>,
7284 file: &ProjectFile,
7285 owner_components: &[String],
7286 member_name: &str,
7287 global: bool,
7288 lexical_scope: &[String],
7289 ) -> LexicalCallableValueResolution {
7290 if owner_components.is_empty() || member_name.is_empty() {
7291 return LexicalCallableValueResolution::Missing;
7292 }
7293 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7294 let owner_name = qualified_owner.join("::");
7295 let type_candidates = self
7296 .type_candidates(file, &owner_name)
7297 .into_iter()
7298 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7299 .collect::<Vec<_>>();
7300 let resolved_type = if type_candidates.is_empty() {
7301 None
7302 } else {
7303 let Some(unit) =
7304 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7305 else {
7306 return LexicalCallableValueResolution::Ambiguous;
7307 };
7308 Some(unit)
7309 };
7310
7311 let mut qualified_callable = qualified_owner;
7312 qualified_callable.push(member_name.to_string());
7313 let callable_name = qualified_callable.join("::");
7314 let free_function = self
7315 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7316 .into_iter()
7317 .find(|candidate| {
7318 canonical_cpp_name_matches(candidate, &callable_name)
7319 && type_owner_of(analyzer, candidate).is_none()
7320 })
7321 .cloned();
7322
7323 match (resolved_type, free_function) {
7324 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7325 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7326 (None, Some(function)) => {
7327 return LexicalCallableValueResolution::FreeFunction(function);
7328 }
7329 (None, None) => {}
7330 }
7331 }
7332 LexicalCallableValueResolution::Missing
7333 }
7334
7335 fn resolve_type_for_declaration(
7336 &self,
7337 visible_from: &ProjectFile,
7338 declaration: &CodeUnit,
7339 raw_name: &str,
7340 ) -> Option<CodeUnit> {
7341 let normalized = normalize_reference_name(raw_name)?;
7342 if !normalized.contains("::")
7343 && let Some(namespace) = cpp_namespace_for(declaration)
7344 {
7345 for prefix in namespace_prefixes(&namespace) {
7346 let qualified = format!("{prefix}::{normalized}");
7347 if let Some(unit) = self
7348 .type_candidates(visible_from, &qualified)
7349 .into_iter()
7350 .next()
7351 {
7352 return Some(unit.clone());
7353 }
7354 }
7355 }
7356 self.resolve_type(visible_from, raw_name)
7357 }
7358
7359 fn resolve_unique_canonical_type_for_declaration(
7360 &self,
7361 analyzer: &CppGraphSource<'_>,
7362 visible_from: &ProjectFile,
7363 declaration: &CodeUnit,
7364 raw_name: &str,
7365 ) -> Option<CodeUnit> {
7366 let current = self.resolve_defining_type_for_declaration(
7367 analyzer,
7368 visible_from,
7369 declaration,
7370 raw_name,
7371 )?;
7372 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7373 .ok()
7374 }
7375
7376 pub fn canonical_type_unit(
7377 &self,
7378 analyzer: &CppGraphSource<'_>,
7379 visible_from: &ProjectFile,
7380 unit: &CodeUnit,
7381 ) -> Option<CodeUnit> {
7382 self.canonical_type_resolution(analyzer, visible_from, unit)
7383 .ok()
7384 }
7385
7386 pub fn canonical_type_unit_in_context(
7394 &self,
7395 analyzer: &CppGraphSource<'_>,
7396 visible_from: &ProjectFile,
7397 reference: Node<'_>,
7398 unit: &CodeUnit,
7399 ) -> Option<CodeUnit> {
7400 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7401 {
7402 return None;
7403 }
7404 self.canonical_type_resolution(analyzer, visible_from, unit)
7405 .ok()
7406 }
7407
7408 fn canonical_type_resolution(
7416 &self,
7417 analyzer: &CppGraphSource<'_>,
7418 visible_from: &ProjectFile,
7419 unit: &CodeUnit,
7420 ) -> Result<CodeUnit, TypeCandidateFailure> {
7421 let mut current = unit.clone();
7422 let mut seen_aliases = HashSet::default();
7423 loop {
7424 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7425 return current
7426 .is_class()
7427 .then_some(current)
7428 .ok_or(TypeCandidateFailure::Unresolvable);
7429 };
7430 if matches!(target, StructuredAliasTarget::Builtin) {
7431 return current
7432 .is_class()
7433 .then_some(current)
7434 .ok_or(TypeCandidateFailure::Unresolvable);
7435 }
7436 if !seen_aliases.insert(current.clone()) {
7437 return Err(TypeCandidateFailure::Unresolvable);
7438 }
7439 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7440 }
7441 }
7442
7443 pub fn canonical_visible_full_type_unit(
7444 &self,
7445 analyzer: &CppGraphSource<'_>,
7446 visible_from: &ProjectFile,
7447 unit: &CodeUnit,
7448 ) -> Option<CodeUnit> {
7449 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7450 if cpp_class_declaration_strength(analyzer, &canonical)
7451 != CppClassDeclarationStrength::Forward
7452 {
7453 return Some(canonical);
7454 }
7455 let mut full = Vec::new();
7456 for candidate in self
7457 .visible_identifier_candidates(visible_from, canonical.identifier())
7458 .filter(|candidate| {
7459 candidate.is_class()
7460 && candidate.fq_name() == canonical.fq_name()
7461 && cpp_class_declaration_strength(analyzer, candidate)
7462 == CppClassDeclarationStrength::Full
7463 })
7464 {
7465 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7466 full.push(candidate.clone());
7467 }
7468 }
7469 match full.len() {
7470 0 => Some(canonical),
7471 1 => full.pop(),
7472 _ => None,
7473 }
7474 }
7475
7476 fn resolve_structured_alias_target(
7477 &self,
7478 visible_from: &ProjectFile,
7479 declaration: &CodeUnit,
7480 target: &StructuredAliasTarget,
7481 ) -> Option<CodeUnit> {
7482 self.structured_alias_target_resolution(visible_from, declaration, target)
7483 .ok()
7484 }
7485
7486 fn structured_alias_target_resolution(
7487 &self,
7488 visible_from: &ProjectFile,
7489 declaration: &CodeUnit,
7490 target: &StructuredAliasTarget,
7491 ) -> Result<CodeUnit, TypeCandidateFailure> {
7492 let primary =
7493 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7494 let StructuredAliasTarget::Named { arguments, .. } = target else {
7495 return Err(TypeCandidateFailure::Unresolvable);
7496 };
7497 match arguments {
7498 Some(arguments) => self
7499 .resolve_template_arguments(visible_from, primary, arguments)
7500 .map_err(|error| match error {
7501 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7502 TypeCandidateFailure::Ambiguous
7503 }
7504 _ => TypeCandidateFailure::Unresolvable,
7505 }),
7506 None => Ok(primary),
7507 }
7508 }
7509
7510 fn resolve_structured_alias_primary(
7511 &self,
7512 visible_from: &ProjectFile,
7513 declaration: &CodeUnit,
7514 target: &StructuredAliasTarget,
7515 ) -> Option<CodeUnit> {
7516 self.structured_alias_primary_resolution(visible_from, declaration, target)
7517 .ok()
7518 }
7519
7520 fn structured_alias_primary_resolution(
7521 &self,
7522 visible_from: &ProjectFile,
7523 declaration: &CodeUnit,
7524 target: &StructuredAliasTarget,
7525 ) -> Result<CodeUnit, TypeCandidateFailure> {
7526 let StructuredAliasTarget::Named {
7527 components, global, ..
7528 } = target
7529 else {
7530 return Err(TypeCandidateFailure::Unresolvable);
7531 };
7532 let qualified = components.join("::");
7533 let candidates = if *global {
7534 let mut candidates = self.type_candidates(visible_from, &qualified);
7541 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7542 candidates
7543 } else {
7544 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7545 };
7546 logical_type_candidate(candidates)
7547 }
7548
7549 pub fn structured_alias_primary_preserves_target(
7550 &self,
7551 analyzer: &CppGraphSource<'_>,
7552 visible_from: &ProjectFile,
7553 candidate: &CodeUnit,
7554 target: &CodeUnit,
7555 ) -> bool {
7556 let mut current = candidate.clone();
7557 let mut seen = HashSet::default();
7558 let mut matched_target = false;
7559 loop {
7560 if same_visible_symbol(¤t, target)
7561 || self.compatible_primary_template_redeclarations(¤t, target)
7562 {
7563 matched_target = true;
7564 }
7565 if !seen.insert(current.clone()) {
7566 return false;
7567 }
7568 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7569 return matched_target;
7570 };
7571 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7572 return matched_target;
7573 };
7574 let Some(primary) =
7575 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7576 else {
7577 return matched_target;
7583 };
7584 current = primary;
7585 }
7586 }
7587
7588 fn lexical_type_candidates_match_target(
7589 &self,
7590 analyzer: &CppGraphSource<'_>,
7591 unit: &CodeUnit,
7592 candidates: &[CodeUnit],
7593 target: &CodeUnit,
7594 ) -> bool {
7595 same_visible_symbol(unit, target)
7596 || self.same_template_member_identity(analyzer, unit, target)
7597 || candidates.iter().any(|candidate| {
7598 same_visible_symbol(candidate, target)
7599 || self.same_template_member_identity(analyzer, candidate, target)
7600 })
7601 }
7602
7603 pub fn structured_class_alias_resolves_to_target(
7604 &self,
7605 analyzer: &CppGraphSource<'_>,
7606 visible_from: &ProjectFile,
7607 alias: &CodeUnit,
7608 target: &CodeUnit,
7609 ) -> bool {
7610 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7611 return false;
7612 };
7613 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7614 return false;
7615 };
7616 let StructuredAliasTarget::Named {
7617 components, global, ..
7618 } = &alias_target
7619 else {
7620 return false;
7621 };
7622 let lexical_scope = canonical_cpp_scope_components(&owner);
7623 match self.resolve_type_components_lexically_for_target(
7624 analyzer,
7625 visible_from,
7626 components,
7627 *global,
7628 &lexical_scope,
7629 target,
7630 ) {
7631 LexicalTypeResolution::Resolved {
7632 unit, candidates, ..
7633 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7634 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7635 self.structured_alias_primary_preserves_target(
7636 analyzer,
7637 visible_from,
7638 alias,
7639 target,
7640 ) || self.flattened_macro_namespace_alias_target_matches(
7641 analyzer,
7642 visible_from,
7643 alias,
7644 &alias_target,
7645 target,
7646 )
7647 }
7648 }
7649 }
7650
7651 pub fn structured_class_alias_path_preserves_target(
7659 &self,
7660 analyzer: &CppGraphSource<'_>,
7661 visible_from: &ProjectFile,
7662 alias: &CodeUnit,
7663 target: &CodeUnit,
7664 ) -> bool {
7665 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7666 return false;
7667 };
7668 let Some(StructuredAliasTarget::Named {
7669 components, global, ..
7670 }) = self.structured_alias_target(analyzer, alias)
7671 else {
7672 return false;
7673 };
7674 let lexical_scope = canonical_cpp_scope_components(&owner);
7675 (1..components.len()).rev().any(|component_count| {
7676 matches!(
7677 self.resolve_type_components_lexically_for_target(
7678 analyzer,
7679 visible_from,
7680 &components[..component_count],
7681 global,
7682 &lexical_scope,
7683 target,
7684 ),
7685 LexicalTypeResolution::Resolved {
7686 ref unit,
7687 ref candidates,
7688 ..
7689 } if self.lexical_type_candidates_match_target(
7690 analyzer,
7691 unit,
7692 candidates,
7693 target,
7694 )
7695 )
7696 })
7697 }
7698
7699 fn flattened_macro_namespace_alias_target_matches(
7700 &self,
7701 analyzer: &CppGraphSource<'_>,
7702 visible_from: &ProjectFile,
7703 alias: &CodeUnit,
7704 alias_target: &StructuredAliasTarget,
7705 target: &CodeUnit,
7706 ) -> bool {
7707 let StructuredAliasTarget::Named {
7708 components,
7709 global: false,
7710 arguments: None,
7711 } = alias_target
7712 else {
7713 return false;
7714 };
7715 let Some((target_name, namespace_components)) = components.split_last() else {
7716 return false;
7717 };
7718 if namespace_components.is_empty()
7719 || target_name != target.identifier()
7720 || alias.source() != target.source()
7721 || alias.source() != visible_from
7722 || !target.is_class()
7723 || declared_type_alias(analyzer, target)
7724 {
7725 return false;
7726 }
7727 if self
7728 .resolve_structured_alias_target(visible_from, alias, alias_target)
7729 .is_some()
7730 {
7731 return false;
7732 }
7733
7734 let Some(alias_start) = analyzer
7735 .declaration_source_occurrences(alias)
7736 .into_iter()
7737 .flatten()
7738 .map(|occurrence| occurrence.range.start_byte)
7739 .min()
7740 else {
7741 return false;
7742 };
7743 let has_matching_declaration = analyzer
7744 .declaration_source_occurrences(target)
7745 .into_iter()
7746 .flatten()
7747 .any(|occurrence| {
7748 occurrence.range.end_byte <= alias_start
7749 && occurrence.fact.flattened_macro_namespace.as_deref()
7750 == Some(namespace_components)
7751 });
7752 if !has_matching_declaration {
7753 return false;
7754 }
7755
7756 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7757 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7758 guard_requirement_sets_match(&alias_guards, &target_guards)
7759 }
7760
7761 pub fn template_alias_arguments_preserve_target(
7762 &self,
7763 analyzer: &CppGraphSource<'_>,
7764 visible_from: &ProjectFile,
7765 alias: &CodeUnit,
7766 arguments: &[CppTemplateExpression],
7767 target: &CodeUnit,
7768 ) -> bool {
7769 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7770 return false;
7771 };
7772 if metadata.alias_target.is_none()
7773 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7774 {
7775 return false;
7776 }
7777 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7778 }
7779
7780 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7781 self.cpp_template_metadata
7782 .get(unit)
7783 .is_some_and(CppTemplateMetadata::is_primary)
7784 }
7785
7786 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7787 self.cpp_template_metadata
7788 .get(unit)
7789 .is_some_and(CppTemplateMetadata::is_specialization)
7790 }
7791
7792 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7793 same_visible_symbol(left, right)
7794 || self.compatible_primary_template_redeclarations(left, right)
7795 }
7796
7797 pub fn same_template_member_identity(
7798 &self,
7799 analyzer: &CppGraphSource<'_>,
7800 left: &CodeUnit,
7801 right: &CodeUnit,
7802 ) -> bool {
7803 if same_visible_symbol(left, right) {
7804 return true;
7805 }
7806 if left.kind() != right.kind()
7807 || left.identifier() != right.identifier()
7808 || left.signature() != right.signature()
7809 {
7810 return false;
7811 }
7812 let (Some(left_owner), Some(right_owner)) =
7813 (analyzer.parent_of(left), analyzer.parent_of(right))
7814 else {
7815 return false;
7816 };
7817 left_owner.is_class()
7818 && right_owner.is_class()
7819 && self.same_template_owner_identity(&left_owner, &right_owner)
7820 }
7821
7822 fn unique_canonical_type_candidate(
7823 &self,
7824 analyzer: &CppGraphSource<'_>,
7825 visible_from: &ProjectFile,
7826 candidates: &[&CodeUnit],
7827 ) -> Option<CodeUnit> {
7828 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7829 .ok()
7830 }
7831
7832 fn canonical_type_candidate_resolution(
7833 &self,
7834 analyzer: &CppGraphSource<'_>,
7835 visible_from: &ProjectFile,
7836 candidates: &[&CodeUnit],
7837 ) -> Result<CodeUnit, TypeCandidateFailure> {
7838 let mut canonical = Vec::new();
7839 for candidate in candidates {
7840 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7841 if canonical
7842 .iter()
7843 .any(|existing| same_visible_symbol(existing, &resolved))
7844 {
7845 continue;
7846 }
7847 if let Some(existing) = canonical.iter_mut().find(|existing| {
7848 self.compatible_primary_template_redeclarations(existing, &resolved)
7849 }) {
7850 if matches!(
7859 (
7860 cpp_class_declaration_strength(analyzer, existing),
7861 cpp_class_declaration_strength(analyzer, &resolved),
7862 ),
7863 (
7864 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7865 CppClassDeclarationStrength::Full,
7866 ) | (
7867 CppClassDeclarationStrength::Unknown,
7868 CppClassDeclarationStrength::Forward,
7869 )
7870 ) {
7871 *existing = resolved;
7872 }
7873 continue;
7874 }
7875 canonical.push(resolved);
7876 if canonical.len() > 1 {
7877 return Err(TypeCandidateFailure::Ambiguous);
7878 }
7879 }
7880 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7881 }
7882
7883 pub fn unique_type_candidate_preserving_target(
7884 &self,
7885 analyzer: &CppGraphSource<'_>,
7886 visible_from: &ProjectFile,
7887 candidates: &[&CodeUnit],
7888 target: &CodeUnit,
7889 ) -> Option<CodeUnit> {
7890 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7901 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7902 {
7903 return Some(target.clone());
7904 }
7905 let mut resolved_candidates = Vec::new();
7906 for candidate in candidates {
7907 let Some(resolved) =
7913 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7914 else {
7915 continue;
7916 };
7917 if resolved_candidates
7918 .iter()
7919 .any(|existing| same_visible_symbol(existing, &resolved))
7920 {
7921 continue;
7922 }
7923 resolved_candidates.push(resolved);
7924 }
7925 match resolved_candidates.as_slice() {
7926 [] => None,
7927 [single] => Some(single.clone()),
7928 _ => self
7933 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7934 .map(|_| target.clone()),
7935 }
7936 }
7937
7938 pub fn same_fqn_type_spelling_for_target<'b>(
7955 &self,
7956 analyzer: &CppGraphSource<'_>,
7957 visible_from: &ProjectFile,
7958 candidates: &[&'b CodeUnit],
7959 target: &CodeUnit,
7960 ) -> Option<&'b CodeUnit> {
7961 let [first, rest @ ..] = candidates else {
7962 return None;
7963 };
7964 if rest.is_empty()
7965 || !rest.iter().all(|candidate| {
7966 candidate.kind() == first.kind()
7967 && candidate.fq_name() == first.fq_name()
7968 && candidate.source() == first.source()
7969 })
7970 {
7971 return None;
7972 }
7973 candidates
7974 .iter()
7975 .copied()
7976 .find(|candidate| same_symbol(candidate, target))
7977 .or_else(|| {
7978 candidates.iter().copied().find(|candidate| {
7979 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7980 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7981 })
7982 })
7983 }
7984
7985 pub fn alternate_same_fqn_type_declarations(
7986 &self,
7987 analyzer: &CppGraphSource<'_>,
7988 candidates: &[&CodeUnit],
7989 target: &CodeUnit,
7990 ) -> bool {
7991 let Some(first) = candidates.first() else {
7992 return false;
7993 };
7994 let same_api = first.kind() == target.kind()
7995 && first.fq_name() == target.fq_name()
7996 && first.source() == target.source()
7997 && candidates.iter().all(|candidate| {
7998 candidate.kind() == target.kind()
7999 && candidate.fq_name() == target.fq_name()
8000 && candidate.source() == target.source()
8001 })
8002 && candidates
8003 .iter()
8004 .any(|candidate| same_symbol(candidate, target))
8005 && candidates
8006 .iter()
8007 .any(|candidate| !same_logical_symbol(candidate, target));
8008 if !same_api {
8009 return false;
8010 }
8011
8012 let requirements = candidates
8013 .iter()
8014 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8015 .collect::<Vec<_>>();
8016 requirements.len() > 1
8017 && requirements
8018 .iter()
8019 .all(|requirement| !requirement.is_empty())
8020 && requirements.iter().enumerate().all(|(index, left)| {
8021 requirements[index + 1..].iter().all(|right| {
8022 left.iter().all(|(_, left_guards)| {
8023 right.iter().all(|(_, right_guards)| {
8024 merge_preprocessor_guards(left_guards, right_guards).is_none()
8025 })
8026 })
8027 })
8028 })
8029 }
8030
8031 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8032 let mut pending = vec![terms.to_vec()];
8033 while let Some(branch_terms) = pending.pop() {
8034 let mut normalized = Vec::new();
8035 let mut covers_branch = false;
8036 for term in branch_terms {
8037 if term.iter().any(|guard| term.contains(&guard.negated())) {
8038 continue;
8039 }
8040 if term.is_empty() {
8041 covers_branch = true;
8042 break;
8043 }
8044 if !normalized.iter().any(|existing| existing == &term) {
8045 normalized.push(term);
8046 }
8047 }
8048 if covers_branch {
8049 continue;
8050 }
8051 let Some(split_guard) = normalized
8052 .iter()
8053 .flat_map(|term| term.iter())
8054 .next()
8055 .cloned()
8056 else {
8057 return false;
8058 };
8059 let negated_guard = split_guard.negated();
8060 let mut when_defined = Vec::new();
8061 let mut when_undefined = Vec::new();
8062 for term in normalized {
8063 if term.contains(&negated_guard) {
8064 } else if term.contains(&split_guard) {
8066 let mut reduced = term.clone();
8067 reduced.remove(&split_guard);
8068 when_defined.push(reduced);
8069 } else {
8070 when_defined.push(term.clone());
8071 }
8072 if term.contains(&split_guard) {
8073 } else if term.contains(&negated_guard) {
8075 let mut reduced = term;
8076 reduced.remove(&negated_guard);
8077 when_undefined.push(reduced);
8078 } else {
8079 when_undefined.push(term);
8080 }
8081 }
8082 pending.push(when_defined);
8083 pending.push(when_undefined);
8084 }
8085 true
8086 }
8087
8088 fn declarations_share_exhaustive_conditional_family(
8097 &self,
8098 analyzer: &CppGraphSource<'_>,
8099 candidates: &[&CodeUnit],
8100 ) -> Option<(usize, usize)> {
8101 let mut family_range = None;
8102 for candidate in candidates {
8103 let mut candidate_family = None;
8104 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8105 let key = occurrence.exhaustive_family?;
8106 if candidate_family.is_some_and(|existing| existing != key) {
8107 return None;
8108 }
8109 candidate_family = Some(key);
8110 }
8111 let candidate_family = candidate_family?;
8112 if family_range.is_some_and(|existing| existing != candidate_family) {
8113 return None;
8114 }
8115 family_range = Some(candidate_family);
8116 }
8117 family_range
8118 }
8119
8120 pub fn complementary_same_fqn_type_declarations(
8121 &self,
8122 analyzer: &CppGraphSource<'_>,
8123 candidates: &[&CodeUnit],
8124 target: &CodeUnit,
8125 ) -> bool {
8126 if candidates.len() < 2
8127 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8128 || self
8129 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8130 .is_none()
8131 {
8132 return false;
8133 }
8134 Self::preprocessor_guard_terms_cover_all_paths(
8135 &self.declaration_family_guard_terms(analyzer, candidates),
8136 )
8137 }
8138
8139 fn declaration_family_guard_terms(
8140 &self,
8141 analyzer: &CppGraphSource<'_>,
8142 candidates: &[&CodeUnit],
8143 ) -> Vec<HashSet<PreprocessorGuard>> {
8144 candidates
8145 .iter()
8146 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8147 .map(|(_, guards)| guards)
8148 .collect()
8149 }
8150
8151 fn exhaustive_guard_family_activation(
8167 &self,
8168 analyzer: &CppGraphSource<'_>,
8169 candidate: &CodeUnit,
8170 reference: &CallableReferenceContext<'_>,
8171 ) -> Option<usize> {
8172 if !analyzer
8175 .declaration_source_occurrences(candidate)?
8176 .iter()
8177 .any(|occurrence| occurrence.fact.callable_activation.is_some())
8178 {
8179 return None;
8180 }
8181 let family = self
8182 .visible_identifier_candidates(reference.file, candidate.identifier())
8183 .filter(|peer| {
8184 peer.kind() == candidate.kind()
8185 && peer.fq_name() == candidate.fq_name()
8186 && peer.source() == candidate.source()
8187 })
8188 .collect::<Vec<_>>();
8189 let (_, family_end) =
8190 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8191 if !Self::preprocessor_guard_terms_cover_all_paths(
8192 &self.declaration_family_guard_terms(analyzer, &family),
8193 ) {
8194 return None;
8195 }
8196 let effective = self.effective_reference_guards(reference.file, reference.guards());
8204 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8205 .iter()
8206 .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8207 {
8208 return None;
8209 }
8210 (first_declaration_byte(analyzer, candidate)?
8211 == family
8212 .iter()
8213 .filter_map(|peer| first_declaration_byte(analyzer, peer))
8214 .min()?)
8215 .then_some(family_end)
8216 }
8217
8218 fn type_candidate_preserving_target(
8219 &self,
8220 analyzer: &CppGraphSource<'_>,
8221 visible_from: &ProjectFile,
8222 candidate: &CodeUnit,
8223 target: &CodeUnit,
8224 ) -> Option<CodeUnit> {
8225 let mut current = candidate.clone();
8226 let mut matched_target = same_visible_symbol(¤t, target)
8227 || self.compatible_primary_template_redeclarations(¤t, target);
8228 let mut seen = HashSet::default();
8229 loop {
8230 if !seen.insert(current.clone()) {
8231 return None;
8232 }
8233 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8234 return matched_target
8235 .then(|| target.clone())
8236 .or_else(|| current.is_class().then_some(current));
8237 };
8238 if self.flattened_macro_namespace_alias_target_matches(
8239 analyzer,
8240 visible_from,
8241 ¤t,
8242 &alias_target,
8243 target,
8244 ) {
8245 return Some(target.clone());
8246 }
8247 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8248 return matched_target
8249 .then(|| target.clone())
8250 .or_else(|| current.is_class().then_some(current));
8251 }
8252 if !self.cpp_template_metadata.contains_key(¤t)
8260 && let Some(primary) =
8261 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8262 && (same_visible_symbol(&primary, target)
8263 || self.compatible_primary_template_redeclarations(&primary, target))
8264 {
8265 return Some(target.clone());
8266 }
8267 if same_visible_symbol(¤t, target) {
8268 return Some(target.clone());
8269 }
8270 if self.cpp_template_metadata.contains_key(¤t) {
8271 return None;
8272 }
8273 let Some(next) =
8274 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
8275 else {
8276 return matched_target.then(|| target.clone());
8277 };
8278 current = next;
8279 matched_target |= same_visible_symbol(¤t, target)
8280 || self.compatible_primary_template_redeclarations(¤t, target);
8281 }
8282 }
8283
8284 fn compatible_primary_template_redeclarations(
8285 &self,
8286 left: &CodeUnit,
8287 right: &CodeUnit,
8288 ) -> bool {
8289 let (Some(left_metadata), Some(right_metadata)) = (
8290 self.cpp_template_metadata.get(left),
8291 self.cpp_template_metadata.get(right),
8292 ) else {
8293 return false;
8294 };
8295 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8296 && left_metadata.is_primary()
8297 && right_metadata.is_primary()
8298 && cpp_reconcile_primary_template_parameters(
8299 &[(left, left_metadata), (right, right_metadata)],
8300 right,
8301 )
8302 .is_some()
8303 }
8304
8305 fn alias_candidate_may_preserve_target(
8306 &self,
8307 analyzer: &CppGraphSource<'_>,
8308 visible_from: &ProjectFile,
8309 candidate: &CodeUnit,
8310 target: &CodeUnit,
8311 ) -> bool {
8312 let mut current = candidate.clone();
8313 let mut seen = HashSet::default();
8314 loop {
8315 if same_visible_symbol(¤t, target)
8316 || self.compatible_primary_template_redeclarations(¤t, target)
8317 {
8318 return true;
8319 }
8320 if self.cpp_template_metadata.contains_key(¤t) {
8321 return true;
8322 }
8323 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8324 return false;
8325 };
8326 let StructuredAliasTarget::Named {
8327 components,
8328 global,
8329 arguments,
8330 } = alias_target
8331 else {
8332 return false;
8333 };
8334 if arguments.is_some() || !seen.insert(current.clone()) {
8335 return true;
8336 }
8337 let qualified = components.join("::");
8338 let next = if global {
8339 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8340 } else {
8341 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8342 };
8343 let Some(next) = next else {
8344 return true;
8345 };
8346 current = next;
8347 }
8348 }
8349
8350 fn alias_candidate_structurally_reaches_target(
8351 &self,
8352 analyzer: &CppGraphSource<'_>,
8353 visible_from: &ProjectFile,
8354 candidate: &CodeUnit,
8355 target: &CodeUnit,
8356 ) -> bool {
8357 let mut current = candidate.clone();
8358 let mut seen = HashSet::default();
8359 loop {
8360 if same_visible_symbol(¤t, target)
8361 || self.compatible_primary_template_redeclarations(¤t, target)
8362 || self
8363 .cpp_template_metadata
8364 .get(¤t)
8365 .zip(self.cpp_template_metadata.get(target))
8366 .is_some_and(|(current, target)| {
8367 current.primary_fq_name == target.primary_fq_name
8368 })
8369 {
8370 return true;
8371 }
8372 if !seen.insert(current.clone()) {
8373 return false;
8374 }
8375 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8376 return false;
8377 };
8378 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8379 && components[..components.len().saturating_sub(1)]
8380 .iter()
8381 .any(|component| {
8382 self.type_reference_component_directly_names_target(component, target)
8383 })
8384 && self.structured_class_alias_path_preserves_target(
8385 analyzer,
8386 visible_from,
8387 ¤t,
8388 target,
8389 )
8390 {
8391 return true;
8392 }
8393 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8394 return false;
8395 }
8396 let Some(primary) =
8397 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8398 else {
8399 return false;
8400 };
8401 current = primary;
8402 }
8403 }
8404
8405 pub fn qualified_alias_reference_may_reach_target(
8417 &self,
8418 analyzer: &CppGraphSource<'_>,
8419 file: &ProjectFile,
8420 components: &[String],
8421 global: bool,
8422 target: &CodeUnit,
8423 ) -> bool {
8424 for end in 0..components.len() {
8425 let spelled = &components[..=end];
8426 for candidate in self
8427 .visible_identifier_candidates(file, &components[end])
8428 .filter(|candidate| declared_type_alias(analyzer, candidate))
8429 {
8430 let candidate_components = canonical_cpp_scope_components(candidate);
8431 let shape_matches = if global {
8432 candidate_components == spelled
8433 } else {
8434 candidate_components.ends_with(spelled)
8435 };
8436 if shape_matches
8437 && self.alias_candidate_structurally_reaches_target(
8438 analyzer, file, candidate, target,
8439 )
8440 {
8441 return true;
8442 }
8443 }
8444 }
8445 false
8446 }
8447
8448 fn type_candidates_for_declaration<'b>(
8452 &'b self,
8453 visible_from: &ProjectFile,
8454 declaration: &CodeUnit,
8455 raw_name: &str,
8456 ) -> Vec<&'b CodeUnit> {
8457 let Some(normalized) = normalize_reference_name(raw_name) else {
8458 return Vec::new();
8459 };
8460 if let Some(namespace) = cpp_namespace_for(declaration) {
8461 for prefix in namespace_prefixes(&namespace) {
8462 let qualified = format!("{prefix}::{normalized}");
8463 let candidates = self.type_candidates(visible_from, &qualified);
8464 if !candidates.is_empty() {
8465 return candidates;
8466 }
8467 }
8468 }
8469 self.type_candidates(visible_from, &normalized)
8470 }
8471
8472 fn resolve_unique_type_for_declaration(
8473 &self,
8474 visible_from: &ProjectFile,
8475 declaration: &CodeUnit,
8476 raw_name: &str,
8477 ) -> Option<CodeUnit> {
8478 unique_logical_type_candidate(self.type_candidates_for_declaration(
8479 visible_from,
8480 declaration,
8481 raw_name,
8482 ))
8483 }
8484
8485 fn resolve_defining_type_for_declaration(
8503 &self,
8504 analyzer: &CppGraphSource<'_>,
8505 visible_from: &ProjectFile,
8506 declaration: &CodeUnit,
8507 raw_name: &str,
8508 ) -> Option<CodeUnit> {
8509 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8510 let logical = unique_logical_type_candidate(candidates.clone())?;
8511 let mut defining = candidates.into_iter().filter(|candidate| {
8512 candidate.is_class()
8513 && cpp_class_declaration_strength(analyzer, candidate)
8514 == CppClassDeclarationStrength::Full
8515 });
8516 match (defining.next(), defining.next()) {
8517 (Some(unique_definition), None) => Some(unique_definition.clone()),
8518 _ => Some(logical),
8519 }
8520 }
8521
8522 pub fn resolves_to_type(
8523 &self,
8524 analyzer: &CppGraphSource<'_>,
8525 file: &ProjectFile,
8526 raw_name: &str,
8527 target: &CodeUnit,
8528 ) -> bool {
8529 let Some(normalized) = normalize_reference_name(raw_name) else {
8530 return false;
8531 };
8532 let candidates = self.type_candidates(file, &normalized);
8533 if candidates.is_empty() {
8534 return self.parser_alias_resolves_to_type(file, raw_name, target);
8535 }
8536 let Some(resolved) =
8537 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8538 else {
8539 return false;
8540 };
8541 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8542 }
8543
8544 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8545 let mut current = alias.clone();
8546 let mut visited = HashSet::default();
8547 while visited.insert(current.clone()) {
8548 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8549 let resolved =
8550 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8551 if resolved.is_class() {
8552 return Some(resolved);
8553 }
8554 if !declared_type_alias(&self.cpp_source(), &resolved) {
8555 return None;
8556 }
8557 current = resolved;
8558 }
8559 None
8560 }
8561
8562 pub fn same_logical_callable(
8577 &self,
8578 analyzer: &CppGraphSource<'_>,
8579 left: &CodeUnit,
8580 right: &CodeUnit,
8581 ) -> bool {
8582 if same_logical_symbol(left, right) {
8583 return true;
8584 }
8585 if left.kind() != right.kind()
8586 || !left.is_callable()
8587 || !right.is_callable()
8588 || left.fq_name() != right.fq_name()
8589 {
8590 return false;
8591 }
8592 let (Some(left_comparable), Some(right_comparable)) = (
8593 self.callable_comparable(analyzer, left),
8594 self.callable_comparable(analyzer, right),
8595 ) else {
8596 return false;
8597 };
8598 let binding = match (&left_comparable.template, &right_comparable.template) {
8606 (Some(left_template), Some(right_template))
8607 if left_template.is_templated() || right_template.is_templated() =>
8608 {
8609 match left_template.reconcile(right_template) {
8610 CppTemplateReconciliation::Equivalent(binding) => binding,
8611 CppTemplateReconciliation::Distinct
8612 | CppTemplateReconciliation::Unsupported => return false,
8613 }
8614 }
8615 (Some(_), Some(_)) => CppTemplateBinding::default(),
8616 _ if self.callable_is_template_declaration(analyzer, left)
8617 || self.callable_is_template_declaration(analyzer, right) =>
8618 {
8619 return false;
8620 }
8621 _ => CppTemplateBinding::default(),
8622 };
8623 if left_comparable.suffix != right_comparable.suffix
8628 || left_comparable.shapes.len() != right_comparable.shapes.len()
8629 {
8630 return false;
8631 }
8632 left_comparable
8633 .shapes
8634 .iter()
8635 .zip(right_comparable.shapes.iter())
8636 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8637 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8638 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8639 self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8640 }
8641 _ => false,
8645 })
8646 }
8647
8648 pub fn same_logical_class_definition(
8661 &self,
8662 analyzer: &CppGraphSource<'_>,
8663 left: &CodeUnit,
8664 right: &CodeUnit,
8665 ) -> bool {
8666 if same_symbol(left, right) {
8667 return true;
8668 }
8669 if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8670 return false;
8671 }
8672 let key = if left <= right {
8673 (left.clone(), right.clone())
8674 } else {
8675 (right.clone(), left.clone())
8676 };
8677 if let Some(cached) = self
8678 .class_definition_agreements
8679 .lock()
8680 .expect("C++ class definition agreement cache poisoned")
8681 .get(&key)
8682 {
8683 return *cached;
8684 }
8685 let agree = self.class_definitions_agree(analyzer, left, right);
8686 self.class_definition_agreements
8687 .lock()
8688 .expect("C++ class definition agreement cache poisoned")
8689 .insert(key, agree);
8690 agree
8691 }
8692
8693 fn class_definitions_agree(
8694 &self,
8695 analyzer: &CppGraphSource<'_>,
8696 left: &CodeUnit,
8697 right: &CodeUnit,
8698 ) -> bool {
8699 let complete_definition = |class: &CodeUnit| {
8700 let mut facts = analyzer
8701 .declaration_source_properties(class)?
8702 .into_iter()
8703 .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8704 let fact = facts.next()?;
8705 facts.next().is_none().then_some(fact)
8708 };
8709 let (Some(left_fact), Some(right_fact)) =
8710 (complete_definition(left), complete_definition(right))
8711 else {
8712 return false;
8713 };
8714 if left_fact.bases != right_fact.bases {
8715 return false;
8716 }
8717 let (Some(left_template), Some(right_template)) = (
8718 self.declaration_template_context(left, left_fact.declaration),
8719 self.declaration_template_context(right, right_fact.declaration),
8720 ) else {
8721 return false;
8722 };
8723 if !matches!(
8724 left_template.reconcile(&right_template),
8725 CppTemplateReconciliation::Equivalent(_)
8726 ) {
8727 return false;
8728 }
8729 let members = |class: &CodeUnit| {
8730 let mut members = analyzer
8731 .direct_children(class)
8732 .into_iter()
8733 .filter(|member| member.source() == class.source())
8734 .map(|member| {
8735 (
8736 member.kind(),
8737 member.identifier().to_owned(),
8738 member.signature().map(str::to_owned),
8739 )
8740 })
8741 .collect::<Vec<_>>();
8742 members.sort();
8743 members
8744 };
8745 members(left) == members(right)
8746 }
8747
8748 pub fn class_definition_bridges_to_file(
8758 &self,
8759 analyzer: &CppGraphSource<'_>,
8760 class: &CodeUnit,
8761 file: &ProjectFile,
8762 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8763 ) -> bool {
8764 class.is_class()
8765 && class.source() != file
8766 && crate::identity::cpp_source_path_is_header(class.source())
8767 && analyzer
8768 .index
8769 .definitions_by_structured_name(class.fq(), Language::Cpp)
8770 .into_iter()
8771 .any(|copy| {
8772 copy.is_class()
8776 && copy.source() != class.source()
8777 && copy.source() != file
8778 && crate::identity::cpp_source_path_is_header(copy.source())
8779 && header_body_related(copy.source(), file)
8780 && self.same_logical_class_definition(analyzer, class, ©)
8781 })
8782 }
8783
8784 pub fn member_declaration_bridges_to_body(
8788 &self,
8789 analyzer: &CppGraphSource<'_>,
8790 declaration: &CodeUnit,
8791 body: &CodeUnit,
8792 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8793 ) -> bool {
8794 declaration.is_callable()
8795 && self
8796 .cpp
8797 .structural_parent_of(declaration)
8798 .is_some_and(|owner| {
8799 self.class_definition_bridges_to_file(
8800 analyzer,
8801 &owner,
8802 body.source(),
8803 header_body_related,
8804 )
8805 })
8806 }
8807
8808 fn comparable_shapes_agree(
8814 &self,
8815 analyzer: &CppGraphSource<'_>,
8816 binding: &CppTemplateBinding,
8817 left: &CppComparableParameter,
8818 right: &CppComparableParameter,
8819 ) -> bool {
8820 let mut stack = vec![(left.root(), right.root())];
8821 while let Some((left_index, right_index)) = stack.pop() {
8822 match (left.node(left_index), right.node(right_index)) {
8823 (
8824 CppComparableNode::Named {
8825 name: left_name,
8826 primitive: left_primitive,
8827 konst: left_konst,
8828 volatil: left_volatil,
8829 },
8830 CppComparableNode::Named {
8831 name: right_name,
8832 primitive: right_primitive,
8833 konst: right_konst,
8834 volatil: right_volatil,
8835 },
8836 ) => {
8837 if left_konst != right_konst
8838 || left_volatil != right_volatil
8839 || left_primitive != right_primitive
8840 || !self.comparable_names_agree(
8841 analyzer,
8842 binding,
8843 left_name,
8844 right_name,
8845 *left_primitive,
8846 )
8847 {
8848 return false;
8849 }
8850 }
8851 (
8852 CppComparableNode::Pointer {
8853 inner: left_inner,
8854 konst: left_konst,
8855 volatil: left_volatil,
8856 },
8857 CppComparableNode::Pointer {
8858 inner: right_inner,
8859 konst: right_konst,
8860 volatil: right_volatil,
8861 },
8862 ) => {
8863 if left_konst != right_konst || left_volatil != right_volatil {
8864 return false;
8865 }
8866 stack.push((*left_inner, *right_inner));
8867 }
8868 (
8869 CppComparableNode::Reference { inner: left_inner },
8870 CppComparableNode::Reference { inner: right_inner },
8871 )
8872 | (
8873 CppComparableNode::Array { inner: left_inner },
8874 CppComparableNode::Array { inner: right_inner },
8875 ) => stack.push((*left_inner, *right_inner)),
8876 (
8877 CppComparableNode::Generic {
8878 base: left_base,
8879 arguments: left_arguments,
8880 },
8881 CppComparableNode::Generic {
8882 base: right_base,
8883 arguments: right_arguments,
8884 },
8885 ) => {
8886 if left_arguments.len() != right_arguments.len() {
8887 return false;
8888 }
8889 stack.push((*left_base, *right_base));
8890 stack.extend(
8891 left_arguments.iter().zip(right_arguments.iter()).map(
8892 |(left_argument, right_argument)| (*left_argument, *right_argument),
8893 ),
8894 );
8895 }
8896 _ => return false,
8897 }
8898 }
8899 true
8900 }
8901
8902 fn comparable_names_agree(
8917 &self,
8918 analyzer: &CppGraphSource<'_>,
8919 binding: &CppTemplateBinding,
8920 left: &StructuredTypeName,
8921 right: &StructuredTypeName,
8922 primitive: bool,
8923 ) -> bool {
8924 if primitive {
8925 return left.path() == right.path();
8926 }
8927 if !binding.is_empty() {
8928 let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
8929 else {
8930 return false;
8931 };
8932 match (
8933 binding.is_left_parameter(left_head),
8934 binding.is_right_parameter(right_head),
8935 ) {
8936 (true, true) => {
8937 return !left.is_absolute()
8938 && !right.is_absolute()
8939 && binding.binds(right_head, left_head)
8940 && left.path()[1..] == right.path()[1..];
8941 }
8942 (false, false) => {}
8943 _ => return false,
8944 }
8945 }
8946 match (
8947 self.comparable_name_terminal(analyzer, left),
8948 self.comparable_name_terminal(analyzer, right),
8949 ) {
8950 (Some(left_terminal), Some(right_terminal)) => {
8951 same_logical_symbol(&left_terminal, &right_terminal)
8952 }
8953 (None, None) => {
8954 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8955 }
8956 _ => false,
8957 }
8958 }
8959
8960 fn comparable_name_terminal(
8970 &self,
8971 analyzer: &CppGraphSource<'_>,
8972 name: &StructuredTypeName,
8973 ) -> Option<CodeUnit> {
8974 let mut current = self.comparable_name_declaration(analyzer, name)?;
8975 let mut visited = HashSet::default();
8976 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8977 if !declared_type_alias(analyzer, ¤t) {
8984 return current.is_class().then_some(current);
8985 }
8986 if !visited.insert(current.clone()) {
8987 return None;
8988 }
8989 let facts = analyzer.declaration_source_properties(¤t)?;
8990 if facts.iter().any(|fact| fact.adds_indirection) {
8991 return None;
8992 }
8993 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
8994 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8995 }
8996 None
8997 }
8998
8999 fn comparable_alias_target(
9010 &self,
9011 analyzer: &CppGraphSource<'_>,
9012 alias: &CodeUnit,
9013 raw_target: &str,
9014 ) -> Option<CodeUnit> {
9015 let absolute = raw_target.trim_start().starts_with("::");
9020 let normalized = normalize_reference_name(raw_target)?;
9021 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9022 brokk_bifrost_core::analyzer::Language::Cpp,
9023 &normalized,
9024 );
9025 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9026 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9027 brokk_bifrost_core::analyzer::Language::Cpp,
9028 &namespace,
9029 )
9030 });
9031 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9032 self.comparable_name_declaration(analyzer, &name)
9033 }
9034
9035 fn comparable_name_declaration(
9043 &self,
9044 analyzer: &CppGraphSource<'_>,
9045 name: &StructuredTypeName,
9046 ) -> Option<CodeUnit> {
9047 let mut cache = self
9054 .comparable_name_declarations
9055 .lock()
9056 .expect("C++ comparable name declaration cache poisoned");
9057 if let Some(cached) = cache.get(name) {
9058 return cached.clone();
9059 }
9060 let interner = segment_interner();
9061 let identifier = name.path().last()?;
9062 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
9063 let first_depth = if name.is_absolute() {
9064 0
9065 } else {
9066 name.lexical_scope().len()
9067 };
9068 let mut resolved = None;
9069 for depth in (0..=first_depth).rev() {
9070 let mut structured = FqName::new();
9071 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9072 structured.push(interner.intern(component, SegmentKind::Unknown));
9073 }
9074 let mut candidates = candidates_by_identifier
9075 .iter()
9076 .filter(|unit| unit.fq().same_segment_texts(&structured))
9077 .filter(|unit| {
9078 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
9079 })
9080 .cloned();
9081 let Some(first) = candidates.next() else {
9082 continue;
9083 };
9084 resolved = candidates
9085 .all(|unit| same_logical_symbol(&unit, &first))
9086 .then_some(first);
9087 break;
9088 }
9089 cache.insert(name.clone(), resolved.clone());
9090 resolved
9091 }
9092
9093 fn callable_comparable(
9099 &self,
9100 analyzer: &CppGraphSource<'_>,
9101 unit: &CodeUnit,
9102 ) -> Option<Arc<ExtractedComparable>> {
9103 if let Some(cached) = self
9104 .callable_comparables
9105 .lock()
9106 .expect("C++ callable comparable cache poisoned")
9107 .get(unit)
9108 .cloned()
9109 {
9110 return cached;
9111 }
9112 let extracted = self
9113 .extract_callable_comparable(analyzer, unit)
9114 .map(Arc::new);
9115 self.callable_comparables
9116 .lock()
9117 .expect("C++ callable comparable cache poisoned")
9118 .insert(unit.clone(), extracted.clone());
9119 extracted
9120 }
9121
9122 fn extract_callable_comparable(
9123 &self,
9124 analyzer: &CppGraphSource<'_>,
9125 unit: &CodeUnit,
9126 ) -> Option<ExtractedComparable> {
9127 let fact = analyzer
9128 .declaration_source_properties(unit)?
9129 .into_iter()
9130 .find(|fact| fact.callable_comparable_shapes.is_some())?;
9131 let template = self.declaration_template_context(unit, fact.declaration);
9132 Some(ExtractedComparable {
9133 shapes: fact.callable_comparable_shapes?,
9134 suffix: fact.callable_identity_suffix?,
9135 template,
9136 })
9137 }
9138
9139 pub fn declaration_template_context(
9142 &self,
9143 unit: &CodeUnit,
9144 declaration: SourceDeclarationId,
9145 ) -> Option<CppTemplateContext> {
9146 let facts = self
9147 .cpp
9148 .declaration_source_facts(self.token, unit.source())?;
9149 let occurrence = facts.source.declaration(declaration).occurrence;
9150 let range = facts.source.occurrence(occurrence).range;
9151 let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9152 let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9153 while node.kind() == "template_declaration" {
9156 node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9157 }
9158 cpp_declaration_template_context(node, syntax.source())
9159 }
9160
9161 pub fn canonical_type_for_reference(
9162 &self,
9163 file: &ProjectFile,
9164 raw_name: &str,
9165 ) -> Option<CodeUnit> {
9166 let resolved = self.resolve_type(file, raw_name)?;
9167 self.alias_target(&resolved).or(Some(resolved))
9168 }
9169
9170 pub fn parser_alias_resolves_to_type(
9171 &self,
9172 file: &ProjectFile,
9173 raw_name: &str,
9174 target: &CodeUnit,
9175 ) -> bool {
9176 let Some(alias_name) = normalize_reference_name(raw_name) else {
9177 return false;
9178 };
9179 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9180 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9181 })
9182 }
9183
9184 fn file_alias_matches(
9185 &self,
9186 cpp: &dyn CppSource,
9187 file: &ProjectFile,
9188 alias_name: &str,
9189 target: &CodeUnit,
9190 ) -> bool {
9191 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9192 cell.get()
9193 .expect("published canonical aliases")
9194 .iter()
9195 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9196 })
9197 }
9198
9199 fn canonical_alias_cell(
9200 &self,
9201 cpp: &dyn CppSource,
9202 file: &ProjectFile,
9203 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9204 let cell = {
9205 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
9206 Arc::clone(
9207 cells
9208 .entry(file.clone())
9209 .or_insert_with(|| Arc::new(OnceLock::new())),
9210 )
9211 };
9212 if cell.get().is_none() {
9213 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
9214 cell.get_or_init(|| {
9215 self.parser_alias_source_parses
9216 .fetch_add(1, Ordering::Relaxed);
9217 #[cfg(any(test, feature = "test-support"))]
9218 {
9219 *self
9220 .alias_source_parse_counts
9221 .lock()
9222 .expect("alias source parse count lock")
9223 .entry(file.clone())
9224 .or_default() += 1;
9225 }
9226 aliases.into_boxed_slice()
9227 });
9228 }
9229 Some(cell)
9230 }
9231
9232 #[cfg(any(test, feature = "test-support"))]
9233 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9234 self.visible_source_files_by_root
9235 .get(file)
9236 .cloned()
9237 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9238 }
9239
9240 #[cfg(any(test, feature = "test-support"))]
9241 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9242 self.alias_source_parse_counts
9243 .lock()
9244 .expect("alias source parse count lock")
9245 .get(file)
9246 .copied()
9247 .unwrap_or(0)
9248 }
9249
9250 #[cfg(any(test, feature = "test-support"))]
9251 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9252 self.parser_alias_fallback_files.load(Ordering::Relaxed)
9253 }
9254
9255 pub fn resolve_named(
9256 &self,
9257 file: &ProjectFile,
9258 raw_name: &str,
9259 kind: TargetKind,
9260 ) -> Option<CodeUnit> {
9261 let normalized = normalize_reference_name(raw_name)?;
9262 self.named_candidates_for_normalized(file, &normalized, kind)
9263 .into_iter()
9264 .next()
9265 .cloned()
9266 }
9267
9268 pub fn contains_named_symbol(
9269 &self,
9270 file: &ProjectFile,
9271 raw_name: &str,
9272 kind: TargetKind,
9273 target: &CodeUnit,
9274 ) -> bool {
9275 let Some(normalized) = normalize_reference_name(raw_name) else {
9276 return false;
9277 };
9278 self.named_candidates_for_normalized(file, &normalized, kind)
9279 .into_iter()
9280 .any(|unit| {
9281 matches_kind_for_lookup(unit, kind)
9282 && reference_matches_unit(&normalized, unit)
9283 && same_visible_symbol(unit, target)
9284 })
9285 }
9286
9287 pub fn named_candidates(
9288 &self,
9289 file: &ProjectFile,
9290 raw_name: &str,
9291 kind: TargetKind,
9292 ) -> Vec<CodeUnit> {
9293 let Some(normalized) = normalize_reference_name(raw_name) else {
9294 return Vec::new();
9295 };
9296 self.named_candidates_for_normalized(file, &normalized, kind)
9297 .into_iter()
9298 .cloned()
9299 .collect()
9300 }
9301
9302 pub fn resolve_known_non_target(
9303 &self,
9304 file: &ProjectFile,
9305 raw_name: &str,
9306 kind: TargetKind,
9307 target: &CodeUnit,
9308 ) -> bool {
9309 let Some(normalized) = normalize_reference_name(raw_name) else {
9310 return false;
9311 };
9312 normalized.contains("::")
9313 && self
9314 .named_candidates_for_normalized(file, &normalized, kind)
9315 .into_iter()
9316 .any(|unit| {
9317 matches_kind_for_lookup(unit, kind)
9318 && reference_matches_unit(&normalized, unit)
9319 && !same_visible_symbol(unit, target)
9320 })
9321 }
9322
9323 pub fn resolve_call_return_binding(
9324 &self,
9325 analyzer: &CppGraphSource<'_>,
9326 file: &ProjectFile,
9327 raw_name: &str,
9328 arity: usize,
9329 lexical_namespace: Option<&str>,
9330 direct_type: Option<&CodeUnit>,
9331 ) -> Option<CppScanBinding> {
9332 let normalized = normalize_reference_name(raw_name)?;
9333 let mut candidates = Vec::new();
9334 for function in
9335 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9336 {
9337 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9338 && !direct_type.is_some_and(|direct_type| {
9339 self.callable_is_constructor_declaration(analyzer, function)
9340 && type_owner_of(analyzer, function)
9341 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9342 })
9343 {
9344 candidates.push(function.clone());
9345 }
9346 }
9347 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9348 unanimous_return_binding(analyzer, self, file, &candidates)
9349 }
9350
9351 pub fn resolve_call_return_binding_without_arity(
9352 &self,
9353 analyzer: &CppGraphSource<'_>,
9354 file: &ProjectFile,
9355 raw_name: &str,
9356 lexical_namespace: Option<&str>,
9357 direct_type: Option<&CodeUnit>,
9358 ) -> (bool, Option<CppScanBinding>) {
9359 let Some(normalized) = normalize_reference_name(raw_name) else {
9360 return (false, None);
9361 };
9362 let mut candidates = self
9363 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9364 .into_iter()
9365 .filter(|function| {
9366 function.is_function()
9367 && !direct_type.is_some_and(|direct_type| {
9368 self.callable_is_constructor_declaration(analyzer, function)
9369 && type_owner_of(analyzer, function)
9370 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9371 })
9372 })
9373 .cloned()
9374 .collect::<Vec<_>>();
9375 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9376 let has_candidates = !candidates.is_empty();
9377 (
9378 has_candidates,
9379 unanimous_return_binding(analyzer, self, file, &candidates),
9380 )
9381 }
9382
9383 pub fn visible_identifier_candidates<'b>(
9384 &'b self,
9385 file: &ProjectFile,
9386 identifier: &str,
9387 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9388 self.visible_by_identifier
9389 .get(file)
9390 .and_then(|by_name| by_name.get(identifier))
9391 .into_iter()
9392 .flatten()
9393 }
9394
9395 pub fn type_reference_component_directly_names_target(
9401 &self,
9402 component: &str,
9403 target: &CodeUnit,
9404 ) -> bool {
9405 component == target.identifier()
9406 || self
9407 .cpp_template_metadata
9408 .get(target)
9409 .is_some_and(|metadata| component == metadata.primary_name)
9410 }
9411
9412 pub fn visible_type_reference_component_names_for_target(
9420 &self,
9421 analyzer: &CppGraphSource<'_>,
9422 file: &ProjectFile,
9423 target: &CodeUnit,
9424 ) -> HashSet<String> {
9425 let mut names = HashSet::from_iter([target.identifier().to_string()]);
9426 if let Some(metadata) = self.cpp_template_metadata.get(target) {
9427 names.insert(metadata.primary_name.clone());
9428 }
9429
9430 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9431 for (identifier, candidates) in by_identifier {
9432 if candidates.iter().any(|candidate| {
9433 (candidate.is_class()
9434 && (same_visible_symbol(candidate, target)
9435 || self.compatible_primary_template_redeclarations(candidate, target)))
9436 || (declared_type_alias(analyzer, candidate)
9437 && self.alias_candidate_structurally_reaches_target(
9438 analyzer, file, candidate, target,
9439 ))
9440 }) {
9441 names.insert(identifier.clone());
9442 }
9443 }
9444 }
9445
9446 names
9447 }
9448
9449 pub fn indexed_structural_class_scope(
9450 &self,
9451 file: &ProjectFile,
9452 class: Node<'_>,
9453 source: &str,
9454 ) -> Option<Vec<String>> {
9455 let key = (file.clone(), class.start_byte(), class.end_byte());
9456 if let Some(cached) = self
9457 .indexed_structural_class_scopes
9458 .lock()
9459 .expect("C++ indexed structural-class scope cache poisoned")
9460 .get(&key)
9461 .cloned()
9462 {
9463 return cached;
9464 }
9465 let resolved = (|| {
9466 let name = class.child_by_field_name("name")?;
9467 let identifier = if name.kind() == "template_type" {
9468 node_text(name.child_by_field_name("name")?, source).to_string()
9469 } else {
9470 let mut components = Vec::new();
9471 append_cpp_name_components(name, source, &mut components)?;
9472 components.last()?.clone()
9473 };
9474 let visible = self
9475 .visible_identifier_candidates(file, &identifier)
9476 .cloned()
9477 .collect::<Vec<_>>();
9478 let mut visible = visible;
9479 for candidate in
9480 self.visible_by_file
9481 .get(file)
9482 .into_iter()
9483 .flatten()
9484 .filter(|candidate| {
9485 self.cpp_template_metadata
9486 .get(candidate)
9487 .is_some_and(|metadata| metadata.primary_name == identifier)
9488 })
9489 {
9490 if !visible
9491 .iter()
9492 .any(|existing| same_logical_symbol(existing, candidate))
9493 {
9494 visible.push(candidate.clone());
9495 }
9496 }
9497 let cpp_source = self.cpp_source();
9500 let candidates = visible
9501 .iter()
9502 .filter(|candidate| {
9503 candidate.source() == file
9504 && candidate.is_class()
9505 && !declared_type_alias(&cpp_source, candidate)
9506 && self.cpp.ranges(candidate).iter().any(|range| {
9507 range.start_byte <= class.start_byte()
9508 && class.end_byte() <= range.end_byte
9509 })
9510 })
9511 .collect::<Vec<_>>();
9512 let owner = if name.kind() == "template_type" {
9513 let expected = normalize_cpp_whitespace(node_text(name, source));
9514 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9515 let exact = candidates
9516 .iter()
9517 .copied()
9518 .filter(|candidate| {
9519 candidate
9520 .fq()
9521 .segments()
9522 .iter()
9523 .rev()
9524 .find_map(|&segment| {
9525 let (text, kind) = interner.resolve(segment);
9526 matches!(
9527 kind,
9528 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9529 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9530 )
9531 .then_some(text)
9532 })
9533 .is_some_and(|text| text == expected)
9534 })
9535 .collect::<Vec<_>>();
9536 unique_logical_type_candidate(exact)
9537 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9538 } else {
9539 unique_logical_type_candidate(candidates)?
9540 };
9541 Some(canonical_cpp_scope_components(&owner))
9542 })();
9543 self.indexed_structural_class_scopes
9544 .lock()
9545 .expect("C++ indexed structural-class scope cache poisoned")
9546 .insert(key, resolved.clone());
9547 resolved
9548 }
9549
9550 pub fn indexed_enclosing_owner_scope(
9551 &self,
9552 analyzer: &CppGraphSource<'_>,
9553 file: &ProjectFile,
9554 node: Node<'_>,
9555 ) -> Option<Vec<String>> {
9556 let anchor = std::iter::successors(Some(node), |current| current.parent())
9557 .find(|current| {
9558 matches!(
9559 current.kind(),
9560 "function_definition"
9561 | "class_specifier"
9562 | "struct_specifier"
9563 | "union_specifier"
9564 )
9565 })
9566 .unwrap_or(node);
9567 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9568 if let Some(cached) = self
9569 .indexed_enclosing_owner_scopes
9570 .lock()
9571 .expect("C++ indexed enclosing-owner scope cache poisoned")
9572 .get(&key)
9573 .cloned()
9574 {
9575 return cached;
9576 }
9577 let resolved = (|| {
9578 let range = Range {
9579 start_byte: node.start_byte(),
9580 end_byte: node.end_byte(),
9581 start_line: node.start_position().row,
9582 end_line: node.end_position().row,
9583 };
9584 let start = analyzer.enclosing_code_unit(file, &range)?;
9585 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9586 start,
9587 |unit| self.cached_precise_parent_of(analyzer, unit),
9588 )
9589 .find(|unit| {
9590 unit.is_class()
9591 && !analyzer
9592 .type_alias_provider()
9593 .is_some_and(|provider| provider.is_type_alias(unit))
9594 })?;
9595 Some(canonical_cpp_scope_components(&owner))
9596 })();
9597 self.indexed_enclosing_owner_scopes
9598 .lock()
9599 .expect("C++ indexed enclosing-owner scope cache poisoned")
9600 .insert(key, resolved.clone());
9601 resolved
9602 }
9603
9604 fn cached_precise_parent_of(
9605 &self,
9606 analyzer: &CppGraphSource<'_>,
9607 code_unit: &CodeUnit,
9608 ) -> Option<CodeUnit> {
9609 if let Some(cached) = self
9610 .precise_parent_cache
9611 .lock()
9612 .expect("C++ precise-parent cache poisoned")
9613 .get(code_unit)
9614 .cloned()
9615 {
9616 return cached;
9617 }
9618 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9619 self.precise_parent_cache
9620 .lock()
9621 .expect("C++ precise-parent cache poisoned")
9622 .insert(code_unit.clone(), resolved.clone());
9623 resolved
9624 }
9625
9626 pub fn callable_is_constructor_declaration(
9627 &self,
9628 analyzer: &CppGraphSource<'_>,
9629 candidate: &CodeUnit,
9630 ) -> bool {
9631 if !candidate.is_function() {
9632 return false;
9633 }
9634 if analyzer
9635 .declaration_source_properties(candidate)
9636 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9637 {
9638 return true;
9639 }
9640 let metadata = analyzer.signature_metadata(candidate);
9641 !metadata.is_empty()
9642 && metadata
9643 .iter()
9644 .all(|signature| signature.return_type_text().is_none())
9645 }
9646
9647 pub fn callable_is_deduction_guide_declaration(
9655 &self,
9656 analyzer: &CppGraphSource<'_>,
9657 candidate: &CodeUnit,
9658 ) -> bool {
9659 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9660 }
9661
9662 pub fn callable_is_template_declaration(
9666 &self,
9667 analyzer: &CppGraphSource<'_>,
9668 candidate: &CodeUnit,
9669 ) -> bool {
9670 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9671 }
9672
9673 pub fn type_name_candidates<'b>(
9674 &'b self,
9675 file: &ProjectFile,
9676 normalized: &str,
9677 ) -> Vec<&'b CodeUnit> {
9678 self.candidate_units(file, normalized, TargetKind::Type)
9679 }
9680
9681 pub fn visible_members_for_owner_name<'b>(
9682 &'b self,
9683 file: &ProjectFile,
9684 owner: &CodeUnit,
9685 name: &str,
9686 ) -> Vec<&'b CodeUnit> {
9687 self.visible_identifier_candidates(file, name)
9688 .filter(|unit| {
9689 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9693 .is_some_and(|parent| parent == owner.fq_name())
9694 })
9695 .collect()
9696 }
9697
9698 pub fn visible_member_for_owner_name(
9699 &self,
9700 file: &ProjectFile,
9701 owner: &CodeUnit,
9702 name: &str,
9703 ) -> VisibleMemberResolution {
9704 let candidates = self.visible_members_for_owner_name(file, owner, name);
9705 let mut callables = Vec::new();
9706 let mut non_callable = None;
9707 for candidate in candidates {
9708 if candidate.is_function() {
9709 callables.push(candidate.clone());
9710 } else if non_callable.is_none() {
9711 non_callable = Some(candidate.clone());
9712 }
9713 }
9714 match (callables.is_empty(), non_callable) {
9715 (false, None) => VisibleMemberResolution::Callable(callables),
9716 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9717 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9718 (true, None) => VisibleMemberResolution::Missing,
9719 }
9720 }
9721
9722 fn field_declared_type_fact(
9723 &self,
9724 analyzer: &CppGraphSource<'_>,
9725 field: &CodeUnit,
9726 ) -> Option<DeclaredFieldTypeFact> {
9727 if let Some(cached) = self
9728 .field_type_facts
9729 .lock()
9730 .expect("C++ field type fact cache poisoned")
9731 .get(field)
9732 .cloned()
9733 {
9734 return cached;
9735 }
9736 let decoded = decode_field_declared_type_fact(analyzer, field);
9737 self.field_type_facts
9738 .lock()
9739 .expect("C++ field type fact cache poisoned")
9740 .insert(field.clone(), decoded.clone());
9741 decoded
9742 }
9743
9744 fn structured_alias_target(
9745 &self,
9746 analyzer: &CppGraphSource<'_>,
9747 unit: &CodeUnit,
9748 ) -> Option<StructuredAliasTarget> {
9749 if let Some(cached) = self
9750 .structured_alias_targets
9751 .lock()
9752 .expect("C++ structured alias target cache poisoned")
9753 .get(unit)
9754 .cloned()
9755 {
9756 return cached;
9757 }
9758 let decoded = decode_structured_alias_target(analyzer, unit);
9759 self.structured_alias_targets
9760 .lock()
9761 .expect("C++ structured alias target cache poisoned")
9762 .insert(unit.clone(), decoded.clone());
9763 decoded
9764 }
9765
9766 pub fn type_candidates<'b>(
9767 &'b self,
9768 file: &ProjectFile,
9769 normalized: &str,
9770 ) -> Vec<&'b CodeUnit> {
9771 let mut candidates = self
9772 .candidate_units(file, normalized, TargetKind::Type)
9773 .into_iter()
9774 .filter(|unit| {
9775 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9776 })
9777 .collect::<Vec<_>>();
9778 dedup_unit_refs(&mut candidates);
9779 candidates
9780 }
9781
9782 pub fn named_candidates_for_normalized<'b>(
9783 &'b self,
9784 file: &ProjectFile,
9785 normalized: &str,
9786 kind: TargetKind,
9787 ) -> Vec<&'b CodeUnit> {
9788 let mut candidates = self
9789 .candidate_units(file, normalized, kind)
9790 .into_iter()
9791 .filter(|unit| {
9792 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9793 })
9794 .collect::<Vec<_>>();
9795 dedup_unit_refs(&mut candidates);
9796 candidates
9797 }
9798
9799 pub fn candidate_units<'b>(
9800 &'b self,
9801 file: &ProjectFile,
9802 normalized: &str,
9803 kind: TargetKind,
9804 ) -> Vec<&'b CodeUnit> {
9805 if normalized.contains("::") {
9806 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9815 brokk_bifrost_core::analyzer::Language::Cpp,
9816 normalized,
9817 );
9818 let Some(identifier) = parts.last() else {
9819 return Vec::new();
9820 };
9821 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9822 return self
9823 .visible_identifier_candidates(file, identifier)
9824 .filter(|unit| {
9825 #[cfg(any(test, feature = "test-support"))]
9826 self.qualified_candidate_inspections
9827 .fetch_add(1, Ordering::Relaxed);
9828 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9829 || canonical_cpp_name_matches(unit, normalized)
9830 })
9831 .collect();
9832 }
9833 self.visible_identifier_candidates(file, normalized)
9834 .collect()
9835 }
9836
9837 #[cfg(any(test, feature = "test-support"))]
9838 pub fn reset_qualified_candidate_inspections(&self) {
9839 self.qualified_candidate_inspections
9840 .store(0, Ordering::Relaxed);
9841 }
9842
9843 #[cfg(any(test, feature = "test-support"))]
9844 pub fn qualified_candidate_inspections(&self) -> usize {
9845 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9846 }
9847
9848 #[cfg(any(test, feature = "test-support"))]
9849 pub fn visibility_identifier_lookup_count(&self) -> usize {
9850 self.visibility_identifier_lookup_count
9851 }
9852
9853 #[cfg(any(test, feature = "test-support"))]
9854 pub fn visibility_identifier_batch_count(&self) -> usize {
9855 self.visibility_identifier_batch_count
9856 }
9857
9858 #[cfg(any(test, feature = "test-support"))]
9859 pub fn reset_target_preserving_type_resolution_count(&self) {
9860 self.target_preserving_type_resolution_count
9861 .store(0, Ordering::Relaxed);
9862 }
9863
9864 #[cfg(any(test, feature = "test-support"))]
9865 pub fn target_preserving_type_resolution_count(&self) -> usize {
9866 self.target_preserving_type_resolution_count
9867 .load(Ordering::Relaxed)
9868 }
9869
9870 #[cfg(any(test, feature = "test-support"))]
9871 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9872 self.visible_parser_alias_name_set_build_count
9873 .load(Ordering::Relaxed)
9874 }
9875}
9876
9877fn callable_declaration_has_fact(
9878 analyzer: &CppGraphSource<'_>,
9879 candidate: &CodeUnit,
9880 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9881) -> bool {
9882 candidate.is_function()
9883 && analyzer
9884 .declaration_source_properties(candidate)
9885 .is_some_and(|facts| facts.iter().any(matches))
9886}
9887
9888#[derive(Default)]
9889struct IncludeGraph {
9890 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9891}
9892
9893impl IncludeGraph {
9894 fn extend_with<F>(
9895 &mut self,
9896 root: &ProjectFile,
9897 cancellation: Option<&CancellationToken>,
9898 targets_for: &mut F,
9899 ) where
9900 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9901 {
9902 let mut stack = vec![root.clone()];
9903 while let Some(file) = stack.pop() {
9904 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9905 break;
9906 }
9907 if self.targets_by_file.contains_key(&file) {
9908 continue;
9909 }
9910 let targets = targets_for(&file);
9911 stack.extend(targets.iter().cloned());
9912 self.targets_by_file.insert(file, targets);
9913 }
9914 }
9915
9916 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9917 self.targets_by_file.keys()
9918 }
9919
9920 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9921 self.targets_by_file
9922 .get(file)
9923 .map(Vec::as_slice)
9924 .unwrap_or_default()
9925 }
9926
9927 fn reachable_files(
9928 &self,
9929 root: &ProjectFile,
9930 cancellation: Option<&CancellationToken>,
9931 ) -> HashSet<ProjectFile> {
9932 let mut pending = vec![root.clone()];
9933 let mut visited = HashSet::default();
9934 while let Some(file) = pending.pop() {
9935 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9936 break;
9937 }
9938 if visited.insert(file.clone()) {
9939 pending.extend(self.targets(&file).iter().cloned());
9940 }
9941 }
9942 visited
9943 }
9944}
9945
9946fn build_bounded_visible_declarations(
9947 cpp: &dyn CppSource,
9948 token: QueryToken<'_>,
9949 analyzer: &CppGraphSource<'_>,
9950 roots: &HashSet<ProjectFile>,
9951 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9952 cancellation: Option<&CancellationToken>,
9953 stats: &mut BoundedVisibilityStats,
9954) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9955 let mut candidates_by_identifier = HashMap::default();
9956 let mut declarations_by_source_and_reading: HashMap<
9957 (ProjectFile, bool),
9958 BoundedVisibilityDeclarations,
9959 > = HashMap::default();
9960 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9961 roots
9962 .iter()
9963 .map(|root| {
9964 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9965 let root_declarations = declarations_by_source_and_reading
9966 .entry((root.clone(), reading_is_c))
9967 .or_insert_with(|| {
9968 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9969 });
9970 let mut visible = root_declarations
9971 .all
9972 .iter()
9973 .cloned()
9974 .collect::<HashSet<_>>();
9975 let mut pending_names = HashSet::default();
9976 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9977 let mut cursor = prepared.tree().walk();
9982 let mut pending_nodes = vec![prepared.tree().root_node()];
9983 while let Some(node) = pending_nodes.pop() {
9984 if matches!(
9985 node.kind(),
9986 "identifier"
9987 | "type_identifier"
9988 | "field_identifier"
9989 | "namespace_identifier"
9990 ) {
9991 pending_names.insert(node_text(node, prepared.source()).to_string());
9992 }
9993 if node.kind() == "preproc_arg" {
9994 for reference in
9995 object_macro_replacement_type_references(node, prepared.source())
9996 {
9997 pending_names.extend(reference.components);
9998 }
9999 }
10000 pending_nodes.extend(node.named_children(&mut cursor));
10001 }
10002 }
10003 stats.root_names += pending_names.len();
10004 let mut completed_names = HashSet::default();
10005 while !pending_names.is_empty() {
10006 stats.rounds += 1;
10007 let round_names = std::mem::take(&mut pending_names);
10008 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10009 HashMap::default();
10010 let mut identifiers = Vec::new();
10011 for identifier in round_names {
10012 if !completed_names.insert(identifier.clone())
10013 || cancellation.is_some_and(CancellationToken::is_cancelled)
10014 {
10015 continue;
10016 }
10017 identifiers.push(identifier);
10018 }
10019 let missing_identifiers = identifiers
10020 .iter()
10021 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10022 .cloned()
10023 .collect::<HashSet<_>>();
10024 if !missing_identifiers.is_empty() {
10025 let lookup_started = Instant::now();
10026 let mut candidates = cpp
10027 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10028 stats.lookup_elapsed += lookup_started.elapsed();
10029 stats.identifier_lookups += missing_identifiers.len();
10030 stats.identifier_batches += 1;
10031 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10032 break;
10033 }
10034 for identifier in missing_identifiers {
10035 let units = candidates.remove(&identifier).unwrap_or_default();
10036 let candidate_count = units.len();
10037 let candidate_sources = units
10038 .into_iter()
10039 .map(|unit| unit.source().clone())
10040 .collect::<HashSet<_>>();
10041 candidates_by_identifier
10042 .insert(identifier, (candidate_sources, candidate_count));
10043 }
10044 }
10045 for identifier in identifiers {
10046 let (candidate_sources, candidate_count) = candidates_by_identifier
10047 .get(&identifier)
10048 .expect("every missing identifier was inserted after the batch lookup");
10049 stats.candidate_units += *candidate_count;
10050 for source in candidate_sources.iter().cloned() {
10051 if source != *root
10052 && visible_sources
10053 .get(root)
10054 .is_some_and(|files| files.contains(&source))
10055 {
10056 requested_names_by_source
10057 .entry(source)
10058 .or_default()
10059 .insert(identifier.clone());
10060 }
10061 }
10062 }
10063 stats.candidate_sources += requested_names_by_source.len();
10064 for (source, requested_names) in requested_names_by_source {
10065 let declarations = declarations_by_source_and_reading
10066 .entry((source.clone(), reading_is_c))
10067 .or_insert_with(|| {
10068 bounded_visibility_declarations(
10069 cpp,
10070 analyzer,
10071 &source,
10072 reading_is_c,
10073 stats,
10074 )
10075 });
10076 let mut selected = HashSet::default();
10077 for name in requested_names {
10078 if let Some(units) = declarations.by_identifier.get(&name) {
10079 selected.extend(units.iter().cloned());
10080 }
10081 }
10082 for unit in selected {
10083 stats.selected_units += 1;
10084 let dependency_names = dependency_names_by_unit
10085 .entry(unit.clone())
10086 .or_insert_with(|| {
10087 let facts_started = Instant::now();
10088 let mut dependency_names = HashSet::default();
10089 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10090 for fact in facts {
10091 for name in fact.dependency_type_names {
10092 stats.dependency_source_names += 1;
10093 #[cfg(any(test, feature = "test-support"))]
10094 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10095 .with(|count| count.set(count.get() + 1));
10096 dependency_names.insert(name);
10097 }
10098 }
10099 stats.dependency_source_elapsed += facts_started.elapsed();
10100 }
10101 dependency_names
10102 });
10103 for name in dependency_names.iter() {
10104 if !completed_names.contains(name) && pending_names.insert(name.clone())
10105 {
10106 stats.dependency_names += 1;
10107 }
10108 }
10109 visible.insert(unit);
10110 }
10111 }
10112 }
10113 (root.clone(), visible)
10114 })
10115 .collect()
10116}
10117
10118struct BoundedVisibilityDeclarations {
10119 all: Vec<CodeUnit>,
10120 by_identifier: HashMap<String, Vec<CodeUnit>>,
10121}
10122
10123fn bounded_visibility_declarations(
10124 cpp: &dyn CppSource,
10125 analyzer: &CppGraphSource<'_>,
10126 file: &ProjectFile,
10127 c_semantics: bool,
10128 stats: &mut BoundedVisibilityStats,
10129) -> BoundedVisibilityDeclarations {
10130 let declarations_started = Instant::now();
10131 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10132 .into_iter()
10133 .collect::<Vec<_>>();
10134 stats.declaration_elapsed += declarations_started.elapsed();
10135 stats.declaration_reads += 1;
10136 stats.declaration_units += all.len();
10137
10138 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10139 for unit in &all {
10140 by_identifier
10141 .entry(unit.identifier().to_string())
10142 .or_default()
10143 .push(unit.clone());
10144 if unit.is_class()
10145 && let Some(metadata) = cpp.template_metadata(unit)
10146 && metadata.primary_name != unit.identifier()
10147 {
10148 by_identifier
10149 .entry(metadata.primary_name)
10150 .or_default()
10151 .push(unit.clone());
10152 }
10153 }
10154 BoundedVisibilityDeclarations { all, by_identifier }
10155}
10156
10157#[derive(Default)]
10158struct BoundedVisibilityStats {
10159 rounds: usize,
10160 root_names: usize,
10161 identifier_lookups: usize,
10162 identifier_batches: usize,
10163 candidate_units: usize,
10164 candidate_sources: usize,
10165 declaration_reads: usize,
10166 declaration_units: usize,
10167 selected_units: usize,
10168 dependency_source_names: usize,
10169 dependency_names: usize,
10170 lookup_elapsed: Duration,
10171 declaration_elapsed: Duration,
10172 dependency_source_elapsed: Duration,
10173}
10174
10175fn bounded_visibility_declarations_in_reading(
10176 analyzer: &CppGraphSource<'_>,
10177 file: &ProjectFile,
10178 c_semantics: bool,
10179) -> BTreeSet<CodeUnit> {
10180 #[cfg(any(test, feature = "test-support"))]
10181 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10182 analyzer.declarations_in_reading(file, c_semantics)
10183}
10184
10185#[cfg(any(test, feature = "test-support"))]
10186pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10187 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10188}
10189
10190#[cfg(any(test, feature = "test-support"))]
10191pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10192 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10193}
10194
10195#[cfg(any(test, feature = "test-support"))]
10196pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10197 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10198}
10199
10200#[cfg(any(test, feature = "test-support"))]
10201pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10202 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10203}
10204
10205#[derive(Default)]
10224struct OutOfLineOwnerBindingStats {
10225 unseen_owners: usize,
10226 definition_lookups: usize,
10227 admitted: usize,
10228}
10229
10230fn extend_with_out_of_line_owner_bindings(
10231 cpp: &dyn CppSource,
10232 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10233) -> OutOfLineOwnerBindingStats {
10234 let mut stats = OutOfLineOwnerBindingStats::default();
10235 for (file, visible) in visible_by_file.iter_mut() {
10236 let mut unseen_owners: HashSet<String> = visible
10240 .iter()
10241 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10242 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10243 .collect();
10244 if unseen_owners.is_empty() {
10245 continue;
10246 }
10247 for unit in visible.iter().filter(|unit| unit.is_class()) {
10248 unseen_owners.remove(&unit.fq_name());
10249 }
10250 stats.unseen_owners += unseen_owners.len();
10251 stats.definition_lookups += unseen_owners.len();
10252 let admitted = unseen_owners
10253 .iter()
10254 .flat_map(|owner| cpp.definitions(owner))
10255 .filter(CodeUnit::is_class)
10256 .collect::<Vec<_>>();
10257 stats.admitted += admitted.len();
10258 visible.extend(admitted);
10259 }
10260 stats
10261}
10262
10263pub enum VisibleMemberResolution {
10264 Callable(Vec<CodeUnit>),
10265 NonCallable,
10266 AmbiguousKind,
10267 Missing,
10268}
10269
10270#[derive(Clone)]
10271pub enum EnclosingMemberOwnerResolution {
10272 Owner(CodeUnit),
10273 Ambiguous,
10274 Missing,
10275}
10276
10277pub fn resolve_declaring_member_owner(
10278 analyzer: &CppGraphSource<'_>,
10279 visibility: &VisibilityIndex<'_>,
10280 file: &ProjectFile,
10281 receiver_owner: &CodeUnit,
10282 member_name: &str,
10283) -> EnclosingMemberOwnerResolution {
10284 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10285 return EnclosingMemberOwnerResolution::Missing;
10286 };
10287 let Some(receiver_owner) =
10288 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10289 else {
10290 return EnclosingMemberOwnerResolution::Ambiguous;
10291 };
10292 let resolve_level = |frontier: &[CodeUnit]| {
10293 let mut member_owners = Vec::new();
10294 for raw_owner in frontier {
10295 let Some(owner) =
10296 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10297 else {
10298 return EnclosingMemberOwnerResolution::Ambiguous;
10299 };
10300 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10301 if !member.is_field() && !member.is_function() {
10306 continue;
10307 }
10308 let Some(member_owner) = type_owner_of(analyzer, member) else {
10309 return EnclosingMemberOwnerResolution::Ambiguous;
10310 };
10311 if !member_owners
10312 .iter()
10313 .any(|existing| same_visible_symbol(existing, &member_owner))
10314 {
10315 member_owners.push(member_owner);
10316 }
10317 }
10318 }
10319 match member_owners.len() {
10320 0 => EnclosingMemberOwnerResolution::Missing,
10321 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10322 _ => EnclosingMemberOwnerResolution::Ambiguous,
10323 }
10324 };
10325 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10329 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10330 return direct;
10331 }
10332 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
10333 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10334 let mut path_matches = Vec::new();
10335 while let Some(raw_owner) = stack.pop() {
10336 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10337 else {
10338 return EnclosingMemberOwnerResolution::Ambiguous;
10339 };
10340 let propagated = propagated_counts.entry(owner.clone()).or_default();
10344 if *propagated == 2 {
10345 continue;
10346 }
10347 *propagated += 1;
10348 match resolve_level(std::slice::from_ref(&owner)) {
10349 EnclosingMemberOwnerResolution::Owner(owner) => {
10350 path_matches.push(owner);
10351 if path_matches.len() == 2 {
10352 return EnclosingMemberOwnerResolution::Ambiguous;
10353 }
10354 }
10355 EnclosingMemberOwnerResolution::Ambiguous => {
10356 return EnclosingMemberOwnerResolution::Ambiguous;
10357 }
10358 EnclosingMemberOwnerResolution::Missing => {
10359 stack.extend(hierarchy.get_direct_ancestors(&owner));
10360 }
10361 }
10362 }
10363 match path_matches.len() {
10364 0 => EnclosingMemberOwnerResolution::Missing,
10365 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10366 _ => unreachable!("base-path matches are capped at one before returning"),
10367 }
10368}
10369
10370pub fn resolve_declaring_callable_owner(
10385 analyzer: &CppGraphSource<'_>,
10386 visibility: &VisibilityIndex<'_>,
10387 file: &ProjectFile,
10388 ordinary: EnclosingMemberOwnerResolution,
10389 member_name: &str,
10390 call_arity: usize,
10391) -> EnclosingMemberOwnerResolution {
10392 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10393 return ordinary;
10394 };
10395 if visibility
10396 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10397 .into_iter()
10398 .any(|unit| {
10399 unit.is_function()
10400 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10401 })
10402 {
10403 return ordinary;
10404 }
10405
10406 let mut pending = match member_using_declaration_bases(
10407 analyzer,
10408 visibility,
10409 file,
10410 ordinary_owner,
10411 member_name,
10412 ) {
10413 Ok(bases) => bases,
10414 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10415 };
10416 let mut visited = HashSet::default();
10417 let mut introduced_owners = Vec::new();
10418 while let Some(owner) = pending.pop() {
10419 if !visited.insert(owner.clone()) {
10420 continue;
10421 }
10422 let accepts_arity = visibility
10423 .visible_members_for_owner_name(file, &owner, member_name)
10424 .into_iter()
10425 .any(|unit| {
10426 unit.is_function()
10427 && cpp_callable_arity(analyzer, unit)
10428 .is_none_or(|known| known.accepts(call_arity))
10429 });
10430 if accepts_arity {
10431 if !introduced_owners
10432 .iter()
10433 .any(|existing| same_visible_symbol(existing, &owner))
10434 {
10435 introduced_owners.push(owner);
10436 }
10437 continue;
10438 }
10439 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10440 Ok(bases) => pending.extend(bases),
10441 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10442 }
10443 }
10444 match introduced_owners.as_slice() {
10445 [] => ordinary,
10446 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10447 _ => EnclosingMemberOwnerResolution::Ambiguous,
10448 }
10449}
10450
10451fn member_using_declaration_bases(
10452 analyzer: &CppGraphSource<'_>,
10453 visibility: &VisibilityIndex<'_>,
10454 file: &ProjectFile,
10455 owner: &CodeUnit,
10456 member_name: &str,
10457) -> Result<Vec<CodeUnit>, ()> {
10458 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10459 return Err(());
10460 };
10461 let scopes: Vec<_> = facts
10462 .iter()
10463 .flat_map(|fact| &fact.member_usings)
10464 .filter(|using| using.member == member_name)
10465 .map(|using| using.scope.join("::"))
10466 .collect();
10467 if scopes.is_empty() {
10468 return Ok(Vec::new());
10469 }
10470 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10471 return Ok(Vec::new());
10472 };
10473 let mut bases = Vec::new();
10474 for raw_ancestor in hierarchy.get_ancestors(owner) {
10475 let Some(ancestor) =
10476 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10477 else {
10478 return Err(());
10479 };
10480 let qualified = cpp_name_for(&ancestor);
10481 if scopes
10482 .iter()
10483 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10484 && !bases
10485 .iter()
10486 .any(|existing| same_visible_symbol(existing, &ancestor))
10487 {
10488 bases.push(ancestor);
10489 }
10490 }
10491 Ok(bases)
10492}
10493
10494pub fn lexical_component_tiers<'a>(
10495 components: &'a [String],
10496 global: bool,
10497 lexical_scope: &'a [String],
10498) -> impl Iterator<Item = Vec<String>> + 'a {
10499 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10500 (0..=first_prefix_len).rev().map(move |prefix_len| {
10501 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10502 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10503 qualified.extend_from_slice(components);
10504 qualified
10505 })
10506}
10507
10508pub fn build_visible_identifier_index(
10509 analyzer: &CppGraphSource<'_>,
10510 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10511 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10512 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10513) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10514 let mut out = HashMap::default();
10515 for (file, visible) in visible_by_file {
10516 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10517 for unit in visible {
10518 if unit.is_field()
10519 && !visible_source_files_by_root
10520 .get(file)
10521 .is_some_and(|sources| sources.contains(unit.source()))
10522 && cpp_global_field_has_internal_linkage_cached(
10523 analyzer,
10524 global_field_internal_linkage,
10525 unit,
10526 )
10527 {
10528 continue;
10529 }
10530 by_identifier
10531 .entry(unit.identifier().to_string())
10532 .or_default()
10533 .push(unit.clone());
10534 }
10535 for units in by_identifier.values_mut() {
10536 sort_lookup_units(units);
10537 units.dedup();
10538 }
10539 out.insert(file.clone(), by_identifier);
10540 }
10541 out
10542}
10543
10544fn sort_lookup_units(units: &mut [CodeUnit]) {
10545 units.sort_by(|left, right| {
10546 left.fq_name()
10547 .cmp(&right.fq_name())
10548 .then_with(|| left.signature().cmp(&right.signature()))
10549 .then_with(|| left.source().cmp(right.source()))
10550 .then_with(|| left.kind().cmp(&right.kind()))
10551 .then_with(|| {
10552 left.package_segment_count()
10553 .cmp(&right.package_segment_count())
10554 })
10555 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10556 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10557 });
10558}
10559
10560fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10561 let interner = segment_interner();
10562 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10563 let (left_text, left_kind) = interner.resolve(left_id);
10564 let (right_text, right_kind) = interner.resolve(right_id);
10565 let order = left_text
10566 .cmp(right_text)
10567 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10568 if order != CmpOrdering::Equal {
10569 return order;
10570 }
10571 }
10572 left.len().cmp(&right.len())
10573}
10574
10575const fn segment_kind_order(kind: SegmentKind) -> u8 {
10576 match kind {
10577 SegmentKind::Path => 0,
10578 SegmentKind::Package => 1,
10579 SegmentKind::Type => 2,
10580 SegmentKind::Companion => 3,
10581 SegmentKind::Nested => 4,
10582 SegmentKind::Member => 5,
10583 SegmentKind::Unknown => 6,
10584 }
10585}
10586
10587fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10588 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10589 std::mem::take(units),
10590 );
10591}
10592
10593pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10594 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10598 brokk_bifrost_core::analyzer::Language::Cpp,
10599 reference,
10600 );
10601 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10602}
10603
10604fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10605 if parts.is_empty() {
10606 return Vec::new();
10607 }
10608
10609 let mut candidates = Vec::new();
10610 for package_len in 0..parts.len() {
10611 let package = parts[..package_len].join("::");
10612 let rest = &parts[package_len..];
10613 if rest.is_empty() {
10614 continue;
10615 }
10616 match kind {
10617 TargetKind::Type | TargetKind::Constructor => {
10618 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10619 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10620 }
10621 TargetKind::FreeFunction
10622 | TargetKind::Method
10623 | TargetKind::GlobalField
10624 | TargetKind::MemberField
10625 | TargetKind::Macro => {
10626 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10627 if rest.len() > 1 {
10628 let owner = rest[..rest.len() - 1].join("$");
10629 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10630 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10631 }
10632 }
10633 }
10634 }
10635 candidates
10636}
10637
10638fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10639 let fqn = if package.is_empty() {
10640 short.to_string()
10641 } else {
10642 format!("{package}.{short}")
10643 };
10644 if !out.contains(&fqn) {
10645 out.push(fqn);
10646 }
10647}
10648
10649pub fn infer_cpp_initializer_type(
10650 analyzer: &CppGraphSource<'_>,
10651 visibility: &VisibilityIndex<'_>,
10652 file: &ProjectFile,
10653 source: &str,
10654 node: Node<'_>,
10655) -> Option<CodeUnit> {
10656 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10657 .and_then(|binding| binding.unit)
10658}
10659
10660pub fn infer_cpp_initializer_binding(
10661 analyzer: &CppGraphSource<'_>,
10662 visibility: &VisibilityIndex<'_>,
10663 file: &ProjectFile,
10664 source: &str,
10665 node: Node<'_>,
10666 receiver_resolver: Option<&ReceiverResolver<'_>>,
10667) -> Option<CppScanBinding> {
10668 match node.kind() {
10669 "new_expression" => {
10670 let type_node = cpp_new_expression_type_node(node)?;
10671 let type_text = node_text(type_node, source);
10672 let name = normalize_cpp_type_name(type_text);
10673 Some(CppScanBinding::from_type_name(
10674 name.clone(),
10675 visibility.resolve_type(file, &name),
10676 1,
10677 ))
10678 }
10679 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10680 if function.kind() == "field_expression" {
10688 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10689 return resolve_field_method_call_return_binding(
10690 analyzer,
10691 visibility,
10692 file,
10693 source,
10694 function,
10695 arity,
10696 receiver_resolver,
10697 );
10698 }
10699 let function_text = node_text(function, source);
10700 let direct_type_binding = visibility
10701 .resolve_type(file, function_text)
10702 .map(|unit| CppScanBinding::from_unit(unit, 0));
10703 if function.kind() == "template_function" && direct_type_binding.is_some() {
10704 let lexical_namespace = enclosing_namespace_context(node, source);
10705 let arity = visibility.call_arity_evidence(file, node, source).exact();
10706 if let Some(arity) = arity
10707 && let Some(binding) = visibility.resolve_call_return_binding(
10708 analyzer,
10709 file,
10710 function_text,
10711 arity,
10712 lexical_namespace.as_deref(),
10713 direct_type_binding
10714 .as_ref()
10715 .and_then(|binding| binding.unit.as_ref()),
10716 )
10717 {
10718 return Some(binding);
10719 }
10720 let (has_callable, callable_binding) = visibility
10721 .resolve_call_return_binding_without_arity(
10722 analyzer,
10723 file,
10724 function_text,
10725 lexical_namespace.as_deref(),
10726 direct_type_binding
10727 .as_ref()
10728 .and_then(|binding| binding.unit.as_ref()),
10729 );
10730 if let Some(binding) = callable_binding {
10731 return Some(binding);
10732 }
10733 if has_callable {
10734 return None;
10735 }
10736 return direct_type_binding;
10737 }
10738 let arity = visibility.call_arity_evidence(file, node, source).exact();
10743 if let Some(arity) = arity {
10744 let direct_type_binding_for_call = direct_type_binding.clone();
10745 if let Some(binding) = resolve_static_method_call_return_binding(
10746 analyzer, visibility, file, source, function, arity,
10747 )
10748 .or_else(|| {
10749 visibility.resolve_call_return_binding(
10754 analyzer,
10755 file,
10756 function_text,
10757 arity,
10758 enclosing_namespace_context(node, source).as_deref(),
10759 direct_type_binding_for_call
10760 .as_ref()
10761 .and_then(|binding| binding.unit.as_ref()),
10762 )
10763 }) {
10764 return Some(binding);
10765 }
10766 }
10767 direct_type_binding
10768 }),
10769 _ => None,
10770 }
10771}
10772
10773pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10779 debug_assert_eq!(expression.kind(), "new_expression");
10780 if let Some(type_node) = expression.child_by_field_name("type") {
10781 return Some(type_node);
10782 }
10783
10784 let mut pending = Vec::new();
10785 let mut cursor = expression.walk();
10786 pending.extend(
10787 expression
10788 .named_children(&mut cursor)
10789 .filter(|child| {
10790 !matches!(
10791 child.kind(),
10792 "argument_list" | "initializer_list" | "new_declarator"
10793 )
10794 })
10795 .collect::<Vec<_>>()
10796 .into_iter()
10797 .rev(),
10798 );
10799 while let Some(node) = pending.pop() {
10800 if is_cpp_type_specifier_node(node) {
10801 return Some(node);
10802 }
10803 if node.kind() == "ERROR" {
10804 let mut cursor = node.walk();
10805 pending.extend(
10806 node.named_children(&mut cursor)
10807 .collect::<Vec<_>>()
10808 .into_iter()
10809 .rev(),
10810 );
10811 }
10812 }
10813 None
10814}
10815
10816fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10817 matches!(
10818 node.kind(),
10819 "class_specifier"
10820 | "decltype"
10821 | "dependent_type"
10822 | "enum_specifier"
10823 | "placeholder_type_specifier"
10824 | "primitive_type"
10825 | "qualified_identifier"
10826 | "sized_type_specifier"
10827 | "struct_specifier"
10828 | "template_type"
10829 | "type_identifier"
10830 | "union_specifier"
10831 )
10832}
10833
10834fn resolve_static_method_call_return_binding(
10835 analyzer: &CppGraphSource<'_>,
10836 visibility: &VisibilityIndex<'_>,
10837 file: &ProjectFile,
10838 source: &str,
10839 function: Node<'_>,
10840 arity: usize,
10841) -> Option<CppScanBinding> {
10842 if function.kind() != "qualified_identifier" {
10843 return None;
10844 }
10845 let qualified = normalize_cpp_reference_text(node_text(function, source));
10846 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10853 brokk_bifrost_core::analyzer::Language::Cpp,
10854 &qualified,
10855 );
10856 let (owner_text, member_name) = match parts.split_last() {
10857 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10858 (owner_parts.join("::"), member.clone())
10859 }
10860 _ => {
10861 let scope = function.child_by_field_name("scope")?;
10862 let name = function.child_by_field_name("name")?;
10863 (
10864 node_text(scope, source).to_string(),
10865 node_text(name, source).to_string(),
10866 )
10867 }
10868 };
10869 let owner = visibility.resolve_type(file, &owner_text)?;
10870 let candidates =
10871 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10872 unanimous_return_binding(analyzer, visibility, file, &candidates)
10873}
10874
10875fn resolve_field_method_call_return_binding(
10876 analyzer: &CppGraphSource<'_>,
10877 visibility: &VisibilityIndex<'_>,
10878 file: &ProjectFile,
10879 source: &str,
10880 function: Node<'_>,
10881 arity: usize,
10882 receiver_resolver: Option<&ReceiverResolver<'_>>,
10883) -> Option<CppScanBinding> {
10884 debug_assert_eq!(
10885 function.kind(),
10886 "field_expression",
10887 "the member-call return binding answers only for a field-expression callee"
10888 );
10889 let receiver_resolver = receiver_resolver?;
10890 let field = function.child_by_field_name("field")?;
10891 let member_name = node_text(function_terminal_node(field), source);
10892 let receiver = function
10893 .child_by_field_name("argument")
10894 .or_else(|| function.named_child(0))?;
10895 let owners = receiver_resolver(receiver, source);
10896 let mut candidates = Vec::new();
10897 for owner in owners {
10898 let declaring_owner =
10899 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10900 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10901 EnclosingMemberOwnerResolution::Missing => continue,
10902 EnclosingMemberOwnerResolution::Ambiguous => return None,
10903 };
10904 candidates.extend(callable_members_accepting_arity(
10905 analyzer,
10906 visibility,
10907 file,
10908 &declaring_owner,
10909 member_name,
10910 arity,
10911 ));
10912 }
10913 unanimous_return_binding(analyzer, visibility, file, &candidates)
10914}
10915
10916fn callable_members_accepting_arity(
10917 analyzer: &CppGraphSource<'_>,
10918 visibility: &VisibilityIndex<'_>,
10919 file: &ProjectFile,
10920 owner: &CodeUnit,
10921 member_name: &str,
10922 arity: usize,
10923) -> Vec<CodeUnit> {
10924 visibility
10925 .visible_members_for_owner_name(file, owner, member_name)
10926 .into_iter()
10927 .filter(|unit| {
10928 unit.is_function()
10929 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10930 })
10931 .cloned()
10932 .collect()
10933}
10934
10935fn unanimous_return_binding(
10936 analyzer: &CppGraphSource<'_>,
10937 visibility: &VisibilityIndex<'_>,
10938 file: &ProjectFile,
10939 candidates: &[CodeUnit],
10940) -> Option<CppScanBinding> {
10941 let mut resolved_return: Option<CppScanBinding> = None;
10942 for function in candidates {
10943 let metadata = analyzer.signature_metadata(function);
10944 let return_types = if metadata.is_empty() {
10945 vec![cpp_function_return_type_text(analyzer, function)?]
10946 } else {
10947 metadata
10948 .iter()
10949 .map(|metadata| metadata.return_type_text().map(str::to_string))
10950 .collect::<Option<Vec<_>>>()?
10951 };
10952 for return_text in return_types {
10953 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10954 let name = normalize_cpp_type_name(&return_text);
10955 let binding = CppScanBinding::from_type_name(
10956 name.clone(),
10957 visibility
10958 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10959 indirection,
10960 );
10961 if let Some(existing) = resolved_return.as_ref()
10962 && (existing.indirection != binding.indirection
10963 || match (&existing.unit, &binding.unit) {
10964 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10965 (None, None) => existing.type_name != binding.type_name,
10966 (Some(_), None) | (None, Some(_)) => true,
10967 })
10968 {
10969 return None;
10970 }
10971 resolved_return = Some(binding);
10972 }
10973 }
10974 resolved_return
10975}
10976
10977fn aliases_from_canonical_source(
10978 cpp: &dyn CppSource,
10979 token: QueryToken<'_>,
10980 file: &ProjectFile,
10981) -> Option<Vec<CppAlias>> {
10982 let source = cpp.declaration_source_facts(token, file)?;
10983 Some(
10984 source
10985 .facts
10986 .declarations
10987 .iter()
10988 .filter_map(|fact| {
10989 let alias = fact.file_scope_alias.as_ref()?;
10990 Some(CppAlias {
10991 name: alias.name.clone(),
10992 target: alias.target.clone(),
10993 namespace: alias.namespace.clone(),
10994 })
10995 })
10996 .collect(),
10997 )
10998}
10999
11000pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11001 let mut current = node.parent();
11002 while let Some(parent) = current {
11003 match parent.kind() {
11004 "translation_unit"
11005 | "namespace_definition"
11006 | "declaration_list"
11007 | "linkage_specification" => current = parent.parent(),
11008 "template_declaration" => current = parent.parent(),
11009 _ => return false,
11010 }
11011 }
11012 true
11013}
11014
11015pub fn collect_include_closure(
11016 analyzer: &CppGraphSource<'_>,
11017 include_targets: &IncludeTargetIndex,
11018 file: &ProjectFile,
11019 out: &mut HashSet<ProjectFile>,
11020 cancellation: Option<&CancellationToken>,
11021) {
11022 let mut stack = vec![file.clone()];
11023 while let Some(file) = stack.pop() {
11024 if cancellation.is_some_and(CancellationToken::is_cancelled) {
11025 break;
11026 }
11027 if !out.insert(file.clone()) {
11028 continue;
11029 }
11030 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11031 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11032 stack.push(target);
11033 }
11034 }
11035 }
11036}
11037
11038fn parse_macro_parameter_list<T>(
11039 replacement: &str,
11040 read: impl FnOnce(Node<'_>, &str) -> T,
11041) -> Option<T> {
11042 let parsed = parse_sentinel(
11043 "void __bifrost_macro_parameters(",
11044 SentinelInput::replacement(replacement),
11045 ");",
11046 );
11047 let (source, tree) = (parsed.source, parsed.tree);
11048 let root = tree.root_node();
11049 if root.has_error() {
11050 return None;
11051 }
11052 let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11053 let declarator = declaration.child_by_field_name("declarator")?;
11054 let parameters = declarator.child_by_field_name("parameters")?;
11055 Some(read(parameters, &source))
11056}
11057
11058fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11059 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11060}
11061
11062pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11065 parse_macro_parameter_list(
11066 replacement,
11067 crate::declarations::cpp_callable_parameter_types,
11068 )
11069}
11070
11071pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11072 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11073}
11074
11075pub fn cpp_callable_parameter_types(
11076 analyzer: &CppGraphSource<'_>,
11077 unit: &CodeUnit,
11078) -> Option<Vec<String>> {
11079 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11080 analyzer.signature_metadata(unit),
11081 )
11082}
11083
11084fn merge_compatible_callable_arities(
11085 left: CallableArity,
11086 right: CallableArity,
11087) -> Option<CallableArity> {
11088 let total = left.total();
11089 let left_repeated = left.accepts(total.saturating_add(1));
11090 let right_repeated = right.accepts(right.total().saturating_add(1));
11091 if total != right.total() || left_repeated != right_repeated {
11092 return None;
11093 }
11094 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11095 Some(CallableArity::new(required, total, left_repeated))
11096}
11097
11098fn collect_macro_events(
11099 cpp: &dyn CppSource,
11100 token: QueryToken<'_>,
11101 file: &ProjectFile,
11102) -> Vec<MacroEvent> {
11103 let Some(prepared) = cpp.prepared_syntax(token, file) else {
11104 return Vec::new();
11105 };
11106 let source = prepared.source();
11107 let mut events = Vec::new();
11108 let root = prepared.tree().root_node();
11109 let mut stack = vec![root];
11110 while let Some(node) = stack.pop() {
11111 match node.kind() {
11112 "preproc_def" | "preproc_function_def" => {
11113 let Some(name) = node.child_by_field_name("name") else {
11114 continue;
11115 };
11116 let name = node_text(name, source).to_string();
11117 events.push(MacroEvent::Define {
11118 name,
11119 binding: MacroBinding {
11120 source: file.clone(),
11121 declaration_byte: node.start_byte(),
11122 definition: VisibilityIndex::decode_macro_definition(node, source),
11123 exact: true,
11124 },
11125 byte: node.start_byte(),
11126 conditionals: owning_preprocessor_conditionals(root, node, source),
11127 });
11128 continue;
11129 }
11130 "preproc_include" => {
11131 let Some(path) = node.child_by_field_name("path") else {
11132 events.push(MacroEvent::Include {
11133 targets: Vec::new(),
11134 byte: node.start_byte(),
11135 conditionals: owning_preprocessor_conditionals(root, node, source),
11136 });
11137 continue;
11138 };
11139 let include = crate::imports::include_path_literal_value(path, source);
11140 let targets = include.map_or_else(Vec::new, |include| {
11141 resolve_include_targets_with_index(file, include, cpp.include_target_index())
11142 });
11143 if targets.is_empty()
11154 && include.is_some_and(|include| {
11155 !cpp.include_target_index().names_indexed_file(include)
11156 })
11157 {
11158 continue;
11159 }
11160 events.push(MacroEvent::Include {
11161 targets,
11162 byte: node.start_byte(),
11163 conditionals: owning_preprocessor_conditionals(root, node, source),
11164 });
11165 continue;
11166 }
11167 "preproc_call" => {
11168 let Some(directive) = node.child_by_field_name("directive") else {
11169 continue;
11170 };
11171 if node_text(directive, source) != "#undef" {
11172 continue;
11173 }
11174 let name = node
11175 .child_by_field_name("argument")
11176 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11177 if let Some(name) = name {
11178 events.push(MacroEvent::Undef {
11179 name,
11180 byte: node.start_byte(),
11181 conditionals: owning_preprocessor_conditionals(root, node, source),
11182 });
11183 } else {
11184 events.push(MacroEvent::Invalidate {
11185 byte: node.start_byte(),
11186 });
11187 }
11188 continue;
11189 }
11190 _ => {}
11191 }
11192 push_named_children_reversed(node, &mut stack);
11193 }
11194 events.sort_by_key(MacroEvent::byte);
11195 events
11196}
11197
11198fn find_include_activation(
11199 cpp: &dyn CppSource,
11200 token: QueryToken<'_>,
11201 file: &ProjectFile,
11202 prepared: &PreparedSyntaxTree,
11203 donor_source: &ProjectFile,
11204 language_guard_is_stable: &dyn Fn() -> bool,
11205) -> Option<usize> {
11206 let include_targets = cpp.include_target_index();
11207 let reference_is_c = file
11208 .rel_path()
11209 .extension()
11210 .and_then(|extension| extension.to_str())
11211 == Some("c");
11212 let mut direct_includes = Vec::new();
11213 let mut nodes = vec![prepared.tree().root_node()];
11214 while let Some(node) = nodes.pop() {
11217 if node.kind() == "preproc_include" {
11218 let Some(required_guards) = include_directive_guard_requirements(
11219 node,
11220 prepared.source(),
11221 reference_is_c,
11222 language_guard_is_stable,
11223 ) else {
11224 continue;
11225 };
11226 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11227 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11228 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11229 file,
11230 include,
11231 include_targets,
11232 )) {
11233 direct_includes.push((node.end_byte(), target));
11234 }
11235 }
11236 }
11237 continue;
11238 }
11239 push_named_children_reversed(node, &mut nodes);
11240 }
11241 direct_includes.sort_by_key(|(activation, _)| *activation);
11242 let mut known_missing = HashSet::default();
11243 direct_includes
11244 .into_iter()
11245 .find(|(_, direct)| {
11246 unconditional_include_reaches(
11247 cpp,
11248 token,
11249 direct,
11250 donor_source,
11251 file,
11252 &mut known_missing,
11253 language_guard_is_stable,
11254 )
11255 })
11256 .map(|(activation, _)| activation)
11257}
11258
11259fn find_conditional_include_projection_index(
11260 cpp: &dyn CppSource,
11261 token: QueryToken<'_>,
11262 file: &ProjectFile,
11263 prepared: &PreparedSyntaxTree,
11264 language_guard_is_stable: &dyn Fn() -> bool,
11265 on_state: &dyn Fn(),
11266) -> ConditionalIncludeProjectionIndex {
11267 let reference_is_c = reference_uses_c_semantics(cpp, file);
11268 let include_targets = cpp.include_target_index();
11269 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11270 HashMap::default();
11271 let mut pending = Vec::new();
11272 let mut nodes = vec![prepared.tree().root_node()];
11273 while let Some(node) = nodes.pop() {
11274 if node.kind() == "preproc_include" {
11275 let Some(required_guards) = include_directive_guard_requirements(
11276 node,
11277 prepared.source(),
11278 reference_is_c,
11279 language_guard_is_stable,
11280 ) else {
11281 continue;
11282 };
11283 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11284 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11285 file,
11286 include,
11287 include_targets,
11288 )) else {
11289 continue;
11290 };
11291 pending.push((target, node.end_byte(), required_guards.clone()));
11292 }
11293 continue;
11294 }
11295 push_named_children_reversed(node, &mut nodes);
11296 }
11297
11298 let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11304 HashMap::default();
11305 while let Some((current_file, activation_byte, path)) = pending.pop() {
11306 let guard_sets = expanded
11307 .entry((current_file.clone(), activation_byte))
11308 .or_default();
11309 if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11310 continue;
11311 }
11312 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11313 .drain(..)
11314 .partition(|existing| path.is_subset(existing));
11315 *guard_sets = kept;
11316 guard_sets.push(path.clone());
11317 if !evicted.is_empty()
11318 && let Some(projections) = projections_by_source.get_mut(¤t_file)
11319 {
11320 projections.retain(|projection| {
11321 projection.activation_byte != activation_byte
11322 || !evicted.iter().any(|existing| {
11323 existing.all == projection.required_guards
11324 && existing.partial == projection.partial_guards
11325 })
11326 });
11327 }
11328 on_state();
11329
11330 projections_by_source
11333 .entry(current_file.clone())
11334 .or_default()
11335 .push(ConditionalIncludeProjection {
11336 activation_byte,
11337 required_guards: path.all.clone(),
11338 partial_guards: path.partial.clone(),
11339 });
11340
11341 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11342 continue;
11343 };
11344 let mut nodes = vec![current_prepared.tree().root_node()];
11345 while let Some(node) = nodes.pop() {
11346 if node.kind() == "preproc_include" {
11347 let Some(include_guards) = include_directive_guard_requirements(
11348 node,
11349 current_prepared.source(),
11350 reference_is_c,
11351 language_guard_is_stable,
11352 ) else {
11353 continue;
11354 };
11355 let Some(reached) = path.merged(&include_guards) else {
11356 continue;
11357 };
11358 for (include, _) in
11359 include_path_from_node(node, current_prepared.source()).into_iter()
11360 {
11361 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11362 ¤t_file,
11363 include,
11364 include_targets,
11365 )) else {
11366 continue;
11367 };
11368 pending.push((target, activation_byte, reached.clone()));
11369 }
11370 continue;
11371 }
11372 push_named_children_reversed(node, &mut nodes);
11373 }
11374 }
11375
11376 projections_by_source
11377 .into_iter()
11378 .map(|(source, mut projections)| {
11379 projections.sort_by_key(|projection| projection.activation_byte);
11380 (source, Arc::from(projections))
11381 })
11382 .collect()
11383}
11384
11385#[allow(clippy::too_many_arguments)]
11391fn find_conditional_include_projection_for_source(
11392 cpp: &dyn CppSource,
11393 token: QueryToken<'_>,
11394 file: &ProjectFile,
11395 prepared: &PreparedSyntaxTree,
11396 donor_source: &ProjectFile,
11397 admission: IncludePathAdmission,
11398 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11399 reference_byte: usize,
11400 language_guard_is_stable: &dyn Fn() -> bool,
11401 on_state: &dyn Fn(),
11402) -> bool {
11403 let Some(reference_guards) = reference_guards else {
11404 return false;
11405 };
11406 let reference_is_c = reference_uses_c_semantics(cpp, file);
11407 let include_targets = cpp.include_target_index();
11408 let mut pending = Vec::new();
11409 let mut nodes = vec![prepared.tree().root_node()];
11410 while let Some(node) = nodes.pop() {
11411 if node.kind() == "preproc_include" {
11412 let Some(required_guards) = include_directive_guard_requirements(
11413 node,
11414 prepared.source(),
11415 reference_is_c,
11416 language_guard_is_stable,
11417 ) else {
11418 continue;
11419 };
11420 if node.end_byte() > reference_byte
11421 || !admission.admits(
11422 &required_guards.all,
11423 &required_guards.partial,
11424 Some(reference_guards),
11425 )
11426 {
11427 continue;
11428 }
11429 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11430 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11431 file,
11432 include,
11433 include_targets,
11434 )) else {
11435 continue;
11436 };
11437 if &target == donor_source {
11438 return true;
11439 }
11440 pending.push((target, required_guards.clone()));
11441 }
11442 continue;
11443 }
11444 push_named_children_reversed(node, &mut nodes);
11445 }
11446
11447 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11448 while let Some((current_file, path)) = pending.pop() {
11449 let guard_sets = expanded.entry(current_file.clone()).or_default();
11450 if guard_sets.contains(&path.all) {
11451 continue;
11452 }
11453 guard_sets.push(path.all.clone());
11454 on_state();
11455
11456 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11457 continue;
11458 };
11459 let mut nodes = vec![current_prepared.tree().root_node()];
11460 while let Some(node) = nodes.pop() {
11461 if node.kind() == "preproc_include" {
11462 let Some(include_guards) = include_directive_guard_requirements(
11463 node,
11464 current_prepared.source(),
11465 reference_is_c,
11466 language_guard_is_stable,
11467 ) else {
11468 continue;
11469 };
11470 let Some(reached) = path.merged(&include_guards) else {
11471 continue;
11472 };
11473 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11474 continue;
11475 }
11476 for (include, _) in
11477 include_path_from_node(node, current_prepared.source()).into_iter()
11478 {
11479 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11480 ¤t_file,
11481 include,
11482 include_targets,
11483 )) else {
11484 continue;
11485 };
11486 if &target == donor_source {
11487 return true;
11488 }
11489 pending.push((target, reached.clone()));
11490 }
11491 continue;
11492 }
11493 push_named_children_reversed(node, &mut nodes);
11494 }
11495 }
11496 false
11497}
11498
11499pub fn cpp_include_closure_reaches(
11512 cpp: &dyn CppSource,
11513 token: QueryToken<'_>,
11514 translation_unit: &ProjectFile,
11515 header: &ProjectFile,
11516) -> bool {
11517 let language_guard_stable = OnceCell::new();
11518 unconditional_include_reaches(
11519 cpp,
11520 token,
11521 translation_unit,
11522 header,
11523 translation_unit,
11524 &mut HashSet::default(),
11525 &|| {
11526 *language_guard_stable.get_or_init(|| {
11527 let mut macro_events =
11528 |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11529 include_language_guard_is_stable_with(
11530 cpp,
11531 token,
11532 translation_unit,
11533 &mut macro_events,
11534 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11535 collect_structured_include_facts(prepared)
11536 },
11537 )
11538 })
11539 },
11540 )
11541}
11542
11543fn unconditional_include_reaches(
11544 cpp: &dyn CppSource,
11545 token: QueryToken<'_>,
11546 first: &ProjectFile,
11547 donor_source: &ProjectFile,
11548 reference_file: &ProjectFile,
11549 known_missing: &mut HashSet<ProjectFile>,
11550 language_guard_is_stable: &dyn Fn() -> bool,
11551) -> bool {
11552 if first == donor_source {
11553 return true;
11554 }
11555 if known_missing.contains(first) {
11556 return false;
11557 }
11558 let reference_is_c = reference_file
11559 .rel_path()
11560 .extension()
11561 .and_then(|extension| extension.to_str())
11562 == Some("c");
11563 if let Some(reaches) =
11564 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11565 {
11566 return reaches;
11567 }
11568 let mut visited = HashSet::default();
11569 let mut files = vec![first.clone()];
11570 let include_targets = cpp.include_target_index();
11571 while let Some(file) = files.pop() {
11572 if file == *donor_source {
11573 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11574 return true;
11575 }
11576 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11577 continue;
11578 }
11579 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11580 continue;
11581 };
11582 let mut cursor = prepared.tree().walk();
11584 let mut nodes = vec![prepared.tree().root_node()];
11585 while let Some(node) = nodes.pop() {
11586 if node.kind() == "preproc_include" {
11587 let Some(required_guards) = include_directive_guard_requirements(
11588 node,
11589 prepared.source(),
11590 reference_is_c,
11591 language_guard_is_stable,
11592 ) else {
11593 continue;
11594 };
11595 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11596 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11597 {
11598 if let Some(target) = unique_include_target(
11599 resolve_include_targets_with_index(&file, include, include_targets),
11600 ) {
11601 files.push(target);
11602 }
11603 }
11604 }
11605 continue;
11606 }
11607 let first_pushed = nodes.len();
11608 nodes.extend(node.named_children(&mut cursor));
11609 nodes[first_pushed..].reverse();
11610 }
11611 }
11612 known_missing.extend(visited);
11613 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11614 false
11615}
11616
11617fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11618 cpp: &dyn CppSource,
11619 token: QueryToken<'_>,
11620 file: &ProjectFile,
11621 mut macro_events: MacroEvents,
11622 mut include_facts: IncludeFacts,
11623) -> bool
11624where
11625 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11626 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11627{
11628 if !crate::imports::is_cpp_translation_unit(file) {
11632 return false;
11633 }
11634 if !cpp.compile_contexts_for(file).is_empty()
11637 || cpp
11638 .reaching_translation_units(file)
11639 .iter()
11640 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11641 {
11642 return false;
11643 }
11644 let mut pending = vec![file.clone()];
11645 let mut visited = HashSet::default();
11646 while let Some(file) = pending.pop() {
11647 if !visited.insert(file.clone()) {
11648 continue;
11649 }
11650 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11651 return false;
11652 };
11653 if macro_events(&file).iter().any(|event| match event {
11654 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11655 name == "__cplusplus"
11656 }
11657 MacroEvent::Invalidate { .. } => true,
11658 MacroEvent::Include { targets, .. } => targets.is_empty(),
11659 }) {
11660 return false;
11661 }
11662 for fact in include_facts(&file, prepared.as_ref()).iter() {
11665 let targets =
11666 resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11667 if targets.is_empty() {
11668 return false;
11669 }
11670 pending.extend(targets);
11671 }
11672 }
11673 true
11674}
11675
11676fn declaration_guard_requirements(
11677 analyzer: &CppGraphSource<'_>,
11678 cpp: &dyn CppSource,
11679 candidate: &CodeUnit,
11680) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11681 if cpp
11682 .prepared_syntax(analyzer.token, candidate.source())
11683 .is_none()
11684 {
11685 return Vec::new();
11686 }
11687 analyzer
11688 .declaration_source_occurrences(candidate)
11689 .into_iter()
11690 .flatten()
11691 .filter_map(|occurrence| {
11692 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11693 Some((occurrence.range.start_byte, guards))
11694 })
11695 .collect()
11696}
11697
11698struct DeclarationGuardSite {
11701 byte: usize,
11703 guards: HashSet<PreprocessorGuard>,
11705 completed_family_before_reference: bool,
11708}
11709
11710fn declaration_guard_sites(
11713 analyzer: &CppGraphSource<'_>,
11714 cpp: &dyn CppSource,
11715 candidate: &CodeUnit,
11716 reference_byte: usize,
11717) -> Vec<DeclarationGuardSite> {
11718 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11719 return Vec::new();
11720 };
11721 let root = prepared.tree().root_node();
11722 analyzer
11723 .declaration_source_occurrences(candidate)
11724 .into_iter()
11725 .flatten()
11726 .filter_map(|occurrence| {
11727 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11728 Some(DeclarationGuardSite {
11729 byte: occurrence.range.start_byte,
11730 guards,
11731 completed_family_before_reference: {
11732 root.descendant_for_byte_range(
11733 occurrence.range.start_byte,
11734 occurrence.range.end_byte,
11735 )
11736 .is_some_and(|node| {
11737 declaration_follows_completed_family_before_reference(
11738 node,
11739 prepared.source(),
11740 reference_byte,
11741 )
11742 })
11743 },
11744 })
11745 })
11746 .collect()
11747}
11748
11749fn declaration_follows_completed_family_before_reference(
11768 node: Node<'_>,
11769 source: &str,
11770 reference_byte: usize,
11771) -> bool {
11772 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11773}
11774
11775pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11778 let mut cache = PreprocessorContextCache::default();
11779 callable_guard_completion_byte_with_cache(node, source, &mut cache)
11780}
11781
11782pub(crate) fn callable_guard_completion_byte_with_cache(
11783 node: Node<'_>,
11784 source: &str,
11785 cache: &mut PreprocessorContextCache,
11786) -> Option<usize> {
11787 let mut completion_byte = 0;
11788 let mut ancestor = node.parent();
11789 while let Some(conditional) = ancestor {
11790 if matches!(
11791 conditional.kind(),
11792 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11793 ) && !is_file_covering_include_guard(conditional, source)
11794 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11795 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11796 {
11797 let family = preprocessor_conditional_family_root(conditional);
11798 if !preprocessor_conditional_family_has_terminal_else(family, cache) {
11799 return None;
11800 }
11801 completion_byte = completion_byte.max(family.end_byte());
11802 }
11803 ancestor = conditional.parent();
11804 }
11805 Some(completion_byte)
11806}
11807
11808fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11809 analyzer
11810 .ranges(candidate)
11811 .into_iter()
11812 .map(|range| range.start_byte)
11813 .min()
11814}
11815
11816fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11822 let (first, rest) = contexts.split_first()?;
11823 Some(
11824 first
11825 .defined_macros
11826 .iter()
11827 .filter(|name| {
11828 rest.iter()
11829 .all(|context| context.defined_macros.contains(*name))
11830 })
11831 .cloned()
11832 .collect(),
11833 )
11834}
11835
11836pub fn guard_requirements_hold_at_reference(
11837 required: &HashSet<PreprocessorGuard>,
11838 reference: Option<&HashSet<PreprocessorGuard>>,
11839) -> bool {
11840 reference.is_some_and(|active| {
11841 required
11842 .iter()
11843 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11844 })
11845}
11846
11847fn preprocessor_guard_holds_at_reference(
11848 required: &PreprocessorGuard,
11849 active: &HashSet<PreprocessorGuard>,
11850) -> bool {
11851 if active.contains(required) {
11852 return true;
11853 }
11854 let active_expression =
11855 BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
11856 active_expression.implies(&required.as_boolean_expression())
11857}
11858
11859fn guards_compatible_at_reference(
11863 declaration: &HashSet<PreprocessorGuard>,
11864 reference: Option<&HashSet<PreprocessorGuard>>,
11865) -> bool {
11866 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11867}
11868
11869pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11870 let mut ancestor = Some(node);
11871 while let Some(current) = ancestor {
11872 if is_cpp_preprocessor_conditional(current)
11873 && preprocessor_conditional_contains_descendant(current, node)
11874 {
11875 return Some(preprocessor_conditional_family_root(current));
11876 }
11877 ancestor = current.parent();
11878 }
11879 None
11880}
11881
11882pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11883 node: Node<'tree>,
11884 cache: &mut PreprocessorContextCache,
11885) -> Option<Node<'tree>> {
11886 let mut ancestor = node.parent();
11887 while let Some(current) = ancestor {
11888 if is_cpp_preprocessor_conditional(current)
11889 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11890 {
11891 let family = preprocessor_conditional_family_root(current);
11892 if preprocessor_conditional_family_has_terminal_else(family, cache) {
11893 return Some(family);
11894 }
11895 }
11896 ancestor = current.parent();
11897 }
11898 None
11899}
11900
11901fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11902 while let Some(parent) = conditional.parent() {
11903 let is_alternative = parent
11904 .child_by_field_name("alternative")
11905 .is_some_and(|alternative| {
11906 alternative.start_byte() == conditional.start_byte()
11907 && alternative.end_byte() == conditional.end_byte()
11908 });
11909 if !is_alternative {
11910 break;
11911 }
11912 conditional = parent;
11913 }
11914 conditional
11915}
11916
11917fn preprocessor_conditional_family_has_terminal_else(
11918 mut conditional: Node<'_>,
11919 cache: &mut PreprocessorContextCache,
11920) -> bool {
11921 loop {
11922 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11923 return cache
11924 .displaced_branch(conditional)
11925 .is_some_and(|directive| directive.is_else);
11926 };
11927 match alternative.kind() {
11928 "preproc_else" => return true,
11929 "preproc_elif" => conditional = alternative,
11930 _ => return false,
11931 }
11932 }
11933}
11934
11935fn include_directive_guard_requirements(
11948 node: Node<'_>,
11949 source: &str,
11950 reference_is_c: bool,
11951 language_guard_is_stable: &dyn Fn() -> bool,
11952) -> Option<PreprocessorGuardEnvironment> {
11953 let mut required = preprocessor_guard_environment_by_family(node, source)?;
11954 let excluded_by_language = required
11955 .all
11956 .iter()
11957 .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11958 if excluded_by_language {
11959 return None;
11960 }
11961 let is_language_guard = |guard: &PreprocessorGuard| {
11962 matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11963 if name == "__cplusplus")
11964 };
11965 if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11968 for guards in [&mut required.all, &mut required.partial] {
11969 guards.retain(|guard| !is_language_guard(guard));
11970 }
11971 }
11972 Some(required)
11973}
11974
11975fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11976 match guard {
11977 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11978 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11979 _ => false,
11980 }
11981}
11982
11983pub fn preprocessor_guard_environment(
11984 node: Node<'_>,
11985 source: &str,
11986) -> Option<HashSet<PreprocessorGuard>> {
11987 let mut cache = PreprocessorContextCache::default();
11988 preprocessor_guard_environment_with_cache(node, source, &mut cache)
11989}
11990
11991pub(crate) fn preprocessor_guard_environment_with_cache(
11992 node: Node<'_>,
11993 source: &str,
11994 cache: &mut PreprocessorContextCache,
11995) -> Option<HashSet<PreprocessorGuard>> {
11996 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11997 .map(|environment| environment.all)
11998}
11999
12000#[derive(Clone)]
12011struct PreprocessorGuardEnvironment {
12012 all: HashSet<PreprocessorGuard>,
12013 partial: HashSet<PreprocessorGuard>,
12014}
12015
12016impl PreprocessorGuardEnvironment {
12017 fn is_subset(&self, other: &Self) -> bool {
12018 self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12019 }
12020
12021 fn merged(&self, other: &Self) -> Option<Self> {
12025 Some(Self {
12026 all: merge_preprocessor_guards(&self.all, &other.all)?,
12027 partial: self.partial.union(&other.partial).cloned().collect(),
12028 })
12029 }
12030}
12031
12032fn preprocessor_guard_environment_by_family(
12033 node: Node<'_>,
12034 source: &str,
12035) -> Option<PreprocessorGuardEnvironment> {
12036 let mut cache = PreprocessorContextCache::default();
12037 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12038}
12039
12040fn preprocessor_guard_environment_by_family_with_cache(
12041 node: Node<'_>,
12042 source: &str,
12043 cache: &mut PreprocessorContextCache,
12044) -> Option<PreprocessorGuardEnvironment> {
12045 let mut all = HashSet::default();
12046 let mut partial = HashSet::default();
12047 let mut ancestor = node.parent();
12048 while let Some(conditional) = ancestor {
12049 if matches!(
12050 conditional.kind(),
12051 "preproc_if" | "preproc_ifdef" | "preproc_elif"
12052 ) && !is_file_covering_include_guard(conditional, source)
12053 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12054 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12055 {
12056 let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12057 match guard {
12058 PreprocessorGuard::Constant(true) => {
12059 ancestor = conditional.parent();
12060 continue;
12061 }
12062 PreprocessorGuard::Constant(false) => return None,
12063 _ => {}
12064 }
12065 if all.contains(&guard.negated()) {
12066 return None;
12067 }
12068 if !preprocessor_conditional_family_has_terminal_else(
12069 preprocessor_conditional_family_root(conditional),
12070 cache,
12071 ) {
12072 partial.insert(guard.clone());
12073 }
12074 all.insert(guard);
12075 }
12076 ancestor = conditional.parent();
12077 }
12078 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12079 match guard {
12080 PreprocessorGuard::Constant(true) => {}
12081 PreprocessorGuard::Constant(false) => return None,
12082 _ => {
12083 if all.contains(&guard.negated()) {
12084 return None;
12085 }
12086 partial.insert(guard.clone());
12090 all.insert(guard);
12091 }
12092 }
12093 }
12094 Some(PreprocessorGuardEnvironment { all, partial })
12095}
12096
12097pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12098 node: Node<'_>,
12099 source: &str,
12100 cache: &mut PreprocessorContextCache,
12101) -> Option<HashSet<PreprocessorGuard>> {
12102 let mut guards = HashSet::default();
12103 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12104 match guard {
12105 PreprocessorGuard::Constant(true) => {}
12106 PreprocessorGuard::Constant(false) => return None,
12107 _ => {
12108 if guards.contains(&guard.negated()) {
12109 return None;
12110 }
12111 guards.insert(guard);
12112 }
12113 }
12114 }
12115 Some(guards)
12116}
12117
12118fn callable_preprocessor_ancestor_guards(
12119 node: Node<'_>,
12120 source: &str,
12121) -> Option<Vec<PreprocessorGuard>> {
12122 let mut cache = PreprocessorContextCache::default();
12123 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12124}
12125
12126fn callable_preprocessor_ancestor_guards_with_cache(
12127 node: Node<'_>,
12128 source: &str,
12129 cache: &mut PreprocessorContextCache,
12130) -> Option<Vec<PreprocessorGuard>> {
12131 let mut guards = Vec::new();
12132 let mut ancestor = node.parent();
12133 while let Some(conditional) = ancestor {
12134 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12135 && !is_file_covering_include_guard(conditional, source)
12136 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12137 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12138 {
12139 guards.push(preprocessor_guard_for_descendant(
12140 conditional,
12141 node,
12142 source,
12143 cache,
12144 )?);
12145 }
12146 ancestor = conditional.parent();
12147 }
12148 Some(guards)
12149}
12150
12151fn fragmented_statement_preprocessor_guard(
12152 descendant: Node<'_>,
12153 source: &str,
12154) -> Option<PreprocessorGuard> {
12155 let mut ancestor = descendant.parent();
12161 while let Some(statement) = ancestor {
12162 if statement.kind() == "if_statement"
12163 && let (Some(consequence), Some(alternative)) = (
12164 statement.child_by_field_name("consequence"),
12165 statement.child_by_field_name("alternative"),
12166 )
12167 && alternative.start_byte() <= descendant.start_byte()
12168 && descendant.end_byte() <= alternative.end_byte()
12169 {
12170 let mut cursor = consequence.walk();
12171 let openers = consequence
12172 .named_children(&mut cursor)
12173 .filter(|child| {
12174 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12175 && child
12176 .child(child.child_count().saturating_sub(1))
12177 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12178 })
12179 .collect::<Vec<_>>();
12180 if openers.len() != 1 {
12181 ancestor = statement.parent();
12182 continue;
12183 }
12184
12185 let mut terminators = Vec::new();
12186 let mut stack = vec![alternative];
12187 while let Some(node) = stack.pop() {
12188 if node.kind() == "preproc_call"
12189 && node.start_byte() >= descendant.end_byte()
12190 && node
12191 .child_by_field_name("directive")
12192 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12193 {
12194 terminators.push(node);
12195 continue;
12196 }
12197 push_named_children_reversed(node, &mut stack);
12198 }
12199 if terminators.len() == 1 {
12200 return simple_preprocessor_guard(openers[0], source);
12201 }
12202 }
12203 ancestor = statement.parent();
12204 }
12205 None
12206}
12207
12208pub(crate) fn preprocessor_guard_for_descendant(
12209 conditional: Node<'_>,
12210 descendant: Node<'_>,
12211 source: &str,
12212 cache: &mut PreprocessorContextCache,
12213) -> Option<PreprocessorGuard> {
12214 let guard = simple_preprocessor_guard(conditional, source)?;
12215 match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12216 PreprocessorBranch::Consequence => Some(guard),
12217 PreprocessorBranch::Structured(alternative)
12221 if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12222 {
12223 Some(guard.negated())
12224 }
12225 PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12226 PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12227 }
12228}
12229
12230enum PreprocessorBranch<'tree> {
12232 Consequence,
12233 Structured(Node<'tree>),
12235 DisplacedElse,
12238 Unreadable,
12240}
12241
12242fn preprocessor_branch_of_descendant<'tree>(
12243 conditional: Node<'tree>,
12244 descendant: Node<'_>,
12245 cache: &mut PreprocessorContextCache,
12246) -> PreprocessorBranch<'tree> {
12247 if let Some(alternative) =
12248 conditional
12249 .child_by_field_name("alternative")
12250 .filter(|alternative| {
12251 alternative.start_byte() <= descendant.start_byte()
12252 && descendant.end_byte() <= alternative.end_byte()
12253 })
12254 {
12255 return PreprocessorBranch::Structured(alternative);
12256 }
12257 match cache.displaced_branch(conditional) {
12258 Some(directive) if directive.end_byte <= descendant.start_byte() => {
12259 if directive.is_else {
12260 PreprocessorBranch::DisplacedElse
12261 } else {
12262 PreprocessorBranch::Unreadable
12263 }
12264 }
12265 _ => PreprocessorBranch::Consequence,
12266 }
12267}
12268
12269pub(crate) fn preprocessor_conditional_contains_descendant(
12270 conditional: Node<'_>,
12271 descendant: Node<'_>,
12272) -> bool {
12273 cpp_displaced_preprocessor_boundary(conditional)
12274 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12275}
12276
12277fn preprocessor_conditional_contains_descendant_with_cache(
12278 conditional: Node<'_>,
12279 descendant: Node<'_>,
12280 cache: &mut PreprocessorContextCache,
12281) -> bool {
12282 cache
12283 .displaced_boundary_end(conditional)
12284 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12285}
12286
12287pub fn merge_preprocessor_guards(
12288 left: &HashSet<PreprocessorGuard>,
12289 right: &HashSet<PreprocessorGuard>,
12290) -> Option<HashSet<PreprocessorGuard>> {
12291 let mut merged = left.clone();
12292 for guard in right {
12293 let boolean_negation = guard.as_boolean_expression().negated();
12294 if merged.contains(&guard.negated())
12295 || merged
12296 .iter()
12297 .map(PreprocessorGuard::as_boolean_expression)
12298 .any(|existing| existing == boolean_negation)
12299 {
12300 return None;
12301 }
12302 merged.insert(guard.clone());
12303 }
12304 Some(merged)
12305}
12306
12307fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12308 if conditional.kind() == "preproc_ifdef" {
12309 let name = conditional.child_by_field_name("name")?;
12310 let name = node_text(name, source).to_string();
12311 return match conditional.child(0)?.kind() {
12312 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12313 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12314 _ => None,
12315 };
12316 }
12317 let condition = conditional.child_by_field_name("condition")?;
12318 simple_preprocessor_expression_guard(condition, source).or_else(|| {
12319 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12320 node_text(condition, source),
12321 )))
12322 })
12323}
12324
12325fn simple_preprocessor_expression_guard(
12326 expression: Node<'_>,
12327 source: &str,
12328) -> Option<PreprocessorGuard> {
12329 match expression.kind() {
12330 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12331 node_text(expression, source).to_string(),
12332 ))),
12333 "number_literal" => match node_text(expression, source).trim() {
12334 "0" => Some(PreprocessorGuard::Constant(false)),
12335 "1" => Some(PreprocessorGuard::Constant(true)),
12336 _ => None,
12337 },
12338 "preproc_defined" => {
12339 let identifier = (0..expression.named_child_count())
12340 .filter_map(|index| expression.named_child(index))
12341 .find(|child| child.kind() == "identifier")?;
12342 Some(PreprocessorGuard::Defined(
12343 node_text(identifier, source).to_string(),
12344 ))
12345 }
12346 "unary_expression"
12347 if expression
12348 .child_by_field_name("operator")
12349 .is_some_and(|operator| operator.kind() == "!") =>
12350 {
12351 simple_preprocessor_expression_guard(
12352 expression.child_by_field_name("argument")?,
12353 source,
12354 )
12355 .map(|guard| guard.negated())
12356 }
12357 "parenthesized_expression" => (0..expression.named_child_count())
12358 .filter_map(|index| expression.named_child(index))
12359 .next()
12360 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12361 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12362 expression, source,
12363 ))),
12364 _ => None,
12365 }
12366}
12367
12368fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12369 match expression.kind() {
12370 "number_literal" => match node_text(expression, source).trim() {
12371 "0" => BooleanGuardExpression::Constant(false),
12372 "1" => BooleanGuardExpression::Constant(true),
12373 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12374 expression, source,
12375 ))),
12376 },
12377 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12378 "preproc_defined" => {
12379 let identifier = (0..expression.named_child_count())
12380 .filter_map(|index| expression.named_child(index))
12381 .find(|child| child.kind() == "identifier");
12382 identifier.map_or_else(
12383 || {
12384 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12385 expression, source,
12386 )))
12387 },
12388 |identifier| {
12389 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12390 },
12391 )
12392 }
12393 "unary_expression"
12394 if expression
12395 .child_by_field_name("operator")
12396 .is_some_and(|operator| operator.kind() == "!") =>
12397 {
12398 expression.child_by_field_name("argument").map_or_else(
12399 || {
12400 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12401 expression, source,
12402 )))
12403 },
12404 |argument| boolean_preprocessor_expression(argument, source).negated(),
12405 )
12406 }
12407 "parenthesized_expression" => (0..expression.named_child_count())
12408 .filter_map(|index| expression.named_child(index))
12409 .next()
12410 .map_or_else(
12411 || {
12412 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12413 expression, source,
12414 )))
12415 },
12416 |child| boolean_preprocessor_expression(child, source),
12417 ),
12418 "binary_expression" => {
12419 let operands = || {
12420 Some((
12421 boolean_preprocessor_expression(
12422 expression.child_by_field_name("left")?,
12423 source,
12424 ),
12425 boolean_preprocessor_expression(
12426 expression.child_by_field_name("right")?,
12427 source,
12428 ),
12429 ))
12430 };
12431 match expression
12432 .child_by_field_name("operator")
12433 .map(|operator| operator.kind())
12434 {
12435 Some("&&") => operands().map_or_else(
12436 || {
12437 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12438 expression, source,
12439 )))
12440 },
12441 |(left, right)| BooleanGuardExpression::all([left, right]),
12442 ),
12443 Some("||") => operands().map_or_else(
12444 || {
12445 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12446 expression, source,
12447 )))
12448 },
12449 |(left, right)| BooleanGuardExpression::any([left, right]),
12450 ),
12451 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12452 expression, source,
12453 ))),
12454 }
12455 }
12456 _ => {
12457 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12458 }
12459 }
12460}
12461
12462fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12463 if targets.len() == 1 {
12464 targets.pop()
12465 } else {
12466 None
12467 }
12468}
12469
12470fn callable_declaration_nodes<'tree>(
12471 analyzer: &CppGraphSource<'_>,
12472 prepared: &'tree PreparedSyntaxTree,
12473 candidate: &CodeUnit,
12474) -> Vec<Node<'tree>> {
12475 let root = prepared.tree().root_node();
12476 analyzer
12477 .ranges(candidate)
12478 .into_iter()
12479 .filter_map(|range| {
12480 let mut declaration =
12481 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12482 while !matches!(
12495 declaration.kind(),
12496 "declaration" | "field_declaration" | "function_definition"
12497 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12498 declaration,
12499 prepared.source(),
12500 ) {
12501 let Some(parent) = declaration.parent() else {
12502 break;
12503 };
12504 declaration = parent;
12505 }
12506 Some(declaration)
12507 })
12508 .collect()
12509}
12510
12511fn real_function_definition_ancestor<'tree>(
12512 node: Node<'tree>,
12513 source: &str,
12514) -> Option<Node<'tree>> {
12515 let mut ancestor = node.parent();
12516 while let Some(node) = ancestor {
12517 if node.kind() == "function_definition"
12518 && !is_recovered_declaration_scope_container(node, source)
12519 {
12520 return Some(node);
12521 }
12522 ancestor = node.parent();
12523 }
12524 None
12525}
12526
12527fn callable_declaration_activation_in_file(
12528 analyzer: &CppGraphSource<'_>,
12529 candidate: &CodeUnit,
12530 reference: &CallableReferenceContext<'_>,
12531) -> Option<usize> {
12532 analyzer
12533 .declaration_source_occurrences(candidate)?
12534 .into_iter()
12535 .filter_map(|occurrence| {
12536 let activation = occurrence.fact.callable_activation?;
12537 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12538 callable_guard_requirements_are_visible(
12539 &guards,
12540 occurrence.fact.callable_guard_completion_byte,
12541 reference,
12542 )
12543 .then_some(activation)
12544 })
12545 .min()
12546}
12547
12548fn callable_guard_requirements_are_visible(
12549 required: &HashSet<PreprocessorGuard>,
12550 completion_byte: Option<usize>,
12551 reference: &CallableReferenceContext<'_>,
12552) -> bool {
12553 if required.iter().all(|guard| match guard {
12554 PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12555 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12556 guard => reference
12557 .guards()
12558 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12559 }) {
12560 return true;
12561 }
12562 if required.iter().any(|guard| match guard {
12563 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12564 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12565 _ => false,
12566 }) {
12567 return false;
12568 }
12569 let undecided: HashSet<_> = required
12570 .iter()
12571 .filter(|guard| {
12572 !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12573 if name == "__cplusplus")
12574 })
12575 .cloned()
12576 .collect();
12577 reference.position.as_ref().is_some_and(|position| {
12578 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12579 && guards_compatible_at_reference(&undecided, reference.guards())
12580 })
12581}
12582
12583struct CallableReferenceContext<'a> {
12589 file: &'a ProjectFile,
12590 position: Option<CallableReferencePosition<'a>>,
12591}
12592
12593struct CallableReferencePosition<'a> {
12597 prepared: &'a PreparedSyntaxTree,
12598 byte: usize,
12599 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12600}
12601
12602impl CallableReferenceContext<'_> {
12603 fn is_c(&self) -> bool {
12604 self.file
12605 .rel_path()
12606 .extension()
12607 .and_then(|extension| extension.to_str())
12608 == Some("c")
12609 }
12610
12611 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12612 let position = self.position.as_ref()?;
12613 position
12614 .guards
12615 .get_or_init(|| {
12616 position
12617 .prepared
12618 .tree()
12619 .root_node()
12620 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12621 .and_then(|node| {
12622 preprocessor_guard_environment(node, position.prepared.source())
12623 })
12624 })
12625 .as_ref()
12626 }
12627}
12628
12629fn callable_declaration_guard_requirements(
12641 node: Node<'_>,
12642 source: &str,
12643 reference: &CallableReferenceContext<'_>,
12644) -> Option<HashSet<PreprocessorGuard>> {
12645 let reference_is_c = reference.is_c();
12646 let mut required = HashSet::default();
12647 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12648 match guard {
12649 PreprocessorGuard::Constant(true) => {}
12650 PreprocessorGuard::Constant(false) => return None,
12651 guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12652 return None;
12653 }
12654 guard => {
12655 required.insert(guard);
12656 }
12657 }
12658 }
12659 Some(required)
12660}
12661
12662fn callable_preprocessor_context_is_visible_for_reference(
12673 node: Node<'_>,
12674 source: &str,
12675 reference: &CallableReferenceContext<'_>,
12676) -> bool {
12677 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12678 return false;
12679 };
12680 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12681 return true;
12682 }
12683 reference.position.as_ref().is_some_and(|position| {
12684 guards_compatible_at_reference(&required, reference.guards())
12685 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12686 })
12687}
12688
12689fn flattened_macro_namespace_declaration_matches(
12690 analyzer: &CppGraphSource<'_>,
12691 _cpp: &dyn CppSource,
12692 reference_file: &ProjectFile,
12693 visible_declaration: &CodeUnit,
12694 qualified_candidate: &CodeUnit,
12695 reference_byte: usize,
12696) -> bool {
12697 if visible_declaration.kind() != qualified_candidate.kind()
12703 || visible_declaration.identifier() != qualified_candidate.identifier()
12704 || visible_declaration.signature() != qualified_candidate.signature()
12705 || !visible_declaration.package_name().is_empty()
12706 || qualified_candidate.package_name().is_empty()
12707 {
12708 return false;
12709 }
12710
12711 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12712 return false;
12713 };
12714 occurrences.into_iter().any(|occurrence| {
12715 occurrence
12716 .displaced_namespace_closing_brace
12717 .is_some_and(|closing_brace| {
12718 visible_declaration.source() != reference_file || closing_brace < reference_byte
12719 })
12720 })
12721}
12722
12723pub(crate) fn flattened_macro_namespace_components(
12724 declaration: Node<'_>,
12725 source: &str,
12726) -> Option<Vec<String>> {
12727 flattened_macro_function_namespace_components(declaration, source)
12728 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12729}
12730
12731fn flattened_macro_function_namespace_components(
12732 declaration: Node<'_>,
12733 source: &str,
12734) -> Option<Vec<String>> {
12735 let body = declaration
12736 .parent()
12737 .filter(|parent| parent.kind() == "compound_statement")?;
12738 let function = body.parent()?;
12739 if function.child_by_field_name("body") != Some(body) {
12740 return None;
12741 }
12742 let namespace_name = recovered_macro_namespace_name(function, source)?;
12743 let mut components = enclosing_namespace_components(declaration, source)?;
12744 components.push(namespace_name);
12745 Some(components)
12746}
12747
12748fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12762 if function.kind() != "function_definition" {
12763 return None;
12764 }
12765 let body = function
12766 .child_by_field_name("body")
12767 .filter(|body| body.kind() == "compound_statement")?;
12768 let mut cursor = function.walk();
12769 let prefix = function
12770 .named_children(&mut cursor)
12771 .take_while(|child| child.start_byte() < body.start_byte())
12772 .filter(|child| child.kind() != "comment")
12773 .collect::<Vec<_>>();
12774 let begin_index = prefix.iter().rposition(|child| {
12775 flattened_macro_sentinel_name(*child, source)
12776 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12777 })?;
12778 let mut identifiers = Vec::new();
12779 let mut stack = prefix[begin_index + 1..]
12780 .iter()
12781 .rev()
12782 .copied()
12783 .collect::<Vec<_>>();
12784 while let Some(current) = stack.pop() {
12785 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12786 identifiers.push(identifier);
12787 continue;
12788 }
12789 let mut cursor = current.walk();
12790 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12791 stack.extend(children.into_iter().rev());
12792 }
12793 let [keyword, namespace_name] = identifiers.as_slice() else {
12794 return None;
12795 };
12796 if keyword != "namespace" || namespace_name.is_empty() {
12799 return None;
12800 }
12801 let mut next = function.next_named_sibling();
12802 let next = loop {
12803 let candidate = next?;
12804 next = candidate.next_named_sibling();
12805 if candidate.kind() != "comment" {
12806 break candidate;
12807 }
12808 };
12809 flattened_macro_sentinel_name(next, source)
12810 .is_some_and(|name| is_namespace_end_sentinel(&name))
12811 .then(|| namespace_name.clone())
12812}
12813
12814pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12819 crate::declarations::is_recovered_exported_class_container(node, source)
12820 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12821 node, source,
12822 )
12823 || recovered_macro_namespace_name(node, source).is_some()
12824}
12825
12826fn flattened_macro_error_namespace_components(
12827 declaration: Node<'_>,
12828 source: &str,
12829) -> Option<Vec<String>> {
12830 let parent = declaration
12831 .parent()
12832 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12833 let mut cursor = parent.walk();
12834 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12835 let declaration_index = siblings
12836 .iter()
12837 .position(|candidate| same_node(*candidate, declaration))?;
12838 let begin_index = (0..declaration_index).rev().find(|index| {
12839 flattened_macro_sentinel_name(siblings[*index], source)
12840 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12841 })?;
12842
12843 let significant = siblings[begin_index + 1..declaration_index]
12844 .iter()
12845 .copied()
12846 .filter(|node| node.kind() != "comment")
12847 .collect::<Vec<_>>();
12848 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12849 return None;
12850 };
12851 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12852 return None;
12853 }
12854 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12855 if significant[2..].iter().any(|node| {
12856 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12857 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12858 })
12859 }) {
12860 return None;
12861 }
12862
12863 let mut saw_namespace_close = false;
12864 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12865 if sibling.kind() == "comment" {
12866 continue;
12867 }
12868 if !saw_namespace_close {
12869 if direct_unmatched_closing_brace(sibling) {
12870 saw_namespace_close = true;
12871 continue;
12872 }
12873 if flattened_macro_sentinel_name(sibling, source).is_some() {
12874 return None;
12875 }
12876 continue;
12877 }
12878 if !flattened_macro_sentinel_name(sibling, source)
12879 .is_some_and(|name| is_namespace_end_sentinel(&name))
12880 {
12881 return None;
12882 }
12883 let mut components = enclosing_namespace_components(declaration, source)?;
12884 components.push(namespace_name);
12885 return Some(components);
12886 }
12887 None
12888}
12889
12890fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12891 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12899 node.named_child(0)?
12900 } else {
12901 node
12902 };
12903 let candidates = [
12904 direct_cpp_identifier_name(node, source),
12905 node.child_by_field_name("type")
12906 .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
12907 macro_modifier_identifier(node)
12908 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
12909 {
12910 let mut cursor = node.walk();
12911 node.named_children(&mut cursor)
12912 .find(|child| child.kind() != "comment")
12913 .and_then(macro_modifier_identifier)
12914 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
12915 },
12916 ];
12917 candidates.into_iter().flatten().find(|candidate| {
12918 cpp_export_macro_token(candidate)
12919 && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
12920 })
12921}
12922
12923#[derive(Clone, Copy, PartialEq, Eq)]
12926enum NamespaceSentinel {
12927 Begin,
12928 End,
12929}
12930
12931fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12932 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12933 Some(NamespaceSentinel::Begin)
12934 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12935 Some(NamespaceSentinel::End)
12936 } else {
12937 None
12938 }
12939}
12940
12941fn is_namespace_begin_sentinel(name: &str) -> bool {
12942 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12943}
12944
12945fn is_namespace_end_sentinel(name: &str) -> bool {
12946 namespace_sentinel(name) == Some(NamespaceSentinel::End)
12947}
12948
12949fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12950 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12951 return None;
12952 }
12953 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12954 (!cpp_export_macro_token(&name)).then_some(name)
12955}
12956
12957fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12958 if !matches!(
12959 node.kind(),
12960 "identifier" | "namespace_identifier" | "type_identifier"
12961 ) {
12962 return None;
12963 }
12964 let name = normalize_cpp_whitespace(node_text(node, source));
12965 (!name.is_empty()).then_some(name)
12966}
12967
12968fn guard_requirement_sets_match(
12969 left: &[(usize, HashSet<PreprocessorGuard>)],
12970 right: &[(usize, HashSet<PreprocessorGuard>)],
12971) -> bool {
12972 left.len() == right.len()
12973 && left.iter().all(|(_, left_guards)| {
12974 right
12975 .iter()
12976 .any(|(_, right_guards)| left_guards == right_guards)
12977 })
12978 && right.iter().all(|(_, right_guards)| {
12979 left.iter()
12980 .any(|(_, left_guards)| right_guards == left_guards)
12981 })
12982}
12983
12984pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12985 node.kind() == "ERROR"
12986 && (0..node.child_count())
12987 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12988}
12989
12990pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
12994 if callable_preprocessor_context_is_visible(node, source) {
12995 Some(HashSet::default())
12996 } else {
12997 preprocessor_guard_environment(node, source)
12998 }
12999}
13000
13001pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13002 let mut ancestor = node.parent();
13003 while let Some(parent) = ancestor {
13004 if is_cpp_preprocessor_conditional(parent)
13005 && !is_file_covering_include_guard(parent, source)
13006 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13007 {
13008 return false;
13009 }
13010 ancestor = parent.parent();
13011 }
13012 true
13013}
13014
13015fn is_split_cpp_language_linkage_wrapper(
13016 conditional: Node<'_>,
13017 descendant: Node<'_>,
13018 source: &str,
13019) -> bool {
13020 if conditional.child_by_field_name("alternative").is_some()
13021 || !matches!(
13022 simple_preprocessor_guard(conditional, source),
13023 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13024 )
13025 {
13026 return false;
13027 }
13028 let mut current = descendant.parent();
13029 let linkage = loop {
13030 let Some(node) = current else {
13031 return false;
13032 };
13033 if node == conditional {
13034 return false;
13035 }
13036 if node.kind() == "linkage_specification" {
13037 break node;
13038 }
13039 current = node.parent();
13040 };
13041 if linkage
13042 .child_by_field_name("value")
13043 .is_none_or(|value| node_text(value, source) != "\"C\"")
13044 {
13045 return false;
13046 }
13047 let Some(body) = linkage.child_by_field_name("body") else {
13048 return false;
13049 };
13050 let mut cursor = body.walk();
13055 let closes_opening_branch = body
13056 .named_children(&mut cursor)
13057 .take_while(|child| child.end_byte() <= descendant.start_byte())
13058 .any(|child| {
13059 child.kind() == "preproc_call"
13060 && child
13061 .child_by_field_name("directive")
13062 .is_some_and(|directive| node_text(directive, source) == "#endif")
13063 });
13064 let mut cursor = body.walk();
13065 let reopens_for_closing_brace = body
13066 .named_children(&mut cursor)
13067 .skip_while(|child| child.start_byte() < descendant.end_byte())
13068 .any(|child| {
13069 matches!(
13070 simple_preprocessor_guard(child, source),
13071 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13072 ) && {
13073 let mut tokens = child.walk();
13074 child
13075 .children(&mut tokens)
13076 .any(|token| token.kind() == "#endif" && token.is_missing())
13077 }
13078 });
13079 closes_opening_branch && reopens_for_closing_brace
13080}
13081
13082pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13086 node.child_by_field_name("arguments")
13087 .or_else(|| node.child_by_field_name("parameters"))
13088 .or_else(|| node.child_by_field_name("value"))
13089 .or_else(|| first_named_child_of_kind(node, "argument_list"))
13090 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13091}
13092
13093pub fn call_arity(node: Node<'_>) -> usize {
13094 call_arguments_node(node)
13095 .map(|args| argument_children(args).count())
13096 .unwrap_or(0)
13097}
13098
13099pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13100 let recovered_block_arguments = recovered_block_literal_arguments(node);
13101 (0..node.child_count())
13102 .filter_map(move |index| node.child(index))
13103 .filter(|child| child.is_named() && !child.is_extra())
13104 .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13109 .flat_map(move |child| {
13110 if let Some((raw, left, right)) = recovered_block_arguments
13111 && child == raw
13112 {
13113 [Some(left), Some(right)]
13114 } else {
13115 [Some(child), None]
13116 }
13117 })
13118 .flatten()
13119}
13120
13121fn block_literal_open_error(node: Node<'_>) -> bool {
13125 if node.kind() != "ERROR" || node.is_missing() {
13126 return false;
13127 }
13128 let mut cursor = node.walk();
13129 let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13130 matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13131}
13132
13133pub fn recovered_c_new_expression_arguments(
13142 node: Node<'_>,
13143 uses_c_semantics: bool,
13144) -> Option<[Node<'_>; 2]> {
13145 if !uses_c_semantics || node.kind() != "new_expression" {
13146 return None;
13147 }
13148 let parent = node.parent()?;
13149 if parent.kind() != "argument_list" {
13150 return None;
13151 }
13152 let keyword = node.child(0)?;
13153 let error = node.child(1)?;
13154 let trailing = node.child(2)?;
13155 if node.child(3).is_some()
13156 || keyword.kind() != "new"
13157 || keyword.is_named()
13158 || keyword.child_count() != 0
13159 || error.kind() != "ERROR"
13160 || !error.is_extra()
13161 || error.child_count() != 1
13162 || error.child(0).is_none_or(|comma| comma.kind() != ",")
13163 || node.child_by_field_name("type") != Some(trailing)
13164 || trailing.kind() != "type_identifier"
13165 {
13166 return None;
13167 }
13168 Some([keyword, trailing])
13169}
13170
13171pub fn recovered_c_new_expression_argument_at(
13174 mut node: Node<'_>,
13175 start_byte: usize,
13176 end_byte: usize,
13177 uses_c_semantics: bool,
13178) -> Option<Node<'_>> {
13179 if !uses_c_semantics {
13185 return None;
13186 }
13187 loop {
13188 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13189 return arguments.into_iter().find(|argument| {
13190 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13191 });
13192 }
13193 node = node.parent()?;
13194 }
13195}
13196
13197fn recovered_c_keyword_argument_count(
13198 file: &ProjectFile,
13199 call: Node<'_>,
13200 arguments: Node<'_>,
13201 source: &str,
13202) -> usize {
13203 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13209 return 0;
13210 }
13211 let mut ancestor = Some(call);
13212 let function = loop {
13213 let Some(current) = ancestor else {
13214 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13215 .unwrap_or(0);
13216 };
13217 if current.kind() == "function_definition" {
13218 break current;
13219 }
13220 ancestor = current.parent();
13221 };
13222 let Some(parameters) = function
13223 .child_by_field_name("declarator")
13224 .and_then(|declarator| declarator.child_by_field_name("parameters"))
13225 else {
13226 return 0;
13227 };
13228 let displaced_parameter_keywords = (0..parameters.child_count())
13229 .filter_map(|index| parameters.child(index))
13230 .filter(|error| error.kind() == "ERROR")
13231 .filter_map(|error| {
13232 let parameter = error.prev_named_sibling()?;
13233 if parameter.kind() != "parameter_declaration"
13234 || parameter.end_byte() != error.start_byte()
13235 || extract_variable_name(parameter, source).is_some()
13236 {
13237 return None;
13238 }
13239 let mut children = (0..error.child_count())
13240 .filter_map(|index| error.child(index))
13241 .filter(|child| !child.is_extra() && !child.is_missing());
13242 let keyword = children.next()?;
13243 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13244 .then_some(keyword)
13245 })
13246 .collect::<Vec<_>>();
13247 if displaced_parameter_keywords.is_empty() {
13248 return 0;
13249 }
13250
13251 (0..arguments.child_count())
13252 .filter_map(|index| arguments.child(index))
13253 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13254 .filter(|error| {
13255 let mut children = (0..error.child_count())
13256 .filter_map(|index| error.child(index))
13257 .filter(|child| !child.is_extra() && !child.is_missing());
13258 let Some(comma) = children.next() else {
13259 return false;
13260 };
13261 let Some(keyword) = children.next() else {
13262 return false;
13263 };
13264 children.next().is_none()
13265 && comma.kind() == ","
13266 && !keyword.is_named()
13267 && keyword.child_count() == 0
13268 && displaced_parameter_keywords
13269 .iter()
13270 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13271 })
13272 .count()
13273}
13274
13275fn recovered_c_keyword_argument_count_from_collapsed_function(
13281 call: Node<'_>,
13282 arguments: Node<'_>,
13283) -> Option<usize> {
13284 let argument_keywords = children_iter(arguments)
13285 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13286 .filter_map(|error| {
13287 let mut children =
13288 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13289 let comma = children.next()?;
13290 let keyword = children.next()?;
13291 (children.next().is_none()
13292 && comma.kind() == ","
13293 && !keyword.is_named()
13294 && keyword.child_count() == 0)
13295 .then_some(keyword)
13296 })
13297 .collect::<Vec<_>>();
13298 if argument_keywords.is_empty() {
13299 return Some(0);
13300 }
13301
13302 let mut ancestor = call.parent();
13307 let body = loop {
13308 let current = ancestor?;
13309 if matches!(current.kind(), "function_definition" | "lambda_expression") {
13310 return None;
13311 }
13312 if current.kind() == "compound_statement"
13313 && current
13314 .parent()
13315 .is_some_and(|parent| parent.kind() == "ERROR")
13316 {
13317 break current;
13318 }
13319 ancestor = current.parent();
13320 };
13321 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13322 let function_error = parameter_error
13323 .parent()
13324 .filter(|parent| parent.kind() == "ERROR")?;
13325 let function_children = children_iter(function_error).collect::<Vec<_>>();
13326 let parameter_error_index = function_children
13327 .iter()
13328 .position(|child| same_node(*child, parameter_error))?;
13329 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13332 .iter()
13333 .enumerate()
13334 .any(|(index, child)| {
13335 function_error.field_name_for_child(index as u32) == Some("type")
13336 && !child.is_extra()
13337 && !child.is_missing()
13338 && matches!(
13339 child.kind(),
13340 "primitive_type" | "sized_type_specifier" | "type_identifier"
13341 )
13342 });
13343 if parameter_error_index < 2
13344 || !has_return_type
13345 || function_children[parameter_error_index - 1].kind() != "("
13346 || function_children[parameter_error_index - 2].kind() != "identifier"
13347 {
13348 return Some(0);
13349 }
13350
13351 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13352 let body_index = parameter_children
13353 .iter()
13354 .position(|child| same_node(*child, body))?;
13355 let close_parenthesis_index = parameter_children[..body_index]
13356 .iter()
13357 .position(|child| child.kind() == ")")?;
13358 let parameter_keywords = parameter_children[..close_parenthesis_index]
13359 .iter()
13360 .enumerate()
13361 .filter(|(index, child)| {
13362 !child.is_named()
13363 && !child.is_extra()
13364 && !child.is_missing()
13365 && child.child_count() == 0
13366 && argument_keywords
13367 .iter()
13368 .any(|keyword| keyword.kind_id() == child.kind_id())
13369 && collapsed_c_parameter_name_position(¶meter_children, *index)
13370 })
13371 .map(|(_, child)| *child)
13372 .collect::<Vec<_>>();
13373 Some(
13374 argument_keywords
13375 .iter()
13376 .filter(|keyword| {
13377 parameter_keywords
13378 .iter()
13379 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13380 })
13381 .count(),
13382 )
13383}
13384
13385fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13390 let segment_start = children[..keyword_index]
13391 .iter()
13392 .rposition(|child| matches!(child.kind(), "(" | ","))
13393 .map_or(0, |index| index + 1);
13394 let mut saw_type = false;
13395 let mut saw_pointer = false;
13396 for child in &children[segment_start..keyword_index] {
13397 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13398 saw_pointer = true;
13399 continue;
13400 }
13401 if saw_pointer {
13402 return false;
13403 }
13404 if !child.is_named() || child.is_extra() || child.is_missing() {
13405 return false;
13406 }
13407 if child.kind() == "type_qualifier" {
13408 continue;
13409 }
13410 if matches!(
13411 child.kind(),
13412 "class_specifier"
13413 | "dependent_type"
13414 | "enum_specifier"
13415 | "identifier"
13416 | "primitive_type"
13417 | "qualified_identifier"
13418 | "scoped_identifier"
13419 | "struct_specifier"
13420 | "template_type"
13421 | "type_identifier"
13422 | "union_specifier"
13423 ) {
13424 saw_type = true;
13425 continue;
13426 }
13427 return false;
13428 }
13429 saw_type && saw_pointer
13430}
13431
13432fn recovered_block_literal_arguments<'tree>(
13433 arguments: Node<'tree>,
13434) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13435 if arguments.kind() != "argument_list" {
13436 return None;
13437 }
13438 let mut raw_arguments = (0..arguments.child_count())
13439 .filter_map(|index| arguments.child(index))
13440 .filter(|child| child.is_named() && !child.is_extra());
13441 let raw = raw_arguments.next()?;
13442 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13443 return None;
13444 }
13445
13446 let left = raw.child_by_field_name("left")?;
13447 if left.is_missing() || left.start_byte() == left.end_byte() {
13448 return None;
13449 }
13450 let right = raw.child_by_field_name("right")?;
13451 if right.kind() != "compound_literal_expression"
13452 || right.is_missing()
13453 || right
13454 .child_by_field_name("type")
13455 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13456 || right
13457 .child_by_field_name("value")
13458 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13459 {
13460 return None;
13461 }
13462 let has_intervening_error = (0..raw.child_count())
13463 .filter_map(|index| raw.child(index))
13464 .any(|child| {
13465 child.kind() == "ERROR"
13466 && !child.is_missing()
13467 && child.start_byte() >= left.end_byte()
13468 && child.end_byte() <= right.start_byte()
13469 });
13470 has_intervening_error.then_some((raw, left, right))
13471}
13472
13473pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13474 match node.kind() {
13475 "new_expression" => node
13476 .child_by_field_name("type")
13477 .or_else(|| node.named_child(0)),
13478 "compound_literal_expression" => node.child_by_field_name("type"),
13479 "call_expression" => node.child_by_field_name("function"),
13480 _ => None,
13481 }
13482}
13483
13484pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13490 if node.kind() != "cast_expression" {
13491 return None;
13492 }
13493 let descriptor = node.child_by_field_name("type")?;
13494 if descriptor.kind() == "type_descriptor" {
13495 descriptor.child_by_field_name("type")
13496 } else {
13497 Some(descriptor)
13498 }
13499}
13500
13501pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13516 debug_assert_eq!(field.kind(), "field_expression");
13517 let operator = field.child_by_field_name("operator")?;
13518 let mut cursor = field.walk();
13519 let receiver = field
13520 .named_children(&mut cursor)
13521 .filter(|child| child.end_byte() <= operator.start_byte())
13522 .last()?;
13523 if receiver.kind() != "ERROR" {
13524 return Some(receiver);
13525 }
13526 (receiver.named_child_count() == 1)
13527 .then(|| receiver.named_child(0))
13528 .flatten()
13529}
13530
13531pub fn field_initializer_constructs_target(
13532 node: Node<'_>,
13533 ctx: &ScanCtx<'_>,
13534 owner: &CodeUnit,
13535) -> bool {
13536 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13545 return qualified_base_initializer_constructs_target(node, ctx, owner);
13546 }
13547 let Some(name) = node
13548 .child_by_field_name("name")
13549 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13550 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13551 else {
13552 return false;
13553 };
13554 let field_name = node_text(name, ctx.source);
13555 ctx.visibility
13556 .visible_identifier_candidates(ctx.file, field_name)
13557 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13558 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13559}
13560
13561fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13571 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13572}
13573
13574fn qualified_base_initializer_constructs_target(
13575 node: Node<'_>,
13576 ctx: &ScanCtx<'_>,
13577 owner: &CodeUnit,
13578) -> bool {
13579 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13580 return false;
13581 };
13582 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13583 return false;
13584 };
13585 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13586 return false;
13587 };
13588 let resolves_target = |components: &[String]| {
13589 matches!(
13590 ctx.visibility.resolve_type_components_lexically_for_target(
13591 &ctx.analyzer,
13592 ctx.file,
13593 components,
13594 is_globally_qualified_cpp_name(qualified),
13595 &lexical_scope,
13596 owner,
13597 ),
13598 LexicalTypeResolution::Resolved { unit, .. }
13599 if same_visible_symbol(&unit, owner)
13600 )
13601 };
13602 if resolves_target(&components) {
13603 return true;
13604 }
13605
13606 components
13612 .last()
13613 .is_some_and(|terminal| terminal == owner.identifier())
13614 && resolves_target(&components[..components.len() - 1])
13615}
13616
13617fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13618 ctx.visibility
13619 .field_declared_type_fact(&ctx.analyzer, unit)
13620 .is_some_and(|fact| {
13621 ctx.visibility
13622 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13623 || ctx.visibility.resolves_to_type(
13624 &ctx.analyzer,
13625 ctx.file,
13626 &normalize_field_type_text(&fact.type_text),
13627 owner,
13628 )
13629 })
13630}
13631
13632pub fn field_declared_binding(
13633 analyzer: &CppGraphSource<'_>,
13634 visibility: &VisibilityIndex<'_>,
13635 visible_from: &ProjectFile,
13636 field: &CodeUnit,
13637) -> Option<CppScanBinding> {
13638 let (normalized, resolved, indirection) =
13639 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13640 let resolved = resolved
13641 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13642 Some(CppScanBinding::from_type_name(
13643 normalized,
13644 resolved,
13645 indirection,
13646 ))
13647}
13648
13649pub fn field_initializer_holds_subobject(
13656 analyzer: &CppGraphSource<'_>,
13657 visibility: &VisibilityIndex<'_>,
13658 field: &CodeUnit,
13659) -> bool {
13660 visibility
13661 .field_declared_type_fact(analyzer, field)
13662 .is_none_or(|fact| !fact.binds_indirectly)
13663}
13664
13665fn anonymous_aggregate_field_owner(
13672 analyzer: &CppGraphSource<'_>,
13673 visibility: &VisibilityIndex<'_>,
13674 visible_from: &ProjectFile,
13675 field: &CodeUnit,
13676) -> Option<CodeUnit> {
13677 let owner = type_owner_of(analyzer, field)?;
13678 if !owner.is_class() {
13679 return None;
13680 }
13681 let declaration = analyzer.get_source(field, false)?;
13682 let mut parser = Parser::new();
13683 parser
13684 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13685 .ok()?;
13686 let tree = parser.parse(&declaration, None)?;
13687 let mut stack = vec![tree.root_node()];
13688 while let Some(node) = stack.pop() {
13689 if matches!(node.kind(), "declaration" | "field_declaration")
13690 && let Some(type_node) = node
13691 .child_by_field_name("type")
13692 .or_else(|| first_type_child(node))
13693 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13694 && type_node.child_by_field_name("name").is_none()
13695 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13696 {
13697 let matches = visibility
13698 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13699 .into_iter()
13700 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13701 .collect::<Vec<_>>();
13702 return match matches.as_slice() {
13703 [child] => Some((*child).clone()),
13704 _ => None,
13705 };
13706 }
13707 let mut cursor = node.walk();
13708 stack.extend(node.named_children(&mut cursor));
13709 }
13710 None
13711}
13712
13713pub fn anonymous_aggregate_owner(
13720 analyzer: &CppGraphSource<'_>,
13721 file: &ProjectFile,
13722 node: Node<'_>,
13723) -> Option<CodeUnit> {
13724 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13725 || node.child_by_field_name("name").is_some()
13726 {
13727 return None;
13728 }
13729 let mut candidates = analyzer
13730 .declarations(file)
13731 .into_iter()
13732 .filter(|candidate| {
13733 candidate.is_class()
13734 && analyzer.ranges(candidate).into_iter().any(|range| {
13735 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13736 })
13737 })
13738 .collect::<Vec<_>>();
13739 candidates.sort_by_key(|candidate| candidate.fq_name());
13740 candidates.dedup();
13741 match candidates.as_slice() {
13742 [candidate] => Some(candidate.clone()),
13743 _ => None,
13744 }
13745}
13746
13747fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13749 let Some(first) = candidates.first() else {
13750 return Err(TypeCandidateFailure::Unresolvable);
13751 };
13752 if candidates
13753 .iter()
13754 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13755 {
13756 Ok((*first).clone())
13757 } else {
13758 Err(TypeCandidateFailure::Ambiguous)
13759 }
13760}
13761
13762fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13763 logical_type_candidate(candidates).ok()
13764}
13765
13766fn unique_type_candidate_preserving_alias(
13767 analyzer: &CppGraphSource<'_>,
13768 file: &ProjectFile,
13769 candidates: &[&CodeUnit],
13770) -> Option<CodeUnit> {
13771 let first = *candidates.first()?;
13772 if declared_type_alias(analyzer, first) {
13773 return candidates
13774 .iter()
13775 .all(|candidate| {
13776 declared_type_alias(analyzer, candidate)
13777 && candidate.kind() == first.kind()
13778 && candidate.fq_name() == first.fq_name()
13779 && candidate.source() == first.source()
13780 })
13781 .then(|| first.clone());
13782 }
13783 if analyzer.reference_uses_c_semantics(file)
13784 && first.is_class()
13785 && indexed_c_tag_kind(analyzer, first).is_some()
13786 {
13787 let mut full_source = None;
13788 let mut tag_kind = None;
13789 for candidate in candidates.iter().copied() {
13790 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13791 if tag_kind
13792 .replace(candidate_tag_kind)
13793 .is_some_and(|existing| existing != candidate_tag_kind)
13794 {
13795 return None;
13796 }
13797 if cpp_class_declaration_strength(analyzer, candidate)
13798 == CppClassDeclarationStrength::Full
13799 && full_source
13800 .replace(candidate.source())
13801 .is_some_and(|existing| existing != candidate.source())
13802 {
13803 return None;
13804 }
13805 }
13806 }
13807 candidates
13808 .iter()
13809 .all(|candidate| {
13810 !declared_type_alias(analyzer, candidate)
13811 && candidate.kind() == first.kind()
13812 && candidate.fq_name() == first.fq_name()
13813 })
13814 .then(|| first.clone())
13815}
13816
13817pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13818 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13819 analyzer.type_alias_provider(),
13820 unit,
13821 )
13822}
13823
13824pub fn field_declared_type_binding(
13825 analyzer: &CppGraphSource<'_>,
13826 visibility: &VisibilityIndex<'_>,
13827 visible_from: &ProjectFile,
13828 field: &CodeUnit,
13829) -> Option<(String, Option<CodeUnit>, i32)> {
13830 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13831 let normalized = normalize_field_type_text(&fact.type_text);
13832 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13833 analyzer,
13834 visible_from,
13835 field,
13836 &normalized,
13837 );
13838 let resolved = match (primary, fact.template_arguments.as_deref()) {
13839 (Some(primary), Some(arguments)) => visibility
13840 .resolve_template_arguments(visible_from, primary, arguments)
13841 .ok(),
13842 (resolved, None) => resolved,
13843 (None, Some(_)) => None,
13844 };
13845 Some((normalized, resolved, fact.indirection))
13846}
13847
13848fn decode_field_declared_type_fact(
13849 analyzer: &CppGraphSource<'_>,
13850 field: &CodeUnit,
13851) -> Option<DeclaredFieldTypeFact> {
13852 if let Some(facts) = analyzer.declaration_source_properties(field)
13853 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13854 && facts
13855 .iter()
13856 .all(|fact| fact.field_type.as_ref() == Some(first))
13857 {
13858 return Some(first.clone());
13859 }
13860 let Some(declaration) = analyzer.get_source(field, false) else {
13861 return decode_indexed_field_declared_type_fact(analyzer, field);
13862 };
13863 let mut parser = Parser::new();
13864 parser
13865 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13866 .ok()?;
13867 let contextual = parse_sentinel(
13871 "struct __bifrost_field_context { ",
13872 SentinelInput::region(&declaration),
13873 " };",
13874 );
13875 let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
13876 let mut stack = vec![contextual_tree.root_node()];
13877 while let Some(node) = stack.pop() {
13878 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13879 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13880 {
13881 return Some(DeclaredFieldTypeFact {
13882 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13883 indirection: recovered.pointer_depth(),
13884 binds_indirectly: recovered.pointer_depth() > 0,
13885 template_arguments: None,
13886 });
13887 }
13888 if let Some(recovered) =
13889 recovered_function_like_field_declarator(node, &contextual_declaration)
13890 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13891 {
13892 let type_node = node
13893 .child_by_field_name("type")
13894 .or_else(|| first_type_child(node))?;
13895 return Some(DeclaredFieldTypeFact {
13896 type_text: node_text(type_node, &contextual_declaration).to_string(),
13897 indirection: recovered.pointer_depth(),
13898 binds_indirectly: true,
13901 template_arguments: cpp_template_reference_arguments(
13902 type_node,
13903 &contextual_declaration,
13904 ),
13905 });
13906 }
13907 if let Some(fact) =
13908 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13909 {
13910 return Some(fact);
13911 }
13912 let mut cursor = node.walk();
13913 stack.extend(node.named_children(&mut cursor));
13914 }
13915 let tree = parser.parse(&declaration, None)?;
13916 let mut stack = vec![tree.root_node()];
13917 while let Some(node) = stack.pop() {
13918 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13919 {
13920 return Some(fact);
13921 }
13922 let mut cursor = node.walk();
13923 stack.extend(node.named_children(&mut cursor));
13924 }
13925 None
13926}
13927
13928fn decode_indexed_field_declared_type_fact(
13933 analyzer: &CppGraphSource<'_>,
13934 field: &CodeUnit,
13935) -> Option<DeclaredFieldTypeFact> {
13936 let cpp = analyzer.cpp?;
13937 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13938 let source = prepared.source();
13939 let root = prepared.tree().root_node();
13940 for range in analyzer.ranges(field) {
13941 let end = range.start_byte.saturating_add(1).min(source.len());
13942 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13943 while let Some(node) = current {
13944 if matches!(node.kind(), "declaration" | "field_declaration")
13945 && let Some(fact) =
13946 decode_declared_field_type_node(node, field.identifier(), source)
13947 {
13948 return Some(fact);
13949 }
13950 current = node.parent();
13951 }
13952 }
13953 None
13954}
13955
13956fn decode_declared_field_type_node(
13957 node: Node<'_>,
13958 field_name: &str,
13959 source: &str,
13960) -> Option<DeclaredFieldTypeFact> {
13961 if !matches!(node.kind(), "declaration" | "field_declaration") {
13962 return None;
13963 }
13964 let type_node = node
13965 .child_by_field_name("type")
13966 .or_else(|| first_type_child(node))?;
13967 cpp_declared_field_type_fact(node, type_node, field_name, source)
13968}
13969
13970pub(crate) fn cpp_declared_field_type_fact(
13971 node: Node<'_>,
13972 type_node: Node<'_>,
13973 field_name: &str,
13974 source: &str,
13975) -> Option<DeclaredFieldTypeFact> {
13976 let binding = declared_name_binding(node, type_node, field_name, source)?;
13977 let declared_type = if matches!(
13978 type_node.kind(),
13979 "class_specifier" | "struct_specifier" | "union_specifier"
13980 ) {
13981 type_node.child_by_field_name("name")
13982 } else {
13983 Some(type_node)
13984 };
13985 Some(DeclaredFieldTypeFact {
13986 type_text: declared_type.map_or_else(
13987 || field_name.to_string(),
13988 |declared_type| node_text(declared_type, source).to_string(),
13989 ),
13990 indirection: binding.pointer_depth,
13991 binds_indirectly: binding.indirect,
13992 template_arguments: declared_type
13993 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13994 })
13995}
13996
13997pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14003 let mut current = Some(declarator);
14004 while let Some(node) = current {
14005 match node.kind() {
14006 "function_declarator" | "abstract_function_declarator" => return true,
14007 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14008 current = node.named_child(0);
14009 }
14010 _ => current = node.child_by_field_name("declarator"),
14011 }
14012 }
14013 false
14014}
14015
14016fn decode_structured_alias_target(
14017 analyzer: &CppGraphSource<'_>,
14018 unit: &CodeUnit,
14019) -> Option<StructuredAliasTarget> {
14020 let facts = analyzer.declaration_source_properties(unit)?;
14021 let first = facts.first()?.alias_target.as_ref()?;
14022 facts
14023 .iter()
14024 .all(|fact| fact.alias_target.as_ref() == Some(first))
14025 .then(|| first.clone())
14026}
14027
14028pub(crate) fn structured_alias_type_target(
14029 mut type_node: Node<'_>,
14030 source: &str,
14031) -> Option<StructuredAliasTarget> {
14032 while type_node.kind() == "type_descriptor" {
14033 type_node = type_node.child_by_field_name("type")?;
14034 }
14035 if type_node.kind() == "primitive_type" {
14036 return Some(StructuredAliasTarget::Builtin);
14037 }
14038 if matches!(
14039 type_node.kind(),
14040 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14041 ) {
14042 type_node = type_node.child_by_field_name("name")?;
14043 }
14044 let global = type_node.child_by_field_name("scope").is_none()
14045 && type_node.child(0).is_some_and(|child| child.kind() == "::");
14046 let mut components = Vec::new();
14047 append_structured_type_components(type_node, source, &mut components)?;
14048 let arguments = cpp_template_reference_arguments(type_node, source);
14049 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14050 components,
14051 global,
14052 arguments,
14053 })
14054}
14055
14056fn append_structured_type_components(
14057 node: Node<'_>,
14058 source: &str,
14059 out: &mut Vec<String>,
14060) -> Option<()> {
14061 match node.kind() {
14062 "identifier" | "namespace_identifier" | "type_identifier" => {
14063 out.push(node_text(node, source).to_string());
14064 Some(())
14065 }
14066 "template_type" | "template_function" => {
14067 append_structured_type_components(node.child_by_field_name("name")?, source, out)
14068 }
14069 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14070 let (scope, name) = qualified_name_parts(node)?;
14071 if let Some(scope) = scope {
14072 append_structured_type_components(scope, source, out)?;
14073 }
14074 append_structured_type_components(name, source, out)
14075 }
14076 _ => None,
14077 }
14078}
14079
14080pub(crate) struct DeclaredNameBinding {
14083 pub pointer_depth: i32,
14085 pub indirect: bool,
14089}
14090
14091pub(crate) fn declared_name_binding(
14092 declaration: Node<'_>,
14093 type_node: Node<'_>,
14094 field_name: &str,
14095 source: &str,
14096) -> Option<DeclaredNameBinding> {
14097 let mut stack = Vec::new();
14098 let mut cursor = declaration.walk();
14099 stack.extend(
14100 declaration
14101 .named_children(&mut cursor)
14102 .filter(|child| !same_node(*child, type_node)),
14103 );
14104 while let Some(node) = stack.pop() {
14105 if matches!(node.kind(), "identifier" | "field_identifier")
14106 && node_text(node, source) == field_name
14107 {
14108 let mut binding = DeclaredNameBinding {
14109 pointer_depth: 0,
14110 indirect: false,
14111 };
14112 let mut current = node.parent();
14113 while let Some(parent) = current {
14114 if same_node(parent, declaration) {
14115 return Some(binding);
14116 }
14117 match parent.kind() {
14118 "pointer_declarator" => {
14119 binding.pointer_depth += 1;
14120 binding.indirect = true;
14121 }
14122 "reference_declarator" => binding.indirect = true,
14123 _ => {}
14124 }
14125 current = parent.parent();
14126 }
14127 return None;
14128 }
14129 let mut cursor = node.walk();
14130 stack.extend(node.named_children(&mut cursor));
14131 }
14132 None
14133}
14134
14135fn normalize_field_type_text(type_text: &str) -> String {
14136 const FIELD_SPECIFIERS: [&str; 8] = [
14137 "extern ",
14138 "static ",
14139 "mutable ",
14140 "constexpr ",
14141 "constinit ",
14142 "inline ",
14143 "volatile ",
14144 "const ",
14145 ];
14146
14147 let mut normalized = normalize_type_text(type_text);
14148 loop {
14149 let Some(stripped) = FIELD_SPECIFIERS
14150 .iter()
14151 .find_map(|specifier| normalized.strip_prefix(specifier))
14152 else {
14153 return normalized;
14154 };
14155 normalized = normalize_type_text(stripped);
14156 }
14157}
14158
14159pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14160 let Some(type_node) = node.child_by_field_name("type") else {
14161 return false;
14162 };
14163 ctx.visibility.resolves_to_type(
14164 &ctx.analyzer,
14165 ctx.file,
14166 node_text(type_node, ctx.source),
14167 owner,
14168 )
14169}
14170
14171pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14172 !ctx.analyzer
14173 .declarations(ctx.file)
14174 .into_iter()
14175 .filter(|unit| unit.is_function())
14176 .any(|unit| {
14177 ctx.analyzer.ranges(&unit).iter().any(|range| {
14178 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14179 })
14180 })
14181}
14182
14183pub enum DeclarationConstructorInitializer<'tree> {
14185 Arguments(Node<'tree>),
14188 Expression(Node<'tree>),
14191 Empty,
14193}
14194
14195pub fn declaration_constructor_initializer(
14196 node: Node<'_>,
14197) -> DeclarationConstructorInitializer<'_> {
14198 let mut cursor = node.walk();
14199 for child in node.named_children(&mut cursor) {
14200 if child.kind() == "init_declarator" {
14201 let Some(value) = child
14202 .child_by_field_name("value")
14203 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14204 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14205 else {
14206 return DeclarationConstructorInitializer::Empty;
14207 };
14208 return match value.kind() {
14209 "argument_list" | "initializer_list" => {
14210 DeclarationConstructorInitializer::Arguments(value)
14211 }
14212 "compound_literal_expression" => call_arguments_node(value)
14213 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14214 DeclarationConstructorInitializer::Arguments(arguments)
14215 }),
14216 _ => DeclarationConstructorInitializer::Expression(value),
14217 };
14218 }
14219 if let Some(declarator) = declaration_declarator(node, child) {
14220 return declarator_parameters(declarator)
14221 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14222 DeclarationConstructorInitializer::Arguments(parameters)
14223 });
14224 }
14225 }
14226 DeclarationConstructorInitializer::Empty
14227}
14228
14229pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14230 match declaration_constructor_initializer(node) {
14231 DeclarationConstructorInitializer::Arguments(arguments) => {
14232 argument_children(arguments).count()
14233 }
14234 DeclarationConstructorInitializer::Expression(_) => 1,
14235 DeclarationConstructorInitializer::Empty => 0,
14236 }
14237}
14238
14239fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14243 let mut current = node;
14244 loop {
14245 if let Some(parameters) = current.child_by_field_name("parameters") {
14246 return Some(parameters);
14247 }
14248 current = current.child_by_field_name("declarator")?;
14249 }
14250}
14251
14252fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14253 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14254}
14255
14256fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14257 if node.kind() == "identifier" {
14258 return true;
14259 }
14260 if node.kind() == "parenthesized_expression" {
14261 return false;
14262 }
14263 if node.kind() == "call_expression" {
14264 return node
14265 .child_by_field_name("function")
14266 .is_some_and(|function| function.kind() == "identifier");
14267 }
14268 let mut stack = vec![node];
14269 while let Some(descendant) = stack.pop() {
14270 if descendant != node && descendant.kind() == "parenthesized_expression" {
14271 continue;
14272 }
14273 if descendant.kind() == "identifier" {
14274 return true;
14275 }
14276 if descendant.kind() == "call_expression" {
14277 if descendant
14278 .child_by_field_name("function")
14279 .is_some_and(|function| function.kind() == "identifier")
14280 {
14281 return true;
14282 }
14283 continue;
14284 }
14285 push_named_children_reversed(descendant, &mut stack);
14286 }
14287 false
14288}
14289
14290fn macro_expansion_shape_is_safe(
14291 node: Node<'_>,
14292 source: &str,
14293 parameters: &[String],
14294 environment: &MacroEnvironment,
14295) -> bool {
14296 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14297 return true;
14298 }
14299 if node.kind() == "call_expression" {
14300 let Some(function) = node.child_by_field_name("function") else {
14301 return true;
14302 };
14303 if function.kind() != "identifier" {
14304 return true;
14305 }
14306 let function_name = node_text(function, source);
14307 if parameters
14308 .iter()
14309 .any(|parameter| parameter == function_name)
14310 {
14311 return false;
14312 }
14313 if !environment.may_bind(function_name) {
14314 return true;
14315 }
14316 let Some(arguments) = node.child_by_field_name("arguments") else {
14317 return false;
14318 };
14319 return argument_children(arguments).all(|argument| {
14320 if argument.kind() == "identifier"
14321 && parameters
14322 .iter()
14323 .any(|parameter| parameter == node_text(argument, source))
14324 {
14325 return false;
14326 }
14327 macro_expansion_shape_is_safe(argument, source, parameters, environment)
14328 });
14329 }
14330 let mut stack = vec![node];
14331 while let Some(descendant) = stack.pop() {
14332 if descendant != node {
14333 if descendant.kind() == "parenthesized_expression" {
14334 continue;
14335 }
14336 if descendant.kind() == "call_expression" {
14337 let expands = descendant
14338 .child_by_field_name("function")
14339 .filter(|function| function.kind() == "identifier")
14340 .is_some_and(|function| environment.may_bind(node_text(function, source)));
14341 if expands {
14342 return false;
14343 }
14344 continue;
14345 }
14346 }
14347 if descendant.kind() == "identifier" {
14348 let identifier = node_text(descendant, source);
14349 if parameters.iter().any(|parameter| parameter == identifier)
14350 || environment.may_bind(identifier)
14351 {
14352 return false;
14353 }
14354 }
14355 push_named_children_reversed(descendant, &mut stack);
14356 }
14357 true
14358}
14359
14360fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14361 let source = prepared.source();
14362 let mut facts = Vec::new();
14363 let mut nodes = vec![prepared.tree().root_node()];
14364 while let Some(node) = nodes.pop() {
14365 if node.kind() == "preproc_include" {
14366 let Some(path) = node
14367 .child_by_field_name("path")
14368 .and_then(|path| crate::imports::include_path_literal_value(path, source))
14369 .map(str::to_owned)
14370 else {
14371 continue;
14372 };
14373 facts.push(StructuredIncludeFact {
14374 start_byte: node.start_byte(),
14375 end_byte: node.end_byte(),
14376 path,
14377 });
14378 continue;
14379 }
14380 push_named_children_reversed(node, &mut nodes);
14381 }
14382 Arc::from(facts.into_boxed_slice())
14383}
14384
14385fn has_unresolved_include_visible_before_in_prepared(
14386 file: &ProjectFile,
14387 prepared: &PreparedSyntaxTree,
14388 include_targets: &IncludeTargetIndex,
14389 facts: &[StructuredIncludeFact],
14390 before_byte: usize,
14391) -> bool {
14392 let guards = OnceCell::new();
14393 let reference = CallableReferenceContext {
14394 file,
14395 position: Some(CallableReferencePosition {
14396 prepared,
14397 byte: before_byte,
14398 guards: &guards,
14399 }),
14400 };
14401 let root = prepared.tree().root_node();
14402 facts
14403 .iter()
14404 .filter(|fact| fact.end_byte <= before_byte)
14405 .any(|fact| {
14406 let node = root
14407 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14408 .expect("structured include fact range must be in prepared tree");
14409 assert_eq!(
14410 node.kind(),
14411 "preproc_include",
14412 "structured include fact range must identify its include node"
14413 );
14414 callable_preprocessor_context_is_visible_for_reference(
14415 node,
14416 prepared.source(),
14417 &reference,
14418 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14419 })
14420}
14421
14422fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14423 let descendant = node;
14424 while let Some(parent) = node.parent() {
14425 if is_cpp_preprocessor_conditional(parent)
14426 && !is_file_covering_include_guard(parent, source)
14427 && preprocessor_conditional_contains_descendant(parent, descendant)
14428 {
14429 return true;
14430 }
14431 node = parent;
14432 }
14433 false
14434}
14435
14436fn owning_preprocessor_conditionals(
14448 root: Node<'_>,
14449 event: Node<'_>,
14450 source: &str,
14451) -> OwningPreprocessorConditionals {
14452 if !has_preprocessor_conditional_ancestor(event, source) {
14453 return OwningPreprocessorConditionals::default();
14454 }
14455 let start = event.start_byte();
14456 let descendant = root
14457 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14458 .expect("a byte inside the parsed tree names a descendant");
14459 let mut owners = Vec::new();
14460 let mut current = descendant.parent();
14461 while let Some(conditional) = current {
14462 if is_cpp_preprocessor_conditional(conditional)
14463 && !is_file_covering_include_guard(conditional, source)
14464 && preprocessor_conditional_contains_descendant(conditional, descendant)
14465 {
14466 owners.push(conditional.start_byte());
14467 }
14468 current = conditional.parent();
14469 }
14470 owners.into_boxed_slice()
14471}
14472
14473fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14474 node.parent()
14475 .filter(|parent| parent.kind() == "translation_unit")
14476 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14477 && is_canonical_include_guard(node, source)
14478}
14479
14480fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14481 if node.kind() != "preproc_ifdef"
14482 || node
14483 .child(0)
14484 .is_none_or(|directive| directive.kind() != "#ifndef")
14485 || node.child_by_field_name("alternative").is_some()
14486 {
14487 return false;
14488 }
14489 let Some(guard_name) = node.child_by_field_name("name") else {
14490 return false;
14491 };
14492 let mut cursor = node.walk();
14493 node.named_children(&mut cursor)
14494 .find(|child| *child != guard_name && child.kind() != "comment")
14495 .filter(|child| child.kind() == "preproc_def")
14496 .and_then(|definition| definition.child_by_field_name("name"))
14497 .is_some_and(|defined_name| {
14498 node_text(defined_name, source) == node_text(guard_name, source)
14499 })
14500}
14501
14502fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14503 let mut guard = None;
14504 for child in named_children_iter(root) {
14505 if child.kind() == "comment" || is_pragma_once(child, source) {
14506 continue;
14507 }
14508 if guard.is_none() && is_canonical_include_guard(child, source) {
14509 guard = Some(child);
14510 } else {
14511 return None;
14512 }
14513 }
14514 guard
14515 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14516 .map(|name| node_text(name, source).to_string())
14517}
14518
14519fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14520 if (0..root.named_child_count())
14521 .filter_map(|index| root.named_child(index))
14522 .any(|child| is_pragma_once(child, source))
14523 {
14524 return MacroIncludeProtection::PragmaOnce;
14525 }
14526 top_level_canonical_include_guard_name(root, source)
14527 .map(MacroIncludeProtection::MacroGuard)
14528 .unwrap_or(MacroIncludeProtection::None)
14529}
14530
14531fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14532 node.kind() == "preproc_call"
14533 && node
14534 .child_by_field_name("directive")
14535 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14536 && node
14537 .child_by_field_name("argument")
14538 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14539}
14540
14541fn parse_preproc_identifier(argument: &str) -> Option<String> {
14542 let parsed = parse_sentinel(
14543 "void __bifrost_undef() { ",
14544 SentinelInput::replacement(argument),
14545 "; }",
14546 );
14547 let (sentinel, tree) = (parsed.source, parsed.tree);
14548 if tree.root_node().has_error() {
14549 return None;
14550 }
14551 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14552 let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
14555 (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
14556}
14557
14558pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14559 match node.kind() {
14560 "identifier" | "field_identifier" => {
14561 let name = node_text(node, source).trim();
14562 (!name.is_empty()).then(|| name.to_string())
14563 }
14564 "abstract_array_declarator"
14565 | "abstract_function_declarator"
14566 | "abstract_parenthesized_declarator"
14567 | "abstract_pointer_declarator"
14568 | "abstract_reference_declarator" => None,
14569 "function_declarator" => node
14570 .child_by_field_name("declarator")
14571 .or_else(|| node.child_by_field_name("name"))
14572 .and_then(|child| extract_variable_name(child, source)),
14573 _ => node
14574 .child_by_field_name("declarator")
14575 .or_else(|| node.child_by_field_name("name"))
14576 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14577 .and_then(|child| extract_variable_name(child, source)),
14578 }
14579}
14580
14581pub fn is_c_source_file(file: &ProjectFile) -> bool {
14592 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14593}
14594
14595pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14602 if !is_c_source_file(file) || node.kind() != "identifier" {
14603 return false;
14604 }
14605 let mut operand = node;
14606 while let Some(parent) = operand.parent().filter(|parent| {
14607 parent.kind() == "parenthesized_expression"
14608 && parent.named_child_count() == 1
14609 && parent.named_child(0) == Some(operand)
14610 }) {
14611 operand = parent;
14612 }
14613 operand.parent().is_some_and(|parent| {
14614 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14615 })
14616}
14617
14618pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14627 if !is_c_offsetof_member_node(node) {
14628 return None;
14629 }
14630 let expression = node.parent()?;
14631 if expression.has_error() {
14632 return None;
14633 }
14634 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14635 if closing.kind() != ")" || closing.is_missing() {
14636 return None;
14637 }
14638 let type_descriptor = expression.child_by_field_name("type")?;
14639 if type_descriptor.kind() != "type_descriptor"
14640 || type_descriptor.is_missing()
14641 || type_descriptor.has_error()
14642 {
14643 return None;
14644 }
14645 let type_specifier = type_descriptor.child_by_field_name("type")?;
14646 if type_specifier.is_missing() || type_specifier.has_error() {
14647 return None;
14648 }
14649 let type_reference = match type_specifier.kind() {
14650 "class_specifier" | "struct_specifier" | "union_specifier" => {
14651 type_specifier.child_by_field_name("name")?
14652 }
14653 _ => type_specifier,
14654 };
14655 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14656}
14657
14658pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14663 node.kind() == "field_identifier"
14664 && node.parent().is_some_and(|parent| {
14665 parent.kind() == "offsetof_expression"
14666 && parent.child_by_field_name("member") == Some(node)
14667 })
14668}
14669
14670pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14674 if node.kind() != "template_function" {
14675 return false;
14676 }
14677 let Some(arguments) = node
14678 .parent()
14679 .filter(|parent| parent.kind() == "template_argument_list")
14680 else {
14681 return false;
14682 };
14683 let Some(owner) = arguments.parent().filter(|parent| {
14684 parent.kind() == "template_type"
14685 && parent.child_by_field_name("arguments") == Some(arguments)
14686 }) else {
14687 return false;
14688 };
14689
14690 let mut current = owner;
14691 while let Some(parent) = current.parent() {
14692 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14693 {
14694 return true;
14695 }
14696 if parent.kind() == "qualified_identifier"
14697 && parent.child_by_field_name("name") == Some(current)
14698 {
14699 current = parent;
14700 continue;
14701 }
14702 return false;
14703 }
14704 false
14705}
14706
14707pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14723 if node.kind() != "type_identifier" {
14724 return false;
14725 }
14726 let Some(descriptor) = node
14727 .parent()
14728 .filter(|parent| parent.kind() == "type_descriptor")
14729 else {
14730 return false;
14731 };
14732 if descriptor.child_by_field_name("type") != Some(node) {
14733 return false;
14734 }
14735 let Some(arguments) = descriptor
14736 .parent()
14737 .filter(|parent| parent.kind() == "template_argument_list")
14738 else {
14739 return false;
14740 };
14741 arguments.parent().is_some_and(|owner| {
14742 matches!(
14743 owner.kind(),
14744 "template_type" | "template_function" | "template_method"
14745 ) && owner.child_by_field_name("arguments") == Some(arguments)
14746 })
14747}
14748
14749pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14762 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14763}
14764
14765pub fn is_declarator_node(node: Node<'_>) -> bool {
14766 matches!(
14767 node.kind(),
14768 "identifier"
14769 | "field_identifier"
14770 | "qualified_identifier"
14771 | "scoped_identifier"
14772 | "pointer_declarator"
14773 | "reference_declarator"
14774 | "array_declarator"
14775 | "parenthesized_declarator"
14776 | "function_declarator"
14777 )
14778}
14779
14780pub fn declaration_declarator<'tree>(
14789 declaration: Node<'tree>,
14790 child: Node<'tree>,
14791) -> Option<Node<'tree>> {
14792 if !matches!(
14793 declaration.kind(),
14794 "declaration"
14795 | "field_declaration"
14796 | "parameter_declaration"
14797 | "optional_parameter_declaration"
14798 | "function_definition"
14799 | "type_definition"
14800 | "alias_declaration"
14801 | "template_instantiation"
14802 ) {
14803 return None;
14804 }
14805 if declaration
14806 .child_by_field_name("type")
14807 .is_some_and(|type_node| same_node(type_node, child))
14808 {
14809 return None;
14810 }
14811 if child.kind() == "init_declarator" {
14812 return child.child_by_field_name("declarator");
14813 }
14814 let field = field_name_in_parent(declaration, child);
14815 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14816 || (declaration.kind() == "type_definition"
14817 && child.kind() == "type_identifier"
14818 && matches!(field, Some("declarator") | None))
14819 {
14820 Some(child)
14821 } else {
14822 None
14823 }
14824}
14825
14826pub fn cpp_binding_parts<'tree>(
14828 node: Node<'tree>,
14829 source: &str,
14830) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14831 let declarator = node.child_by_field_name("declarator")?;
14832 let name = extract_variable_name(declarator, source)?;
14833 let type_node = node
14834 .child_by_field_name("type")
14835 .or_else(|| first_type_child(node));
14836 Some((name, declarator, type_node))
14837}
14838
14839#[derive(Clone, Debug, PartialEq, Eq)]
14842pub struct RecoveredNamespaceRegion {
14843 pub start: usize,
14845 pub end: usize,
14847 pub components: Vec<String>,
14850 pub component_ranges: Vec<Range>,
14861 pub component_name_ranges: Vec<Range>,
14867 pub component_closes: Vec<Option<Range>>,
14873}
14874
14875#[derive(Clone, Debug, Default)]
14940pub struct OrphanedNamespaceScopeIndex {
14941 regions: Vec<RecoveredNamespaceRegion>,
14942 brace_closes: HashMap<usize, Range>,
14945}
14946
14947impl OrphanedNamespaceScopeIndex {
14948 pub fn build(root: Node<'_>, source: &str) -> Self {
14949 if !root.has_error() {
14950 return Self::default();
14951 }
14952 #[derive(Clone, Copy)]
14958 struct OpenHead {
14959 head: Range,
14960 open: usize,
14961 }
14962 struct PendingRegion {
14965 start: usize,
14966 end: usize,
14967 components: Vec<String>,
14968 heads: Vec<OpenHead>,
14969 name_ranges: Vec<Range>,
14970 }
14971 struct Frame<'tree> {
14972 node: Node<'tree>,
14973 children: Vec<Node<'tree>>,
14974 next: usize,
14975 parsed_scope: Vec<String>,
14976 run: Option<PendingRegion>,
14977 }
14978 fn frame<'tree>(
14979 node: Node<'tree>,
14980 mut parsed_scope: Vec<String>,
14981 source: &str,
14982 ) -> Frame<'tree> {
14983 if node.kind() == "namespace_definition"
14984 && let Some(name) = node.child_by_field_name("name")
14985 {
14986 let mut components = Vec::new();
14987 if append_cpp_name_components(name, source, &mut components).is_some() {
14988 parsed_scope.extend(components);
14989 }
14990 }
14991 let mut cursor = node.walk();
14992 Frame {
14993 node,
14994 children: node.children(&mut cursor).collect(),
14995 next: 0,
14996 parsed_scope,
14997 run: None,
14998 }
14999 }
15000 let mut regions = Vec::new();
15001 let mut brace_closes = HashMap::default();
15002 let mut namespace_brace_proofs = Vec::new();
15009 let mut unpaired_closes: Vec<Range> = Vec::new();
15015 let mut open = Vec::new();
15019 let mut lexical_scope = Vec::new();
15020 let mut lexical_heads: Vec<OpenHead> = Vec::new();
15025 let mut lexical_name_ranges: Vec<Range> = Vec::new();
15028 let mut frames = vec![frame(root, Vec::new(), source)];
15029 while !frames.is_empty() {
15034 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15035 let current = frames.last_mut().expect("frames is non-empty");
15036 if current.next == current.children.len() {
15037 regions.extend(frames.pop().expect("the frame just borrowed").run);
15038 continue;
15039 }
15040 let child = current.children[current.next];
15041 current.next += 1;
15042 match child.kind() {
15043 "{" if !child.is_missing() => {
15044 regions.extend(current.run.take());
15045 if let Some(namespace) = parent_node.filter(|parent| {
15046 parent.kind() == "namespace_definition"
15047 && parent.child_by_field_name("body") == Some(current.node)
15048 && child.start_byte() == current.node.start_byte()
15049 }) && let Some(proof) = namespace_body_brace_proof(current.node)
15050 {
15051 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15052 }
15053 let mut named = parent_node.and_then(|parent| {
15058 namespace_body_name_components(parent, current.node, source)
15059 });
15060 if named.is_none() {
15061 named = recovered_namespace_open_components(
15062 ¤t.children[..current.next - 1],
15063 source,
15064 );
15065 }
15066 open.push((child.start_byte(), lexical_scope.len()));
15067 if let Some((written_at, components)) = named {
15068 let head = OpenHead {
15069 head: Range {
15070 start_byte: written_at.start_byte(),
15071 end_byte: child.end_byte(),
15072 start_line: written_at.start_position().row + 1,
15073 end_line: child.end_position().row + 1,
15074 },
15075 open: child.start_byte(),
15076 };
15077 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15078 lexical_name_ranges
15079 .extend(components.iter().map(|(_, name_range)| *name_range));
15080 lexical_scope
15081 .extend(components.into_iter().map(|(component, _)| component));
15082 }
15083 continue;
15084 }
15085 "}" if !child.is_missing() => {
15086 regions.extend(current.run.take());
15087 let close = Range {
15088 start_byte: child.start_byte(),
15089 end_byte: child.end_byte(),
15090 start_line: child.start_position().row + 1,
15091 end_line: child.end_position().row + 1,
15092 };
15093 match open.pop() {
15094 Some((start, namespace_len)) => {
15095 debug_assert!(
15096 start < close.start_byte,
15097 "a close pairs with an open before it: {start} then {close:?}"
15098 );
15099 lexical_scope.truncate(namespace_len);
15100 lexical_heads.truncate(namespace_len);
15101 lexical_name_ranges.truncate(namespace_len);
15102 brace_closes.insert(start, close);
15103 }
15104 None => unpaired_closes.push(close),
15105 }
15106 continue;
15107 }
15108 _ => {}
15109 }
15110 if current.node.kind() != "namespace_definition"
15114 && lexical_scope != current.parsed_scope
15115 {
15116 match &mut current.run {
15117 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15118 run => {
15119 regions.extend(run.take());
15120 *run = Some(PendingRegion {
15121 start: child.start_byte(),
15122 end: child.end_byte(),
15123 components: lexical_scope.clone(),
15124 heads: lexical_heads.clone(),
15125 name_ranges: lexical_name_ranges.clone(),
15126 });
15127 }
15128 }
15129 } else {
15130 regions.extend(current.run.take());
15131 }
15132 let descend = child.has_error()
15140 || (child.kind() == "function_definition"
15141 && child.child_by_field_name("body").is_some_and(|body| {
15142 namespace_body_name_components(child, body, source).is_some()
15143 }));
15144 if descend {
15145 let parsed_scope = current.parsed_scope.clone();
15146 frames.push(frame(child, parsed_scope, source));
15147 }
15148 }
15149 if !unpaired_closes.is_empty() {
15158 brace_closes.clear();
15159 } else {
15160 let missing_namespaces = namespace_brace_proofs
15166 .iter()
15167 .filter_map(|(_, namespace, proof)| {
15168 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15169 })
15170 .collect::<HashSet<_>>();
15171 for (open, namespace, proof) in namespace_brace_proofs {
15172 if !std::iter::successors(Some(namespace), |node| node.parent())
15173 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15174 {
15175 continue;
15176 }
15177 match proof {
15178 NamespaceBodyBraceProof::Real(close) => {
15179 brace_closes.insert(open, close);
15180 }
15181 NamespaceBodyBraceProof::Missing => {
15182 brace_closes.remove(&open);
15183 }
15184 }
15185 }
15186 }
15187 let regions = regions
15192 .into_iter()
15193 .map(|region| {
15194 debug_assert_eq!(
15195 region.components.len(),
15196 region.heads.len(),
15197 "every recovered namespace level carries the head that writes it: {:?}",
15198 region.components
15199 );
15200 debug_assert_eq!(
15201 region.components.len(),
15202 region.name_ranges.len(),
15203 "every recovered namespace level carries the name token that writes it: {:?}",
15204 region.components
15205 );
15206 let component_closes: Vec<_> = region
15207 .heads
15208 .iter()
15209 .map(|level| brace_closes.get(&level.open).copied())
15210 .collect();
15211 let component_ranges = region
15212 .heads
15213 .iter()
15214 .zip(&component_closes)
15215 .map(|(level, close)| match close {
15216 Some(close) => Range {
15217 start_byte: level.head.start_byte,
15218 end_byte: close.end_byte,
15219 start_line: level.head.start_line,
15220 end_line: close.end_line,
15221 },
15222 None => level.head,
15223 })
15224 .collect();
15225 RecoveredNamespaceRegion {
15226 start: region.start,
15227 end: region.end,
15228 components: region.components,
15229 component_ranges,
15230 component_name_ranges: region.name_ranges,
15231 component_closes,
15232 }
15233 })
15234 .collect();
15235 Self {
15236 regions,
15237 brace_closes,
15238 }
15239 }
15240
15241 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15248 self.brace_closes.get(&open).copied()
15249 }
15250
15251 pub fn is_empty(&self) -> bool {
15252 self.regions.is_empty()
15253 }
15254
15255 pub fn approximate_size(&self) -> usize {
15257 self.regions.iter().fold(
15258 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15259 |total, region| {
15260 total
15261 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15262 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15263 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15264 .saturating_add(
15265 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15266 )
15267 .saturating_add(
15268 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15269 )
15270 },
15271 )
15272 }
15273
15274 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15276 self.regions
15277 .iter()
15278 .filter(|region| region.start <= byte && byte < region.end)
15279 .min_by_key(|region| region.end - region.start)
15280 }
15281
15282 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15286 self.enclosing_namespace_levels(node, source)
15287 .into_iter()
15288 .map(|(component, ..)| component)
15289 .collect()
15290 }
15291
15292 pub fn enclosing_namespace_levels(
15301 &self,
15302 node: Node<'_>,
15303 source: &str,
15304 ) -> Vec<(String, Range, Range)> {
15305 let mut parsed = Vec::new();
15306 let mut current = node.parent();
15307 while let Some(parent) = current {
15308 if parent.kind() == "namespace_definition"
15309 && let Some(name) = parent.child_by_field_name("name")
15310 && let Some(components) = cpp_name_component_ranges(name, source)
15311 {
15312 let range = node_range(parent);
15313 parsed.push((
15314 parent.start_byte(),
15315 components
15316 .into_iter()
15317 .map(|(component, name_range)| (component, range, name_range))
15318 .collect(),
15319 namespace_definition_has_real_braces(parent),
15320 ));
15321 }
15322 current = parent.parent();
15323 }
15324 parsed.reverse();
15325 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15326 region
15327 .components
15328 .iter()
15329 .take(active_components)
15330 .cloned()
15331 .zip(
15332 region
15333 .component_ranges
15334 .iter()
15335 .take(active_components)
15336 .copied(),
15337 )
15338 .zip(
15339 region
15340 .component_name_ranges
15341 .iter()
15342 .take(active_components)
15343 .copied(),
15344 )
15345 .map(|((component, range), name_range)| (component, range, name_range))
15346 .collect()
15347 })
15348 }
15349
15350 pub fn restore_enclosing_namespaces(
15356 &self,
15357 parsed: Vec<(usize, Vec<String>, bool)>,
15358 node_start: usize,
15359 ) -> Vec<String> {
15360 self.splice_recovered_path(parsed, node_start, |region, active_components| {
15361 region
15362 .components
15363 .iter()
15364 .take(active_components)
15365 .cloned()
15366 .collect()
15367 })
15368 }
15369
15370 fn splice_recovered_path<T>(
15376 &self,
15377 parsed: Vec<(usize, Vec<T>, bool)>,
15378 node_start: usize,
15379 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15380 ) -> Vec<T> {
15381 let Some(region) = self.region_at(node_start) else {
15382 return parsed
15383 .into_iter()
15384 .flat_map(|(_, levels, _)| levels)
15385 .collect();
15386 };
15387 debug_assert_eq!(
15388 region.components.len(),
15389 region.component_closes.len(),
15390 "every recovered namespace component carries its close proof: {region:?}"
15391 );
15392 debug_assert_eq!(
15393 region.components.len(),
15394 region.component_name_ranges.len(),
15395 "every recovered namespace component carries its name token: {region:?}"
15396 );
15397 if region.components.is_empty() {
15398 return parsed
15399 .into_iter()
15400 .filter(|(_, _, complete)| *complete)
15401 .flat_map(|(_, levels, _)| levels)
15402 .collect();
15403 }
15404 let active_components = region
15405 .component_closes
15406 .iter()
15407 .take_while(|close| {
15408 close
15409 .as_ref()
15410 .is_none_or(|close| node_start < close.end_byte)
15411 })
15412 .count();
15413 recovered(region, active_components)
15414 .into_iter()
15415 .chain(
15416 parsed
15417 .into_iter()
15418 .filter(|(start, _, _)| *start >= region.start)
15419 .flat_map(|(_, levels, _)| levels),
15420 )
15421 .collect()
15422 }
15423}
15424
15425pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15429 debug_assert_eq!(namespace.kind(), "namespace_definition");
15430 let Some(body) = namespace.child_by_field_name("body") else {
15431 return false;
15432 };
15433 let mut opening = false;
15434 let mut closing = false;
15435 let mut cursor = body.walk();
15436 for child in body.children(&mut cursor) {
15437 match child.kind() {
15438 "{" if !child.is_missing() => opening = true,
15439 "}" if !child.is_missing() => closing = true,
15440 _ => {}
15441 }
15442 }
15443 opening && closing
15444}
15445
15446fn recovered_namespace_open_components<'tree>(
15462 preceding: &[Node<'tree>],
15463 source: &str,
15464) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15465 let mut head = Vec::new();
15466 for &sibling in preceding.iter().rev() {
15467 if sibling.kind() != "comment" {
15468 head.push(sibling);
15469 if head.len() == 2 {
15470 break;
15471 }
15472 }
15473 }
15474 let [name, keyword] = head[..] else {
15475 return None;
15476 };
15477 if keyword.kind() != "namespace" {
15478 return None;
15479 }
15480 let components = cpp_name_component_ranges(name, source)?;
15481 (!components.is_empty()).then_some((keyword, components))
15482}
15483
15484fn namespace_body_name_components<'tree>(
15489 parent: Node<'tree>,
15490 body: Node<'tree>,
15491 source: &str,
15492) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15493 if parent.child_by_field_name("body") != Some(body) {
15494 return None;
15495 }
15496 let (written_at, name) = match (parent.kind(), body.kind()) {
15497 ("namespace_definition", "declaration_list") => {
15498 (parent, parent.child_by_field_name("name")?)
15499 }
15500 ("function_definition", "compound_statement") => {
15501 recovered_literal_namespace_head(parent, body, source)
15507 .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15508 }
15509 _ => return None,
15510 };
15511 let components = cpp_name_component_ranges(name, source)?;
15512 (!components.is_empty()).then_some((written_at, components))
15513}
15514
15515fn recovered_literal_namespace_head<'tree>(
15518 parent: Node<'tree>,
15519 body: Node<'tree>,
15520 source: &str,
15521) -> Option<(Node<'tree>, Node<'tree>)> {
15522 let mut cursor = parent.walk();
15526 let children = parent
15527 .children(&mut cursor)
15528 .filter(|child| child.kind() != "comment")
15529 .collect::<Vec<_>>();
15530 let [prefix, keyword, error, actual_body] = children.as_slice() else {
15531 return None;
15532 };
15533 if *actual_body != body
15534 || parent.child_by_field_name("type") != Some(*prefix)
15535 || parent.child_by_field_name("declarator") != Some(*keyword)
15536 || prefix.kind() != "type_identifier"
15537 || prefix.is_missing()
15538 || prefix.has_error()
15539 || prefix.child_count() != 0
15540 || keyword.kind() != "identifier"
15541 || keyword.is_missing()
15542 || keyword.has_error()
15543 || keyword.child_count() != 0
15544 || node_text(*keyword, source) != "namespace"
15545 || error.kind() != "ERROR"
15546 || error.is_missing()
15547 || body.is_missing()
15548 {
15549 return None;
15550 }
15551 let mut cursor = error.walk();
15552 let names = error
15553 .children(&mut cursor)
15554 .filter(|child| child.kind() != "comment")
15555 .collect::<Vec<_>>();
15556 let [name] = names.as_slice() else {
15557 return None;
15558 };
15559 if !matches!(name.kind(), "identifier" | "namespace_identifier")
15560 || name.is_missing()
15561 || name.has_error()
15562 || name.child_count() != 0
15563 {
15564 return None;
15565 }
15566 let open = body.child(0)?;
15567 if open.kind() != "{"
15568 || open.is_missing()
15569 || open.has_error()
15570 || open.start_byte() != body.start_byte()
15571 {
15572 return None;
15573 }
15574 Some((*keyword, *name))
15577}
15578
15579fn macro_modifier_namespace_head<'tree>(
15583 parent: Node<'tree>,
15584 body: Node<'tree>,
15585 source: &str,
15586) -> Option<(Node<'tree>, Node<'tree>)> {
15587 let mut cursor = parent.walk();
15595 let children = parent
15596 .children(&mut cursor)
15597 .filter(|child| child.kind() != "comment")
15598 .collect::<Vec<_>>();
15599 let [prefix, keyword, name, actual_body] = children.as_slice() else {
15600 return None;
15601 };
15602 if *actual_body != body
15603 || parent.child_by_field_name("type") != Some(*keyword)
15604 || parent.child_by_field_name("declarator") != Some(*name)
15605 || prefix.kind() != "macro_modifier"
15606 || prefix.is_missing()
15607 || prefix.has_error()
15608 || macro_modifier_identifier(*prefix).is_none()
15609 || keyword.kind() != "type_identifier"
15610 || keyword.is_missing()
15611 || keyword.has_error()
15612 || keyword.child_count() != 0
15613 || node_text(*keyword, source) != "namespace"
15614 || !matches!(name.kind(), "identifier" | "namespace_identifier")
15615 || name.is_missing()
15616 || name.has_error()
15617 || name.child_count() != 0
15618 || body.is_missing()
15619 {
15620 return None;
15621 }
15622 let open = body.child(0)?;
15623 if open.kind() != "{"
15624 || open.is_missing()
15625 || open.has_error()
15626 || open.start_byte() != body.start_byte()
15627 {
15628 return None;
15629 }
15630 Some((*keyword, *name))
15633}
15634
15635#[derive(Clone, Copy)]
15636enum NamespaceBodyBraceProof {
15637 Real(Range),
15638 Missing,
15639}
15640
15641fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15647 if body.kind() != "declaration_list" {
15648 return None;
15649 }
15650 let mut cursor = body.walk();
15651 let children = body.children(&mut cursor).collect::<Vec<_>>();
15652 let close_index = children
15653 .iter()
15654 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15655 let close = children[close_index];
15656 if close.is_missing() {
15657 return Some(NamespaceBodyBraceProof::Missing);
15658 }
15659 let namespace_terminator_sibling = body
15660 .parent()
15661 .filter(|namespace| namespace.kind() == "namespace_definition")
15662 .and_then(|namespace| namespace.next_sibling())
15663 .is_some_and(|sibling| {
15664 sibling.kind() == ";"
15665 || (sibling.kind() == "expression_statement"
15666 && sibling.child_count() == 1
15667 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15668 });
15669 if namespace_terminator_sibling
15674 || children
15675 .get(close_index + 1)
15676 .is_some_and(|sibling| sibling.kind() == ";")
15677 || children[..close_index]
15678 .iter()
15679 .rev()
15680 .find(|child| !child.is_extra() && child.kind() != "{")
15681 .is_some_and(|previous| {
15682 matches!(
15683 previous.kind(),
15684 "expression_statement" | "labeled_statement"
15685 )
15686 })
15687 {
15688 return None;
15689 }
15690 Some(NamespaceBodyBraceProof::Real(node_range(close)))
15691}
15692
15693#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15694pub enum RecoveredDeclaratorTypeContext {
15695 Declaration,
15696 FunctionDefinition,
15697 Parameter,
15698}
15699
15700pub fn recovered_macro_decorated_declarator_type(
15715 node: Node<'_>,
15716) -> Option<RecoveredDeclaratorTypeContext> {
15717 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15718}
15719
15720pub fn recovered_macro_decorated_type_node(
15725 node: Node<'_>,
15726) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15727 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15728 return None;
15729 }
15730 let qualified = node.parent()?;
15731 if qualified.kind() != "qualified_identifier"
15732 || qualified.child_by_field_name("scope") != Some(node)
15733 || !(0..qualified.child_count())
15734 .filter_map(|index| qualified.child(index))
15735 .any(|child| child.kind() == "::" && child.is_missing())
15736 {
15737 return None;
15738 }
15739 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15740 return None;
15741 }
15742
15743 let (declaration, context) = recovered_declarator_container(qualified)?;
15744 let type_node = declaration
15745 .child_by_field_name("type")
15746 .filter(|type_node| {
15747 *type_node != qualified
15748 && !type_node.is_missing()
15749 && type_node.start_byte() != type_node.end_byte()
15750 })?;
15751 Some((type_node, context))
15752}
15753
15754pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
15761 matches!(node.kind(), "type_identifier" | "template_type")
15762 && node.parent().is_some_and(|declaration| {
15763 matches!(
15764 declaration.kind(),
15765 "declaration"
15766 | "function_definition"
15767 | "parameter_declaration"
15768 | "optional_parameter_declaration"
15769 ) && declaration.child_by_field_name("type") == Some(node)
15770 && declaration.child_by_field_name("declarator").is_some()
15771 && crate::declarations::cpp_declaration_type_follows_macro_modifier(
15772 declaration,
15773 node,
15774 )
15775 })
15776}
15777
15778fn recovered_declarator_container(
15779 mut declarator: Node<'_>,
15780) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15781 loop {
15782 let parent = declarator.parent()?;
15783 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15784 return Some((
15785 parent
15786 .parent()
15787 .filter(|declaration| declaration.kind() == "declaration")?,
15788 RecoveredDeclaratorTypeContext::Declaration,
15789 ));
15790 }
15791 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15792 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15793 }
15794 if parent.kind() == "function_definition"
15795 && has_field_child(parent, "declarator", declarator)
15796 {
15797 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15798 }
15799 if matches!(
15805 parent.kind(),
15806 "parameter_declaration" | "optional_parameter_declaration"
15807 ) && has_field_child(parent, "declarator", declarator)
15808 {
15809 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15810 }
15811 if !matches!(
15812 parent.kind(),
15813 "array_declarator"
15814 | "function_declarator"
15815 | "parenthesized_declarator"
15816 | "pointer_declarator"
15817 | "pointer_type_declarator"
15818 | "reference_declarator"
15819 ) || !has_field_child(parent, "declarator", declarator)
15820 {
15821 return None;
15822 }
15823 declarator = parent;
15824 }
15825}
15826
15827fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15828 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15829}
15830
15831fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15832 loop {
15833 if node.is_missing() || node.start_byte() == node.end_byte() {
15834 return false;
15835 }
15836 match node.kind() {
15837 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15838 return true;
15839 }
15840 "array_declarator"
15841 | "function_declarator"
15842 | "parenthesized_declarator"
15843 | "pointer_declarator"
15844 | "pointer_type_declarator"
15845 | "reference_declarator" => {
15846 let Some(declarator) = node.child_by_field_name("declarator") else {
15847 return false;
15848 };
15849 node = declarator;
15850 }
15851 _ => return false,
15852 }
15853 }
15854}
15855
15856pub enum DesignatedInitializerOwner {
15858 Resolved(CodeUnit),
15859 Unresolved,
15860}
15861
15862enum InitializerOwnerStep {
15863 Field(String),
15864 AggregateWrapper,
15865}
15866
15867pub fn designated_initializer_owner(
15877 analyzer: &CppGraphSource<'_>,
15878 visibility: &VisibilityIndex<'_>,
15879 file: &ProjectFile,
15880 source: &str,
15881 node: Node<'_>,
15882) -> Option<DesignatedInitializerOwner> {
15883 if let Some(designator) = node
15884 .parent()
15885 .filter(|parent| parent.kind() == "field_designator")
15886 {
15887 let pair = designator.parent()?;
15888 if pair.kind() != "initializer_pair" {
15889 return None;
15890 }
15891 let mut cursor = pair.walk();
15892 let designators = pair
15893 .children_by_field_name("designator", &mut cursor)
15894 .collect::<Vec<_>>();
15895 let position = designators
15896 .iter()
15897 .position(|candidate| same_node(*candidate, designator))?;
15898 let initializer = pair.parent()?;
15899 if initializer.kind() != "initializer_list" {
15900 return None;
15901 }
15902 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15903 for prior in &designators[..position] {
15904 let field = prior
15905 .child_by_field_name("field")
15906 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15907 owner = owner.and_then(|owner| {
15908 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15909 });
15910 }
15911 return Some(classified_designated_owner(owner));
15912 }
15913
15914 let init_declarator = node.parent()?;
15915 if init_declarator.child_by_field_name("declarator") != Some(node)
15916 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15917 {
15918 return None;
15919 }
15920 Some(classified_designated_owner(declaration_owner(
15921 analyzer,
15922 visibility,
15923 file,
15924 source,
15925 init_declarator.parent()?,
15926 )))
15927}
15928
15929fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15930 owner.map_or(
15931 DesignatedInitializerOwner::Unresolved,
15932 DesignatedInitializerOwner::Resolved,
15933 )
15934}
15935
15936fn initializer_list_owner(
15937 analyzer: &CppGraphSource<'_>,
15938 visibility: &VisibilityIndex<'_>,
15939 file: &ProjectFile,
15940 source: &str,
15941 initializer: Node<'_>,
15942) -> Option<CodeUnit> {
15943 let mut current = initializer;
15944 let mut steps = Vec::new();
15945 loop {
15946 let parent = current.parent()?;
15947 match parent.kind() {
15948 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15949 let designator = parent.child_by_field_name("designator")?;
15950 let step = designator
15951 .child_by_field_name("field")
15952 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15953 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15954 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15955 steps.push(step);
15956 current = parent.parent()?;
15957 }
15958 "initializer_list" => {
15959 current = parent;
15960 }
15961 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15962 let declaration = parent.parent()?;
15963 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15964 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15965 }
15966 "compound_literal_expression"
15967 if parent.child_by_field_name("value") == Some(current) =>
15968 {
15969 let type_node = parent.child_by_field_name("type")?;
15970 let owner =
15971 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15972 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15973 }
15974 "ERROR" => current = parent,
15975 _ => return None,
15976 }
15977 }
15978}
15979
15980fn apply_initializer_owner_steps(
15981 analyzer: &CppGraphSource<'_>,
15982 visibility: &VisibilityIndex<'_>,
15983 file: &ProjectFile,
15984 mut owner: CodeUnit,
15985 steps: Vec<InitializerOwnerStep>,
15986) -> Option<CodeUnit> {
15987 for step in steps.into_iter().rev() {
15988 if let InitializerOwnerStep::Field(field_name) = step {
15989 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15990 }
15991 }
15992 Some(owner)
15993}
15994
15995fn initializer_field_owner(
15996 analyzer: &CppGraphSource<'_>,
15997 visibility: &VisibilityIndex<'_>,
15998 file: &ProjectFile,
15999 owner: CodeUnit,
16000 field_name: &str,
16001) -> Option<CodeUnit> {
16002 let fields = visibility
16003 .visible_members_for_owner_name(file, &owner, field_name)
16004 .into_iter()
16005 .filter(|field| field.is_field())
16006 .collect::<Vec<_>>();
16007 let field = match fields.as_slice() {
16008 [field] => *field,
16009 _ => return None,
16010 };
16011 field_declared_binding(analyzer, visibility, file, field)?.unit
16012}
16013
16014fn declaration_owner(
16015 analyzer: &CppGraphSource<'_>,
16016 visibility: &VisibilityIndex<'_>,
16017 file: &ProjectFile,
16018 source: &str,
16019 declaration: Node<'_>,
16020) -> Option<CodeUnit> {
16021 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16022 return None;
16023 }
16024 let type_node = declaration
16025 .child_by_field_name("type")
16026 .or_else(|| first_type_child(declaration))?;
16027 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16028}
16029
16030fn resolve_designated_owner_type(
16031 analyzer: &CppGraphSource<'_>,
16032 visibility: &VisibilityIndex<'_>,
16033 file: &ProjectFile,
16034 source: &str,
16035 type_node: Node<'_>,
16036) -> Option<CodeUnit> {
16037 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16038 return Some(owner);
16039 }
16040 let type_name = normalize_type_text(node_text(type_node, source));
16041 visibility
16042 .resolve_type(file, &type_name)
16043 .filter(CodeUnit::is_class)
16044}
16045
16046pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16047 let mut cursor = node.walk();
16048 node.named_children(&mut cursor).find(|child| {
16049 matches!(
16050 child.kind(),
16051 "type_identifier"
16052 | "primitive_type"
16053 | "qualified_identifier"
16054 | "scoped_type_identifier"
16055 | "struct_specifier"
16056 | "union_specifier"
16057 | "enum_specifier"
16058 )
16059 })
16060}
16061
16062pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16063 visibility: &VisibilityIndex<'_>,
16064 file: &ProjectFile,
16065 source: &str,
16066 declarator: Node<'_>,
16067 type_text: Option<&str>,
16068 bindings: &LocalInferenceEngine<T>,
16069) -> bool {
16070 if !has_ancestor_kind(declarator, "compound_statement") {
16071 return false;
16072 }
16073 if declarator
16074 .child_by_field_name("declarator")
16075 .is_none_or(|declarator| declarator.kind() != "identifier")
16076 {
16077 return false;
16078 }
16079 if !type_text
16080 .and_then(|text| visibility.resolve_type(file, text))
16081 .is_some_and(|unit| unit.is_class())
16082 {
16083 return false;
16084 }
16085 declarator
16086 .child_by_field_name("parameters")
16087 .is_some_and(|parameters| {
16088 constructor_parameters_look_like_expressions(parameters, source, bindings)
16089 })
16090}
16091
16092fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16093 parameters: Node<'_>,
16094 source: &str,
16095 bindings: &LocalInferenceEngine<T>,
16096) -> bool {
16097 let mut cursor = parameters.walk();
16098 parameters.named_children(&mut cursor).any(|parameter| {
16099 !matches!(
16100 parameter.kind(),
16101 "parameter_declaration" | "optional_parameter_declaration"
16102 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16103 })
16104}
16105
16106fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16107 parameter: Node<'_>,
16108 source: &str,
16109 bindings: &LocalInferenceEngine<T>,
16110) -> bool {
16111 let text = node_text(parameter, source).trim();
16112 if text
16113 .chars()
16114 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16115 && bindings.is_shadowed(text)
16116 {
16117 return true;
16118 }
16119
16120 let Some(base) = parameter
16121 .child_by_field_name("type")
16122 .filter(|base| base.kind() == "type_identifier")
16123 else {
16124 return false;
16125 };
16126 let Some(subscript) = parameter
16127 .child_by_field_name("declarator")
16128 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16129 else {
16130 return false;
16131 };
16132 subscript.child_by_field_name("size").is_some()
16133 && bindings.is_shadowed(node_text(base, source).trim())
16134}
16135
16136pub fn is_declaration_name(node: Node<'_>) -> bool {
16137 let Some(parent) = node.parent() else {
16138 return false;
16139 };
16140 if parent
16141 .child_by_field_name("name")
16142 .is_some_and(|name| same_node(name, node))
16143 {
16144 if matches!(
16145 parent.kind(),
16146 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16147 ) {
16148 return cpp_tag_specifier_declares_name(parent);
16149 }
16150 if matches!(
16151 parent.kind(),
16152 "namespace_definition"
16153 | "namespace_alias_definition"
16154 | "alias_declaration"
16155 | "enumerator"
16156 ) {
16157 return true;
16158 }
16159 }
16160
16161 let mut current = Some(parent);
16162 while let Some(ancestor) = current {
16163 let type_definition = ancestor.kind() == "type_definition";
16164 let mut child_cursor = ancestor.walk();
16165 if ancestor.named_children(&mut child_cursor).any(|child| {
16166 declaration_declarator(ancestor, child).is_some_and(|declarator| {
16167 declarator_name_path_contains(declarator, node, type_definition)
16168 })
16169 }) {
16170 return true;
16171 }
16172 if matches!(
16173 ancestor.kind(),
16174 "declaration"
16175 | "field_declaration"
16176 | "parameter_declaration"
16177 | "optional_parameter_declaration"
16178 | "function_definition"
16179 | "type_definition"
16180 | "alias_declaration"
16181 | "template_instantiation"
16182 | "class_specifier"
16183 | "struct_specifier"
16184 | "union_specifier"
16185 | "enum_specifier"
16186 ) {
16187 return false;
16188 }
16189 current = ancestor.parent();
16190 }
16191 false
16192}
16193
16194pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16203 if !matches!(
16204 node.kind(),
16205 "qualified_identifier" | "scoped_type_identifier"
16206 ) {
16207 return false;
16208 }
16209 let Some(declaration) = node
16210 .parent()
16211 .filter(|parent| parent.kind() == "declaration")
16212 else {
16213 return false;
16214 };
16215 if declaration.child_by_field_name("declarator") != Some(node)
16216 || !declaration
16217 .child_by_field_name("type")
16218 .is_some_and(|friend| {
16219 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16220 })
16221 {
16222 return false;
16223 }
16224 let mut cursor = declaration.walk();
16225 let mut errors = declaration
16226 .named_children(&mut cursor)
16227 .filter(|child| child.kind() == "ERROR");
16228 let Some(error) = errors.next() else {
16229 return false;
16230 };
16231 errors.next().is_none()
16232 && error.named_child_count() == 1
16233 && error.named_child(0).is_some_and(|class| {
16234 class.kind() == "identifier" && node_text(class, source) == "class"
16235 })
16236}
16237
16238pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16239 if !matches!(node.kind(), "identifier" | "field_identifier") {
16240 return false;
16241 }
16242 if let Some(parent) = node.parent() {
16243 if parent.kind() == "call_expression"
16244 && parent.child_by_field_name("function") == Some(node)
16245 {
16246 return false;
16247 }
16248 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16249 && parent.child_by_field_name("label") == Some(node)
16250 {
16251 return false;
16252 }
16253 }
16254 if is_declaration_name(node) {
16255 return false;
16256 }
16257 let mut current = node.parent();
16258 while let Some(ancestor) = current {
16259 match ancestor.kind() {
16260 "preproc_ifdef" | "preproc_ifndef" => {
16261 if ancestor
16262 .child_by_field_name("name")
16263 .is_some_and(|name| node_range_contains(name, node))
16264 {
16265 return false;
16266 }
16267 }
16268 "preproc_if" | "preproc_elif" => {
16269 if ancestor
16270 .child_by_field_name("condition")
16271 .is_some_and(|condition| node_range_contains(condition, node))
16272 {
16273 return false;
16274 }
16275 }
16276 "preproc_else" => {}
16277 kind if kind.starts_with("preproc_") => return false,
16278 _ => {}
16279 }
16280 if matches!(
16281 ancestor.kind(),
16282 "translation_unit" | "function_definition" | "compound_statement"
16283 ) {
16284 break;
16285 }
16286 current = ancestor.parent();
16287 }
16288 true
16289}
16290
16291fn recovered_c_reference_node(
16292 visibility: &VisibilityIndex<'_>,
16293 file: &ProjectFile,
16294 node: Node<'_>,
16295 source: &str,
16296) -> bool {
16297 if node.start_byte() >= node.end_byte()
16298 || node.is_error()
16299 || node.is_missing()
16300 || !matches!(
16301 node.kind(),
16302 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16303 )
16304 || recovered_c_macro_binding_role(node)
16305 || recovered_c_label_role(node)
16306 {
16307 return false;
16308 }
16309 let name = node_text(node, source);
16317 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16318 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16319 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16320 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16321 if is_declaration_name(node)
16322 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16323 && !recovered_function_call
16324 && !recovered_macro_call
16325 && !recovered_parenthesized_reference
16326 {
16327 return false;
16328 }
16329
16330 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16331 return true;
16332 }
16333 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16334 return true;
16335 }
16336 if recovered_parenthesized_reference {
16337 return true;
16338 }
16339 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16340 if recovered_function_call {
16341 return true;
16342 }
16343 return visibility
16344 .visible_identifier_candidates(file, name)
16345 .any(|candidate| candidate.is_class() || candidate.is_module());
16346 }
16347 let visible = visibility
16348 .visible_identifier_candidates(file, name)
16349 .next()
16350 .is_some();
16351 visible
16352 && (recovered_c_reference_anchor(node)
16353 || recovered_c_error_expression_leaf(node)
16354 || recovered_function_call)
16355}
16356
16357fn push_recovered_c_range(
16358 ranges: &mut Vec<Range>,
16359 seen: &mut HashSet<(usize, usize)>,
16360 start_byte: usize,
16361 end_byte: usize,
16362 node: Node<'_>,
16363 limit: usize,
16364) -> bool {
16365 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16366 return true;
16367 }
16368 if ranges.len() >= limit {
16369 return false;
16370 }
16371 ranges.push(Range {
16372 start_byte,
16373 end_byte,
16374 start_line: node.start_position().row,
16375 end_line: node.end_position().row,
16376 });
16377 true
16378}
16379
16380fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16385 let mut current = node.parent();
16386 while let Some(parent) = current {
16387 if parent.is_error() {
16388 let Some(anchor) = parent.parent() else {
16389 return false;
16390 };
16391 return anchor.kind().ends_with("_expression")
16392 || matches!(
16393 anchor.kind(),
16394 "argument_list"
16395 | "return_statement"
16396 | "expression_statement"
16397 | "case_statement"
16398 | "initializer_list"
16399 | "field_designator"
16400 | "enumerator"
16401 );
16402 }
16403 if matches!(
16404 parent.kind(),
16405 "translation_unit" | "function_definition" | "compound_statement"
16406 ) {
16407 return false;
16408 }
16409 current = parent.parent();
16410 }
16411 false
16412}
16413
16414fn recovered_c_function_call(
16421 visibility: &VisibilityIndex<'_>,
16422 file: &ProjectFile,
16423 node: Node<'_>,
16424 name: &str,
16425) -> bool {
16426 if !matches!(
16427 node.kind(),
16428 "identifier" | "field_identifier" | "type_identifier"
16429 ) {
16430 return false;
16431 }
16432 let error_call_prefix = node.parent().is_some_and(|error| {
16437 error.is_error()
16438 && error
16439 .parent()
16440 .is_some_and(|parent| parent.kind() == "compound_statement")
16441 }) && node
16442 .prev_sibling()
16443 .is_none_or(|previous| previous.kind() == ";")
16444 && node.next_sibling().is_some_and(|open| {
16445 open.kind() == "("
16446 && open.next_named_sibling().is_some_and(|argument| {
16447 argument.kind() == "parameter_declaration" && !argument.has_error()
16448 })
16449 });
16450 (error_call_prefix || recovered_c_function_declarator_invocation(node))
16451 && visibility
16452 .visible_identifier_candidates(file, name)
16453 .any(CodeUnit::is_function)
16454}
16455
16456pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16464 if declarator.kind() != "function_declarator" || !declarator.has_error() {
16465 return None;
16466 }
16467 let definition = declarator.parent().filter(|parent| {
16468 parent.kind() == "function_definition"
16469 && parent.child_by_field_name("declarator") == Some(declarator)
16470 })?;
16471 if definition.child_by_field_name("type").is_some()
16472 || definition.named_child(0) != Some(declarator)
16473 || definition.child_by_field_name("body").is_none()
16474 || definition.parent()?.kind() != "compound_statement"
16475 {
16476 return None;
16477 }
16478 Some(definition)
16479}
16480
16481fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16491 let mut current = if node.parent().is_some_and(|parent| {
16496 parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16497 }) {
16498 node.parent().expect("checked macro_type_specifier parent")
16499 } else {
16500 let mut function_declarator = if node.parent().is_some_and(|parent| {
16501 parent.kind() == "function_declarator"
16502 && parent.child_by_field_name("declarator") == Some(node)
16503 }) {
16504 node.parent().expect("checked function declarator parent")
16505 } else {
16506 let Some(parameter) = node.parent().filter(|parent| {
16507 parent.kind() == "parameter_declaration"
16508 && parent.child_by_field_name("type") == Some(node)
16509 }) else {
16510 return false;
16511 };
16512 if !parameter
16513 .child_by_field_name("declarator")
16514 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16515 {
16516 return false;
16517 }
16518 let Some(parameters) = parameter
16519 .parent()
16520 .filter(|parent| parent.kind() == "parameter_list")
16521 else {
16522 return false;
16523 };
16524 let Some(function_declarator) = parameters
16525 .parent()
16526 .filter(|parent| parent.kind() == "function_declarator")
16527 else {
16528 return false;
16529 };
16530 function_declarator
16531 };
16532
16533 while let Some(parent) = function_declarator.parent().filter(|parent| {
16536 parent.kind() == "function_declarator"
16537 && parent.child_by_field_name("declarator") == Some(function_declarator)
16538 }) {
16539 function_declarator = parent;
16540 }
16541 let Some(current) = function_declarator.parent().filter(|parent| {
16546 parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
16547 }) else {
16548 return false;
16549 };
16550 current
16551 };
16552 loop {
16553 let Some(parent) = current.parent() else {
16554 return false;
16555 };
16556 if matches!(
16557 parent.kind(),
16558 "translation_unit"
16559 | "compound_statement"
16560 | "preproc_if"
16561 | "preproc_ifdef"
16562 | "preproc_ifndef"
16563 | "preproc_else"
16564 | "preproc_elif"
16565 ) {
16566 return true;
16567 }
16568 if parent.kind() == "function_definition"
16569 && parent.child_by_field_name("declarator") == Some(current)
16570 && parent.named_child(0) == Some(current)
16571 && parent.child_by_field_name("body").is_some()
16572 {
16573 return true;
16574 }
16575 if parent.is_error()
16576 || matches!(
16577 parent.kind(),
16578 "parameter_declaration"
16579 | "parameter_list"
16580 | "function_declarator"
16581 | "abstract_function_declarator"
16582 | "parenthesized_declarator"
16583 )
16584 {
16585 current = parent;
16586 continue;
16587 }
16588 return false;
16589 }
16590}
16591
16592fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16598 if node
16604 .parent()
16605 .is_some_and(|parent| parent.kind() == "type_descriptor")
16606 {
16607 let Some(type_descriptor) = node.parent() else {
16608 return false;
16609 };
16610 let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
16611 return false;
16612 };
16613 if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
16614 return false;
16615 }
16616 let Some(dependent) = error
16617 .parent()
16618 .and_then(|parent| parent.parent())
16619 .filter(|parent| parent.kind() == "dependent_type")
16620 else {
16621 return false;
16622 };
16623 return dependent
16624 .child(0)
16625 .is_some_and(|keyword| keyword.kind() == "typename");
16626 }
16627 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16628 return false;
16629 };
16630 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16631 return false;
16632 }
16633 let Some(declarator) = error
16634 .parent()
16635 .filter(|parent| parent.kind() == "parenthesized_declarator")
16636 else {
16637 return false;
16638 };
16639 let Some(declaration) = declarator
16640 .parent()
16641 .filter(|parent| parent.kind() == "declaration")
16642 else {
16643 return false;
16644 };
16645 if declaration.child_by_field_name("declarator") != Some(declarator) {
16646 return false;
16647 }
16648 let Some(type_node) = declaration.child_by_field_name("type") else {
16649 return false;
16650 };
16651 type_node.kind() == "dependent_type"
16652 && type_node
16653 .child(0)
16654 .is_some_and(|keyword| keyword.kind() == "typename")
16655}
16656
16657fn recovered_c_explicit_assignment_callee(
16658 visibility: &VisibilityIndex<'_>,
16659 file: &ProjectFile,
16660 node: Node<'_>,
16661 name: &str,
16662) -> bool {
16663 let mut current = node;
16664 let error = loop {
16665 let Some(parent) = current.parent() else {
16666 return false;
16667 };
16668 if parent.is_error() {
16669 break parent;
16670 }
16671 current = parent;
16672 };
16673 let mut cursor = error.walk();
16674 let explicit_recovery_precedes_callee = error
16675 .named_children(&mut cursor)
16676 .take_while(|child| child.start_byte() < node.start_byte())
16677 .any(|child| child.kind() == "explicit_function_specifier");
16678 if !explicit_recovery_precedes_callee {
16679 return false;
16680 }
16681 visibility
16682 .visible_identifier_candidates(file, name)
16683 .any(CodeUnit::is_function)
16684}
16685
16686fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16687 while let Some(parent) = node.parent() {
16688 if matches!(
16689 parent.kind(),
16690 "preproc_def" | "preproc_function_def" | "preproc_params"
16691 ) {
16692 return true;
16693 }
16694 if parent.is_error()
16695 || matches!(
16696 parent.kind(),
16697 "translation_unit" | "function_definition" | "compound_statement"
16698 )
16699 {
16700 return false;
16701 }
16702 node = parent;
16703 }
16704 false
16705}
16706
16707fn recovered_c_label_role(node: Node<'_>) -> bool {
16708 node.parent().is_some_and(|parent| {
16709 matches!(parent.kind(), "labeled_statement" | "goto_statement")
16710 && parent.child_by_field_name("label") == Some(node)
16711 })
16712}
16713
16714fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16715 while let Some(parent) = node.parent() {
16716 if parent.is_error() {
16717 return false;
16718 }
16719 if parent.kind() == "optional_parameter_declaration"
16724 && parent
16725 .child_by_field_name("default_value")
16726 .is_some_and(|value| node_range_contains(value, node))
16727 {
16728 return true;
16729 }
16730 if parent.kind().ends_with("_expression")
16731 || matches!(
16732 parent.kind(),
16733 "argument_list"
16734 | "return_statement"
16735 | "expression_statement"
16736 | "case_statement"
16737 | "initializer_list"
16738 | "init_declarator"
16739 | "array_declarator"
16740 | "field_designator"
16741 | "enumerator"
16742 )
16743 {
16744 return true;
16745 }
16746 if matches!(
16747 parent.kind(),
16748 "translation_unit"
16749 | "function_definition"
16750 | "compound_statement"
16751 | "declaration"
16752 | "field_declaration"
16753 | "parameter_declaration"
16754 ) {
16755 return false;
16756 }
16757 node = parent;
16758 }
16759 false
16760}
16761
16762pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16770 let mut current = parameter.parent();
16771 while let Some(ancestor) = current {
16772 if ancestor.kind() == "lambda_expression" {
16773 return ancestor
16774 .child_by_field_name("declarator")
16775 .is_some_and(|declarator| {
16776 declarator.start_byte() <= parameter.start_byte()
16777 && parameter.end_byte() <= declarator.end_byte()
16778 });
16779 }
16780 if ancestor.kind() == "function_definition" {
16781 return ancestor
16782 .child_by_field_name("declarator")
16783 .is_some_and(|declarator| {
16784 declarator.start_byte() <= parameter.start_byte()
16785 && parameter.end_byte() <= declarator.end_byte()
16786 });
16787 }
16788 current = ancestor.parent();
16789 }
16790 false
16791}
16792
16793pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16794 let mut current = node.parent();
16795 while let Some(ancestor) = current {
16796 if matches!(
16797 ancestor.kind(),
16798 "parameter_declaration" | "optional_parameter_declaration"
16799 ) {
16800 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16801 }
16802 if matches!(
16803 ancestor.kind(),
16804 "function_definition" | "lambda_expression" | "compound_statement"
16805 ) {
16806 return false;
16807 }
16808 current = ancestor.parent();
16809 }
16810 false
16811}
16812
16813pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16817 specifier: Node<'_>,
16818) -> impl Iterator<Item = Node<'_>> {
16819 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16820 !matches!(
16821 ancestor.kind(),
16822 "type_descriptor"
16823 | "parameter_declaration"
16824 | "optional_parameter_declaration"
16825 | "template_argument_list"
16826 | "cast_expression"
16827 )
16828 })
16829}
16830
16831pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16832 if specifier.child_by_field_name("body").is_some() {
16833 return true;
16834 }
16835 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16836 match ancestor.kind() {
16837 "declaration" | "field_declaration" => {
16838 let mut cursor = ancestor.walk();
16839 return ancestor
16840 .children_by_field_name("declarator", &mut cursor)
16841 .next()
16842 .is_none();
16843 }
16844 "translation_unit" => return true,
16845 _ => {}
16846 }
16847 }
16848 false
16849}
16850
16851pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16852 match node.kind() {
16853 "identifier"
16854 | "field_identifier"
16855 | "qualified_identifier"
16856 | "scoped_identifier"
16857 | "operator_name"
16858 | "destructor_name"
16859 | "literal_operator_name" => Some(node),
16860 "reference_declarator" | "parenthesized_declarator" => {
16861 node.named_child(0).and_then(declarator_name_node)
16862 }
16863 _ => node
16864 .child_by_field_name("declarator")
16865 .or_else(|| node.child_by_field_name("name"))
16866 .or_else(|| node.child_by_field_name("field"))
16867 .and_then(declarator_name_node),
16868 }
16869}
16870
16871pub fn declarator_path_contains_before_name(
16873 declarator: Node<'_>,
16874 name: Node<'_>,
16875 candidate: Node<'_>,
16876) -> bool {
16877 let mut current = Some(declarator);
16878 while let Some(node) = current {
16879 if same_node(node, candidate) {
16880 return true;
16881 }
16882 if same_node(node, name) {
16883 return false;
16884 }
16885 current = node
16886 .child_by_field_name("declarator")
16887 .or_else(|| node.child_by_field_name("name"))
16888 .or_else(|| node.child_by_field_name("field"));
16889 }
16890 false
16891}
16892
16893fn declarator_name_path_contains(
16894 declarator: Node<'_>,
16895 candidate: Node<'_>,
16896 allow_type_identifier: bool,
16897) -> bool {
16898 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16899 return false;
16900 };
16901 declarator_path_contains_before_name(declarator, name, candidate)
16902}
16903
16904fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16905 match node.kind() {
16906 "identifier"
16907 | "field_identifier"
16908 | "operator_name"
16909 | "destructor_name"
16910 | "literal_operator_name" => Some(node),
16911 "type_identifier" if allow_type_identifier => Some(node),
16912 _ => node
16913 .child_by_field_name("declarator")
16914 .or_else(|| node.child_by_field_name("name"))
16915 .or_else(|| node.child_by_field_name("field"))
16916 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16917 }
16918}
16919
16920pub fn is_nested_type_node(node: Node<'_>) -> bool {
16923 node.parent().is_some_and(|parent| {
16924 matches!(
16925 parent.kind(),
16926 "qualified_identifier" | "scoped_type_identifier" | "template_type"
16927 )
16928 })
16929}
16930
16931pub struct OutOfLineMemberDefinitionOwners<'tree> {
16932 pub owners: Vec<(Node<'tree>, CodeUnit)>,
16933 innermost: Option<(Node<'tree>, CodeUnit)>,
16934}
16935
16936impl OutOfLineMemberDefinitionOwners<'_> {
16937 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16938 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16939 }
16940}
16941
16942pub struct QualifiedOwnerComponents<'tree> {
16943 pub nodes: Vec<Node<'tree>>,
16944 pub names: Vec<String>,
16945 pub global: bool,
16946}
16947
16948pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16953 let mut stack = vec![node];
16954 let mut found_separator = false;
16955 while let Some(current) = stack.pop() {
16956 if !matches!(
16957 current.kind(),
16958 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16959 ) {
16960 continue;
16961 }
16962 let mut current_has_separator = false;
16963 for child in children_iter(current) {
16964 if child.kind() == "::" {
16965 if child.is_missing() {
16966 return false;
16967 }
16968 current_has_separator = true;
16969 found_separator = true;
16970 }
16971 }
16972 if !current_has_separator {
16973 return false;
16974 }
16975 for field in ["scope", "name"] {
16976 if let Some(child) = current.child_by_field_name(field)
16977 && matches!(
16978 child.kind(),
16979 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16980 )
16981 {
16982 stack.push(child);
16983 }
16984 }
16985 }
16986 found_separator
16987}
16988
16989pub fn qualified_owner_components<'tree>(
16990 node: Node<'tree>,
16991 source: &str,
16992) -> Option<QualifiedOwnerComponents<'tree>> {
16993 if !qualified_name_has_concrete_scope_separators(node) {
16994 return None;
16995 }
16996 let mut nodes = cpp_name_component_nodes(node)?;
16997 nodes.pop()?;
16998 if nodes.is_empty() {
16999 return None;
17000 }
17001 let names = nodes
17002 .iter()
17003 .map(|component| node_text(*component, source).to_string())
17004 .collect();
17005 Some(QualifiedOwnerComponents {
17006 nodes,
17007 names,
17008 global: is_globally_qualified_cpp_name(node),
17009 })
17010}
17011
17012pub fn out_of_line_member_definition_owner<'tree>(
17013 analyzer: &CppGraphSource<'_>,
17014 visibility: &VisibilityIndex<'_>,
17015 file: &ProjectFile,
17016 source: &str,
17017 node: Node<'tree>,
17018) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17019 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17020 || !has_ancestor_kind(node, "function_definition")
17021 || !is_function_declarator_name_root(node)
17022 {
17023 return None;
17024 }
17025 let qualified = qualified_owner_components(node, source)?;
17026 let lexical_scope = enclosing_namespace_components(node, source)?;
17027 let mut owners = Vec::new();
17028 let mut innermost = None;
17029
17030 for component_count in 1..=qualified.names.len() {
17031 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17032 .resolve_type_components_lexically(
17033 analyzer,
17034 file,
17035 &qualified.names[..component_count],
17036 qualified.global,
17037 &lexical_scope,
17038 )
17039 && !owners
17040 .iter()
17041 .any(|(_, existing)| same_visible_symbol(existing, &unit))
17042 {
17043 if component_count == qualified.names.len() {
17044 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17045 }
17046 owners.push((qualified.nodes[component_count - 1], unit));
17047 }
17048 }
17049
17050 if innermost.is_none() {
17060 let indexed_owner_components = visibility
17061 .indexed_enclosing_owner_scope(analyzer, file, node)
17062 .or_else(|| {
17063 if qualified.names.len() <= 1 {
17068 return None;
17069 }
17070 let range = Range {
17071 start_byte: node.start_byte(),
17072 end_byte: node.end_byte(),
17073 start_line: node.start_position().row,
17074 end_line: node.end_position().row,
17075 };
17076 let start = analyzer.enclosing_code_unit(file, &range)?;
17077 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17078 brokk_bifrost_core::analyzer::Language::Cpp,
17079 &cpp_name_for(&start),
17080 );
17081 components.pop();
17082 Some(components)
17083 });
17084 if let Some(indexed_owner_components) = indexed_owner_components
17085 && indexed_owner_components.len() > qualified.names.len()
17086 && indexed_owner_components.ends_with(&qualified.names)
17087 && indexed_namespace_path_is_recoverable(
17088 &lexical_scope,
17089 &indexed_owner_components,
17090 qualified.names.len(),
17091 )
17092 && (qualified.names.len() > 1 || !qualified.global)
17097 {
17098 let namespace_count = indexed_owner_components.len() - qualified.names.len();
17099 for component_count in 1..=qualified.names.len() {
17100 let expected = &indexed_owner_components[..namespace_count + component_count];
17101 let owner_node = qualified.nodes[component_count - 1];
17102 for owner in visibility
17103 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17104 .filter(|candidate| candidate.is_class())
17105 .filter(|candidate| {
17106 canonical_cpp_scope_components(candidate) == expected
17107 && visibility.external_type_candidate_visible_in_context(
17108 analyzer, file, candidate, node,
17109 )
17110 })
17111 {
17112 if component_count == qualified.names.len() && innermost.is_none() {
17113 innermost = Some((owner_node, owner.clone()));
17114 }
17115 if !owners
17116 .iter()
17117 .any(|(_, existing)| same_symbol(existing, owner))
17118 {
17119 owners.push((owner_node, owner.clone()));
17120 }
17121 }
17122 }
17123 }
17124 }
17125 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17126}
17127
17128fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17129 let mut current = node;
17130 while let Some(parent) = current.parent() {
17131 if parent.kind() == "function_declarator" {
17132 return parent.child_by_field_name("declarator") == Some(current);
17133 }
17134 if matches!(
17135 parent.kind(),
17136 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17137 ) && parent.child_by_field_name("declarator") == Some(current)
17138 {
17139 current = parent;
17140 continue;
17141 }
17142 return false;
17143 }
17144 false
17145}
17146
17147pub fn append_cpp_name_components(
17148 node: Node<'_>,
17149 source: &str,
17150 out: &mut Vec<String>,
17151) -> Option<()> {
17152 out.extend(
17153 cpp_name_component_nodes(node)?
17154 .into_iter()
17155 .map(|component| node_text(component, source).to_string()),
17156 );
17157 Some(())
17158}
17159
17160fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17163 cpp_name_component_nodes(node).map(|components| {
17164 components
17165 .into_iter()
17166 .map(|component| {
17167 (
17168 node_text(component, source).to_string(),
17169 node_range(component),
17170 )
17171 })
17172 .collect()
17173 })
17174}
17175
17176pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17177 let mut components = Vec::new();
17178 append_cpp_name_components(node, source, &mut components)?;
17179 Some(components)
17180}
17181
17182pub fn unique_macro_replacement_type_candidate(
17191 analyzer: &CppGraphSource<'_>,
17192 visibility: &VisibilityIndex<'_>,
17193 file: &ProjectFile,
17194 components: &[String],
17195) -> Option<CodeUnit> {
17196 let terminal = components.last()?;
17197 let mut candidates = Vec::new();
17198 for candidate in visibility
17199 .visible_identifier_candidates(file, terminal)
17200 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17201 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17202 {
17203 if !candidates
17204 .iter()
17205 .any(|existing| same_logical_symbol(existing, candidate))
17206 {
17207 candidates.push(candidate.clone());
17208 }
17209 }
17210 (candidates.len() == 1).then(|| candidates.remove(0))
17211}
17212
17213pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17217 qualified == scope
17218 || qualified
17219 .strip_suffix(scope)
17220 .is_some_and(|prefix| prefix.ends_with("::"))
17221}
17222
17223pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17228 let Some(type_descriptor) = node.parent() else {
17229 return false;
17230 };
17231 if type_descriptor.kind() != "type_descriptor"
17232 || type_descriptor.child_by_field_name("type") != Some(node)
17233 {
17234 return false;
17235 }
17236 let Some(arguments) = type_descriptor.parent() else {
17237 return false;
17238 };
17239 if arguments.kind() != "template_argument_list" {
17240 return false;
17241 }
17242 arguments.parent().is_some_and(|parent| {
17243 matches!(parent.kind(), "template_type" | "template_function")
17244 && parent.child_by_field_name("arguments") == Some(arguments)
17245 })
17246}
17247
17248pub fn cpp_template_reference_arguments(
17249 mut node: Node<'_>,
17250 source: &str,
17251) -> Option<Vec<CppTemplateExpression>> {
17252 loop {
17253 match node.kind() {
17254 "template_type" | "template_function" => {
17255 let arguments = node.child_by_field_name("arguments")?;
17256 let mut cursor = arguments.walk();
17257 return Some(
17258 arguments
17259 .named_children(&mut cursor)
17260 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17261 .map(|argument| CppTemplateExpression {
17262 text: normalize_cpp_whitespace(node_text(argument, source)),
17263 term: cpp_template_term(
17265 argument,
17266 source,
17267 &[],
17268 &ParentIndex::unindexed(),
17269 ),
17270 })
17271 .collect(),
17272 );
17273 }
17274 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17275 node = node
17276 .child_by_field_name("name")
17277 .or_else(|| node.child_by_field_name("type"))?;
17278 }
17279 _ => return None,
17280 }
17281 }
17282}
17283
17284fn cpp_reconcile_primary_template_parameters(
17285 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17286 preferred: &CodeUnit,
17287) -> Option<Vec<CppTemplateParameterMetadata>> {
17288 let canonical = candidates
17289 .iter()
17290 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17291 let mut merged = canonical
17292 .parameters
17293 .iter()
17294 .map(|parameter| CppTemplateParameterMetadata {
17295 name: parameter.name.clone(),
17296 kind: parameter.kind,
17297 variadic: parameter.variadic,
17298 default: None,
17299 })
17300 .collect::<Vec<_>>();
17301
17302 for (_, metadata) in candidates {
17303 if metadata.parameters.len() != merged.len() {
17304 return None;
17305 }
17306 let rename_bindings = metadata
17307 .parameters
17308 .iter()
17309 .zip(&merged)
17310 .map(|(parameter, canonical)| {
17311 (
17312 parameter.name.clone(),
17313 CppTemplateTerm::Parameter(canonical.name.clone()),
17314 )
17315 })
17316 .collect::<HashMap<_, _>>();
17317 for ((parameter, canonical), merged_parameter) in metadata
17318 .parameters
17319 .iter()
17320 .zip(&canonical.parameters)
17321 .zip(&mut merged)
17322 {
17323 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17324 return None;
17325 }
17326 let Some(default) = ¶meter.default else {
17327 continue;
17328 };
17329 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17330 if let Some(existing) = &merged_parameter.default {
17331 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17332 return None;
17333 }
17334 } else {
17335 merged_parameter.default = Some(CppTemplateExpression {
17336 text: default.text.clone(),
17337 term: normalized_term,
17338 });
17339 }
17340 }
17341 }
17342 Some(merged)
17343}
17344
17345pub fn cpp_bind_template_arguments(
17346 parameters: &[CppTemplateParameterMetadata],
17347 explicit_arguments: &[CppTemplateExpression],
17348) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17349 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17350 if variadic_index.is_some_and(|index| {
17351 index + 1 != parameters.len()
17352 || parameters[index + 1..]
17353 .iter()
17354 .any(|parameter| parameter.variadic)
17355 }) {
17356 return None;
17357 }
17358 let fixed_count = variadic_index.unwrap_or(parameters.len());
17359 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17360 return None;
17361 }
17362 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17363 let mut expanded = explicit_arguments[..explicit_fixed_count]
17364 .iter()
17365 .map(cpp_clone_template_expression_iterative)
17366 .collect::<Vec<_>>();
17367 let mut bindings = HashMap::default();
17368 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17369 bindings.insert(
17370 parameter.name.clone(),
17371 cpp_clone_template_term_iterative(&argument.term),
17372 );
17373 }
17374 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
17375 let default = parameter.default.as_ref()?;
17376 let term = cpp_substitute_template_term(&default.term, &bindings)?;
17377 bindings.insert(parameter.name.clone(), term.clone());
17378 expanded.push(CppTemplateExpression {
17379 text: default.text.clone(),
17380 term,
17381 });
17382 }
17383 if let Some(index) = variadic_index {
17384 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17385 expanded.extend(
17386 packed_arguments
17387 .iter()
17388 .map(cpp_clone_template_expression_iterative),
17389 );
17390 bindings.insert(
17391 parameters[index].name.clone(),
17392 CppTemplateTerm::Node {
17393 kind: "parameter_pack".to_string(),
17394 children: packed_arguments
17395 .iter()
17396 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17397 .collect(),
17398 },
17399 );
17400 }
17401 Some((expanded, bindings))
17402}
17403
17404fn cpp_specialization_matches(
17405 metadata: &CppTemplateMetadata,
17406 arguments: &[CppTemplateExpression],
17407) -> bool {
17408 cpp_specialization_terms_match(
17409 &metadata.parameters,
17410 &metadata.specialization_arguments,
17411 arguments,
17412 )
17413}
17414
17415fn cpp_specialization_terms_match(
17416 parameters: &[CppTemplateParameterMetadata],
17417 patterns: &[CppTemplateExpression],
17418 arguments: &[CppTemplateExpression],
17419) -> bool {
17420 if patterns.len() != arguments.len() {
17421 return false;
17422 }
17423 let parameter_names = parameters
17424 .iter()
17425 .map(|parameter| parameter.name.as_str())
17426 .collect::<HashSet<_>>();
17427 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17428 patterns.iter().zip(arguments).all(|(pattern, argument)| {
17429 cpp_unify_template_term(
17430 &pattern.term,
17431 &argument.term,
17432 ¶meter_names,
17433 &mut bindings,
17434 )
17435 })
17436}
17437
17438fn cpp_specialization_more_specialized(
17439 candidate: &CppTemplateMetadata,
17440 other: &CppTemplateMetadata,
17441) -> bool {
17442 cpp_specialization_pattern_accepts(other, candidate)
17443 && !cpp_specialization_pattern_accepts(candidate, other)
17444}
17445
17446fn cpp_specialization_pattern_accepts(
17447 broader: &CppTemplateMetadata,
17448 narrower: &CppTemplateMetadata,
17449) -> bool {
17450 cpp_specialization_terms_match(
17451 &broader.parameters,
17452 &broader.specialization_arguments,
17453 &narrower.specialization_arguments,
17454 )
17455}
17456
17457pub fn cpp_substitute_template_term(
17458 term: &CppTemplateTerm,
17459 bindings: &HashMap<String, CppTemplateTerm>,
17460) -> Option<CppTemplateTerm> {
17461 enum Work<'a> {
17462 Visit(&'a CppTemplateTerm),
17463 Build { kind: String, child_count: usize },
17464 }
17465
17466 let mut work = vec![Work::Visit(term)];
17467 let mut substituted = Vec::new();
17468 while let Some(next) = work.pop() {
17469 match next {
17470 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17471 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17472 }
17473 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17474 substituted.push(CppTemplateTerm::Atom {
17475 kind: kind.clone(),
17476 text: text.clone(),
17477 });
17478 }
17479 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17480 work.push(Work::Build {
17481 kind: kind.clone(),
17482 child_count: children.len(),
17483 });
17484 work.extend(children.iter().rev().map(Work::Visit));
17485 }
17486 Work::Build { kind, child_count } => {
17487 let children = substituted.split_off(substituted.len() - child_count);
17488 substituted.push(CppTemplateTerm::Node { kind, children });
17489 }
17490 }
17491 }
17492 substituted.pop()
17493}
17494
17495pub fn cpp_substitute_template_arguments(
17496 arguments: &[CppTemplateExpression],
17497 bindings: &HashMap<String, CppTemplateTerm>,
17498) -> Option<Vec<CppTemplateExpression>> {
17499 let mut substituted = Vec::new();
17500 for argument in arguments {
17501 let CppTemplateTerm::Node { kind, children } = &argument.term else {
17502 substituted.push(CppTemplateExpression {
17503 text: argument.text.clone(),
17504 term: cpp_substitute_template_term(&argument.term, bindings)?,
17505 });
17506 continue;
17507 };
17508 if kind != "parameter_pack_expansion" {
17509 substituted.push(CppTemplateExpression {
17510 text: argument.text.clone(),
17511 term: cpp_substitute_template_term(&argument.term, bindings)?,
17512 });
17513 continue;
17514 }
17515 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17516 return None;
17517 };
17518 if ellipsis != "..." {
17519 return None;
17520 }
17521
17522 let mut pack_names = Vec::new();
17523 let mut work = vec![pattern];
17524 while let Some(term) = work.pop() {
17525 match term {
17526 CppTemplateTerm::Parameter(name)
17527 if matches!(
17528 bindings.get(name),
17529 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
17530 ) =>
17531 {
17532 if !pack_names.contains(name) {
17533 pack_names.push(name.clone());
17534 }
17535 }
17536 CppTemplateTerm::Node { children, .. } => work.extend(children),
17537 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
17538 }
17539 }
17540 let first_pack = pack_names.first()?;
17541 let CppTemplateTerm::Node {
17542 children: first_elements,
17543 ..
17544 } = bindings.get(first_pack)?
17545 else {
17546 return None;
17547 };
17548 let pack_len = first_elements.len();
17549 for pack_name in &pack_names {
17550 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17551 return None;
17552 };
17553 if children.len() != pack_len {
17554 return None;
17555 }
17556 }
17557 for index in 0..pack_len {
17558 let mut element_bindings = bindings.clone();
17559 for pack_name in &pack_names {
17560 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17561 return None;
17562 };
17563 element_bindings.insert(
17564 pack_name.clone(),
17565 cpp_clone_template_term_iterative(&children[index]),
17566 );
17567 }
17568 substituted.push(CppTemplateExpression {
17569 text: argument.text.clone(),
17570 term: cpp_substitute_template_term(pattern, &element_bindings)?,
17571 });
17572 }
17573 }
17574 Some(substituted)
17575}
17576
17577fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17578 enum Work<'a> {
17579 Visit(&'a CppTemplateTerm),
17580 Build { kind: String, child_count: usize },
17581 }
17582
17583 let mut work = vec![Work::Visit(term)];
17584 let mut cloned = Vec::new();
17585 while let Some(next) = work.pop() {
17586 match next {
17587 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17588 cloned.push(CppTemplateTerm::Parameter(name.clone()));
17589 }
17590 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17591 cloned.push(CppTemplateTerm::Atom {
17592 kind: kind.clone(),
17593 text: text.clone(),
17594 });
17595 }
17596 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17597 work.push(Work::Build {
17598 kind: kind.clone(),
17599 child_count: children.len(),
17600 });
17601 work.extend(children.iter().rev().map(Work::Visit));
17602 }
17603 Work::Build { kind, child_count } => {
17604 let children = cloned.split_off(cloned.len() - child_count);
17605 cloned.push(CppTemplateTerm::Node { kind, children });
17606 }
17607 }
17608 }
17609 cloned
17610 .pop()
17611 .expect("template term traversal emits one root")
17612}
17613
17614fn cpp_clone_template_expression_iterative(
17615 expression: &CppTemplateExpression,
17616) -> CppTemplateExpression {
17617 CppTemplateExpression {
17618 text: expression.text.clone(),
17619 term: cpp_clone_template_term_iterative(&expression.term),
17620 }
17621}
17622
17623pub fn cpp_unify_template_term(
17624 pattern: &CppTemplateTerm,
17625 argument: &CppTemplateTerm,
17626 parameters: &HashSet<&str>,
17627 bindings: &mut HashMap<String, CppTemplateTerm>,
17628) -> bool {
17629 let mut work = vec![(pattern, argument)];
17630 while let Some((pattern, argument)) = work.pop() {
17631 match pattern {
17632 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17633 if let Some(bound) = bindings.get(name) {
17634 if !cpp_template_terms_equal(bound, argument) {
17635 return false;
17636 }
17637 } else {
17638 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17639 }
17640 }
17641 CppTemplateTerm::Atom {
17642 kind: pattern_kind,
17643 text: pattern_text,
17644 } => {
17645 if !matches!(
17646 argument,
17647 CppTemplateTerm::Atom { kind, text }
17648 if kind == pattern_kind && text == pattern_text
17649 ) {
17650 return false;
17651 }
17652 }
17653 CppTemplateTerm::Node {
17654 kind: pattern_kind,
17655 children: pattern_children,
17656 } => {
17657 let CppTemplateTerm::Node { kind, children } = argument else {
17658 return false;
17659 };
17660 if kind != pattern_kind || children.len() != pattern_children.len() {
17661 return false;
17662 }
17663 work.extend(pattern_children.iter().zip(children).rev());
17664 }
17665 CppTemplateTerm::Parameter(_) => return false,
17666 }
17667 }
17668 true
17669}
17670
17671pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17672 let mut work = vec![(left, right)];
17673 while let Some((left, right)) = work.pop() {
17674 match (left, right) {
17675 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17676 if left != right {
17677 return false;
17678 }
17679 }
17680 (
17681 CppTemplateTerm::Atom {
17682 kind: left_kind,
17683 text: left_text,
17684 },
17685 CppTemplateTerm::Atom {
17686 kind: right_kind,
17687 text: right_text,
17688 },
17689 ) => {
17690 if left_kind != right_kind || left_text != right_text {
17691 return false;
17692 }
17693 }
17694 (
17695 CppTemplateTerm::Node {
17696 kind: left_kind,
17697 children: left_children,
17698 },
17699 CppTemplateTerm::Node {
17700 kind: right_kind,
17701 children: right_children,
17702 },
17703 ) => {
17704 if left_kind != right_kind || left_children.len() != right_children.len() {
17705 return false;
17706 }
17707 work.extend(left_children.iter().zip(right_children).rev());
17708 }
17709 _ => return false,
17710 }
17711 }
17712 true
17713}
17714
17715pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17716 let mut components = Vec::new();
17717 let mut stack = vec![node];
17718 while let Some(current) = stack.pop() {
17719 match current.kind() {
17720 "identifier"
17721 | "field_identifier"
17722 | "namespace_identifier"
17723 | "type_identifier"
17724 | "operator_name"
17725 | "destructor_name" => components.push(current),
17726 "template_type" | "template_function" => {
17727 stack.push(current.child_by_field_name("name")?);
17728 }
17729 "dependent_name" => stack.push(current.named_child(0)?),
17730 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17731 stack.push(current.child_by_field_name("name")?);
17732 if let Some(scope) = current.child_by_field_name("scope") {
17733 stack.push(scope);
17734 }
17735 }
17736 "nested_namespace_specifier" => {
17737 for index in (0..current.named_child_count()).rev() {
17738 stack.push(current.named_child(index)?);
17739 }
17740 }
17741 _ => return None,
17742 }
17743 }
17744 Some(components)
17745}
17746
17747pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17748 node.child_by_field_name("scope").is_none()
17749 && node.child(0).is_some_and(|child| child.kind() == "::")
17750}
17751
17752fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17753 let mut namespaces = Vec::new();
17754 for parent in enclosing_cpp_namespace_nodes(node) {
17755 if let Some(name) = parent.child_by_field_name("name") {
17756 let mut components = Vec::new();
17757 append_cpp_name_components(name, source, &mut components)?;
17758 namespaces.push(components);
17759 }
17760 }
17761 Some(namespaces.into_iter().flatten().collect())
17762}
17763
17764fn indexed_namespace_path_is_recoverable(
17775 lexical_scope: &[String],
17776 indexed_owner_scope: &[String],
17777 explicit_owner_component_count: usize,
17778) -> bool {
17779 if lexical_scope.is_empty() {
17780 return explicit_owner_component_count > 1;
17781 }
17782 if lexical_scope.len() >= indexed_owner_scope.len() {
17783 return false;
17784 }
17785 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17786}
17787
17788pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17789
17790pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17796 let mut current = Some(node);
17797 while let Some(candidate) = current {
17798 if candidate.kind() == "declaration" {
17799 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17800 return false;
17801 }
17802 let mut cursor = candidate.walk();
17803 return candidate.named_children(&mut cursor).any(|child| {
17804 child.kind() == "init_declarator"
17805 && child
17806 .child_by_field_name("value")
17807 .is_some_and(|value| value.kind() == "cast_expression")
17808 });
17809 }
17810 current = candidate.parent();
17811 }
17812 false
17813}
17814
17815#[derive(Clone, Copy, PartialEq, Eq)]
17816pub(crate) enum QualifiedAliasReferenceKind {
17817 Ordinary,
17818 ConstructorWithExpressionArgument,
17819 ExhaustiveTemplate,
17820}
17821
17822pub(crate) fn qualified_alias_reference_preserves_target(
17829 node: Node<'_>,
17830 target: &CodeUnit,
17831 analyzer: &CppGraphSource<'_>,
17832 visibility: &VisibilityIndex<'_>,
17833 file: &ProjectFile,
17834 source: &str,
17835) -> Option<QualifiedAliasReferenceKind> {
17836 if !matches!(
17837 node.kind(),
17838 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17839 ) {
17840 return None;
17841 }
17842 let components = cpp_type_name_components(node, source)?;
17843 let name = components.last()?;
17844 analyzer.type_alias_provider().and_then(|provider| {
17845 visibility
17846 .visible_identifier_candidates(file, name)
17847 .find_map(|candidate| {
17848 let proof = provider.is_type_alias(candidate)
17849 && canonical_cpp_scope_components(candidate) == components
17850 && visibility.external_type_candidate_visible_in_context(
17851 analyzer, file, candidate, node,
17852 )
17853 && match cpp_template_reference_arguments(node, source) {
17854 Some(arguments) => visibility.template_alias_arguments_preserve_target(
17855 analyzer, file, candidate, &arguments, target,
17856 ),
17857 None => visibility.structured_alias_primary_preserves_target(
17858 analyzer, file, candidate, target,
17859 ),
17860 };
17861 proof.then(|| {
17862 if cpp_template_reference_arguments(node, source).is_some()
17863 && visibility.is_exhaustive_same_fqn_type_declaration_family(
17864 analyzer, file, candidate,
17865 )
17866 {
17867 QualifiedAliasReferenceKind::ExhaustiveTemplate
17868 } else if qualified_alias_constructor_has_expression_argument(node)
17869 || qualified_alias_local_constructor_declaration(node)
17870 {
17871 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17872 } else {
17873 QualifiedAliasReferenceKind::Ordinary
17874 }
17875 })
17876 })
17877 })
17878}
17879
17880pub(crate) fn qualified_alias_reference_requires_terminal(
17881 reference: Option<QualifiedAliasReferenceKind>,
17882) -> bool {
17883 matches!(
17884 reference,
17885 Some(
17886 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17887 | QualifiedAliasReferenceKind::ExhaustiveTemplate
17888 )
17889 )
17890}
17891
17892fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17893 let Some(declaration) = node.parent().filter(|parent| {
17894 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17895 }) else {
17896 return false;
17897 };
17898 let mut cursor = declaration.walk();
17899 declaration.named_children(&mut cursor).any(|child| {
17900 child.kind() == "init_declarator"
17901 && child
17902 .child_by_field_name("value")
17903 .filter(|value| value.kind() == "argument_list")
17904 .is_some_and(|arguments| {
17905 let mut cursor = arguments.walk();
17906 arguments.named_children(&mut cursor).any(|argument| {
17907 let is_parameter = matches!(
17908 argument.kind(),
17909 "parameter_declaration" | "optional_parameter_declaration"
17910 );
17911 if is_parameter {
17912 argument
17913 .child_by_field_name("type")
17914 .is_some_and(|type_node| {
17915 type_node.kind() == "type_identifier"
17916 && argument.child_by_field_name("declarator").is_none()
17917 })
17918 } else {
17919 !argument.kind().ends_with("_literal")
17920 && !matches!(argument.kind(), "true" | "false" | "nullptr")
17921 }
17922 })
17923 })
17924 })
17925}
17926
17927fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17932 let Some(declaration) = node.parent().filter(|parent| {
17933 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17934 }) else {
17935 return false;
17936 };
17937 if declaration
17938 .parent()
17939 .is_none_or(|parent| parent.kind() != "compound_statement")
17940 {
17941 return false;
17942 }
17943 let mut cursor = declaration.walk();
17944 declaration
17945 .named_children(&mut cursor)
17946 .any(|child| child.kind() == "function_declarator")
17947}
17948
17949pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17955 loop {
17956 let next = match node.kind() {
17957 "qualified_identifier"
17958 | "scoped_identifier"
17959 | "template_method"
17960 | "template_function"
17961 | "template_type" => node.child_by_field_name("name"),
17962 "field_expression" => node.child_by_field_name("field"),
17963 _ => None,
17964 };
17965 let Some(next) = next else {
17966 return node;
17967 };
17968 node = next;
17969 }
17970}
17971
17972#[derive(Clone, Copy)]
17973pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17974 pub receiver: Node<'tree>,
17975 pub member: Node<'tree>,
17976 pub arity: usize,
17977}
17978
17979pub fn recovered_relational_template_member_call(
17987 field: Node<'_>,
17988) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17989 if field.kind() != "field_expression" {
17990 return None;
17991 }
17992 let receiver = field
17993 .child_by_field_name("argument")
17994 .or_else(|| field.child_by_field_name("object"))?;
17995 let member = field.child_by_field_name("field")?;
17996 let less = field.parent()?;
17997 if less.kind() != "binary_expression"
17998 || less.child_by_field_name("left") != Some(field)
17999 || less
18000 .child_by_field_name("operator")
18001 .is_none_or(|operator| operator.kind() != "<")
18002 || less.child_by_field_name("right").is_none()
18003 {
18004 return None;
18005 }
18006 let greater = less.parent()?;
18007 if greater.kind() != "binary_expression"
18008 || greater.child_by_field_name("left") != Some(less)
18009 || greater
18010 .child_by_field_name("operator")
18011 .is_none_or(|operator| operator.kind() != ">")
18012 {
18013 return None;
18014 }
18015 let arguments = greater.child_by_field_name("right")?;
18016 if arguments.kind() != "parenthesized_expression" {
18017 return None;
18018 }
18019 let arity = parenthesized_call_argument_arity(arguments)?;
18020 Some(RecoveredRelationalTemplateMemberCall {
18021 receiver,
18022 member,
18023 arity,
18024 })
18025}
18026
18027fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18028 let expression = arguments.named_child(0)?;
18029 if expression.kind() != "comma_expression" {
18030 return Some(1);
18031 }
18032 let mut arity = 0usize;
18033 let mut stack = vec![expression];
18034 while let Some(node) = stack.pop() {
18035 if node.kind() == "comma_expression" {
18036 stack.push(node.child_by_field_name("right")?);
18037 stack.push(node.child_by_field_name("left")?);
18038 } else {
18039 arity += 1;
18040 }
18041 }
18042 Some(arity)
18043}
18044
18045pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18048 while let Some(parent) = node.parent() {
18049 match parent.kind() {
18050 "call_expression" => {
18051 return parent
18052 .child_by_field_name("function")
18053 .or_else(|| parent.named_child(0))
18054 == Some(node);
18055 }
18056 "qualified_identifier"
18057 | "scoped_identifier"
18058 | "template_function"
18059 | "template_type"
18060 | "field_expression" => node = parent,
18061 _ => return false,
18062 }
18063 }
18064 false
18065}
18066
18067pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18071 loop {
18072 let Some(parent) = node.parent() else {
18073 return node;
18074 };
18075 if parent.kind() == "template_type"
18076 && parent
18077 .child_by_field_name("name")
18078 .is_some_and(|name| same_node(name, node))
18079 {
18080 node = parent;
18081 continue;
18082 }
18083 if matches!(
18084 parent.kind(),
18085 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18086 ) && (parent
18087 .child_by_field_name("scope")
18088 .is_some_and(|scope| same_node(scope, node))
18089 || parent
18090 .child_by_field_name("name")
18091 .is_some_and(|name| same_node(name, node)))
18092 && parent.end_byte() == node.end_byte()
18093 {
18094 node = parent;
18095 continue;
18096 }
18097 return node;
18098 }
18099}
18100
18101pub fn normalize_type_text(value: &str) -> String {
18102 strip_tag_type_prefix(
18103 normalize_cpp_whitespace(value)
18104 .trim_start_matches("const ")
18105 .trim_end_matches('*')
18106 .trim_end_matches('&')
18107 .trim(),
18108 )
18109 .to_string()
18110}
18111
18112use crate::declarations::strip_tag_type_prefix;
18113
18114pub fn normalize_reference_name(value: &str) -> Option<String> {
18115 let normalized = normalize_cpp_reference_text(value);
18116 (!normalized.is_empty()).then_some(normalized)
18117}
18118
18119pub fn normalize_cpp_reference_text(value: &str) -> String {
18120 let mut text = normalize_cpp_whitespace(value)
18121 .trim_start_matches("new ")
18122 .trim()
18123 .to_string();
18124 if let Some(index) = text.find(['(', '{']) {
18125 text.truncate(index);
18126 }
18127 if let Some(index) = text.find('<') {
18128 text.truncate(index);
18129 }
18130 let normalized = text
18131 .trim()
18132 .trim_start_matches("const ")
18133 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18134 .trim_matches(':')
18135 .trim();
18136 strip_tag_type_prefix(normalized).to_string()
18137}
18138
18139pub fn cpp_name_for(unit: &CodeUnit) -> String {
18140 let short = unit.short_name().replace(['.', '$'], "::");
18141 if unit.package_name().is_empty() {
18142 short
18143 } else {
18144 format!("{}::{}", unit.package_name(), short)
18145 }
18146}
18147
18148fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18152 let fq = unit.fq();
18153 if fq.is_empty() {
18154 return None;
18155 }
18156 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18157 Some(
18158 fq.segments()
18159 .iter()
18160 .map(|&segment| interner.resolve(segment).0)
18161 .collect::<Vec<_>>()
18162 .join("::"),
18163 )
18164}
18165
18166fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18167 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18168 || unit.fq().is_empty() && cpp_name_for(unit) == expected
18169}
18170
18171pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18180 let fq = unit.fq();
18181 if !fq.is_empty() {
18182 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18183 let scope = fq
18184 .segments()
18185 .iter()
18186 .filter_map(|&segment| {
18187 let (text, kind) = interner.resolve(segment);
18188 matches!(
18189 kind,
18190 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
18191 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
18192 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
18193 )
18194 .then(|| text.to_string())
18195 })
18196 .collect();
18197 return scope;
18198 }
18199 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18200 brokk_bifrost_core::analyzer::Language::Cpp,
18201 &cpp_name_for(unit),
18202 )
18203}
18204
18205pub fn terminal_name(value: &str) -> &str {
18216 value
18217 .rsplit("::")
18218 .next()
18219 .unwrap_or(value)
18220 .rsplit(['.', '-', '>'])
18221 .next()
18222 .unwrap_or(value)
18223 .trim()
18224}
18225
18226pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18227 terminal_name(&normalize_cpp_reference_text(value)) == expected
18228}
18229
18230pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18231 name_matches_terminal(value, expected)
18232 || expected.starts_with("operator")
18233 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18234}
18235
18236pub fn name_mentions(value: &str, expected: &str) -> bool {
18237 normalize_cpp_reference_text(value)
18238 .split("::")
18239 .any(|part| part == expected)
18240}
18241
18242pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18243 let cpp_name = cpp_name_for(unit);
18244 if reference.contains("::") {
18245 return reference == cpp_name;
18246 }
18247 reference == cpp_name
18248 || terminal_name(reference) == unit.identifier()
18249 && (unit.package_name().is_empty() || reference == unit.identifier())
18250}
18251
18252pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18253 match kind {
18254 TargetKind::Type
18255 | TargetKind::Constructor
18256 | TargetKind::Method
18257 | TargetKind::MemberField => true,
18258 TargetKind::FreeFunction => unit.is_function(),
18259 TargetKind::GlobalField => unit.is_field(),
18260 TargetKind::Macro => unit.is_macro(),
18261 }
18262}
18263
18264fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18265 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18266 let target_name = cpp_name_for(target);
18267 if normalized.contains("::") {
18268 return normalized == target_name;
18269 }
18270 if let Some(namespace) = alias.namespace.as_deref() {
18271 return namespace_prefixes(namespace)
18272 .into_iter()
18273 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18274 }
18275 target.package_name().is_empty() && normalized == target.identifier()
18276}
18277
18278pub fn cpp_function_return_type_text(
18281 analyzer: &CppGraphSource<'_>,
18282 function: &CodeUnit,
18283) -> Option<String> {
18284 let metadata = analyzer.signature_metadata(function);
18285 let first = metadata.first()?.return_type_text()?;
18286 metadata
18287 .iter()
18288 .all(|metadata| metadata.return_type_text() == Some(first))
18289 .then(|| first.to_owned())
18290}
18291
18292pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18293 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18303 namespace
18304 .strip_prefix("anonymous_namespace::")
18305 .unwrap_or(namespace)
18306 .to_string()
18307 })
18308}
18309
18310pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18312 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18318 brokk_bifrost_core::analyzer::Language::Cpp,
18319 namespace,
18320 );
18321 let mut prefixes = Vec::new();
18322 while !parts.is_empty() {
18323 prefixes.push(parts.join("::"));
18324 parts.pop();
18325 }
18326 prefixes
18327}
18328
18329fn nearest_namespace_candidates(
18330 candidates: Vec<CodeUnit>,
18331 normalized: &str,
18332 lexical_namespace: Option<&str>,
18333) -> Vec<CodeUnit> {
18334 if normalized.contains("::") {
18335 return candidates;
18336 }
18337 if let Some(namespace) = lexical_namespace {
18338 for prefix in namespace_prefixes(namespace) {
18339 let scoped = candidates
18340 .iter()
18341 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18342 .cloned()
18343 .collect::<Vec<_>>();
18344 if !scoped.is_empty() {
18345 return scoped;
18346 }
18347 }
18348 }
18349 candidates
18350 .into_iter()
18351 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18352 .collect()
18353}
18354
18355pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18356 let mut namespaces = Vec::new();
18357 for parent in enclosing_cpp_namespace_nodes(node) {
18358 if let Some(name) = parent.child_by_field_name("name") {
18359 let namespace = normalize_cpp_reference_text(node_text(name, source));
18360 if !namespace.is_empty() {
18361 namespaces.push(namespace);
18362 }
18363 }
18364 }
18365 if namespaces.is_empty() {
18366 None
18367 } else {
18368 Some(namespaces.join("::"))
18369 }
18370}
18371
18372pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18376 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18377}
18378
18379fn type_owner_resolution(
18380 analyzer: &CppGraphSource<'_>,
18381 code_unit: &CodeUnit,
18382) -> Option<ResolvedTypeOwner> {
18383 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18384}
18385
18386fn target_type_owner_resolution(
18387 analyzer: &CppGraphSource<'_>,
18388 code_unit: &CodeUnit,
18389) -> Option<ResolvedTypeOwner> {
18390 match type_owner_resolution(analyzer, code_unit) {
18391 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18392 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18393 }
18394}
18395
18396fn target_forward_owner_resolution(
18408 analyzer: &CppGraphSource<'_>,
18409 code_unit: &CodeUnit,
18410) -> Option<ResolvedTypeOwner> {
18411 if !code_unit.is_function() {
18412 return None;
18413 }
18414 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18420 let cpp = analyzer.cpp?;
18421 let mut visible_files = HashSet::default();
18422 collect_include_closure(
18423 analyzer,
18424 cpp.include_target_index(),
18425 code_unit.source(),
18426 &mut visible_files,
18427 None,
18428 );
18429 let candidates = analyzer.workspace_definitions().exact(&owner_name);
18430 let visible_candidates = candidates
18431 .iter()
18432 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18433 .cloned()
18434 .collect::<Vec<_>>();
18435 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18436 DirectOwnerResolution::UniqueFull(unit) => {
18437 return Some(ResolvedTypeOwner {
18438 unit,
18439 is_forward_declaration: false,
18440 });
18441 }
18442 DirectOwnerResolution::ForwardsOnly(forwards) => {
18443 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18444 unit: forwards.into_iter().next().unwrap(),
18445 is_forward_declaration: true,
18446 });
18447 }
18448 DirectOwnerResolution::Ambiguous => return None,
18449 DirectOwnerResolution::None => {}
18450 }
18451
18452 let candidates = candidates
18453 .into_iter()
18454 .filter(|candidate| candidate.is_class())
18455 .collect::<Vec<_>>();
18456 let (unit, is_forward_declaration) =
18457 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18458 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18459 DirectOwnerResolution::ForwardsOnly(forwards) => {
18460 (unique_logical_forward_owner(forwards)?, true)
18461 }
18462 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18463 };
18464 Some(ResolvedTypeOwner {
18465 unit,
18466 is_forward_declaration,
18467 })
18468}
18469
18470pub fn precise_parent_of(
18471 analyzer: &CppGraphSource<'_>,
18472 visibility: &VisibilityIndex<'_>,
18473 code_unit: &CodeUnit,
18474) -> Option<CodeUnit> {
18475 visibility.cached_precise_parent_of(analyzer, code_unit)
18476}
18477
18478fn precise_parent_resolution(
18479 analyzer: &CppGraphSource<'_>,
18480 code_unit: &CodeUnit,
18481) -> Option<ResolvedTypeOwner> {
18482 #[cfg(any(test, feature = "test-support"))]
18483 if let Some(cpp) = analyzer.cpp {
18484 cpp.record_cpp_parent_resolution_for_test();
18485 }
18486 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18487 return Some(ResolvedTypeOwner {
18488 unit,
18489 is_forward_declaration: false,
18490 });
18491 }
18492 let fallback = analyzer.parent_of(code_unit);
18493 if !code_unit.owner_is_type_scope() {
18494 return fallback.map(|unit| ResolvedTypeOwner {
18495 unit,
18496 is_forward_declaration: false,
18497 });
18498 }
18499 let owner_fq = code_unit
18500 .fq()
18501 .parent()
18502 .expect("a unit with an owner identifier has a structured parent");
18503 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18504 match same_source_owner(analyzer, code_unit, &owner_candidates) {
18505 DirectOwnerResolution::UniqueFull(owner) => {
18506 return Some(ResolvedTypeOwner {
18507 unit: owner,
18508 is_forward_declaration: false,
18509 });
18510 }
18511 DirectOwnerResolution::Ambiguous => return None,
18512 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18513 }
18514 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18515 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18516 unit: owner,
18517 is_forward_declaration: false,
18518 }),
18519 DirectOwnerResolution::Ambiguous => None,
18520 DirectOwnerResolution::ForwardsOnly(forwards) => {
18521 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18522 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18523 unit: owner,
18524 is_forward_declaration: false,
18525 }),
18526 FullOwnerResolution::None => {
18527 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
18528 unit,
18529 is_forward_declaration: true,
18530 })
18531 }
18532 FullOwnerResolution::Ambiguous => None,
18533 }
18534 }
18535 DirectOwnerResolution::None => {
18536 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18537 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18538 unit: owner,
18539 is_forward_declaration: false,
18540 }),
18541 FullOwnerResolution::Ambiguous => None,
18542 FullOwnerResolution::None => fallback
18543 .filter(|parent| {
18544 parent.source() == code_unit.source()
18545 && parent.fq() == &owner_fq
18546 && (!parent.is_class()
18547 || cpp_class_declaration_strength(analyzer, parent)
18548 == CppClassDeclarationStrength::Full)
18549 })
18550 .map(|unit| ResolvedTypeOwner {
18551 unit,
18552 is_forward_declaration: false,
18553 }),
18554 }
18555 }
18556 }
18557}
18558
18559fn exact_structural_type_parent(
18560 analyzer: &CppGraphSource<'_>,
18561 code_unit: &CodeUnit,
18562) -> Option<CodeUnit> {
18563 if !code_unit.is_function() && !code_unit.is_field() {
18564 return None;
18565 }
18566 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
18568 let parent = cpp.structural_parent_of(code_unit)?;
18569 (!parent.is_module()
18570 && parent.source() == code_unit.source()
18571 && parent.package_name() == code_unit.package_name()
18572 && parent.short_name() == encoded_owner)
18573 .then_some(parent)
18574}
18575
18576fn same_source_owner(
18577 analyzer: &CppGraphSource<'_>,
18578 code_unit: &CodeUnit,
18579 owner_candidates: &[CodeUnit],
18580) -> DirectOwnerResolution {
18581 let candidates = owner_candidates
18582 .iter()
18583 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18584 .cloned()
18585 .collect::<Vec<_>>();
18586 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18587 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18588}
18589
18590fn visible_full_cpp_owner(
18591 analyzer: &CppGraphSource<'_>,
18592 code_unit: &CodeUnit,
18593 owner_candidates: &[CodeUnit],
18594) -> FullOwnerResolution {
18595 let Some(cpp) = analyzer.cpp else {
18596 return FullOwnerResolution::None;
18597 };
18598 let mut visible_files = HashSet::default();
18599 collect_include_closure(
18600 analyzer,
18601 cpp.include_target_index(),
18602 code_unit.source(),
18603 &mut visible_files,
18604 None,
18605 );
18606 let candidates = owner_candidates
18607 .iter()
18608 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18609 .cloned()
18610 .collect::<Vec<_>>();
18611 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18612 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18613 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18614 (candidate, strength)
18615 })) {
18616 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18617 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18618 FullOwnerResolution::None
18619 }
18620 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18621 }
18622}
18623
18624pub enum DirectOwnerResolution {
18625 None,
18626 ForwardsOnly(Vec<CodeUnit>),
18627 UniqueFull(CodeUnit),
18628 Ambiguous,
18629}
18630
18631enum FullOwnerResolution {
18632 None,
18633 Unique(CodeUnit),
18634 Ambiguous,
18635}
18636
18637fn directly_included_owner(
18638 analyzer: &CppGraphSource<'_>,
18639 code_unit: &CodeUnit,
18640 owner_candidates: &[CodeUnit],
18641) -> DirectOwnerResolution {
18642 let Some(cpp) = analyzer.cpp else {
18643 return DirectOwnerResolution::None;
18644 };
18645 let direct_includes: HashSet<ProjectFile> = analyzer
18646 .canonical_include_paths(code_unit.source())
18647 .unwrap_or_default()
18648 .into_iter()
18649 .flat_map(|include| {
18650 resolve_include_targets_with_index(
18651 code_unit.source(),
18652 &include,
18653 cpp.include_target_index(),
18654 )
18655 })
18656 .collect();
18657 let candidates = owner_candidates
18658 .iter()
18659 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18660 .cloned()
18661 .collect::<Vec<_>>();
18662 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18663 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18664}
18665
18666fn prefer_member_declaring_owners(
18667 analyzer: &CppGraphSource<'_>,
18668 member: &CodeUnit,
18669 candidates: Vec<CodeUnit>,
18670) -> Vec<CodeUnit> {
18671 let matching = candidates
18672 .iter()
18673 .filter(|owner| owner_declares_member(analyzer, owner, member))
18674 .cloned()
18675 .collect::<Vec<_>>();
18676 if matching.is_empty() {
18677 candidates
18678 } else {
18679 matching
18680 }
18681}
18682
18683fn owner_declares_member(
18684 analyzer: &CppGraphSource<'_>,
18685 owner: &CodeUnit,
18686 member: &CodeUnit,
18687) -> bool {
18688 analyzer.direct_children(owner).into_iter().any(|child| {
18689 child.kind() == member.kind()
18690 && child.identifier() == member.identifier()
18691 && child.signature() == member.signature()
18692 })
18693}
18694
18695fn classify_direct_owner_candidates(
18696 analyzer: &CppGraphSource<'_>,
18697 candidates: impl Iterator<Item = CodeUnit>,
18698) -> DirectOwnerResolution {
18699 collapse_owner_candidates(candidates.map(|candidate| {
18700 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18701 (candidate, strength)
18702 }))
18703}
18704
18705pub fn collapse_owner_candidates(
18706 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18707) -> DirectOwnerResolution {
18708 let mut full_definition = None;
18709 let mut forwards = Vec::new();
18710 for (candidate, strength) in candidates {
18711 match strength {
18712 CppClassDeclarationStrength::Full if full_definition.is_some() => {
18713 return DirectOwnerResolution::Ambiguous;
18714 }
18715 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18716 CppClassDeclarationStrength::Forward => forwards.push(candidate),
18717 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18718 }
18719 }
18720 if let Some(owner) = full_definition {
18721 DirectOwnerResolution::UniqueFull(owner)
18722 } else if !forwards.is_empty() {
18723 DirectOwnerResolution::ForwardsOnly(forwards)
18724 } else {
18725 DirectOwnerResolution::None
18726 }
18727}
18728
18729#[cfg(any(test, feature = "test-support"))]
18730pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18731 unique_logical_forward_owner(forwards)
18732}
18733
18734fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18735 let first = forwards.pop()?;
18736 forwards
18737 .iter()
18738 .all(|forward| same_logical_symbol(forward, &first))
18739 .then_some(first)
18740}
18741
18742pub fn cpp_class_declaration_strength(
18743 analyzer: &CppGraphSource<'_>,
18744 candidate: &CodeUnit,
18745) -> CppClassDeclarationStrength {
18746 let Some(cpp) = analyzer.cpp else {
18754 return uncached_cpp_class_declaration_strength(analyzer, candidate);
18755 };
18756 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18757 return strength;
18758 }
18759 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18760 cpp.cache_class_declaration_strength(candidate, strength);
18761 strength
18762}
18763
18764fn uncached_cpp_class_declaration_strength(
18765 analyzer: &CppGraphSource<'_>,
18766 candidate: &CodeUnit,
18767) -> CppClassDeclarationStrength {
18768 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18769 if facts
18770 .iter()
18771 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18772 {
18773 return CppClassDeclarationStrength::Full;
18774 }
18775 if facts
18776 .iter()
18777 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18778 {
18779 return CppClassDeclarationStrength::Forward;
18780 }
18781 }
18782 if let Some(cpp) = analyzer.cpp
18783 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18784 {
18785 return cpp_class_declaration_strength_in_tree(
18786 analyzer,
18787 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18788 candidate,
18789 prepared.source(),
18790 prepared.tree().root_node(),
18791 );
18792 }
18793 let Some(source) = analyzer.indexed_source(candidate.source()) else {
18794 return CppClassDeclarationStrength::Unknown;
18795 };
18796 let mut parser = Parser::new();
18797 if parser
18798 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18799 .is_err()
18800 {
18801 return CppClassDeclarationStrength::Unknown;
18802 }
18803 let Some(tree) = parser.parse(&source, None) else {
18804 return CppClassDeclarationStrength::Unknown;
18805 };
18806 let recovered_export_classes =
18809 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18810 cpp_class_declaration_strength_in_tree(
18811 analyzer,
18812 &recovered_export_classes,
18813 candidate,
18814 &source,
18815 tree.root_node(),
18816 )
18817}
18818
18819fn cpp_class_declaration_strength_in_tree(
18820 analyzer: &CppGraphSource<'_>,
18821 recovered_export_classes: &CppRecoveredExportClassIndex,
18822 candidate: &CodeUnit,
18823 source: &str,
18824 root: Node<'_>,
18825) -> CppClassDeclarationStrength {
18826 let ranges = analyzer.ranges(candidate);
18827 let mut saw_forward = false;
18828 for range in ranges {
18829 match recovered_class_body_at(
18832 recovered_export_classes,
18833 root,
18834 source,
18835 candidate.identifier(),
18836 &range,
18837 ) {
18838 Some(true) => return CppClassDeclarationStrength::Full,
18839 Some(false) => {
18840 saw_forward = true;
18841 continue;
18842 }
18843 None => {}
18844 }
18845 let covers_range_start = |node: &Node<'_>| {
18852 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18853 };
18854 let mut stack = Vec::new();
18855 if covers_range_start(&root) {
18856 stack.push(root);
18857 }
18858 while let Some(node) = stack.pop() {
18859 if node.start_byte() == range.start_byte
18860 && node.end_byte() == range.end_byte
18861 && matches!(
18862 node.kind(),
18863 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18864 )
18865 {
18866 if cpp_class_node_has_body(node) {
18867 return CppClassDeclarationStrength::Full;
18868 }
18869 saw_forward = true;
18870 }
18871 let mut cursor = node.walk();
18872 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18873 }
18874 }
18875 if saw_forward {
18876 CppClassDeclarationStrength::Forward
18877 } else {
18878 CppClassDeclarationStrength::Unknown
18879 }
18880}
18881
18882fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18883 child_by_field_or_named_child_of_kinds(
18884 node,
18885 "body",
18886 &[
18887 "declaration_list",
18888 "field_declaration_list",
18889 "enumerator_list",
18890 ],
18891 )
18892 .is_some()
18893}
18894
18895#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18896enum CppCTagKind {
18897 Struct,
18898 Union,
18899}
18900
18901fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18902 let declaration = analyzer.get_source(code_unit, false)?;
18903 let mut parser = Parser::new();
18904 parser
18905 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18906 .ok()?;
18907 let tree = parser.parse(&declaration, None)?;
18908 let mut stack = vec![tree.root_node()];
18909 while let Some(node) = stack.pop() {
18910 let kind = match node.kind() {
18911 "struct_specifier" => CppCTagKind::Struct,
18912 "union_specifier" => CppCTagKind::Union,
18913 _ => {
18914 let mut cursor = node.walk();
18915 stack.extend(node.named_children(&mut cursor));
18916 continue;
18917 }
18918 };
18919 if node
18920 .child_by_field_name("name")
18921 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18922 {
18923 return Some(kind);
18924 }
18925 let mut cursor = node.walk();
18926 stack.extend(node.named_children(&mut cursor));
18927 }
18928 None
18929}
18930
18931pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18932 if !code_unit.owner_is_type_scope() {
18933 return None;
18934 }
18935 let owner_fq = code_unit.fq().parent()?;
18936 ctx.analyzer
18937 .workspace_definitions()
18938 .exact(&owner_fq)
18939 .into_iter()
18940 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18941}
18942
18943pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18944 same_logical_symbol(left, right) && left.source() == right.source()
18945}
18946
18947pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18948 same_symbol(left, right) || same_logical_symbol(left, right)
18949}
18950
18951pub fn same_visible_global_field_symbol(
18952 analyzer: &CppGraphSource<'_>,
18953 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18954 left: &CodeUnit,
18955 right: &CodeUnit,
18956) -> bool {
18957 if same_symbol(left, right) {
18958 return true;
18959 }
18960 if !same_logical_symbol(left, right) {
18961 return false;
18962 }
18963 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18964 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18965 {
18966 left.source() == right.source()
18967 } else {
18968 true
18969 }
18970}
18971
18972fn cpp_global_field_has_internal_linkage_cached(
18973 analyzer: &CppGraphSource<'_>,
18974 cache: &mut HashMap<CodeUnit, bool>,
18975 candidate: &CodeUnit,
18976) -> bool {
18977 if let Some(internal) = cache.get(candidate) {
18978 return *internal;
18979 }
18980 #[cfg(any(test, feature = "test-support"))]
18981 note_cpp_global_field_internal_linkage_classification_for_test();
18982 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18983 cache.insert(candidate.clone(), internal);
18984 internal
18985}
18986
18987#[cfg(any(test, feature = "test-support"))]
18988thread_local! {
18989 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18990}
18991
18992#[cfg(any(test, feature = "test-support"))]
18993fn note_cpp_global_field_internal_linkage_classification_for_test() {
18994 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18995 count.set(count.get() + 1);
18996 });
18997}
18998
18999#[cfg(any(test, feature = "test-support"))]
19000pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19001 body: impl FnOnce() -> T,
19002) -> (T, usize) {
19003 with_thread_local_counter_for_test(
19004 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19005 body,
19006 )
19007}
19008
19009#[cfg(any(test, feature = "test-support"))]
19010fn with_thread_local_counter_for_test<T>(
19011 counter: &'static std::thread::LocalKey<Cell<usize>>,
19012 body: impl FnOnce() -> T,
19013) -> (T, usize) {
19014 counter.with(|count| {
19015 count.set(0);
19016 let result = body();
19017 let observed = count.get();
19018 count.set(0);
19019 (result, observed)
19020 })
19021}
19022
19023pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19024 left.kind() == right.kind()
19025 && left.fq_name() == right.fq_name()
19026 && left.signature() == right.signature()
19027}
19028
19029pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19032 candidates: &[&'a CodeUnit],
19033 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19034) -> Option<&'a CodeUnit> {
19035 let first = *candidates.first()?;
19036 if candidates.len() == 1 {
19037 return Some(first);
19038 }
19039 let first_target = structured_target(first)?;
19040 candidates
19041 .iter()
19042 .skip(1)
19043 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19044 .then_some(first)
19045}
19046
19047pub fn cpp_global_field_has_internal_linkage(
19048 analyzer: &CppGraphSource<'_>,
19049 candidate: &CodeUnit,
19050) -> bool {
19051 if !candidate.is_field() || candidate.short_name().contains('.') {
19052 return false;
19053 }
19054 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19055 return false;
19056 };
19057 match local_linkage {
19058 CppFieldLinkage::Internal => true,
19059 CppFieldLinkage::External => false,
19060 CppFieldLinkage::InternalUnlessExternalPeer => {
19061 !cpp_global_field_linkage_peers(analyzer, candidate)
19062 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19063 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19064 }
19065 }
19066}
19067
19068fn cpp_global_field_linkage_peers<'a>(
19069 analyzer: &CppGraphSource<'a>,
19070 candidate: &'a CodeUnit,
19071) -> impl Iterator<Item = CodeUnit> + 'a {
19072 let name = candidate.fq().clone();
19073 analyzer
19074 .workspace_definitions()
19075 .exact(&name)
19076 .into_iter()
19077 .filter(move |peer| {
19078 if peer == candidate {
19079 return false;
19080 }
19081 #[cfg(any(test, feature = "test-support"))]
19082 note_cpp_global_field_linkage_peer_inspection_for_test();
19083 same_logical_symbol(peer, candidate)
19084 })
19085}
19086
19087#[cfg(any(test, feature = "test-support"))]
19088thread_local! {
19089 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19090}
19091
19092#[cfg(any(test, feature = "test-support"))]
19093fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19094 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19095 count.set(count.get() + 1);
19096 });
19097}
19098
19099#[cfg(any(test, feature = "test-support"))]
19100pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19101 body: impl FnOnce() -> T,
19102) -> (T, usize) {
19103 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19104}
19105
19106fn cpp_global_field_declaration_linkage(
19107 analyzer: &CppGraphSource<'_>,
19108 candidate: &CodeUnit,
19109) -> Option<CppFieldLinkage> {
19110 analyzer.cpp_field_linkage(candidate)
19111}
19112
19113pub fn cpp_canonical_alias_target_text(
19115 analyzer: &CppGraphSource<'_>,
19116 unit: &CodeUnit,
19117) -> Option<String> {
19118 let facts = analyzer.declaration_source_properties(unit)?;
19119 let first_fact = facts.first()?;
19120 let first = first_fact.alias_target_text.as_ref()?;
19121 let normalized = normalize_cpp_whitespace(first);
19122 facts
19126 .iter()
19127 .all(|fact| {
19128 fact.alias_target_text.as_ref().is_some_and(|target| {
19129 target == first
19130 || (first_fact.alias_target.is_some()
19131 && fact.alias_target == first_fact.alias_target
19132 && normalize_cpp_whitespace(target) == normalized)
19133 })
19134 })
19135 .then(|| first.clone())
19136}
19137
19138#[cfg(test)]
19139mod tests {
19140
19141 #[test]
19142 fn resolver_displaced_conditional_grammar_boundary() {
19143 let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19147 let tree = parse_cpp(source);
19148 let conditional = tree.root_node().named_child(0).unwrap();
19149 assert_eq!(conditional.kind(), "preproc_ifdef");
19150 assert!(conditional.has_error());
19151 let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19152 let directive = crate::grammar_test_support::nodes(conditional)
19153 .into_iter()
19154 .find(|node| node.kind() == "#endif" && !node.is_missing())
19155 .unwrap();
19156 assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19157 assert_eq!(directive.end_byte(), boundary.end_byte);
19158 let inside = named_node_at(&tree, source, "values");
19159 let outside = named_node_at(&tree, source, "outside");
19160 assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19161 assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19162 assert!(inside.end_byte() < directive.start_byte());
19163 assert!(outside.start_byte() > directive.end_byte());
19164 assert!(preprocessor_conditional_contains_descendant(
19165 conditional,
19166 inside
19167 ));
19168 assert!(!preprocessor_conditional_contains_descendant(
19169 conditional,
19170 outside
19171 ));
19172 }
19173
19174 #[test]
19175 fn resolver_recovered_namespace_boundaries() {
19176 let source = STOLEN_BRACE_CASCADE;
19177 let tree = parse_cpp(source);
19178 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19179 assert!(!index.regions.is_empty(), "fixture must enter recovery");
19180 for (needle, expected) in [
19181 ("First", vec!["app", "matchers", "detail"]),
19182 ("Second", vec!["app", "matchers", "detail"]),
19183 ("tail_function", vec!["app", "matchers"]),
19184 ("TailClass", vec!["app", "matchers"]),
19185 ("AfterAll", vec![]),
19186 ] {
19187 let node = named_node_at(&tree, source, needle);
19188 let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19189 .filter(|node| node.kind() == "namespace_definition")
19190 .filter_map(|namespace| {
19191 let name = namespace.child_by_field_name("name")?;
19192 let mut components = Vec::new();
19193 append_cpp_name_components(name, source, &mut components)?;
19194 Some((
19195 namespace.start_byte(),
19196 components,
19197 namespace_definition_has_real_braces(namespace),
19198 ))
19199 })
19200 .collect();
19201 parsed.reverse();
19202 assert_eq!(
19203 index.restore_enclosing_namespaces(parsed, node.start_byte()),
19204 expected,
19205 "{needle}"
19206 );
19207 }
19208 for region in &index.regions {
19210 assert!(region.start < region.end);
19211 assert!(index.region_at(region.start).is_some());
19212 assert!(
19213 index
19214 .region_at(region.end)
19215 .is_none_or(|found| !std::ptr::eq(found, region)),
19216 "region leaked beyond its half-open boundary: {region:?}"
19217 );
19218 }
19219 let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19222 .into_iter()
19223 .filter(|node| node.kind() == "ERROR")
19224 .filter(|node| {
19225 let mut cursor = node.walk();
19226 node.children(&mut cursor).any(|child| child.kind() == "}")
19227 })
19228 .collect();
19229 assert!(
19230 !errors.is_empty(),
19231 "fixture must retain unmatched close tokens"
19232 );
19233 for error in errors {
19234 assert!(direct_unmatched_closing_brace(error), "{error:?}");
19235 }
19236 }
19237
19238 #[test]
19241 fn resolver_declarator_grammar_boundary_table() {
19242 for (source, name, kind, binder) in [
19243 ("int first, values[3];", "values", "identifier", true),
19244 (
19245 "void f() { int first, values[3]; }",
19246 "values",
19247 "identifier",
19248 true,
19249 ),
19250 (
19251 "typedef unsigned Underlying; enum Named : Underlying { Value };",
19252 "Underlying {",
19253 "type_identifier",
19254 false,
19255 ),
19256 ] {
19257 let tree = parse_cpp(source);
19258 assert!(!tree.root_node().has_error(), "{source}");
19259 let spelling = name.split_whitespace().next().unwrap();
19260 let start = source.find(name).unwrap();
19261 let node = tree
19262 .root_node()
19263 .named_descendant_for_byte_range(start, start + spelling.len())
19264 .unwrap();
19265 assert_eq!(node.kind(), kind, "{source}");
19266 if spelling == "values" {
19267 let array = node.parent().unwrap();
19268 assert_eq!(array.kind(), "array_declarator");
19269 assert_eq!(array.child_by_field_name("declarator"), Some(node));
19270 assert!(is_declarator_node(array), "{source}");
19271 } else {
19272 let enumeration = node.parent().unwrap();
19273 assert_eq!(enumeration.kind(), "enum_specifier");
19274 assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19275 assert_eq!(
19276 enumeration.child_by_field_name("name").is_some(),
19277 source.contains("enum Named")
19278 );
19279 }
19280 assert_eq!(is_declaration_name(node), binder, "{source}");
19281 }
19282 }
19283
19284 #[test]
19285 fn resolver_alias_scope_and_argument_grammar_table() {
19286 for (source, scope) in [
19287 ("template<class T> using Alias = T;", true),
19288 ("namespace n { template<class T> using Alias = T; }", true),
19289 (
19290 "struct Owner { template<class T> using Alias = T; };",
19291 false,
19292 ),
19293 ("void f() { using Alias = int; }", false),
19294 ] {
19295 let tree = parse_cpp(source);
19296 assert!(!tree.root_node().has_error(), "{source}");
19297 let alias = crate::grammar_test_support::nodes(tree.root_node())
19298 .into_iter()
19299 .find(|node| node.kind() == "alias_declaration")
19300 .unwrap();
19301 assert_eq!(
19302 node_text(alias.child_by_field_name("name").unwrap(), source),
19303 "Alias"
19304 );
19305 assert!(alias.child_by_field_name("type").is_some());
19306 assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19307 }
19308 for (source, argument_kinds, expression) in [
19309 (
19310 "ns::Alias value(42, arg);",
19311 vec!["number_literal", "identifier"],
19312 true,
19313 ),
19314 (
19315 "ns::Alias value(42, 7);",
19316 vec!["number_literal", "number_literal"],
19317 false,
19318 ),
19319 ("ns::Alias value();", vec![], false),
19320 ] {
19321 let tree = parse_cpp(source);
19322 assert!(!tree.root_node().has_error(), "{source}");
19323 let declaration = tree.root_node().named_child(0).unwrap();
19324 let ty = declaration.child_by_field_name("type").unwrap();
19325 assert_eq!(node_text(ty, source), "ns::Alias");
19326 let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19327 .into_iter()
19328 .filter(|node| node.kind() == "argument_list")
19329 .collect();
19330 let kinds: Vec<_> = argument_lists
19331 .iter()
19332 .flat_map(|node| {
19333 let mut cursor = node.walk();
19334 node.named_children(&mut cursor)
19335 .map(|child| child.kind())
19336 .collect::<Vec<_>>()
19337 })
19338 .collect();
19339 assert_eq!(kinds, argument_kinds, "{source}");
19340 assert_eq!(
19341 qualified_alias_constructor_has_expression_argument(ty),
19342 expression,
19343 "{source}"
19344 );
19345 }
19346 }
19347
19348 #[test]
19349 fn resolver_namespace_brace_grammar_table() {
19350 for (source, complete, closing_missing) in [
19351 ("namespace n { int item; }", true, false),
19352 ("namespace n { int item;", false, true),
19353 ] {
19354 let tree = parse_cpp(source);
19355 let namespace = tree.root_node().named_child(0).unwrap();
19356 assert_eq!(namespace.kind(), "namespace_definition");
19357 let body = namespace.child_by_field_name("body").unwrap();
19358 assert_eq!(body.kind(), "declaration_list");
19359 let open = body.child(0).unwrap();
19360 let close = body.child(body.child_count() - 1).unwrap();
19361 assert_eq!(open.kind(), "{");
19362 assert!(!open.is_missing());
19363 assert_eq!(close.kind(), "}");
19364 assert_eq!(close.is_missing(), closing_missing);
19365 assert_eq!(
19366 namespace_definition_has_real_braces(namespace),
19367 complete,
19368 "{source}"
19369 );
19370 }
19371 let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19372 let tree = parse_cpp(source);
19373 assert!(!tree.root_node().has_error());
19374 let anonymous = tree.root_node().named_child(0).unwrap();
19375 assert_eq!(anonymous.kind(), "namespace_definition");
19376 assert!(anonymous.child_by_field_name("name").is_none());
19377 let run = named_node_at(&tree, source, "run");
19378 assert_eq!(
19379 enclosing_namespace_components(run, source),
19380 Some(vec!["detail".to_owned()])
19381 );
19382 }
19383
19384 #[test]
19385 fn resolver_c_block_envelope_grammar_table() {
19386 for (source, name, damaged, recovered) in [
19387 ("void outer(void) { nested() {} }", "nested", false, false),
19388 (
19389 "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19390 "Apply",
19391 true,
19392 true,
19393 ),
19394 ] {
19395 let tree = parse_cpp(source);
19396 let node = named_node_at(&tree, source, name);
19397 let declarator = node.parent().unwrap();
19398 assert_eq!(
19399 declarator.kind(),
19400 "function_declarator",
19401 "{source}: {:?}",
19402 tree.root_node()
19403 );
19404 assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19405 assert_eq!(declarator.has_error(), damaged, "{source}");
19406 let definition = declarator.parent().unwrap();
19407 assert_eq!(definition.kind(), "function_definition");
19408 assert!(definition.child_by_field_name("type").is_none());
19409 assert!(definition.child_by_field_name("body").is_some());
19410 assert_eq!(
19411 recovered_c_block_call_envelope(declarator).is_some(),
19412 recovered,
19413 "{source}"
19414 );
19415 }
19416 }
19417
19418 #[test]
19419 fn resolver_linkage_guard_grammar_table() {
19420 for (source, recovered, visible) in [
19421 (
19422 "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19423 false,
19424 true,
19425 ),
19426 (
19427 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19428 true,
19429 false,
19430 ),
19431 (
19432 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19433 true,
19434 false,
19435 ),
19436 ] {
19437 let tree = parse_cpp(source);
19438 let name = named_node_at(&tree, source, "target");
19439 let mut declaration = name;
19440 while declaration.kind() != "declaration" {
19441 declaration = declaration.parent().unwrap();
19442 }
19443 assert_eq!(
19444 has_ancestor_kind(declaration, "linkage_specification"),
19445 recovered,
19446 "{source}"
19447 );
19448 assert_eq!(
19449 has_ancestor_kind(declaration, "preproc_ifdef"),
19450 recovered,
19451 "{source}"
19452 );
19453 if !recovered {
19454 assert!(!tree.root_node().has_error());
19455 let open = tree.root_node().named_child(0).unwrap();
19456 assert_eq!(open.kind(), "preproc_linkage_open");
19457 assert!(open.child_by_field_name("value").is_some());
19458 }
19459 assert_eq!(
19460 callable_preprocessor_context_is_visible(declaration, source),
19461 visible,
19462 "{source}"
19463 );
19464 }
19465 }
19466
19467 #[test]
19468 #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19469 fn resolver_anonymous_enum_base_grammar_boundary() {
19470 let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19471 let tree = parse_cpp(source);
19472 assert!(
19473 !tree.root_node().has_error(),
19474 "{}",
19475 tree.root_node().to_sexp()
19476 );
19477 let start = source.find("Underlying {").unwrap();
19478 let base = tree
19479 .root_node()
19480 .named_descendant_for_byte_range(start, start + "Underlying".len())
19481 .unwrap();
19482 let enumeration = base.parent().unwrap();
19483 assert_eq!(enumeration.kind(), "enum_specifier");
19484 assert!(enumeration.child_by_field_name("name").is_none());
19485 assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19486 assert!(!is_declaration_name(base));
19487 }
19488
19489 #[test]
19490 #[hardwon(
19491 "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19492 )]
19493 fn macro_splices_preserve_statement_origin_ranges() {
19494 for newline in ["\n", "\r\n"] {
19495 for (replacement, name, written) in [
19496 (
19497 format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19498 "joined",
19499 format!("jo\\{newline}ined"),
19500 ),
19501 (
19502 format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19503 "block",
19504 format!("bl\\{newline}ock"),
19505 ),
19506 ] {
19507 let parameters = vec!["block".to_owned()];
19508 let (source, tree, original_offsets) =
19509 VisibilityIndex::parse_macro_replacement_body(&replacement, ¶meters)
19510 .expect("joined replacement parses");
19511 let body = ParsedReplacementBody {
19512 source,
19513 tree,
19514 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
19515 parameters,
19516 original_offsets,
19517 };
19518 let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
19519 let matching: Vec<_> = nodes
19520 .iter()
19521 .filter(|node| {
19522 node.kind() == "identifier" && node_text(**node, &body.source) == name
19523 })
19524 .collect();
19525 assert_eq!(matching.len(), 1, "{replacement}");
19526 let range = body.file_range(*matching[0], 0);
19527 assert_eq!(&replacement[range], written);
19528 for node in nodes.iter().filter(|node| {
19529 node.kind() == "identifier" && node_text(**node, &body.source) == "after"
19530 }) {
19531 assert_eq!(&replacement[body.file_range(*node, 0)], "after");
19532 }
19533 }
19534 }
19535 }
19536
19537 #[test]
19538 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
19539 fn issue_3089_statement_formal_at_end_of_replacement() {
19540 let parameters = vec!["handle".to_owned(), "block".to_owned()];
19541 for replacement in [
19542 "do { header_event_t* event; if ((handle)->active) block } while (0)",
19543 "do { header_event_t* event; block } while (0)",
19544 ] {
19545 assert!(
19546 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
19547 .is_some(),
19548 "{replacement}"
19549 );
19550 }
19551 }
19552 use super::*;
19553 use brokk_bifrost_test_macros::hardwon;
19554
19555 #[test]
19556 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
19557 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
19558 let source = "int size(void) { return sizeof(((Payload))); }\n";
19559 let mut parser = Parser::new();
19560 parser
19561 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19562 .expect("C++ grammar");
19563 let tree = parser.parse(source, None).expect("fixture tree");
19564 let start = source.find("Payload").expect("sizeof operand");
19565 let node = tree
19566 .root_node()
19567 .named_descendant_for_byte_range(start, start + "Payload".len())
19568 .expect("focused operand");
19569 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
19570 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
19571
19572 assert_eq!(node.kind(), "identifier");
19573 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
19574 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
19575 }
19576
19577 #[test]
19578 fn recovered_macro_actuals_keep_their_cst_boundaries() {
19579 for (source, recovered) in [
19580 ("int f() { return M(Type, value); }", false),
19581 ("int f() { return M(Type, ^(int n){ return n; }); }", true),
19582 ] {
19583 let tree = parse_cpp(source);
19584 let first = named_node_at(&tree, source, "Type");
19585 let (call, index, actual) = c_function_macro_argument(first)
19586 .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
19587 assert_eq!(index, 0);
19588 assert_eq!(node_text(actual, source), "Type");
19589 let arguments = call.child_by_field_name("arguments").unwrap();
19590 let raw = arguments.named_child(0).unwrap();
19591 if recovered {
19592 assert_eq!(raw.kind(), "binary_expression");
19593 assert_eq!(raw.child_by_field_name("left"), Some(actual));
19594 let inside = named_node_at(&tree, source, "return n;");
19595 let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
19596 assert_eq!(index, 1);
19597 assert_eq!(block.kind(), "compound_literal_expression");
19598 assert_eq!(node_text(block, source), "(int n){ return n; }");
19599 assert_eq!(raw.child_by_field_name("right"), Some(block));
19600 } else {
19601 assert_eq!(raw, actual);
19602 let second = named_node_at(&tree, source, "value");
19603 let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
19604 assert_eq!(index, 1);
19605 assert_eq!(node_text(actual, source), "value");
19606 }
19607 }
19608 }
19609
19610 fn parse_cpp(source: &str) -> Tree {
19611 crate::diagnostics::parse_cpp_tree(source)
19612 }
19613
19614 #[test]
19615 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
19616 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
19617 let source = "auto value = new ns::Box<int> [ 3 ];";
19618 let tree = parse_cpp(source);
19619 let mut pending = vec![tree.root_node()];
19620 let allocation = loop {
19621 let node = pending.pop().expect("new expression in fixture");
19622 if node.kind() == "new_expression" {
19623 break node;
19624 }
19625 let mut cursor = node.walk();
19626 pending.extend(
19627 node.named_children(&mut cursor)
19628 .collect::<Vec<_>>()
19629 .into_iter()
19630 .rev(),
19631 );
19632 };
19633
19634 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
19635 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
19636 assert_eq!(node_text(type_node, source), "ns::Box<int>");
19637 assert_eq!(
19638 allocation
19639 .child_by_field_name("declarator")
19640 .expect("array declarator")
19641 .kind(),
19642 "new_declarator"
19643 );
19644
19645 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
19646 let old_split = old_split
19647 .strip_prefix("new ")
19648 .unwrap_or(old_split.as_str())
19649 .split(['(', '{'])
19650 .next()
19651 .expect("old split result");
19652 assert_ne!(old_split.trim(), node_text(type_node, source));
19653 }
19654
19655 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
19656 let start = source.find(needle).expect("fixture needle");
19657 tree.root_node()
19658 .named_descendant_for_byte_range(start, start + needle.len())
19659 .expect("node at needle")
19660 }
19661
19662 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
19663 let tree = crate::diagnostics::parse_cpp_tree(source);
19664 PreparedSyntaxTree::new(
19665 PreparedSyntaxSource::Exact(Arc::from(source)),
19666 tree,
19667 compute_line_starts(source),
19668 LanguageDialect::Standard(Language::Cpp),
19669 PreparedSourceOrigin::Disk,
19670 None,
19671 )
19672 }
19673
19674 fn unresolved_include_before(source: &str, reference: &str) -> bool {
19675 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
19676 let prepared = prepared_cpp(source);
19677 let facts = collect_structured_include_facts(&prepared);
19678 let include_targets = IncludeTargetIndex::build([&file]);
19679 has_unresolved_include_visible_before_in_prepared(
19680 &file,
19681 &prepared,
19682 &include_targets,
19683 &facts,
19684 source.find(reference).expect("reference fixture"),
19685 )
19686 }
19687
19688 #[test]
19689 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19690 fn unresolved_include_before_reference_is_visible() {
19691 let source = "#include \"missing.h\"\nint use = Missing;\n";
19692 assert!(unresolved_include_before(source, "Missing"));
19693 }
19694
19695 #[test]
19696 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19697 fn unresolved_include_after_reference_is_not_visible() {
19698 let source = "int use = Missing;\n#include \"missing.h\"\n";
19699 assert!(!unresolved_include_before(source, "Missing"));
19700 }
19701
19702 #[test]
19703 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19704 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
19705 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
19706 assert!(!unresolved_include_before(source, "Missing"));
19707 }
19708
19709 #[test]
19710 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19711 fn unresolved_include_in_current_branch_is_visible() {
19712 let source =
19713 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
19714 assert!(unresolved_include_before(source, "Missing"));
19715 }
19716
19717 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
19722namespace matchers {
19723 namespace detail {
19724 class API [[nodiscard]] First {
19725 public:
19726 int value() const { return count_ + 1; }
19727 private:
19728 int count_;
19729 };
19730 class API [[nodiscard]] Second {
19731 public:
19732 int value() const { return count_ + 2; }
19733 private:
19734 int count_;
19735 };
19736 } // namespace detail
19737
19738 template <typename T>
19739 void tail_function(MatcherBase<T> const& value);
19740
19741 class TailClass {};
19742} // namespace matchers
19743} // namespace app
19744
19745struct AfterAll {};
19746"#;
19747
19748 #[test]
19749 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19750 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
19751 let source = STOLEN_BRACE_CASCADE;
19752 let tree = parse_cpp(source);
19753 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19754
19755 let tail_class = named_node_at(&tree, source, "TailClass");
19756 assert!(
19757 !has_ancestor_kind(tail_class, "namespace_definition"),
19758 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
19759 );
19760 let displaced = named_node_at(&tree, source, "Second");
19761 assert_eq!(
19762 enclosing_namespace_components(displaced, source),
19763 Some(vec!["app".to_string(), "matchers".to_string()]),
19764 "the fixture must displace the second class out of detail"
19765 );
19766
19767 let components = |needle: &str| {
19768 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
19769 };
19770 assert_eq!(components("First"), ["app", "matchers", "detail"]);
19771 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
19772 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
19773 assert_eq!(components("tail_function"), ["app", "matchers"]);
19774 assert_eq!(components("TailClass"), ["app", "matchers"]);
19775 assert!(components("AfterAll").is_empty());
19776 }
19777
19778 #[test]
19779 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19780 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
19781 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
19782 let tree = parse_cpp(clean);
19783 assert!(!tree.root_node().has_error());
19784 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
19785
19786 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
19789 let tree = parse_cpp(damaged);
19790 assert!(tree.root_node().has_error());
19791 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
19792 assert_eq!(
19793 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
19794 ["a"]
19795 );
19796 }
19797
19798 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
19805
19806 class Target {
19807 int value_;
19808 };
19809
19810 template <typename T>
19811 class Holder {
19812 public:
19813 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
19814
19815#define HOLDER_DEFINE_OP( id, op ) \
19816 template <typename U> \
19817 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
19818 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
19819 return Target{}; \
19820 }
19821
19822 HOLDER_DEFINE_OP( equal, == )
19823#undef HOLDER_DEFINE_OP
19824 T m_lhs;
19825 };
19826
19827 class Tail {};
19828}
19829"#;
19830
19831 #[test]
19832 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
19833 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
19834 let source = COLLAPSED_NAMESPACE_HEAD;
19835 let tree = parse_cpp(source);
19836 let target = named_node_at(&tree, source, "class Target");
19837
19838 assert!(
19844 tree.root_node().has_error(),
19845 "the fixture must retain the macro invocation's parse damage"
19846 );
19847 assert!(
19848 has_ancestor_kind(target, "namespace_definition"),
19849 "the namespace head survives: the class keeps its parsed namespace ancestor"
19850 );
19851
19852 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19853 assert_eq!(
19854 index.enclosing_namespace_components(target, source),
19855 ["app"]
19856 );
19857 assert_eq!(
19858 index.enclosing_namespace_levels(target, source),
19859 vec![(
19860 "app".to_string(),
19861 Range {
19862 start_byte: 0,
19863 end_byte: source.len() - 1,
19864 start_line: 1,
19865 end_line: 25,
19866 },
19867 Range {
19868 start_byte: 10,
19869 end_byte: 13,
19870 start_line: 1,
19871 end_line: 1,
19872 },
19873 )],
19874 "the recovered namespace owns its full construct, not its first member or just its header"
19875 );
19876 }
19877
19878 #[test]
19879 #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
19880 fn namespace_body_name_components_recovers_literal_function_head() {
19881 let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
19887 let tree = parse_cpp(source);
19888 let keyword_start = source.find("namespace recovered").unwrap();
19889 let keyword = tree
19890 .root_node()
19891 .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
19892 .expect("literal keyword node");
19893 assert_eq!(keyword.kind(), "type_identifier");
19898 assert_eq!(keyword.child_count(), 0);
19899 assert!(!keyword.is_missing() && !keyword.has_error());
19900 assert_eq!(node_text(keyword, source), "namespace");
19901 let wrapper = keyword.parent().expect("actual wrapper");
19902 assert_eq!(wrapper.kind(), "function_definition");
19903 let mut cursor = wrapper.walk();
19904 let children = wrapper
19905 .children(&mut cursor)
19906 .filter(|child| child.kind() != "comment")
19907 .collect::<Vec<_>>();
19908 assert_eq!(
19909 children.len(),
19910 4,
19911 "all named and unnamed children accounted: {children:?}"
19912 );
19913 let prefix = children[0];
19914 assert_eq!(prefix.kind(), "macro_modifier");
19915 assert!(!prefix.is_missing() && !prefix.has_error());
19916 assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
19917 assert_eq!(children[1], keyword);
19918 assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
19919 let name = children[2];
19920 assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
19921 assert_eq!(name.child_count(), 0);
19922 assert!(!name.is_missing() && !name.has_error());
19923 assert_eq!(node_text(name, source), "recovered");
19924 assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
19925 let body = wrapper.child_by_field_name("body").expect("actual body");
19926 assert_eq!(children[3], body);
19927 assert_eq!(body.kind(), "compound_statement");
19928 assert!(!body.is_missing());
19929 let open = body.child(0).expect("actual first child");
19930 assert_eq!(open.kind(), "{");
19931 assert!(!open.is_missing() && !open.has_error());
19932 assert_eq!(open.start_byte(), body.start_byte());
19933 let (head, names) = namespace_body_name_components(wrapper, body, source)
19934 .expect("strict synthetic literal head recognized");
19935 assert_eq!(head, keyword);
19936 assert_eq!(
19937 names
19938 .iter()
19939 .map(|(name, _)| name.as_str())
19940 .collect::<Vec<_>>(),
19941 ["recovered"]
19942 );
19943 assert_eq!(
19944 (names[0].1.start_byte, names[0].1.end_byte),
19945 (name.start_byte(), name.end_byte())
19946 );
19947 let inside = named_node_at(&tree, source, "int member;");
19948 assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
19949 }
19950
19951 #[test]
19952 #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
19953 fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
19954 for refusal in [
19955 "int ordinary(int value) { return value; }",
19956 "OPAQUE_PREFIX namespace_ recovered { int member; }",
19957 "OPAQUE_PREFIX namespace recovered other { int member; }",
19958 "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
19959 ] {
19960 let tree = parse_cpp(refusal);
19961 let mut stack = vec![tree.root_node()];
19962 let mut helper_calls = 0;
19963 while let Some(node) = stack.pop() {
19964 if node.kind() == "function_definition"
19965 && let Some(body) = node.child_by_field_name("body")
19966 {
19967 helper_calls += 1;
19968 assert_eq!(
19969 (node.start_byte(), node.end_byte()),
19970 (0, refusal.len()),
19971 "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
19972 );
19973 assert!(
19974 namespace_body_name_components(node, body, refusal).is_none(),
19975 "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
19976 );
19977 }
19978 let mut cursor = node.walk();
19979 stack.extend(node.named_children(&mut cursor));
19980 }
19981 assert_eq!(
19982 helper_calls,
19983 1,
19984 "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
19985 tree.root_node()
19986 );
19987 }
19988
19989 for absent in [
19995 "OPAQUE_PREFIX namespace recovered = other { int member; }",
19996 "OPAQUE_PREFIX namespace recovered int member;",
19997 ] {
19998 let tree = parse_cpp(absent);
19999 let mut stack = vec![tree.root_node()];
20000 let mut helper_candidates = 0;
20001 while let Some(node) = stack.pop() {
20002 if node.kind() == "function_definition"
20003 && node.child_by_field_name("body").is_some()
20004 {
20005 helper_candidates += 1;
20006 }
20007 let mut cursor = node.walk();
20008 stack.extend(node.named_children(&mut cursor));
20009 }
20010 assert_eq!(
20011 helper_candidates,
20012 0,
20013 "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20014 tree.root_node()
20015 );
20016 }
20017 }
20018
20019 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20028
20029namespace app {
20030namespace detail {
20031
20032bool check(bool active) {
20033 APP_IF_ACTIVE
20034 return true;
20035 }
20036 return false;
20037}
20038
20039class Tail {};
20040
20041} // namespace detail
20042} // namespace app
20043"#;
20044
20045 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20052namespace compile_time {
20053namespace number_parsing {
20054consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20055 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20056 auto digit = [](uint8_t c) -> uint32_t {
20057 }
20058 }
20059 if (a) {
20060 while (a) {
20061 }
20062 }
20063 switch (c) {
20064 case '{': {
20065 }
20066 }
20067 }
20068 while (a) {
20069 cursor += field.second;
20070 if (a) {
20071 case '{': {
20072 if (a) {
20073 }
20074 }
20075 case 'n': {
20076 if (a) {
20077 }
20078 }
20079 }
20080 }
20081 if (a) {
20082 simdjson_consteval_error("Expected '}'");
20083 }
20084}
20085}
20086}
20087"#;
20088
20089 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20091 let mut stack = vec![root];
20092 let mut counts = (0, 0);
20093 while let Some(node) = stack.pop() {
20094 match (node.kind(), node.is_missing()) {
20095 ("{", false) => counts.0 += 1,
20096 ("}", false) => counts.1 += 1,
20097 _ => {}
20098 }
20099 let mut cursor = node.walk();
20100 stack.extend(node.children(&mut cursor));
20101 }
20102 counts
20103 }
20104
20105 fn namespace_open_byte(source: &str, head: &str) -> usize {
20107 let at = source.find(head).expect("fixture namespace head");
20108 at + head.len() - 1
20109 }
20110
20111 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20112 let index = OrphanedNamespaceScopeIndex::build(root, source);
20113 for head in heads {
20114 assert_eq!(
20115 index.matching_close_brace(namespace_open_byte(source, head)),
20116 None,
20117 "{head} must not claim a close belonging to an inner scope"
20118 );
20119 }
20120 }
20121
20122 #[test]
20123 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20124 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20125 let source = MACRO_OPENED_SCOPE;
20126 let tree = parse_cpp(source);
20127 let root = tree.root_node();
20128 assert_eq!(
20129 real_brace_token_counts(root),
20130 (4, 5),
20131 "the fixture must reproduce the loss: one scope the source opens \
20132 has no `{{` token, so the stream holds an extra real close"
20133 );
20134
20135 assert_namespace_heads_have_no_matching_close(
20136 root,
20137 source,
20138 &["namespace app {", "namespace detail {"],
20139 );
20140 }
20141
20142 #[test]
20143 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20144 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20145 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20149 let tree = parse_cpp(&source);
20150 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20151 let after_all = named_node_at(&tree, &source, "struct AfterAll");
20152 assert!(
20153 index
20154 .enclosing_namespace_components(after_all, &source)
20155 .is_empty()
20156 );
20157 }
20158
20159 #[test]
20160 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20161 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20162 let source = REFLECTION_CASE_LABEL_RECOVERY;
20163 let tree = parse_cpp(source);
20164 let root = tree.root_node();
20165 let mut stack = vec![root];
20166 let mut mis_lexed_opens = Vec::new();
20167 while let Some(node) = stack.pop() {
20168 if node.kind() == "character"
20169 && &source[node.start_byte()..node.end_byte()] == "{"
20170 && node
20171 .parent()
20172 .is_some_and(|parent| parent.kind() != "char_literal")
20173 {
20174 mis_lexed_opens.push(node.start_position().row + 1);
20175 }
20176 let mut cursor = node.walk();
20177 stack.extend(node.children(&mut cursor));
20178 }
20179 assert!(
20180 mis_lexed_opens.is_empty(),
20181 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20182 the old ABI14 mis-lex is no longer a valid recovery witness"
20183 );
20184 assert_eq!(
20185 real_brace_token_counts(root),
20186 (17, 17),
20187 "the improved parse retains balanced real brace tokens"
20188 );
20189 assert_eq!(root.kind(), "translation_unit");
20190 assert!(root.has_error(), "the malformed body remains incomplete");
20191 let index = OrphanedNamespaceScopeIndex::build(root, source);
20192 assert_eq!(
20193 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20194 None,
20195 "a missing outer close must not borrow an inner namespace's real close"
20196 );
20197 for (head, close) in [
20198 ("namespace compile_time {", source.len() - 2),
20199 ("namespace number_parsing {", source.len() - 4),
20200 ] {
20201 assert_eq!(
20202 index
20203 .matching_close_brace(namespace_open_byte(source, head))
20204 .map(|range| range.start_byte),
20205 Some(close),
20206 "{head} retains its own real close"
20207 );
20208 }
20209 }
20210
20211 #[test]
20212 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20213 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20214 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20215 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20216 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20217 assert!(indexed_namespace_path_is_recoverable(
20218 &["cache".to_string()],
20219 &indexed,
20220 1,
20221 ));
20222 }
20223
20224 #[test]
20225 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20226 fn sort_lookup_units_totally_orders_every_identity_field() {
20227 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20228 let base = CodeUnit::with_signature(
20229 file.clone(),
20230 CodeUnitType::Function,
20231 "scope",
20232 "value",
20233 Some("()".to_string()),
20234 false,
20235 );
20236 let different_kind = CodeUnit::with_signature(
20237 file.clone(),
20238 CodeUnitType::Field,
20239 "scope",
20240 "value",
20241 Some("()".to_string()),
20242 false,
20243 );
20244 let synthetic = base.with_synthetic(true);
20245
20246 let interner = segment_interner();
20247 let mut member_fq = FqName::new();
20248 member_fq.push(interner.intern("scope", SegmentKind::Package));
20249 member_fq.push(interner.intern("value", SegmentKind::Member));
20250 let different_package_boundary = CodeUnit::from_fq(
20251 file.clone(),
20252 CodeUnitType::Function,
20253 member_fq,
20254 0,
20255 Some("()".to_string()),
20256 false,
20257 );
20258
20259 let mut unknown_fq = FqName::new();
20260 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20261 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20262 let different_segment_kind = CodeUnit::from_fq(
20263 file,
20264 CodeUnitType::Function,
20265 unknown_fq,
20266 1,
20267 Some("()".to_string()),
20268 false,
20269 );
20270
20271 let input = vec![
20272 base,
20273 different_kind,
20274 synthetic,
20275 different_package_boundary,
20276 different_segment_kind,
20277 ];
20278 let mut expected = input.clone();
20279 sort_lookup_units(&mut expected);
20280 assert!(expected.windows(2).all(|pair| {
20281 let mut ordered = pair.to_vec();
20282 sort_lookup_units(&mut ordered);
20283 ordered == pair && pair[0] != pair[1]
20284 }));
20285
20286 let mut reversed = input.clone();
20287 reversed.reverse();
20288 sort_lookup_units(&mut reversed);
20289 assert_eq!(reversed, expected);
20290
20291 let mut rotated = input;
20292 rotated.rotate_left(2);
20293 sort_lookup_units(&mut rotated);
20294 assert_eq!(rotated, expected);
20295 }
20296
20297 #[test]
20298 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20299 fn structured_conditional_array_bound_preserves_the_real_guard() {
20300 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";
20301 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20302 let parse = |source: &str| {
20303 let mut parser = Parser::new();
20304 parser
20305 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20306 .expect("C++ grammar");
20307 parser.parse(source, None).expect("fixture tree")
20308 };
20309
20310 let tree = parse(damaged);
20311 let root = tree.root_node();
20312 assert!(
20313 !root.has_error(),
20314 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20315 root.to_sexp()
20316 );
20317 let target = damaged.find("target").expect("target byte");
20318 let declaration = root
20319 .descendant_for_byte_range(target, target + "target".len())
20320 .and_then(|mut node| {
20321 loop {
20322 if node.kind() == "declaration" {
20323 break Some(node);
20324 }
20325 node = node.parent()?;
20326 }
20327 })
20328 .expect("declaration after the displaced terminator");
20329 let outer = declaration
20330 .parent()
20331 .filter(|node| node.kind() == "preproc_ifdef")
20332 .expect("ordinary outer include guard");
20333 let bound = outer
20334 .named_children(&mut outer.walk())
20335 .filter(|node| node.kind() == "declaration")
20336 .filter_map(|node| node.child_by_field_name("declarator"))
20337 .find(|node| node.kind() == "array_declarator")
20338 .and_then(|node| node.child_by_field_name("size"))
20339 .expect("structured conditional array bound");
20340 assert_eq!(bound.kind(), "preproc_array_size");
20341 let conditional = bound
20342 .named_children(&mut bound.walk())
20343 .find(|node| node.kind() == "preproc_ifdef")
20344 .expect("array-size conditional");
20345 assert!(conditional.end_byte() <= declaration.start_byte());
20346 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20347 assert!(
20348 !std::iter::successors(Some(declaration), |node| node.parent())
20349 .any(|ancestor| same_node(ancestor, conditional)),
20350 "the declaration must not inherit the array-size feature guard"
20351 );
20352 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20353 assert!(preprocessor_conditional_contains_descendant(
20354 outer,
20355 declaration
20356 ));
20357
20358 let tree = parse(guarded);
20359 let conditional = tree
20360 .root_node()
20361 .named_child(0)
20362 .filter(|node| node.kind() == "preproc_ifdef")
20363 .expect("ordinary conditional");
20364 let declaration = conditional
20365 .named_children(&mut conditional.walk())
20366 .find(|node| node.kind() == "declaration")
20367 .expect("guarded declaration");
20368 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20369 assert!(preprocessor_conditional_contains_descendant(
20370 conditional,
20371 declaration
20372 ));
20373
20374 let damaged_alternative = format!(
20375 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20376 "UNUSED(value)\n".repeat(64)
20377 );
20378 let tree = parse(&damaged_alternative);
20379 let conditional = tree
20380 .root_node()
20381 .named_child(0)
20382 .filter(|node| node.kind() == "preproc_ifdef")
20383 .expect("outer conditional with an alternative");
20384 assert!(conditional.has_error());
20385 assert!(conditional.child_by_field_name("alternative").is_some());
20386 assert!(
20387 conditional
20388 .child(conditional.child_count() - 1)
20389 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20390 );
20391 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20392
20393 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";
20394 let tree = parse(split_declaration);
20395 let root = tree.root_node();
20396 let split = root
20403 .named_children(&mut root.walk())
20404 .find(|node| node.kind() == "type_definition")
20405 .expect("the split declaration folds into one type definition");
20406 assert_eq!(
20407 split.end_position().row + 1,
20408 9,
20409 "the split declaration ends at its own declarator line"
20410 );
20411 let target = split_declaration
20412 .find("static int target")
20413 .expect("target byte");
20414 assert!(split.end_byte() <= target);
20415 let target_node = root
20416 .descendant_for_byte_range(target, target + "static".len())
20417 .expect("target node");
20418 assert!(
20419 !std::iter::successors(Some(target_node), |node| node.parent())
20420 .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20421 "the declaration after the shredded split declaration must not \
20422 inherit a damaged conditional ancestor"
20423 );
20424 }
20425
20426 #[test]
20427 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20428 fn fragmented_reference_guard_is_recovered() {
20429 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";
20430 let mut parser = Parser::new();
20431 parser
20432 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20433 .expect("C++ grammar");
20434 let tree = parser.parse(source, None).expect("fixture tree");
20435 let start = source.rfind("helper").expect("reference byte");
20436 let node = tree
20437 .root_node()
20438 .descendant_for_byte_range(start, start + "helper".len())
20439 .expect("reference node");
20440 let mut expected = HashSet::default();
20441 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20442 vec![
20443 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20444 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20445 ],
20446 )));
20447 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20448 }
20449
20450 #[test]
20451 #[hardwon(
20452 "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20453 )]
20454 fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20455 let parse = |source: &str| {
20456 let mut parser = Parser::new();
20457 parser
20458 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20459 .expect("C++ grammar");
20460 parser.parse(source, None).expect("fixture tree")
20461 };
20462 fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20463 let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20464 root.descendant_for_byte_range(start, start + token.len())
20465 .expect("token node")
20466 }
20467 let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20468 let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20469
20470 let source = "namespace n {\n#ifdef A\nBEGIN_NS\nint x;\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#define MODE 2\n#endif\n}\n";
20474 let tree = parse(source);
20475 let root = tree.root_node();
20476 let x = at(root, source, "int x", "x");
20477 let conditional = std::iter::successors(Some(x), |node| node.parent())
20478 .find(|node| node.kind() == "preproc_ifdef")
20479 .expect("ifdef");
20480 assert!(conditional.child_by_field_name("alternative").is_none());
20481 let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20482 assert_eq!(directive.kind(), "#else");
20483 assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20484 let defined = PreprocessorGuard::Defined("A".to_string());
20485 assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20486 let class_name = at(root, source, "class T", "T");
20487 assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20488 let define = at(root, source, "#define MODE", "MODE")
20489 .parent()
20490 .filter(|node| node.kind() == "preproc_def")
20491 .expect("macro event");
20492 let mut cache = PreprocessorContextCache::default();
20493 assert!(matches!(
20494 preprocessor_branch_of_descendant(conditional, define, &mut cache),
20495 PreprocessorBranch::DisplacedElse
20496 ));
20497 assert_eq!(
20498 preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20499 Some(conditional),
20500 "a displaced #else completes the family"
20501 );
20502 assert_eq!(
20503 callable_guard_completion_byte(class_name, source),
20504 Some(conditional.end_byte())
20505 );
20506
20507 let source = "namespace n {\n#if A\nBEGIN_NS\nint x;\nEND_NS\n#elif B\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nstruct T {};\nEND_NS\n#endif\n}\n";
20511 let tree = parse(source);
20512 let root = tree.root_node();
20513 let x = at(root, source, "int x", "x");
20514 let conditional = std::iter::successors(Some(x), |node| node.parent())
20515 .find(|node| node.kind() == "preproc_if")
20516 .expect("if");
20517 assert!(conditional.child_by_field_name("alternative").is_none());
20518 assert_eq!(
20519 cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
20520 Some("#elif")
20521 );
20522 let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
20523 assert_eq!(guards(x, source), Some(set(&[truthy])));
20524 let mut cache = PreprocessorContextCache::default();
20525 for needle in ["class T", "struct T"] {
20526 let name = at(root, source, needle, "T");
20527 assert_eq!(
20528 guards(name, source),
20529 None,
20530 "{needle} after a displaced #elif"
20531 );
20532 assert!(matches!(
20533 preprocessor_branch_of_descendant(conditional, name, &mut cache),
20534 PreprocessorBranch::Unreadable
20535 ));
20536 assert_eq!(
20537 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
20538 None,
20539 "{needle}: an unreadable #elif branch does not complete the family"
20540 );
20541 assert_eq!(callable_guard_completion_byte(name, source), None);
20542 }
20543
20544 let source = "namespace n {\n#if defined(A)\nBEGIN_NS\nint x;\nEND_NS\n#elif defined(B)\nBEGIN_NS\nclass T {};\nEND_NS\n#else\nBEGIN_NS\nclass T {};\nEND_NS\n#endif\n}\n";
20547 let tree = parse(source);
20548 let root = tree.root_node();
20549 let start = source.rfind("class T").expect("else class") + "class ".len();
20550 let name = root
20551 .descendant_for_byte_range(start, start + 1)
20552 .expect("else class name");
20553 let elif = std::iter::successors(Some(name), |node| node.parent())
20554 .find(|node| node.kind() == "preproc_elif")
20555 .expect("structured elif");
20556 assert_eq!(
20557 cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
20558 Some("#else")
20559 );
20560 assert_eq!(
20561 guards(name, source),
20562 Some(set(&[
20563 PreprocessorGuard::Defined("A".to_string()).negated(),
20564 PreprocessorGuard::Defined("B".to_string()).negated(),
20565 ]))
20566 );
20567 let mut cache = PreprocessorContextCache::default();
20568 assert!(
20569 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
20570 );
20571 }
20572
20573 #[test]
20574 #[hardwon(
20575 "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
20576 )]
20577 fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
20578 let source = "API static inline T make(void);\nAPI T value;\nvoid take(API T p);\nstatic inline T plain(void);\nAPI ns::T qualified(void);\nAPI static inline T body(void) { return 0; }\n";
20579 let mut parser = Parser::new();
20580 parser
20581 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20582 .expect("C++ grammar");
20583 let tree = parser.parse(source, None).expect("fixture tree");
20584 let root = tree.root_node();
20585 let type_before = |declared: &str| {
20586 let end = source.find(declared).expect("declared name");
20587 let start = source[..end].rfind('T').expect("type token");
20588 root.descendant_for_byte_range(start, start + 1)
20589 .expect("type node")
20590 };
20591 for declared in ["make(", "value;", "p)", "body("] {
20592 let node = type_before(declared);
20593 assert_eq!(node.kind(), "type_identifier", "{declared}");
20594 assert!(
20595 macro_modifier_decorated_type_node(node),
20596 "{declared}: {}",
20597 node.parent().expect("declaration").to_sexp()
20598 );
20599 }
20600 for declared in ["plain(", "qualified("] {
20601 let node = type_before(declared);
20602 assert!(
20603 !macro_modifier_decorated_type_node(node),
20604 "{declared} is outside the macro-decorated head scope: {}",
20605 node.parent().expect("parent").to_sexp()
20606 );
20607 }
20608 }
20609
20610 #[test]
20611 fn expression_defined_and_ifndef_guards_are_incompatible() {
20612 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
20613 let mut parser = Parser::new();
20614 parser
20615 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20616 .expect("C++ grammar");
20617 let tree = parser.parse(source, None).expect("fixture tree");
20618 let root = tree.root_node();
20619 let selected_start = source.find("selected").expect("selected declaration");
20620 let rejected_start = source.find("rejected").expect("rejected declaration");
20621 let selected = root
20622 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
20623 .expect("selected node");
20624 let rejected = root
20625 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
20626 .expect("rejected node");
20627 let selected_guards =
20628 preprocessor_guard_environment(selected, source).expect("selected guards");
20629 let rejected_guards =
20630 preprocessor_guard_environment(rejected, source).expect("rejected guards");
20631
20632 assert!(
20633 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
20634 "opposite spellings of one macro guard must contradict"
20635 );
20636 }
20637
20638 #[test]
20639 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
20640 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
20641 let source = r#"#ifdef _WIN32
20642#if defined(__cplusplus)
20643extern "C"
20644#endif
20645int platform_api(void);
20646#endif
20647
20648#ifdef _WIN32
20649static int entropy_target(void) { return 0; }
20650#else
20651#ifdef HAVE_COMMON_RANDOM
20652static int other_target(void) { return 0; }
20653#elif defined(HAVE_GETENTROPY)
20654static int entropy_target(void) { return 1; }
20655static int use_entropy(void) { return entropy_target(); }
20656#endif
20657#endif
20658"#;
20659 let mut parser = Parser::new();
20660 parser
20661 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20662 .expect("C++ grammar");
20663 let tree = parser.parse(source, None).expect("fixture tree");
20664 let start = source.rfind("entropy_target()").expect("reference");
20665 let node = tree
20666 .root_node()
20667 .descendant_for_byte_range(start, start + "entropy_target".len())
20668 .expect("reference node");
20669 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
20670 assert!(
20671 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
20672 "{guards:#?}"
20673 );
20674 assert!(
20675 guards.contains(&PreprocessorGuard::Undefined(
20676 "HAVE_COMMON_RANDOM".to_string()
20677 )),
20678 "{guards:#?}"
20679 );
20680 assert!(
20681 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
20682 "{guards:#?}"
20683 );
20684 assert!(
20685 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
20686 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
20687 );
20688 }
20689
20690 #[test]
20691 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
20692 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
20693 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";
20694 let mut parser = Parser::new();
20695 parser
20696 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20697 .expect("C++ grammar");
20698 let tree = parser.parse(source, None).expect("fixture tree");
20699 let root = tree.root_node();
20700 let node_at = |text: &str, start: usize| {
20701 root.descendant_for_byte_range(start, start + text.len())
20702 .expect("token node")
20703 };
20704
20705 let key_start = source.find("case KEY").expect("case label") + "case ".len();
20706 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
20707 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
20708 assert!(!is_ordinary_macro_reference_node(node_at(
20709 "ENABLE_KEYS",
20710 guard_start,
20711 )));
20712 }
20713
20714 #[test]
20715 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20716 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
20717 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";
20718 let mut parser = Parser::new();
20719 parser
20720 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20721 .expect("C++ grammar");
20722 let tree = parser.parse(source, None).expect("fixture tree");
20723 let root = tree.root_node();
20724 let definition_start = source.find("target(void)").expect("definition");
20725 let reference_start = source.rfind("target()").expect("reference");
20726 let definition = root
20727 .descendant_for_byte_range(definition_start, definition_start + "target".len())
20728 .expect("definition node");
20729 let reference = root
20730 .descendant_for_byte_range(reference_start, reference_start + "target".len())
20731 .expect("reference node");
20732 let required =
20733 preprocessor_guard_environment(definition, source).expect("definition guard");
20734 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
20735 assert!(guard_requirements_hold_at_reference(
20736 &required,
20737 Some(&active)
20738 ));
20739 }
20740
20741 #[test]
20742 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20743 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
20744 let source = "g_autoptr(FuChunkArray) self = make_array();";
20745 let mut parser = Parser::new();
20746 parser
20747 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20748 .expect("C++ grammar");
20749 let tree = parser.parse(source, None).expect("fixture tree");
20750 let statement = tree.root_node().named_child(0).expect("statement");
20751 let binding =
20752 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
20753 assert_eq!(binding.name, "self");
20754 assert_eq!(binding.type_name, "FuChunkArray");
20755 assert_eq!(binding.pointer_depth, 1);
20756
20757 let near_miss = "holder(FuChunkArray) self = make_array();";
20758 let tree = parser.parse(near_miss, None).expect("near-miss tree");
20759 let statement = tree.root_node().named_child(0).expect("statement");
20760 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
20761 }
20762
20763 #[test]
20764 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
20765 fn boolean_guard_normalization_proves_equivalence_and_implication() {
20766 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
20767 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
20768 let negated_windows_branch =
20769 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
20770 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
20771 assert_eq!(negated_windows_branch, portable);
20772
20773 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
20774 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
20775 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
20776 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
20777 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
20778 assert!(fallback_branch.implies(&fallback_declaration));
20779 assert!(
20780 BooleanGuardExpression::Truthy("FEATURE".to_string())
20781 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
20782 );
20783 assert!(
20784 BooleanGuardExpression::Undefined("FEATURE".to_string())
20785 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
20786 );
20787 assert!(
20788 !BooleanGuardExpression::Defined("FEATURE".to_string())
20789 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
20790 );
20791 assert!(!fallback_declaration.implies(&fallback_branch));
20792 }
20793
20794 #[test]
20795 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
20796 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
20797 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";
20798 let mut parser = Parser::new();
20799 parser
20800 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20801 .expect("C++ grammar");
20802 let tree = parser.parse(source, None).expect("fixture tree");
20803 let root = tree.root_node();
20804 let call = |marker: &str| {
20805 let start = source.find(marker).expect("call marker");
20806 let mut node = root
20807 .descendant_for_byte_range(start, start + "helper".len())
20808 .expect("call name node");
20809 loop {
20810 if node.kind() == "call_expression" {
20811 break node;
20812 }
20813 node = node.parent().expect("call expression ancestor");
20814 }
20815 };
20816 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
20817 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
20818 let keyword_call = call("helper(NULL, template); /* bound */");
20819 let keyword_arguments = keyword_call
20820 .child_by_field_name("arguments")
20821 .expect("keyword argument list");
20822 assert_eq!(
20823 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
20824 1
20825 );
20826
20827 let nested_call = call("helper(NULL, template); /* nested bound */");
20828 let nested_arguments = nested_call
20829 .child_by_field_name("arguments")
20830 .expect("nested keyword argument list");
20831 assert_eq!(
20832 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
20833 1
20834 );
20835
20836 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
20837 let lambda_arguments = lambda_call
20838 .child_by_field_name("arguments")
20839 .expect("lambda keyword argument list");
20840 assert_eq!(
20841 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
20842 0,
20843 "a lambda body must not borrow the enclosing collapsed C parameter"
20844 );
20845
20846 assert_eq!(
20847 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
20848 0
20849 );
20850
20851 let unbound_call = call("helper(NULL, template); /* unbound */");
20852 let unbound_arguments = unbound_call
20853 .child_by_field_name("arguments")
20854 .expect("unbound argument list");
20855 assert_eq!(
20856 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
20857 0
20858 );
20859 }
20860
20861 #[test]
20862 #[brokk_bifrost_test_macros::hardwon(
20863 "#4407: collapsed C keyword parameters need structural position proof"
20864 )]
20865 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
20866 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
20867int type_keyword(template *value) {
20868 return helper(NULL, template); /* type */
20869}
20870int qualifier_keyword(const *template) {
20871 return helper(NULL, template); /* qualifier */
20872}
20873int annotation_keyword(wchar_t *value __attribute__((template))) {
20874 return helper(NULL, template); /* annotation */
20875}
20876int default_keyword(wchar_t *value = template) {
20877 return helper(NULL, template); /* default */
20878}
20879int nested_expression(wchar_t *value, (template)) {
20880 return helper(NULL, template); /* nested */
20881}
20882int malformed_head = (wchar_t *template) {
20883 return helper(NULL, template); /* malformed */
20884};
20885"#;
20886 let tree = parse_cpp(source);
20887 let root = tree.root_node();
20888 let call = |marker: &str| {
20889 let start = source.find(marker).expect("call marker");
20890 let mut node = root
20891 .descendant_for_byte_range(start, start + "helper".len())
20892 .expect("call name node");
20893 loop {
20894 if node.kind() == "call_expression" {
20895 break Some(node);
20896 }
20897 let Some(parent) = node.parent() else {
20898 break None;
20899 };
20900 node = parent;
20901 }
20902 };
20903 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
20904 let markers = [
20905 ("helper(NULL, template); /* type */", true),
20906 ("helper(NULL, template); /* qualifier */", false),
20907 ("helper(NULL, template); /* annotation */", false),
20908 ("helper(NULL, template); /* default */", false),
20909 ("helper(NULL, template); /* nested */", false),
20910 ("helper(NULL, template); /* malformed */", false),
20911 ];
20912 let mut exercised = 0;
20913 for (marker, expect_call) in markers {
20914 let Some(keyword_call) = call(marker) else {
20921 assert!(
20922 !expect_call,
20923 "{marker} lost its call_expression; the near-miss matrix changed"
20924 );
20925 continue;
20926 };
20927 assert!(
20928 expect_call,
20929 "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
20930 );
20931 exercised += 1;
20932 let keyword_arguments = keyword_call
20933 .child_by_field_name("arguments")
20934 .expect("keyword argument list");
20935 assert_eq!(
20936 recovered_c_keyword_argument_count(
20937 &c_file,
20938 keyword_call,
20939 keyword_arguments,
20940 source,
20941 ),
20942 0,
20943 "keyword in {marker} must not prove a parameter binding"
20944 );
20945 }
20946 assert!(
20947 exercised > 0,
20948 "at least one near-miss marker must exercise the count itself"
20949 );
20950 }
20951
20952 #[test]
20953 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20954 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
20955 let source = r#"#define MAKE(type) type *value
20956MAKE(int *);
20957typedef struct Item Item;
20958struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
20959void gen_op_movl(void *s, int first, int second) { }
20960const char *strZ(const char *value) { return value; }
20961int body(void *s) {
20962 MAKE(int *);
20963 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
20964 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
20965 execvp(strZ(value), UNCONSTIFY(char **, args));
20966}
20967 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
20968 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
20969int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
20970 if (d->units) {
20971 switch (target->nodetype) {
20972 case 1:
20973 case 2:
20974 break;
20975 default:
20976 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
20977 }
20978 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
20979 lysdict_remove(ctx, ((struct Item *)target)->units);
20980 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
20981 }
20982 return 0;
20983 }
20984STATIC EFI_STATUS Encode () { return 0; }
20985"#;
20986 let tree = parse_cpp(source);
20987 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
20988 let top_macro = tree
20989 .root_node()
20990 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
20991 .expect("top macro node");
20992 let body_macro_start = source
20993 .match_indices("MAKE(int *);")
20994 .nth(1)
20995 .expect("body macro")
20996 .0;
20997 let body_macro = tree
20998 .root_node()
20999 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21000 .expect("body macro node");
21001 let function_call_start = source
21002 .find("gen_op_movl(s, offsetof(CPUX86State")
21003 .expect("function call");
21004 let function_call = tree
21005 .root_node()
21006 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21007 .expect("function call node");
21008 let strz_start = source.find("strZ(value)").expect("nested function call");
21009 let strz = tree
21010 .root_node()
21011 .named_descendant_for_byte_range(strz_start, strz_start + 4)
21012 .expect("nested function call node");
21013 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21014 let recovered_call = tree
21015 .root_node()
21016 .named_descendant_for_byte_range(
21017 recovered_call_start,
21018 recovered_call_start + "lysdict_remove".len(),
21019 )
21020 .expect("recovered call node");
21021 let binder_start = source.find("Encode").expect("binder");
21022 let binder = tree
21023 .root_node()
21024 .named_descendant_for_byte_range(binder_start, binder_start + 6)
21025 .expect("binder node");
21026
21027 assert!(recovered_c_function_declarator_invocation(top_macro));
21028 assert!(recovered_c_function_declarator_invocation(body_macro));
21029 assert!(recovered_c_function_declarator_invocation(function_call));
21030 assert!(recovered_c_function_declarator_invocation(strz));
21031 assert!(recovered_c_function_declarator_invocation(recovered_call));
21032 assert!(!recovered_c_function_declarator_invocation(binder));
21033 }
21034
21035 #[test]
21036 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21037 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21038 let source = r#"typedef int krb5_context;
21039int helper(int first, int second) { return first + second; }
21040static krb5_context ctx;
21041int main(int argc, char **argv) {
21042 int ccinitial;
21043 const char *collection_name, *typename;
21044 typename = helper(ctx, ccinitial);
21045 return 0;
21046}
21047"#;
21048 let tree = parse_cpp(source);
21049 let ctx = tree
21050 .root_node()
21051 .descendant_for_byte_range(
21052 source.find("ctx, ccinitial").expect("ctx argument"),
21053 source.find("ctx, ccinitial").expect("ctx argument") + 3,
21054 )
21055 .expect("ctx node");
21056 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21057 let ccinitial = tree
21058 .root_node()
21059 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21060 .expect("sibling node");
21061 let typename = named_node_at(&tree, source, "typename = helper");
21062 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21063
21064 assert_eq!(ctx.kind(), "type_identifier");
21068 assert!(recovered_c_parenthesized_declarator_reference(ctx));
21069 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21070 assert!(!recovered_c_parenthesized_declarator_reference(typename));
21071 assert!(!recovered_c_parenthesized_declarator_reference(helper));
21072 }
21073
21074 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21075 let mut parser = Parser::new();
21076 parser
21077 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21078 .expect("C++ grammar");
21079 let tree = parser.parse(source, None).expect("C++ fixture tree");
21080 let mut stack = vec![tree.root_node()];
21081 while let Some(node) = stack.pop() {
21082 if node.kind() == "enum_specifier" {
21083 return flattened_macro_namespace_components(node, source);
21084 }
21085 let mut cursor = node.walk();
21086 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21087 stack.extend(children.into_iter().rev());
21088 }
21089 None
21090 }
21091
21092 #[test]
21093 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21094 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21095 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21096namespace detail
21097{
21098enum class value_t { null };
21099}
21100NLOHMANN_JSON_NAMESPACE_END
21101NLOHMANN_JSON_NAMESPACE_BEGIN
21102namespace next
21103{
21104struct next_type {};
21105}
21106NLOHMANN_JSON_NAMESPACE_END
21107"#;
21108 assert_eq!(
21109 first_enum_flattened_namespace(complete),
21110 Some(vec!["detail".to_string()])
21111 );
21112
21113 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21114 assert_eq!(
21115 first_enum_flattened_namespace(&stale_end),
21116 Some(vec!["detail".to_string()]),
21117 "a stale end marker before the begin marker must not replace the intended namespace"
21118 );
21119
21120 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21121namespace detail
21122{
21123enum class value_t { null };
21124}
21125struct next_type {};
21126"#;
21127 assert_eq!(first_enum_flattened_namespace(incomplete), None);
21128 }
21129}
21130
21131#[cfg(test)]
21147mod lookup_order_properties {
21148 use super::*;
21149 use proptest::prelude::*;
21150
21151 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21155 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21156 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21160 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21161 const KINDS: [CodeUnitType; 6] = [
21162 CodeUnitType::Class,
21163 CodeUnitType::Function,
21164 CodeUnitType::Field,
21165 CodeUnitType::Module,
21166 CodeUnitType::Macro,
21167 CodeUnitType::FileScope,
21168 ];
21169
21170 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21173 enum ProbedOrder {
21174 Before,
21175 Tied,
21176 After,
21177 Contradictory,
21180 }
21181
21182 impl ProbedOrder {
21183 fn mirror(self) -> Self {
21184 match self {
21185 ProbedOrder::Before => ProbedOrder::After,
21186 ProbedOrder::After => ProbedOrder::Before,
21187 other => other,
21188 }
21189 }
21190
21191 fn signum(self) -> i8 {
21193 match self {
21194 ProbedOrder::Before => -1,
21195 ProbedOrder::Tied => 0,
21196 ProbedOrder::After => 1,
21197 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21198 }
21199 }
21200 }
21201
21202 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21210 if left == right {
21211 return ProbedOrder::Tied;
21214 }
21215 let mut forward = vec![left.clone(), right.clone()];
21216 sort_lookup_units(&mut forward);
21217 let mut backward = vec![right.clone(), left.clone()];
21218 sort_lookup_units(&mut backward);
21219 let left_first = backward[0] == *left;
21220 let right_first = forward[0] == *right;
21221 match (left_first, right_first) {
21222 (true, true) => ProbedOrder::Contradictory,
21223 (true, false) => ProbedOrder::Before,
21224 (false, true) => ProbedOrder::After,
21225 (false, false) => ProbedOrder::Tied,
21226 }
21227 }
21228
21229 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21230 (
21231 0..ROOT_NAMES.len(),
21232 0..REL_PATHS.len(),
21233 0..KINDS.len(),
21234 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21235 0..3usize,
21236 0..SIGNATURES.len(),
21237 any::<bool>(),
21238 )
21239 .prop_map(
21240 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21241 let source = ProjectFile::new(
21242 std::env::temp_dir().join(ROOT_NAMES[root]),
21243 REL_PATHS[rel_path],
21244 );
21245 let interner = segment_interner();
21246 let mut fq = FqName::new();
21247 for (atom, segment_kind) in &segments {
21248 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21249 }
21250 let package_segment_count = package_prefix % fq.len();
21252 CodeUnit::from_fq(
21253 source,
21254 KINDS[kind],
21255 fq,
21256 package_segment_count,
21257 SIGNATURES[signature].map(str::to_string),
21258 synthetic,
21259 )
21260 },
21261 )
21262 }
21263
21264 proptest! {
21265 #![proptest_config(ProptestConfig::with_cases(256))]
21266
21267 #[test]
21270 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21271 fn lookup_order_is_reflexive_and_dual(
21272 left in code_unit_strategy(),
21273 right in code_unit_strategy(),
21274 ) {
21275 prop_assert_eq!(
21276 probe_order(&left, &left),
21277 ProbedOrder::Tied,
21278 "a unit must tie with itself: {:?}",
21279 left
21280 );
21281 let forward = probe_order(&left, &right);
21282 prop_assert_ne!(
21283 forward,
21284 ProbedOrder::Contradictory,
21285 "comparator put each of these strictly first: left={:?} right={:?}",
21286 left,
21287 right
21288 );
21289 prop_assert_eq!(
21290 probe_order(&right, &left),
21291 forward.mirror(),
21292 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21293 left,
21294 right
21295 );
21296 }
21297
21298 #[test]
21300 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21301 fn lookup_order_is_transitive(
21302 a in code_unit_strategy(),
21303 b in code_unit_strategy(),
21304 c in code_unit_strategy(),
21305 ) {
21306 let ab = probe_order(&a, &b);
21307 let bc = probe_order(&b, &c);
21308 let ac = probe_order(&a, &c);
21309 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21310 prop_assert_ne!(
21311 probed,
21312 ProbedOrder::Contradictory,
21313 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21314 pair,
21315 a,
21316 b,
21317 c
21318 );
21319 }
21320 if ab.signum() <= 0 && bc.signum() <= 0 {
21321 prop_assert!(
21322 ac.signum() <= 0,
21323 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21324 a={:?} b={:?} c={:?}",
21325 ab,
21326 bc,
21327 ac,
21328 a,
21329 b,
21330 c
21331 );
21332 }
21333 }
21334
21335 #[test]
21338 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21339 fn lookup_order_separates_distinct_identities(
21340 left in code_unit_strategy(),
21341 right in code_unit_strategy(),
21342 ) {
21343 if probe_order(&left, &right) == ProbedOrder::Tied {
21344 prop_assert_eq!(
21345 &left,
21346 &right,
21347 "distinct identities tied, so their order is whatever order they \
21348 arrived in: left_segments={:?} right_segments={:?}",
21349 left.fq_segments_debug(),
21350 right.fq_segments_debug()
21351 );
21352 }
21353 }
21354
21355 #[test]
21358 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21359 fn lookup_sort_is_permutation_invariant(
21360 units in prop::collection::vec(code_unit_strategy(), 1..=8),
21361 ) {
21362 let mut sorted = units.clone();
21363 sort_lookup_units(&mut sorted);
21364 for rotation in 0..units.len() {
21365 for reversed in [false, true] {
21366 let mut permuted = units.clone();
21367 permuted.rotate_left(rotation);
21368 if reversed {
21369 permuted.reverse();
21370 }
21371 sort_lookup_units(&mut permuted);
21372 prop_assert_eq!(
21373 &permuted,
21374 &sorted,
21375 "sorting a permutation gave a different list \
21376 (rotation={}, reversed={}): input={:?}",
21377 rotation,
21378 reversed,
21379 units
21380 );
21381 }
21382 }
21383 }
21384 }
21385}
21386
21387#[cfg(test)]
21388mod resolver_domain_properties {
21389 use super::*;
21390 use proptest::prelude::*;
21391
21392 proptest! {
21393 #[test]
21396 fn template_substitution_respects_free_variables(
21397 first in 0u16..1000,
21398 second in 1000u16..2000,
21399 unrelated in prop::collection::vec(0u16..1000, 1..8),
21400 rename in "[A-Z][a-z]{0,6}",
21401 ) {
21402 let atom = |value: u16| CppTemplateTerm::Atom {
21403 kind: "number_literal".to_owned(), text: value.to_string(),
21404 };
21405 let term = CppTemplateTerm::Node {
21406 kind: "template_argument_list".to_owned(),
21407 children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21408 };
21409 let mut bindings = HashMap::default();
21410 bindings.insert("T".to_owned(), atom(first));
21411 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21412 for (index, value) in unrelated.into_iter().enumerate() {
21413 bindings.insert(format!("unrelated{index}"), atom(value));
21414 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21415 }
21416 bindings.insert(format!("U{rename}"), atom(second));
21417 let expected = CppTemplateTerm::Node {
21418 kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21419 };
21420 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21421 }
21422
21423 #[test]
21426 fn callable_arity_merge_is_a_symmetric_domain_union(
21427 total in 0usize..20,
21428 left_required in 0usize..20,
21429 right_required in 0usize..20,
21430 left_variadic in any::<bool>(),
21431 right_variadic in any::<bool>(),
21432 ) {
21433 let left = CallableArity::new(left_required.min(total), total, left_variadic);
21434 let right = CallableArity::new(right_required.min(total), total, right_variadic);
21435 let merged = merge_compatible_callable_arities(left, right);
21436 prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21437 if let Some(merged) = merged {
21438 for count in 0..=total + 2 {
21439 prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21440 }
21441 } else {
21442 prop_assert_ne!(left_variadic, right_variadic);
21443 }
21444 }
21445
21446 #[test]
21449 fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21450 let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21451 let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21452 let twice = environment.merged(&environment).unwrap();
21453 prop_assert_eq!(&twice.all, &guards);
21454 prop_assert_eq!(&twice.partial, &guards);
21455 }
21456 }
21457}
21458
21459#[cfg(test)]
21460mod resolver_lookup_key_properties {
21461 use super::*;
21462 use proptest::prelude::*;
21463
21464 proptest! {
21465 #![proptest_config(ProptestConfig::with_cases(32))]
21466
21467 #[test]
21471 fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21472 namespaces in 0usize..3,
21473 owners in 1usize..5,
21474 suffix in "[a-z]{1,6}",
21475 ) {
21476 let mut source = String::new();
21477 let mut components = Vec::new();
21478 for index in 0..namespaces {
21479 let name = format!("ns{index}_{suffix}");
21480 source.push_str(&format!("namespace {name} {{ "));
21481 components.push(name);
21482 }
21483 for index in 0..owners {
21484 let name = format!("Owner{index}");
21485 source.push_str(&format!("struct {name} {{ "));
21486 components.push(name);
21487 }
21488 source.push_str("struct Leaf {}; ");
21489 for _ in 0..owners { source.push_str("}; "); }
21490 for _ in 0..namespaces { source.push_str("} "); }
21491 components.push("Leaf".to_owned());
21492 source.push_str(&format!("{}* value;", components.join("::")));
21493
21494 let tree = crate::diagnostics::parse_cpp_tree(&source);
21495 prop_assert!(!tree.root_node().has_error());
21496 let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21497 let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21498 let leaf = parsed.declarations().iter()
21499 .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21500 .expect("declaration producer indexed the nested type");
21501 let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21502 prop_assert_eq!(declaration.kind(), "declaration");
21503 let type_node = declaration.child_by_field_name("type").unwrap();
21504 let parts = cpp_type_name_components(type_node, &source).unwrap();
21505 prop_assert_eq!(&parts, &components);
21506 let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21507 let declaration_key = leaf.fq_name();
21508 prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21509 }
21510 }
21511}