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 function_macro_replacement_span, normalize_macro_continuations,
17 object_macro_replacement_type_references, 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;
25pub use brokk_bifrost_core::analyzer::cpp_facts::{
26 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
27};
28use brokk_bifrost_core::analyzer::cpp_facts::{
29 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
30 CppStructuredAliasTarget as StructuredAliasTarget,
31};
32use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
33use brokk_bifrost_core::analyzer::model::{
34 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
35 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
36 SignatureMetadata, StructuredTypeName,
37};
38use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
39use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
40#[cfg(test)]
41use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
42use brokk_bifrost_core::analyzer::query_token::QueryToken;
43use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
44use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
45use brokk_bifrost_core::analyzer::tree_walk::{
46 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
47 named_children_iter, node_for_exact_range, push_named_children_reversed,
48};
49use brokk_bifrost_core::analyzer::usages::common::same_node;
50use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
51use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
52use brokk_bifrost_core::cancellation::CancellationToken;
53use brokk_bifrost_core::hash::{HashMap, HashSet};
54#[cfg(test)]
55use brokk_bifrost_core::text_utils::compute_line_starts;
56use std::borrow::Cow;
57#[cfg(any(test, feature = "test-support"))]
58use std::cell::Cell;
59use std::cell::OnceCell;
60use std::cmp::Ordering as CmpOrdering;
61use std::collections::BTreeSet;
62use std::hash::Hash;
63use std::sync::atomic::{AtomicUsize, Ordering};
64use std::sync::{Arc, Mutex, OnceLock, RwLock};
65use std::time::{Duration, Instant};
66use tree_sitter::{Node, Parser, Tree};
67
68const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
69const CALLABLE_METADATA_CACHE_CAP: usize = 512;
70const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
71
72#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
73struct PreprocessorContextNodeKey {
74 id: usize,
75 start_byte: usize,
76 end_byte: usize,
77}
78
79#[derive(Default)]
88pub(crate) struct PreprocessorContextCache {
89 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
90 displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
91}
92
93#[derive(Clone, Copy)]
96struct DisplacedBranchDirective {
97 end_byte: usize,
98 is_else: bool,
99}
100
101impl PreprocessorContextCache {
102 fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
103 let key = PreprocessorContextNodeKey {
104 id: conditional.id(),
105 start_byte: conditional.start_byte(),
106 end_byte: conditional.end_byte(),
107 };
108 if let Some(directive) = self.displaced_branches.get(&key) {
109 return *directive;
110 }
111 let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
112 DisplacedBranchDirective {
113 end_byte: directive.end_byte(),
114 is_else: directive.kind() == "#else",
115 }
116 });
117 if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
118 self.displaced_branches.insert(key, directive);
119 }
120 directive
121 }
122
123 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
124 let key = PreprocessorContextNodeKey {
125 id: conditional.id(),
126 start_byte: conditional.start_byte(),
127 end_byte: conditional.end_byte(),
128 };
129 if let Some(boundary) = self.displaced_boundaries.get(&key) {
130 return *boundary;
131 }
132 let boundary =
133 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
134 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
135 self.displaced_boundaries.insert(key, boundary);
136 }
137 boundary
138 }
139}
140
141#[cfg(any(test, feature = "test-support"))]
142thread_local! {
143 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
144 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
145}
146
147#[derive(Clone, Copy, PartialEq, Eq)]
148pub enum TargetKind {
149 Type,
150 Constructor,
151 FreeFunction,
152 Method,
153 GlobalField,
154 MemberField,
155 Macro,
156}
157
158pub enum LexicalTypeResolution {
159 Resolved {
160 unit: CodeUnit,
161 components: Vec<String>,
162 candidates: Vec<CodeUnit>,
163 },
164 Ambiguous,
165 Missing,
166}
167
168#[derive(Clone, Copy)]
169enum TypeCandidateResolution<'a> {
170 Canonical,
171 PreserveAlias,
172 PreserveTarget(&'a CodeUnit),
173}
174
175#[derive(Clone, Copy, Debug, PartialEq, Eq)]
184enum TypeCandidateFailure {
185 Ambiguous,
186 Unresolvable,
187}
188
189impl TypeCandidateFailure {
190 fn lexical_resolution(self) -> LexicalTypeResolution {
191 match self {
192 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
193 Self::Unresolvable => LexicalTypeResolution::Missing,
194 }
195 }
196}
197
198pub enum LexicalCallableValueResolution {
199 Type(CodeUnit),
200 FreeFunction(CodeUnit),
201 Ambiguous,
202 Missing,
203}
204
205pub enum UsingEnumMemberResolution {
206 Resolved { owner: CodeUnit, member: CodeUnit },
207 Ambiguous,
208 Missing,
209}
210
211pub enum NamespaceValueResolution {
212 Resolved,
213 Ambiguous,
214 Missing,
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub enum OrdinaryMacroReferenceResolution {
219 Resolved(CodeUnit),
220 Ambiguous,
221 Missing,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
225pub enum RecoveredCReferenceRanges {
226 Complete(Vec<Range>),
227 LimitExceeded,
228}
229
230pub fn resolve_namespace_value(
231 analyzer: &CppGraphSource<'_>,
232 visibility: &VisibilityIndex<'_>,
233 file: &ProjectFile,
234 namespace: &str,
235 name: &str,
236 before_byte: usize,
237) -> NamespaceValueResolution {
238 let mut matches = Vec::new();
239 for candidate in visibility.visible_identifier_candidates(file, name) {
240 if type_owner_of(analyzer, candidate).is_some()
241 || candidate.package_name() != namespace
242 || (candidate.source() == file
243 && !analyzer
244 .ranges(candidate)
245 .iter()
246 .any(|range| range.start_byte < before_byte))
247 || matches
248 .iter()
249 .any(|existing| same_visible_symbol(existing, candidate))
250 {
251 continue;
252 }
253 matches.push(candidate.clone());
254 if matches.len() > 1 {
255 return NamespaceValueResolution::Ambiguous;
256 }
257 }
258 matches
259 .pop()
260 .map(|_| NamespaceValueResolution::Resolved)
261 .unwrap_or(NamespaceValueResolution::Missing)
262}
263
264pub(crate) struct ScopedUsingEnumOwners {
265 scopes: Vec<Vec<CodeUnit>>,
266}
267
268pub(crate) struct SemanticUsingEnumOwners {
273 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
274 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
275}
276
277pub(crate) enum SemanticUsingEnumMemberResolution {
278 Class(UsingEnumMemberResolution),
279 Namespace(UsingEnumMemberResolution),
280 Missing,
281}
282
283impl SemanticUsingEnumOwners {
284 pub(crate) fn new() -> Self {
285 Self {
286 class_imports: HashMap::default(),
287 namespace_imports: HashMap::default(),
288 }
289 }
290
291 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
292 let imports = self.class_imports.entry(class).or_default();
293 if !imports
294 .iter()
295 .any(|existing| same_visible_symbol(existing, &enum_owner))
296 {
297 imports.push(enum_owner);
298 }
299 }
300
301 pub fn import_namespace(
302 &mut self,
303 namespace: Vec<String>,
304 declaration_byte: usize,
305 enum_owner: CodeUnit,
306 ) {
307 let imports = self.namespace_imports.entry(namespace).or_default();
308 if !imports
309 .iter()
310 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
311 {
312 imports.push((declaration_byte, enum_owner));
313 }
314 }
315
316 pub fn resolve_member(
317 &self,
318 visibility: &VisibilityIndex<'_>,
319 file: &ProjectFile,
320 class: Option<&CodeUnit>,
321 namespace: &[String],
322 before_byte: usize,
323 name: &str,
324 ) -> SemanticUsingEnumMemberResolution {
325 if let Some(class) = class
326 && let Some((_, imports)) = self
327 .class_imports
328 .iter()
329 .find(|(owner, _)| same_visible_symbol(owner, class))
330 {
331 let resolution =
332 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
333 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
334 return SemanticUsingEnumMemberResolution::Class(resolution);
335 }
336 }
337 for prefix_len in (0..=namespace.len()).rev() {
338 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
339 continue;
340 };
341 let owners = imports
342 .iter()
343 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
344 .map(|(_, owner)| owner);
345 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
346 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
347 return SemanticUsingEnumMemberResolution::Namespace(resolution);
348 }
349 }
350 SemanticUsingEnumMemberResolution::Missing
351 }
352}
353
354fn resolve_using_enum_member_for_owners<'a>(
355 visibility: &VisibilityIndex<'_>,
356 file: &ProjectFile,
357 owners: impl IntoIterator<Item = &'a CodeUnit>,
358 name: &str,
359) -> UsingEnumMemberResolution {
360 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
361 for owner in owners {
362 for member in visibility.visible_members_for_owner_name(file, owner, name) {
363 if !member.is_field()
364 || matches.iter().any(|(existing_owner, existing_member)| {
365 same_visible_symbol(existing_owner, owner)
366 && same_visible_symbol(existing_member, member)
367 })
368 {
369 continue;
370 }
371 matches.push((owner.clone(), member.clone()));
372 }
373 }
374 match matches.len() {
375 0 => UsingEnumMemberResolution::Missing,
376 1 => {
377 let (owner, member) = matches.pop().expect("one using-enum match");
378 UsingEnumMemberResolution::Resolved { owner, member }
379 }
380 _ => UsingEnumMemberResolution::Ambiguous,
381 }
382}
383
384impl ScopedUsingEnumOwners {
385 pub(crate) fn new() -> Self {
386 Self {
387 scopes: vec![Vec::new()],
388 }
389 }
390
391 pub fn enter_scope(&mut self) {
392 self.scopes.push(Vec::new());
393 }
394
395 pub fn exit_scope(&mut self) {
396 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
397 }
398
399 pub fn import(&mut self, owner: CodeUnit) {
400 let scope = self
401 .scopes
402 .last_mut()
403 .expect("using-enum scope stack is never empty");
404 if !scope
405 .iter()
406 .any(|existing| same_visible_symbol(existing, &owner))
407 {
408 scope.push(owner);
409 }
410 }
411
412 pub fn resolve_member(
413 &self,
414 visibility: &VisibilityIndex<'_>,
415 file: &ProjectFile,
416 name: &str,
417 ) -> UsingEnumMemberResolution {
418 for scope in self.scopes.iter().rev() {
419 let resolution =
420 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
421 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
422 return resolution;
423 }
424 }
425 UsingEnumMemberResolution::Missing
426 }
427}
428
429#[derive(Clone)]
430pub struct TargetSpec {
431 pub target: CodeUnit,
432 pub kind: TargetKind,
433 pub owner: Option<CodeUnit>,
434 pub member_name: String,
435 pub callable_arity: Option<CallableArity>,
436 pub activated_callable_arities: Vec<ActivatedCallableArity>,
437 pub param_types: Option<Vec<String>>,
438 pub enum_owner_kind: EnumOwnerKind,
439 pub owner_is_forward_declaration: bool,
440 pub callable_has_definition_body: bool,
441}
442
443#[derive(Clone, Copy)]
444pub struct ActivatedCallableArity {
445 pub activation_byte: usize,
446 pub arity: CallableArity,
447}
448
449#[derive(Debug, PartialEq, Eq, Hash)]
450pub struct TypeScanKey {
451 target: LogicalSymbolKey,
452 member_name: String,
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Hash)]
456struct LogicalSymbolKey {
457 kind: CodeUnitType,
458 fq_name: String,
459 signature: Option<String>,
460}
461
462struct ResolvedTypeOwner {
463 unit: CodeUnit,
464 is_forward_declaration: bool,
465}
466
467impl TargetSpec {
468 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
469 (self.kind == TargetKind::Type).then(|| TypeScanKey {
470 target: logical_symbol_key(&self.target),
471 member_name: self.member_name.clone(),
472 })
473 }
474
475 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
476 if target.is_class() {
477 return Some(Self::new(
478 target.clone(),
479 TargetKind::Type,
480 Some(target.clone()),
481 target.identifier().to_string(),
482 None,
483 None,
484 ));
485 }
486
487 if target.is_field() {
488 let owner = type_owner_of(analyzer, target);
494 let kind = if owner.is_some() {
495 TargetKind::MemberField
496 } else {
497 TargetKind::GlobalField
498 };
499 let enum_owner_kind = owner
500 .as_ref()
501 .map(|owner| classify_enum_owner(analyzer, owner))
502 .unwrap_or(EnumOwnerKind::NonEnum);
503 let mut spec = Self::new(
504 target.clone(),
505 kind,
506 owner,
507 target.identifier().to_string(),
508 None,
509 None,
510 );
511 spec.enum_owner_kind = enum_owner_kind;
512 return Some(spec);
513 }
514
515 if target.is_function() {
516 let owner_resolution = target_type_owner_resolution(analyzer, target);
519 let owner_is_forward_declaration = owner_resolution
520 .as_ref()
521 .is_some_and(|owner| owner.is_forward_declaration);
522 let owner = owner_resolution.map(|owner| owner.unit);
523 let kind = if owner.as_ref().is_some_and(|owner| {
524 target.identifier() == owner.identifier()
525 || analyzer
526 .cpp
527 .and_then(|cpp| cpp.template_metadata(owner))
528 .is_some_and(|metadata| metadata.primary_name == target.identifier())
529 }) {
530 TargetKind::Constructor
531 } else if owner.is_some() {
532 TargetKind::Method
533 } else {
534 TargetKind::FreeFunction
535 };
536 let mut spec = Self::new(
537 target.clone(),
538 kind,
539 owner,
540 target.identifier().to_string(),
541 cpp_callable_arity(analyzer, target),
542 cpp_callable_parameter_types(analyzer, target),
543 );
544 spec.owner_is_forward_declaration = owner_is_forward_declaration;
545 spec.callable_has_definition_body =
546 callable_target_has_definition_body(analyzer, target);
547 return Some(spec);
548 }
549
550 if target.is_macro() {
551 return Some(Self::new(
552 target.clone(),
553 TargetKind::Macro,
554 None,
555 target.identifier().to_string(),
556 None,
557 None,
558 ));
559 }
560
561 None
562 }
563
564 pub fn with_visible_callable_arities<'a>(
565 &'a self,
566 analyzer: &CppGraphSource<'_>,
567 cpp: &dyn CppSource,
568 visibility: &VisibilityIndex<'_>,
569 file: &ProjectFile,
570 prepared: &PreparedSyntaxTree,
571 ) -> Cow<'a, Self> {
572 let macro_parameter_arity =
573 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
574 let activated_callable_arities =
575 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
576 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
577 return Cow::Borrowed(self);
578 }
579 let mut effective = self.clone();
580 if let Some(macro_parameter_arity) = macro_parameter_arity {
581 effective.callable_arity = Some(macro_parameter_arity);
582 }
583 effective.activated_callable_arities = activated_callable_arities;
584 Cow::Owned(effective)
585 }
586
587 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
588 let base = self.callable_arity?;
589 Some(
590 self.activated_callable_arities
591 .iter()
592 .filter(|candidate| candidate.activation_byte <= byte)
593 .fold(base, |arity, candidate| {
594 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
595 }),
596 )
597 }
598
599 pub fn new(
600 target: CodeUnit,
601 kind: TargetKind,
602 owner: Option<CodeUnit>,
603 member_name: String,
604 callable_arity: Option<CallableArity>,
605 param_types: Option<Vec<String>>,
606 ) -> Self {
607 Self {
608 target,
609 kind,
610 owner,
611 member_name,
612 callable_arity,
613 activated_callable_arities: Vec::new(),
614 param_types,
615 enum_owner_kind: EnumOwnerKind::NonEnum,
616 owner_is_forward_declaration: false,
617 callable_has_definition_body: false,
618 }
619 }
620}
621
622fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
623 analyzer
624 .signature_metadata(target)
625 .iter()
626 .any(|metadata| !metadata.is_declaration_only())
627}
628
629fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
630 LogicalSymbolKey {
631 kind: unit.kind(),
632 fq_name: unit.fq_name(),
633 signature: unit.signature().map(str::to_string),
634 }
635}
636
637fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
638 let Some(facts) = analyzer.declaration_source_properties(owner) else {
639 return EnumOwnerKind::NonEnum;
640 };
641 let Some(first) = facts.first() else {
642 return EnumOwnerKind::NonEnum;
643 };
644 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
645 first.enum_kind
646 } else {
647 EnumOwnerKind::NonEnum
648 }
649}
650
651#[derive(Clone, PartialEq, Eq, Hash)]
652pub struct CppScanBinding {
653 pub unit: Option<CodeUnit>,
654 pub type_name: Option<String>,
655 pub indirection: i32,
656}
657
658impl CppScanBinding {
659 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
660 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
661 }
662
663 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
664 Self {
665 type_name: Some(type_name),
666 unit,
667 indirection,
668 }
669 }
670
671 pub fn as_arg_type(&self) -> Option<CppArgType> {
672 let name = self
673 .type_name
674 .clone()
675 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
676 Some(CppArgType {
677 name,
678 unit: self.unit.clone(),
679 indirection: self.indirection,
680 pointee_const: false,
681 })
682 }
683}
684
685type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
686pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
687pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
688type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
689type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
690type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
691type MacroLexicalTemplateCache =
692 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
693
694type MacroLocalBindingTemplateCache =
695 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
696type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
697type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
698type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
699
700struct StructuredIncludeFact {
701 start_byte: usize,
702 end_byte: usize,
703 path: String,
704}
705
706#[derive(Clone, Default)]
707pub struct MacroEnvironment {
708 bindings: HashMap<String, MacroBinding>,
709 known_undefined_names: HashSet<String>,
710 build_proven_defines: HashSet<String>,
715 unknown_names: bool,
716 applied_pragma_once_files: HashSet<ProjectFile>,
717 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
718}
719
720pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
729
730struct MacroEnvironmentCheckpoint {
732 frontier: usize,
734 environment: Arc<MacroEnvironment>,
735}
736
737struct MacroEnvironmentCheckpoints {
746 checkpoints: Vec<MacroEnvironmentCheckpoint>,
747}
748
749impl MacroEnvironmentCheckpoints {
750 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
752 let index = self
753 .checkpoints
754 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
755 assert!(
756 index > 0,
757 "a checkpoint vector starts at frontier zero, which precedes every request"
758 );
759 &self.checkpoints[index - 1]
760 }
761}
762
763fn clone_or_default_file_cell<T: Default + Clone>(
764 cells: &Mutex<HashMap<ProjectFile, T>>,
765 file: &ProjectFile,
766 poison_message: &str,
767) -> T {
768 cells
769 .lock()
770 .expect(poison_message)
771 .entry(file.clone())
772 .or_default()
773 .clone()
774}
775
776impl MacroEnvironment {
777 fn binding(&self, name: &str) -> Option<&MacroBinding> {
778 self.bindings.get(name)
779 }
780
781 fn may_bind(&self, name: &str) -> bool {
782 self.bindings.contains_key(name) || self.unknown_names
783 }
784
785 fn insert(&mut self, name: String, binding: MacroBinding) {
786 self.known_undefined_names.remove(&name);
787 self.bindings.insert(name, binding);
788 }
789
790 fn remove(&mut self, name: &str) {
791 self.bindings.remove(name);
792 self.known_undefined_names.insert(name.to_string());
793 }
794
795 fn remove_known_undefined(&mut self, name: &str) {
796 self.known_undefined_names.remove(name);
797 }
798
799 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
800 for binding in self.bindings.values_mut() {
801 *binding = MacroBinding::uncertain_from(binding, source, byte);
802 }
803 self.known_undefined_names.clear();
804 self.build_proven_defines.clear();
809 self.unknown_names = true;
810 }
811
812 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
813 guards.iter().all(|guard| self.guard_may_hold(guard))
814 }
815
816 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
817 let Some(expression) = guard.as_boolean_expression() else {
818 return true;
819 };
820 self.boolean_guard_may_hold(&expression)
821 }
822
823 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
824 match expression {
825 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
826 BooleanGuardExpression::Undefined(name) => {
827 self.bindings
828 .get(name)
829 .is_none_or(|binding| !binding.is_exact())
830 && (!self.build_proven_defines.contains(name)
831 || self.known_undefined_names.contains(name))
832 }
833 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
834 BooleanGuardExpression::Opaque(_)
835 | BooleanGuardExpression::NegatedOpaque(_)
836 | BooleanGuardExpression::Constant(true) => true,
837 BooleanGuardExpression::Constant(false) => false,
838 BooleanGuardExpression::All(expressions) => expressions
839 .iter()
840 .all(|expression| self.boolean_guard_may_hold(expression)),
841 BooleanGuardExpression::Any(expressions) => expressions
842 .iter()
843 .any(|expression| self.boolean_guard_may_hold(expression)),
844 }
845 }
846}
847
848fn any_visible_source_file_matches(
849 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
850 file: &ProjectFile,
851 mut predicate: impl FnMut(&ProjectFile) -> bool,
852) -> bool {
853 match visible_source_files_by_root.get(file) {
854 Some(files) => files.iter().any(predicate),
855 None => predicate(file),
856 }
857}
858
859#[derive(Clone)]
860pub enum EffectiveUsingTarget {
861 Ordinary {
862 name: String,
863 target_components: Vec<String>,
864 global: bool,
865 },
866 Namespace {
867 namespace_components: Vec<String>,
868 global: bool,
869 },
870}
871
872#[derive(Clone)]
873pub struct OrdinaryTypeImport {
874 pub target: EffectiveUsingTarget,
875 pub source: ProjectFile,
876 pub declaration_byte: usize,
877 pub scope_start: usize,
878 pub scope_end: usize,
879 pub scope_depth: usize,
880 pub block_scope: bool,
881 pub lexical_depth: usize,
882 pub declaration_namespace: Vec<String>,
883 pub namespace_scope: Option<Vec<String>>,
884 pub resolved_target_components: Option<Vec<String>>,
885 pub required_guards: HashSet<PreprocessorGuard>,
886}
887
888#[derive(Clone)]
889pub struct ConditionalIncludeProjection {
890 pub activation_byte: usize,
891 pub required_guards: HashSet<PreprocessorGuard>,
892 pub partial_guards: HashSet<PreprocessorGuard>,
896}
897
898#[derive(Default)]
899pub struct SourceUsingIndex {
900 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
901 pub directives: Vec<OrdinaryTypeImport>,
902}
903
904#[derive(Default)]
905pub struct ProjectUsingIndex {
906 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
907 pub directives: Vec<OrdinaryTypeImport>,
908}
909
910type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
911
912pub struct EffectiveUsingIndex {
913 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
914}
915
916impl EffectiveUsingIndex {
917 fn new(_root: ProjectFile) -> Self {
918 Self {
919 projected_by_name: Mutex::new(HashMap::default()),
920 }
921 }
922
923 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
924 self.projected_by_name
925 .lock()
926 .expect("C++ effective-using projection cache poisoned")
927 .entry(name.to_string())
928 .or_default()
929 .clone()
930 }
931}
932
933pub enum OrdinaryTypeImportResolution {
934 Resolved {
935 target: CodeUnit,
936 target_components: Vec<String>,
937 lexical_depth: usize,
938 is_direct: bool,
939 },
940 Ambiguous {
941 lexical_depth: usize,
942 },
943 Missing,
944}
945
946type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
947type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
948type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
949type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
950type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
951type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
952type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
953type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
954
955struct ExtractedComparable {
960 shapes: Vec<CppComparableSlot>,
961 suffix: String,
962}
963
964const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
968
969#[derive(Clone, Copy, Debug, PartialEq, Eq)]
991enum IncludePathAdmission {
992 Proven,
993 Compatible,
994}
995
996impl IncludePathAdmission {
997 fn admits(
998 self,
999 required: &HashSet<PreprocessorGuard>,
1000 partial: &HashSet<PreprocessorGuard>,
1001 reference_guards: Option<&HashSet<PreprocessorGuard>>,
1002 ) -> bool {
1003 match self {
1004 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1005 Self::Compatible => {
1006 guard_requirements_hold_at_reference(partial, reference_guards)
1007 && guards_compatible_at_reference(required, reference_guards)
1008 }
1009 }
1010 }
1011}
1012
1013pub struct VisibilityIndex<'a> {
1017 cpp: &'a dyn CppSource,
1018 token: QueryToken<'a>,
1019 data: Arc<VisibilityIndexData>,
1020}
1021
1022impl std::ops::Deref for VisibilityIndex<'_> {
1023 type Target = VisibilityIndexData;
1024
1025 fn deref(&self) -> &Self::Target {
1026 &self.data
1027 }
1028}
1029
1030pub struct VisibilityIndexData {
1033 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1034 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1035 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1036 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1037 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1038 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1039 parser_alias_target_matches:
1040 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1041 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1042 project_using_index: OnceLock<ProjectUsingIndex>,
1043 callable_reference_specs:
1044 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1045 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1046 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1047 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1048 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1049 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1050 #[cfg(any(test, feature = "test-support"))]
1051 conditional_include_projection_index_build_count: AtomicUsize,
1052 #[cfg(any(test, feature = "test-support"))]
1053 conditional_include_projection_state_count: AtomicUsize,
1054 #[cfg(any(test, feature = "test-support"))]
1055 conditional_include_target_state_count: AtomicUsize,
1056 #[cfg(any(test, feature = "test-support"))]
1057 include_activation_build_count: AtomicUsize,
1058 #[cfg(any(test, feature = "test-support"))]
1059 using_donor_activation_count: AtomicUsize,
1060 #[cfg(any(test, feature = "test-support"))]
1061 using_namespace_lookup_count: AtomicUsize,
1062 #[cfg(any(test, feature = "test-support"))]
1063 using_name_candidate_inspection_count: AtomicUsize,
1064 #[cfg(any(test, feature = "test-support"))]
1065 callable_reference_spec_build_count: AtomicUsize,
1066 #[cfg(any(test, feature = "test-support"))]
1067 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1068 #[cfg(any(test, feature = "test-support"))]
1069 visible_parser_alias_name_set_build_count: AtomicUsize,
1070 parser_alias_fallback_calls: AtomicUsize,
1071 parser_alias_fallback_files: AtomicUsize,
1072 parser_alias_source_parses: AtomicUsize,
1073 parser_alias_fallback_elapsed_micros: AtomicUsize,
1074 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1075 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1076 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1077 callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1083 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1084 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1085 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1086 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1087 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1088 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1089 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1090 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1091 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1092 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1099 macro_replacements: Mutex<MacroReplacementCache>,
1100 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1101 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1102 macro_lexical_indexes:
1103 Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1104 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1105 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1106 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1107 #[cfg(any(test, feature = "test-support"))]
1108 pub macro_replacement_parse_count: AtomicUsize,
1109 #[cfg(any(test, feature = "test-support"))]
1110 pub macro_event_application_count: AtomicUsize,
1111 #[cfg(any(test, feature = "test-support"))]
1114 pub macro_environment_checkpoint_build_count: AtomicUsize,
1115 #[cfg(any(test, feature = "test-support"))]
1118 pub macro_environment_copy_count: AtomicUsize,
1119 #[cfg(any(test, feature = "test-support"))]
1120 pub macro_environment_request_count: AtomicUsize,
1121 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1122 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1123 #[cfg(any(test, feature = "test-support"))]
1124 qualified_candidate_inspections: AtomicUsize,
1125 #[cfg(any(test, feature = "test-support"))]
1126 target_preserving_type_resolution_count: AtomicUsize,
1127 #[cfg(any(test, feature = "test-support"))]
1128 visibility_identifier_lookup_count: usize,
1129 #[cfg(any(test, feature = "test-support"))]
1130 visibility_identifier_batch_count: usize,
1131}
1132
1133impl Drop for VisibilityIndexData {
1134 fn drop(&mut self) {
1135 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1136 return;
1137 }
1138 #[cfg(any(test, feature = "test-support"))]
1139 eprintln!(
1140 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1141 self.macro_environment_request_count.load(Ordering::Relaxed),
1142 self.macro_environment_copy_count.load(Ordering::Relaxed),
1143 self.macro_environment_checkpoint_build_count
1144 .load(Ordering::Relaxed),
1145 self.macro_event_application_count.load(Ordering::Relaxed),
1146 );
1147 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1148 if calls == 0 {
1149 return;
1150 }
1151 eprintln!(
1152 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1153 calls,
1154 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1155 self.parser_alias_source_parses.load(Ordering::Relaxed),
1156 self.parser_alias_fallback_elapsed_micros
1157 .load(Ordering::Relaxed)
1158 / 1_000,
1159 );
1160 }
1161}
1162
1163#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1164pub enum PreprocessorGuard {
1165 Defined(String),
1166 Undefined(String),
1167 Boolean(BooleanGuardExpression),
1168 Expression(String),
1169 NegatedExpression(String),
1170 Constant(bool),
1171}
1172
1173#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1174pub enum BooleanGuardExpression {
1175 Defined(String),
1176 Undefined(String),
1177 Truthy(String),
1178 Falsy(String),
1179 Opaque(String),
1180 NegatedOpaque(String),
1181 All(Vec<BooleanGuardExpression>),
1182 Any(Vec<BooleanGuardExpression>),
1183 Constant(bool),
1184}
1185
1186impl BooleanGuardExpression {
1187 fn negated(&self) -> Self {
1188 match self {
1189 Self::Defined(name) => Self::Undefined(name.clone()),
1190 Self::Undefined(name) => Self::Defined(name.clone()),
1191 Self::Truthy(name) => Self::Falsy(name.clone()),
1192 Self::Falsy(name) => Self::Truthy(name.clone()),
1193 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1194 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1195 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1196 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1197 Self::Constant(value) => Self::Constant(!value),
1198 }
1199 }
1200
1201 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1202 Self::normalized(expressions, true)
1203 }
1204
1205 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1206 Self::normalized(expressions, false)
1207 }
1208
1209 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1210 let mut normalized = Vec::new();
1211 for expression in expressions {
1212 match expression {
1213 Self::All(nested) if conjunction => normalized.extend(nested),
1214 Self::Any(nested) if !conjunction => normalized.extend(nested),
1215 Self::Constant(value) if value == conjunction => {}
1216 Self::Constant(value) => return Self::Constant(value),
1217 expression => normalized.push(expression),
1218 }
1219 }
1220 normalized.sort_unstable();
1221 normalized.dedup();
1222 match normalized.len() {
1223 0 => Self::Constant(conjunction),
1224 1 => normalized.pop().expect("one Boolean guard expression"),
1225 _ if conjunction => Self::All(normalized),
1226 _ => Self::Any(normalized),
1227 }
1228 }
1229
1230 fn implies(&self, required: &Self) -> bool {
1231 if self == required
1232 || matches!(self, Self::Constant(false))
1233 || matches!(required, Self::Constant(true))
1234 {
1235 return true;
1236 }
1237 if matches!(
1238 (self, required),
1239 (Self::Truthy(active), Self::Defined(required))
1240 | (Self::Undefined(active), Self::Falsy(required))
1241 if active == required
1242 ) {
1243 return true;
1244 }
1245 match self {
1246 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1247 Self::All(active) => match required {
1248 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1249 _ => active.iter().any(|expression| expression.implies(required)),
1250 },
1251 _ => match required {
1252 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1253 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1254 _ => false,
1255 },
1256 }
1257 }
1258
1259 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1260 match self {
1261 Self::Defined(name)
1262 | Self::Undefined(name)
1263 | Self::Truthy(name)
1264 | Self::Falsy(name) => name == macro_name,
1265 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1268 Self::All(expressions) | Self::Any(expressions) => expressions
1269 .iter()
1270 .any(|expression| expression.may_depend_on_macro(macro_name)),
1271 Self::Constant(_) => false,
1272 }
1273 }
1274
1275 pub fn heap_size(&self) -> usize {
1276 match self {
1277 Self::Defined(value)
1278 | Self::Undefined(value)
1279 | Self::Truthy(value)
1280 | Self::Falsy(value)
1281 | Self::Opaque(value)
1282 | Self::NegatedOpaque(value) => value.len(),
1283 Self::All(expressions) | Self::Any(expressions) => {
1284 expressions
1285 .iter()
1286 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1287 size.saturating_add(std::mem::size_of::<Self>())
1288 .saturating_add(expression.heap_size())
1289 })
1290 }
1291 Self::Constant(_) => 0,
1292 }
1293 }
1294}
1295
1296impl PreprocessorGuard {
1297 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1298 match self {
1299 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1300 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1301 Self::Boolean(expression) => Some(expression.clone()),
1302 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1303 Self::Expression(_) | Self::NegatedExpression(_) => None,
1304 }
1305 }
1306
1307 fn negated(&self) -> Self {
1308 match self {
1309 Self::Defined(name) => Self::Undefined(name.clone()),
1310 Self::Undefined(name) => Self::Defined(name.clone()),
1311 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1312 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1313 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1314 Self::Constant(value) => Self::Constant(!value),
1315 }
1316 }
1317
1318 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1319 match self {
1320 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1321 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1322 Self::Expression(_) | Self::NegatedExpression(_) => true,
1325 Self::Constant(_) => false,
1326 }
1327 }
1328}
1329
1330#[derive(Clone, PartialEq, Eq)]
1331pub enum MacroDefinition {
1332 Object {
1333 replacement: String,
1334 },
1335 Function {
1336 parameters: Vec<String>,
1337 replacement: String,
1338 },
1339 VariadicFunction {
1340 parameters: Vec<String>,
1341 replacement: String,
1342 },
1343 Unsupported,
1344}
1345
1346#[derive(Clone, Debug, PartialEq, Eq)]
1347pub enum MacroIncludeProtection {
1348 MacroGuard(String),
1349 PragmaOnce,
1350 None,
1351}
1352
1353enum ParsedMacroReplacement {
1354 Parsed { source: String, tree: Tree },
1355 Unsupported,
1356}
1357
1358const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1362
1363pub struct ParsedReplacementBody {
1372 pub source: String,
1373 pub tree: Tree,
1374 pub body_offset: usize,
1375 pub parameters: Vec<String>,
1376 original_offsets: Box<[usize]>,
1381}
1382
1383impl ParsedReplacementBody {
1384 pub fn statements(&self) -> Option<Node<'_>> {
1386 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1387 .child_by_field_name("body")
1388 }
1389
1390 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1396 assert!(
1397 node.start_byte() >= self.body_offset,
1398 "synthetic sentinel node cannot be mapped to a macro replacement"
1399 );
1400 assert!(
1401 node.end_byte() >= self.body_offset,
1402 "synthetic sentinel node cannot be mapped to a macro replacement"
1403 );
1404 let start_offset = node.start_byte() - self.body_offset;
1405 let end_offset = node.end_byte() - self.body_offset;
1406 assert!(start_offset <= end_offset);
1407 let start_origin = *self
1408 .original_offsets
1409 .get(start_offset)
1410 .expect("replacement node start must have a source mapping");
1411 let end_origin = *self
1412 .original_offsets
1413 .get(end_offset)
1414 .expect("replacement node end must have a source mapping");
1415 assert!(start_origin <= end_origin);
1416 let start = replacement_start + start_origin;
1417 let end = replacement_start + end_origin;
1418 start..end
1419 }
1420
1421 fn expands_variadic_arguments(&self) -> bool {
1428 let mut stack = vec![self.tree.root_node()];
1429 while let Some(node) = stack.pop() {
1430 if matches!(
1431 node.kind(),
1432 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1433 ) && node_text(node, &self.source) == "__VA_ARGS__"
1434 {
1435 return true;
1436 }
1437 push_named_children_reversed(node, &mut stack);
1438 }
1439 false
1440 }
1441}
1442
1443pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1445 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1446 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1447 if compact.starts_with("0x") || compact.starts_with("0X") {
1448 (16, 2, |ch| ch.is_ascii_hexdigit())
1449 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1450 (2, 2, |ch| matches!(ch, '0' | '1'))
1451 } else if compact.starts_with('0') && compact.len() > 1 {
1452 (8, 0, |ch| matches!(ch, '0'..='7'))
1453 } else {
1454 (10, 0, |ch| ch.is_ascii_digit())
1455 };
1456 let digit_len = compact[digits_start..]
1457 .chars()
1458 .take_while(|ch| digit_matches(*ch))
1459 .map(char::len_utf8)
1460 .sum::<usize>();
1461 if digit_len == 0 {
1462 return None;
1463 }
1464 let digits_end = digits_start + digit_len;
1465 if !compact[digits_end..]
1466 .chars()
1467 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1468 {
1469 return None;
1470 }
1471 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1472}
1473
1474#[derive(Clone)]
1475enum MacroLocalBindingTypeTemplate {
1476 Parameter(usize),
1477 Fixed(String),
1478}
1479
1480#[derive(Clone)]
1481struct MacroLocalBindingTemplate {
1482 name: String,
1483 declared_type: MacroLocalBindingTypeTemplate,
1484 pointer_depth: i32,
1485}
1486
1487pub struct MacroLocalBinding<'tree> {
1495 pub name: String,
1496 pub type_name: String,
1497 pub type_node: Option<Node<'tree>>,
1498 pub pointer_depth: i32,
1499 pub proven_unit: Option<CodeUnit>,
1500}
1501
1502#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1508pub enum MacroLexicalBindingKind {
1509 Parameter,
1510 Local,
1511}
1512
1513#[derive(Clone, Debug, PartialEq, Eq)]
1514pub struct MacroLexicalBinding {
1515 pub definition: ProjectFile,
1516 pub kind: MacroLexicalBindingKind,
1517 pub name: String,
1518 pub name_range: std::ops::Range<usize>,
1519 pub declaration_range: std::ops::Range<usize>,
1520}
1521
1522#[derive(Clone, Debug, Default, PartialEq, Eq)]
1526pub struct MacroLexicalReferences {
1527 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1528 pub truncated: bool,
1529 pub cancelled: bool,
1530}
1531
1532fn macro_replacement_type_parameters(
1533 body: &ParsedReplacementBody,
1534 parameters: &[String],
1535) -> Option<Vec<usize>> {
1536 let mut found = Vec::new();
1537 let mut stack = vec![body.tree.root_node()];
1538 while let Some(node) = stack.pop() {
1539 let type_position = node.kind() == "type_identifier"
1540 || (node.kind() == "identifier"
1541 && node.parent().is_some_and(|parent| {
1542 parent.kind() == "type_descriptor"
1543 && parent.child_by_field_name("type") == Some(node)
1544 }));
1545 let offsetof_type_position = node.kind() == "identifier"
1546 && node
1547 .parent()
1548 .filter(|parent| parent.kind() == "argument_list")
1549 .and_then(|arguments| arguments.parent())
1550 .is_some_and(|call| {
1551 call.kind() == "call_expression"
1552 && call
1553 .child_by_field_name("function")
1554 .is_some_and(|function| {
1555 function.kind() == "identifier"
1556 && node_text(function, &body.source) == "offsetof"
1557 })
1558 && call
1559 .child_by_field_name("arguments")
1560 .is_some_and(|arguments| {
1561 argument_children(arguments).next() == Some(node)
1562 })
1563 });
1564 if (type_position || offsetof_type_position)
1565 && let Some(index) = parameters
1566 .iter()
1567 .position(|parameter| parameter == node_text(node, &body.source))
1568 && !found.contains(&index)
1569 {
1570 found.push(index);
1571 }
1572 push_named_children_reversed(node, &mut stack);
1573 }
1574 (!found.is_empty()).then_some(found)
1575}
1576
1577fn macro_replacement_type_parameter(
1578 body: &ParsedReplacementBody,
1579 parameters: &[String],
1580) -> Option<usize> {
1581 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1582 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1583}
1584
1585pub(crate) fn macro_type_argument_node<'tree>(
1586 node: Node<'tree>,
1587 source: &str,
1588) -> Option<Node<'tree>> {
1589 match node.kind() {
1590 "type_descriptor" => {
1591 let type_child = node
1592 .child_by_field_name("type")
1593 .or_else(|| first_type_child(node))?;
1594 for index in (0..node.named_child_count()).rev() {
1595 let child = node.named_child(index)?;
1596 if child != type_child
1597 && matches!(
1598 child.kind(),
1599 "identifier"
1600 | "type_identifier"
1601 | "qualified_identifier"
1602 | "scoped_type_identifier"
1603 )
1604 {
1605 return Some(child);
1606 }
1607 }
1608 macro_type_argument_node(type_child, source)
1609 }
1610 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1611 node.child_by_field_name("name")
1612 }
1613 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1614 .next_named_sibling()
1615 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1616 .and_then(|error| error.named_child(0))
1617 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1618 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1619 }
1620}
1621
1622fn c_function_macro_argument<'tree>(
1623 node: Node<'tree>,
1624) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1625 let mut current = node;
1626 while let Some(parent) = current.parent() {
1627 if parent.kind() == "argument_list" {
1628 let call = parent.parent().filter(|call| {
1629 call.kind() == "call_expression"
1630 && call.child_by_field_name("arguments") == Some(parent)
1631 && call
1632 .child_by_field_name("function")
1633 .is_some_and(|function| {
1634 function.kind() == "identifier"
1635 && !node_range_contains(function, current)
1636 })
1637 })?;
1638 let mut actuals = argument_children(parent).enumerate();
1639 let (index, argument) = actuals.find(|(_, argument)| {
1640 node_range_contains(*argument, node)
1641 && (argument.start_byte() == current.start_byte()
1642 || argument.end_byte() == current.end_byte())
1643 })?;
1644 return Some((call, index, argument));
1645 }
1646 current = parent;
1647 }
1648 None
1649}
1650
1651fn recognized_c_macro_declarator_binding<'tree>(
1659 statement: Node<'tree>,
1660 source: &str,
1661) -> Option<MacroLocalBinding<'tree>> {
1662 if statement.kind() == "declaration" {
1663 let specifier = statement
1664 .child_by_field_name("type")
1665 .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1666 let macro_name = specifier
1667 .child_by_field_name("name")
1668 .filter(|name| name.kind() == "identifier")?;
1669 if node_text(macro_name, source) != "g_autoptr" {
1670 return None;
1671 }
1672 let type_node = specifier
1673 .child_by_field_name("type")
1674 .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1675 let declarator = statement
1676 .child_by_field_name("declarator")
1677 .filter(|declarator| declarator.kind() == "init_declarator")?;
1678 let name_node = declarator
1679 .child_by_field_name("declarator")
1680 .filter(|name| name.kind() == "identifier")?;
1681 declarator.child_by_field_name("value")?;
1682 let name = node_text(name_node, source).trim();
1683 let type_name = node_text(type_node, source).trim();
1684 if name.is_empty() || type_name.is_empty() {
1685 return None;
1686 }
1687 return Some(MacroLocalBinding {
1688 name: name.to_string(),
1689 type_name: type_name.to_string(),
1690 type_node: Some(type_node),
1691 pointer_depth: 1,
1692 proven_unit: None,
1693 });
1694 }
1695 let assignment = match statement.kind() {
1696 "assignment_expression" => statement,
1697 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1698 _ => return None,
1699 };
1700 if assignment.kind() != "assignment_expression" {
1701 return None;
1702 }
1703 let call = assignment.child_by_field_name("left")?;
1704 if call.kind() != "call_expression" {
1705 return None;
1706 }
1707 let function = call.child_by_field_name("function")?;
1708 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1709 return None;
1710 }
1711 let arguments = call.child_by_field_name("arguments")?;
1712 let mut actuals = argument_children(arguments);
1713 let type_node = actuals.next()?;
1714 if actuals.next().is_some()
1715 || !matches!(
1716 type_node.kind(),
1717 "identifier"
1718 | "type_identifier"
1719 | "qualified_identifier"
1720 | "scoped_type_identifier"
1721 | "template_type"
1722 )
1723 {
1724 return None;
1725 }
1726 let name_node = (0..assignment.named_child_count())
1727 .filter_map(|index| assignment.named_child(index))
1728 .filter(|child| child.kind() == "ERROR")
1729 .filter_map(|error| {
1730 (error.named_child_count() == 1)
1731 .then(|| error.named_child(0))
1732 .flatten()
1733 })
1734 .find(|node| node.kind() == "identifier")?;
1735 let name = node_text(name_node, source).trim();
1736 let type_name = node_text(type_node, source).trim();
1737 if name.is_empty() || type_name.is_empty() {
1738 return None;
1739 }
1740 Some(MacroLocalBinding {
1741 name: name.to_string(),
1742 type_name: type_name.to_string(),
1743 type_node: Some(type_node),
1744 pointer_depth: 1,
1745 proven_unit: None,
1746 })
1747}
1748
1749#[derive(Clone, PartialEq, Eq)]
1750pub struct MacroBinding {
1751 source: ProjectFile,
1752 declaration_byte: usize,
1753 definition: MacroDefinition,
1754 exact: bool,
1755}
1756
1757impl MacroBinding {
1758 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1759 Self {
1760 source: source.clone(),
1761 declaration_byte,
1762 definition: MacroDefinition::Unsupported,
1763 exact: false,
1764 }
1765 }
1766
1767 fn is_exact(&self) -> bool {
1768 self.exact
1769 }
1770
1771 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1772 Self {
1773 source: source.clone(),
1774 declaration_byte,
1775 definition: current.definition.clone(),
1776 exact: false,
1777 }
1778 }
1779}
1780
1781type OwningPreprocessorConditionals = Box<[usize]>;
1793
1794#[derive(Clone)]
1795pub enum MacroEvent {
1796 Define {
1797 name: String,
1798 binding: MacroBinding,
1799 byte: usize,
1800 conditionals: OwningPreprocessorConditionals,
1801 },
1802 Undef {
1803 name: String,
1804 byte: usize,
1805 conditionals: OwningPreprocessorConditionals,
1806 },
1807 Include {
1808 targets: Vec<ProjectFile>,
1809 byte: usize,
1810 conditionals: OwningPreprocessorConditionals,
1811 },
1812 Invalidate {
1813 byte: usize,
1814 },
1815}
1816
1817impl MacroEvent {
1818 pub fn byte(&self) -> usize {
1819 match self {
1820 Self::Define { byte, .. }
1821 | Self::Undef { byte, .. }
1822 | Self::Include { byte, .. }
1823 | Self::Invalidate { byte } => *byte,
1824 }
1825 }
1826}
1827
1828#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1829pub enum CallArityEvidence {
1830 Exact(usize),
1831 Unknown,
1832}
1833
1834impl CallArityEvidence {
1835 pub fn exact(self) -> Option<usize> {
1836 match self {
1837 Self::Exact(arity) => Some(arity),
1838 Self::Unknown => None,
1839 }
1840 }
1841
1842 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1843 self.exact().map(|arity| expected.accepts(arity))
1844 }
1845}
1846
1847struct CppAlias {
1848 name: String,
1849 target: String,
1850 namespace: Option<String>,
1851}
1852
1853type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1854
1855#[derive(Debug, Clone, PartialEq, Eq)]
1859pub enum CppTemplateResolutionError {
1860 AliasCycle { alias: CodeUnit },
1862 ArgumentBinding,
1864 Substitution,
1866 PrimarySelection,
1869 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1872}
1873
1874fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1877 let mut distinct: Vec<CodeUnit> = Vec::new();
1878 for unit in units {
1879 if !distinct
1880 .iter()
1881 .any(|existing| same_visible_symbol(existing, unit))
1882 {
1883 distinct.push(unit.clone());
1884 }
1885 }
1886 distinct
1887}
1888
1889pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1892 visibility: Factory,
1893 file: &ProjectFile,
1894 root: Node<'_>,
1895 source: &str,
1896 max_references: usize,
1897 cancelled: impl FnMut() -> bool,
1898) -> MacroLexicalReferences
1899where
1900 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1901{
1902 crate::graph::macro_lexical::all_references(
1903 visibility,
1904 file,
1905 root,
1906 source,
1907 max_references,
1908 cancelled,
1909 )
1910}
1911
1912impl<'a> VisibilityIndex<'a> {
1913 pub fn data(&self) -> Arc<VisibilityIndexData> {
1914 Arc::clone(&self.data)
1915 }
1916
1917 pub fn from_data(
1918 cpp: &'a dyn CppSource,
1919 token: QueryToken<'a>,
1920 data: Arc<VisibilityIndexData>,
1921 ) -> Self {
1922 Self { cpp, token, data }
1923 }
1924
1925 pub fn cpp(&self) -> &'a dyn CppSource {
1926 self.cpp
1927 }
1928
1929 pub fn token(&self) -> QueryToken<'a> {
1931 self.token
1932 }
1933
1934 pub(crate) fn macro_lexical_index(
1938 &self,
1939 file: &ProjectFile,
1940 root: Node<'_>,
1941 source: &str,
1942 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1943 if let Some(index) = self
1944 .macro_lexical_indexes
1945 .lock()
1946 .expect("macro lexical index cache poisoned")
1947 .get(file)
1948 .cloned()
1949 {
1950 debug_assert_eq!(
1951 index.source_len,
1952 source.len(),
1953 "macro lexical index for {file:?} was built from a different source"
1954 );
1955 return index;
1956 }
1957 let index = Arc::new(crate::graph::macro_lexical::build_index(
1958 self, file, root, source,
1959 ));
1960 let mut cache = self
1961 .macro_lexical_indexes
1962 .lock()
1963 .expect("macro lexical index cache poisoned");
1964 if let Some(existing) = cache.get(file).cloned() {
1965 return existing;
1966 }
1967 if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1968 cache.insert(file.clone(), Arc::clone(&index));
1969 }
1970 index
1971 }
1972
1973 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1979 if let Some(metadata) = self
1980 .callable_metadata
1981 .lock()
1982 .expect("C++ callable metadata cache poisoned")
1983 .get(unit)
1984 .cloned()
1985 {
1986 return metadata;
1987 }
1988 let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1989 let mut cache = self
1990 .callable_metadata
1991 .lock()
1992 .expect("C++ callable metadata cache poisoned");
1993 if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1994 cache.insert(unit.clone(), Arc::clone(&metadata));
1995 }
1996 metadata
1997 }
1998
1999 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2001 crate::identity::cpp_callable_linkage_from_metadata(
2002 self.callable_metadata(unit).iter().cloned(),
2003 )
2004 }
2005
2006 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2008 brokk_bifrost_core::analyzer::model::first_callable_arity(
2009 self.callable_metadata(unit).iter().cloned(),
2010 )
2011 }
2012
2013 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2018 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2019 self.callable_metadata(unit).iter().cloned(),
2020 )
2021 }
2022
2023 pub fn has_unresolved_include_visible_before(
2031 &self,
2032 file: &ProjectFile,
2033 before_byte: usize,
2034 ) -> bool {
2035 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2036 return false;
2037 };
2038 let facts = self.structured_include_facts(file, prepared.as_ref());
2039 has_unresolved_include_visible_before_in_prepared(
2040 file,
2041 prepared.as_ref(),
2042 self.cpp.include_target_index(),
2043 &facts,
2044 before_byte,
2045 )
2046 }
2047
2048 fn structured_include_facts(
2049 &self,
2050 file: &ProjectFile,
2051 prepared: &PreparedSyntaxTree,
2052 ) -> Arc<[StructuredIncludeFact]> {
2053 let cell = self
2054 .structured_include_fact_cells
2055 .lock()
2056 .expect("C++ structured include-fact cache poisoned")
2057 .entry(file.clone())
2058 .or_default()
2059 .clone();
2060 Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2061 }
2062
2063 #[cfg(any(test, feature = "test-support"))]
2071 pub fn from_visible_files_for_test(
2072 cpp: &'a dyn CppSource,
2073 token: QueryToken<'a>,
2074 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2075 ) -> Self {
2076 let visible_source_files_by_root = visible_by_file
2077 .iter()
2078 .map(|(file, visible)| {
2079 (
2080 file.clone(),
2081 visible
2082 .iter()
2083 .map(|unit| unit.source().clone())
2084 .chain(std::iter::once(file.clone()))
2085 .collect(),
2086 )
2087 })
2088 .collect();
2089 let mut global_field_internal_linkage = HashMap::default();
2090 Self {
2091 cpp,
2092 token,
2093 data: Arc::new(VisibilityIndexData {
2094 visible_by_identifier: build_visible_identifier_index(
2095 &CppGraphSource::from_source(cpp, token),
2096 &visible_by_file,
2097 &visible_source_files_by_root,
2098 &mut global_field_internal_linkage,
2099 ),
2100 global_field_internal_linkage,
2101 visible_by_file,
2102 visible_source_files_by_root,
2103 alias_cells: Mutex::new(HashMap::default()),
2104 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2105 parser_alias_target_matches: RwLock::new(HashMap::default()),
2106 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2107 project_using_index: OnceLock::new(),
2108 callable_reference_specs: Mutex::new(HashMap::default()),
2109 structured_include_fact_cells: Mutex::new(HashMap::default()),
2110 include_activation_cells: Mutex::new(HashMap::default()),
2111 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2112 include_path_admission_cells: Mutex::new(HashMap::default()),
2113 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2114 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2115 conditional_include_projection_state_count: AtomicUsize::new(0),
2116 conditional_include_target_state_count: AtomicUsize::new(0),
2117 include_activation_build_count: AtomicUsize::new(0),
2118 using_donor_activation_count: AtomicUsize::new(0),
2119 using_namespace_lookup_count: AtomicUsize::new(0),
2120 using_name_candidate_inspection_count: AtomicUsize::new(0),
2121 callable_reference_spec_build_count: AtomicUsize::new(0),
2122 alias_source_parse_counts: Mutex::new(HashMap::default()),
2123 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2124 parser_alias_fallback_calls: AtomicUsize::new(0),
2125 parser_alias_fallback_files: AtomicUsize::new(0),
2126 parser_alias_source_parses: AtomicUsize::new(0),
2127 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2128 field_type_facts: Mutex::new(HashMap::default()),
2129 structured_alias_targets: Mutex::new(HashMap::default()),
2130 callable_comparables: Mutex::new(HashMap::default()),
2131 callable_metadata: Mutex::new(HashMap::default()),
2132 comparable_name_declarations: Mutex::new(HashMap::default()),
2133 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2134 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2135 precise_parent_cache: Mutex::new(HashMap::default()),
2136 c_tag_kind_cache: Mutex::new(HashMap::default()),
2137 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2138 macro_event_cells: Mutex::new(HashMap::default()),
2139 macro_event_name_sets: Mutex::new(HashMap::default()),
2140 macro_include_protection_cells: Mutex::new(HashMap::default()),
2141 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2142 macro_replacements: Mutex::new(HashMap::default()),
2143 macro_local_binding_templates: Mutex::new(HashMap::default()),
2144 macro_lexical_templates: Mutex::new(HashMap::default()),
2145 macro_lexical_indexes: Mutex::new(HashMap::default()),
2146 macro_replacement_bodies: Mutex::new(HashMap::default()),
2147 macro_type_parameters: Mutex::new(HashMap::default()),
2148 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2149 macro_replacement_parse_count: AtomicUsize::new(0),
2150 macro_event_application_count: AtomicUsize::new(0),
2151 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2152 macro_environment_copy_count: AtomicUsize::new(0),
2153 macro_environment_request_count: AtomicUsize::new(0),
2154 cpp_template_metadata: HashMap::default(),
2155 cpp_template_families: HashMap::default(),
2156 qualified_candidate_inspections: AtomicUsize::new(0),
2157 target_preserving_type_resolution_count: AtomicUsize::new(0),
2158 visibility_identifier_lookup_count: 0,
2159 visibility_identifier_batch_count: 0,
2160 }),
2161 }
2162 }
2163
2164 fn cpp_source(&self) -> CppGraphSource<'a> {
2171 CppGraphSource::from_source(self.cpp, self.token)
2172 }
2173
2174 pub fn build(
2175 cpp: &'a dyn CppSource,
2176 token: QueryToken<'a>,
2177 analyzer: &CppGraphSource<'_>,
2178 roots: &HashSet<ProjectFile>,
2179 ) -> Self {
2180 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2181 }
2182
2183 pub fn build_with_cancellation(
2184 cpp: &'a dyn CppSource,
2185 token: QueryToken<'a>,
2186 analyzer: &CppGraphSource<'_>,
2187 roots: &HashSet<ProjectFile>,
2188 cancellation: Option<&CancellationToken>,
2189 ) -> Self {
2190 let visibility_started = Instant::now();
2191 let include_targets = cpp.include_target_index();
2192 let includes_started = Instant::now();
2193 let mut include_graph = IncludeGraph::default();
2194 for root in roots {
2195 include_graph.extend_with(root, cancellation, &mut |file| {
2196 cpp.canonical_include_paths(token, file)
2197 .unwrap_or_default()
2198 .into_iter()
2199 .flat_map(|include| {
2200 resolve_include_targets_with_index(file, &include, include_targets)
2201 })
2202 .collect()
2203 });
2204 }
2205 let include_elapsed = includes_started.elapsed();
2206 let include_file_count = include_graph.files().count();
2207 let visible_source_files_by_root = roots
2208 .iter()
2209 .map(|root| {
2210 (
2211 root.clone(),
2212 include_graph.reachable_files(root, cancellation),
2213 )
2214 })
2215 .collect::<HashMap<_, _>>();
2216 let mut visibility_stats = BoundedVisibilityStats::default();
2217 let mut visible_by_file = build_bounded_visible_declarations(
2218 cpp,
2219 token,
2220 analyzer,
2221 roots,
2222 &visible_source_files_by_root,
2223 cancellation,
2224 &mut visibility_stats,
2225 );
2226 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2227 eprintln!(
2228 "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={}",
2229 visibility_started.elapsed().as_millis(),
2230 include_elapsed.as_millis(),
2231 include_file_count,
2232 visibility_stats.rounds,
2233 visibility_stats.root_names,
2234 visibility_stats.identifier_lookups,
2235 visibility_stats.identifier_batches,
2236 visibility_stats.candidate_units,
2237 visibility_stats.candidate_sources,
2238 visibility_stats.declaration_reads,
2239 visibility_stats.declaration_units,
2240 visibility_stats.selected_units,
2241 visibility_stats.dependency_source_names,
2242 visibility_stats.dependency_names,
2243 visibility_stats.lookup_elapsed.as_millis(),
2244 visibility_stats.declaration_elapsed.as_millis(),
2245 visibility_stats.dependency_source_elapsed.as_millis(),
2246 );
2247 }
2248 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2249 let finalize_started = Instant::now();
2250 if report_stats {
2251 eprintln!(
2252 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2253 visible_by_file.len(),
2254 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2255 );
2256 }
2257 let owner_started = Instant::now();
2258 if report_stats {
2259 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2260 }
2261 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2262 if report_stats {
2263 eprintln!(
2264 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2265 owner_stats.unseen_owners,
2266 owner_stats.definition_lookups,
2267 owner_stats.admitted,
2268 owner_started.elapsed().as_millis(),
2269 );
2270 }
2271 let mut global_field_internal_linkage = HashMap::default();
2272 let identifier_started = Instant::now();
2273 if report_stats {
2274 eprintln!(
2275 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2276 );
2277 }
2278 let visible_by_identifier = build_visible_identifier_index(
2279 analyzer,
2280 &visible_by_file,
2281 &visible_source_files_by_root,
2282 &mut global_field_internal_linkage,
2283 );
2284 if report_stats {
2285 eprintln!(
2286 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2287 visible_by_identifier.len(),
2288 visible_by_identifier
2289 .values()
2290 .map(HashMap::len)
2291 .sum::<usize>(),
2292 visible_by_identifier
2293 .values()
2294 .flat_map(HashMap::values)
2295 .map(Vec::len)
2296 .sum::<usize>(),
2297 identifier_started.elapsed().as_millis(),
2298 );
2299 }
2300 let mut cpp_template_metadata = HashMap::default();
2301 let metadata_started = Instant::now();
2302 let mut template_classes = 0usize;
2303 if report_stats {
2304 eprintln!(
2305 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2306 );
2307 }
2308 for unit in visible_by_file
2309 .values()
2310 .flatten()
2311 .filter(|unit| unit.is_class())
2312 {
2313 template_classes += 1;
2314 if cpp_template_metadata.contains_key(unit) {
2315 continue;
2316 }
2317 if let Some(metadata) = cpp.template_metadata(unit) {
2318 cpp_template_metadata.insert(unit.clone(), metadata);
2319 }
2320 }
2321 if report_stats {
2322 eprintln!(
2323 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2324 template_classes,
2325 cpp_template_metadata.len(),
2326 metadata_started.elapsed().as_millis(),
2327 );
2328 }
2329 let families_started = Instant::now();
2330 if report_stats {
2331 eprintln!(
2332 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2333 );
2334 }
2335 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2336 for (unit, metadata) in &cpp_template_metadata {
2337 cpp_template_families
2338 .entry(metadata.primary_fq_name.clone())
2339 .or_default()
2340 .push(unit.clone());
2341 }
2342 for family in cpp_template_families.values_mut() {
2351 sort_lookup_units(family);
2352 }
2353 if report_stats {
2354 eprintln!(
2355 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2356 cpp_template_families.len(),
2357 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2358 families_started.elapsed().as_millis(),
2359 );
2360 eprintln!(
2361 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2362 visible_by_file.len(),
2363 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2364 finalize_started.elapsed().as_millis(),
2365 visibility_started.elapsed().as_millis(),
2366 );
2367 }
2368 Self {
2369 cpp,
2370 token,
2371 data: Arc::new(VisibilityIndexData {
2372 visible_by_file,
2373 visible_by_identifier,
2374 global_field_internal_linkage,
2375 visible_source_files_by_root,
2376 alias_cells: Mutex::new(HashMap::default()),
2377 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2378 parser_alias_target_matches: RwLock::new(HashMap::default()),
2379 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2380 project_using_index: OnceLock::new(),
2381 callable_reference_specs: Mutex::new(HashMap::default()),
2382 structured_include_fact_cells: Mutex::new(HashMap::default()),
2383 include_activation_cells: Mutex::new(HashMap::default()),
2384 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2385 include_path_admission_cells: Mutex::new(HashMap::default()),
2386 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2387 #[cfg(any(test, feature = "test-support"))]
2388 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2389 #[cfg(any(test, feature = "test-support"))]
2390 conditional_include_projection_state_count: AtomicUsize::new(0),
2391 #[cfg(any(test, feature = "test-support"))]
2392 conditional_include_target_state_count: AtomicUsize::new(0),
2393 #[cfg(any(test, feature = "test-support"))]
2394 include_activation_build_count: AtomicUsize::new(0),
2395 #[cfg(any(test, feature = "test-support"))]
2396 using_donor_activation_count: AtomicUsize::new(0),
2397 #[cfg(any(test, feature = "test-support"))]
2398 using_namespace_lookup_count: AtomicUsize::new(0),
2399 #[cfg(any(test, feature = "test-support"))]
2400 using_name_candidate_inspection_count: AtomicUsize::new(0),
2401 #[cfg(any(test, feature = "test-support"))]
2402 callable_reference_spec_build_count: AtomicUsize::new(0),
2403 #[cfg(any(test, feature = "test-support"))]
2404 alias_source_parse_counts: Mutex::new(HashMap::default()),
2405 #[cfg(any(test, feature = "test-support"))]
2406 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2407 parser_alias_fallback_calls: AtomicUsize::new(0),
2408 parser_alias_fallback_files: AtomicUsize::new(0),
2409 parser_alias_source_parses: AtomicUsize::new(0),
2410 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2411 field_type_facts: Mutex::new(HashMap::default()),
2412 structured_alias_targets: Mutex::new(HashMap::default()),
2413 callable_comparables: Mutex::new(HashMap::default()),
2414 callable_metadata: Mutex::new(HashMap::default()),
2415 comparable_name_declarations: Mutex::new(HashMap::default()),
2416 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2417 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2418 precise_parent_cache: Mutex::new(HashMap::default()),
2419 c_tag_kind_cache: Mutex::new(HashMap::default()),
2420 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2421 macro_event_cells: Mutex::new(HashMap::default()),
2422 macro_event_name_sets: Mutex::new(HashMap::default()),
2423 macro_include_protection_cells: Mutex::new(HashMap::default()),
2424 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2425 macro_replacements: Mutex::new(HashMap::default()),
2426 macro_local_binding_templates: Mutex::new(HashMap::default()),
2427 macro_lexical_templates: Mutex::new(HashMap::default()),
2428 macro_lexical_indexes: Mutex::new(HashMap::default()),
2429 macro_replacement_bodies: Mutex::new(HashMap::default()),
2430 macro_type_parameters: Mutex::new(HashMap::default()),
2431 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2432 #[cfg(any(test, feature = "test-support"))]
2433 macro_replacement_parse_count: AtomicUsize::new(0),
2434 #[cfg(any(test, feature = "test-support"))]
2435 macro_event_application_count: AtomicUsize::new(0),
2436 #[cfg(any(test, feature = "test-support"))]
2437 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2438 #[cfg(any(test, feature = "test-support"))]
2439 macro_environment_copy_count: AtomicUsize::new(0),
2440 #[cfg(any(test, feature = "test-support"))]
2441 macro_environment_request_count: AtomicUsize::new(0),
2442 cpp_template_metadata,
2443 cpp_template_families,
2444 #[cfg(any(test, feature = "test-support"))]
2445 qualified_candidate_inspections: AtomicUsize::new(0),
2446 #[cfg(any(test, feature = "test-support"))]
2447 target_preserving_type_resolution_count: AtomicUsize::new(0),
2448 #[cfg(any(test, feature = "test-support"))]
2449 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2450 #[cfg(any(test, feature = "test-support"))]
2451 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2452 }),
2453 }
2454 }
2455
2456 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2457 if file == target.source() {
2458 return true;
2459 }
2460 if self.global_field_has_internal_linkage(target) {
2461 return self
2462 .visible_source_files_by_root
2463 .get(file)
2464 .is_some_and(|sources| sources.contains(target.source()));
2465 }
2466 self.visible_by_file
2467 .get(file)
2468 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2469 }
2470
2471 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2472 self.global_field_internal_linkage
2473 .get(unit)
2474 .copied()
2475 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2476 }
2477
2478 pub fn call_arity_evidence(
2479 &self,
2480 file: &ProjectFile,
2481 call: Node<'_>,
2482 source: &str,
2483 ) -> CallArityEvidence {
2484 self.call_arity_evidence_at(file, call, source, call.start_byte())
2485 }
2486
2487 pub fn call_arity_evidence_at(
2495 &self,
2496 file: &ProjectFile,
2497 call: Node<'_>,
2498 source: &str,
2499 environment_byte: usize,
2500 ) -> CallArityEvidence {
2501 let Some(arguments) = call
2502 .child_by_field_name("arguments")
2503 .or_else(|| call.child_by_field_name("parameters"))
2504 .or_else(|| call.child_by_field_name("value"))
2505 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2506 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2507 else {
2508 return CallArityEvidence::Exact(0);
2509 };
2510 let recovered_c_keyword_arguments =
2511 recovered_c_keyword_argument_count(file, call, arguments, source);
2512 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2513 let arguments = argument_children(arguments)
2514 .flat_map(|argument| {
2515 recovered_c_new_expression_arguments(argument, c_semantics)
2516 .map(Vec::from)
2517 .unwrap_or_else(|| vec![argument])
2518 })
2519 .collect::<Vec<_>>();
2520 if arguments
2521 .iter()
2522 .all(|argument| !argument_shape_may_change_arity(*argument))
2523 {
2524 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2525 }
2526 let environment = self.macro_environment(file, environment_byte);
2527 let mut stack = Vec::new();
2528 let mut total = recovered_c_keyword_arguments;
2529 for argument in arguments {
2530 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2531 return CallArityEvidence::Unknown;
2532 }
2533 let CallArityEvidence::Exact(spread) =
2534 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2535 else {
2536 return CallArityEvidence::Unknown;
2537 };
2538 total += spread;
2539 }
2540 CallArityEvidence::Exact(total)
2541 }
2542
2543 fn argument_arity_evidence(
2544 &self,
2545 argument: Node<'_>,
2546 source: &str,
2547 environment: &MacroEnvironment,
2548 stack: &mut Vec<(ProjectFile, usize)>,
2549 ) -> CallArityEvidence {
2550 let (name, invocation_arguments, function_like) = match argument.kind() {
2551 "identifier" => (node_text(argument, source), None, false),
2552 "call_expression" => {
2553 let Some(function) = argument.child_by_field_name("function") else {
2554 return CallArityEvidence::Exact(1);
2555 };
2556 if function.kind() != "identifier" {
2557 return CallArityEvidence::Exact(1);
2558 }
2559 let Some(arguments) = argument.child_by_field_name("arguments") else {
2560 return CallArityEvidence::Exact(1);
2561 };
2562 (node_text(function, source), Some(arguments), true)
2563 }
2564 _ => return CallArityEvidence::Exact(1),
2565 };
2566 let Some(binding) = environment.binding(name) else {
2567 return if environment.unknown_names {
2568 CallArityEvidence::Unknown
2569 } else {
2570 CallArityEvidence::Exact(1)
2571 };
2572 };
2573 if !binding.is_exact() {
2574 return CallArityEvidence::Unknown;
2575 }
2576 match (&binding.definition, invocation_arguments, function_like) {
2577 (MacroDefinition::Object { replacement }, None, false) => self
2578 .replacement_arity_evidence(
2579 replacement,
2580 &[],
2581 &[],
2582 source,
2583 environment,
2584 stack,
2585 binding,
2586 ),
2587 (
2588 MacroDefinition::Function {
2589 parameters,
2590 replacement,
2591 },
2592 Some(arguments),
2593 true,
2594 ) => {
2595 let actuals = argument_children(arguments).collect::<Vec<_>>();
2596 if actuals.len() != parameters.len() {
2597 CallArityEvidence::Unknown
2598 } else {
2599 self.replacement_arity_evidence(
2600 replacement,
2601 parameters,
2602 &actuals,
2603 source,
2604 environment,
2605 stack,
2606 binding,
2607 )
2608 }
2609 }
2610 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2611 _ => CallArityEvidence::Unknown,
2612 }
2613 }
2614
2615 #[allow(clippy::too_many_arguments)]
2616 fn replacement_arity_evidence(
2617 &self,
2618 replacement: &str,
2619 parameters: &[String],
2620 actuals: &[Node<'_>],
2621 actual_source: &str,
2622 environment: &MacroEnvironment,
2623 stack: &mut Vec<(ProjectFile, usize)>,
2624 binding: &MacroBinding,
2625 ) -> CallArityEvidence {
2626 let identity = (binding.source.clone(), binding.declaration_byte);
2627 if stack.contains(&identity) || replacement.trim().is_empty() {
2628 return CallArityEvidence::Unknown;
2629 }
2630 stack.push(identity);
2631 let parsed = self.parsed_macro_replacement(binding, replacement);
2632 let evidence = (|| {
2633 let ParsedMacroReplacement::Parsed {
2634 source: sentinel,
2635 tree,
2636 } = parsed.as_ref()
2637 else {
2638 return None;
2639 };
2640 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2641 let arguments = call.child_by_field_name("arguments")?;
2642 let mut total = 0usize;
2643 for argument in argument_children(arguments) {
2644 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2645 return None;
2646 }
2647 if argument.kind() == "identifier"
2648 && let Some(parameter_index) = parameters
2649 .iter()
2650 .position(|parameter| parameter == node_text(argument, sentinel))
2651 {
2652 if !macro_expansion_shape_is_safe(
2653 actuals[parameter_index],
2654 actual_source,
2655 &[],
2656 environment,
2657 ) {
2658 return None;
2659 }
2660 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2661 actuals[parameter_index],
2662 actual_source,
2663 environment,
2664 stack,
2665 ) else {
2666 return None;
2667 };
2668 total += spread;
2669 continue;
2670 }
2671 let CallArityEvidence::Exact(spread) =
2672 self.argument_arity_evidence(argument, sentinel, environment, stack)
2673 else {
2674 return None;
2675 };
2676 total += spread;
2677 }
2678 Some(CallArityEvidence::Exact(total))
2679 })()
2680 .unwrap_or(CallArityEvidence::Unknown);
2681 stack.pop();
2682 evidence
2683 }
2684
2685 fn parsed_macro_replacement(
2686 &self,
2687 binding: &MacroBinding,
2688 replacement: &str,
2689 ) -> Arc<ParsedMacroReplacement> {
2690 let key = (binding.source.clone(), binding.declaration_byte);
2691 let mut cache = self
2692 .macro_replacements
2693 .lock()
2694 .expect("C++ macro replacement cache poisoned");
2695 if let Some(parsed) = cache.get(&key) {
2696 return Arc::clone(parsed);
2697 }
2698 #[cfg(any(test, feature = "test-support"))]
2699 self.macro_replacement_parse_count
2700 .fetch_add(1, Ordering::Relaxed);
2701 let source =
2702 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2703 let mut parser = Parser::new();
2704 let parsed = parser
2705 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2706 .ok()
2707 .and_then(|()| parser.parse(&source, None))
2708 .filter(|tree| !tree.root_node().has_error())
2709 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2710 ParsedMacroReplacement::Parsed { source, tree }
2711 });
2712 let parsed = Arc::new(parsed);
2713 cache.insert(key, Arc::clone(&parsed));
2714 parsed
2715 }
2716
2717 pub fn function_macro_local_binding<'tree>(
2727 &self,
2728 file: &ProjectFile,
2729 statement: Node<'tree>,
2730 source: &str,
2731 ) -> Option<MacroLocalBinding<'tree>> {
2732 if !is_c_source_file(file) {
2733 return None;
2734 }
2735 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2736 return Some(binding);
2737 }
2738 let call = match statement.kind() {
2739 "call_expression" => statement,
2740 "expression_statement" if statement.named_child_count() == 1 => {
2741 statement.named_child(0)?
2742 }
2743 _ => return None,
2744 };
2745 if call.kind() != "call_expression" {
2746 return None;
2747 }
2748 let function = call.child_by_field_name("function")?;
2749 if function.kind() != "identifier" {
2750 return None;
2751 }
2752 let arguments = call.child_by_field_name("arguments")?;
2753 let actuals = argument_children(arguments).collect::<Vec<_>>();
2754 let environment = self.macro_environment(file, call.start_byte());
2755 let function_name = node_text(function, source);
2756 let binding = environment.binding(function_name)?;
2757 let MacroDefinition::Function {
2758 parameters,
2759 replacement,
2760 } = &binding.definition
2761 else {
2762 return None;
2763 };
2764 if actuals.len() != parameters.len() {
2765 return None;
2766 }
2767 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2768 let (type_name, type_node) = match &template.declared_type {
2769 MacroLocalBindingTypeTemplate::Parameter(index) => {
2770 let actual = *actuals.get(*index)?;
2771 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2772 return None;
2773 }
2774 (node_text(actual, source).trim().to_string(), Some(actual))
2775 }
2776 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2777 };
2778 if type_name.is_empty() {
2779 return None;
2780 }
2781 Some(MacroLocalBinding {
2782 name: template.name.clone(),
2783 type_name,
2784 type_node,
2785 pointer_depth: template.pointer_depth,
2786 proven_unit: None,
2787 })
2788 }
2789
2790 pub fn function_macro_container_binding<'tree>(
2799 &self,
2800 analyzer: &CppGraphSource<'_>,
2801 file: &ProjectFile,
2802 assignment: Node<'tree>,
2803 source: &str,
2804 ) -> Option<MacroLocalBinding<'tree>> {
2805 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2806 return None;
2807 }
2808 let name_node = assignment.child_by_field_name("left")?;
2809 if name_node.kind() != "identifier" {
2810 return None;
2811 }
2812 let call = assignment.child_by_field_name("right")?;
2813 if call.kind() != "call_expression" {
2814 return None;
2815 }
2816 let function = call.child_by_field_name("function")?;
2817 if function.kind() != "identifier" {
2818 return None;
2819 }
2820 let arguments = call.child_by_field_name("arguments")?;
2821 let actuals = argument_children(arguments).collect::<Vec<_>>();
2822 let function_name = node_text(function, source);
2823 let environment = self.macro_environment(file, call.start_byte());
2824 let binding = environment.binding(function_name)?;
2825 if !binding.is_exact() {
2826 return None;
2827 }
2828 let MacroDefinition::Function {
2829 parameters,
2830 replacement,
2831 } = &binding.definition
2832 else {
2833 return None;
2834 };
2835 if actuals.len() != parameters.len() {
2836 return None;
2837 }
2838 let body = self.parsed_macro_replacement_body(
2839 &(binding.source.clone(), binding.declaration_byte),
2840 parameters,
2841 replacement,
2842 )?;
2843 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2844 let type_argument = *actuals.get(type_parameter)?;
2845 let type_node = macro_type_argument_node(type_argument, source)?;
2846 let type_name = node_text(type_node, source).trim().to_string();
2847 if type_name.is_empty() {
2848 return None;
2849 }
2850 let explicit_tag = match node_text(type_argument, source) {
2851 "struct" => Some(CppCTagKind::Struct),
2852 "union" => Some(CppCTagKind::Union),
2853 _ => None,
2854 };
2855 let resolved_type = if let Some(tag) = explicit_tag {
2856 let candidates = self
2857 .visible_identifier_candidates(file, &type_name)
2858 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2859 .collect::<Vec<_>>();
2860 self.resolve_type_candidates(
2861 analyzer,
2862 file,
2863 &candidates,
2864 TypeCandidateResolution::Canonical,
2865 )
2866 .ok()
2867 } else {
2868 self.resolve_type_node_result(file, type_node, source)
2869 .ok()
2870 .flatten()
2871 };
2872 let proven_unit = resolved_type?;
2873 Some(MacroLocalBinding {
2874 name: node_text(name_node, source).to_string(),
2875 type_name,
2876 type_node: Some(type_node),
2877 pointer_depth: 1,
2878 proven_unit: Some(proven_unit),
2879 })
2880 }
2881
2882 pub fn macro_local_binding_at<'tree>(
2885 &self,
2886 file: &ProjectFile,
2887 root: Node<'tree>,
2888 source: &str,
2889 start_byte: usize,
2890 end_byte: usize,
2891 ) -> Option<MacroLocalBinding<'tree>> {
2892 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2893 }
2894
2895 pub fn macro_lexical_binding(
2899 &self,
2900 file: &ProjectFile,
2901 root: Node<'_>,
2902 source: &str,
2903 start_byte: usize,
2904 end_byte: usize,
2905 ) -> Option<MacroLexicalBinding> {
2906 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2907 }
2908
2909 pub fn macro_lexical_references(
2914 &self,
2915 file: &ProjectFile,
2916 root: Node<'_>,
2917 source: &str,
2918 max_references: usize,
2919 cancelled: impl FnMut() -> bool,
2920 ) -> MacroLexicalReferences {
2921 crate::graph::macro_lexical::all_references(
2922 || self,
2923 file,
2924 root,
2925 source,
2926 max_references,
2927 cancelled,
2928 )
2929 }
2930
2931 pub(crate) fn function_macro_binding_at(
2936 &self,
2937 file: &ProjectFile,
2938 name: &str,
2939 before_byte: usize,
2940 ) -> Option<(ProjectFile, usize)> {
2941 let environment = self.macro_environment(file, before_byte);
2942 let binding = environment.binding(name)?;
2943 if binding.is_exact()
2944 && matches!(
2945 binding.definition,
2946 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2947 )
2948 {
2949 return Some((binding.source.clone(), binding.declaration_byte));
2950 }
2951 if binding.source != *file {
2957 return None;
2958 }
2959 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2960 let root = prepared.tree().root_node();
2961 let source = prepared.source();
2962 let reference = root.descendant_for_byte_range(
2963 before_byte,
2964 before_byte.saturating_add(1).min(source.len()),
2965 )?;
2966 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2967 let cell = self.macro_event_cell(file);
2968 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2969 let event = events
2970 .iter()
2971 .find(|event| event.byte() == binding.declaration_byte)?;
2972 let MacroEvent::Define {
2973 name: defined_name,
2974 binding: definition,
2975 conditionals,
2976 ..
2977 } = event
2978 else {
2979 return None;
2980 };
2981 if defined_name != name
2982 || conditionals.is_empty()
2983 || !conditionals
2984 .iter()
2985 .all(|condition| reference_conditions.contains(condition))
2986 || !matches!(
2987 definition.definition,
2988 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2989 )
2990 {
2991 return None;
2992 }
2993 Some((definition.source.clone(), definition.declaration_byte))
2994 }
2995
2996 pub fn function_macro_type_argument<'tree>(
3003 &self,
3004 file: &ProjectFile,
3005 node: Node<'tree>,
3006 source: &str,
3007 ) -> Option<Node<'tree>> {
3008 if !is_c_source_file(file) {
3009 return None;
3010 }
3011 let (call, argument_index, argument) = c_function_macro_argument(node)?;
3012 let function = call.child_by_field_name("function")?;
3013 let function_name = node_text(function, source);
3014 if function_name.is_empty() {
3015 return None;
3016 }
3017 if !self.file_defines_macro_name(file, function_name)
3018 && !self
3019 .visible_identifier_candidates(file, function_name)
3020 .any(|candidate| candidate.is_macro())
3021 {
3022 return None;
3023 }
3024 let environment = self.macro_environment(file, call.start_byte());
3025 let binding = environment.binding(function_name)?;
3026 if !binding.is_exact() {
3027 return None;
3028 }
3029 let (parameters, replacement, variadic) = match &binding.definition {
3030 MacroDefinition::Function {
3031 parameters,
3032 replacement,
3033 } => (parameters, replacement, false),
3034 MacroDefinition::VariadicFunction {
3035 parameters,
3036 replacement,
3037 } => (parameters, replacement, true),
3038 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3039 };
3040 let actuals = call
3041 .child_by_field_name("arguments")
3042 .map(argument_children)
3043 .into_iter()
3044 .flatten()
3045 .collect::<Vec<_>>();
3046 let arity_matches = if variadic {
3047 actuals.len() >= parameters.len()
3048 } else {
3049 actuals.len() == parameters.len()
3050 };
3051 let type_parameters = self.macro_type_parameter_indices(
3052 &(binding.source.clone(), binding.declaration_byte),
3053 parameters,
3054 replacement,
3055 )?;
3056 if !arity_matches || !type_parameters.contains(&argument_index) {
3057 return None;
3058 }
3059 let type_node = macro_type_argument_node(argument, source)?;
3060 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3061 return None;
3062 }
3063 Some(type_node)
3064 }
3065
3066 fn macro_local_binding_template(
3067 &self,
3068 binding: &MacroBinding,
3069 parameters: &[String],
3070 replacement: &str,
3071 ) -> Option<Arc<MacroLocalBindingTemplate>> {
3072 let key = (binding.source.clone(), binding.declaration_byte);
3073 if let Some(template) = self
3074 .macro_local_binding_templates
3075 .lock()
3076 .expect("C++ macro local-binding cache poisoned")
3077 .get(&key)
3078 {
3079 return template.clone();
3080 }
3081 let template = (|| {
3082 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3083 let sentinel = body.source.as_str();
3084 let statements = body.statements()?;
3085 if statements.named_child_count() != 1 {
3086 return None;
3087 }
3088 let declaration = statements.named_child(0)?;
3089 if declaration.kind() != "declaration" {
3090 return None;
3091 }
3092 let type_node = declaration
3093 .child_by_field_name("type")
3094 .or_else(|| first_type_child(declaration))?;
3095 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3096 let mut cursor = declaration.walk();
3097 declaration.named_children(&mut cursor).find_map(|child| {
3098 if child.kind() == "init_declarator" {
3099 child.child_by_field_name("declarator")
3100 } else {
3101 is_declarator_node(child).then_some(child)
3102 }
3103 })
3104 })?;
3105 let name = extract_variable_name(declarator, sentinel)?;
3106 let pointer_depth =
3107 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3108 let type_text = node_text(type_node, sentinel).trim();
3109 let declared_type = parameters
3110 .iter()
3111 .position(|parameter| parameter == type_text)
3112 .map(MacroLocalBindingTypeTemplate::Parameter)
3113 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3114 Some(Arc::new(MacroLocalBindingTemplate {
3115 name,
3116 declared_type,
3117 pointer_depth,
3118 }))
3119 })();
3120 self.macro_local_binding_templates
3121 .lock()
3122 .expect("C++ macro local-binding cache poisoned")
3123 .insert(key, template.clone());
3124 template
3125 }
3126
3127 pub fn function_macro_replacement_body(
3134 &self,
3135 file: &ProjectFile,
3136 definition: Node<'_>,
3137 source: &str,
3138 ) -> Option<Arc<ParsedReplacementBody>> {
3139 debug_assert_eq!(definition.kind(), "preproc_function_def");
3140 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3141 MacroDefinition::Function {
3142 parameters,
3143 replacement,
3144 }
3145 | MacroDefinition::VariadicFunction {
3146 parameters,
3147 replacement,
3148 } => (parameters, replacement),
3149 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3150 };
3151 self.parsed_macro_replacement_body(
3152 &(file.clone(), definition.start_byte()),
3153 ¶meters,
3154 &replacement,
3155 )
3156 }
3157
3158 fn parsed_macro_replacement_body(
3166 &self,
3167 key: &(ProjectFile, usize),
3168 parameters: &[String],
3169 replacement: &str,
3170 ) -> Option<Arc<ParsedReplacementBody>> {
3171 if let Some(body) = self
3172 .macro_replacement_bodies
3173 .lock()
3174 .expect("C++ macro replacement body cache poisoned")
3175 .get(key)
3176 {
3177 return body.clone();
3178 }
3179 let body = (|| {
3180 if replacement.trim().is_empty() {
3181 return None;
3182 }
3183 let (source, tree, original_offsets) =
3184 Self::parse_macro_replacement_body(replacement, parameters)?;
3185 let body = ParsedReplacementBody {
3186 source,
3187 tree,
3188 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3189 parameters: parameters.to_vec(),
3190 original_offsets,
3191 };
3192 body.statements()?;
3193 if body.expands_variadic_arguments() {
3194 return None;
3195 }
3196 Some(Arc::new(body))
3197 })();
3198 self.macro_replacement_bodies
3199 .lock()
3200 .expect("C++ macro replacement body cache poisoned")
3201 .insert(key.clone(), body.clone());
3202 body
3203 }
3204
3205 fn parse_macro_replacement_body(
3213 replacement: &str,
3214 parameters: &[String],
3215 ) -> Option<(String, Tree, Box<[usize]>)> {
3216 let normalized = normalize_macro_continuations(replacement);
3217 let parse = |replacement: &str| {
3218 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3219 let mut parser = Parser::new();
3220 parser
3221 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3222 .ok()?;
3223 let tree = parser.parse(&source, None)?;
3224 Some((source, tree))
3225 };
3226 let (source, tree) = parse(&normalized)?;
3227 if !tree.root_node().has_error() {
3228 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3229 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3230 return Some((source, tree, original_offsets.into_boxed_slice()));
3231 }
3232
3233 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3234 let mut insertion_points = Vec::new();
3235 let mut stack = vec![tree.root_node()];
3236 while let Some(node) = stack.pop() {
3237 if matches!(
3238 node.kind(),
3239 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3240 ) && parameters
3241 .iter()
3242 .any(|parameter| parameter.as_str() == node_text(node, &source))
3243 && Self::macro_formal_needs_statement_separator(node)
3244 {
3245 let point = node.end_byte().saturating_sub(body_offset);
3246 if point <= normalized.len() && !insertion_points.contains(&point) {
3247 insertion_points.push(point);
3248 }
3249 }
3250 push_named_children_reversed(node, &mut stack);
3251 }
3252 if insertion_points.is_empty() {
3253 return None;
3254 }
3255 insertion_points.sort_unstable();
3256 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3257 let mut original_offsets =
3258 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3259 let mut next_insertion = 0;
3260 for (index, byte) in normalized.bytes().enumerate() {
3261 recovered.push(byte);
3262 original_offsets.push(index);
3263 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3264 recovered.push(b';');
3265 original_offsets.push(index + 1);
3266 next_insertion += 1;
3267 }
3268 }
3269 original_offsets.push(normalized.len());
3270 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3271 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3272 let (source, tree) = parse(&recovered)?;
3273 if tree.root_node().has_error() {
3274 return None;
3275 }
3276 Some((source, tree, original_offsets.into_boxed_slice()))
3277 }
3278
3279 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3284 offsets.extend([replacement_end; 3]);
3285 }
3286
3287 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3292 let mut current = node;
3293 let mut crossed_recovery = false;
3294 while let Some(parent) = current.parent() {
3295 if parent.is_error() {
3296 crossed_recovery = true;
3297 current = parent;
3298 continue;
3299 }
3300 if matches!(
3301 parent.kind(),
3302 "call_expression"
3303 | "argument_list"
3304 | "field_expression"
3305 | "binary_expression"
3306 | "unary_expression"
3307 | "assignment_expression"
3308 | "conditional_expression"
3309 | "parenthesized_expression"
3310 | "subscript_expression"
3311 ) {
3312 return false;
3313 }
3314 if parent.kind() == "expression_statement" {
3315 return parent.named_child_count() == 1;
3316 }
3317 if parent.kind() == "compound_statement" && current == node {
3318 return true;
3319 }
3320 if matches!(
3321 parent.kind(),
3322 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3323 ) {
3324 return crossed_recovery
3325 || parent.child_by_field_name("consequence") == Some(current);
3326 }
3327 if matches!(parent.kind(), "declaration" | "init_declarator") {
3328 return false;
3329 }
3330 current = parent;
3331 }
3332 false
3333 }
3334
3335 fn macro_type_parameter_indices(
3343 &self,
3344 key: &(ProjectFile, usize),
3345 parameters: &[String],
3346 replacement: &str,
3347 ) -> Option<Arc<[usize]>> {
3348 if let Some(indices) = self
3349 .macro_type_parameters
3350 .lock()
3351 .expect("C++ macro type-parameter cache poisoned")
3352 .get(key)
3353 {
3354 return indices.clone();
3355 }
3356 #[cfg(any(test, feature = "test-support"))]
3357 self.macro_replacement_parse_count
3358 .fetch_add(1, Ordering::Relaxed);
3359 let indices = (|| {
3360 if replacement.trim().is_empty() {
3361 return None;
3362 }
3363 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3367 let mut parser = Parser::new();
3368 parser
3369 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3370 .ok()?;
3371 let tree = parser.parse(&source, None)?;
3372 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3373 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3374 let body = ParsedReplacementBody {
3375 source,
3376 tree,
3377 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3378 parameters: parameters.to_vec(),
3379 original_offsets: original_offsets.into_boxed_slice(),
3380 };
3381 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3382 })();
3383 self.macro_type_parameters
3384 .lock()
3385 .expect("C++ macro type-parameter cache poisoned")
3386 .insert(key.clone(), indices.clone());
3387 indices
3388 }
3389
3390 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3391 let replacement = if node.kind() == "preproc_function_def" {
3392 function_macro_replacement_span(node, source)
3393 .and_then(|span| source.get(span))
3394 .map(str::to_owned)
3395 .or_else(|| {
3396 node.child_by_field_name("value")
3397 .map(|value| node_text(value, source).to_string())
3398 })
3399 .unwrap_or_default()
3400 } else {
3401 node.child_by_field_name("value")
3402 .map(|value| node_text(value, source).to_string())
3403 .unwrap_or_default()
3404 };
3405 if node.kind() == "preproc_def" {
3406 return MacroDefinition::Object { replacement };
3407 }
3408 let Some(parameters) = node.child_by_field_name("parameters") else {
3409 return MacroDefinition::Unsupported;
3410 };
3411 let variadic = (0..parameters.child_count()).any(|index| {
3412 parameters
3413 .child(index)
3414 .is_some_and(|child| child.kind() == "...")
3415 });
3416 let parameters = (0..parameters.named_child_count())
3417 .filter_map(|index| parameters.named_child(index))
3418 .map(|parameter| node_text(parameter, source).to_string())
3419 .collect::<Vec<_>>();
3420 if variadic {
3421 MacroDefinition::VariadicFunction {
3422 parameters,
3423 replacement,
3424 }
3425 } else {
3426 MacroDefinition::Function {
3427 parameters,
3428 replacement,
3429 }
3430 }
3431 }
3432
3433 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3434 clone_or_default_file_cell(
3435 &self.macro_event_cells,
3436 file,
3437 "C++ macro event cache poisoned",
3438 )
3439 }
3440
3441 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3442 if let Some(names) = self
3443 .macro_event_name_sets
3444 .lock()
3445 .expect("C++ macro event-name cache poisoned")
3446 .get(file)
3447 .cloned()
3448 {
3449 return names.contains(name);
3450 }
3451 let cell = self.macro_event_cell(file);
3452 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3453 let names = Arc::new(
3454 events
3455 .iter()
3456 .filter_map(|event| match event {
3457 MacroEvent::Define { name, .. } => Some(name.clone()),
3458 MacroEvent::Undef { .. }
3459 | MacroEvent::Include { .. }
3460 | MacroEvent::Invalidate { .. } => None,
3461 })
3462 .collect(),
3463 );
3464 self.macro_event_name_sets
3465 .lock()
3466 .expect("C++ macro event-name cache poisoned")
3467 .insert(file.clone(), Arc::clone(&names));
3468 names.contains(name)
3469 }
3470
3471 fn macro_environment_checkpoint_cell(
3472 &self,
3473 file: &ProjectFile,
3474 ) -> MacroEnvironmentCheckpointCell {
3475 clone_or_default_file_cell(
3476 &self.macro_environment_checkpoints,
3477 file,
3478 "C++ macro environment checkpoint cache poisoned",
3479 )
3480 }
3481
3482 pub fn macro_environment(
3484 &self,
3485 file: &ProjectFile,
3486 before_byte: usize,
3487 ) -> Arc<MacroEnvironment> {
3488 #[cfg(any(test, feature = "test-support"))]
3489 self.macro_environment_request_count
3490 .fetch_add(1, Ordering::Relaxed);
3491 let cell = self.macro_event_cell(file);
3492 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3493 let frontier = events.partition_point(|event| event.byte() < before_byte);
3494 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3495 let checkpoints =
3496 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3497 let checkpoint = checkpoints.at_or_before(frontier);
3498 if checkpoint.frontier == frontier {
3499 return Arc::clone(&checkpoint.environment);
3500 }
3501 #[cfg(any(test, feature = "test-support"))]
3502 self.macro_environment_copy_count
3503 .fetch_add(1, Ordering::Relaxed);
3504 let mut environment = checkpoint.environment.as_ref().clone();
3505 let mut include_stack = HashSet::from_iter([file.clone()]);
3506 for event in &events[checkpoint.frontier..frontier] {
3507 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3508 }
3509 Arc::new(environment)
3510 }
3511
3512 fn build_macro_environment_checkpoints(
3515 &self,
3516 file: &ProjectFile,
3517 events: &[MacroEvent],
3518 ) -> MacroEnvironmentCheckpoints {
3519 #[cfg(any(test, feature = "test-support"))]
3520 self.macro_environment_checkpoint_build_count
3521 .fetch_add(1, Ordering::Relaxed);
3522 let mut environment = MacroEnvironment {
3527 build_proven_defines: self
3528 .compile_proven_guards(file)
3529 .iter()
3530 .filter_map(|guard| match guard {
3531 PreprocessorGuard::Defined(name) => Some(name.clone()),
3532 _ => None,
3533 })
3534 .collect(),
3535 ..MacroEnvironment::default()
3536 };
3537 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3538 frontier: 0,
3539 environment: Arc::new(environment.clone()),
3540 }];
3541 let checkpoint_stride = events
3548 .len()
3549 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3550 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3551 let mut include_stack = HashSet::from_iter([file.clone()]);
3552 for (index, event) in events.iter().enumerate() {
3553 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3554 let frontier = index + 1;
3555 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3556 checkpoints.push(MacroEnvironmentCheckpoint {
3557 frontier,
3558 environment: Arc::new(environment.clone()),
3559 });
3560 }
3561 }
3562 MacroEnvironmentCheckpoints { checkpoints }
3563 }
3564
3565 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3574 self.macro_environment(file, before_byte)
3575 .binding(name)
3576 .is_some()
3577 }
3578
3579 pub fn macro_name_may_be_bound_at(
3580 &self,
3581 file: &ProjectFile,
3582 name: &str,
3583 before_byte: usize,
3584 ) -> bool {
3585 self.macro_environment(file, before_byte).may_bind(name)
3586 }
3587
3588 pub fn macro_binding_matches_target_at(
3592 &self,
3593 analyzer: &CppGraphSource<'_>,
3594 file: &ProjectFile,
3595 name: &str,
3596 before_byte: usize,
3597 target: &CodeUnit,
3598 ) -> bool {
3599 let ranges = analyzer.ranges(target);
3600 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3601 self.macro_binding_matches_target_declaration_at(
3602 file,
3603 name,
3604 before_byte,
3605 target.source(),
3606 &declaration_bytes,
3607 )
3608 }
3609
3610 pub(crate) fn macro_declaration_bytes(
3615 &self,
3616 target: &CodeUnit,
3617 ranges: &[Range],
3618 ) -> Vec<usize> {
3619 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3620 return Vec::new();
3621 };
3622 ranges
3623 .iter()
3624 .filter_map(|range| {
3625 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3626 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3627 node = node.parent()?;
3628 }
3629 Some(node.start_byte())
3630 })
3631 .collect()
3632 }
3633
3634 pub(crate) fn macro_binding_matches_target_declaration_at(
3635 &self,
3636 file: &ProjectFile,
3637 name: &str,
3638 before_byte: usize,
3639 target_source: &ProjectFile,
3640 target_declaration_bytes: &[usize],
3641 ) -> bool {
3642 let environment = self.macro_environment(file, before_byte);
3643 let Some(binding) = environment.binding(name) else {
3644 return false;
3645 };
3646 if binding.definition == MacroDefinition::Unsupported {
3647 return false;
3648 }
3649 if binding.source != *target_source {
3653 return false;
3654 }
3655 target_declaration_bytes.contains(&binding.declaration_byte)
3656 }
3657
3658 pub fn resolve_ordinary_macro_reference(
3665 &self,
3666 analyzer: &CppGraphSource<'_>,
3667 file: &ProjectFile,
3668 node: Node<'_>,
3669 source: &str,
3670 ) -> OrdinaryMacroReferenceResolution {
3671 if !is_ordinary_macro_reference_node(node) {
3672 return OrdinaryMacroReferenceResolution::Missing;
3673 }
3674 let name = node_text(node, source);
3675 if name.is_empty() {
3676 return OrdinaryMacroReferenceResolution::Missing;
3677 }
3678 let visible = self
3679 .visible_identifier_candidates(file, name)
3680 .filter(|candidate| candidate.is_macro())
3681 .cloned()
3682 .collect::<Vec<_>>();
3683 let mut exact = Vec::new();
3684 for candidate in &visible {
3685 if self.macro_binding_matches_target_at(
3686 analyzer,
3687 file,
3688 name,
3689 node.start_byte(),
3690 candidate,
3691 ) && !exact
3692 .iter()
3693 .any(|existing| same_visible_symbol(existing, candidate))
3694 {
3695 exact.push(candidate.clone());
3696 }
3697 }
3698 match exact.len() {
3699 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3700 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3701 0 if !visible.is_empty()
3702 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3703 {
3704 OrdinaryMacroReferenceResolution::Ambiguous
3705 }
3706 0 => OrdinaryMacroReferenceResolution::Missing,
3707 }
3708 }
3709
3710 pub fn recovered_c_reference_ranges(
3718 &self,
3719 file: &ProjectFile,
3720 root: Node<'_>,
3721 source: &str,
3722 limit: usize,
3723 ) -> RecoveredCReferenceRanges {
3724 if !is_c_source_file(file) {
3725 return RecoveredCReferenceRanges::Complete(Vec::new());
3726 }
3727 let mut ranges = Vec::new();
3728 let mut seen = HashSet::default();
3729 let mut stack = vec![(root, root.is_error())];
3730 while let Some((node, inside_error)) = stack.pop() {
3731 let inside_error =
3736 inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3737 if node.kind() == "preproc_arg" {
3738 let macro_value_kind = node.parent().and_then(|parent| {
3743 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3744 });
3745 if matches!(
3746 macro_value_kind,
3747 Some("preproc_def" | "preproc_function_def")
3748 ) {
3749 let name = node_text(node, source);
3750 if !name.is_empty()
3751 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3752 && !push_recovered_c_range(
3753 &mut ranges,
3754 &mut seen,
3755 node.start_byte(),
3756 node.end_byte(),
3757 node,
3758 limit,
3759 )
3760 {
3761 return RecoveredCReferenceRanges::LimitExceeded;
3762 }
3763 }
3764 if macro_value_kind == Some("preproc_def") {
3765 for reference in object_macro_replacement_type_references(node, source) {
3766 for range in reference.component_ranges {
3767 let visible = self
3768 .visible_identifier_candidates(file, &source[range.clone()])
3769 .any(|candidate| candidate.is_class() || candidate.is_module());
3770 if visible
3771 && !push_recovered_c_range(
3772 &mut ranges,
3773 &mut seen,
3774 range.start,
3775 range.end,
3776 node,
3777 limit,
3778 )
3779 {
3780 return RecoveredCReferenceRanges::LimitExceeded;
3781 }
3782 }
3783 }
3784 }
3785 }
3786 if inside_error
3787 && recovered_c_reference_node(self, file, node, source)
3788 && !push_recovered_c_range(
3789 &mut ranges,
3790 &mut seen,
3791 node.start_byte(),
3792 node.end_byte(),
3793 node,
3794 limit,
3795 )
3796 {
3797 return RecoveredCReferenceRanges::LimitExceeded;
3798 }
3799 let mut cursor = node.walk();
3800 for child in node.named_children(&mut cursor) {
3801 stack.push((child, inside_error));
3802 }
3803 }
3804 ranges.sort_unstable();
3805 RecoveredCReferenceRanges::Complete(ranges)
3806 }
3807
3808 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3814 self.visible_identifier_candidates(file, target.identifier())
3815 .filter(|candidate| candidate.is_macro())
3816 .any(|candidate| {
3817 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3818 })
3819 }
3820
3821 pub fn object_macro_replacement_at(
3822 &self,
3823 file: &ProjectFile,
3824 name: &str,
3825 before_byte: usize,
3826 ) -> Option<String> {
3827 let environment = self.macro_environment(file, before_byte);
3828 let binding = environment.binding(name)?;
3829 if !binding.exact {
3830 return None;
3831 }
3832 match &binding.definition {
3833 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3834 MacroDefinition::Function { .. }
3835 | MacroDefinition::VariadicFunction { .. }
3836 | MacroDefinition::Unsupported => None,
3837 }
3838 }
3839
3840 fn apply_macro_events(
3841 &self,
3842 file: &ProjectFile,
3843 before_byte: Option<usize>,
3844 environment: &mut MacroEnvironment,
3845 include_stack: &mut HashSet<ProjectFile>,
3846 ) {
3847 if !include_stack.insert(file.clone()) {
3848 return;
3849 }
3850 if self.cpp.prepared_syntax(self.token, file).is_none() {
3851 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3852 include_stack.remove(file);
3853 return;
3854 }
3855 match self.macro_include_protection(file) {
3856 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3857 Some(binding) if binding.is_exact() => {
3858 include_stack.remove(file);
3859 return;
3860 }
3861 Some(_) | None if environment.unknown_names => {
3862 let mut ambiguous_seen = HashSet::default();
3863 self.mark_macro_events_ambiguous(
3864 file,
3865 environment,
3866 &mut ambiguous_seen,
3867 file,
3868 before_byte.unwrap_or_default(),
3869 );
3870 include_stack.remove(file);
3871 return;
3872 }
3873 Some(_) => {
3874 let mut ambiguous_seen = HashSet::default();
3875 self.mark_macro_events_ambiguous(
3876 file,
3877 environment,
3878 &mut ambiguous_seen,
3879 file,
3880 before_byte.unwrap_or_default(),
3881 );
3882 include_stack.remove(file);
3883 return;
3884 }
3885 None => {}
3886 },
3887 MacroIncludeProtection::PragmaOnce => {
3888 if !environment.applied_pragma_once_files.insert(file.clone()) {
3889 include_stack.remove(file);
3890 return;
3891 }
3892 if environment.maybe_applied_pragma_once_files.remove(file) {
3893 let mut ambiguous_seen = HashSet::default();
3898 environment.applied_pragma_once_files.remove(file);
3899 self.mark_macro_events_ambiguous(
3900 file,
3901 environment,
3902 &mut ambiguous_seen,
3903 file,
3904 before_byte.unwrap_or_default(),
3905 );
3906 environment.maybe_applied_pragma_once_files.remove(file);
3907 environment.applied_pragma_once_files.insert(file.clone());
3908 include_stack.remove(file);
3909 return;
3910 }
3911 }
3912 MacroIncludeProtection::None => {}
3913 }
3914 let cell = self.macro_event_cell(file);
3915 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3916 for event in events {
3917 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3918 break;
3919 }
3920 self.apply_macro_event(file, event, environment, include_stack);
3921 }
3922 include_stack.remove(file);
3923 }
3924
3925 fn apply_macro_event(
3926 &self,
3927 file: &ProjectFile,
3928 event: &MacroEvent,
3929 environment: &mut MacroEnvironment,
3930 include_stack: &mut HashSet<ProjectFile>,
3931 ) {
3932 #[cfg(any(test, feature = "test-support"))]
3933 self.macro_event_application_count
3934 .fetch_add(1, Ordering::Relaxed);
3935 match event {
3936 MacroEvent::Define {
3937 name,
3938 binding,
3939 conditionals,
3940 byte,
3941 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3942 Some(true) => environment.insert(name.clone(), binding.clone()),
3943 Some(false) => {}
3944 None => Self::merge_conditional_macro_definition(
3945 environment,
3946 name,
3947 binding,
3948 file,
3949 *byte,
3950 ),
3951 },
3952 MacroEvent::Undef {
3953 name,
3954 conditionals,
3955 byte,
3956 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3957 Some(true) => environment.remove(name),
3958 Some(false) => {}
3959 None => {
3960 if environment.binding(name).is_some() {
3961 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3962 }
3963 }
3964 },
3965 MacroEvent::Include {
3966 targets,
3967 conditionals,
3968 byte,
3969 } => {
3970 let condition =
3971 self.macro_event_condition_value(file, *byte, environment, conditionals);
3972 if condition == Some(false) {
3973 return;
3974 }
3975 if targets.is_empty() {
3976 environment.mark_unknown_names(file, *byte);
3977 return;
3978 }
3979 if condition.is_none() || targets.len() > 1 {
3980 let mut ambiguous_seen = HashSet::default();
3981 for target in targets {
3982 self.mark_macro_events_ambiguous(
3983 target,
3984 environment,
3985 &mut ambiguous_seen,
3986 file,
3987 *byte,
3988 );
3989 }
3990 } else if let Some(target) = targets.first() {
3991 self.apply_macro_events(target, None, environment, include_stack);
3992 }
3993 }
3994 MacroEvent::Invalidate { byte } => {
3995 for binding in environment.bindings.values_mut() {
3996 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3997 }
3998 }
3999 }
4000 }
4001
4002 fn macro_event_condition_value(
4013 &self,
4014 file: &ProjectFile,
4015 event_byte: usize,
4016 environment: &MacroEnvironment,
4017 conditionals: &OwningPreprocessorConditionals,
4018 ) -> Option<bool> {
4019 if conditionals.is_empty() {
4020 return Some(true);
4021 }
4022 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4023 let source = prepared.source();
4024 let root = prepared.tree().root_node();
4025 let descendant = root.descendant_for_byte_range(
4026 event_byte,
4027 event_byte.saturating_add(1).min(source.len()),
4028 )?;
4029 let mut unknown = false;
4030 let mut current = descendant.parent();
4031 while let Some(conditional) = current {
4032 if matches!(
4033 conditional.kind(),
4034 "preproc_if" | "preproc_ifdef" | "preproc_elif"
4035 ) && conditionals.contains(&conditional.start_byte())
4036 {
4037 let mut value = match conditional.kind() {
4038 "preproc_ifdef" => {
4039 let name = conditional.child_by_field_name("name")?;
4040 let defined =
4041 self.macro_name_defined_value(environment, node_text(name, source));
4042 match conditional.child(0)?.kind() {
4043 "#ifdef" => defined,
4044 "#ifndef" => defined.map(|defined| !defined),
4045 _ => None,
4046 }
4047 }
4048 "preproc_if" | "preproc_elif" => conditional
4049 .child_by_field_name("condition")
4050 .and_then(|condition| {
4051 self.preprocessor_integer_value(
4052 condition,
4053 source,
4054 environment,
4055 &mut Vec::new(),
4056 0,
4057 )
4058 })
4059 .map(|value| value != 0),
4060 _ => unreachable!(),
4061 };
4062 match preprocessor_branch_of_descendant(
4063 conditional,
4064 descendant,
4065 &mut PreprocessorContextCache::default(),
4066 ) {
4067 PreprocessorBranch::Consequence => {}
4068 PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4069 value = value.map(|value| !value);
4070 }
4071 PreprocessorBranch::Unreadable => value = None,
4072 }
4073 match value {
4074 Some(true) => {}
4075 Some(false) => return Some(false),
4076 None => unknown = true,
4077 }
4078 }
4079 current = conditional.parent();
4080 }
4081 (!unknown).then_some(true)
4082 }
4083
4084 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4085 if environment.known_undefined_names.contains(name) {
4086 return Some(false);
4087 }
4088 if let Some(binding) = environment.binding(name) {
4089 return binding.is_exact().then_some(true);
4090 }
4091 environment
4092 .build_proven_defines
4093 .contains(name)
4094 .then_some(true)
4095 }
4096
4097 fn preprocessor_integer_value(
4098 &self,
4099 expression: Node<'_>,
4100 source: &str,
4101 environment: &MacroEnvironment,
4102 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4103 depth: usize,
4104 ) -> Option<i128> {
4105 if depth >= 64 {
4108 return None;
4109 }
4110 match expression.kind() {
4111 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4112 "identifier" | "type_identifier" => {
4113 let binding = environment.binding(node_text(expression, source))?;
4114 if !binding.is_exact() {
4115 return None;
4116 }
4117 let MacroDefinition::Object { replacement } = &binding.definition else {
4118 return None;
4119 };
4120 let identity = (binding.source.clone(), binding.declaration_byte);
4121 if expansion_stack.contains(&identity) {
4122 return None;
4123 }
4124 expansion_stack.push(identity);
4125 let parsed = self.parsed_macro_replacement(binding, replacement);
4126 let value = match parsed.as_ref() {
4127 ParsedMacroReplacement::Parsed {
4128 source: replacement_source,
4129 tree,
4130 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4131 .and_then(|call| call.child_by_field_name("arguments"))
4132 .and_then(|arguments| argument_children(arguments).next())
4133 .and_then(|argument| {
4134 self.preprocessor_integer_value(
4135 argument,
4136 replacement_source,
4137 environment,
4138 expansion_stack,
4139 depth + 1,
4140 )
4141 }),
4142 ParsedMacroReplacement::Unsupported => None,
4143 };
4144 expansion_stack.pop();
4145 value
4146 }
4147 "preproc_defined" => {
4148 let mut cursor = expression.walk();
4149 let name = expression
4150 .named_children(&mut cursor)
4151 .find(|child| child.kind() == "identifier")?;
4152 self.macro_name_defined_value(environment, node_text(name, source))
4153 .map(i128::from)
4154 }
4155 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4156 self.preprocessor_integer_value(
4157 child,
4158 source,
4159 environment,
4160 expansion_stack,
4161 depth + 1,
4162 )
4163 }),
4164 "unary_expression" => {
4165 let operator = expression.child_by_field_name("operator")?.kind();
4166 let argument = expression.child_by_field_name("argument")?;
4167 let value = self.preprocessor_integer_value(
4168 argument,
4169 source,
4170 environment,
4171 expansion_stack,
4172 depth + 1,
4173 )?;
4174 match operator {
4175 "+" => Some(value),
4176 "-" => value.checked_neg(),
4177 "!" => Some(i128::from(value == 0)),
4178 "~" => Some(!value),
4179 _ => None,
4180 }
4181 }
4182 "binary_expression" => {
4183 let left = self.preprocessor_integer_value(
4184 expression.child_by_field_name("left")?,
4185 source,
4186 environment,
4187 expansion_stack,
4188 depth + 1,
4189 )?;
4190 let right = self.preprocessor_integer_value(
4191 expression.child_by_field_name("right")?,
4192 source,
4193 environment,
4194 expansion_stack,
4195 depth + 1,
4196 )?;
4197 match expression.child_by_field_name("operator")?.kind() {
4198 "+" => left.checked_add(right),
4199 "-" => left.checked_sub(right),
4200 "*" => left.checked_mul(right),
4201 "/" => left.checked_div(right),
4202 "%" => left.checked_rem(right),
4203 "<<" => u32::try_from(right)
4204 .ok()
4205 .and_then(|shift| left.checked_shl(shift)),
4206 ">>" => u32::try_from(right)
4207 .ok()
4208 .and_then(|shift| left.checked_shr(shift)),
4209 "<" => Some(i128::from(left < right)),
4210 "<=" => Some(i128::from(left <= right)),
4211 ">" => Some(i128::from(left > right)),
4212 ">=" => Some(i128::from(left >= right)),
4213 "==" => Some(i128::from(left == right)),
4214 "!=" => Some(i128::from(left != right)),
4215 "&" => Some(left & right),
4216 "|" => Some(left | right),
4217 "^" => Some(left ^ right),
4218 "&&" => Some(i128::from(left != 0 && right != 0)),
4219 "||" => Some(i128::from(left != 0 || right != 0)),
4220 _ => None,
4221 }
4222 }
4223 _ => None,
4224 }
4225 }
4226
4227 fn mark_macro_events_ambiguous(
4228 &self,
4229 file: &ProjectFile,
4230 environment: &mut MacroEnvironment,
4231 include_stack: &mut HashSet<ProjectFile>,
4232 conditional_file: &ProjectFile,
4233 conditional_byte: usize,
4234 ) {
4235 if !include_stack.insert(file.clone()) {
4236 return;
4237 }
4238 if self.cpp.prepared_syntax(self.token, file).is_none() {
4239 environment.mark_unknown_names(conditional_file, conditional_byte);
4240 return;
4241 }
4242 match self.macro_include_protection(file) {
4243 MacroIncludeProtection::MacroGuard(guard) => {
4244 if environment
4245 .binding(&guard)
4246 .is_some_and(MacroBinding::is_exact)
4247 {
4248 return;
4249 }
4250 }
4251 MacroIncludeProtection::PragmaOnce => {
4252 if environment.applied_pragma_once_files.contains(file) {
4253 return;
4254 }
4255 environment
4256 .maybe_applied_pragma_once_files
4257 .insert(file.clone());
4258 }
4259 MacroIncludeProtection::None => {}
4260 }
4261 let cell = self.macro_event_cell(file);
4262 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4263 for event in events {
4264 #[cfg(any(test, feature = "test-support"))]
4265 self.macro_event_application_count
4266 .fetch_add(1, Ordering::Relaxed);
4267 match event {
4268 MacroEvent::Define { name, binding, .. } => {
4269 Self::merge_conditional_macro_definition(
4270 environment,
4271 name,
4272 binding,
4273 conditional_file,
4274 conditional_byte,
4275 );
4276 }
4277 MacroEvent::Undef { name, .. } => {
4278 if environment.binding(name).is_some() {
4279 environment.insert(
4280 name.clone(),
4281 MacroBinding::ambiguous(conditional_file, conditional_byte),
4282 );
4283 } else {
4284 environment.remove_known_undefined(name);
4285 }
4286 }
4287 MacroEvent::Include { targets, .. } => {
4288 if targets.is_empty() {
4289 environment.mark_unknown_names(conditional_file, conditional_byte);
4290 continue;
4291 }
4292 for target in targets {
4293 self.mark_macro_events_ambiguous(
4294 target,
4295 environment,
4296 include_stack,
4297 conditional_file,
4298 conditional_byte,
4299 );
4300 }
4301 }
4302 MacroEvent::Invalidate { .. } => {
4303 for binding in environment.bindings.values_mut() {
4304 *binding = MacroBinding::uncertain_from(
4305 binding,
4306 conditional_file,
4307 conditional_byte,
4308 );
4309 }
4310 }
4311 }
4312 }
4313 }
4314
4315 fn merge_conditional_macro_definition(
4316 environment: &mut MacroEnvironment,
4317 name: &str,
4318 possible_binding: &MacroBinding,
4319 conditional_file: &ProjectFile,
4320 conditional_byte: usize,
4321 ) {
4322 if environment.binding(name).is_some_and(|current| {
4327 current.definition != MacroDefinition::Unsupported
4328 && current.definition == possible_binding.definition
4329 }) {
4330 return;
4331 }
4332 environment.insert(
4333 name.to_string(),
4334 MacroBinding::ambiguous(conditional_file, conditional_byte),
4335 );
4336 }
4337
4338 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4339 let cell = self
4340 .macro_include_protection_cells
4341 .lock()
4342 .expect("C++ include protection cache poisoned")
4343 .entry(file.clone())
4344 .or_default()
4345 .clone();
4346 cell.get_or_init(|| {
4347 self.cpp.prepared_syntax(self.token, file).map_or(
4348 MacroIncludeProtection::None,
4349 |prepared| {
4350 top_level_macro_include_protection(
4351 prepared.tree().root_node(),
4352 prepared.source(),
4353 )
4354 },
4355 )
4356 })
4357 .clone()
4358 }
4359
4360 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4361 collect_macro_events(self.cpp, self.token, file)
4362 }
4363
4364 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4365 self.ordinary_type_import_cells
4366 .lock()
4367 .expect("C++ ordinary type import cache poisoned")
4368 .entry(file.clone())
4369 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4370 .clone()
4371 }
4372
4373 pub fn project_using_index(
4374 &self,
4375 build: impl FnOnce() -> ProjectUsingIndex,
4376 ) -> &ProjectUsingIndex {
4377 self.project_using_index.get_or_init(build)
4378 }
4379
4380 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4381 let mut files = self
4382 .visible_source_files_by_root
4383 .values()
4384 .flatten()
4385 .cloned()
4386 .collect::<HashSet<_>>()
4387 .into_iter()
4388 .collect::<Vec<_>>();
4389 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4390 files
4391 }
4392
4393 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4394 self.visible_source_files_by_root
4395 .get(root)
4396 .is_some_and(|files| files.contains(source))
4397 }
4398
4399 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4400 let cached = self
4401 .visible_parser_alias_name_sets
4402 .read()
4403 .expect("visible parser alias-name cache poisoned")
4404 .get(file)
4405 .cloned();
4406 let cell = if let Some(cached) = cached {
4407 cached
4408 } else {
4409 let mut cells = self
4410 .visible_parser_alias_name_sets
4411 .write()
4412 .expect("visible parser alias-name cache poisoned");
4413 Arc::clone(
4414 cells
4415 .entry(file.clone())
4416 .or_insert_with(|| Arc::new(OnceLock::new())),
4417 )
4418 };
4419 if let Some(names) = cell.get() {
4420 return names.contains(name);
4421 }
4422 let mut names = HashSet::default();
4423 let visible_files = self
4424 .visible_source_files_by_root
4425 .get(file)
4426 .cloned()
4427 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4428 for visible_file in visible_files {
4429 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4430 return false;
4431 };
4432 for alias in aliases.get().expect("published canonical aliases").iter() {
4433 names.insert(alias.name.clone());
4434 }
4435 }
4436 #[cfg(any(test, feature = "test-support"))]
4437 self.visible_parser_alias_name_set_build_count
4438 .fetch_add(1, Ordering::Relaxed);
4439 cell.get_or_init(|| names).contains(name)
4440 }
4441
4442 pub fn parser_alias_name_may_resolve_to_target(
4443 &self,
4444 file: &ProjectFile,
4445 alias_name: &str,
4446 target: &CodeUnit,
4447 ) -> bool {
4448 let started = std::time::Instant::now();
4449 self.parser_alias_fallback_calls
4450 .fetch_add(1, Ordering::Relaxed);
4451 let key = (
4452 file.clone(),
4453 alias_name.to_string(),
4454 logical_symbol_key(target),
4455 );
4456 let cached = self
4457 .parser_alias_target_matches
4458 .read()
4459 .expect("parser alias target-match cache poisoned")
4460 .get(&key)
4461 .cloned();
4462 let cell = if let Some(cached) = cached {
4463 cached
4464 } else {
4465 let mut cells = self
4466 .parser_alias_target_matches
4467 .write()
4468 .expect("parser alias target-match cache poisoned");
4469 Arc::clone(
4470 cells
4471 .entry(key)
4472 .or_insert_with(|| Arc::new(OnceLock::new())),
4473 )
4474 };
4475 let matched = *cell.get_or_init(|| {
4476 any_visible_source_file_matches(
4477 &self.visible_source_files_by_root,
4478 file,
4479 |visible_file| {
4480 self.parser_alias_fallback_files
4481 .fetch_add(1, Ordering::Relaxed);
4482 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4483 },
4484 )
4485 });
4486 self.parser_alias_fallback_elapsed_micros.fetch_add(
4487 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4488 Ordering::Relaxed,
4489 );
4490 matched
4491 }
4492
4493 fn callable_arities_for_target(
4494 &self,
4495 analyzer: &CppGraphSource<'_>,
4496 cpp: &dyn CppSource,
4497 file: &ProjectFile,
4498 prepared: &PreparedSyntaxTree,
4499 spec: &TargetSpec,
4500 ) -> Vec<ActivatedCallableArity> {
4501 let Some(signature) = spec.target.signature() else {
4502 return Vec::new();
4503 };
4504 let Some(candidates) = self
4505 .visible_by_identifier
4506 .get(file)
4507 .and_then(|by_name| by_name.get(&spec.member_name))
4508 else {
4509 return Vec::new();
4510 };
4511 let differing_candidates = candidates
4512 .iter()
4513 .filter(|candidate| {
4514 candidate.is_function()
4515 && candidate.fq_name() == spec.target.fq_name()
4516 && candidate.signature() == Some(signature)
4517 })
4518 .filter_map(|candidate| {
4519 analyzer
4520 .signature_metadata(candidate)
4521 .into_iter()
4522 .find_map(|metadata| metadata.callable_arity())
4523 .filter(|arity| Some(*arity) != spec.callable_arity)
4524 .map(|arity| (candidate, arity))
4525 })
4526 .collect::<Vec<_>>();
4527 if differing_candidates.is_empty() {
4528 return Vec::new();
4529 }
4530 let mut arities = Vec::with_capacity(differing_candidates.len());
4531 let reference = CallableReferenceContext {
4534 file,
4535 position: None,
4536 };
4537 for (candidate, candidate_arity) in differing_candidates {
4538 let declaration_activation =
4539 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4540 let Some(declaration_activation) = declaration_activation else {
4541 continue;
4542 };
4543 let activation_byte = if candidate.source() == file {
4544 Some(declaration_activation)
4545 } else {
4546 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4547 };
4548 if let Some(activation_byte) = activation_byte {
4549 arities.push(ActivatedCallableArity {
4550 activation_byte,
4551 arity: candidate_arity,
4552 });
4553 }
4554 }
4555 arities
4556 }
4557
4558 fn callable_parameter_macro_arity(
4559 &self,
4560 target: &CodeUnit,
4561 parameter_types: Option<&[String]>,
4562 ) -> Option<CallableArity> {
4563 let parameter_types = parameter_types?;
4564 let [macro_name] = parameter_types else {
4565 return None;
4566 };
4567 if macro_name.is_empty()
4568 || !macro_name
4569 .chars()
4570 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4571 {
4572 return None;
4573 }
4574 let cache_key = (target.source().clone(), macro_name.clone());
4575 if let Some(cached) = self
4576 .callable_parameter_macro_arities
4577 .lock()
4578 .expect("C++ callable parameter-macro arity cache poisoned")
4579 .get(&cache_key)
4580 .copied()
4581 {
4582 return cached;
4583 }
4584 let mut visible_files = HashSet::default();
4585 collect_include_closure(
4586 &self.cpp_source(),
4587 self.cpp.include_target_index(),
4588 target.source(),
4589 &mut visible_files,
4590 None,
4591 );
4592 let mut arities = Vec::new();
4593 for visible_file in visible_files {
4594 let cell = self.macro_event_cell(&visible_file);
4595 for event in
4596 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4597 {
4598 let MacroEvent::Define { name, binding, .. } = event else {
4599 continue;
4600 };
4601 if name != macro_name {
4602 continue;
4603 }
4604 let MacroDefinition::Object { replacement } = &binding.definition else {
4605 continue;
4606 };
4607 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4608 continue;
4609 };
4610 if !arities.contains(&arity) {
4611 arities.push(arity);
4612 }
4613 }
4614 }
4615 let resolved = (|| {
4616 let required = arities
4617 .iter()
4618 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4619 .min()?;
4620 let total = arities.iter().map(|arity| arity.total()).max()?;
4621 let repeated = arities
4622 .iter()
4623 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4624 Some(CallableArity::new(required, total, repeated))
4629 })();
4630 self.callable_parameter_macro_arities
4631 .lock()
4632 .expect("C++ callable parameter-macro arity cache poisoned")
4633 .insert(cache_key, resolved);
4634 resolved
4635 }
4636
4637 pub fn include_activation_for_source(
4638 &self,
4639 cpp: &dyn CppSource,
4640 file: &ProjectFile,
4641 prepared: &PreparedSyntaxTree,
4642 donor_source: &ProjectFile,
4643 ) -> Option<usize> {
4644 let key = (file.clone(), donor_source.clone());
4645 if let Some(cached) = self
4646 .include_activation_cells
4647 .lock()
4648 .expect("C++ include activation cache poisoned")
4649 .get(&key)
4650 .copied()
4651 {
4652 return cached;
4653 }
4654 #[cfg(any(test, feature = "test-support"))]
4655 self.include_activation_build_count
4656 .fetch_add(1, Ordering::Relaxed);
4657 let language_guard_stable = OnceCell::new();
4658 let activation =
4659 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4660 *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4661 });
4662 let mut cells = self
4663 .include_activation_cells
4664 .lock()
4665 .expect("C++ include activation cache poisoned");
4666 *cells.entry(key).or_insert(activation)
4667 }
4668
4669 pub fn conditional_include_projections_for_source(
4670 &self,
4671 file: &ProjectFile,
4672 prepared: &PreparedSyntaxTree,
4673 donor_source: &ProjectFile,
4674 ) -> Arc<[ConditionalIncludeProjection]> {
4675 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4676 let cell = self
4677 .conditional_include_projection_cells
4678 .lock()
4679 .expect("C++ conditional include projection cache poisoned")
4680 .entry(file.clone())
4681 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4682 .clone();
4683 let index = cell.get_or_build_pool_independent(|| {
4684 #[cfg(any(test, feature = "test-support"))]
4685 self.conditional_include_projection_index_build_count
4686 .fetch_add(1, Ordering::Relaxed);
4687 let language_guard_stable = OnceCell::new();
4688 find_conditional_include_projection_index(
4689 self.cpp,
4690 self.token,
4691 file,
4692 prepared,
4693 &|| {
4694 *language_guard_stable
4695 .get_or_init(|| self.include_language_guard_is_stable(file))
4696 },
4697 &|| {
4698 #[cfg(any(test, feature = "test-support"))]
4699 self.conditional_include_projection_state_count
4700 .fetch_add(1, Ordering::Relaxed);
4701 },
4702 )
4703 });
4704 index
4705 .get(donor_source)
4706 .cloned()
4707 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4708 }
4709
4710 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4711 let mut macro_events = |file: &ProjectFile| {
4712 let cell = self.macro_event_cell(file);
4713 cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4714 .clone()
4715 };
4716 include_language_guard_is_stable_with(
4717 self.cpp,
4718 self.token,
4719 file,
4720 &mut macro_events,
4721 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4722 self.structured_include_facts(file, prepared)
4723 },
4724 )
4725 }
4726
4727 #[cfg(any(test, feature = "test-support"))]
4728 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4729 (
4730 self.conditional_include_projection_index_build_count
4731 .load(Ordering::Relaxed),
4732 self.conditional_include_projection_state_count
4733 .load(Ordering::Relaxed),
4734 )
4735 }
4736
4737 #[cfg(any(test, feature = "test-support"))]
4738 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4739 self.conditional_include_target_state_count
4740 .load(Ordering::Relaxed)
4741 }
4742
4743 #[cfg(any(test, feature = "test-support"))]
4744 pub fn include_activation_build_count_for_test(&self) -> usize {
4745 self.include_activation_build_count.load(Ordering::Relaxed)
4746 }
4747
4748 #[cfg(any(test, feature = "test-support"))]
4749 pub fn note_using_donor_activation_for_test(&self) {
4750 self.using_donor_activation_count
4751 .fetch_add(1, Ordering::Relaxed);
4752 }
4753
4754 #[cfg(not(any(test, feature = "test-support")))]
4755 pub fn note_using_donor_activation_for_test(&self) {}
4756
4757 #[cfg(any(test, feature = "test-support"))]
4758 pub fn note_using_namespace_lookup_for_test(&self) {
4759 self.using_namespace_lookup_count
4760 .fetch_add(1, Ordering::Relaxed);
4761 }
4762
4763 #[cfg(not(any(test, feature = "test-support")))]
4764 pub fn note_using_namespace_lookup_for_test(&self) {}
4765
4766 #[cfg(any(test, feature = "test-support"))]
4767 pub fn note_using_name_candidate_inspection_for_test(&self) {
4768 self.using_name_candidate_inspection_count
4769 .fetch_add(1, Ordering::Relaxed);
4770 }
4771
4772 #[cfg(not(any(test, feature = "test-support")))]
4773 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4774
4775 #[cfg(any(test, feature = "test-support"))]
4776 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4777 (
4778 self.using_donor_activation_count.load(Ordering::Relaxed),
4779 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4780 self.callable_reference_spec_build_count
4781 .load(Ordering::Relaxed),
4782 self.using_name_candidate_inspection_count
4783 .load(Ordering::Relaxed),
4784 )
4785 }
4786
4787 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4788 file == target.source()
4789 || self
4790 .visible_by_file
4791 .get(file)
4792 .is_some_and(|visible| visible.contains(target))
4793 }
4794
4795 pub fn declaration_visible_at(
4807 &self,
4808 analyzer: &CppGraphSource<'_>,
4809 file: &ProjectFile,
4810 declaration: &CodeUnit,
4811 reference_byte: usize,
4812 ) -> bool {
4813 let reference_guards = OnceCell::new();
4814 self.visible_identifier_candidates(file, declaration.identifier())
4815 .filter(|candidate| {
4816 self.same_logical_callable(analyzer, candidate, declaration)
4817 || flattened_macro_namespace_declaration_matches(
4818 analyzer,
4819 self.cpp,
4820 file,
4821 candidate,
4822 declaration,
4823 reference_byte,
4824 )
4825 })
4826 .any(|candidate| {
4827 self.physical_declaration_visible_at(
4828 analyzer,
4829 file,
4830 candidate,
4831 reference_byte,
4832 &reference_guards,
4833 )
4834 })
4835 }
4836
4837 pub fn declaration_visible_at_reference(
4844 &self,
4845 analyzer: &CppGraphSource<'_>,
4846 file: &ProjectFile,
4847 declaration: &CodeUnit,
4848 reference: Node<'_>,
4849 ) -> bool {
4850 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4851 return false;
4852 };
4853 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4854 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4855 if !declaration_guards.iter().any(|(_, required)| {
4856 guards_compatible_at_reference(required, reference_guards.as_ref())
4857 }) {
4858 return false;
4859 }
4860 if declaration.source() == file
4861 && !analyzer.reference_uses_c_semantics(file)
4862 && (declaration.is_field() || declaration.is_callable())
4863 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4864 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4865 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4866 })
4867 {
4868 return true;
4872 }
4873 if declaration.is_field() && declaration.source() == file {
4874 let reference_byte = reference.start_byte();
4875 let reference_function =
4876 real_function_definition_ancestor(reference, prepared.source());
4877 let guards = OnceCell::new();
4878 let field_reference = CallableReferenceContext {
4879 file,
4880 position: Some(CallableReferencePosition {
4881 prepared: prepared.as_ref(),
4882 byte: reference_byte,
4883 guards: &guards,
4884 }),
4885 };
4886 let mut has_local_declaration = false;
4887 let mut local_declaration_visible = false;
4888 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4889 {
4890 let Some(declaration_function) =
4891 real_function_definition_ancestor(declaration, prepared.source())
4892 else {
4893 continue;
4894 };
4895 has_local_declaration = true;
4896 if reference_function.is_some_and(|reference_function| {
4897 reference_function.start_byte() == declaration_function.start_byte()
4898 && reference_function.end_byte() == declaration_function.end_byte()
4899 }) && callable_preprocessor_context_is_visible_for_reference(
4900 declaration,
4901 prepared.source(),
4902 &field_reference,
4903 ) && declaration.end_byte() < reference_byte
4904 {
4905 local_declaration_visible = true;
4906 break;
4907 }
4908 }
4909 if has_local_declaration {
4910 return local_declaration_visible;
4911 }
4912 }
4913 let guards = OnceCell::new();
4914 self.physical_declaration_visible_at(
4915 analyzer,
4916 file,
4917 declaration,
4918 reference.start_byte(),
4919 &guards,
4920 )
4921 }
4922
4923 pub fn declaration_visible_for_c_forward_call(
4929 &self,
4930 analyzer: &CppGraphSource<'_>,
4931 file: &ProjectFile,
4932 declaration: &CodeUnit,
4933 reference_byte: usize,
4934 ) -> bool {
4935 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4936 return true;
4937 }
4938 if declaration.source() != file {
4939 return false;
4940 }
4941 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4942 return false;
4943 };
4944 let reference_guards = prepared
4945 .tree()
4946 .root_node()
4947 .descendant_for_byte_range(reference_byte, reference_byte)
4948 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4949 declaration_guard_requirements(analyzer, self.cpp, declaration)
4950 .into_iter()
4951 .any(|(_, required)| {
4952 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4953 })
4954 }
4955
4956 pub fn callable_arity_at_reference(
4957 &self,
4958 analyzer: &CppGraphSource<'_>,
4959 file: &ProjectFile,
4960 candidate: &CodeUnit,
4961 reference_byte: usize,
4962 ) -> Option<CallableArity> {
4963 let key = (file.clone(), logical_symbol_key(candidate));
4964 let cell = self
4965 .callable_reference_specs
4966 .lock()
4967 .expect("C++ callable reference-spec cache poisoned")
4968 .entry(key)
4969 .or_default()
4970 .clone();
4971 let spec = cell.get_or_init(|| {
4972 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4973 let spec = TargetSpec::from_target(analyzer, candidate)?;
4974 let spec = spec
4975 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4976 .into_owned();
4977 #[cfg(any(test, feature = "test-support"))]
4978 self.callable_reference_spec_build_count
4979 .fetch_add(1, Ordering::Relaxed);
4980 Some(spec)
4981 });
4982 spec.as_ref()?.callable_arity_at(reference_byte)
4983 }
4984
4985 fn physical_declaration_visible_at(
4986 &self,
4987 analyzer: &CppGraphSource<'_>,
4988 file: &ProjectFile,
4989 declaration: &CodeUnit,
4990 reference_byte: usize,
4991 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4992 ) -> bool {
4993 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4994 return false;
4995 };
4996 let reference = CallableReferenceContext {
4997 file,
4998 position: Some(CallableReferencePosition {
4999 prepared: prepared.as_ref(),
5000 byte: reference_byte,
5001 guards: reference_guards,
5002 }),
5003 };
5004 if declaration.source() == file {
5005 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5006 .or_else(|| {
5007 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5008 })
5009 .is_some_and(|activation| activation < reference_byte);
5010 }
5011 if self
5012 .foreign_callable_declaration_activation(analyzer, declaration, &reference)
5013 .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
5014 .is_none()
5015 {
5016 return false;
5017 }
5018 declaration_guard_requirements(analyzer, self.cpp, declaration)
5019 .into_iter()
5020 .any(|(_, declaration_guards)| {
5021 self.foreign_declaration_reachable_at_reference(
5022 file,
5023 prepared.as_ref(),
5024 declaration.source(),
5025 &declaration_guards,
5026 reference.guards(),
5027 reference_byte,
5028 )
5029 })
5030 }
5031
5032 fn foreign_callable_declaration_activation(
5047 &self,
5048 analyzer: &CppGraphSource<'_>,
5049 declaration: &CodeUnit,
5050 reference: &CallableReferenceContext<'_>,
5051 ) -> Option<usize> {
5052 let build_decides = !self.compile_context_is_absent(reference.file);
5053 let proven = self.compile_proven_guards(reference.file);
5054 let augmented;
5055 let active = match reference.guards() {
5056 Some(active) if !proven.is_empty() => {
5057 augmented = active.union(&proven).cloned().collect();
5058 Some(&augmented)
5059 }
5060 other => other,
5061 };
5062 analyzer
5063 .declaration_source_occurrences(declaration)?
5064 .into_iter()
5065 .filter_map(|occurrence| {
5066 let activation = occurrence.fact.callable_activation?;
5067 let mut required =
5068 cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5069 if required.iter().any(|guard| match guard {
5070 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
5071 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
5072 !reference.is_c()
5073 }
5074 _ => false,
5075 }) {
5076 return None;
5077 }
5078 required.retain(|guard| {
5079 !matches!(guard,
5080 PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5081 if name == "__cplusplus")
5082 });
5083 let visible = required.is_empty()
5084 || if build_decides {
5085 guard_requirements_hold_at_reference(&required, active)
5086 } else {
5087 guards_compatible_at_reference(&required, reference.guards())
5088 };
5089 visible.then_some(activation)
5090 })
5091 .min()
5092 }
5093
5094 pub fn external_type_candidate_visible_at(
5095 &self,
5096 file: &ProjectFile,
5097 candidate: &CodeUnit,
5098 reference_byte: usize,
5099 ) -> bool {
5100 if candidate.source() == file {
5101 return true;
5102 }
5103 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5104 return false;
5105 };
5106 self.visible_identifier_candidates(file, candidate.identifier())
5107 .filter(|peer| same_logical_symbol(candidate, peer))
5108 .any(|peer| {
5109 peer.source() == file
5110 || self
5111 .include_activation_for_source(
5112 self.cpp,
5113 file,
5114 prepared.as_ref(),
5115 peer.source(),
5116 )
5117 .is_some_and(|activation| activation <= reference_byte)
5118 })
5119 }
5120
5121 pub fn external_type_declaration_visible_at(
5122 &self,
5123 file: &ProjectFile,
5124 candidate: &CodeUnit,
5125 reference_byte: usize,
5126 ) -> bool {
5127 if candidate.source() == file {
5128 return true;
5129 }
5130 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5131 return false;
5132 };
5133 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5134 .is_some_and(|activation| activation <= reference_byte)
5135 }
5136
5137 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5156 if let Some(cached) = self
5157 .compile_proven_guard_cells
5158 .lock()
5159 .expect("C++ compile-proven guard cache poisoned")
5160 .get(file)
5161 {
5162 return Arc::clone(cached);
5163 }
5164 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5165 Some(names) => names,
5166 None => {
5167 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5168 let seed = translation_units.next().and_then(|translation_unit| {
5169 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5170 });
5171 match seed {
5172 None => HashSet::default(),
5173 Some(mut names) => {
5174 for translation_unit in translation_units {
5175 let Some(reached) = context_fact_names(
5176 self.cpp.compile_contexts_for(&translation_unit),
5177 ) else {
5178 names.clear();
5179 break;
5180 };
5181 names.retain(|name| reached.contains(name));
5182 if names.is_empty() {
5183 break;
5184 }
5185 }
5186 names
5187 }
5188 }
5189 }
5190 };
5191 let proven = Arc::new(
5192 names
5193 .into_iter()
5194 .map(PreprocessorGuard::Defined)
5195 .collect::<HashSet<_>>(),
5196 );
5197 self.compile_proven_guard_cells
5198 .lock()
5199 .expect("C++ compile-proven guard cache poisoned")
5200 .insert(file.clone(), Arc::clone(&proven));
5201 proven
5202 }
5203
5204 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5214 if let Some(cached) = self
5215 .include_path_admission_cells
5216 .lock()
5217 .expect("C++ include-path admission cache poisoned")
5218 .get(file)
5219 .copied()
5220 {
5221 return cached;
5222 }
5223 let admission = if self.compile_context_is_absent(file) {
5224 IncludePathAdmission::Compatible
5225 } else {
5226 IncludePathAdmission::Proven
5227 };
5228 self.include_path_admission_cells
5229 .lock()
5230 .expect("C++ include-path admission cache poisoned")
5231 .insert(file.clone(), admission);
5232 admission
5233 }
5234
5235 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5242 if !self.cpp.compile_contexts_for(file).is_empty() {
5243 return false;
5244 }
5245 let translation_units = self.cpp.reaching_translation_units(file);
5246 translation_units.is_empty()
5247 || translation_units
5248 .iter()
5249 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5250 }
5251
5252 pub fn miss_requires_compile_context(
5264 &self,
5265 file: &ProjectFile,
5266 identifier: &str,
5267 reference: Node<'_>,
5268 ) -> bool {
5269 if !self.compile_context_is_absent(file) {
5270 return false;
5271 }
5272 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5273 return false;
5274 };
5275 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5276 let reference_byte = reference.start_byte();
5277 let mut sources = self
5278 .visible_identifier_candidates(file, identifier)
5279 .map(CodeUnit::source)
5280 .filter(|source| *source != file)
5281 .collect::<Vec<_>>();
5282 sources.sort();
5283 sources.dedup();
5284 sources.into_iter().any(|declaration_source| {
5285 self.conditional_include_projections_for_source(
5286 file,
5287 prepared.as_ref(),
5288 declaration_source,
5289 )
5290 .iter()
5291 .any(|projection| {
5292 projection.activation_byte <= reference_byte
5293 && !guard_requirements_hold_at_reference(
5294 &projection.required_guards,
5295 reference_guards.as_ref(),
5296 )
5297 && guards_compatible_at_reference(
5298 &projection.required_guards,
5299 reference_guards.as_ref(),
5300 )
5301 })
5302 })
5303 }
5304
5305 fn foreign_declaration_reachable_at_reference(
5317 &self,
5318 file: &ProjectFile,
5319 prepared: &PreparedSyntaxTree,
5320 declaration_source: &ProjectFile,
5321 declaration_guards: &HashSet<PreprocessorGuard>,
5322 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5323 reference_byte: usize,
5324 ) -> bool {
5325 let proven = self.compile_proven_guards(file);
5331 let augmented;
5332 let reference_guards = match reference_guards {
5333 Some(active) if !proven.is_empty() => {
5334 augmented = active.union(&proven).cloned().collect();
5335 Some(&augmented)
5336 }
5337 other => other,
5338 };
5339 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5340 eprintln!(
5341 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5342 declaration_source.rel_path().display(),
5343 );
5344 }
5345 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5346 return false;
5347 }
5348 if self
5349 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5350 .is_some_and(|activation| activation <= reference_byte)
5351 {
5352 return true;
5353 }
5354 let projections =
5355 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5356 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5357 eprintln!(
5358 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5359 declaration_source.rel_path().display(),
5360 declaration_guards.len(),
5361 proven.len(),
5362 projections.len(),
5363 );
5364 }
5365 let admission = self.include_path_admission(file);
5366 projections.iter().any(|projection| {
5367 projection.activation_byte <= reference_byte
5368 && admission.admits(
5369 &projection.required_guards,
5370 &projection.partial_guards,
5371 reference_guards,
5372 )
5373 && self.preprocessor_guards_stable_between(
5374 file,
5375 projection.activation_byte,
5376 reference_byte,
5377 &projection.required_guards,
5378 )
5379 })
5380 }
5381
5382 fn foreign_declaration_may_be_reachable_from_raw_guards(
5383 &self,
5384 file: &ProjectFile,
5385 prepared: &PreparedSyntaxTree,
5386 declaration_source: &ProjectFile,
5387 declaration_guards: &HashSet<PreprocessorGuard>,
5388 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5389 reference_byte: usize,
5390 ) -> bool {
5391 let proven = self.compile_proven_guards(file);
5392 let augmented;
5393 let reference_guards = match reference_guards {
5394 Some(active) if !proven.is_empty() => {
5395 augmented = active.union(&proven).cloned().collect();
5396 Some(&augmented)
5397 }
5398 other => other,
5399 };
5400 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5401 return false;
5402 }
5403 if self
5404 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5405 .is_some_and(|activation| activation <= reference_byte)
5406 {
5407 return true;
5408 }
5409 let language_guard_stable = OnceCell::new();
5410 let reachable = find_conditional_include_projection_for_source(
5411 self.cpp,
5412 self.token,
5413 file,
5414 prepared,
5415 declaration_source,
5416 self.include_path_admission(file),
5417 reference_guards,
5418 reference_byte,
5419 &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5420 &|| {
5421 #[cfg(any(test, feature = "test-support"))]
5422 self.conditional_include_target_state_count
5423 .fetch_add(1, Ordering::Relaxed);
5424 },
5425 );
5426 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5427 eprintln!(
5428 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5429 declaration_source.rel_path().display(),
5430 declaration_guards.len(),
5431 proven.len(),
5432 reference_guards.map_or(0, HashSet::len),
5433 );
5434 }
5435 reachable
5436 }
5437
5438 fn foreign_declaration_reachable_from_compile_proven_guards(
5439 &self,
5440 file: &ProjectFile,
5441 prepared: &PreparedSyntaxTree,
5442 declaration_source: &ProjectFile,
5443 declaration_guards: &HashSet<PreprocessorGuard>,
5444 reference_byte: usize,
5445 ) -> bool {
5446 let proven = self.compile_proven_guards(file);
5447 if proven.is_empty()
5448 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5449 {
5450 return false;
5451 }
5452 let projections =
5453 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5454 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5455 eprintln!(
5456 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5457 declaration_source.rel_path().display(),
5458 declaration_guards.len(),
5459 proven.len(),
5460 projections.len(),
5461 );
5462 }
5463 projections.iter().any(|projection| {
5464 projection.activation_byte <= reference_byte
5465 && guard_requirements_hold_at_reference(
5466 &projection.required_guards,
5467 Some(proven.as_ref()),
5468 )
5469 && self.preprocessor_guards_stable_between(
5474 file,
5475 0,
5476 projection.activation_byte,
5477 &projection.required_guards,
5478 )
5479 })
5480 }
5481
5482 pub fn external_type_candidate_visible_in_context(
5483 &self,
5484 analyzer: &CppGraphSource<'_>,
5485 file: &ProjectFile,
5486 candidate: &CodeUnit,
5487 reference: Node<'_>,
5488 ) -> bool {
5489 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5490 if report_stats {
5491 eprintln!(
5492 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5493 candidate.fq_name(),
5494 candidate.source().rel_path().display(),
5495 file.rel_path().display(),
5496 reference.start_byte(),
5497 );
5498 }
5499 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5500 return false;
5501 };
5502 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5503 let reference_guards = OnceCell::new();
5504 let reference_guards_at_site = || {
5505 reference_guards.get_or_init(|| {
5506 let started = Instant::now();
5507 if report_stats {
5508 eprintln!(
5509 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5510 file.rel_path().display(),
5511 reference.start_byte(),
5512 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5513 );
5514 }
5515 let macro_environment = self.macro_environment(file, reference.start_byte());
5516 let filtered = raw_reference_guards
5517 .clone()
5518 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5519 if report_stats {
5520 eprintln!(
5521 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5522 filtered.is_some(),
5523 started.elapsed().as_millis(),
5524 );
5525 }
5526 filtered
5527 })
5528 };
5529
5530 let peers = self
5531 .visible_identifier_candidates(file, candidate.identifier())
5532 .filter(|peer| same_logical_symbol(candidate, peer))
5533 .collect::<Vec<_>>();
5534 if report_stats {
5535 let peer_sources = peers
5536 .iter()
5537 .map(|peer| peer.source().rel_path().display().to_string())
5538 .collect::<Vec<_>>();
5539 eprintln!(
5540 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5541 candidate.fq_name(),
5542 );
5543 }
5544 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5545 declaration_guard_requirements(analyzer, self.cpp, peer)
5546 .into_iter()
5547 .any(|(declaration_byte, declaration_guards)| {
5548 if peer.source() == file {
5549 let visible = declaration_byte < reference.start_byte()
5550 && declaration_guards.is_empty();
5551 if report_stats {
5552 eprintln!(
5553 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5554 peer.source().rel_path().display(),
5555 declaration_guards.len(),
5556 );
5557 }
5558 return visible;
5559 }
5560 let direct = declaration_guards.is_empty()
5561 && self
5562 .include_activation_for_source(
5563 self.cpp,
5564 file,
5565 prepared.as_ref(),
5566 peer.source(),
5567 )
5568 .is_some_and(|activation| activation <= reference.start_byte());
5569 let compile_proven = !direct
5570 && self.foreign_declaration_reachable_from_compile_proven_guards(
5571 file,
5572 prepared.as_ref(),
5573 peer.source(),
5574 &declaration_guards,
5575 reference.start_byte(),
5576 );
5577 if report_stats {
5578 eprintln!(
5579 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5580 peer.source().rel_path().display(),
5581 declaration_guards.len(),
5582 );
5583 }
5584 direct || compile_proven
5585 })
5586 });
5587 if directly_visible_without_reference_environment {
5588 if report_stats {
5589 eprintln!(
5590 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5591 candidate.fq_name(),
5592 );
5593 }
5594 return true;
5595 }
5596 let directly_visible = peers.iter().any(|peer| {
5597 declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5598 .into_iter()
5599 .any(|site| {
5600 let declaration_byte = site.byte;
5601 let declaration_guards = site.guards;
5602 if peer.source() == file {
5603 if declaration_byte >= reference.start_byte() {
5604 return false;
5605 }
5606 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5611 guard_requirements_hold_at_reference(&declaration_guards, guards)
5612 || (site.completed_family_before_reference
5613 && guards_compatible_at_reference(&declaration_guards, guards))
5614 };
5615 if !active(raw_reference_guards.as_ref()) {
5616 return false;
5617 }
5618 return active(reference_guards_at_site().as_ref())
5619 && self.preprocessor_guards_stable_between(
5620 file,
5621 declaration_byte,
5622 reference.start_byte(),
5623 &declaration_guards,
5624 );
5625 }
5626 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5627 file,
5628 prepared.as_ref(),
5629 peer.source(),
5630 &declaration_guards,
5631 raw_reference_guards.as_ref(),
5632 reference.start_byte(),
5633 );
5634 if report_stats {
5635 eprintln!(
5636 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5637 peer.source().rel_path().display(),
5638 declaration_guards.len(),
5639 );
5640 }
5641 if !raw_feasible {
5642 return false;
5643 }
5644 self.foreign_declaration_reachable_at_reference(
5645 file,
5646 prepared.as_ref(),
5647 peer.source(),
5648 &declaration_guards,
5649 reference_guards_at_site().as_ref(),
5650 reference.start_byte(),
5651 )
5652 })
5653 });
5654 if directly_visible {
5655 if report_stats {
5656 eprintln!(
5657 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5658 candidate.fq_name(),
5659 );
5660 }
5661 return true;
5662 }
5663 let complementary = self
5664 .visible_identifier_candidates(file, candidate.identifier())
5665 .filter(|peer| {
5666 peer.kind() == candidate.kind()
5667 && peer.fq_name() == candidate.fq_name()
5668 && peer.source() == candidate.source()
5669 })
5670 .collect::<Vec<_>>();
5671 let complementary_family =
5676 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5677 let raw_candidate_branch_compatible = complementary_family
5678 && raw_reference_guards.as_ref().is_some_and(|active| {
5679 declaration_guard_requirements(analyzer, self.cpp, candidate)
5680 .iter()
5681 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5682 });
5683 if report_stats {
5684 eprintln!(
5685 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5686 candidate.fq_name(),
5687 complementary.len(),
5688 complementary_family,
5689 raw_candidate_branch_compatible,
5690 );
5691 }
5692 let candidate_branch_compatible = raw_candidate_branch_compatible
5693 && reference_guards_at_site().as_ref().is_some_and(|active| {
5694 declaration_guard_requirements(analyzer, self.cpp, candidate)
5695 .iter()
5696 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5697 });
5698 let complementary_visible = candidate_branch_compatible
5699 && if candidate.source() == file {
5700 declaration_guard_requirements(analyzer, self.cpp, candidate)
5701 .iter()
5702 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5703 } else {
5704 self.include_activation_for_source(
5705 self.cpp,
5706 file,
5707 prepared.as_ref(),
5708 candidate.source(),
5709 )
5710 .is_some_and(|activation| activation <= reference.start_byte())
5711 };
5712 if report_stats {
5713 eprintln!(
5714 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5715 if complementary_visible {
5716 "complementary"
5717 } else {
5718 "missing"
5719 },
5720 candidate.fq_name(),
5721 );
5722 }
5723 complementary_visible
5724 }
5725
5726 pub fn is_exhaustive_same_fqn_type_declaration_family(
5727 &self,
5728 analyzer: &CppGraphSource<'_>,
5729 file: &ProjectFile,
5730 candidate: &CodeUnit,
5731 ) -> bool {
5732 let candidates = self
5733 .visible_identifier_candidates(file, candidate.identifier())
5734 .filter(|peer| {
5735 peer.kind() == candidate.kind()
5736 && peer.fq_name() == candidate.fq_name()
5737 && peer.source() == candidate.source()
5738 })
5739 .collect::<Vec<_>>();
5740 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5741 }
5742
5743 pub fn dependent_member_pointer_alias_visible_in_context(
5758 &self,
5759 analyzer: &CppGraphSource<'_>,
5760 file: &ProjectFile,
5761 candidate: &CodeUnit,
5762 owner_components: &[String],
5763 reference: Node<'_>,
5764 ) -> bool {
5765 if !analyzer
5766 .type_alias_provider()
5767 .is_some_and(|provider| provider.is_type_alias(candidate))
5768 {
5769 return false;
5770 }
5771 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5772 return false;
5773 };
5774 if terminal != candidate.identifier()
5775 || canonical_cpp_scope_components(candidate) != owner_components
5776 {
5777 return false;
5778 }
5779 let Some(expected_parent_fq_name) =
5780 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5781 else {
5782 return false;
5783 };
5784 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5785 return false;
5786 };
5787 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5788 || parent_anchor.source() != candidate.source()
5789 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5790 {
5791 return false;
5792 }
5793
5794 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5800 || candidate.source() == file
5801 && !analyzer
5802 .ranges(candidate)
5803 .iter()
5804 .any(|range| range.start_byte < reference.start_byte())
5805 {
5806 return false;
5807 }
5808
5809 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5810 if candidate_guards.is_empty() {
5811 return false;
5812 }
5813 let same_guard_sets =
5814 |left: &[(usize, HashSet<PreprocessorGuard>)],
5815 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5816 left.iter().all(|(_, left_guards)| {
5817 right
5818 .iter()
5819 .any(|(_, right_guards)| left_guards == right_guards)
5820 })
5821 };
5822 let parent_candidates = self
5823 .visible_identifier_candidates(file, parent_anchor.identifier())
5824 .filter(|peer| {
5825 peer.kind() == parent_anchor.kind()
5826 && peer.fq_name() == expected_parent_fq_name.as_str()
5827 && peer.source() == parent_anchor.source()
5828 && canonical_cpp_scope_components(peer) == owner_prefix
5829 })
5830 .filter_map(|peer| {
5831 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5832 (candidate_guards.len() == parent_guards.len()
5833 && same_guard_sets(&candidate_guards, &parent_guards)
5834 && same_guard_sets(&parent_guards, &candidate_guards))
5835 .then(|| (peer.clone(), parent_guards))
5836 })
5837 .collect::<Vec<_>>();
5838 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5839 return false;
5840 };
5841
5842 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5843 return false;
5844 };
5845 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5846 else {
5847 return false;
5848 };
5849 if !candidate_guards.iter().any(|(_, target_guards)| {
5854 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5855 && (candidate.source() != file
5856 || self.preprocessor_guards_stable_between(
5857 file,
5858 0,
5859 reference.start_byte(),
5860 target_guards,
5861 ))
5862 }) {
5863 return false;
5864 }
5865
5866 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5867 }
5868
5869 pub fn external_type_candidate_guard_compatible_in_context(
5879 &self,
5880 analyzer: &CppGraphSource<'_>,
5881 file: &ProjectFile,
5882 candidate: &CodeUnit,
5883 reference: Node<'_>,
5884 ) -> bool {
5885 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5886 return false;
5887 };
5888 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5889
5890 self.visible_identifier_candidates(file, candidate.identifier())
5891 .filter(|peer| same_logical_symbol(candidate, peer))
5892 .any(|peer| {
5893 declaration_guard_requirements(analyzer, self.cpp, peer)
5894 .into_iter()
5895 .any(|(declaration_byte, declaration_guards)| {
5896 if peer.source() == file {
5897 let (start, end) = if declaration_byte <= reference.start_byte() {
5898 (declaration_byte, reference.start_byte())
5899 } else {
5900 (reference.start_byte(), declaration_byte)
5901 };
5902 return guard_requirements_hold_at_reference(
5903 &declaration_guards,
5904 reference_guards.as_ref(),
5905 ) && self.preprocessor_guards_stable_between(
5906 file,
5907 start,
5908 end,
5909 &declaration_guards,
5910 );
5911 }
5912 self.foreign_declaration_reachable_at_reference(
5913 file,
5914 prepared.as_ref(),
5915 peer.source(),
5916 &declaration_guards,
5917 reference_guards.as_ref(),
5918 reference.start_byte(),
5919 )
5920 })
5921 })
5922 }
5923
5924 pub fn same_file_callable_guard_compatible_ignoring_order(
5932 &self,
5933 analyzer: &CppGraphSource<'_>,
5934 file: &ProjectFile,
5935 candidate: &CodeUnit,
5936 reference: Node<'_>,
5937 ) -> bool {
5938 if candidate.source() != file || !candidate.is_callable() {
5939 return false;
5940 }
5941 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5942 return false;
5943 };
5944 let guards = OnceCell::new();
5945 let context = CallableReferenceContext {
5946 file,
5947 position: Some(CallableReferencePosition {
5948 prepared: prepared.as_ref(),
5949 byte: reference.start_byte(),
5950 guards: &guards,
5951 }),
5952 };
5953 analyzer
5954 .declaration_source_occurrences(candidate)
5955 .into_iter()
5956 .flatten()
5957 .filter_map(|occurrence| {
5958 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5959 Some(callable_guard_requirements_are_visible(
5960 &guards,
5961 occurrence.fact.callable_guard_completion_byte,
5962 &context,
5963 ))
5964 })
5965 .any(|visible| visible)
5966 }
5967
5968 pub fn type_candidate_may_be_visible_before_reference(
5969 &self,
5970 analyzer: &CppGraphSource<'_>,
5971 file: &ProjectFile,
5972 candidate: &CodeUnit,
5973 reference_byte: usize,
5974 ) -> bool {
5975 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5976 return false;
5977 };
5978 let root = prepared.tree().root_node();
5979 let end_byte = reference_byte
5980 .saturating_add(1)
5981 .min(prepared.source().len());
5982 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5983 return false;
5984 };
5985 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5986 }
5987
5988 pub fn preprocessor_guards_stable_between(
5989 &self,
5990 file: &ProjectFile,
5991 start_byte: usize,
5992 end_byte: usize,
5993 guards: &HashSet<PreprocessorGuard>,
5994 ) -> bool {
5995 if guards.is_empty() || start_byte >= end_byte {
5996 return true;
5997 }
5998 let cell = self.macro_event_cell(file);
5999 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6000 let mut visited = HashSet::from_iter([file.clone()]);
6001 !events.iter().any(|event| {
6002 event.byte() >= start_byte
6003 && event.byte() < end_byte
6004 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6005 })
6006 }
6007
6008 fn macro_event_may_mutate_guards(
6009 &self,
6010 event: &MacroEvent,
6011 guards: &HashSet<PreprocessorGuard>,
6012 visited: &mut HashSet<ProjectFile>,
6013 ) -> bool {
6014 match event {
6015 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6016 guards.iter().any(|guard| guard.may_depend_on_macro(name))
6017 }
6018 MacroEvent::Include { targets, .. } => {
6019 targets.is_empty()
6020 || targets
6021 .iter()
6022 .any(|target| self.source_may_mutate_guards(target, guards, visited))
6023 }
6024 MacroEvent::Invalidate { .. } => true,
6025 }
6026 }
6027
6028 fn source_may_mutate_guards(
6029 &self,
6030 file: &ProjectFile,
6031 guards: &HashSet<PreprocessorGuard>,
6032 visited: &mut HashSet<ProjectFile>,
6033 ) -> bool {
6034 if !visited.insert(file.clone()) {
6035 return false;
6036 }
6037 let cell = self.macro_event_cell(file);
6038 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6039 events
6040 .iter()
6041 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6042 }
6043
6044 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6045 let normalized = normalize_reference_name(raw_name)?;
6046 self.type_candidates(file, &normalized)
6047 .into_iter()
6048 .next()
6049 .cloned()
6050 }
6051
6052 pub fn unique_visible_parameter_type_fallback(
6061 &self,
6062 analyzer: &CppGraphSource<'_>,
6063 file: &ProjectFile,
6064 node: Node<'_>,
6065 source: &str,
6066 ) -> Option<CodeUnit> {
6067 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6068 return None;
6069 }
6070 let name = node_text(node, source);
6071 let candidates = self
6072 .visible_identifier_candidates(file, name)
6073 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6074 .filter(|candidate| {
6075 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6076 })
6077 .collect::<Vec<_>>();
6078 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6079 }
6080
6081 pub fn resolve_type_node_result(
6082 &self,
6083 file: &ProjectFile,
6084 node: Node<'_>,
6085 source: &str,
6086 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6087 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6088 return Ok(None);
6089 };
6090 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6091 return Ok(Some(primary));
6092 };
6093 self.resolve_template_arguments(file, primary, &arguments)
6094 .map(Some)
6095 }
6096
6097 pub fn resolve_type_node_primary(
6098 &self,
6099 file: &ProjectFile,
6100 node: Node<'_>,
6101 source: &str,
6102 ) -> Option<CodeUnit> {
6103 let components = cpp_type_name_components(node, source)?;
6104 self.resolve_type(file, &components.join("::"))
6105 }
6106
6107 pub fn resolve_template_arguments(
6108 &self,
6109 file: &ProjectFile,
6110 primary: CodeUnit,
6111 arguments: &[CppTemplateExpression],
6112 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6113 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6114 }
6115
6116 fn resolve_template_arguments_inner(
6117 &self,
6118 file: &ProjectFile,
6119 primary: CodeUnit,
6120 arguments: &[CppTemplateExpression],
6121 seen_aliases: &mut HashSet<CodeUnit>,
6122 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6123 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6124 && let Some(alias_target) = &metadata.alias_target
6125 {
6126 if !seen_aliases.insert(primary.clone()) {
6127 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6128 }
6129 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6130 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6131 let target_name = alias_target.components.join("::");
6132 let target_primary = if alias_target.global {
6133 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6134 } else {
6135 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6136 };
6137 let Some(target_primary) = target_primary else {
6138 return Ok(primary);
6142 };
6143 let Some(target_arguments) = &alias_target.arguments else {
6144 return Ok(target_primary);
6145 };
6146 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6147 .ok_or(CppTemplateResolutionError::Substitution)?;
6148 return self.resolve_template_arguments_inner(
6149 file,
6150 target_primary,
6151 &target_arguments,
6152 seen_aliases,
6153 );
6154 }
6155
6156 let primary_fq_name = self
6157 .cpp_template_metadata
6158 .get(&primary)
6159 .map(|metadata| metadata.primary_fq_name.clone())
6160 .unwrap_or_else(|| primary.fq_name());
6161 let has_specialization_metadata = self
6162 .cpp_template_families
6163 .get(&primary_fq_name)
6164 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6165 if !has_specialization_metadata {
6166 return Ok(primary);
6167 }
6168 self.select_template_specialization(file, &primary, arguments)
6169 }
6170
6171 fn select_template_specialization(
6172 &self,
6173 file: &ProjectFile,
6174 resolved: &CodeUnit,
6175 explicit_arguments: &[CppTemplateExpression],
6176 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6177 let primary_fq_name = self
6178 .cpp_template_metadata
6179 .get(resolved)
6180 .map(|metadata| metadata.primary_fq_name.clone())
6181 .unwrap_or_else(|| resolved.fq_name());
6182 let family = self
6183 .cpp_template_families
6184 .get(&primary_fq_name)
6185 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6186 let primary_candidates = family
6187 .iter()
6188 .filter_map(|unit| {
6189 let metadata = self.cpp_template_metadata.get(unit)?;
6190 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6191 })
6192 .collect::<Vec<_>>();
6193 let primary_unit = primary_candidates
6194 .iter()
6195 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6196 .or_else(|| {
6197 primary_candidates
6198 .iter()
6199 .map(|(unit, _)| *unit)
6200 .min_by_key(|unit| {
6201 (
6202 unit.source().to_string(),
6203 unit.signature().unwrap_or_default(),
6204 )
6205 })
6206 })
6207 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6208 let primary_parameters =
6209 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6210 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6211 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6212 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6213
6214 let mut applicable = Vec::new();
6215 for unit in family {
6216 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6217 continue;
6218 };
6219 if metadata.is_primary() || !self.is_visible(file, unit) {
6220 continue;
6221 }
6222 if !cpp_specialization_matches(metadata, &expanded) {
6223 continue;
6224 }
6225 applicable.push((unit, metadata));
6226 }
6227 if applicable.is_empty() {
6228 return Ok(primary_unit.clone());
6229 }
6230
6231 let winners = applicable
6236 .iter()
6237 .filter(|(candidate, candidate_metadata)| {
6238 applicable.iter().all(|(other, other_metadata)| {
6239 same_visible_symbol(candidate, other)
6240 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6241 })
6242 })
6243 .copied()
6244 .collect::<Vec<_>>();
6245 let Some((selected, _)) = winners.first() else {
6246 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6249 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6250 });
6251 };
6252 if winners
6253 .iter()
6254 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6255 {
6256 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6257 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6258 });
6259 }
6260 Ok((*selected).clone())
6261 }
6262
6263 pub fn resolve_type_components_lexically(
6264 &self,
6265 analyzer: &CppGraphSource<'_>,
6266 file: &ProjectFile,
6267 components: &[String],
6268 global: bool,
6269 lexical_scope: &[String],
6270 ) -> LexicalTypeResolution {
6271 self.resolve_type_components_lexically_inner(
6272 analyzer,
6273 file,
6274 components,
6275 global,
6276 lexical_scope,
6277 TypeCandidateResolution::Canonical,
6278 )
6279 }
6280
6281 pub fn resolve_type_components_lexically_for_forward(
6282 &self,
6283 analyzer: &CppGraphSource<'_>,
6284 file: &ProjectFile,
6285 components: &[String],
6286 global: bool,
6287 lexical_scope: &[String],
6288 ) -> LexicalTypeResolution {
6289 self.resolve_type_components_lexically_inner(
6290 analyzer,
6291 file,
6292 components,
6293 global,
6294 lexical_scope,
6295 TypeCandidateResolution::PreserveAlias,
6296 )
6297 }
6298
6299 pub fn resolve_type_components_lexically_for_target(
6300 &self,
6301 analyzer: &CppGraphSource<'_>,
6302 file: &ProjectFile,
6303 components: &[String],
6304 global: bool,
6305 lexical_scope: &[String],
6306 target: &CodeUnit,
6307 ) -> LexicalTypeResolution {
6308 #[cfg(any(test, feature = "test-support"))]
6309 self.target_preserving_type_resolution_count
6310 .fetch_add(1, Ordering::Relaxed);
6311 self.resolve_type_components_lexically_inner(
6312 analyzer,
6313 file,
6314 components,
6315 global,
6316 lexical_scope,
6317 TypeCandidateResolution::PreserveTarget(target),
6318 )
6319 }
6320
6321 pub fn coarse_unqualified_type_reference_may_resolve(
6322 &self,
6323 file: &ProjectFile,
6324 name: &str,
6325 ) -> bool {
6326 if name.is_empty() {
6327 return true;
6328 }
6329 self.visible_identifier_candidates(file, name)
6330 .any(|candidate| {
6331 candidate.kind() == CodeUnitType::Class
6332 || declared_type_alias(&self.cpp_source(), candidate)
6333 })
6334 || self.visible_parser_alias_name_is_visible(file, name)
6335 }
6336
6337 #[allow(clippy::too_many_arguments)]
6338 pub fn structured_type_reference_may_resolve_to_target(
6339 &self,
6340 analyzer: &CppGraphSource<'_>,
6341 file: &ProjectFile,
6342 components: &[String],
6343 global: bool,
6344 lexical_scope: &[String],
6345 target: &CodeUnit,
6346 ) -> bool {
6347 if components.is_empty() {
6348 return true;
6349 }
6350 let Some(terminal) = components.last() else {
6351 return true;
6352 };
6353 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6354 .map(|qualified| qualified.join("::"))
6355 .collect::<Vec<_>>();
6356 let target_name = cpp_name_for(target);
6357 if qualified_tiers
6358 .iter()
6359 .any(|qualified| qualified == &target_name)
6360 {
6361 return true;
6362 }
6363
6364 let mut saw_shape_candidate = false;
6365 for candidate in self.visible_identifier_candidates(file, terminal) {
6366 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6367 {
6368 continue;
6369 }
6370 let candidate_name = cpp_name_for(candidate);
6371 let shape_matches = if global || components.len() > 1 {
6372 qualified_tiers
6373 .iter()
6374 .any(|qualified| qualified == &candidate_name)
6375 } else {
6376 true
6377 };
6378 if !shape_matches {
6379 continue;
6380 }
6381 saw_shape_candidate = true;
6382 if same_visible_symbol(candidate, target)
6383 || self.c_tag_declaration_family_matches_target(
6384 analyzer,
6385 file,
6386 std::slice::from_ref(&candidate),
6387 target,
6388 )
6389 || self.compatible_primary_template_redeclarations(candidate, target)
6390 || (declared_type_alias(analyzer, candidate)
6391 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6392 {
6393 return true;
6394 }
6395 }
6396
6397 !saw_shape_candidate
6398 }
6399
6400 fn cached_c_tag_kind(
6407 &self,
6408 analyzer: &CppGraphSource<'_>,
6409 candidate: &CodeUnit,
6410 ) -> Option<CppCTagKind> {
6411 if let Some(kind) = self
6412 .c_tag_kind_cache
6413 .lock()
6414 .expect("C tag kind cache poisoned")
6415 .get(candidate)
6416 {
6417 return *kind;
6418 }
6419 let kind = indexed_c_tag_kind(analyzer, candidate);
6420 self.c_tag_kind_cache
6421 .lock()
6422 .expect("C tag kind cache poisoned")
6423 .insert(candidate.clone(), kind);
6424 kind
6425 }
6426
6427 fn cached_unique_c_tag_complete_definition(
6428 &self,
6429 analyzer: &CppGraphSource<'_>,
6430 target: &CodeUnit,
6431 target_tag: CppCTagKind,
6432 ) -> Option<CodeUnit> {
6433 if let Some(definition) = self
6434 .c_tag_complete_definition_cache
6435 .lock()
6436 .expect("C tag complete-definition cache poisoned")
6437 .get(target)
6438 {
6439 return definition.clone();
6440 }
6441 let complete_definitions = analyzer
6442 .definitions(&target.fq_name())
6443 .filter(|candidate| {
6444 candidate.is_class()
6445 && !declared_type_alias(analyzer, candidate)
6446 && is_c_source_file(candidate.source())
6447 && analyzer.parent_of(candidate).is_none()
6448 && cpp_class_declaration_strength(analyzer, candidate)
6449 == CppClassDeclarationStrength::Full
6450 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6451 })
6452 .collect::<HashSet<_>>();
6453 let definition = (complete_definitions.len() == 1)
6454 .then(|| complete_definitions.into_iter().next())
6455 .flatten()
6456 .filter(|candidate| same_visible_symbol(candidate, target));
6457 self.c_tag_complete_definition_cache
6458 .lock()
6459 .expect("C tag complete-definition cache poisoned")
6460 .insert(target.clone(), definition.clone());
6461 definition
6462 }
6463
6464 pub fn c_tag_declaration_family_matches_target(
6465 &self,
6466 analyzer: &CppGraphSource<'_>,
6467 visible_from: &ProjectFile,
6468 candidates: &[&CodeUnit],
6469 target: &CodeUnit,
6470 ) -> bool {
6471 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6472 let candidate_evidence = candidates
6473 .iter()
6474 .map(|candidate| {
6475 (
6476 candidate.fq_name(),
6477 candidate.source().rel_path().to_path_buf(),
6478 cpp_class_declaration_strength(analyzer, candidate),
6479 indexed_c_tag_kind(analyzer, candidate),
6480 self.is_physically_visible(visible_from, candidate),
6481 )
6482 })
6483 .collect::<Vec<_>>();
6484 eprintln!(
6485 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6486 visible_from.rel_path().display(),
6487 target.fq_name(),
6488 target.source().rel_path().display(),
6489 cpp_class_declaration_strength(analyzer, target),
6490 indexed_c_tag_kind(analyzer, target),
6491 );
6492 }
6493 if candidates.is_empty()
6494 || !target.is_class()
6495 || declared_type_alias(analyzer, target)
6496 || !is_c_source_file(target.source())
6497 || analyzer.parent_of(target).is_some()
6498 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6499 {
6500 return false;
6501 }
6502 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6503 return false;
6504 };
6505 if self
6506 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6507 .is_none()
6508 {
6509 return false;
6510 }
6511 let mut saw_visible_forward = false;
6512 for candidate in candidates.iter().copied() {
6513 if candidate == target {
6514 continue;
6515 }
6516 if !candidate.is_class()
6517 || declared_type_alias(analyzer, candidate)
6518 || candidate.fq_name() != target.fq_name()
6519 || analyzer.parent_of(candidate).is_some()
6520 || cpp_class_declaration_strength(analyzer, candidate)
6521 != CppClassDeclarationStrength::Forward
6522 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6523 || !self.is_physically_visible(visible_from, candidate)
6524 {
6525 return false;
6526 }
6527 saw_visible_forward = true;
6528 }
6529 saw_visible_forward
6530 }
6531
6532 fn unique_c_tag_declaration_family(
6537 &self,
6538 analyzer: &CppGraphSource<'_>,
6539 visible_from: &ProjectFile,
6540 candidates: &[&CodeUnit],
6541 ) -> Option<CodeUnit> {
6542 let first = candidates.first()?;
6543 let target_fq_name = first.fq_name();
6544 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6545 let mut full = None;
6546 let mut saw_forward = false;
6547 for candidate in candidates.iter().copied() {
6548 if !candidate.is_class()
6549 || declared_type_alias(analyzer, candidate)
6550 || candidate.fq_name() != target_fq_name
6551 || analyzer.parent_of(candidate).is_some()
6552 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6553 {
6554 return None;
6555 }
6556 match cpp_class_declaration_strength(analyzer, candidate) {
6557 CppClassDeclarationStrength::Full
6558 if is_c_source_file(candidate.source())
6559 && full.replace(candidate.clone()).is_none() => {}
6560 CppClassDeclarationStrength::Forward
6561 if self.is_physically_visible(visible_from, candidate) =>
6562 {
6563 saw_forward = true
6564 }
6565 _ => return None,
6566 }
6567 }
6568 if saw_forward { full } else { None }
6569 }
6570
6571 pub fn target_preserving_reference_namespace(
6572 &self,
6573 analyzer: &CppGraphSource<'_>,
6574 file: &ProjectFile,
6575 identifier: &str,
6576 target: &CodeUnit,
6577 ) -> Option<Vec<String>> {
6578 let mut namespace = None;
6579 for candidate in self.visible_identifier_candidates(file, identifier) {
6580 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6581 {
6582 continue;
6583 }
6584 if !(same_visible_symbol(candidate, target)
6585 || self.compatible_primary_template_redeclarations(candidate, target)
6586 || declared_type_alias(analyzer, candidate)
6587 && self.structured_alias_primary_preserves_target(
6588 analyzer, file, candidate, target,
6589 ))
6590 {
6591 continue;
6592 }
6593 if namespace
6594 .as_ref()
6595 .is_some_and(|existing| existing != candidate.package_name())
6596 {
6597 return None;
6598 }
6599 namespace = Some(candidate.package_name().to_string());
6600 }
6601 let namespace = namespace?;
6602 Some(
6603 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6604 brokk_bifrost_core::analyzer::Language::Cpp,
6605 &namespace,
6606 ),
6607 )
6608 }
6609
6610 pub fn resolve_imported_type_candidate(
6611 &self,
6612 analyzer: &CppGraphSource<'_>,
6613 file: &ProjectFile,
6614 target: &CodeUnit,
6615 target_components: &[String],
6616 direct_target: Option<&CodeUnit>,
6617 preserve_alias: bool,
6618 ) -> LexicalTypeResolution {
6619 let candidates = [target];
6620 let resolution = if preserve_alias {
6621 TypeCandidateResolution::PreserveAlias
6622 } else {
6623 direct_target.map_or(
6624 TypeCandidateResolution::Canonical,
6625 TypeCandidateResolution::PreserveTarget,
6626 )
6627 };
6628 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6632 Ok(unit) => LexicalTypeResolution::Resolved {
6633 unit,
6634 components: target_components.to_vec(),
6635 candidates: vec![target.clone()],
6636 },
6637 Err(failure) => failure.lexical_resolution(),
6638 }
6639 }
6640
6641 fn resolve_type_components_lexically_inner(
6642 &self,
6643 analyzer: &CppGraphSource<'_>,
6644 file: &ProjectFile,
6645 components: &[String],
6646 global: bool,
6647 lexical_scope: &[String],
6648 resolution: TypeCandidateResolution<'_>,
6649 ) -> LexicalTypeResolution {
6650 if components.is_empty() {
6651 return LexicalTypeResolution::Missing;
6652 }
6653 let mut injected = self.resolve_injected_class_name(
6663 analyzer,
6664 file,
6665 components,
6666 global,
6667 lexical_scope,
6668 resolution,
6669 );
6670 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6671 let prefix_len = qualified.len().saturating_sub(components.len());
6672 if injected
6673 .as_ref()
6674 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6675 {
6676 return injected
6677 .take()
6678 .expect("injected class resolution was just present")
6679 .1;
6680 }
6681 let qualified_name = qualified.join("::");
6682 let candidates = self
6683 .type_candidates(file, &qualified_name)
6684 .into_iter()
6685 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6686 .collect::<Vec<_>>();
6687 if candidates.is_empty() {
6688 if !global && components.len() == 1 {
6689 match self.resolve_inherited_type_for_lexical_scope(
6690 analyzer,
6691 file,
6692 &qualified[..prefix_len],
6693 &components[0],
6694 resolution,
6695 ) {
6696 LexicalTypeResolution::Missing => {}
6697 inherited => return inherited,
6698 }
6699 }
6700 continue;
6701 }
6702 let candidates =
6703 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6704 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6705 Ok(unit) => unit,
6706 Err(failure) => return failure.lexical_resolution(),
6707 };
6708 return LexicalTypeResolution::Resolved {
6709 unit,
6710 components: qualified,
6711 candidates: candidates.into_iter().cloned().collect(),
6712 };
6713 }
6714 LexicalTypeResolution::Missing
6715 }
6716
6717 fn resolve_injected_class_name(
6718 &self,
6719 analyzer: &CppGraphSource<'_>,
6720 file: &ProjectFile,
6721 components: &[String],
6722 global: bool,
6723 lexical_scope: &[String],
6724 resolution: TypeCandidateResolution<'_>,
6725 ) -> Option<(usize, LexicalTypeResolution)> {
6726 if global
6727 || components.len() != 1
6728 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6729 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6730 {
6731 return None;
6732 }
6733 let name = components.first()?;
6734 let mut matches: Vec<&CodeUnit> = Vec::new();
6735 let mut owner_len = 0;
6736 for candidate in self.visible_identifier_candidates(file, name) {
6737 if !candidate.is_class()
6738 || declared_type_alias(analyzer, candidate)
6739 || candidate.identifier() != name
6740 {
6741 continue;
6742 }
6743 let candidate_scope = canonical_cpp_scope_components(candidate);
6744 if candidate_scope.len() > lexical_scope.len()
6745 || !lexical_scope.starts_with(&candidate_scope)
6746 || candidate_scope.last().is_none_or(|last| last != name)
6747 {
6748 continue;
6749 }
6750 if candidate_scope.len() > owner_len {
6751 owner_len = candidate_scope.len();
6752 matches.clear();
6753 }
6754 if candidate_scope.len() == owner_len
6755 && !matches
6756 .iter()
6757 .any(|existing| same_logical_symbol(existing, candidate))
6758 {
6759 matches.push(candidate);
6760 }
6761 }
6762 if matches.is_empty() {
6763 return None;
6764 }
6765 let owner_components = lexical_scope[..owner_len].to_vec();
6772 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6773 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6774 Ok(unit) => LexicalTypeResolution::Resolved {
6775 unit,
6776 components: owner_components,
6777 candidates: matches.into_iter().cloned().collect(),
6778 },
6779 Err(failure) => failure.lexical_resolution(),
6780 };
6781 Some((owner_len, resolution))
6782 }
6783
6784 fn resolve_inherited_type_for_lexical_scope(
6785 &self,
6786 analyzer: &CppGraphSource<'_>,
6787 file: &ProjectFile,
6788 lexical_scope: &[String],
6789 name: &str,
6790 resolution: TypeCandidateResolution<'_>,
6791 ) -> LexicalTypeResolution {
6792 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6793 return LexicalTypeResolution::Missing;
6794 };
6795 let lexical_owner_name = lexical_scope.join("::");
6796 if lexical_owner_name.is_empty() {
6797 return LexicalTypeResolution::Missing;
6798 }
6799 let owner_candidates = self
6800 .type_candidates(file, &lexical_owner_name)
6801 .into_iter()
6802 .filter(|candidate| {
6803 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6804 && !declared_type_alias(analyzer, candidate)
6805 })
6806 .collect::<Vec<_>>();
6807 if owner_candidates.is_empty() {
6808 return LexicalTypeResolution::Missing;
6809 }
6810 let physical_owner_candidates = owner_candidates
6815 .iter()
6816 .copied()
6817 .filter(|candidate| candidate.source() == file)
6818 .collect::<Vec<_>>();
6819 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6820 owner_candidates
6821 } else {
6822 physical_owner_candidates
6823 };
6824 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6825 return LexicalTypeResolution::Ambiguous;
6826 };
6827
6828 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6829 let mut visited_owners = HashSet::default();
6830 while !frontier.is_empty() {
6831 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6832 let mut next_frontier = Vec::new();
6833 for owner in frontier {
6834 if !visited_owners.insert(owner.fq_name()) {
6835 continue;
6836 }
6837 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6838 let candidates = self
6839 .type_candidates(file, &qualified_name)
6840 .into_iter()
6841 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6842 .collect::<Vec<_>>();
6843 if candidates.is_empty() {
6844 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6845 if !next_frontier
6846 .iter()
6847 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6848 {
6849 next_frontier.push(ancestor);
6850 }
6851 }
6852 continue;
6853 }
6854 let candidates =
6855 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6856 let unit =
6857 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6858 Ok(unit) => unit,
6859 Err(failure) => return failure.lexical_resolution(),
6860 };
6861 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6862 }
6863 if let Some((unit, candidates)) = level_matches.first().cloned() {
6864 let Some(first_declaration) = candidates.first() else {
6865 return LexicalTypeResolution::Ambiguous;
6866 };
6867 if !level_matches.iter().all(|(_, declarations)| {
6868 declarations
6869 .iter()
6870 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6871 }) {
6872 return LexicalTypeResolution::Ambiguous;
6873 }
6874 let mut components = lexical_scope.to_vec();
6875 components.push(name.to_string());
6876 return LexicalTypeResolution::Resolved {
6877 unit,
6878 components,
6879 candidates,
6880 };
6881 }
6882 frontier = next_frontier;
6883 }
6884 LexicalTypeResolution::Missing
6885 }
6886
6887 pub fn inherited_injected_class_owner(
6904 &self,
6905 analyzer: &CppGraphSource<'_>,
6906 file: &ProjectFile,
6907 enclosing_owner: &CodeUnit,
6908 injected_name: &str,
6909 ) -> Option<CodeUnit> {
6910 let hierarchy = analyzer.type_hierarchy_provider()?;
6911 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6912 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6913 while !frontier.is_empty() {
6914 let mut level_matches = Vec::new();
6915 let mut next_frontier = Vec::new();
6916 for raw_owner in frontier {
6917 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6918 else {
6919 if raw_owner.identifier() == injected_name {
6920 return None;
6921 }
6922 continue;
6923 };
6924 let propagated = propagated_counts.entry(owner.clone()).or_default();
6925 if *propagated == 2 {
6926 continue;
6927 }
6928 *propagated += 1;
6929 if owner.identifier() == injected_name {
6930 level_matches.push(owner.clone());
6931 }
6932 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6933 }
6934 match level_matches.as_slice() {
6935 [owner] => return Some(owner.clone()),
6936 [_, ..] => return None,
6937 [] => {}
6938 }
6939 frontier = next_frontier;
6940 }
6941 None
6942 }
6943
6944 fn resolve_type_candidates(
6949 &self,
6950 analyzer: &CppGraphSource<'_>,
6951 file: &ProjectFile,
6952 candidates: &[&CodeUnit],
6953 resolution: TypeCandidateResolution<'_>,
6954 ) -> Result<CodeUnit, TypeCandidateFailure> {
6955 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6956 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6957 {
6958 return Ok(unit);
6959 }
6960 match resolution {
6961 TypeCandidateResolution::Canonical => {
6962 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6963 }
6964 TypeCandidateResolution::PreserveAlias => {
6965 let same_fqn_alias_family = candidates.len() > 1
6972 && candidates.iter().all(|candidate| {
6973 declared_type_alias(analyzer, candidate)
6974 && same_logical_symbol(candidates[0], candidate)
6975 })
6976 && candidates
6977 .iter()
6978 .any(|candidate| candidate.source() != candidates[0].source());
6979 if same_fqn_alias_family {
6980 let physically_visible = candidates
6981 .iter()
6982 .copied()
6983 .filter(|candidate| self.is_physically_visible(file, candidate))
6984 .collect::<Vec<_>>();
6985 if let Some(alias) = select_alias_spelling_with_agreed_targets(
6995 &physically_visible,
6996 |candidate| self.structured_alias_target(analyzer, candidate),
6997 ) {
6998 return Ok(alias.clone());
6999 }
7000 }
7001 unique_type_candidate_preserving_alias(analyzer, file, candidates)
7002 .ok_or(TypeCandidateFailure::Ambiguous)
7003 }
7004 TypeCandidateResolution::PreserveTarget(target) => self
7005 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7006 .ok_or(TypeCandidateFailure::Ambiguous),
7007 }
7008 }
7009
7010 fn candidates_for_type_resolution<'b>(
7011 &self,
7012 analyzer: &CppGraphSource<'_>,
7013 file: &ProjectFile,
7014 candidates: &[&'b CodeUnit],
7015 resolution: TypeCandidateResolution<'_>,
7016 ) -> Vec<&'b CodeUnit> {
7017 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7018 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7019 if compile_proven.len() == 1 {
7020 return compile_proven;
7021 }
7022 }
7023 candidates.to_vec()
7024 }
7025
7026 fn compile_proven_type_candidates<'b>(
7033 &self,
7034 analyzer: &CppGraphSource<'_>,
7035 file: &ProjectFile,
7036 candidates: &[&'b CodeUnit],
7037 ) -> Vec<&'b CodeUnit> {
7038 let proven = self.compile_proven_guards(file);
7039 if proven.is_empty() {
7040 return Vec::new();
7041 }
7042 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7043 return Vec::new();
7044 };
7045 candidates
7046 .iter()
7047 .copied()
7048 .filter(|candidate| {
7049 let declaration_guards =
7050 declaration_guard_requirements(analyzer, self.cpp, candidate);
7051 if declaration_guards.is_empty() {
7052 return false;
7053 }
7054 if candidate.source() == file {
7055 return declaration_guards.iter().any(|(_, required)| {
7056 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7057 });
7058 }
7059 declaration_guards.iter().any(|(_, required)| {
7060 self.foreign_declaration_reachable_from_compile_proven_guards(
7061 file,
7062 prepared.as_ref(),
7063 candidate.source(),
7064 required,
7065 usize::MAX,
7066 )
7067 })
7068 })
7069 .collect()
7070 }
7071
7072 pub fn resolve_callable_value_components_lexically(
7073 &self,
7074 analyzer: &CppGraphSource<'_>,
7075 file: &ProjectFile,
7076 owner_components: &[String],
7077 member_name: &str,
7078 global: bool,
7079 lexical_scope: &[String],
7080 ) -> LexicalCallableValueResolution {
7081 if owner_components.is_empty() || member_name.is_empty() {
7082 return LexicalCallableValueResolution::Missing;
7083 }
7084 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7085 let owner_name = qualified_owner.join("::");
7086 let type_candidates = self
7087 .type_candidates(file, &owner_name)
7088 .into_iter()
7089 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7090 .collect::<Vec<_>>();
7091 let resolved_type = if type_candidates.is_empty() {
7092 None
7093 } else {
7094 let Some(unit) =
7095 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7096 else {
7097 return LexicalCallableValueResolution::Ambiguous;
7098 };
7099 Some(unit)
7100 };
7101
7102 let mut qualified_callable = qualified_owner;
7103 qualified_callable.push(member_name.to_string());
7104 let callable_name = qualified_callable.join("::");
7105 let free_function = self
7106 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7107 .into_iter()
7108 .find(|candidate| {
7109 canonical_cpp_name_matches(candidate, &callable_name)
7110 && type_owner_of(analyzer, candidate).is_none()
7111 })
7112 .cloned();
7113
7114 match (resolved_type, free_function) {
7115 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7116 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7117 (None, Some(function)) => {
7118 return LexicalCallableValueResolution::FreeFunction(function);
7119 }
7120 (None, None) => {}
7121 }
7122 }
7123 LexicalCallableValueResolution::Missing
7124 }
7125
7126 fn resolve_type_for_declaration(
7127 &self,
7128 visible_from: &ProjectFile,
7129 declaration: &CodeUnit,
7130 raw_name: &str,
7131 ) -> Option<CodeUnit> {
7132 let normalized = normalize_reference_name(raw_name)?;
7133 if !normalized.contains("::")
7134 && let Some(namespace) = cpp_namespace_for(declaration)
7135 {
7136 for prefix in namespace_prefixes(&namespace) {
7137 let qualified = format!("{prefix}::{normalized}");
7138 if let Some(unit) = self
7139 .type_candidates(visible_from, &qualified)
7140 .into_iter()
7141 .next()
7142 {
7143 return Some(unit.clone());
7144 }
7145 }
7146 }
7147 self.resolve_type(visible_from, raw_name)
7148 }
7149
7150 fn resolve_unique_canonical_type_for_declaration(
7151 &self,
7152 analyzer: &CppGraphSource<'_>,
7153 visible_from: &ProjectFile,
7154 declaration: &CodeUnit,
7155 raw_name: &str,
7156 ) -> Option<CodeUnit> {
7157 let current = self.resolve_defining_type_for_declaration(
7158 analyzer,
7159 visible_from,
7160 declaration,
7161 raw_name,
7162 )?;
7163 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7164 .ok()
7165 }
7166
7167 pub fn canonical_type_unit(
7168 &self,
7169 analyzer: &CppGraphSource<'_>,
7170 visible_from: &ProjectFile,
7171 unit: &CodeUnit,
7172 ) -> Option<CodeUnit> {
7173 self.canonical_type_resolution(analyzer, visible_from, unit)
7174 .ok()
7175 }
7176
7177 pub fn canonical_type_unit_in_context(
7185 &self,
7186 analyzer: &CppGraphSource<'_>,
7187 visible_from: &ProjectFile,
7188 reference: Node<'_>,
7189 unit: &CodeUnit,
7190 ) -> Option<CodeUnit> {
7191 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7192 {
7193 return None;
7194 }
7195 self.canonical_type_resolution(analyzer, visible_from, unit)
7196 .ok()
7197 }
7198
7199 fn canonical_type_resolution(
7207 &self,
7208 analyzer: &CppGraphSource<'_>,
7209 visible_from: &ProjectFile,
7210 unit: &CodeUnit,
7211 ) -> Result<CodeUnit, TypeCandidateFailure> {
7212 let mut current = unit.clone();
7213 let mut seen_aliases = HashSet::default();
7214 loop {
7215 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7216 return current
7217 .is_class()
7218 .then_some(current)
7219 .ok_or(TypeCandidateFailure::Unresolvable);
7220 };
7221 if matches!(target, StructuredAliasTarget::Builtin) {
7222 return current
7223 .is_class()
7224 .then_some(current)
7225 .ok_or(TypeCandidateFailure::Unresolvable);
7226 }
7227 if !seen_aliases.insert(current.clone()) {
7228 return Err(TypeCandidateFailure::Unresolvable);
7229 }
7230 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7231 }
7232 }
7233
7234 pub fn canonical_visible_full_type_unit(
7235 &self,
7236 analyzer: &CppGraphSource<'_>,
7237 visible_from: &ProjectFile,
7238 unit: &CodeUnit,
7239 ) -> Option<CodeUnit> {
7240 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7241 if cpp_class_declaration_strength(analyzer, &canonical)
7242 != CppClassDeclarationStrength::Forward
7243 {
7244 return Some(canonical);
7245 }
7246 let mut full = Vec::new();
7247 for candidate in self
7248 .visible_identifier_candidates(visible_from, canonical.identifier())
7249 .filter(|candidate| {
7250 candidate.is_class()
7251 && candidate.fq_name() == canonical.fq_name()
7252 && cpp_class_declaration_strength(analyzer, candidate)
7253 == CppClassDeclarationStrength::Full
7254 })
7255 {
7256 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7257 full.push(candidate.clone());
7258 }
7259 }
7260 match full.len() {
7261 0 => Some(canonical),
7262 1 => full.pop(),
7263 _ => None,
7264 }
7265 }
7266
7267 fn resolve_structured_alias_target(
7268 &self,
7269 visible_from: &ProjectFile,
7270 declaration: &CodeUnit,
7271 target: &StructuredAliasTarget,
7272 ) -> Option<CodeUnit> {
7273 self.structured_alias_target_resolution(visible_from, declaration, target)
7274 .ok()
7275 }
7276
7277 fn structured_alias_target_resolution(
7278 &self,
7279 visible_from: &ProjectFile,
7280 declaration: &CodeUnit,
7281 target: &StructuredAliasTarget,
7282 ) -> Result<CodeUnit, TypeCandidateFailure> {
7283 let primary =
7284 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7285 let StructuredAliasTarget::Named { arguments, .. } = target else {
7286 return Err(TypeCandidateFailure::Unresolvable);
7287 };
7288 match arguments {
7289 Some(arguments) => self
7290 .resolve_template_arguments(visible_from, primary, arguments)
7291 .map_err(|error| match error {
7292 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7293 TypeCandidateFailure::Ambiguous
7294 }
7295 _ => TypeCandidateFailure::Unresolvable,
7296 }),
7297 None => Ok(primary),
7298 }
7299 }
7300
7301 fn resolve_structured_alias_primary(
7302 &self,
7303 visible_from: &ProjectFile,
7304 declaration: &CodeUnit,
7305 target: &StructuredAliasTarget,
7306 ) -> Option<CodeUnit> {
7307 self.structured_alias_primary_resolution(visible_from, declaration, target)
7308 .ok()
7309 }
7310
7311 fn structured_alias_primary_resolution(
7312 &self,
7313 visible_from: &ProjectFile,
7314 declaration: &CodeUnit,
7315 target: &StructuredAliasTarget,
7316 ) -> Result<CodeUnit, TypeCandidateFailure> {
7317 let StructuredAliasTarget::Named {
7318 components, global, ..
7319 } = target
7320 else {
7321 return Err(TypeCandidateFailure::Unresolvable);
7322 };
7323 let qualified = components.join("::");
7324 let candidates = if *global {
7325 let mut candidates = self.type_candidates(visible_from, &qualified);
7332 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7333 candidates
7334 } else {
7335 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7336 };
7337 logical_type_candidate(candidates)
7338 }
7339
7340 pub fn structured_alias_primary_preserves_target(
7341 &self,
7342 analyzer: &CppGraphSource<'_>,
7343 visible_from: &ProjectFile,
7344 candidate: &CodeUnit,
7345 target: &CodeUnit,
7346 ) -> bool {
7347 let mut current = candidate.clone();
7348 let mut seen = HashSet::default();
7349 let mut matched_target = false;
7350 loop {
7351 if same_visible_symbol(¤t, target)
7352 || self.compatible_primary_template_redeclarations(¤t, target)
7353 {
7354 matched_target = true;
7355 }
7356 if !seen.insert(current.clone()) {
7357 return false;
7358 }
7359 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7360 return matched_target;
7361 };
7362 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7363 return matched_target;
7364 };
7365 let Some(primary) =
7366 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7367 else {
7368 return matched_target;
7374 };
7375 current = primary;
7376 }
7377 }
7378
7379 fn lexical_type_candidates_match_target(
7380 &self,
7381 analyzer: &CppGraphSource<'_>,
7382 unit: &CodeUnit,
7383 candidates: &[CodeUnit],
7384 target: &CodeUnit,
7385 ) -> bool {
7386 same_visible_symbol(unit, target)
7387 || self.same_template_member_identity(analyzer, unit, target)
7388 || candidates.iter().any(|candidate| {
7389 same_visible_symbol(candidate, target)
7390 || self.same_template_member_identity(analyzer, candidate, target)
7391 })
7392 }
7393
7394 pub fn structured_class_alias_resolves_to_target(
7395 &self,
7396 analyzer: &CppGraphSource<'_>,
7397 visible_from: &ProjectFile,
7398 alias: &CodeUnit,
7399 target: &CodeUnit,
7400 ) -> bool {
7401 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7402 return false;
7403 };
7404 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7405 return false;
7406 };
7407 let StructuredAliasTarget::Named {
7408 components, global, ..
7409 } = &alias_target
7410 else {
7411 return false;
7412 };
7413 let lexical_scope = canonical_cpp_scope_components(&owner);
7414 match self.resolve_type_components_lexically_for_target(
7415 analyzer,
7416 visible_from,
7417 components,
7418 *global,
7419 &lexical_scope,
7420 target,
7421 ) {
7422 LexicalTypeResolution::Resolved {
7423 unit, candidates, ..
7424 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7425 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7426 self.structured_alias_primary_preserves_target(
7427 analyzer,
7428 visible_from,
7429 alias,
7430 target,
7431 ) || self.flattened_macro_namespace_alias_target_matches(
7432 analyzer,
7433 visible_from,
7434 alias,
7435 &alias_target,
7436 target,
7437 )
7438 }
7439 }
7440 }
7441
7442 pub fn structured_class_alias_path_preserves_target(
7450 &self,
7451 analyzer: &CppGraphSource<'_>,
7452 visible_from: &ProjectFile,
7453 alias: &CodeUnit,
7454 target: &CodeUnit,
7455 ) -> bool {
7456 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7457 return false;
7458 };
7459 let Some(StructuredAliasTarget::Named {
7460 components, global, ..
7461 }) = self.structured_alias_target(analyzer, alias)
7462 else {
7463 return false;
7464 };
7465 let lexical_scope = canonical_cpp_scope_components(&owner);
7466 (1..components.len()).rev().any(|component_count| {
7467 matches!(
7468 self.resolve_type_components_lexically_for_target(
7469 analyzer,
7470 visible_from,
7471 &components[..component_count],
7472 global,
7473 &lexical_scope,
7474 target,
7475 ),
7476 LexicalTypeResolution::Resolved {
7477 ref unit,
7478 ref candidates,
7479 ..
7480 } if self.lexical_type_candidates_match_target(
7481 analyzer,
7482 unit,
7483 candidates,
7484 target,
7485 )
7486 )
7487 })
7488 }
7489
7490 fn flattened_macro_namespace_alias_target_matches(
7491 &self,
7492 analyzer: &CppGraphSource<'_>,
7493 visible_from: &ProjectFile,
7494 alias: &CodeUnit,
7495 alias_target: &StructuredAliasTarget,
7496 target: &CodeUnit,
7497 ) -> bool {
7498 let StructuredAliasTarget::Named {
7499 components,
7500 global: false,
7501 arguments: None,
7502 } = alias_target
7503 else {
7504 return false;
7505 };
7506 let Some((target_name, namespace_components)) = components.split_last() else {
7507 return false;
7508 };
7509 if namespace_components.is_empty()
7510 || target_name != target.identifier()
7511 || alias.source() != target.source()
7512 || alias.source() != visible_from
7513 || !target.is_class()
7514 || declared_type_alias(analyzer, target)
7515 {
7516 return false;
7517 }
7518 if self
7519 .resolve_structured_alias_target(visible_from, alias, alias_target)
7520 .is_some()
7521 {
7522 return false;
7523 }
7524
7525 let Some(alias_start) = analyzer
7526 .declaration_source_occurrences(alias)
7527 .into_iter()
7528 .flatten()
7529 .map(|occurrence| occurrence.range.start_byte)
7530 .min()
7531 else {
7532 return false;
7533 };
7534 let has_matching_declaration = analyzer
7535 .declaration_source_occurrences(target)
7536 .into_iter()
7537 .flatten()
7538 .any(|occurrence| {
7539 occurrence.range.end_byte <= alias_start
7540 && occurrence.fact.flattened_macro_namespace.as_deref()
7541 == Some(namespace_components)
7542 });
7543 if !has_matching_declaration {
7544 return false;
7545 }
7546
7547 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7548 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7549 guard_requirement_sets_match(&alias_guards, &target_guards)
7550 }
7551
7552 pub fn template_alias_arguments_preserve_target(
7553 &self,
7554 analyzer: &CppGraphSource<'_>,
7555 visible_from: &ProjectFile,
7556 alias: &CodeUnit,
7557 arguments: &[CppTemplateExpression],
7558 target: &CodeUnit,
7559 ) -> bool {
7560 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7561 return false;
7562 };
7563 if metadata.alias_target.is_none()
7564 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7565 {
7566 return false;
7567 }
7568 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7569 }
7570
7571 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7572 self.cpp_template_metadata
7573 .get(unit)
7574 .is_some_and(CppTemplateMetadata::is_primary)
7575 }
7576
7577 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7578 self.cpp_template_metadata
7579 .get(unit)
7580 .is_some_and(CppTemplateMetadata::is_specialization)
7581 }
7582
7583 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7584 same_visible_symbol(left, right)
7585 || self.compatible_primary_template_redeclarations(left, right)
7586 }
7587
7588 pub fn same_template_member_identity(
7589 &self,
7590 analyzer: &CppGraphSource<'_>,
7591 left: &CodeUnit,
7592 right: &CodeUnit,
7593 ) -> bool {
7594 if same_visible_symbol(left, right) {
7595 return true;
7596 }
7597 if left.kind() != right.kind()
7598 || left.identifier() != right.identifier()
7599 || left.signature() != right.signature()
7600 {
7601 return false;
7602 }
7603 let (Some(left_owner), Some(right_owner)) =
7604 (analyzer.parent_of(left), analyzer.parent_of(right))
7605 else {
7606 return false;
7607 };
7608 left_owner.is_class()
7609 && right_owner.is_class()
7610 && self.same_template_owner_identity(&left_owner, &right_owner)
7611 }
7612
7613 fn unique_canonical_type_candidate(
7614 &self,
7615 analyzer: &CppGraphSource<'_>,
7616 visible_from: &ProjectFile,
7617 candidates: &[&CodeUnit],
7618 ) -> Option<CodeUnit> {
7619 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7620 .ok()
7621 }
7622
7623 fn canonical_type_candidate_resolution(
7624 &self,
7625 analyzer: &CppGraphSource<'_>,
7626 visible_from: &ProjectFile,
7627 candidates: &[&CodeUnit],
7628 ) -> Result<CodeUnit, TypeCandidateFailure> {
7629 let mut canonical = Vec::new();
7630 for candidate in candidates {
7631 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7632 if canonical
7633 .iter()
7634 .any(|existing| same_visible_symbol(existing, &resolved))
7635 {
7636 continue;
7637 }
7638 if let Some(existing) = canonical.iter_mut().find(|existing| {
7639 self.compatible_primary_template_redeclarations(existing, &resolved)
7640 }) {
7641 if matches!(
7650 (
7651 cpp_class_declaration_strength(analyzer, existing),
7652 cpp_class_declaration_strength(analyzer, &resolved),
7653 ),
7654 (
7655 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7656 CppClassDeclarationStrength::Full,
7657 ) | (
7658 CppClassDeclarationStrength::Unknown,
7659 CppClassDeclarationStrength::Forward,
7660 )
7661 ) {
7662 *existing = resolved;
7663 }
7664 continue;
7665 }
7666 canonical.push(resolved);
7667 if canonical.len() > 1 {
7668 return Err(TypeCandidateFailure::Ambiguous);
7669 }
7670 }
7671 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7672 }
7673
7674 pub fn unique_type_candidate_preserving_target(
7675 &self,
7676 analyzer: &CppGraphSource<'_>,
7677 visible_from: &ProjectFile,
7678 candidates: &[&CodeUnit],
7679 target: &CodeUnit,
7680 ) -> Option<CodeUnit> {
7681 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7692 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7693 {
7694 return Some(target.clone());
7695 }
7696 let mut resolved_candidates = Vec::new();
7697 for candidate in candidates {
7698 let Some(resolved) =
7704 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7705 else {
7706 continue;
7707 };
7708 if resolved_candidates
7709 .iter()
7710 .any(|existing| same_visible_symbol(existing, &resolved))
7711 {
7712 continue;
7713 }
7714 resolved_candidates.push(resolved);
7715 }
7716 match resolved_candidates.as_slice() {
7717 [] => None,
7718 [single] => Some(single.clone()),
7719 _ => self
7724 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7725 .map(|_| target.clone()),
7726 }
7727 }
7728
7729 pub fn same_fqn_type_spelling_for_target<'b>(
7746 &self,
7747 analyzer: &CppGraphSource<'_>,
7748 visible_from: &ProjectFile,
7749 candidates: &[&'b CodeUnit],
7750 target: &CodeUnit,
7751 ) -> Option<&'b CodeUnit> {
7752 let [first, rest @ ..] = candidates else {
7753 return None;
7754 };
7755 if rest.is_empty()
7756 || !rest.iter().all(|candidate| {
7757 candidate.kind() == first.kind()
7758 && candidate.fq_name() == first.fq_name()
7759 && candidate.source() == first.source()
7760 })
7761 {
7762 return None;
7763 }
7764 candidates
7765 .iter()
7766 .copied()
7767 .find(|candidate| same_symbol(candidate, target))
7768 .or_else(|| {
7769 candidates.iter().copied().find(|candidate| {
7770 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7771 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7772 })
7773 })
7774 }
7775
7776 pub fn alternate_same_fqn_type_declarations(
7777 &self,
7778 analyzer: &CppGraphSource<'_>,
7779 candidates: &[&CodeUnit],
7780 target: &CodeUnit,
7781 ) -> bool {
7782 let Some(first) = candidates.first() else {
7783 return false;
7784 };
7785 let same_api = first.kind() == target.kind()
7786 && first.fq_name() == target.fq_name()
7787 && first.source() == target.source()
7788 && candidates.iter().all(|candidate| {
7789 candidate.kind() == target.kind()
7790 && candidate.fq_name() == target.fq_name()
7791 && candidate.source() == target.source()
7792 })
7793 && candidates
7794 .iter()
7795 .any(|candidate| same_symbol(candidate, target))
7796 && candidates
7797 .iter()
7798 .any(|candidate| !same_logical_symbol(candidate, target));
7799 if !same_api {
7800 return false;
7801 }
7802
7803 let requirements = candidates
7804 .iter()
7805 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7806 .collect::<Vec<_>>();
7807 requirements.len() > 1
7808 && requirements
7809 .iter()
7810 .all(|requirement| !requirement.is_empty())
7811 && requirements.iter().enumerate().all(|(index, left)| {
7812 requirements[index + 1..].iter().all(|right| {
7813 left.iter().all(|(_, left_guards)| {
7814 right.iter().all(|(_, right_guards)| {
7815 merge_preprocessor_guards(left_guards, right_guards).is_none()
7816 })
7817 })
7818 })
7819 })
7820 }
7821
7822 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7823 let mut pending = vec![terms.to_vec()];
7824 while let Some(branch_terms) = pending.pop() {
7825 let mut normalized = Vec::new();
7826 let mut covers_branch = false;
7827 for term in branch_terms {
7828 if term.iter().any(|guard| term.contains(&guard.negated())) {
7829 continue;
7830 }
7831 if term.is_empty() {
7832 covers_branch = true;
7833 break;
7834 }
7835 if !normalized.iter().any(|existing| existing == &term) {
7836 normalized.push(term);
7837 }
7838 }
7839 if covers_branch {
7840 continue;
7841 }
7842 let Some(split_guard) = normalized
7843 .iter()
7844 .flat_map(|term| term.iter())
7845 .next()
7846 .cloned()
7847 else {
7848 return false;
7849 };
7850 let negated_guard = split_guard.negated();
7851 let mut when_defined = Vec::new();
7852 let mut when_undefined = Vec::new();
7853 for term in normalized {
7854 if term.contains(&negated_guard) {
7855 } else if term.contains(&split_guard) {
7857 let mut reduced = term.clone();
7858 reduced.remove(&split_guard);
7859 when_defined.push(reduced);
7860 } else {
7861 when_defined.push(term.clone());
7862 }
7863 if term.contains(&split_guard) {
7864 } else if term.contains(&negated_guard) {
7866 let mut reduced = term;
7867 reduced.remove(&negated_guard);
7868 when_undefined.push(reduced);
7869 } else {
7870 when_undefined.push(term);
7871 }
7872 }
7873 pending.push(when_defined);
7874 pending.push(when_undefined);
7875 }
7876 true
7877 }
7878
7879 fn declarations_share_exhaustive_conditional_family(
7888 &self,
7889 analyzer: &CppGraphSource<'_>,
7890 candidates: &[&CodeUnit],
7891 ) -> Option<(usize, usize)> {
7892 let mut family_range = None;
7893 for candidate in candidates {
7894 let mut candidate_family = None;
7895 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7896 let key = occurrence.exhaustive_family?;
7897 if candidate_family.is_some_and(|existing| existing != key) {
7898 return None;
7899 }
7900 candidate_family = Some(key);
7901 }
7902 let candidate_family = candidate_family?;
7903 if family_range.is_some_and(|existing| existing != candidate_family) {
7904 return None;
7905 }
7906 family_range = Some(candidate_family);
7907 }
7908 family_range
7909 }
7910
7911 pub fn complementary_same_fqn_type_declarations(
7912 &self,
7913 analyzer: &CppGraphSource<'_>,
7914 candidates: &[&CodeUnit],
7915 target: &CodeUnit,
7916 ) -> bool {
7917 if candidates.len() < 2
7918 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7919 || self
7920 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7921 .is_none()
7922 {
7923 return false;
7924 }
7925 Self::preprocessor_guard_terms_cover_all_paths(
7926 &self.declaration_family_guard_terms(analyzer, candidates),
7927 )
7928 }
7929
7930 fn declaration_family_guard_terms(
7931 &self,
7932 analyzer: &CppGraphSource<'_>,
7933 candidates: &[&CodeUnit],
7934 ) -> Vec<HashSet<PreprocessorGuard>> {
7935 candidates
7936 .iter()
7937 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7938 .map(|(_, guards)| guards)
7939 .collect()
7940 }
7941
7942 fn exhaustive_guard_family_activation(
7958 &self,
7959 analyzer: &CppGraphSource<'_>,
7960 candidate: &CodeUnit,
7961 reference: &CallableReferenceContext<'_>,
7962 ) -> Option<usize> {
7963 if !analyzer
7966 .declaration_source_occurrences(candidate)?
7967 .iter()
7968 .any(|occurrence| occurrence.fact.callable_activation.is_some())
7969 {
7970 return None;
7971 }
7972 let family = self
7973 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7974 .filter(|peer| {
7975 peer.kind() == candidate.kind()
7976 && peer.fq_name() == candidate.fq_name()
7977 && peer.source() == candidate.source()
7978 })
7979 .collect::<Vec<_>>();
7980 let (_, family_end) =
7981 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7982 if !Self::preprocessor_guard_terms_cover_all_paths(
7983 &self.declaration_family_guard_terms(analyzer, &family),
7984 ) {
7985 return None;
7986 }
7987 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7991 .iter()
7992 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7993 {
7994 return None;
7995 }
7996 (first_declaration_byte(analyzer, candidate)?
7997 == family
7998 .iter()
7999 .filter_map(|peer| first_declaration_byte(analyzer, peer))
8000 .min()?)
8001 .then_some(family_end)
8002 }
8003
8004 fn type_candidate_preserving_target(
8005 &self,
8006 analyzer: &CppGraphSource<'_>,
8007 visible_from: &ProjectFile,
8008 candidate: &CodeUnit,
8009 target: &CodeUnit,
8010 ) -> Option<CodeUnit> {
8011 let mut current = candidate.clone();
8012 let mut matched_target = same_visible_symbol(¤t, target)
8013 || self.compatible_primary_template_redeclarations(¤t, target);
8014 let mut seen = HashSet::default();
8015 loop {
8016 if !seen.insert(current.clone()) {
8017 return None;
8018 }
8019 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8020 return matched_target
8021 .then(|| target.clone())
8022 .or_else(|| current.is_class().then_some(current));
8023 };
8024 if self.flattened_macro_namespace_alias_target_matches(
8025 analyzer,
8026 visible_from,
8027 ¤t,
8028 &alias_target,
8029 target,
8030 ) {
8031 return Some(target.clone());
8032 }
8033 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8034 return matched_target
8035 .then(|| target.clone())
8036 .or_else(|| current.is_class().then_some(current));
8037 }
8038 if !self.cpp_template_metadata.contains_key(¤t)
8046 && let Some(primary) =
8047 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8048 && (same_visible_symbol(&primary, target)
8049 || self.compatible_primary_template_redeclarations(&primary, target))
8050 {
8051 return Some(target.clone());
8052 }
8053 if same_visible_symbol(¤t, target) {
8054 return Some(target.clone());
8055 }
8056 if self.cpp_template_metadata.contains_key(¤t) {
8057 return None;
8058 }
8059 let Some(next) =
8060 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
8061 else {
8062 return matched_target.then(|| target.clone());
8063 };
8064 current = next;
8065 matched_target |= same_visible_symbol(¤t, target)
8066 || self.compatible_primary_template_redeclarations(¤t, target);
8067 }
8068 }
8069
8070 fn compatible_primary_template_redeclarations(
8071 &self,
8072 left: &CodeUnit,
8073 right: &CodeUnit,
8074 ) -> bool {
8075 let (Some(left_metadata), Some(right_metadata)) = (
8076 self.cpp_template_metadata.get(left),
8077 self.cpp_template_metadata.get(right),
8078 ) else {
8079 return false;
8080 };
8081 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8082 && left_metadata.is_primary()
8083 && right_metadata.is_primary()
8084 && cpp_reconcile_primary_template_parameters(
8085 &[(left, left_metadata), (right, right_metadata)],
8086 right,
8087 )
8088 .is_some()
8089 }
8090
8091 fn alias_candidate_may_preserve_target(
8092 &self,
8093 analyzer: &CppGraphSource<'_>,
8094 visible_from: &ProjectFile,
8095 candidate: &CodeUnit,
8096 target: &CodeUnit,
8097 ) -> bool {
8098 let mut current = candidate.clone();
8099 let mut seen = HashSet::default();
8100 loop {
8101 if same_visible_symbol(¤t, target)
8102 || self.compatible_primary_template_redeclarations(¤t, target)
8103 {
8104 return true;
8105 }
8106 if self.cpp_template_metadata.contains_key(¤t) {
8107 return true;
8108 }
8109 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8110 return false;
8111 };
8112 let StructuredAliasTarget::Named {
8113 components,
8114 global,
8115 arguments,
8116 } = alias_target
8117 else {
8118 return false;
8119 };
8120 if arguments.is_some() || !seen.insert(current.clone()) {
8121 return true;
8122 }
8123 let qualified = components.join("::");
8124 let next = if global {
8125 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8126 } else {
8127 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8128 };
8129 let Some(next) = next else {
8130 return true;
8131 };
8132 current = next;
8133 }
8134 }
8135
8136 fn alias_candidate_structurally_reaches_target(
8137 &self,
8138 analyzer: &CppGraphSource<'_>,
8139 visible_from: &ProjectFile,
8140 candidate: &CodeUnit,
8141 target: &CodeUnit,
8142 ) -> bool {
8143 let mut current = candidate.clone();
8144 let mut seen = HashSet::default();
8145 loop {
8146 if same_visible_symbol(¤t, target)
8147 || self.compatible_primary_template_redeclarations(¤t, target)
8148 || self
8149 .cpp_template_metadata
8150 .get(¤t)
8151 .zip(self.cpp_template_metadata.get(target))
8152 .is_some_and(|(current, target)| {
8153 current.primary_fq_name == target.primary_fq_name
8154 })
8155 {
8156 return true;
8157 }
8158 if !seen.insert(current.clone()) {
8159 return false;
8160 }
8161 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8162 return false;
8163 };
8164 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8165 && components[..components.len().saturating_sub(1)]
8166 .iter()
8167 .any(|component| {
8168 self.type_reference_component_directly_names_target(component, target)
8169 })
8170 && self.structured_class_alias_path_preserves_target(
8171 analyzer,
8172 visible_from,
8173 ¤t,
8174 target,
8175 )
8176 {
8177 return true;
8178 }
8179 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8180 return false;
8181 }
8182 let Some(primary) =
8183 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8184 else {
8185 return false;
8186 };
8187 current = primary;
8188 }
8189 }
8190
8191 pub fn qualified_alias_reference_may_reach_target(
8203 &self,
8204 analyzer: &CppGraphSource<'_>,
8205 file: &ProjectFile,
8206 components: &[String],
8207 global: bool,
8208 target: &CodeUnit,
8209 ) -> bool {
8210 for end in 0..components.len() {
8211 let spelled = &components[..=end];
8212 for candidate in self
8213 .visible_identifier_candidates(file, &components[end])
8214 .filter(|candidate| declared_type_alias(analyzer, candidate))
8215 {
8216 let candidate_components = canonical_cpp_scope_components(candidate);
8217 let shape_matches = if global {
8218 candidate_components == spelled
8219 } else {
8220 candidate_components.ends_with(spelled)
8221 };
8222 if shape_matches
8223 && self.alias_candidate_structurally_reaches_target(
8224 analyzer, file, candidate, target,
8225 )
8226 {
8227 return true;
8228 }
8229 }
8230 }
8231 false
8232 }
8233
8234 fn type_candidates_for_declaration<'b>(
8238 &'b self,
8239 visible_from: &ProjectFile,
8240 declaration: &CodeUnit,
8241 raw_name: &str,
8242 ) -> Vec<&'b CodeUnit> {
8243 let Some(normalized) = normalize_reference_name(raw_name) else {
8244 return Vec::new();
8245 };
8246 if let Some(namespace) = cpp_namespace_for(declaration) {
8247 for prefix in namespace_prefixes(&namespace) {
8248 let qualified = format!("{prefix}::{normalized}");
8249 let candidates = self.type_candidates(visible_from, &qualified);
8250 if !candidates.is_empty() {
8251 return candidates;
8252 }
8253 }
8254 }
8255 self.type_candidates(visible_from, &normalized)
8256 }
8257
8258 fn resolve_unique_type_for_declaration(
8259 &self,
8260 visible_from: &ProjectFile,
8261 declaration: &CodeUnit,
8262 raw_name: &str,
8263 ) -> Option<CodeUnit> {
8264 unique_logical_type_candidate(self.type_candidates_for_declaration(
8265 visible_from,
8266 declaration,
8267 raw_name,
8268 ))
8269 }
8270
8271 fn resolve_defining_type_for_declaration(
8289 &self,
8290 analyzer: &CppGraphSource<'_>,
8291 visible_from: &ProjectFile,
8292 declaration: &CodeUnit,
8293 raw_name: &str,
8294 ) -> Option<CodeUnit> {
8295 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8296 let logical = unique_logical_type_candidate(candidates.clone())?;
8297 let mut defining = candidates.into_iter().filter(|candidate| {
8298 candidate.is_class()
8299 && cpp_class_declaration_strength(analyzer, candidate)
8300 == CppClassDeclarationStrength::Full
8301 });
8302 match (defining.next(), defining.next()) {
8303 (Some(unique_definition), None) => Some(unique_definition.clone()),
8304 _ => Some(logical),
8305 }
8306 }
8307
8308 pub fn resolves_to_type(
8309 &self,
8310 analyzer: &CppGraphSource<'_>,
8311 file: &ProjectFile,
8312 raw_name: &str,
8313 target: &CodeUnit,
8314 ) -> bool {
8315 let Some(normalized) = normalize_reference_name(raw_name) else {
8316 return false;
8317 };
8318 let candidates = self.type_candidates(file, &normalized);
8319 if candidates.is_empty() {
8320 return self.parser_alias_resolves_to_type(file, raw_name, target);
8321 }
8322 let Some(resolved) =
8323 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8324 else {
8325 return false;
8326 };
8327 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8328 }
8329
8330 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8331 let mut current = alias.clone();
8332 let mut visited = HashSet::default();
8333 while visited.insert(current.clone()) {
8334 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8335 let resolved =
8336 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8337 if resolved.is_class() {
8338 return Some(resolved);
8339 }
8340 if !declared_type_alias(&self.cpp_source(), &resolved) {
8341 return None;
8342 }
8343 current = resolved;
8344 }
8345 None
8346 }
8347
8348 pub fn same_logical_callable(
8363 &self,
8364 analyzer: &CppGraphSource<'_>,
8365 left: &CodeUnit,
8366 right: &CodeUnit,
8367 ) -> bool {
8368 if same_logical_symbol(left, right) {
8369 return true;
8370 }
8371 if left.kind() != right.kind()
8372 || !left.is_callable()
8373 || !right.is_callable()
8374 || left.fq_name() != right.fq_name()
8375 {
8376 return false;
8377 }
8378 if self.callable_is_template_declaration(analyzer, left)
8384 || self.callable_is_template_declaration(analyzer, right)
8385 {
8386 return false;
8387 }
8388 let (Some(left_comparable), Some(right_comparable)) = (
8389 self.callable_comparable(analyzer, left),
8390 self.callable_comparable(analyzer, right),
8391 ) else {
8392 return false;
8393 };
8394 if left_comparable.suffix != right_comparable.suffix
8399 || left_comparable.shapes.len() != right_comparable.shapes.len()
8400 {
8401 return false;
8402 }
8403 left_comparable
8404 .shapes
8405 .iter()
8406 .zip(right_comparable.shapes.iter())
8407 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8408 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8409 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8410 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8411 }
8412 _ => false,
8416 })
8417 }
8418
8419 fn comparable_shapes_agree(
8425 &self,
8426 analyzer: &CppGraphSource<'_>,
8427 left: &CppComparableParameter,
8428 right: &CppComparableParameter,
8429 ) -> bool {
8430 let mut stack = vec![(left.root(), right.root())];
8431 while let Some((left_index, right_index)) = stack.pop() {
8432 match (left.node(left_index), right.node(right_index)) {
8433 (
8434 CppComparableNode::Named {
8435 name: left_name,
8436 primitive: left_primitive,
8437 konst: left_konst,
8438 volatil: left_volatil,
8439 },
8440 CppComparableNode::Named {
8441 name: right_name,
8442 primitive: right_primitive,
8443 konst: right_konst,
8444 volatil: right_volatil,
8445 },
8446 ) => {
8447 if left_konst != right_konst
8448 || left_volatil != right_volatil
8449 || left_primitive != right_primitive
8450 || !self.comparable_names_agree(
8451 analyzer,
8452 left_name,
8453 right_name,
8454 *left_primitive,
8455 )
8456 {
8457 return false;
8458 }
8459 }
8460 (
8461 CppComparableNode::Pointer {
8462 inner: left_inner,
8463 konst: left_konst,
8464 volatil: left_volatil,
8465 },
8466 CppComparableNode::Pointer {
8467 inner: right_inner,
8468 konst: right_konst,
8469 volatil: right_volatil,
8470 },
8471 ) => {
8472 if left_konst != right_konst || left_volatil != right_volatil {
8473 return false;
8474 }
8475 stack.push((*left_inner, *right_inner));
8476 }
8477 (
8478 CppComparableNode::Reference { inner: left_inner },
8479 CppComparableNode::Reference { inner: right_inner },
8480 )
8481 | (
8482 CppComparableNode::Array { inner: left_inner },
8483 CppComparableNode::Array { inner: right_inner },
8484 ) => stack.push((*left_inner, *right_inner)),
8485 (
8486 CppComparableNode::Generic {
8487 base: left_base,
8488 arguments: left_arguments,
8489 },
8490 CppComparableNode::Generic {
8491 base: right_base,
8492 arguments: right_arguments,
8493 },
8494 ) => {
8495 if left_arguments.len() != right_arguments.len() {
8496 return false;
8497 }
8498 stack.push((*left_base, *right_base));
8499 stack.extend(
8500 left_arguments.iter().zip(right_arguments.iter()).map(
8501 |(left_argument, right_argument)| (*left_argument, *right_argument),
8502 ),
8503 );
8504 }
8505 _ => return false,
8506 }
8507 }
8508 true
8509 }
8510
8511 fn comparable_names_agree(
8521 &self,
8522 analyzer: &CppGraphSource<'_>,
8523 left: &StructuredTypeName,
8524 right: &StructuredTypeName,
8525 primitive: bool,
8526 ) -> bool {
8527 if primitive {
8528 return left.path() == right.path();
8529 }
8530 match (
8531 self.comparable_name_terminal(analyzer, left),
8532 self.comparable_name_terminal(analyzer, right),
8533 ) {
8534 (Some(left_terminal), Some(right_terminal)) => {
8535 same_logical_symbol(&left_terminal, &right_terminal)
8536 }
8537 (None, None) => {
8538 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8539 }
8540 _ => false,
8541 }
8542 }
8543
8544 fn comparable_name_terminal(
8554 &self,
8555 analyzer: &CppGraphSource<'_>,
8556 name: &StructuredTypeName,
8557 ) -> Option<CodeUnit> {
8558 let mut current = self.comparable_name_declaration(analyzer, name)?;
8559 let mut visited = HashSet::default();
8560 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8561 if !declared_type_alias(analyzer, ¤t) {
8568 return current.is_class().then_some(current);
8569 }
8570 if !visited.insert(current.clone()) {
8571 return None;
8572 }
8573 let facts = analyzer.declaration_source_properties(¤t)?;
8574 if facts.iter().any(|fact| fact.adds_indirection) {
8575 return None;
8576 }
8577 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
8578 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8579 }
8580 None
8581 }
8582
8583 fn comparable_alias_target(
8594 &self,
8595 analyzer: &CppGraphSource<'_>,
8596 alias: &CodeUnit,
8597 raw_target: &str,
8598 ) -> Option<CodeUnit> {
8599 let absolute = raw_target.trim_start().starts_with("::");
8604 let normalized = normalize_reference_name(raw_target)?;
8605 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8606 brokk_bifrost_core::analyzer::Language::Cpp,
8607 &normalized,
8608 );
8609 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8610 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8611 brokk_bifrost_core::analyzer::Language::Cpp,
8612 &namespace,
8613 )
8614 });
8615 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8616 self.comparable_name_declaration(analyzer, &name)
8617 }
8618
8619 fn comparable_name_declaration(
8627 &self,
8628 analyzer: &CppGraphSource<'_>,
8629 name: &StructuredTypeName,
8630 ) -> Option<CodeUnit> {
8631 let mut cache = self
8638 .comparable_name_declarations
8639 .lock()
8640 .expect("C++ comparable name declaration cache poisoned");
8641 if let Some(cached) = cache.get(name) {
8642 return cached.clone();
8643 }
8644 let interner = segment_interner();
8645 let identifier = name.path().last()?;
8646 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8647 let first_depth = if name.is_absolute() {
8648 0
8649 } else {
8650 name.lexical_scope().len()
8651 };
8652 let mut resolved = None;
8653 for depth in (0..=first_depth).rev() {
8654 let mut structured = FqName::new();
8655 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8656 structured.push(interner.intern(component, SegmentKind::Unknown));
8657 }
8658 let mut candidates = candidates_by_identifier
8659 .iter()
8660 .filter(|unit| unit.fq().same_segment_texts(&structured))
8661 .filter(|unit| {
8662 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8663 })
8664 .cloned();
8665 let Some(first) = candidates.next() else {
8666 continue;
8667 };
8668 resolved = candidates
8669 .all(|unit| same_logical_symbol(&unit, &first))
8670 .then_some(first);
8671 break;
8672 }
8673 cache.insert(name.clone(), resolved.clone());
8674 resolved
8675 }
8676
8677 fn callable_comparable(
8683 &self,
8684 analyzer: &CppGraphSource<'_>,
8685 unit: &CodeUnit,
8686 ) -> Option<Arc<ExtractedComparable>> {
8687 if let Some(cached) = self
8688 .callable_comparables
8689 .lock()
8690 .expect("C++ callable comparable cache poisoned")
8691 .get(unit)
8692 .cloned()
8693 {
8694 return cached;
8695 }
8696 let extracted = self
8697 .extract_callable_comparable(analyzer, unit)
8698 .map(Arc::new);
8699 self.callable_comparables
8700 .lock()
8701 .expect("C++ callable comparable cache poisoned")
8702 .insert(unit.clone(), extracted.clone());
8703 extracted
8704 }
8705
8706 fn extract_callable_comparable(
8707 &self,
8708 analyzer: &CppGraphSource<'_>,
8709 unit: &CodeUnit,
8710 ) -> Option<ExtractedComparable> {
8711 let fact = analyzer
8712 .declaration_source_properties(unit)?
8713 .into_iter()
8714 .find(|fact| fact.callable_comparable_shapes.is_some())?;
8715 Some(ExtractedComparable {
8716 shapes: fact.callable_comparable_shapes?,
8717 suffix: fact.callable_identity_suffix?,
8718 })
8719 }
8720
8721 pub fn canonical_type_for_reference(
8722 &self,
8723 file: &ProjectFile,
8724 raw_name: &str,
8725 ) -> Option<CodeUnit> {
8726 let resolved = self.resolve_type(file, raw_name)?;
8727 self.alias_target(&resolved).or(Some(resolved))
8728 }
8729
8730 pub fn parser_alias_resolves_to_type(
8731 &self,
8732 file: &ProjectFile,
8733 raw_name: &str,
8734 target: &CodeUnit,
8735 ) -> bool {
8736 let Some(alias_name) = normalize_reference_name(raw_name) else {
8737 return false;
8738 };
8739 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8740 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8741 })
8742 }
8743
8744 fn file_alias_matches(
8745 &self,
8746 cpp: &dyn CppSource,
8747 file: &ProjectFile,
8748 alias_name: &str,
8749 target: &CodeUnit,
8750 ) -> bool {
8751 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8752 cell.get()
8753 .expect("published canonical aliases")
8754 .iter()
8755 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8756 })
8757 }
8758
8759 fn canonical_alias_cell(
8760 &self,
8761 cpp: &dyn CppSource,
8762 file: &ProjectFile,
8763 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8764 let cell = {
8765 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8766 Arc::clone(
8767 cells
8768 .entry(file.clone())
8769 .or_insert_with(|| Arc::new(OnceLock::new())),
8770 )
8771 };
8772 if cell.get().is_none() {
8773 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8774 cell.get_or_init(|| {
8775 self.parser_alias_source_parses
8776 .fetch_add(1, Ordering::Relaxed);
8777 #[cfg(any(test, feature = "test-support"))]
8778 {
8779 *self
8780 .alias_source_parse_counts
8781 .lock()
8782 .expect("alias source parse count lock")
8783 .entry(file.clone())
8784 .or_default() += 1;
8785 }
8786 aliases.into_boxed_slice()
8787 });
8788 }
8789 Some(cell)
8790 }
8791
8792 #[cfg(any(test, feature = "test-support"))]
8793 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8794 self.visible_source_files_by_root
8795 .get(file)
8796 .cloned()
8797 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8798 }
8799
8800 #[cfg(any(test, feature = "test-support"))]
8801 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8802 self.alias_source_parse_counts
8803 .lock()
8804 .expect("alias source parse count lock")
8805 .get(file)
8806 .copied()
8807 .unwrap_or(0)
8808 }
8809
8810 #[cfg(any(test, feature = "test-support"))]
8811 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8812 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8813 }
8814
8815 pub fn resolve_named(
8816 &self,
8817 file: &ProjectFile,
8818 raw_name: &str,
8819 kind: TargetKind,
8820 ) -> Option<CodeUnit> {
8821 let normalized = normalize_reference_name(raw_name)?;
8822 self.named_candidates_for_normalized(file, &normalized, kind)
8823 .into_iter()
8824 .next()
8825 .cloned()
8826 }
8827
8828 pub fn contains_named_symbol(
8829 &self,
8830 file: &ProjectFile,
8831 raw_name: &str,
8832 kind: TargetKind,
8833 target: &CodeUnit,
8834 ) -> bool {
8835 let Some(normalized) = normalize_reference_name(raw_name) else {
8836 return false;
8837 };
8838 self.named_candidates_for_normalized(file, &normalized, kind)
8839 .into_iter()
8840 .any(|unit| {
8841 matches_kind_for_lookup(unit, kind)
8842 && reference_matches_unit(&normalized, unit)
8843 && same_visible_symbol(unit, target)
8844 })
8845 }
8846
8847 pub fn named_candidates(
8848 &self,
8849 file: &ProjectFile,
8850 raw_name: &str,
8851 kind: TargetKind,
8852 ) -> Vec<CodeUnit> {
8853 let Some(normalized) = normalize_reference_name(raw_name) else {
8854 return Vec::new();
8855 };
8856 self.named_candidates_for_normalized(file, &normalized, kind)
8857 .into_iter()
8858 .cloned()
8859 .collect()
8860 }
8861
8862 pub fn resolve_known_non_target(
8863 &self,
8864 file: &ProjectFile,
8865 raw_name: &str,
8866 kind: TargetKind,
8867 target: &CodeUnit,
8868 ) -> bool {
8869 let Some(normalized) = normalize_reference_name(raw_name) else {
8870 return false;
8871 };
8872 normalized.contains("::")
8873 && self
8874 .named_candidates_for_normalized(file, &normalized, kind)
8875 .into_iter()
8876 .any(|unit| {
8877 matches_kind_for_lookup(unit, kind)
8878 && reference_matches_unit(&normalized, unit)
8879 && !same_visible_symbol(unit, target)
8880 })
8881 }
8882
8883 pub fn resolve_call_return_binding(
8884 &self,
8885 analyzer: &CppGraphSource<'_>,
8886 file: &ProjectFile,
8887 raw_name: &str,
8888 arity: usize,
8889 lexical_namespace: Option<&str>,
8890 direct_type: Option<&CodeUnit>,
8891 ) -> Option<CppScanBinding> {
8892 let normalized = normalize_reference_name(raw_name)?;
8893 let mut candidates = Vec::new();
8894 for function in
8895 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8896 {
8897 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8898 && !direct_type.is_some_and(|direct_type| {
8899 self.callable_is_constructor_declaration(analyzer, function)
8900 && type_owner_of(analyzer, function)
8901 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8902 })
8903 {
8904 candidates.push(function.clone());
8905 }
8906 }
8907 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8908 unanimous_return_binding(analyzer, self, file, &candidates)
8909 }
8910
8911 pub fn resolve_call_return_binding_without_arity(
8912 &self,
8913 analyzer: &CppGraphSource<'_>,
8914 file: &ProjectFile,
8915 raw_name: &str,
8916 lexical_namespace: Option<&str>,
8917 direct_type: Option<&CodeUnit>,
8918 ) -> (bool, Option<CppScanBinding>) {
8919 let Some(normalized) = normalize_reference_name(raw_name) else {
8920 return (false, None);
8921 };
8922 let mut candidates = self
8923 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8924 .into_iter()
8925 .filter(|function| {
8926 function.is_function()
8927 && !direct_type.is_some_and(|direct_type| {
8928 self.callable_is_constructor_declaration(analyzer, function)
8929 && type_owner_of(analyzer, function)
8930 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8931 })
8932 })
8933 .cloned()
8934 .collect::<Vec<_>>();
8935 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8936 let has_candidates = !candidates.is_empty();
8937 (
8938 has_candidates,
8939 unanimous_return_binding(analyzer, self, file, &candidates),
8940 )
8941 }
8942
8943 pub fn visible_identifier_candidates<'b>(
8944 &'b self,
8945 file: &ProjectFile,
8946 identifier: &str,
8947 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8948 self.visible_by_identifier
8949 .get(file)
8950 .and_then(|by_name| by_name.get(identifier))
8951 .into_iter()
8952 .flatten()
8953 }
8954
8955 pub fn type_reference_component_directly_names_target(
8961 &self,
8962 component: &str,
8963 target: &CodeUnit,
8964 ) -> bool {
8965 component == target.identifier()
8966 || self
8967 .cpp_template_metadata
8968 .get(target)
8969 .is_some_and(|metadata| component == metadata.primary_name)
8970 }
8971
8972 pub fn visible_type_reference_component_names_for_target(
8980 &self,
8981 analyzer: &CppGraphSource<'_>,
8982 file: &ProjectFile,
8983 target: &CodeUnit,
8984 ) -> HashSet<String> {
8985 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8986 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8987 names.insert(metadata.primary_name.clone());
8988 }
8989
8990 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8991 for (identifier, candidates) in by_identifier {
8992 if candidates.iter().any(|candidate| {
8993 (candidate.is_class()
8994 && (same_visible_symbol(candidate, target)
8995 || self.compatible_primary_template_redeclarations(candidate, target)))
8996 || (declared_type_alias(analyzer, candidate)
8997 && self.alias_candidate_structurally_reaches_target(
8998 analyzer, file, candidate, target,
8999 ))
9000 }) {
9001 names.insert(identifier.clone());
9002 }
9003 }
9004 }
9005
9006 names
9007 }
9008
9009 pub fn indexed_structural_class_scope(
9010 &self,
9011 file: &ProjectFile,
9012 class: Node<'_>,
9013 source: &str,
9014 ) -> Option<Vec<String>> {
9015 let key = (file.clone(), class.start_byte(), class.end_byte());
9016 if let Some(cached) = self
9017 .indexed_structural_class_scopes
9018 .lock()
9019 .expect("C++ indexed structural-class scope cache poisoned")
9020 .get(&key)
9021 .cloned()
9022 {
9023 return cached;
9024 }
9025 let resolved = (|| {
9026 let name = class.child_by_field_name("name")?;
9027 let identifier = if name.kind() == "template_type" {
9028 node_text(name.child_by_field_name("name")?, source).to_string()
9029 } else {
9030 let mut components = Vec::new();
9031 append_cpp_name_components(name, source, &mut components)?;
9032 components.last()?.clone()
9033 };
9034 let visible = self
9035 .visible_identifier_candidates(file, &identifier)
9036 .cloned()
9037 .collect::<Vec<_>>();
9038 let mut visible = visible;
9039 for candidate in
9040 self.visible_by_file
9041 .get(file)
9042 .into_iter()
9043 .flatten()
9044 .filter(|candidate| {
9045 self.cpp_template_metadata
9046 .get(candidate)
9047 .is_some_and(|metadata| metadata.primary_name == identifier)
9048 })
9049 {
9050 if !visible
9051 .iter()
9052 .any(|existing| same_logical_symbol(existing, candidate))
9053 {
9054 visible.push(candidate.clone());
9055 }
9056 }
9057 let cpp_source = self.cpp_source();
9060 let candidates = visible
9061 .iter()
9062 .filter(|candidate| {
9063 candidate.source() == file
9064 && candidate.is_class()
9065 && !declared_type_alias(&cpp_source, candidate)
9066 && self.cpp.ranges(candidate).iter().any(|range| {
9067 range.start_byte <= class.start_byte()
9068 && class.end_byte() <= range.end_byte
9069 })
9070 })
9071 .collect::<Vec<_>>();
9072 let owner = if name.kind() == "template_type" {
9073 let expected = normalize_cpp_whitespace(node_text(name, source));
9074 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9075 let exact = candidates
9076 .iter()
9077 .copied()
9078 .filter(|candidate| {
9079 candidate
9080 .fq()
9081 .segments()
9082 .iter()
9083 .rev()
9084 .find_map(|&segment| {
9085 let (text, kind) = interner.resolve(segment);
9086 matches!(
9087 kind,
9088 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9089 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9090 )
9091 .then_some(text)
9092 })
9093 .is_some_and(|text| text == expected)
9094 })
9095 .collect::<Vec<_>>();
9096 unique_logical_type_candidate(exact)
9097 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9098 } else {
9099 unique_logical_type_candidate(candidates)?
9100 };
9101 Some(canonical_cpp_scope_components(&owner))
9102 })();
9103 self.indexed_structural_class_scopes
9104 .lock()
9105 .expect("C++ indexed structural-class scope cache poisoned")
9106 .insert(key, resolved.clone());
9107 resolved
9108 }
9109
9110 pub fn indexed_enclosing_owner_scope(
9111 &self,
9112 analyzer: &CppGraphSource<'_>,
9113 file: &ProjectFile,
9114 node: Node<'_>,
9115 ) -> Option<Vec<String>> {
9116 let anchor = std::iter::successors(Some(node), |current| current.parent())
9117 .find(|current| {
9118 matches!(
9119 current.kind(),
9120 "function_definition"
9121 | "class_specifier"
9122 | "struct_specifier"
9123 | "union_specifier"
9124 )
9125 })
9126 .unwrap_or(node);
9127 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9128 if let Some(cached) = self
9129 .indexed_enclosing_owner_scopes
9130 .lock()
9131 .expect("C++ indexed enclosing-owner scope cache poisoned")
9132 .get(&key)
9133 .cloned()
9134 {
9135 return cached;
9136 }
9137 let resolved = (|| {
9138 let range = Range {
9139 start_byte: node.start_byte(),
9140 end_byte: node.end_byte(),
9141 start_line: node.start_position().row,
9142 end_line: node.end_position().row,
9143 };
9144 let start = analyzer.enclosing_code_unit(file, &range)?;
9145 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9146 start,
9147 |unit| self.cached_precise_parent_of(analyzer, unit),
9148 )
9149 .find(|unit| {
9150 unit.is_class()
9151 && !analyzer
9152 .type_alias_provider()
9153 .is_some_and(|provider| provider.is_type_alias(unit))
9154 })?;
9155 Some(canonical_cpp_scope_components(&owner))
9156 })();
9157 self.indexed_enclosing_owner_scopes
9158 .lock()
9159 .expect("C++ indexed enclosing-owner scope cache poisoned")
9160 .insert(key, resolved.clone());
9161 resolved
9162 }
9163
9164 fn cached_precise_parent_of(
9165 &self,
9166 analyzer: &CppGraphSource<'_>,
9167 code_unit: &CodeUnit,
9168 ) -> Option<CodeUnit> {
9169 if let Some(cached) = self
9170 .precise_parent_cache
9171 .lock()
9172 .expect("C++ precise-parent cache poisoned")
9173 .get(code_unit)
9174 .cloned()
9175 {
9176 return cached;
9177 }
9178 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9179 self.precise_parent_cache
9180 .lock()
9181 .expect("C++ precise-parent cache poisoned")
9182 .insert(code_unit.clone(), resolved.clone());
9183 resolved
9184 }
9185
9186 pub fn callable_is_constructor_declaration(
9187 &self,
9188 analyzer: &CppGraphSource<'_>,
9189 candidate: &CodeUnit,
9190 ) -> bool {
9191 if !candidate.is_function() {
9192 return false;
9193 }
9194 if analyzer
9195 .declaration_source_properties(candidate)
9196 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9197 {
9198 return true;
9199 }
9200 let metadata = analyzer.signature_metadata(candidate);
9201 !metadata.is_empty()
9202 && metadata
9203 .iter()
9204 .all(|signature| signature.return_type_text().is_none())
9205 }
9206
9207 pub fn callable_is_deduction_guide_declaration(
9215 &self,
9216 analyzer: &CppGraphSource<'_>,
9217 candidate: &CodeUnit,
9218 ) -> bool {
9219 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9220 }
9221
9222 pub fn callable_is_template_declaration(
9226 &self,
9227 analyzer: &CppGraphSource<'_>,
9228 candidate: &CodeUnit,
9229 ) -> bool {
9230 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9231 }
9232
9233 pub fn type_name_candidates<'b>(
9234 &'b self,
9235 file: &ProjectFile,
9236 normalized: &str,
9237 ) -> Vec<&'b CodeUnit> {
9238 self.candidate_units(file, normalized, TargetKind::Type)
9239 }
9240
9241 pub fn visible_members_for_owner_name<'b>(
9242 &'b self,
9243 file: &ProjectFile,
9244 owner: &CodeUnit,
9245 name: &str,
9246 ) -> Vec<&'b CodeUnit> {
9247 self.visible_identifier_candidates(file, name)
9248 .filter(|unit| {
9249 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9253 .is_some_and(|parent| parent == owner.fq_name())
9254 })
9255 .collect()
9256 }
9257
9258 pub fn visible_member_for_owner_name(
9259 &self,
9260 file: &ProjectFile,
9261 owner: &CodeUnit,
9262 name: &str,
9263 ) -> VisibleMemberResolution {
9264 let candidates = self.visible_members_for_owner_name(file, owner, name);
9265 let mut callables = Vec::new();
9266 let mut non_callable = None;
9267 for candidate in candidates {
9268 if candidate.is_function() {
9269 callables.push(candidate.clone());
9270 } else if non_callable.is_none() {
9271 non_callable = Some(candidate.clone());
9272 }
9273 }
9274 match (callables.is_empty(), non_callable) {
9275 (false, None) => VisibleMemberResolution::Callable(callables),
9276 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9277 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9278 (true, None) => VisibleMemberResolution::Missing,
9279 }
9280 }
9281
9282 fn field_declared_type_fact(
9283 &self,
9284 analyzer: &CppGraphSource<'_>,
9285 field: &CodeUnit,
9286 ) -> Option<DeclaredFieldTypeFact> {
9287 if let Some(cached) = self
9288 .field_type_facts
9289 .lock()
9290 .expect("C++ field type fact cache poisoned")
9291 .get(field)
9292 .cloned()
9293 {
9294 return cached;
9295 }
9296 let decoded = decode_field_declared_type_fact(analyzer, field);
9297 self.field_type_facts
9298 .lock()
9299 .expect("C++ field type fact cache poisoned")
9300 .insert(field.clone(), decoded.clone());
9301 decoded
9302 }
9303
9304 fn structured_alias_target(
9305 &self,
9306 analyzer: &CppGraphSource<'_>,
9307 unit: &CodeUnit,
9308 ) -> Option<StructuredAliasTarget> {
9309 if let Some(cached) = self
9310 .structured_alias_targets
9311 .lock()
9312 .expect("C++ structured alias target cache poisoned")
9313 .get(unit)
9314 .cloned()
9315 {
9316 return cached;
9317 }
9318 let decoded = decode_structured_alias_target(analyzer, unit);
9319 self.structured_alias_targets
9320 .lock()
9321 .expect("C++ structured alias target cache poisoned")
9322 .insert(unit.clone(), decoded.clone());
9323 decoded
9324 }
9325
9326 pub fn type_candidates<'b>(
9327 &'b self,
9328 file: &ProjectFile,
9329 normalized: &str,
9330 ) -> Vec<&'b CodeUnit> {
9331 let mut candidates = self
9332 .candidate_units(file, normalized, TargetKind::Type)
9333 .into_iter()
9334 .filter(|unit| {
9335 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9336 })
9337 .collect::<Vec<_>>();
9338 dedup_unit_refs(&mut candidates);
9339 candidates
9340 }
9341
9342 pub fn named_candidates_for_normalized<'b>(
9343 &'b self,
9344 file: &ProjectFile,
9345 normalized: &str,
9346 kind: TargetKind,
9347 ) -> Vec<&'b CodeUnit> {
9348 let mut candidates = self
9349 .candidate_units(file, normalized, kind)
9350 .into_iter()
9351 .filter(|unit| {
9352 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9353 })
9354 .collect::<Vec<_>>();
9355 dedup_unit_refs(&mut candidates);
9356 candidates
9357 }
9358
9359 pub fn candidate_units<'b>(
9360 &'b self,
9361 file: &ProjectFile,
9362 normalized: &str,
9363 kind: TargetKind,
9364 ) -> Vec<&'b CodeUnit> {
9365 if normalized.contains("::") {
9366 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9375 brokk_bifrost_core::analyzer::Language::Cpp,
9376 normalized,
9377 );
9378 let Some(identifier) = parts.last() else {
9379 return Vec::new();
9380 };
9381 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9382 return self
9383 .visible_identifier_candidates(file, identifier)
9384 .filter(|unit| {
9385 #[cfg(any(test, feature = "test-support"))]
9386 self.qualified_candidate_inspections
9387 .fetch_add(1, Ordering::Relaxed);
9388 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9389 || canonical_cpp_name_matches(unit, normalized)
9390 })
9391 .collect();
9392 }
9393 self.visible_identifier_candidates(file, normalized)
9394 .collect()
9395 }
9396
9397 #[cfg(any(test, feature = "test-support"))]
9398 pub fn reset_qualified_candidate_inspections(&self) {
9399 self.qualified_candidate_inspections
9400 .store(0, Ordering::Relaxed);
9401 }
9402
9403 #[cfg(any(test, feature = "test-support"))]
9404 pub fn qualified_candidate_inspections(&self) -> usize {
9405 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9406 }
9407
9408 #[cfg(any(test, feature = "test-support"))]
9409 pub fn visibility_identifier_lookup_count(&self) -> usize {
9410 self.visibility_identifier_lookup_count
9411 }
9412
9413 #[cfg(any(test, feature = "test-support"))]
9414 pub fn visibility_identifier_batch_count(&self) -> usize {
9415 self.visibility_identifier_batch_count
9416 }
9417
9418 #[cfg(any(test, feature = "test-support"))]
9419 pub fn reset_target_preserving_type_resolution_count(&self) {
9420 self.target_preserving_type_resolution_count
9421 .store(0, Ordering::Relaxed);
9422 }
9423
9424 #[cfg(any(test, feature = "test-support"))]
9425 pub fn target_preserving_type_resolution_count(&self) -> usize {
9426 self.target_preserving_type_resolution_count
9427 .load(Ordering::Relaxed)
9428 }
9429
9430 #[cfg(any(test, feature = "test-support"))]
9431 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9432 self.visible_parser_alias_name_set_build_count
9433 .load(Ordering::Relaxed)
9434 }
9435}
9436
9437fn callable_declaration_has_fact(
9438 analyzer: &CppGraphSource<'_>,
9439 candidate: &CodeUnit,
9440 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9441) -> bool {
9442 candidate.is_function()
9443 && analyzer
9444 .declaration_source_properties(candidate)
9445 .is_some_and(|facts| facts.iter().any(matches))
9446}
9447
9448#[derive(Default)]
9449struct IncludeGraph {
9450 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9451}
9452
9453impl IncludeGraph {
9454 fn extend_with<F>(
9455 &mut self,
9456 root: &ProjectFile,
9457 cancellation: Option<&CancellationToken>,
9458 targets_for: &mut F,
9459 ) where
9460 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9461 {
9462 let mut stack = vec![root.clone()];
9463 while let Some(file) = stack.pop() {
9464 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9465 break;
9466 }
9467 if self.targets_by_file.contains_key(&file) {
9468 continue;
9469 }
9470 let targets = targets_for(&file);
9471 stack.extend(targets.iter().cloned());
9472 self.targets_by_file.insert(file, targets);
9473 }
9474 }
9475
9476 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9477 self.targets_by_file.keys()
9478 }
9479
9480 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9481 self.targets_by_file
9482 .get(file)
9483 .map(Vec::as_slice)
9484 .unwrap_or_default()
9485 }
9486
9487 fn reachable_files(
9488 &self,
9489 root: &ProjectFile,
9490 cancellation: Option<&CancellationToken>,
9491 ) -> HashSet<ProjectFile> {
9492 let mut pending = vec![root.clone()];
9493 let mut visited = HashSet::default();
9494 while let Some(file) = pending.pop() {
9495 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9496 break;
9497 }
9498 if visited.insert(file.clone()) {
9499 pending.extend(self.targets(&file).iter().cloned());
9500 }
9501 }
9502 visited
9503 }
9504}
9505
9506fn build_bounded_visible_declarations(
9507 cpp: &dyn CppSource,
9508 token: QueryToken<'_>,
9509 analyzer: &CppGraphSource<'_>,
9510 roots: &HashSet<ProjectFile>,
9511 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9512 cancellation: Option<&CancellationToken>,
9513 stats: &mut BoundedVisibilityStats,
9514) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9515 let mut candidates_by_identifier = HashMap::default();
9516 let mut declarations_by_source_and_reading: HashMap<
9517 (ProjectFile, bool),
9518 BoundedVisibilityDeclarations,
9519 > = HashMap::default();
9520 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9521 roots
9522 .iter()
9523 .map(|root| {
9524 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9525 let root_declarations = declarations_by_source_and_reading
9526 .entry((root.clone(), reading_is_c))
9527 .or_insert_with(|| {
9528 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9529 });
9530 let mut visible = root_declarations
9531 .all
9532 .iter()
9533 .cloned()
9534 .collect::<HashSet<_>>();
9535 let mut pending_names = HashSet::default();
9536 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9537 let mut cursor = prepared.tree().walk();
9542 let mut pending_nodes = vec![prepared.tree().root_node()];
9543 while let Some(node) = pending_nodes.pop() {
9544 if matches!(
9545 node.kind(),
9546 "identifier"
9547 | "type_identifier"
9548 | "field_identifier"
9549 | "namespace_identifier"
9550 ) {
9551 pending_names.insert(node_text(node, prepared.source()).to_string());
9552 }
9553 if node.kind() == "preproc_arg" {
9554 for reference in
9555 object_macro_replacement_type_references(node, prepared.source())
9556 {
9557 pending_names.extend(reference.components);
9558 }
9559 }
9560 pending_nodes.extend(node.named_children(&mut cursor));
9561 }
9562 }
9563 stats.root_names += pending_names.len();
9564 let mut completed_names = HashSet::default();
9565 while !pending_names.is_empty() {
9566 stats.rounds += 1;
9567 let round_names = std::mem::take(&mut pending_names);
9568 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9569 HashMap::default();
9570 let mut identifiers = Vec::new();
9571 for identifier in round_names {
9572 if !completed_names.insert(identifier.clone())
9573 || cancellation.is_some_and(CancellationToken::is_cancelled)
9574 {
9575 continue;
9576 }
9577 identifiers.push(identifier);
9578 }
9579 let missing_identifiers = identifiers
9580 .iter()
9581 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9582 .cloned()
9583 .collect::<HashSet<_>>();
9584 if !missing_identifiers.is_empty() {
9585 let lookup_started = Instant::now();
9586 let mut candidates = cpp
9587 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9588 stats.lookup_elapsed += lookup_started.elapsed();
9589 stats.identifier_lookups += missing_identifiers.len();
9590 stats.identifier_batches += 1;
9591 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9592 break;
9593 }
9594 for identifier in missing_identifiers {
9595 let units = candidates.remove(&identifier).unwrap_or_default();
9596 let candidate_count = units.len();
9597 let candidate_sources = units
9598 .into_iter()
9599 .map(|unit| unit.source().clone())
9600 .collect::<HashSet<_>>();
9601 candidates_by_identifier
9602 .insert(identifier, (candidate_sources, candidate_count));
9603 }
9604 }
9605 for identifier in identifiers {
9606 let (candidate_sources, candidate_count) = candidates_by_identifier
9607 .get(&identifier)
9608 .expect("every missing identifier was inserted after the batch lookup");
9609 stats.candidate_units += *candidate_count;
9610 for source in candidate_sources.iter().cloned() {
9611 if source != *root
9612 && visible_sources
9613 .get(root)
9614 .is_some_and(|files| files.contains(&source))
9615 {
9616 requested_names_by_source
9617 .entry(source)
9618 .or_default()
9619 .insert(identifier.clone());
9620 }
9621 }
9622 }
9623 stats.candidate_sources += requested_names_by_source.len();
9624 for (source, requested_names) in requested_names_by_source {
9625 let declarations = declarations_by_source_and_reading
9626 .entry((source.clone(), reading_is_c))
9627 .or_insert_with(|| {
9628 bounded_visibility_declarations(
9629 cpp,
9630 analyzer,
9631 &source,
9632 reading_is_c,
9633 stats,
9634 )
9635 });
9636 let mut selected = HashSet::default();
9637 for name in requested_names {
9638 if let Some(units) = declarations.by_identifier.get(&name) {
9639 selected.extend(units.iter().cloned());
9640 }
9641 }
9642 for unit in selected {
9643 stats.selected_units += 1;
9644 let dependency_names = dependency_names_by_unit
9645 .entry(unit.clone())
9646 .or_insert_with(|| {
9647 let facts_started = Instant::now();
9648 let mut dependency_names = HashSet::default();
9649 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9650 for fact in facts {
9651 for name in fact.dependency_type_names {
9652 stats.dependency_source_names += 1;
9653 #[cfg(any(test, feature = "test-support"))]
9654 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9655 .with(|count| count.set(count.get() + 1));
9656 dependency_names.insert(name);
9657 }
9658 }
9659 stats.dependency_source_elapsed += facts_started.elapsed();
9660 }
9661 dependency_names
9662 });
9663 for name in dependency_names.iter() {
9664 if !completed_names.contains(name) && pending_names.insert(name.clone())
9665 {
9666 stats.dependency_names += 1;
9667 }
9668 }
9669 visible.insert(unit);
9670 }
9671 }
9672 }
9673 (root.clone(), visible)
9674 })
9675 .collect()
9676}
9677
9678struct BoundedVisibilityDeclarations {
9679 all: Vec<CodeUnit>,
9680 by_identifier: HashMap<String, Vec<CodeUnit>>,
9681}
9682
9683fn bounded_visibility_declarations(
9684 cpp: &dyn CppSource,
9685 analyzer: &CppGraphSource<'_>,
9686 file: &ProjectFile,
9687 c_semantics: bool,
9688 stats: &mut BoundedVisibilityStats,
9689) -> BoundedVisibilityDeclarations {
9690 let declarations_started = Instant::now();
9691 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9692 .into_iter()
9693 .collect::<Vec<_>>();
9694 stats.declaration_elapsed += declarations_started.elapsed();
9695 stats.declaration_reads += 1;
9696 stats.declaration_units += all.len();
9697
9698 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9699 for unit in &all {
9700 by_identifier
9701 .entry(unit.identifier().to_string())
9702 .or_default()
9703 .push(unit.clone());
9704 if unit.is_class()
9705 && let Some(metadata) = cpp.template_metadata(unit)
9706 && metadata.primary_name != unit.identifier()
9707 {
9708 by_identifier
9709 .entry(metadata.primary_name)
9710 .or_default()
9711 .push(unit.clone());
9712 }
9713 }
9714 BoundedVisibilityDeclarations { all, by_identifier }
9715}
9716
9717#[derive(Default)]
9718struct BoundedVisibilityStats {
9719 rounds: usize,
9720 root_names: usize,
9721 identifier_lookups: usize,
9722 identifier_batches: usize,
9723 candidate_units: usize,
9724 candidate_sources: usize,
9725 declaration_reads: usize,
9726 declaration_units: usize,
9727 selected_units: usize,
9728 dependency_source_names: usize,
9729 dependency_names: usize,
9730 lookup_elapsed: Duration,
9731 declaration_elapsed: Duration,
9732 dependency_source_elapsed: Duration,
9733}
9734
9735fn bounded_visibility_declarations_in_reading(
9736 analyzer: &CppGraphSource<'_>,
9737 file: &ProjectFile,
9738 c_semantics: bool,
9739) -> BTreeSet<CodeUnit> {
9740 #[cfg(any(test, feature = "test-support"))]
9741 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9742 analyzer.declarations_in_reading(file, c_semantics)
9743}
9744
9745#[cfg(any(test, feature = "test-support"))]
9746pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9747 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9748}
9749
9750#[cfg(any(test, feature = "test-support"))]
9751pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9752 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9753}
9754
9755#[cfg(any(test, feature = "test-support"))]
9756pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9757 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9758}
9759
9760#[cfg(any(test, feature = "test-support"))]
9761pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9762 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9763}
9764
9765pub struct VisibilityData {
9766 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9767 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9768}
9769
9770pub fn build_visibility_data<F, R, D>(
9780 roots: &HashSet<ProjectFile>,
9781 cancellation: Option<&CancellationToken>,
9782 mut targets_for: F,
9783 mut reading_is_c_for: R,
9784 mut declarations_for: D,
9785) -> VisibilityData
9786where
9787 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9788 R: FnMut(&ProjectFile) -> bool,
9789 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9790{
9791 let mut include_graph = IncludeGraph::default();
9792 for file in roots {
9793 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9794 break;
9795 }
9796 include_graph.extend_with(file, cancellation, &mut targets_for);
9797 }
9798 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9799 .files()
9800 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9801 .map(|file| (file.clone(), declarations_for(file, false)))
9802 .collect();
9803 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9804 let mut visible_by_file = HashMap::default();
9805 let mut visible_source_files_by_root = HashMap::default();
9806 for file in roots {
9807 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9808 break;
9809 }
9810 let mut visited = HashSet::default();
9811 let mut visible = HashSet::default();
9812 let declarations_by_file = if reading_is_c_for(file) {
9813 for reached in cpp_declarations_by_file.keys() {
9814 if !c_declarations_by_file.contains_key(reached) {
9815 let declarations = declarations_for(reached, true);
9816 c_declarations_by_file.insert(reached.clone(), declarations);
9817 }
9818 }
9819 &c_declarations_by_file
9820 } else {
9821 &cpp_declarations_by_file
9822 };
9823 collect_visible_declarations(
9824 &include_graph,
9825 declarations_by_file,
9826 file,
9827 &mut visited,
9828 &mut visible,
9829 cancellation,
9830 );
9831 visible_by_file.insert(file.clone(), visible);
9832 visible_source_files_by_root.insert(file.clone(), visited);
9833 }
9834 VisibilityData {
9835 visible_by_file,
9836 visible_source_files_by_root,
9837 }
9838}
9839
9840#[derive(Default)]
9859struct OutOfLineOwnerBindingStats {
9860 unseen_owners: usize,
9861 definition_lookups: usize,
9862 admitted: usize,
9863}
9864
9865fn extend_with_out_of_line_owner_bindings(
9866 cpp: &dyn CppSource,
9867 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9868) -> OutOfLineOwnerBindingStats {
9869 let mut stats = OutOfLineOwnerBindingStats::default();
9870 for (file, visible) in visible_by_file.iter_mut() {
9871 let mut unseen_owners: HashSet<String> = visible
9875 .iter()
9876 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9877 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9878 .collect();
9879 if unseen_owners.is_empty() {
9880 continue;
9881 }
9882 for unit in visible.iter().filter(|unit| unit.is_class()) {
9883 unseen_owners.remove(&unit.fq_name());
9884 }
9885 stats.unseen_owners += unseen_owners.len();
9886 stats.definition_lookups += unseen_owners.len();
9887 let admitted = unseen_owners
9888 .iter()
9889 .flat_map(|owner| cpp.definitions(owner))
9890 .filter(CodeUnit::is_class)
9891 .collect::<Vec<_>>();
9892 stats.admitted += admitted.len();
9893 visible.extend(admitted);
9894 }
9895 stats
9896}
9897
9898pub enum VisibleMemberResolution {
9899 Callable(Vec<CodeUnit>),
9900 NonCallable,
9901 AmbiguousKind,
9902 Missing,
9903}
9904
9905#[derive(Clone)]
9906pub enum EnclosingMemberOwnerResolution {
9907 Owner(CodeUnit),
9908 Ambiguous,
9909 Missing,
9910}
9911
9912pub fn resolve_declaring_member_owner(
9913 analyzer: &CppGraphSource<'_>,
9914 visibility: &VisibilityIndex<'_>,
9915 file: &ProjectFile,
9916 receiver_owner: &CodeUnit,
9917 member_name: &str,
9918) -> EnclosingMemberOwnerResolution {
9919 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9920 return EnclosingMemberOwnerResolution::Missing;
9921 };
9922 let Some(receiver_owner) =
9923 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9924 else {
9925 return EnclosingMemberOwnerResolution::Ambiguous;
9926 };
9927 let resolve_level = |frontier: &[CodeUnit]| {
9928 let mut member_owners = Vec::new();
9929 for raw_owner in frontier {
9930 let Some(owner) =
9931 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9932 else {
9933 return EnclosingMemberOwnerResolution::Ambiguous;
9934 };
9935 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9936 if !member.is_field() && !member.is_function() {
9941 continue;
9942 }
9943 let Some(member_owner) = type_owner_of(analyzer, member) else {
9944 return EnclosingMemberOwnerResolution::Ambiguous;
9945 };
9946 if !member_owners
9947 .iter()
9948 .any(|existing| same_visible_symbol(existing, &member_owner))
9949 {
9950 member_owners.push(member_owner);
9951 }
9952 }
9953 }
9954 match member_owners.len() {
9955 0 => EnclosingMemberOwnerResolution::Missing,
9956 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9957 _ => EnclosingMemberOwnerResolution::Ambiguous,
9958 }
9959 };
9960 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9964 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9965 return direct;
9966 }
9967 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9968 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9969 let mut path_matches = Vec::new();
9970 while let Some(raw_owner) = stack.pop() {
9971 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9972 else {
9973 return EnclosingMemberOwnerResolution::Ambiguous;
9974 };
9975 let propagated = propagated_counts.entry(owner.clone()).or_default();
9979 if *propagated == 2 {
9980 continue;
9981 }
9982 *propagated += 1;
9983 match resolve_level(std::slice::from_ref(&owner)) {
9984 EnclosingMemberOwnerResolution::Owner(owner) => {
9985 path_matches.push(owner);
9986 if path_matches.len() == 2 {
9987 return EnclosingMemberOwnerResolution::Ambiguous;
9988 }
9989 }
9990 EnclosingMemberOwnerResolution::Ambiguous => {
9991 return EnclosingMemberOwnerResolution::Ambiguous;
9992 }
9993 EnclosingMemberOwnerResolution::Missing => {
9994 stack.extend(hierarchy.get_direct_ancestors(&owner));
9995 }
9996 }
9997 }
9998 match path_matches.len() {
9999 0 => EnclosingMemberOwnerResolution::Missing,
10000 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10001 _ => unreachable!("base-path matches are capped at one before returning"),
10002 }
10003}
10004
10005pub fn resolve_declaring_callable_owner(
10020 analyzer: &CppGraphSource<'_>,
10021 visibility: &VisibilityIndex<'_>,
10022 file: &ProjectFile,
10023 ordinary: EnclosingMemberOwnerResolution,
10024 member_name: &str,
10025 call_arity: usize,
10026) -> EnclosingMemberOwnerResolution {
10027 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10028 return ordinary;
10029 };
10030 if visibility
10031 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10032 .into_iter()
10033 .any(|unit| {
10034 unit.is_function()
10035 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10036 })
10037 {
10038 return ordinary;
10039 }
10040
10041 let mut pending = match member_using_declaration_bases(
10042 analyzer,
10043 visibility,
10044 file,
10045 ordinary_owner,
10046 member_name,
10047 ) {
10048 Ok(bases) => bases,
10049 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10050 };
10051 let mut visited = HashSet::default();
10052 let mut introduced_owners = Vec::new();
10053 while let Some(owner) = pending.pop() {
10054 if !visited.insert(owner.clone()) {
10055 continue;
10056 }
10057 let accepts_arity = visibility
10058 .visible_members_for_owner_name(file, &owner, member_name)
10059 .into_iter()
10060 .any(|unit| {
10061 unit.is_function()
10062 && cpp_callable_arity(analyzer, unit)
10063 .is_none_or(|known| known.accepts(call_arity))
10064 });
10065 if accepts_arity {
10066 if !introduced_owners
10067 .iter()
10068 .any(|existing| same_visible_symbol(existing, &owner))
10069 {
10070 introduced_owners.push(owner);
10071 }
10072 continue;
10073 }
10074 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10075 Ok(bases) => pending.extend(bases),
10076 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10077 }
10078 }
10079 match introduced_owners.as_slice() {
10080 [] => ordinary,
10081 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10082 _ => EnclosingMemberOwnerResolution::Ambiguous,
10083 }
10084}
10085
10086fn member_using_declaration_bases(
10087 analyzer: &CppGraphSource<'_>,
10088 visibility: &VisibilityIndex<'_>,
10089 file: &ProjectFile,
10090 owner: &CodeUnit,
10091 member_name: &str,
10092) -> Result<Vec<CodeUnit>, ()> {
10093 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10094 return Err(());
10095 };
10096 let scopes: Vec<_> = facts
10097 .iter()
10098 .flat_map(|fact| &fact.member_usings)
10099 .filter(|using| using.member == member_name)
10100 .map(|using| using.scope.join("::"))
10101 .collect();
10102 if scopes.is_empty() {
10103 return Ok(Vec::new());
10104 }
10105 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10106 return Ok(Vec::new());
10107 };
10108 let mut bases = Vec::new();
10109 for raw_ancestor in hierarchy.get_ancestors(owner) {
10110 let Some(ancestor) =
10111 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10112 else {
10113 return Err(());
10114 };
10115 let qualified = cpp_name_for(&ancestor);
10116 if scopes
10117 .iter()
10118 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10119 && !bases
10120 .iter()
10121 .any(|existing| same_visible_symbol(existing, &ancestor))
10122 {
10123 bases.push(ancestor);
10124 }
10125 }
10126 Ok(bases)
10127}
10128
10129pub fn lexical_component_tiers<'a>(
10130 components: &'a [String],
10131 global: bool,
10132 lexical_scope: &'a [String],
10133) -> impl Iterator<Item = Vec<String>> + 'a {
10134 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10135 (0..=first_prefix_len).rev().map(move |prefix_len| {
10136 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10137 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10138 qualified.extend_from_slice(components);
10139 qualified
10140 })
10141}
10142
10143pub fn build_visible_identifier_index(
10144 analyzer: &CppGraphSource<'_>,
10145 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10146 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10147 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10148) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10149 let mut out = HashMap::default();
10150 for (file, visible) in visible_by_file {
10151 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10152 for unit in visible {
10153 if unit.is_field()
10154 && !visible_source_files_by_root
10155 .get(file)
10156 .is_some_and(|sources| sources.contains(unit.source()))
10157 && cpp_global_field_has_internal_linkage_cached(
10158 analyzer,
10159 global_field_internal_linkage,
10160 unit,
10161 )
10162 {
10163 continue;
10164 }
10165 by_identifier
10166 .entry(unit.identifier().to_string())
10167 .or_default()
10168 .push(unit.clone());
10169 }
10170 for units in by_identifier.values_mut() {
10171 sort_lookup_units(units);
10172 units.dedup();
10173 }
10174 out.insert(file.clone(), by_identifier);
10175 }
10176 out
10177}
10178
10179fn sort_lookup_units(units: &mut [CodeUnit]) {
10180 units.sort_by(|left, right| {
10181 left.fq_name()
10182 .cmp(&right.fq_name())
10183 .then_with(|| left.signature().cmp(&right.signature()))
10184 .then_with(|| left.source().cmp(right.source()))
10185 .then_with(|| left.kind().cmp(&right.kind()))
10186 .then_with(|| {
10187 left.package_segment_count()
10188 .cmp(&right.package_segment_count())
10189 })
10190 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10191 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10192 });
10193}
10194
10195fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10196 let interner = segment_interner();
10197 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10198 let (left_text, left_kind) = interner.resolve(left_id);
10199 let (right_text, right_kind) = interner.resolve(right_id);
10200 let order = left_text
10201 .cmp(right_text)
10202 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10203 if order != CmpOrdering::Equal {
10204 return order;
10205 }
10206 }
10207 left.len().cmp(&right.len())
10208}
10209
10210const fn segment_kind_order(kind: SegmentKind) -> u8 {
10211 match kind {
10212 SegmentKind::Path => 0,
10213 SegmentKind::Package => 1,
10214 SegmentKind::Type => 2,
10215 SegmentKind::Companion => 3,
10216 SegmentKind::Nested => 4,
10217 SegmentKind::Member => 5,
10218 SegmentKind::Unknown => 6,
10219 }
10220}
10221
10222fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10223 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10224 std::mem::take(units),
10225 );
10226}
10227
10228pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10229 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10233 brokk_bifrost_core::analyzer::Language::Cpp,
10234 reference,
10235 );
10236 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10237}
10238
10239fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10240 if parts.is_empty() {
10241 return Vec::new();
10242 }
10243
10244 let mut candidates = Vec::new();
10245 for package_len in 0..parts.len() {
10246 let package = parts[..package_len].join("::");
10247 let rest = &parts[package_len..];
10248 if rest.is_empty() {
10249 continue;
10250 }
10251 match kind {
10252 TargetKind::Type | TargetKind::Constructor => {
10253 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10254 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10255 }
10256 TargetKind::FreeFunction
10257 | TargetKind::Method
10258 | TargetKind::GlobalField
10259 | TargetKind::MemberField
10260 | TargetKind::Macro => {
10261 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10262 if rest.len() > 1 {
10263 let owner = rest[..rest.len() - 1].join("$");
10264 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10265 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10266 }
10267 }
10268 }
10269 }
10270 candidates
10271}
10272
10273fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10274 let fqn = if package.is_empty() {
10275 short.to_string()
10276 } else {
10277 format!("{package}.{short}")
10278 };
10279 if !out.contains(&fqn) {
10280 out.push(fqn);
10281 }
10282}
10283
10284pub fn infer_cpp_initializer_type(
10285 analyzer: &CppGraphSource<'_>,
10286 visibility: &VisibilityIndex<'_>,
10287 file: &ProjectFile,
10288 source: &str,
10289 node: Node<'_>,
10290) -> Option<CodeUnit> {
10291 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10292 .and_then(|binding| binding.unit)
10293}
10294
10295pub fn infer_cpp_initializer_binding(
10296 analyzer: &CppGraphSource<'_>,
10297 visibility: &VisibilityIndex<'_>,
10298 file: &ProjectFile,
10299 source: &str,
10300 node: Node<'_>,
10301 receiver_resolver: Option<&ReceiverResolver<'_>>,
10302) -> Option<CppScanBinding> {
10303 match node.kind() {
10304 "new_expression" => {
10305 let type_node = cpp_new_expression_type_node(node)?;
10306 let type_text = node_text(type_node, source);
10307 let name = normalize_cpp_type_name(type_text);
10308 Some(CppScanBinding::from_type_name(
10309 name.clone(),
10310 visibility.resolve_type(file, &name),
10311 1,
10312 ))
10313 }
10314 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10315 if function.kind() == "field_expression" {
10323 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10324 return resolve_field_method_call_return_binding(
10325 analyzer,
10326 visibility,
10327 file,
10328 source,
10329 function,
10330 arity,
10331 receiver_resolver,
10332 );
10333 }
10334 let function_text = node_text(function, source);
10335 let direct_type_binding = visibility
10336 .resolve_type(file, function_text)
10337 .map(|unit| CppScanBinding::from_unit(unit, 0));
10338 if function.kind() == "template_function" && direct_type_binding.is_some() {
10339 let lexical_namespace = enclosing_namespace_context(node, source);
10340 let arity = visibility.call_arity_evidence(file, node, source).exact();
10341 if let Some(arity) = arity
10342 && let Some(binding) = visibility.resolve_call_return_binding(
10343 analyzer,
10344 file,
10345 function_text,
10346 arity,
10347 lexical_namespace.as_deref(),
10348 direct_type_binding
10349 .as_ref()
10350 .and_then(|binding| binding.unit.as_ref()),
10351 )
10352 {
10353 return Some(binding);
10354 }
10355 let (has_callable, callable_binding) = visibility
10356 .resolve_call_return_binding_without_arity(
10357 analyzer,
10358 file,
10359 function_text,
10360 lexical_namespace.as_deref(),
10361 direct_type_binding
10362 .as_ref()
10363 .and_then(|binding| binding.unit.as_ref()),
10364 );
10365 if let Some(binding) = callable_binding {
10366 return Some(binding);
10367 }
10368 if has_callable {
10369 return None;
10370 }
10371 return direct_type_binding;
10372 }
10373 let arity = visibility.call_arity_evidence(file, node, source).exact();
10378 if let Some(arity) = arity {
10379 let direct_type_binding_for_call = direct_type_binding.clone();
10380 if let Some(binding) = resolve_static_method_call_return_binding(
10381 analyzer, visibility, file, source, function, arity,
10382 )
10383 .or_else(|| {
10384 visibility.resolve_call_return_binding(
10389 analyzer,
10390 file,
10391 function_text,
10392 arity,
10393 enclosing_namespace_context(node, source).as_deref(),
10394 direct_type_binding_for_call
10395 .as_ref()
10396 .and_then(|binding| binding.unit.as_ref()),
10397 )
10398 }) {
10399 return Some(binding);
10400 }
10401 }
10402 direct_type_binding
10403 }),
10404 _ => None,
10405 }
10406}
10407
10408pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10414 debug_assert_eq!(expression.kind(), "new_expression");
10415 if let Some(type_node) = expression.child_by_field_name("type") {
10416 return Some(type_node);
10417 }
10418
10419 let mut pending = Vec::new();
10420 let mut cursor = expression.walk();
10421 pending.extend(
10422 expression
10423 .named_children(&mut cursor)
10424 .filter(|child| {
10425 !matches!(
10426 child.kind(),
10427 "argument_list" | "initializer_list" | "new_declarator"
10428 )
10429 })
10430 .collect::<Vec<_>>()
10431 .into_iter()
10432 .rev(),
10433 );
10434 while let Some(node) = pending.pop() {
10435 if is_cpp_type_specifier_node(node) {
10436 return Some(node);
10437 }
10438 if node.kind() == "ERROR" {
10439 let mut cursor = node.walk();
10440 pending.extend(
10441 node.named_children(&mut cursor)
10442 .collect::<Vec<_>>()
10443 .into_iter()
10444 .rev(),
10445 );
10446 }
10447 }
10448 None
10449}
10450
10451fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10452 matches!(
10453 node.kind(),
10454 "class_specifier"
10455 | "decltype"
10456 | "dependent_type"
10457 | "enum_specifier"
10458 | "placeholder_type_specifier"
10459 | "primitive_type"
10460 | "qualified_identifier"
10461 | "sized_type_specifier"
10462 | "struct_specifier"
10463 | "template_type"
10464 | "type_identifier"
10465 | "union_specifier"
10466 )
10467}
10468
10469fn resolve_static_method_call_return_binding(
10470 analyzer: &CppGraphSource<'_>,
10471 visibility: &VisibilityIndex<'_>,
10472 file: &ProjectFile,
10473 source: &str,
10474 function: Node<'_>,
10475 arity: usize,
10476) -> Option<CppScanBinding> {
10477 if function.kind() != "qualified_identifier" {
10478 return None;
10479 }
10480 let qualified = normalize_cpp_reference_text(node_text(function, source));
10481 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10488 brokk_bifrost_core::analyzer::Language::Cpp,
10489 &qualified,
10490 );
10491 let (owner_text, member_name) = match parts.split_last() {
10492 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10493 (owner_parts.join("::"), member.clone())
10494 }
10495 _ => {
10496 let scope = function.child_by_field_name("scope")?;
10497 let name = function.child_by_field_name("name")?;
10498 (
10499 node_text(scope, source).to_string(),
10500 node_text(name, source).to_string(),
10501 )
10502 }
10503 };
10504 let owner = visibility.resolve_type(file, &owner_text)?;
10505 let candidates =
10506 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10507 unanimous_return_binding(analyzer, visibility, file, &candidates)
10508}
10509
10510fn resolve_field_method_call_return_binding(
10511 analyzer: &CppGraphSource<'_>,
10512 visibility: &VisibilityIndex<'_>,
10513 file: &ProjectFile,
10514 source: &str,
10515 function: Node<'_>,
10516 arity: usize,
10517 receiver_resolver: Option<&ReceiverResolver<'_>>,
10518) -> Option<CppScanBinding> {
10519 debug_assert_eq!(
10520 function.kind(),
10521 "field_expression",
10522 "the member-call return binding answers only for a field-expression callee"
10523 );
10524 let receiver_resolver = receiver_resolver?;
10525 let field = function.child_by_field_name("field")?;
10526 let member_name = node_text(function_terminal_node(field), source);
10527 let receiver = function
10528 .child_by_field_name("argument")
10529 .or_else(|| function.named_child(0))?;
10530 let owners = receiver_resolver(receiver, source);
10531 let mut candidates = Vec::new();
10532 for owner in owners {
10533 let declaring_owner =
10534 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10535 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10536 EnclosingMemberOwnerResolution::Missing => continue,
10537 EnclosingMemberOwnerResolution::Ambiguous => return None,
10538 };
10539 candidates.extend(callable_members_accepting_arity(
10540 analyzer,
10541 visibility,
10542 file,
10543 &declaring_owner,
10544 member_name,
10545 arity,
10546 ));
10547 }
10548 unanimous_return_binding(analyzer, visibility, file, &candidates)
10549}
10550
10551fn callable_members_accepting_arity(
10552 analyzer: &CppGraphSource<'_>,
10553 visibility: &VisibilityIndex<'_>,
10554 file: &ProjectFile,
10555 owner: &CodeUnit,
10556 member_name: &str,
10557 arity: usize,
10558) -> Vec<CodeUnit> {
10559 visibility
10560 .visible_members_for_owner_name(file, owner, member_name)
10561 .into_iter()
10562 .filter(|unit| {
10563 unit.is_function()
10564 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10565 })
10566 .cloned()
10567 .collect()
10568}
10569
10570fn unanimous_return_binding(
10571 analyzer: &CppGraphSource<'_>,
10572 visibility: &VisibilityIndex<'_>,
10573 file: &ProjectFile,
10574 candidates: &[CodeUnit],
10575) -> Option<CppScanBinding> {
10576 let mut resolved_return: Option<CppScanBinding> = None;
10577 for function in candidates {
10578 let metadata = analyzer.signature_metadata(function);
10579 let return_types = if metadata.is_empty() {
10580 vec![cpp_function_return_type_text(analyzer, function)?]
10581 } else {
10582 metadata
10583 .iter()
10584 .map(|metadata| metadata.return_type_text().map(str::to_string))
10585 .collect::<Option<Vec<_>>>()?
10586 };
10587 for return_text in return_types {
10588 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10589 let name = normalize_cpp_type_name(&return_text);
10590 let binding = CppScanBinding::from_type_name(
10591 name.clone(),
10592 visibility
10593 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10594 indirection,
10595 );
10596 if let Some(existing) = resolved_return.as_ref()
10597 && (existing.indirection != binding.indirection
10598 || match (&existing.unit, &binding.unit) {
10599 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10600 (None, None) => existing.type_name != binding.type_name,
10601 (Some(_), None) | (None, Some(_)) => true,
10602 })
10603 {
10604 return None;
10605 }
10606 resolved_return = Some(binding);
10607 }
10608 }
10609 resolved_return
10610}
10611
10612fn aliases_from_canonical_source(
10613 cpp: &dyn CppSource,
10614 token: QueryToken<'_>,
10615 file: &ProjectFile,
10616) -> Option<Vec<CppAlias>> {
10617 let source = cpp.declaration_source_facts(token, file)?;
10618 Some(
10619 source
10620 .facts
10621 .declarations
10622 .iter()
10623 .filter_map(|fact| {
10624 let alias = fact.file_scope_alias.as_ref()?;
10625 Some(CppAlias {
10626 name: alias.name.clone(),
10627 target: alias.target.clone(),
10628 namespace: alias.namespace.clone(),
10629 })
10630 })
10631 .collect(),
10632 )
10633}
10634
10635pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10636 let mut current = node.parent();
10637 while let Some(parent) = current {
10638 match parent.kind() {
10639 "translation_unit"
10640 | "namespace_definition"
10641 | "declaration_list"
10642 | "linkage_specification" => current = parent.parent(),
10643 "template_declaration" => current = parent.parent(),
10644 _ => return false,
10645 }
10646 }
10647 true
10648}
10649
10650pub fn collect_include_closure(
10651 analyzer: &CppGraphSource<'_>,
10652 include_targets: &IncludeTargetIndex,
10653 file: &ProjectFile,
10654 out: &mut HashSet<ProjectFile>,
10655 cancellation: Option<&CancellationToken>,
10656) {
10657 let mut stack = vec![file.clone()];
10658 while let Some(file) = stack.pop() {
10659 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10660 break;
10661 }
10662 if !out.insert(file.clone()) {
10663 continue;
10664 }
10665 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10666 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10667 stack.push(target);
10668 }
10669 }
10670 }
10671}
10672
10673fn collect_visible_declarations(
10674 include_graph: &IncludeGraph,
10675 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10676 file: &ProjectFile,
10677 visited: &mut HashSet<ProjectFile>,
10678 out: &mut HashSet<CodeUnit>,
10679 cancellation: Option<&CancellationToken>,
10680) {
10681 let mut stack = vec![file.clone()];
10682 while let Some(file) = stack.pop() {
10683 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10684 break;
10685 }
10686 if !visited.insert(file.clone()) {
10687 continue;
10688 }
10689 if let Some(declarations) = declarations_by_file.get(&file) {
10690 out.extend(declarations.iter().cloned());
10691 }
10692 stack.extend(include_graph.targets(&file).iter().cloned());
10693 }
10694}
10695
10696fn parse_macro_parameter_list<T>(
10697 replacement: &str,
10698 read: impl FnOnce(Node<'_>, &str) -> T,
10699) -> Option<T> {
10700 let source = format!("void __bifrost_macro_parameters({replacement});");
10701 let mut parser = Parser::new();
10702 parser
10703 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10704 .ok()?;
10705 let tree = parser.parse(&source, None)?;
10706 let root = tree.root_node();
10707 if root.has_error() {
10708 return None;
10709 }
10710 let declaration = root.named_child(0)?;
10711 let declarator = declaration.child_by_field_name("declarator")?;
10712 let parameters = declarator.child_by_field_name("parameters")?;
10713 Some(read(parameters, &source))
10714}
10715
10716fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10717 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10718}
10719
10720pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10723 parse_macro_parameter_list(
10724 replacement,
10725 crate::declarations::cpp_callable_parameter_types,
10726 )
10727}
10728
10729pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10730 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10731}
10732
10733pub fn cpp_callable_parameter_types(
10734 analyzer: &CppGraphSource<'_>,
10735 unit: &CodeUnit,
10736) -> Option<Vec<String>> {
10737 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10738 analyzer.signature_metadata(unit),
10739 )
10740}
10741
10742fn merge_compatible_callable_arities(
10743 left: CallableArity,
10744 right: CallableArity,
10745) -> Option<CallableArity> {
10746 let total = left.total();
10747 let left_repeated = left.accepts(total.saturating_add(1));
10748 let right_repeated = right.accepts(right.total().saturating_add(1));
10749 if total != right.total() || left_repeated != right_repeated {
10750 return None;
10751 }
10752 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10753 Some(CallableArity::new(required, total, left_repeated))
10754}
10755
10756fn collect_macro_events(
10757 cpp: &dyn CppSource,
10758 token: QueryToken<'_>,
10759 file: &ProjectFile,
10760) -> Vec<MacroEvent> {
10761 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10762 return Vec::new();
10763 };
10764 let source = prepared.source();
10765 let mut events = Vec::new();
10766 let root = prepared.tree().root_node();
10767 let mut stack = vec![root];
10768 while let Some(node) = stack.pop() {
10769 match node.kind() {
10770 "preproc_def" | "preproc_function_def" => {
10771 let Some(name) = node.child_by_field_name("name") else {
10772 continue;
10773 };
10774 let name = node_text(name, source).to_string();
10775 events.push(MacroEvent::Define {
10776 name,
10777 binding: MacroBinding {
10778 source: file.clone(),
10779 declaration_byte: node.start_byte(),
10780 definition: VisibilityIndex::decode_macro_definition(node, source),
10781 exact: true,
10782 },
10783 byte: node.start_byte(),
10784 conditionals: owning_preprocessor_conditionals(root, node, source),
10785 });
10786 continue;
10787 }
10788 "preproc_include" => {
10789 let Some(path) = node.child_by_field_name("path") else {
10790 events.push(MacroEvent::Include {
10791 targets: Vec::new(),
10792 byte: node.start_byte(),
10793 conditionals: owning_preprocessor_conditionals(root, node, source),
10794 });
10795 continue;
10796 };
10797 let include = crate::imports::include_path_literal_value(path, source);
10798 let targets = include.map_or_else(Vec::new, |include| {
10799 resolve_include_targets_with_index(file, include, cpp.include_target_index())
10800 });
10801 if targets.is_empty()
10812 && include.is_some_and(|include| {
10813 !cpp.include_target_index().names_indexed_file(include)
10814 })
10815 {
10816 continue;
10817 }
10818 events.push(MacroEvent::Include {
10819 targets,
10820 byte: node.start_byte(),
10821 conditionals: owning_preprocessor_conditionals(root, node, source),
10822 });
10823 continue;
10824 }
10825 "preproc_call" => {
10826 let Some(directive) = node.child_by_field_name("directive") else {
10827 continue;
10828 };
10829 if node_text(directive, source) != "#undef" {
10830 continue;
10831 }
10832 let name = node
10833 .child_by_field_name("argument")
10834 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10835 if let Some(name) = name {
10836 events.push(MacroEvent::Undef {
10837 name,
10838 byte: node.start_byte(),
10839 conditionals: owning_preprocessor_conditionals(root, node, source),
10840 });
10841 } else {
10842 events.push(MacroEvent::Invalidate {
10843 byte: node.start_byte(),
10844 });
10845 }
10846 continue;
10847 }
10848 _ => {}
10849 }
10850 push_named_children_reversed(node, &mut stack);
10851 }
10852 events.sort_by_key(MacroEvent::byte);
10853 events
10854}
10855
10856fn find_include_activation(
10857 cpp: &dyn CppSource,
10858 token: QueryToken<'_>,
10859 file: &ProjectFile,
10860 prepared: &PreparedSyntaxTree,
10861 donor_source: &ProjectFile,
10862 language_guard_is_stable: &dyn Fn() -> bool,
10863) -> Option<usize> {
10864 let include_targets = cpp.include_target_index();
10865 let reference_is_c = file
10866 .rel_path()
10867 .extension()
10868 .and_then(|extension| extension.to_str())
10869 == Some("c");
10870 let mut direct_includes = Vec::new();
10871 let mut nodes = vec![prepared.tree().root_node()];
10872 while let Some(node) = nodes.pop() {
10875 if node.kind() == "preproc_include" {
10876 let Some(required_guards) = include_directive_guard_requirements(
10877 node,
10878 prepared.source(),
10879 reference_is_c,
10880 language_guard_is_stable,
10881 ) else {
10882 continue;
10883 };
10884 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10885 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10886 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10887 file,
10888 include,
10889 include_targets,
10890 )) {
10891 direct_includes.push((node.end_byte(), target));
10892 }
10893 }
10894 }
10895 continue;
10896 }
10897 push_named_children_reversed(node, &mut nodes);
10898 }
10899 direct_includes.sort_by_key(|(activation, _)| *activation);
10900 let mut known_missing = HashSet::default();
10901 direct_includes
10902 .into_iter()
10903 .find(|(_, direct)| {
10904 unconditional_include_reaches(
10905 cpp,
10906 token,
10907 direct,
10908 donor_source,
10909 file,
10910 &mut known_missing,
10911 language_guard_is_stable,
10912 )
10913 })
10914 .map(|(activation, _)| activation)
10915}
10916
10917fn find_conditional_include_projection_index(
10918 cpp: &dyn CppSource,
10919 token: QueryToken<'_>,
10920 file: &ProjectFile,
10921 prepared: &PreparedSyntaxTree,
10922 language_guard_is_stable: &dyn Fn() -> bool,
10923 on_state: &dyn Fn(),
10924) -> ConditionalIncludeProjectionIndex {
10925 let reference_is_c = reference_uses_c_semantics(cpp, file);
10926 let include_targets = cpp.include_target_index();
10927 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10928 HashMap::default();
10929 let mut pending = Vec::new();
10930 let mut nodes = vec![prepared.tree().root_node()];
10931 while let Some(node) = nodes.pop() {
10932 if node.kind() == "preproc_include" {
10933 let Some(required_guards) = include_directive_guard_requirements(
10934 node,
10935 prepared.source(),
10936 reference_is_c,
10937 language_guard_is_stable,
10938 ) else {
10939 continue;
10940 };
10941 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10942 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10943 file,
10944 include,
10945 include_targets,
10946 )) else {
10947 continue;
10948 };
10949 pending.push((target, node.end_byte(), required_guards.clone()));
10950 }
10951 continue;
10952 }
10953 push_named_children_reversed(node, &mut nodes);
10954 }
10955
10956 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10968 HashMap::default();
10969 while let Some((current_file, activation_byte, path)) = pending.pop() {
10970 let guard_sets = expanded
10971 .entry((current_file.clone(), activation_byte))
10972 .or_default();
10973 if guard_sets
10977 .iter()
10978 .any(|existing| existing.is_subset(&path.all))
10979 {
10980 continue;
10981 }
10982 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10983 .drain(..)
10984 .partition(|existing| path.all.is_subset(existing));
10985 *guard_sets = kept;
10986 guard_sets.push(path.all.clone());
10987 if !evicted.is_empty()
10988 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10989 {
10990 projections.retain(|projection| {
10991 projection.activation_byte != activation_byte
10992 || !evicted.contains(&projection.required_guards)
10993 });
10994 }
10995 on_state();
10996
10997 projections_by_source
11000 .entry(current_file.clone())
11001 .or_default()
11002 .push(ConditionalIncludeProjection {
11003 activation_byte,
11004 required_guards: path.all.clone(),
11005 partial_guards: path.partial.clone(),
11006 });
11007
11008 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11009 continue;
11010 };
11011 let mut nodes = vec![current_prepared.tree().root_node()];
11012 while let Some(node) = nodes.pop() {
11013 if node.kind() == "preproc_include" {
11014 let Some(include_guards) = include_directive_guard_requirements(
11015 node,
11016 current_prepared.source(),
11017 reference_is_c,
11018 language_guard_is_stable,
11019 ) else {
11020 continue;
11021 };
11022 let Some(reached) = path.merged(&include_guards) else {
11023 continue;
11024 };
11025 for (include, _) in
11026 include_path_from_node(node, current_prepared.source()).into_iter()
11027 {
11028 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11029 ¤t_file,
11030 include,
11031 include_targets,
11032 )) else {
11033 continue;
11034 };
11035 pending.push((target, activation_byte, reached.clone()));
11036 }
11037 continue;
11038 }
11039 push_named_children_reversed(node, &mut nodes);
11040 }
11041 }
11042
11043 projections_by_source
11044 .into_iter()
11045 .map(|(source, mut projections)| {
11046 projections.sort_by_key(|projection| projection.activation_byte);
11047 (source, Arc::from(projections))
11048 })
11049 .collect()
11050}
11051
11052#[allow(clippy::too_many_arguments)]
11058fn find_conditional_include_projection_for_source(
11059 cpp: &dyn CppSource,
11060 token: QueryToken<'_>,
11061 file: &ProjectFile,
11062 prepared: &PreparedSyntaxTree,
11063 donor_source: &ProjectFile,
11064 admission: IncludePathAdmission,
11065 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11066 reference_byte: usize,
11067 language_guard_is_stable: &dyn Fn() -> bool,
11068 on_state: &dyn Fn(),
11069) -> bool {
11070 let Some(reference_guards) = reference_guards else {
11071 return false;
11072 };
11073 let reference_is_c = reference_uses_c_semantics(cpp, file);
11074 let include_targets = cpp.include_target_index();
11075 let mut pending = Vec::new();
11076 let mut nodes = vec![prepared.tree().root_node()];
11077 while let Some(node) = nodes.pop() {
11078 if node.kind() == "preproc_include" {
11079 let Some(required_guards) = include_directive_guard_requirements(
11080 node,
11081 prepared.source(),
11082 reference_is_c,
11083 language_guard_is_stable,
11084 ) else {
11085 continue;
11086 };
11087 if node.end_byte() > reference_byte
11088 || !admission.admits(
11089 &required_guards.all,
11090 &required_guards.partial,
11091 Some(reference_guards),
11092 )
11093 {
11094 continue;
11095 }
11096 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11097 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11098 file,
11099 include,
11100 include_targets,
11101 )) else {
11102 continue;
11103 };
11104 if &target == donor_source {
11105 return true;
11106 }
11107 pending.push((target, required_guards.clone()));
11108 }
11109 continue;
11110 }
11111 push_named_children_reversed(node, &mut nodes);
11112 }
11113
11114 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11115 while let Some((current_file, path)) = pending.pop() {
11116 let guard_sets = expanded.entry(current_file.clone()).or_default();
11117 if guard_sets.contains(&path.all) {
11118 continue;
11119 }
11120 guard_sets.push(path.all.clone());
11121 on_state();
11122
11123 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11124 continue;
11125 };
11126 let mut nodes = vec![current_prepared.tree().root_node()];
11127 while let Some(node) = nodes.pop() {
11128 if node.kind() == "preproc_include" {
11129 let Some(include_guards) = include_directive_guard_requirements(
11130 node,
11131 current_prepared.source(),
11132 reference_is_c,
11133 language_guard_is_stable,
11134 ) else {
11135 continue;
11136 };
11137 let Some(reached) = path.merged(&include_guards) else {
11138 continue;
11139 };
11140 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11141 continue;
11142 }
11143 for (include, _) in
11144 include_path_from_node(node, current_prepared.source()).into_iter()
11145 {
11146 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11147 ¤t_file,
11148 include,
11149 include_targets,
11150 )) else {
11151 continue;
11152 };
11153 if &target == donor_source {
11154 return true;
11155 }
11156 pending.push((target, reached.clone()));
11157 }
11158 continue;
11159 }
11160 push_named_children_reversed(node, &mut nodes);
11161 }
11162 }
11163 false
11164}
11165
11166pub fn cpp_include_closure_reaches(
11179 cpp: &dyn CppSource,
11180 token: QueryToken<'_>,
11181 translation_unit: &ProjectFile,
11182 header: &ProjectFile,
11183) -> bool {
11184 let language_guard_stable = OnceCell::new();
11185 unconditional_include_reaches(
11186 cpp,
11187 token,
11188 translation_unit,
11189 header,
11190 translation_unit,
11191 &mut HashSet::default(),
11192 &|| {
11193 *language_guard_stable.get_or_init(|| {
11194 let mut macro_events =
11195 |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11196 include_language_guard_is_stable_with(
11197 cpp,
11198 token,
11199 translation_unit,
11200 &mut macro_events,
11201 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11202 collect_structured_include_facts(prepared)
11203 },
11204 )
11205 })
11206 },
11207 )
11208}
11209
11210fn unconditional_include_reaches(
11211 cpp: &dyn CppSource,
11212 token: QueryToken<'_>,
11213 first: &ProjectFile,
11214 donor_source: &ProjectFile,
11215 reference_file: &ProjectFile,
11216 known_missing: &mut HashSet<ProjectFile>,
11217 language_guard_is_stable: &dyn Fn() -> bool,
11218) -> bool {
11219 if first == donor_source {
11220 return true;
11221 }
11222 if known_missing.contains(first) {
11223 return false;
11224 }
11225 let reference_is_c = reference_file
11226 .rel_path()
11227 .extension()
11228 .and_then(|extension| extension.to_str())
11229 == Some("c");
11230 if let Some(reaches) =
11231 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11232 {
11233 return reaches;
11234 }
11235 let mut visited = HashSet::default();
11236 let mut files = vec![first.clone()];
11237 let include_targets = cpp.include_target_index();
11238 while let Some(file) = files.pop() {
11239 if file == *donor_source {
11240 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11241 return true;
11242 }
11243 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11244 continue;
11245 }
11246 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11247 continue;
11248 };
11249 let mut cursor = prepared.tree().walk();
11251 let mut nodes = vec![prepared.tree().root_node()];
11252 while let Some(node) = nodes.pop() {
11253 if node.kind() == "preproc_include" {
11254 let Some(required_guards) = include_directive_guard_requirements(
11255 node,
11256 prepared.source(),
11257 reference_is_c,
11258 language_guard_is_stable,
11259 ) else {
11260 continue;
11261 };
11262 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11263 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11264 {
11265 if let Some(target) = unique_include_target(
11266 resolve_include_targets_with_index(&file, include, include_targets),
11267 ) {
11268 files.push(target);
11269 }
11270 }
11271 }
11272 continue;
11273 }
11274 let first_pushed = nodes.len();
11275 nodes.extend(node.named_children(&mut cursor));
11276 nodes[first_pushed..].reverse();
11277 }
11278 }
11279 known_missing.extend(visited);
11280 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11281 false
11282}
11283
11284fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11285 cpp: &dyn CppSource,
11286 token: QueryToken<'_>,
11287 file: &ProjectFile,
11288 mut macro_events: MacroEvents,
11289 mut include_facts: IncludeFacts,
11290) -> bool
11291where
11292 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11293 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11294{
11295 if !crate::imports::is_cpp_translation_unit(file) {
11299 return false;
11300 }
11301 if !cpp.compile_contexts_for(file).is_empty()
11304 || cpp
11305 .reaching_translation_units(file)
11306 .iter()
11307 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11308 {
11309 return false;
11310 }
11311 let mut pending = vec![file.clone()];
11312 let mut visited = HashSet::default();
11313 while let Some(file) = pending.pop() {
11314 if !visited.insert(file.clone()) {
11315 continue;
11316 }
11317 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11318 return false;
11319 };
11320 if macro_events(&file).iter().any(|event| match event {
11321 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11322 name == "__cplusplus"
11323 }
11324 MacroEvent::Invalidate { .. } => true,
11325 MacroEvent::Include { targets, .. } => targets.is_empty(),
11326 }) {
11327 return false;
11328 }
11329 for fact in include_facts(&file, prepared.as_ref()).iter() {
11332 let targets =
11333 resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11334 if targets.is_empty() {
11335 return false;
11336 }
11337 pending.extend(targets);
11338 }
11339 }
11340 true
11341}
11342
11343fn declaration_guard_requirements(
11344 analyzer: &CppGraphSource<'_>,
11345 cpp: &dyn CppSource,
11346 candidate: &CodeUnit,
11347) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11348 declaration_guard_sites(analyzer, cpp, candidate, None)
11349 .into_iter()
11350 .map(|site| (site.byte, site.guards))
11351 .collect()
11352}
11353
11354struct DeclarationGuardSite {
11357 byte: usize,
11359 guards: HashSet<PreprocessorGuard>,
11361 completed_family_before_reference: bool,
11365}
11366
11367fn declaration_guard_sites(
11370 analyzer: &CppGraphSource<'_>,
11371 cpp: &dyn CppSource,
11372 candidate: &CodeUnit,
11373 reference_byte: Option<usize>,
11374) -> Vec<DeclarationGuardSite> {
11375 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11376 return Vec::new();
11377 };
11378 let root = prepared.tree().root_node();
11379 analyzer
11380 .declaration_source_occurrences(candidate)
11381 .into_iter()
11382 .flatten()
11383 .filter_map(|occurrence| {
11384 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11385 Some(DeclarationGuardSite {
11386 byte: occurrence.range.start_byte,
11387 guards,
11388 completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11389 root.descendant_for_byte_range(
11390 occurrence.range.start_byte,
11391 occurrence.range.end_byte,
11392 )
11393 .is_some_and(|node| {
11394 declaration_follows_completed_family_before_reference(
11395 node,
11396 prepared.source(),
11397 reference_byte,
11398 )
11399 })
11400 }),
11401 })
11402 })
11403 .collect()
11404}
11405
11406fn declaration_follows_completed_family_before_reference(
11425 node: Node<'_>,
11426 source: &str,
11427 reference_byte: usize,
11428) -> bool {
11429 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11430}
11431
11432pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11435 let mut cache = PreprocessorContextCache::default();
11436 callable_guard_completion_byte_with_cache(node, source, &mut cache)
11437}
11438
11439pub(crate) fn callable_guard_completion_byte_with_cache(
11440 node: Node<'_>,
11441 source: &str,
11442 cache: &mut PreprocessorContextCache,
11443) -> Option<usize> {
11444 let mut completion_byte = 0;
11445 let mut ancestor = node.parent();
11446 while let Some(conditional) = ancestor {
11447 if matches!(
11448 conditional.kind(),
11449 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11450 ) && !is_file_covering_include_guard(conditional, source)
11451 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11452 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11453 {
11454 let family = preprocessor_conditional_family_root(conditional);
11455 if !preprocessor_conditional_family_has_terminal_else(family, cache) {
11456 return None;
11457 }
11458 completion_byte = completion_byte.max(family.end_byte());
11459 }
11460 ancestor = conditional.parent();
11461 }
11462 Some(completion_byte)
11463}
11464
11465fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11466 analyzer
11467 .ranges(candidate)
11468 .into_iter()
11469 .map(|range| range.start_byte)
11470 .min()
11471}
11472
11473fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11479 let (first, rest) = contexts.split_first()?;
11480 Some(
11481 first
11482 .defined_macros
11483 .iter()
11484 .filter(|name| {
11485 rest.iter()
11486 .all(|context| context.defined_macros.contains(*name))
11487 })
11488 .cloned()
11489 .collect(),
11490 )
11491}
11492
11493pub fn guard_requirements_hold_at_reference(
11494 required: &HashSet<PreprocessorGuard>,
11495 reference: Option<&HashSet<PreprocessorGuard>>,
11496) -> bool {
11497 reference.is_some_and(|active| {
11498 required
11499 .iter()
11500 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11501 })
11502}
11503
11504fn preprocessor_guard_holds_at_reference(
11505 required: &PreprocessorGuard,
11506 active: &HashSet<PreprocessorGuard>,
11507) -> bool {
11508 if active.contains(required) {
11509 return true;
11510 }
11511 let active_expression = BooleanGuardExpression::all(
11512 active
11513 .iter()
11514 .filter_map(PreprocessorGuard::as_boolean_expression),
11515 );
11516 required
11517 .as_boolean_expression()
11518 .is_some_and(|required| active_expression.implies(&required))
11519}
11520
11521fn guards_compatible_at_reference(
11526 declaration: &HashSet<PreprocessorGuard>,
11527 reference: Option<&HashSet<PreprocessorGuard>>,
11528) -> bool {
11529 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11530}
11531
11532pub fn preprocessor_conditional_family_range(
11541 root: Node<'_>,
11542 start_byte: usize,
11543 end_byte: usize,
11544) -> Option<(usize, usize)> {
11545 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11546 preprocessor_conditional_family_node(node)
11547 .map(|family| (family.start_byte(), family.end_byte()))
11548}
11549
11550pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11551 let mut ancestor = Some(node);
11552 while let Some(current) = ancestor {
11553 if is_cpp_preprocessor_conditional(current)
11554 && preprocessor_conditional_contains_descendant(current, node)
11555 {
11556 return Some(preprocessor_conditional_family_root(current));
11557 }
11558 ancestor = current.parent();
11559 }
11560 None
11561}
11562
11563pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11564 node: Node<'tree>,
11565 cache: &mut PreprocessorContextCache,
11566) -> Option<Node<'tree>> {
11567 let mut ancestor = node.parent();
11568 while let Some(current) = ancestor {
11569 if is_cpp_preprocessor_conditional(current)
11570 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11571 {
11572 let family = preprocessor_conditional_family_root(current);
11573 if preprocessor_conditional_family_has_terminal_else(family, cache) {
11574 return Some(family);
11575 }
11576 }
11577 ancestor = current.parent();
11578 }
11579 None
11580}
11581
11582fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11583 while let Some(parent) = conditional.parent() {
11584 let is_alternative = parent
11585 .child_by_field_name("alternative")
11586 .is_some_and(|alternative| {
11587 alternative.start_byte() == conditional.start_byte()
11588 && alternative.end_byte() == conditional.end_byte()
11589 });
11590 if !is_alternative {
11591 break;
11592 }
11593 conditional = parent;
11594 }
11595 conditional
11596}
11597
11598fn preprocessor_conditional_family_has_terminal_else(
11599 mut conditional: Node<'_>,
11600 cache: &mut PreprocessorContextCache,
11601) -> bool {
11602 loop {
11603 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11604 return cache
11605 .displaced_branch(conditional)
11606 .is_some_and(|directive| directive.is_else);
11607 };
11608 match alternative.kind() {
11609 "preproc_else" => return true,
11610 "preproc_elif" => conditional = alternative,
11611 _ => return false,
11612 }
11613 }
11614}
11615
11616fn include_directive_guard_requirements(
11629 node: Node<'_>,
11630 source: &str,
11631 reference_is_c: bool,
11632 language_guard_is_stable: &dyn Fn() -> bool,
11633) -> Option<PreprocessorGuardEnvironment> {
11634 let mut required = preprocessor_guard_environment_by_family(node, source)?;
11635 let excluded_by_language = required
11636 .all
11637 .iter()
11638 .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11639 if excluded_by_language {
11640 return None;
11641 }
11642 let is_language_guard = |guard: &PreprocessorGuard| {
11643 matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11644 if name == "__cplusplus")
11645 };
11646 if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11649 for guards in [&mut required.all, &mut required.partial] {
11650 guards.retain(|guard| !is_language_guard(guard));
11651 }
11652 }
11653 Some(required)
11654}
11655
11656fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11657 match guard {
11658 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11659 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11660 _ => false,
11661 }
11662}
11663
11664pub fn preprocessor_guard_environment(
11665 node: Node<'_>,
11666 source: &str,
11667) -> Option<HashSet<PreprocessorGuard>> {
11668 let mut cache = PreprocessorContextCache::default();
11669 preprocessor_guard_environment_with_cache(node, source, &mut cache)
11670}
11671
11672pub(crate) fn preprocessor_guard_environment_with_cache(
11673 node: Node<'_>,
11674 source: &str,
11675 cache: &mut PreprocessorContextCache,
11676) -> Option<HashSet<PreprocessorGuard>> {
11677 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11678 .map(|environment| environment.all)
11679}
11680
11681#[derive(Clone)]
11692struct PreprocessorGuardEnvironment {
11693 all: HashSet<PreprocessorGuard>,
11694 partial: HashSet<PreprocessorGuard>,
11695}
11696
11697impl PreprocessorGuardEnvironment {
11698 fn merged(&self, other: &Self) -> Option<Self> {
11702 Some(Self {
11703 all: merge_preprocessor_guards(&self.all, &other.all)?,
11704 partial: self.partial.union(&other.partial).cloned().collect(),
11705 })
11706 }
11707}
11708
11709fn preprocessor_guard_environment_by_family(
11710 node: Node<'_>,
11711 source: &str,
11712) -> Option<PreprocessorGuardEnvironment> {
11713 let mut cache = PreprocessorContextCache::default();
11714 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11715}
11716
11717fn preprocessor_guard_environment_by_family_with_cache(
11718 node: Node<'_>,
11719 source: &str,
11720 cache: &mut PreprocessorContextCache,
11721) -> Option<PreprocessorGuardEnvironment> {
11722 let mut all = HashSet::default();
11723 let mut partial = HashSet::default();
11724 let mut ancestor = node.parent();
11725 while let Some(conditional) = ancestor {
11726 if matches!(
11727 conditional.kind(),
11728 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11729 ) && !is_file_covering_include_guard(conditional, source)
11730 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11731 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11732 {
11733 let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
11734 match guard {
11735 PreprocessorGuard::Constant(true) => {
11736 ancestor = conditional.parent();
11737 continue;
11738 }
11739 PreprocessorGuard::Constant(false) => return None,
11740 _ => {}
11741 }
11742 if all.contains(&guard.negated()) {
11743 return None;
11744 }
11745 if !preprocessor_conditional_family_has_terminal_else(
11746 preprocessor_conditional_family_root(conditional),
11747 cache,
11748 ) {
11749 partial.insert(guard.clone());
11750 }
11751 all.insert(guard);
11752 }
11753 ancestor = conditional.parent();
11754 }
11755 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11756 match guard {
11757 PreprocessorGuard::Constant(true) => {}
11758 PreprocessorGuard::Constant(false) => return None,
11759 _ => {
11760 if all.contains(&guard.negated()) {
11761 return None;
11762 }
11763 partial.insert(guard.clone());
11767 all.insert(guard);
11768 }
11769 }
11770 }
11771 Some(PreprocessorGuardEnvironment { all, partial })
11772}
11773
11774pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11775 node: Node<'_>,
11776 source: &str,
11777 cache: &mut PreprocessorContextCache,
11778) -> Option<HashSet<PreprocessorGuard>> {
11779 let mut guards = HashSet::default();
11780 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11781 match guard {
11782 PreprocessorGuard::Constant(true) => {}
11783 PreprocessorGuard::Constant(false) => return None,
11784 _ => {
11785 if guards.contains(&guard.negated()) {
11786 return None;
11787 }
11788 guards.insert(guard);
11789 }
11790 }
11791 }
11792 Some(guards)
11793}
11794
11795fn callable_preprocessor_ancestor_guards(
11796 node: Node<'_>,
11797 source: &str,
11798) -> Option<Vec<PreprocessorGuard>> {
11799 let mut cache = PreprocessorContextCache::default();
11800 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11801}
11802
11803fn callable_preprocessor_ancestor_guards_with_cache(
11804 node: Node<'_>,
11805 source: &str,
11806 cache: &mut PreprocessorContextCache,
11807) -> Option<Vec<PreprocessorGuard>> {
11808 let mut guards = Vec::new();
11809 let mut ancestor = node.parent();
11810 while let Some(conditional) = ancestor {
11811 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11812 && !is_file_covering_include_guard(conditional, source)
11813 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11814 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11815 {
11816 guards.push(preprocessor_guard_for_descendant(
11817 conditional,
11818 node,
11819 source,
11820 cache,
11821 )?);
11822 }
11823 ancestor = conditional.parent();
11824 }
11825 Some(guards)
11826}
11827
11828fn fragmented_statement_preprocessor_guard(
11829 descendant: Node<'_>,
11830 source: &str,
11831) -> Option<PreprocessorGuard> {
11832 let mut ancestor = descendant.parent();
11838 while let Some(statement) = ancestor {
11839 if statement.kind() == "if_statement"
11840 && let (Some(consequence), Some(alternative)) = (
11841 statement.child_by_field_name("consequence"),
11842 statement.child_by_field_name("alternative"),
11843 )
11844 && alternative.start_byte() <= descendant.start_byte()
11845 && descendant.end_byte() <= alternative.end_byte()
11846 {
11847 let mut cursor = consequence.walk();
11848 let openers = consequence
11849 .named_children(&mut cursor)
11850 .filter(|child| {
11851 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11852 && child
11853 .child(child.child_count().saturating_sub(1))
11854 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11855 })
11856 .collect::<Vec<_>>();
11857 if openers.len() != 1 {
11858 ancestor = statement.parent();
11859 continue;
11860 }
11861
11862 let mut terminators = Vec::new();
11863 let mut stack = vec![alternative];
11864 while let Some(node) = stack.pop() {
11865 if node.kind() == "preproc_call"
11866 && node.start_byte() >= descendant.end_byte()
11867 && node
11868 .child_by_field_name("directive")
11869 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11870 {
11871 terminators.push(node);
11872 continue;
11873 }
11874 push_named_children_reversed(node, &mut stack);
11875 }
11876 if terminators.len() == 1 {
11877 return simple_preprocessor_guard(openers[0], source);
11878 }
11879 }
11880 ancestor = statement.parent();
11881 }
11882 None
11883}
11884
11885pub(crate) fn preprocessor_guard_for_descendant(
11886 conditional: Node<'_>,
11887 descendant: Node<'_>,
11888 source: &str,
11889 cache: &mut PreprocessorContextCache,
11890) -> Option<PreprocessorGuard> {
11891 let guard = simple_preprocessor_guard(conditional, source)?;
11892 match preprocessor_branch_of_descendant(conditional, descendant, cache) {
11893 PreprocessorBranch::Consequence => Some(guard),
11894 PreprocessorBranch::Structured(alternative)
11898 if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
11899 {
11900 Some(guard.negated())
11901 }
11902 PreprocessorBranch::DisplacedElse => Some(guard.negated()),
11903 PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
11904 }
11905}
11906
11907enum PreprocessorBranch<'tree> {
11909 Consequence,
11910 Structured(Node<'tree>),
11912 DisplacedElse,
11915 Unreadable,
11917}
11918
11919fn preprocessor_branch_of_descendant<'tree>(
11920 conditional: Node<'tree>,
11921 descendant: Node<'_>,
11922 cache: &mut PreprocessorContextCache,
11923) -> PreprocessorBranch<'tree> {
11924 if let Some(alternative) =
11925 conditional
11926 .child_by_field_name("alternative")
11927 .filter(|alternative| {
11928 alternative.start_byte() <= descendant.start_byte()
11929 && descendant.end_byte() <= alternative.end_byte()
11930 })
11931 {
11932 return PreprocessorBranch::Structured(alternative);
11933 }
11934 match cache.displaced_branch(conditional) {
11935 Some(directive) if directive.end_byte <= descendant.start_byte() => {
11936 if directive.is_else {
11937 PreprocessorBranch::DisplacedElse
11938 } else {
11939 PreprocessorBranch::Unreadable
11940 }
11941 }
11942 _ => PreprocessorBranch::Consequence,
11943 }
11944}
11945
11946pub(crate) fn preprocessor_conditional_contains_descendant(
11947 conditional: Node<'_>,
11948 descendant: Node<'_>,
11949) -> bool {
11950 cpp_displaced_preprocessor_boundary(conditional)
11951 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11952}
11953
11954fn preprocessor_conditional_contains_descendant_with_cache(
11955 conditional: Node<'_>,
11956 descendant: Node<'_>,
11957 cache: &mut PreprocessorContextCache,
11958) -> bool {
11959 cache
11960 .displaced_boundary_end(conditional)
11961 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11962}
11963
11964pub fn merge_preprocessor_guards(
11965 left: &HashSet<PreprocessorGuard>,
11966 right: &HashSet<PreprocessorGuard>,
11967) -> Option<HashSet<PreprocessorGuard>> {
11968 let mut merged = left.clone();
11969 for guard in right {
11970 let boolean_negation = guard
11971 .as_boolean_expression()
11972 .map(|expression| expression.negated());
11973 if merged.contains(&guard.negated())
11974 || boolean_negation.is_some_and(|negated| {
11975 merged
11976 .iter()
11977 .filter_map(PreprocessorGuard::as_boolean_expression)
11978 .any(|existing| existing == negated)
11979 })
11980 {
11981 return None;
11982 }
11983 merged.insert(guard.clone());
11984 }
11985 Some(merged)
11986}
11987
11988fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11989 if conditional.kind() == "preproc_ifdef" {
11990 let name = conditional.child_by_field_name("name")?;
11991 let name = node_text(name, source).to_string();
11992 return match conditional.child(0)?.kind() {
11993 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11994 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11995 _ => None,
11996 };
11997 }
11998 let condition = conditional.child_by_field_name("condition")?;
11999 simple_preprocessor_expression_guard(condition, source).or_else(|| {
12000 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12001 node_text(condition, source),
12002 )))
12003 })
12004}
12005
12006fn simple_preprocessor_expression_guard(
12007 expression: Node<'_>,
12008 source: &str,
12009) -> Option<PreprocessorGuard> {
12010 match expression.kind() {
12011 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12012 node_text(expression, source).to_string(),
12013 ))),
12014 "number_literal" => match node_text(expression, source).trim() {
12015 "0" => Some(PreprocessorGuard::Constant(false)),
12016 "1" => Some(PreprocessorGuard::Constant(true)),
12017 _ => None,
12018 },
12019 "preproc_defined" => {
12020 let identifier = (0..expression.named_child_count())
12021 .filter_map(|index| expression.named_child(index))
12022 .find(|child| child.kind() == "identifier")?;
12023 Some(PreprocessorGuard::Defined(
12024 node_text(identifier, source).to_string(),
12025 ))
12026 }
12027 "unary_expression"
12028 if expression
12029 .child_by_field_name("operator")
12030 .is_some_and(|operator| operator.kind() == "!") =>
12031 {
12032 simple_preprocessor_expression_guard(
12033 expression.child_by_field_name("argument")?,
12034 source,
12035 )
12036 .map(|guard| guard.negated())
12037 }
12038 "parenthesized_expression" => (0..expression.named_child_count())
12039 .filter_map(|index| expression.named_child(index))
12040 .next()
12041 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12042 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12043 expression, source,
12044 ))),
12045 _ => None,
12046 }
12047}
12048
12049fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12050 match expression.kind() {
12051 "number_literal" => match node_text(expression, source).trim() {
12052 "0" => BooleanGuardExpression::Constant(false),
12053 "1" => BooleanGuardExpression::Constant(true),
12054 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12055 expression, source,
12056 ))),
12057 },
12058 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12059 "preproc_defined" => {
12060 let identifier = (0..expression.named_child_count())
12061 .filter_map(|index| expression.named_child(index))
12062 .find(|child| child.kind() == "identifier");
12063 identifier.map_or_else(
12064 || {
12065 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12066 expression, source,
12067 )))
12068 },
12069 |identifier| {
12070 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12071 },
12072 )
12073 }
12074 "unary_expression"
12075 if expression
12076 .child_by_field_name("operator")
12077 .is_some_and(|operator| operator.kind() == "!") =>
12078 {
12079 expression.child_by_field_name("argument").map_or_else(
12080 || {
12081 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12082 expression, source,
12083 )))
12084 },
12085 |argument| boolean_preprocessor_expression(argument, source).negated(),
12086 )
12087 }
12088 "parenthesized_expression" => (0..expression.named_child_count())
12089 .filter_map(|index| expression.named_child(index))
12090 .next()
12091 .map_or_else(
12092 || {
12093 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12094 expression, source,
12095 )))
12096 },
12097 |child| boolean_preprocessor_expression(child, source),
12098 ),
12099 "binary_expression" => {
12100 let operands = || {
12101 Some((
12102 boolean_preprocessor_expression(
12103 expression.child_by_field_name("left")?,
12104 source,
12105 ),
12106 boolean_preprocessor_expression(
12107 expression.child_by_field_name("right")?,
12108 source,
12109 ),
12110 ))
12111 };
12112 match expression
12113 .child_by_field_name("operator")
12114 .map(|operator| operator.kind())
12115 {
12116 Some("&&") => operands().map_or_else(
12117 || {
12118 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12119 expression, source,
12120 )))
12121 },
12122 |(left, right)| BooleanGuardExpression::all([left, right]),
12123 ),
12124 Some("||") => operands().map_or_else(
12125 || {
12126 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12127 expression, source,
12128 )))
12129 },
12130 |(left, right)| BooleanGuardExpression::any([left, right]),
12131 ),
12132 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12133 expression, source,
12134 ))),
12135 }
12136 }
12137 _ => {
12138 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12139 }
12140 }
12141}
12142
12143fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12144 if targets.len() == 1 {
12145 targets.pop()
12146 } else {
12147 None
12148 }
12149}
12150
12151fn callable_declaration_nodes<'tree>(
12152 analyzer: &CppGraphSource<'_>,
12153 prepared: &'tree PreparedSyntaxTree,
12154 candidate: &CodeUnit,
12155) -> Vec<Node<'tree>> {
12156 let root = prepared.tree().root_node();
12157 analyzer
12158 .ranges(candidate)
12159 .into_iter()
12160 .filter_map(|range| {
12161 let mut declaration =
12162 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12163 while !matches!(
12176 declaration.kind(),
12177 "declaration" | "field_declaration" | "function_definition"
12178 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12179 declaration,
12180 prepared.source(),
12181 ) {
12182 let Some(parent) = declaration.parent() else {
12183 break;
12184 };
12185 declaration = parent;
12186 }
12187 Some(declaration)
12188 })
12189 .collect()
12190}
12191
12192fn real_function_definition_ancestor<'tree>(
12193 node: Node<'tree>,
12194 source: &str,
12195) -> Option<Node<'tree>> {
12196 let mut ancestor = node.parent();
12197 while let Some(node) = ancestor {
12198 if node.kind() == "function_definition"
12199 && !is_recovered_declaration_scope_container(node, source)
12200 {
12201 return Some(node);
12202 }
12203 ancestor = node.parent();
12204 }
12205 None
12206}
12207
12208fn callable_declaration_activation_in_file(
12209 analyzer: &CppGraphSource<'_>,
12210 candidate: &CodeUnit,
12211 reference: &CallableReferenceContext<'_>,
12212) -> Option<usize> {
12213 analyzer
12214 .declaration_source_occurrences(candidate)?
12215 .into_iter()
12216 .filter_map(|occurrence| {
12217 let activation = occurrence.fact.callable_activation?;
12218 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12219 callable_guard_requirements_are_visible(
12220 &guards,
12221 occurrence.fact.callable_guard_completion_byte,
12222 reference,
12223 )
12224 .then_some(activation)
12225 })
12226 .min()
12227}
12228
12229fn callable_guard_requirements_are_visible(
12230 required: &HashSet<PreprocessorGuard>,
12231 completion_byte: Option<usize>,
12232 reference: &CallableReferenceContext<'_>,
12233) -> bool {
12234 if required.iter().all(|guard| match guard {
12235 PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12236 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12237 guard => reference
12238 .guards()
12239 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12240 }) {
12241 return true;
12242 }
12243 if required.iter().any(|guard| match guard {
12244 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12245 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12246 _ => false,
12247 }) {
12248 return false;
12249 }
12250 let undecided: HashSet<_> = required
12251 .iter()
12252 .filter(|guard| {
12253 !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12254 if name == "__cplusplus")
12255 })
12256 .cloned()
12257 .collect();
12258 reference.position.as_ref().is_some_and(|position| {
12259 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12260 && guards_compatible_at_reference(&undecided, reference.guards())
12261 })
12262}
12263
12264struct CallableReferenceContext<'a> {
12270 file: &'a ProjectFile,
12271 position: Option<CallableReferencePosition<'a>>,
12272}
12273
12274struct CallableReferencePosition<'a> {
12278 prepared: &'a PreparedSyntaxTree,
12279 byte: usize,
12280 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12281}
12282
12283impl CallableReferenceContext<'_> {
12284 fn is_c(&self) -> bool {
12285 self.file
12286 .rel_path()
12287 .extension()
12288 .and_then(|extension| extension.to_str())
12289 == Some("c")
12290 }
12291
12292 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12293 let position = self.position.as_ref()?;
12294 position
12295 .guards
12296 .get_or_init(|| {
12297 position
12298 .prepared
12299 .tree()
12300 .root_node()
12301 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12302 .and_then(|node| {
12303 preprocessor_guard_environment(node, position.prepared.source())
12304 })
12305 })
12306 .as_ref()
12307 }
12308}
12309
12310fn callable_declaration_guard_requirements(
12322 node: Node<'_>,
12323 source: &str,
12324 reference: &CallableReferenceContext<'_>,
12325) -> Option<HashSet<PreprocessorGuard>> {
12326 let reference_is_c = reference.is_c();
12327 let mut required = HashSet::default();
12328 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12329 match guard {
12330 PreprocessorGuard::Constant(true) => {}
12331 PreprocessorGuard::Constant(false) => return None,
12332 guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12333 return None;
12334 }
12335 guard => {
12336 required.insert(guard);
12337 }
12338 }
12339 }
12340 Some(required)
12341}
12342
12343fn callable_preprocessor_context_is_visible_for_reference(
12354 node: Node<'_>,
12355 source: &str,
12356 reference: &CallableReferenceContext<'_>,
12357) -> bool {
12358 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12359 return false;
12360 };
12361 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12362 return true;
12363 }
12364 reference.position.as_ref().is_some_and(|position| {
12365 guards_compatible_at_reference(&required, reference.guards())
12366 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12367 })
12368}
12369
12370fn flattened_macro_namespace_declaration_matches(
12371 analyzer: &CppGraphSource<'_>,
12372 _cpp: &dyn CppSource,
12373 reference_file: &ProjectFile,
12374 visible_declaration: &CodeUnit,
12375 qualified_candidate: &CodeUnit,
12376 reference_byte: usize,
12377) -> bool {
12378 if visible_declaration.kind() != qualified_candidate.kind()
12384 || visible_declaration.identifier() != qualified_candidate.identifier()
12385 || visible_declaration.signature() != qualified_candidate.signature()
12386 || !visible_declaration.package_name().is_empty()
12387 || qualified_candidate.package_name().is_empty()
12388 {
12389 return false;
12390 }
12391
12392 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12393 return false;
12394 };
12395 occurrences.into_iter().any(|occurrence| {
12396 occurrence
12397 .displaced_namespace_closing_brace
12398 .is_some_and(|closing_brace| {
12399 visible_declaration.source() != reference_file || closing_brace < reference_byte
12400 })
12401 })
12402}
12403
12404pub(crate) fn flattened_macro_namespace_components(
12405 declaration: Node<'_>,
12406 source: &str,
12407) -> Option<Vec<String>> {
12408 flattened_macro_function_namespace_components(declaration, source)
12409 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12410}
12411
12412fn flattened_macro_function_namespace_components(
12413 declaration: Node<'_>,
12414 source: &str,
12415) -> Option<Vec<String>> {
12416 let body = declaration
12417 .parent()
12418 .filter(|parent| parent.kind() == "compound_statement")?;
12419 let function = body.parent()?;
12420 if function.child_by_field_name("body") != Some(body) {
12421 return None;
12422 }
12423 let namespace_name = recovered_macro_namespace_name(function, source)?;
12424 let mut components = enclosing_namespace_components(declaration, source)?;
12425 components.push(namespace_name);
12426 Some(components)
12427}
12428
12429fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12443 if function.kind() != "function_definition" {
12444 return None;
12445 }
12446 let body = function
12447 .child_by_field_name("body")
12448 .filter(|body| body.kind() == "compound_statement")?;
12449 let mut cursor = function.walk();
12450 let prefix = function
12451 .named_children(&mut cursor)
12452 .take_while(|child| child.start_byte() < body.start_byte())
12453 .filter(|child| child.kind() != "comment")
12454 .collect::<Vec<_>>();
12455 let begin_index = prefix.iter().rposition(|child| {
12456 flattened_macro_sentinel_name(*child, source)
12457 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12458 })?;
12459 let mut identifiers = Vec::new();
12460 let mut stack = prefix[begin_index + 1..]
12461 .iter()
12462 .rev()
12463 .copied()
12464 .collect::<Vec<_>>();
12465 while let Some(current) = stack.pop() {
12466 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12467 identifiers.push(identifier);
12468 continue;
12469 }
12470 let mut cursor = current.walk();
12471 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12472 stack.extend(children.into_iter().rev());
12473 }
12474 let [keyword, namespace_name] = identifiers.as_slice() else {
12475 return None;
12476 };
12477 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12478 {
12479 return None;
12480 }
12481 let mut next = function.next_named_sibling();
12482 let next = loop {
12483 let candidate = next?;
12484 next = candidate.next_named_sibling();
12485 if candidate.kind() != "comment" {
12486 break candidate;
12487 }
12488 };
12489 flattened_macro_sentinel_name(next, source)
12490 .is_some_and(|name| is_namespace_end_sentinel(&name))
12491 .then(|| namespace_name.clone())
12492}
12493
12494pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12499 crate::declarations::is_recovered_exported_class_container(node, source)
12500 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12501 node, source,
12502 )
12503 || recovered_macro_namespace_name(node, source).is_some()
12504}
12505
12506fn flattened_macro_error_namespace_components(
12507 declaration: Node<'_>,
12508 source: &str,
12509) -> Option<Vec<String>> {
12510 let parent = declaration
12511 .parent()
12512 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12513 let mut cursor = parent.walk();
12514 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12515 let declaration_index = siblings
12516 .iter()
12517 .position(|candidate| same_node(*candidate, declaration))?;
12518 let begin_index = (0..declaration_index).rev().find(|index| {
12519 flattened_macro_sentinel_name(siblings[*index], source)
12520 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12521 })?;
12522
12523 let significant = siblings[begin_index + 1..declaration_index]
12524 .iter()
12525 .copied()
12526 .filter(|node| node.kind() != "comment")
12527 .collect::<Vec<_>>();
12528 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12529 return None;
12530 };
12531 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12532 return None;
12533 }
12534 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12535 if significant[2..].iter().any(|node| {
12536 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12537 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12538 })
12539 }) {
12540 return None;
12541 }
12542
12543 let mut saw_namespace_close = false;
12544 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12545 if sibling.kind() == "comment" {
12546 continue;
12547 }
12548 if !saw_namespace_close {
12549 if direct_unmatched_closing_brace(sibling) {
12550 saw_namespace_close = true;
12551 continue;
12552 }
12553 if flattened_macro_sentinel_name(sibling, source).is_some() {
12554 return None;
12555 }
12556 continue;
12557 }
12558 if !flattened_macro_sentinel_name(sibling, source)
12559 .is_some_and(|name| is_namespace_end_sentinel(&name))
12560 {
12561 return None;
12562 }
12563 let mut components = enclosing_namespace_components(declaration, source)?;
12564 components.push(namespace_name);
12565 return Some(components);
12566 }
12567 None
12568}
12569
12570fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12571 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12579 node.named_child(0)?
12580 } else {
12581 node
12582 };
12583 let candidates = [
12584 direct_cpp_identifier_name(node, source),
12585 node.child_by_field_name("type")
12586 .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
12587 macro_modifier_identifier(node)
12588 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
12589 {
12590 let mut cursor = node.walk();
12591 node.named_children(&mut cursor)
12592 .find(|child| child.kind() != "comment")
12593 .and_then(macro_modifier_identifier)
12594 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
12595 },
12596 ];
12597 candidates.into_iter().flatten().find(|candidate| {
12598 cpp_export_macro_token(candidate)
12599 && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
12600 })
12601}
12602
12603#[derive(Clone, Copy, PartialEq, Eq)]
12606enum NamespaceSentinel {
12607 Begin,
12608 End,
12609}
12610
12611fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12612 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12613 Some(NamespaceSentinel::Begin)
12614 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12615 Some(NamespaceSentinel::End)
12616 } else {
12617 None
12618 }
12619}
12620
12621fn is_namespace_begin_sentinel(name: &str) -> bool {
12622 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12623}
12624
12625fn is_namespace_end_sentinel(name: &str) -> bool {
12626 namespace_sentinel(name) == Some(NamespaceSentinel::End)
12627}
12628
12629fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12630 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12631 return None;
12632 }
12633 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12634 (!cpp_export_macro_token(&name)).then_some(name)
12635}
12636
12637fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12638 if !matches!(
12639 node.kind(),
12640 "identifier" | "namespace_identifier" | "type_identifier"
12641 ) {
12642 return None;
12643 }
12644 let name = normalize_cpp_whitespace(node_text(node, source));
12645 (!name.is_empty()).then_some(name)
12646}
12647
12648fn guard_requirement_sets_match(
12649 left: &[(usize, HashSet<PreprocessorGuard>)],
12650 right: &[(usize, HashSet<PreprocessorGuard>)],
12651) -> bool {
12652 left.len() == right.len()
12653 && left.iter().all(|(_, left_guards)| {
12654 right
12655 .iter()
12656 .any(|(_, right_guards)| left_guards == right_guards)
12657 })
12658 && right.iter().all(|(_, right_guards)| {
12659 left.iter()
12660 .any(|(_, left_guards)| right_guards == left_guards)
12661 })
12662}
12663
12664pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12665 node.kind() == "ERROR"
12666 && (0..node.child_count())
12667 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12668}
12669
12670pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12671 let mut ancestor = node.parent();
12672 while let Some(parent) = ancestor {
12673 if is_cpp_preprocessor_conditional(parent)
12674 && !is_file_covering_include_guard(parent, source)
12675 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12676 {
12677 return false;
12678 }
12679 ancestor = parent.parent();
12680 }
12681 true
12682}
12683
12684fn is_split_cpp_language_linkage_wrapper(
12685 conditional: Node<'_>,
12686 descendant: Node<'_>,
12687 source: &str,
12688) -> bool {
12689 if conditional.child_by_field_name("alternative").is_some()
12690 || !matches!(
12691 simple_preprocessor_guard(conditional, source),
12692 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12693 )
12694 {
12695 return false;
12696 }
12697 let mut current = descendant.parent();
12698 let linkage = loop {
12699 let Some(node) = current else {
12700 return false;
12701 };
12702 if node == conditional {
12703 return false;
12704 }
12705 if node.kind() == "linkage_specification" {
12706 break node;
12707 }
12708 current = node.parent();
12709 };
12710 if linkage
12711 .child_by_field_name("value")
12712 .is_none_or(|value| node_text(value, source) != "\"C\"")
12713 {
12714 return false;
12715 }
12716 let Some(body) = linkage.child_by_field_name("body") else {
12717 return false;
12718 };
12719 let mut cursor = body.walk();
12724 let closes_opening_branch = body
12725 .named_children(&mut cursor)
12726 .take_while(|child| child.end_byte() <= descendant.start_byte())
12727 .any(|child| {
12728 child.kind() == "preproc_call"
12729 && child
12730 .child_by_field_name("directive")
12731 .is_some_and(|directive| node_text(directive, source) == "#endif")
12732 });
12733 let mut cursor = body.walk();
12734 let reopens_for_closing_brace = body
12735 .named_children(&mut cursor)
12736 .skip_while(|child| child.start_byte() < descendant.end_byte())
12737 .any(|child| {
12738 matches!(
12739 simple_preprocessor_guard(child, source),
12740 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12741 ) && {
12742 let mut tokens = child.walk();
12743 child
12744 .children(&mut tokens)
12745 .any(|token| token.kind() == "#endif" && token.is_missing())
12746 }
12747 });
12748 closes_opening_branch && reopens_for_closing_brace
12749}
12750
12751pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12755 node.child_by_field_name("arguments")
12756 .or_else(|| node.child_by_field_name("parameters"))
12757 .or_else(|| node.child_by_field_name("value"))
12758 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12759 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12760}
12761
12762pub fn call_arity(node: Node<'_>) -> usize {
12763 call_arguments_node(node)
12764 .map(|args| argument_children(args).count())
12765 .unwrap_or(0)
12766}
12767
12768pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12769 let recovered_block_arguments = recovered_block_literal_arguments(node);
12770 (0..node.child_count())
12771 .filter_map(move |index| node.child(index))
12772 .filter(|child| child.is_named() && !child.is_extra())
12773 .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
12778 .flat_map(move |child| {
12779 if let Some((raw, left, right)) = recovered_block_arguments
12780 && child == raw
12781 {
12782 [Some(left), Some(right)]
12783 } else {
12784 [Some(child), None]
12785 }
12786 })
12787 .flatten()
12788}
12789
12790fn block_literal_open_error(node: Node<'_>) -> bool {
12794 if node.kind() != "ERROR" || node.is_missing() {
12795 return false;
12796 }
12797 let mut cursor = node.walk();
12798 let tokens = node.children(&mut cursor).collect::<Vec<_>>();
12799 matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
12800}
12801
12802pub fn recovered_c_new_expression_arguments(
12811 node: Node<'_>,
12812 uses_c_semantics: bool,
12813) -> Option<[Node<'_>; 2]> {
12814 if !uses_c_semantics || node.kind() != "new_expression" {
12815 return None;
12816 }
12817 let parent = node.parent()?;
12818 if parent.kind() != "argument_list" {
12819 return None;
12820 }
12821 let keyword = node.child(0)?;
12822 let error = node.child(1)?;
12823 let trailing = node.child(2)?;
12824 if node.child(3).is_some()
12825 || keyword.kind() != "new"
12826 || keyword.is_named()
12827 || keyword.child_count() != 0
12828 || error.kind() != "ERROR"
12829 || !error.is_extra()
12830 || error.child_count() != 1
12831 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12832 || node.child_by_field_name("type") != Some(trailing)
12833 || trailing.kind() != "type_identifier"
12834 {
12835 return None;
12836 }
12837 Some([keyword, trailing])
12838}
12839
12840pub fn recovered_c_new_expression_argument_at(
12843 mut node: Node<'_>,
12844 start_byte: usize,
12845 end_byte: usize,
12846 uses_c_semantics: bool,
12847) -> Option<Node<'_>> {
12848 if !uses_c_semantics {
12854 return None;
12855 }
12856 loop {
12857 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12858 return arguments.into_iter().find(|argument| {
12859 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12860 });
12861 }
12862 node = node.parent()?;
12863 }
12864}
12865
12866fn recovered_c_keyword_argument_count(
12867 file: &ProjectFile,
12868 call: Node<'_>,
12869 arguments: Node<'_>,
12870 source: &str,
12871) -> usize {
12872 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12878 return 0;
12879 }
12880 let mut ancestor = Some(call);
12881 let function = loop {
12882 let Some(current) = ancestor else {
12883 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12884 .unwrap_or(0);
12885 };
12886 if current.kind() == "function_definition" {
12887 break current;
12888 }
12889 ancestor = current.parent();
12890 };
12891 let Some(parameters) = function
12892 .child_by_field_name("declarator")
12893 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12894 else {
12895 return 0;
12896 };
12897 let displaced_parameter_keywords = (0..parameters.child_count())
12898 .filter_map(|index| parameters.child(index))
12899 .filter(|error| error.kind() == "ERROR")
12900 .filter_map(|error| {
12901 let parameter = error.prev_named_sibling()?;
12902 if parameter.kind() != "parameter_declaration"
12903 || parameter.end_byte() != error.start_byte()
12904 || extract_variable_name(parameter, source).is_some()
12905 {
12906 return None;
12907 }
12908 let mut children = (0..error.child_count())
12909 .filter_map(|index| error.child(index))
12910 .filter(|child| !child.is_extra() && !child.is_missing());
12911 let keyword = children.next()?;
12912 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12913 .then_some(keyword)
12914 })
12915 .collect::<Vec<_>>();
12916 if displaced_parameter_keywords.is_empty() {
12917 return 0;
12918 }
12919
12920 (0..arguments.child_count())
12921 .filter_map(|index| arguments.child(index))
12922 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12923 .filter(|error| {
12924 let mut children = (0..error.child_count())
12925 .filter_map(|index| error.child(index))
12926 .filter(|child| !child.is_extra() && !child.is_missing());
12927 let Some(comma) = children.next() else {
12928 return false;
12929 };
12930 let Some(keyword) = children.next() else {
12931 return false;
12932 };
12933 children.next().is_none()
12934 && comma.kind() == ","
12935 && !keyword.is_named()
12936 && keyword.child_count() == 0
12937 && displaced_parameter_keywords
12938 .iter()
12939 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12940 })
12941 .count()
12942}
12943
12944fn recovered_c_keyword_argument_count_from_collapsed_function(
12950 call: Node<'_>,
12951 arguments: Node<'_>,
12952) -> Option<usize> {
12953 let argument_keywords = children_iter(arguments)
12954 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12955 .filter_map(|error| {
12956 let mut children =
12957 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12958 let comma = children.next()?;
12959 let keyword = children.next()?;
12960 (children.next().is_none()
12961 && comma.kind() == ","
12962 && !keyword.is_named()
12963 && keyword.child_count() == 0)
12964 .then_some(keyword)
12965 })
12966 .collect::<Vec<_>>();
12967 if argument_keywords.is_empty() {
12968 return Some(0);
12969 }
12970
12971 let mut ancestor = call.parent();
12976 let body = loop {
12977 let current = ancestor?;
12978 if matches!(current.kind(), "function_definition" | "lambda_expression") {
12979 return None;
12980 }
12981 if current.kind() == "compound_statement"
12982 && current
12983 .parent()
12984 .is_some_and(|parent| parent.kind() == "ERROR")
12985 {
12986 break current;
12987 }
12988 ancestor = current.parent();
12989 };
12990 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12991 let function_error = parameter_error
12992 .parent()
12993 .filter(|parent| parent.kind() == "ERROR")?;
12994 let function_children = children_iter(function_error).collect::<Vec<_>>();
12995 let parameter_error_index = function_children
12996 .iter()
12997 .position(|child| same_node(*child, parameter_error))?;
12998 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13001 .iter()
13002 .enumerate()
13003 .any(|(index, child)| {
13004 function_error.field_name_for_child(index as u32) == Some("type")
13005 && !child.is_extra()
13006 && !child.is_missing()
13007 && matches!(
13008 child.kind(),
13009 "primitive_type" | "sized_type_specifier" | "type_identifier"
13010 )
13011 });
13012 if parameter_error_index < 2
13013 || !has_return_type
13014 || function_children[parameter_error_index - 1].kind() != "("
13015 || function_children[parameter_error_index - 2].kind() != "identifier"
13016 {
13017 return Some(0);
13018 }
13019
13020 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13021 let body_index = parameter_children
13022 .iter()
13023 .position(|child| same_node(*child, body))?;
13024 let close_parenthesis_index = parameter_children[..body_index]
13025 .iter()
13026 .position(|child| child.kind() == ")")?;
13027 let parameter_keywords = parameter_children[..close_parenthesis_index]
13028 .iter()
13029 .enumerate()
13030 .filter(|(index, child)| {
13031 !child.is_named()
13032 && !child.is_extra()
13033 && !child.is_missing()
13034 && child.child_count() == 0
13035 && argument_keywords
13036 .iter()
13037 .any(|keyword| keyword.kind_id() == child.kind_id())
13038 && collapsed_c_parameter_name_position(¶meter_children, *index)
13039 })
13040 .map(|(_, child)| *child)
13041 .collect::<Vec<_>>();
13042 Some(
13043 argument_keywords
13044 .iter()
13045 .filter(|keyword| {
13046 parameter_keywords
13047 .iter()
13048 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13049 })
13050 .count(),
13051 )
13052}
13053
13054fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13059 let segment_start = children[..keyword_index]
13060 .iter()
13061 .rposition(|child| matches!(child.kind(), "(" | ","))
13062 .map_or(0, |index| index + 1);
13063 let mut saw_type = false;
13064 let mut saw_pointer = false;
13065 for child in &children[segment_start..keyword_index] {
13066 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13067 saw_pointer = true;
13068 continue;
13069 }
13070 if saw_pointer {
13071 return false;
13072 }
13073 if !child.is_named() || child.is_extra() || child.is_missing() {
13074 return false;
13075 }
13076 if child.kind() == "type_qualifier" {
13077 continue;
13078 }
13079 if matches!(
13080 child.kind(),
13081 "class_specifier"
13082 | "dependent_type"
13083 | "enum_specifier"
13084 | "identifier"
13085 | "primitive_type"
13086 | "qualified_identifier"
13087 | "scoped_identifier"
13088 | "struct_specifier"
13089 | "template_type"
13090 | "type_identifier"
13091 | "union_specifier"
13092 ) {
13093 saw_type = true;
13094 continue;
13095 }
13096 return false;
13097 }
13098 saw_type && saw_pointer
13099}
13100
13101fn recovered_block_literal_arguments<'tree>(
13102 arguments: Node<'tree>,
13103) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13104 if arguments.kind() != "argument_list" {
13105 return None;
13106 }
13107 let mut raw_arguments = (0..arguments.child_count())
13108 .filter_map(|index| arguments.child(index))
13109 .filter(|child| child.is_named() && !child.is_extra());
13110 let raw = raw_arguments.next()?;
13111 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13112 return None;
13113 }
13114
13115 let left = raw.child_by_field_name("left")?;
13116 if left.is_missing() || left.start_byte() == left.end_byte() {
13117 return None;
13118 }
13119 let right = raw.child_by_field_name("right")?;
13120 if right.kind() != "compound_literal_expression"
13121 || right.is_missing()
13122 || right
13123 .child_by_field_name("type")
13124 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13125 || right
13126 .child_by_field_name("value")
13127 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13128 {
13129 return None;
13130 }
13131 let has_intervening_error = (0..raw.child_count())
13132 .filter_map(|index| raw.child(index))
13133 .any(|child| {
13134 child.kind() == "ERROR"
13135 && !child.is_missing()
13136 && child.start_byte() >= left.end_byte()
13137 && child.end_byte() <= right.start_byte()
13138 });
13139 has_intervening_error.then_some((raw, left, right))
13140}
13141
13142pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13143 match node.kind() {
13144 "new_expression" => node
13145 .child_by_field_name("type")
13146 .or_else(|| node.named_child(0)),
13147 "compound_literal_expression" => node.child_by_field_name("type"),
13148 "call_expression" => node.child_by_field_name("function"),
13149 _ => None,
13150 }
13151}
13152
13153pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13159 if node.kind() != "cast_expression" {
13160 return None;
13161 }
13162 let descriptor = node.child_by_field_name("type")?;
13163 if descriptor.kind() == "type_descriptor" {
13164 descriptor.child_by_field_name("type")
13165 } else {
13166 Some(descriptor)
13167 }
13168}
13169
13170pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13185 debug_assert_eq!(field.kind(), "field_expression");
13186 let operator = field.child_by_field_name("operator")?;
13187 let mut cursor = field.walk();
13188 let receiver = field
13189 .named_children(&mut cursor)
13190 .filter(|child| child.end_byte() <= operator.start_byte())
13191 .last()?;
13192 if receiver.kind() != "ERROR" {
13193 return Some(receiver);
13194 }
13195 (receiver.named_child_count() == 1)
13196 .then(|| receiver.named_child(0))
13197 .flatten()
13198}
13199
13200pub fn field_initializer_constructs_target(
13201 node: Node<'_>,
13202 ctx: &ScanCtx<'_>,
13203 owner: &CodeUnit,
13204) -> bool {
13205 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13214 return qualified_base_initializer_constructs_target(node, ctx, owner);
13215 }
13216 let Some(name) = node
13217 .child_by_field_name("name")
13218 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13219 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13220 else {
13221 return false;
13222 };
13223 let field_name = node_text(name, ctx.source);
13224 ctx.visibility
13225 .visible_identifier_candidates(ctx.file, field_name)
13226 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13227 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13228}
13229
13230fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13240 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13241}
13242
13243fn qualified_base_initializer_constructs_target(
13244 node: Node<'_>,
13245 ctx: &ScanCtx<'_>,
13246 owner: &CodeUnit,
13247) -> bool {
13248 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13249 return false;
13250 };
13251 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13252 return false;
13253 };
13254 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13255 return false;
13256 };
13257 let resolves_target = |components: &[String]| {
13258 matches!(
13259 ctx.visibility.resolve_type_components_lexically_for_target(
13260 &ctx.analyzer,
13261 ctx.file,
13262 components,
13263 is_globally_qualified_cpp_name(qualified),
13264 &lexical_scope,
13265 owner,
13266 ),
13267 LexicalTypeResolution::Resolved { unit, .. }
13268 if same_visible_symbol(&unit, owner)
13269 )
13270 };
13271 if resolves_target(&components) {
13272 return true;
13273 }
13274
13275 components
13281 .last()
13282 .is_some_and(|terminal| terminal == owner.identifier())
13283 && resolves_target(&components[..components.len() - 1])
13284}
13285
13286fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13287 ctx.visibility
13288 .field_declared_type_fact(&ctx.analyzer, unit)
13289 .is_some_and(|fact| {
13290 ctx.visibility
13291 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13292 || ctx.visibility.resolves_to_type(
13293 &ctx.analyzer,
13294 ctx.file,
13295 &normalize_field_type_text(&fact.type_text),
13296 owner,
13297 )
13298 })
13299}
13300
13301pub fn field_declared_binding(
13302 analyzer: &CppGraphSource<'_>,
13303 visibility: &VisibilityIndex<'_>,
13304 visible_from: &ProjectFile,
13305 field: &CodeUnit,
13306) -> Option<CppScanBinding> {
13307 let (normalized, resolved, indirection) =
13308 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13309 let resolved = resolved
13310 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13311 Some(CppScanBinding::from_type_name(
13312 normalized,
13313 resolved,
13314 indirection,
13315 ))
13316}
13317
13318pub fn field_initializer_holds_subobject(
13325 analyzer: &CppGraphSource<'_>,
13326 visibility: &VisibilityIndex<'_>,
13327 field: &CodeUnit,
13328) -> bool {
13329 visibility
13330 .field_declared_type_fact(analyzer, field)
13331 .is_none_or(|fact| !fact.binds_indirectly)
13332}
13333
13334fn anonymous_aggregate_field_owner(
13341 analyzer: &CppGraphSource<'_>,
13342 visibility: &VisibilityIndex<'_>,
13343 visible_from: &ProjectFile,
13344 field: &CodeUnit,
13345) -> Option<CodeUnit> {
13346 let owner = type_owner_of(analyzer, field)?;
13347 if !owner.is_class() {
13348 return None;
13349 }
13350 let declaration = analyzer.get_source(field, false)?;
13351 let mut parser = Parser::new();
13352 parser
13353 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13354 .ok()?;
13355 let tree = parser.parse(&declaration, None)?;
13356 let mut stack = vec![tree.root_node()];
13357 while let Some(node) = stack.pop() {
13358 if matches!(node.kind(), "declaration" | "field_declaration")
13359 && let Some(type_node) = node
13360 .child_by_field_name("type")
13361 .or_else(|| first_type_child(node))
13362 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13363 && type_node.child_by_field_name("name").is_none()
13364 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13365 {
13366 let matches = visibility
13367 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13368 .into_iter()
13369 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13370 .collect::<Vec<_>>();
13371 return match matches.as_slice() {
13372 [child] => Some((*child).clone()),
13373 _ => None,
13374 };
13375 }
13376 let mut cursor = node.walk();
13377 stack.extend(node.named_children(&mut cursor));
13378 }
13379 None
13380}
13381
13382pub fn anonymous_aggregate_owner(
13389 analyzer: &CppGraphSource<'_>,
13390 file: &ProjectFile,
13391 node: Node<'_>,
13392) -> Option<CodeUnit> {
13393 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13394 || node.child_by_field_name("name").is_some()
13395 {
13396 return None;
13397 }
13398 let mut candidates = analyzer
13399 .declarations(file)
13400 .into_iter()
13401 .filter(|candidate| {
13402 candidate.is_class()
13403 && analyzer.ranges(candidate).into_iter().any(|range| {
13404 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13405 })
13406 })
13407 .collect::<Vec<_>>();
13408 candidates.sort_by_key(|candidate| candidate.fq_name());
13409 candidates.dedup();
13410 match candidates.as_slice() {
13411 [candidate] => Some(candidate.clone()),
13412 _ => None,
13413 }
13414}
13415
13416fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13418 let Some(first) = candidates.first() else {
13419 return Err(TypeCandidateFailure::Unresolvable);
13420 };
13421 if candidates
13422 .iter()
13423 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13424 {
13425 Ok((*first).clone())
13426 } else {
13427 Err(TypeCandidateFailure::Ambiguous)
13428 }
13429}
13430
13431fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13432 logical_type_candidate(candidates).ok()
13433}
13434
13435fn unique_type_candidate_preserving_alias(
13436 analyzer: &CppGraphSource<'_>,
13437 file: &ProjectFile,
13438 candidates: &[&CodeUnit],
13439) -> Option<CodeUnit> {
13440 let first = *candidates.first()?;
13441 if declared_type_alias(analyzer, first) {
13442 return candidates
13443 .iter()
13444 .all(|candidate| {
13445 declared_type_alias(analyzer, candidate)
13446 && candidate.kind() == first.kind()
13447 && candidate.fq_name() == first.fq_name()
13448 && candidate.source() == first.source()
13449 })
13450 .then(|| first.clone());
13451 }
13452 if analyzer.reference_uses_c_semantics(file)
13453 && first.is_class()
13454 && indexed_c_tag_kind(analyzer, first).is_some()
13455 {
13456 let mut full_source = None;
13457 let mut tag_kind = None;
13458 for candidate in candidates.iter().copied() {
13459 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13460 if tag_kind
13461 .replace(candidate_tag_kind)
13462 .is_some_and(|existing| existing != candidate_tag_kind)
13463 {
13464 return None;
13465 }
13466 if cpp_class_declaration_strength(analyzer, candidate)
13467 == CppClassDeclarationStrength::Full
13468 && full_source
13469 .replace(candidate.source())
13470 .is_some_and(|existing| existing != candidate.source())
13471 {
13472 return None;
13473 }
13474 }
13475 }
13476 candidates
13477 .iter()
13478 .all(|candidate| {
13479 !declared_type_alias(analyzer, candidate)
13480 && candidate.kind() == first.kind()
13481 && candidate.fq_name() == first.fq_name()
13482 })
13483 .then(|| first.clone())
13484}
13485
13486pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13487 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13488 analyzer.type_alias_provider(),
13489 unit,
13490 )
13491}
13492
13493pub fn field_declared_type_binding(
13494 analyzer: &CppGraphSource<'_>,
13495 visibility: &VisibilityIndex<'_>,
13496 visible_from: &ProjectFile,
13497 field: &CodeUnit,
13498) -> Option<(String, Option<CodeUnit>, i32)> {
13499 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13500 let normalized = normalize_field_type_text(&fact.type_text);
13501 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13502 analyzer,
13503 visible_from,
13504 field,
13505 &normalized,
13506 );
13507 let resolved = match (primary, fact.template_arguments.as_deref()) {
13508 (Some(primary), Some(arguments)) => visibility
13509 .resolve_template_arguments(visible_from, primary, arguments)
13510 .ok(),
13511 (resolved, None) => resolved,
13512 (None, Some(_)) => None,
13513 };
13514 Some((normalized, resolved, fact.indirection))
13515}
13516
13517fn decode_field_declared_type_fact(
13518 analyzer: &CppGraphSource<'_>,
13519 field: &CodeUnit,
13520) -> Option<DeclaredFieldTypeFact> {
13521 if let Some(facts) = analyzer.declaration_source_properties(field)
13522 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13523 && facts
13524 .iter()
13525 .all(|fact| fact.field_type.as_ref() == Some(first))
13526 {
13527 return Some(first.clone());
13528 }
13529 let Some(declaration) = analyzer.get_source(field, false) else {
13530 return decode_indexed_field_declared_type_fact(analyzer, field);
13531 };
13532 let mut parser = Parser::new();
13533 parser
13534 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13535 .ok()?;
13536 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13540 let contextual_tree = parser.parse(&contextual_declaration, None)?;
13541 let mut stack = vec![contextual_tree.root_node()];
13542 while let Some(node) = stack.pop() {
13543 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13544 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13545 {
13546 return Some(DeclaredFieldTypeFact {
13547 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13548 indirection: recovered.pointer_depth(),
13549 binds_indirectly: recovered.pointer_depth() > 0,
13550 template_arguments: None,
13551 });
13552 }
13553 if let Some(recovered) =
13554 recovered_function_like_field_declarator(node, &contextual_declaration)
13555 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13556 {
13557 let type_node = node
13558 .child_by_field_name("type")
13559 .or_else(|| first_type_child(node))?;
13560 return Some(DeclaredFieldTypeFact {
13561 type_text: node_text(type_node, &contextual_declaration).to_string(),
13562 indirection: recovered.pointer_depth(),
13563 binds_indirectly: true,
13566 template_arguments: cpp_template_reference_arguments(
13567 type_node,
13568 &contextual_declaration,
13569 ),
13570 });
13571 }
13572 if let Some(fact) =
13573 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13574 {
13575 return Some(fact);
13576 }
13577 let mut cursor = node.walk();
13578 stack.extend(node.named_children(&mut cursor));
13579 }
13580 let tree = parser.parse(&declaration, None)?;
13581 let mut stack = vec![tree.root_node()];
13582 while let Some(node) = stack.pop() {
13583 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13584 {
13585 return Some(fact);
13586 }
13587 let mut cursor = node.walk();
13588 stack.extend(node.named_children(&mut cursor));
13589 }
13590 None
13591}
13592
13593fn decode_indexed_field_declared_type_fact(
13598 analyzer: &CppGraphSource<'_>,
13599 field: &CodeUnit,
13600) -> Option<DeclaredFieldTypeFact> {
13601 let cpp = analyzer.cpp?;
13602 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13603 let source = prepared.source();
13604 let root = prepared.tree().root_node();
13605 for range in analyzer.ranges(field) {
13606 let end = range.start_byte.saturating_add(1).min(source.len());
13607 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13608 while let Some(node) = current {
13609 if matches!(node.kind(), "declaration" | "field_declaration")
13610 && let Some(fact) =
13611 decode_declared_field_type_node(node, field.identifier(), source)
13612 {
13613 return Some(fact);
13614 }
13615 current = node.parent();
13616 }
13617 }
13618 None
13619}
13620
13621fn decode_declared_field_type_node(
13622 node: Node<'_>,
13623 field_name: &str,
13624 source: &str,
13625) -> Option<DeclaredFieldTypeFact> {
13626 if !matches!(node.kind(), "declaration" | "field_declaration") {
13627 return None;
13628 }
13629 let type_node = node
13630 .child_by_field_name("type")
13631 .or_else(|| first_type_child(node))?;
13632 cpp_declared_field_type_fact(node, type_node, field_name, source)
13633}
13634
13635pub(crate) fn cpp_declared_field_type_fact(
13636 node: Node<'_>,
13637 type_node: Node<'_>,
13638 field_name: &str,
13639 source: &str,
13640) -> Option<DeclaredFieldTypeFact> {
13641 let binding = declared_name_binding(node, type_node, field_name, source)?;
13642 let declared_type = if matches!(
13643 type_node.kind(),
13644 "class_specifier" | "struct_specifier" | "union_specifier"
13645 ) {
13646 type_node.child_by_field_name("name")
13647 } else {
13648 Some(type_node)
13649 };
13650 Some(DeclaredFieldTypeFact {
13651 type_text: declared_type.map_or_else(
13652 || field_name.to_string(),
13653 |declared_type| node_text(declared_type, source).to_string(),
13654 ),
13655 indirection: binding.pointer_depth,
13656 binds_indirectly: binding.indirect,
13657 template_arguments: declared_type
13658 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13659 })
13660}
13661
13662pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13676 let mut parser = Parser::new();
13677 parser
13678 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13679 .ok()?;
13680 let tree = parser.parse(declaration, None)?;
13681 let mut stack = vec![tree.root_node()];
13682 while let Some(node) = stack.pop() {
13683 let type_node = match node.kind() {
13684 "type_definition" => {
13685 let mut cursor = node.walk();
13686 if node
13687 .children_by_field_name("declarator", &mut cursor)
13688 .any(declarator_names_function_type)
13689 {
13690 return None;
13691 }
13692 node.child_by_field_name("type")?
13693 }
13694 "alias_declaration" => {
13695 let type_node = node.child_by_field_name("type")?;
13696 if type_node
13697 .child_by_field_name("declarator")
13698 .is_some_and(declarator_names_function_type)
13699 {
13700 return None;
13701 }
13702 type_node
13703 }
13704 _ => {
13705 let mut cursor = node.walk();
13706 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13707 stack.extend(children.into_iter().rev());
13708 continue;
13709 }
13710 };
13711 return Some(node_text(type_node, declaration).to_string());
13712 }
13713 None
13714}
13715
13716pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13722 let mut current = Some(declarator);
13723 while let Some(node) = current {
13724 match node.kind() {
13725 "function_declarator" | "abstract_function_declarator" => return true,
13726 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13727 current = node.named_child(0);
13728 }
13729 _ => current = node.child_by_field_name("declarator"),
13730 }
13731 }
13732 false
13733}
13734
13735fn decode_structured_alias_target(
13736 analyzer: &CppGraphSource<'_>,
13737 unit: &CodeUnit,
13738) -> Option<StructuredAliasTarget> {
13739 let facts = analyzer.declaration_source_properties(unit)?;
13740 let first = facts.first()?.alias_target.as_ref()?;
13741 facts
13742 .iter()
13743 .all(|fact| fact.alias_target.as_ref() == Some(first))
13744 .then(|| first.clone())
13745}
13746
13747pub(crate) fn structured_alias_type_target(
13748 mut type_node: Node<'_>,
13749 source: &str,
13750) -> Option<StructuredAliasTarget> {
13751 while type_node.kind() == "type_descriptor" {
13752 type_node = type_node.child_by_field_name("type")?;
13753 }
13754 if type_node.kind() == "primitive_type" {
13755 return Some(StructuredAliasTarget::Builtin);
13756 }
13757 if matches!(
13758 type_node.kind(),
13759 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13760 ) {
13761 type_node = type_node.child_by_field_name("name")?;
13762 }
13763 let global = type_node.child_by_field_name("scope").is_none()
13764 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13765 let mut components = Vec::new();
13766 append_structured_type_components(type_node, source, &mut components)?;
13767 let arguments = cpp_template_reference_arguments(type_node, source);
13768 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13769 components,
13770 global,
13771 arguments,
13772 })
13773}
13774
13775fn append_structured_type_components(
13776 node: Node<'_>,
13777 source: &str,
13778 out: &mut Vec<String>,
13779) -> Option<()> {
13780 match node.kind() {
13781 "identifier" | "namespace_identifier" | "type_identifier" => {
13782 out.push(node_text(node, source).to_string());
13783 Some(())
13784 }
13785 "template_type" | "template_function" => {
13786 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13787 }
13788 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13789 let (scope, name) = qualified_name_parts(node)?;
13790 if let Some(scope) = scope {
13791 append_structured_type_components(scope, source, out)?;
13792 }
13793 append_structured_type_components(name, source, out)
13794 }
13795 _ => None,
13796 }
13797}
13798
13799pub(crate) struct DeclaredNameBinding {
13802 pub pointer_depth: i32,
13804 pub indirect: bool,
13808}
13809
13810pub(crate) fn declared_name_binding(
13811 declaration: Node<'_>,
13812 type_node: Node<'_>,
13813 field_name: &str,
13814 source: &str,
13815) -> Option<DeclaredNameBinding> {
13816 let mut stack = Vec::new();
13817 let mut cursor = declaration.walk();
13818 stack.extend(
13819 declaration
13820 .named_children(&mut cursor)
13821 .filter(|child| !same_node(*child, type_node)),
13822 );
13823 while let Some(node) = stack.pop() {
13824 if matches!(node.kind(), "identifier" | "field_identifier")
13825 && node_text(node, source) == field_name
13826 {
13827 let mut binding = DeclaredNameBinding {
13828 pointer_depth: 0,
13829 indirect: false,
13830 };
13831 let mut current = node.parent();
13832 while let Some(parent) = current {
13833 if same_node(parent, declaration) {
13834 return Some(binding);
13835 }
13836 match parent.kind() {
13837 "pointer_declarator" => {
13838 binding.pointer_depth += 1;
13839 binding.indirect = true;
13840 }
13841 "reference_declarator" => binding.indirect = true,
13842 _ => {}
13843 }
13844 current = parent.parent();
13845 }
13846 return None;
13847 }
13848 let mut cursor = node.walk();
13849 stack.extend(node.named_children(&mut cursor));
13850 }
13851 None
13852}
13853
13854fn normalize_field_type_text(type_text: &str) -> String {
13855 const FIELD_SPECIFIERS: [&str; 8] = [
13856 "extern ",
13857 "static ",
13858 "mutable ",
13859 "constexpr ",
13860 "constinit ",
13861 "inline ",
13862 "volatile ",
13863 "const ",
13864 ];
13865
13866 let mut normalized = normalize_type_text(type_text);
13867 loop {
13868 let Some(stripped) = FIELD_SPECIFIERS
13869 .iter()
13870 .find_map(|specifier| normalized.strip_prefix(specifier))
13871 else {
13872 return normalized;
13873 };
13874 normalized = normalize_type_text(stripped);
13875 }
13876}
13877
13878pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13879 let Some(type_node) = node.child_by_field_name("type") else {
13880 return false;
13881 };
13882 ctx.visibility.resolves_to_type(
13883 &ctx.analyzer,
13884 ctx.file,
13885 node_text(type_node, ctx.source),
13886 owner,
13887 )
13888}
13889
13890pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13891 !ctx.analyzer
13892 .declarations(ctx.file)
13893 .into_iter()
13894 .filter(|unit| unit.is_function())
13895 .any(|unit| {
13896 ctx.analyzer.ranges(&unit).iter().any(|range| {
13897 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13898 })
13899 })
13900}
13901
13902pub enum DeclarationConstructorInitializer<'tree> {
13904 Arguments(Node<'tree>),
13907 Expression(Node<'tree>),
13910 Empty,
13912}
13913
13914pub fn declaration_constructor_initializer(
13915 node: Node<'_>,
13916) -> DeclarationConstructorInitializer<'_> {
13917 let mut cursor = node.walk();
13918 for child in node.named_children(&mut cursor) {
13919 if child.kind() == "init_declarator" {
13920 let Some(value) = child
13921 .child_by_field_name("value")
13922 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13923 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13924 else {
13925 return DeclarationConstructorInitializer::Empty;
13926 };
13927 return match value.kind() {
13928 "argument_list" | "initializer_list" => {
13929 DeclarationConstructorInitializer::Arguments(value)
13930 }
13931 "compound_literal_expression" => call_arguments_node(value)
13932 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13933 DeclarationConstructorInitializer::Arguments(arguments)
13934 }),
13935 _ => DeclarationConstructorInitializer::Expression(value),
13936 };
13937 }
13938 if let Some(declarator) = declaration_declarator(node, child) {
13939 return declarator_parameters(declarator)
13940 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13941 DeclarationConstructorInitializer::Arguments(parameters)
13942 });
13943 }
13944 }
13945 DeclarationConstructorInitializer::Empty
13946}
13947
13948pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13949 match declaration_constructor_initializer(node) {
13950 DeclarationConstructorInitializer::Arguments(arguments) => {
13951 argument_children(arguments).count()
13952 }
13953 DeclarationConstructorInitializer::Expression(_) => 1,
13954 DeclarationConstructorInitializer::Empty => 0,
13955 }
13956}
13957
13958fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13962 let mut current = node;
13963 loop {
13964 if let Some(parameters) = current.child_by_field_name("parameters") {
13965 return Some(parameters);
13966 }
13967 current = current.child_by_field_name("declarator")?;
13968 }
13969}
13970
13971fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13972 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13973}
13974
13975fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13976 if node.kind() == "identifier" {
13977 return true;
13978 }
13979 if node.kind() == "parenthesized_expression" {
13980 return false;
13981 }
13982 if node.kind() == "call_expression" {
13983 return node
13984 .child_by_field_name("function")
13985 .is_some_and(|function| function.kind() == "identifier");
13986 }
13987 let mut stack = vec![node];
13988 while let Some(descendant) = stack.pop() {
13989 if descendant != node && descendant.kind() == "parenthesized_expression" {
13990 continue;
13991 }
13992 if descendant.kind() == "identifier" {
13993 return true;
13994 }
13995 if descendant.kind() == "call_expression" {
13996 if descendant
13997 .child_by_field_name("function")
13998 .is_some_and(|function| function.kind() == "identifier")
13999 {
14000 return true;
14001 }
14002 continue;
14003 }
14004 push_named_children_reversed(descendant, &mut stack);
14005 }
14006 false
14007}
14008
14009fn macro_expansion_shape_is_safe(
14010 node: Node<'_>,
14011 source: &str,
14012 parameters: &[String],
14013 environment: &MacroEnvironment,
14014) -> bool {
14015 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14016 return true;
14017 }
14018 if node.kind() == "call_expression" {
14019 let Some(function) = node.child_by_field_name("function") else {
14020 return true;
14021 };
14022 if function.kind() != "identifier" {
14023 return true;
14024 }
14025 let function_name = node_text(function, source);
14026 if parameters
14027 .iter()
14028 .any(|parameter| parameter == function_name)
14029 {
14030 return false;
14031 }
14032 if !environment.may_bind(function_name) {
14033 return true;
14034 }
14035 let Some(arguments) = node.child_by_field_name("arguments") else {
14036 return false;
14037 };
14038 return argument_children(arguments).all(|argument| {
14039 if argument.kind() == "identifier"
14040 && parameters
14041 .iter()
14042 .any(|parameter| parameter == node_text(argument, source))
14043 {
14044 return false;
14045 }
14046 macro_expansion_shape_is_safe(argument, source, parameters, environment)
14047 });
14048 }
14049 let mut stack = vec![node];
14050 while let Some(descendant) = stack.pop() {
14051 if descendant != node {
14052 if descendant.kind() == "parenthesized_expression" {
14053 continue;
14054 }
14055 if descendant.kind() == "call_expression" {
14056 let expands = descendant
14057 .child_by_field_name("function")
14058 .filter(|function| function.kind() == "identifier")
14059 .is_some_and(|function| environment.may_bind(node_text(function, source)));
14060 if expands {
14061 return false;
14062 }
14063 continue;
14064 }
14065 }
14066 if descendant.kind() == "identifier" {
14067 let identifier = node_text(descendant, source);
14068 if parameters.iter().any(|parameter| parameter == identifier)
14069 || environment.may_bind(identifier)
14070 {
14071 return false;
14072 }
14073 }
14074 push_named_children_reversed(descendant, &mut stack);
14075 }
14076 true
14077}
14078
14079fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14080 let source = prepared.source();
14081 let mut facts = Vec::new();
14082 let mut nodes = vec![prepared.tree().root_node()];
14083 while let Some(node) = nodes.pop() {
14084 if node.kind() == "preproc_include" {
14085 let Some(path) = node
14086 .child_by_field_name("path")
14087 .and_then(|path| crate::imports::include_path_literal_value(path, source))
14088 .map(str::to_owned)
14089 else {
14090 continue;
14091 };
14092 facts.push(StructuredIncludeFact {
14093 start_byte: node.start_byte(),
14094 end_byte: node.end_byte(),
14095 path,
14096 });
14097 continue;
14098 }
14099 push_named_children_reversed(node, &mut nodes);
14100 }
14101 Arc::from(facts.into_boxed_slice())
14102}
14103
14104fn has_unresolved_include_visible_before_in_prepared(
14105 file: &ProjectFile,
14106 prepared: &PreparedSyntaxTree,
14107 include_targets: &IncludeTargetIndex,
14108 facts: &[StructuredIncludeFact],
14109 before_byte: usize,
14110) -> bool {
14111 let guards = OnceCell::new();
14112 let reference = CallableReferenceContext {
14113 file,
14114 position: Some(CallableReferencePosition {
14115 prepared,
14116 byte: before_byte,
14117 guards: &guards,
14118 }),
14119 };
14120 let root = prepared.tree().root_node();
14121 facts
14122 .iter()
14123 .filter(|fact| fact.end_byte <= before_byte)
14124 .any(|fact| {
14125 let node = root
14126 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14127 .expect("structured include fact range must be in prepared tree");
14128 assert_eq!(
14129 node.kind(),
14130 "preproc_include",
14131 "structured include fact range must identify its include node"
14132 );
14133 callable_preprocessor_context_is_visible_for_reference(
14134 node,
14135 prepared.source(),
14136 &reference,
14137 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14138 })
14139}
14140
14141fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14142 let descendant = node;
14143 while let Some(parent) = node.parent() {
14144 if is_cpp_preprocessor_conditional(parent)
14145 && !is_file_covering_include_guard(parent, source)
14146 && preprocessor_conditional_contains_descendant(parent, descendant)
14147 {
14148 return true;
14149 }
14150 node = parent;
14151 }
14152 false
14153}
14154
14155fn owning_preprocessor_conditionals(
14167 root: Node<'_>,
14168 event: Node<'_>,
14169 source: &str,
14170) -> OwningPreprocessorConditionals {
14171 if !has_preprocessor_conditional_ancestor(event, source) {
14172 return OwningPreprocessorConditionals::default();
14173 }
14174 let start = event.start_byte();
14175 let descendant = root
14176 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14177 .expect("a byte inside the parsed tree names a descendant");
14178 let mut owners = Vec::new();
14179 let mut current = descendant.parent();
14180 while let Some(conditional) = current {
14181 if is_cpp_preprocessor_conditional(conditional)
14182 && !is_file_covering_include_guard(conditional, source)
14183 && preprocessor_conditional_contains_descendant(conditional, descendant)
14184 {
14185 owners.push(conditional.start_byte());
14186 }
14187 current = conditional.parent();
14188 }
14189 owners.into_boxed_slice()
14190}
14191
14192fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14193 node.parent()
14194 .filter(|parent| parent.kind() == "translation_unit")
14195 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14196 && is_canonical_include_guard(node, source)
14197}
14198
14199fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14200 if node.kind() != "preproc_ifdef"
14201 || node
14202 .child(0)
14203 .is_none_or(|directive| directive.kind() != "#ifndef")
14204 || node.child_by_field_name("alternative").is_some()
14205 {
14206 return false;
14207 }
14208 let Some(guard_name) = node.child_by_field_name("name") else {
14209 return false;
14210 };
14211 let mut cursor = node.walk();
14212 node.named_children(&mut cursor)
14213 .find(|child| *child != guard_name && child.kind() != "comment")
14214 .filter(|child| child.kind() == "preproc_def")
14215 .and_then(|definition| definition.child_by_field_name("name"))
14216 .is_some_and(|defined_name| {
14217 node_text(defined_name, source) == node_text(guard_name, source)
14218 })
14219}
14220
14221fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14222 let mut guard = None;
14223 for child in named_children_iter(root) {
14224 if child.kind() == "comment" || is_pragma_once(child, source) {
14225 continue;
14226 }
14227 if guard.is_none() && is_canonical_include_guard(child, source) {
14228 guard = Some(child);
14229 } else {
14230 return None;
14231 }
14232 }
14233 guard
14234 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14235 .map(|name| node_text(name, source).to_string())
14236}
14237
14238fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14239 if (0..root.named_child_count())
14240 .filter_map(|index| root.named_child(index))
14241 .any(|child| is_pragma_once(child, source))
14242 {
14243 return MacroIncludeProtection::PragmaOnce;
14244 }
14245 top_level_canonical_include_guard_name(root, source)
14246 .map(MacroIncludeProtection::MacroGuard)
14247 .unwrap_or(MacroIncludeProtection::None)
14248}
14249
14250fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14251 node.kind() == "preproc_call"
14252 && node
14253 .child_by_field_name("directive")
14254 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14255 && node
14256 .child_by_field_name("argument")
14257 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14258}
14259
14260fn parse_preproc_identifier(argument: &str) -> Option<String> {
14261 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14262 let mut parser = Parser::new();
14263 parser
14264 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14265 .ok()?;
14266 let tree = parser.parse(&sentinel, None)?;
14267 if tree.root_node().has_error() {
14268 return None;
14269 }
14270 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14271 let identifier = statement.named_child(0)?;
14272 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14273 .then(|| node_text(identifier, &sentinel).to_string())
14274}
14275
14276pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14277 match node.kind() {
14278 "identifier" | "field_identifier" => {
14279 let name = node_text(node, source).trim();
14280 (!name.is_empty()).then(|| name.to_string())
14281 }
14282 "abstract_array_declarator"
14283 | "abstract_function_declarator"
14284 | "abstract_parenthesized_declarator"
14285 | "abstract_pointer_declarator"
14286 | "abstract_reference_declarator" => None,
14287 "function_declarator" => node
14288 .child_by_field_name("declarator")
14289 .or_else(|| node.child_by_field_name("name"))
14290 .and_then(|child| extract_variable_name(child, source)),
14291 _ => node
14292 .child_by_field_name("declarator")
14293 .or_else(|| node.child_by_field_name("name"))
14294 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14295 .and_then(|child| extract_variable_name(child, source)),
14296 }
14297}
14298
14299pub fn is_c_source_file(file: &ProjectFile) -> bool {
14310 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14311}
14312
14313pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14320 if !is_c_source_file(file) || node.kind() != "identifier" {
14321 return false;
14322 }
14323 let mut operand = node;
14324 while let Some(parent) = operand.parent().filter(|parent| {
14325 parent.kind() == "parenthesized_expression"
14326 && parent.named_child_count() == 1
14327 && parent.named_child(0) == Some(operand)
14328 }) {
14329 operand = parent;
14330 }
14331 operand.parent().is_some_and(|parent| {
14332 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14333 })
14334}
14335
14336pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14345 if !is_c_offsetof_member_node(node) {
14346 return None;
14347 }
14348 let expression = node.parent()?;
14349 if expression.has_error() {
14350 return None;
14351 }
14352 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14353 if closing.kind() != ")" || closing.is_missing() {
14354 return None;
14355 }
14356 let type_descriptor = expression.child_by_field_name("type")?;
14357 if type_descriptor.kind() != "type_descriptor"
14358 || type_descriptor.is_missing()
14359 || type_descriptor.has_error()
14360 {
14361 return None;
14362 }
14363 let type_specifier = type_descriptor.child_by_field_name("type")?;
14364 if type_specifier.is_missing() || type_specifier.has_error() {
14365 return None;
14366 }
14367 let type_reference = match type_specifier.kind() {
14368 "class_specifier" | "struct_specifier" | "union_specifier" => {
14369 type_specifier.child_by_field_name("name")?
14370 }
14371 _ => type_specifier,
14372 };
14373 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14374}
14375
14376pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14381 node.kind() == "field_identifier"
14382 && node.parent().is_some_and(|parent| {
14383 parent.kind() == "offsetof_expression"
14384 && parent.child_by_field_name("member") == Some(node)
14385 })
14386}
14387
14388pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14392 if node.kind() != "template_function" {
14393 return false;
14394 }
14395 let Some(arguments) = node
14396 .parent()
14397 .filter(|parent| parent.kind() == "template_argument_list")
14398 else {
14399 return false;
14400 };
14401 let Some(owner) = arguments.parent().filter(|parent| {
14402 parent.kind() == "template_type"
14403 && parent.child_by_field_name("arguments") == Some(arguments)
14404 }) else {
14405 return false;
14406 };
14407
14408 let mut current = owner;
14409 while let Some(parent) = current.parent() {
14410 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14411 {
14412 return true;
14413 }
14414 if parent.kind() == "qualified_identifier"
14415 && parent.child_by_field_name("name") == Some(current)
14416 {
14417 current = parent;
14418 continue;
14419 }
14420 return false;
14421 }
14422 false
14423}
14424
14425pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14441 if node.kind() != "type_identifier" {
14442 return false;
14443 }
14444 let Some(descriptor) = node
14445 .parent()
14446 .filter(|parent| parent.kind() == "type_descriptor")
14447 else {
14448 return false;
14449 };
14450 if descriptor.child_by_field_name("type") != Some(node) {
14451 return false;
14452 }
14453 let Some(arguments) = descriptor
14454 .parent()
14455 .filter(|parent| parent.kind() == "template_argument_list")
14456 else {
14457 return false;
14458 };
14459 arguments.parent().is_some_and(|owner| {
14460 matches!(
14461 owner.kind(),
14462 "template_type" | "template_function" | "template_method"
14463 ) && owner.child_by_field_name("arguments") == Some(arguments)
14464 })
14465}
14466
14467pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14480 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14481}
14482
14483pub fn is_declarator_node(node: Node<'_>) -> bool {
14484 matches!(
14485 node.kind(),
14486 "identifier"
14487 | "field_identifier"
14488 | "qualified_identifier"
14489 | "scoped_identifier"
14490 | "pointer_declarator"
14491 | "reference_declarator"
14492 | "array_declarator"
14493 | "parenthesized_declarator"
14494 | "function_declarator"
14495 )
14496}
14497
14498pub fn declaration_declarator<'tree>(
14507 declaration: Node<'tree>,
14508 child: Node<'tree>,
14509) -> Option<Node<'tree>> {
14510 if !matches!(
14511 declaration.kind(),
14512 "declaration"
14513 | "field_declaration"
14514 | "parameter_declaration"
14515 | "optional_parameter_declaration"
14516 | "function_definition"
14517 | "type_definition"
14518 | "alias_declaration"
14519 | "template_instantiation"
14520 ) {
14521 return None;
14522 }
14523 if declaration
14524 .child_by_field_name("type")
14525 .is_some_and(|type_node| same_node(type_node, child))
14526 {
14527 return None;
14528 }
14529 if child.kind() == "init_declarator" {
14530 return child.child_by_field_name("declarator");
14531 }
14532 let field = field_name_in_parent(declaration, child);
14533 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14534 || (declaration.kind() == "type_definition"
14535 && child.kind() == "type_identifier"
14536 && matches!(field, Some("declarator") | None))
14537 {
14538 Some(child)
14539 } else {
14540 None
14541 }
14542}
14543
14544pub fn cpp_binding_parts<'tree>(
14546 node: Node<'tree>,
14547 source: &str,
14548) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14549 let declarator = node.child_by_field_name("declarator")?;
14550 let name = extract_variable_name(declarator, source)?;
14551 let type_node = node
14552 .child_by_field_name("type")
14553 .or_else(|| first_type_child(node));
14554 Some((name, declarator, type_node))
14555}
14556
14557#[derive(Clone, Debug, PartialEq, Eq)]
14560pub struct RecoveredNamespaceRegion {
14561 pub start: usize,
14563 pub end: usize,
14565 pub components: Vec<String>,
14568 pub component_ranges: Vec<Range>,
14579 pub component_name_ranges: Vec<Range>,
14585 pub component_closes: Vec<Option<Range>>,
14591}
14592
14593#[derive(Clone, Debug, Default)]
14658pub struct OrphanedNamespaceScopeIndex {
14659 regions: Vec<RecoveredNamespaceRegion>,
14660 brace_closes: HashMap<usize, Range>,
14663}
14664
14665impl OrphanedNamespaceScopeIndex {
14666 pub fn build(root: Node<'_>, source: &str) -> Self {
14667 if !root.has_error() {
14668 return Self::default();
14669 }
14670 #[derive(Clone, Copy)]
14676 struct OpenHead {
14677 head: Range,
14678 open: usize,
14679 }
14680 struct PendingRegion {
14683 start: usize,
14684 end: usize,
14685 components: Vec<String>,
14686 heads: Vec<OpenHead>,
14687 name_ranges: Vec<Range>,
14688 }
14689 struct Frame<'tree> {
14690 node: Node<'tree>,
14691 children: Vec<Node<'tree>>,
14692 next: usize,
14693 parsed_scope: Vec<String>,
14694 run: Option<PendingRegion>,
14695 }
14696 fn frame<'tree>(
14697 node: Node<'tree>,
14698 mut parsed_scope: Vec<String>,
14699 source: &str,
14700 ) -> Frame<'tree> {
14701 if node.kind() == "namespace_definition"
14702 && let Some(name) = node.child_by_field_name("name")
14703 {
14704 let mut components = Vec::new();
14705 if append_cpp_name_components(name, source, &mut components).is_some() {
14706 parsed_scope.extend(components);
14707 }
14708 }
14709 let mut cursor = node.walk();
14710 Frame {
14711 node,
14712 children: node.children(&mut cursor).collect(),
14713 next: 0,
14714 parsed_scope,
14715 run: None,
14716 }
14717 }
14718 let mut regions = Vec::new();
14719 let mut brace_closes = HashMap::default();
14720 let mut namespace_brace_proofs = Vec::new();
14727 let mut unpaired_closes: Vec<Range> = Vec::new();
14733 let mut open = Vec::new();
14737 let mut lexical_scope = Vec::new();
14738 let mut lexical_heads: Vec<OpenHead> = Vec::new();
14743 let mut lexical_name_ranges: Vec<Range> = Vec::new();
14746 let mut frames = vec![frame(root, Vec::new(), source)];
14747 while !frames.is_empty() {
14752 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14753 let current = frames.last_mut().expect("frames is non-empty");
14754 if current.next == current.children.len() {
14755 regions.extend(frames.pop().expect("the frame just borrowed").run);
14756 continue;
14757 }
14758 let child = current.children[current.next];
14759 current.next += 1;
14760 match child.kind() {
14761 "{" if !child.is_missing() => {
14762 regions.extend(current.run.take());
14763 if let Some(namespace) = parent_node.filter(|parent| {
14764 parent.kind() == "namespace_definition"
14765 && parent.child_by_field_name("body") == Some(current.node)
14766 && child.start_byte() == current.node.start_byte()
14767 }) && let Some(proof) = namespace_body_brace_proof(current.node)
14768 {
14769 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14770 }
14771 let mut named = parent_node.and_then(|parent| {
14776 namespace_body_name_components(parent, current.node, source)
14777 });
14778 if named.is_none() {
14779 named = recovered_namespace_open_components(
14780 ¤t.children[..current.next - 1],
14781 source,
14782 );
14783 }
14784 open.push((child.start_byte(), lexical_scope.len()));
14785 if let Some((written_at, components)) = named {
14786 let head = OpenHead {
14787 head: Range {
14788 start_byte: written_at.start_byte(),
14789 end_byte: child.end_byte(),
14790 start_line: written_at.start_position().row + 1,
14791 end_line: child.end_position().row + 1,
14792 },
14793 open: child.start_byte(),
14794 };
14795 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14796 lexical_name_ranges
14797 .extend(components.iter().map(|(_, name_range)| *name_range));
14798 lexical_scope
14799 .extend(components.into_iter().map(|(component, _)| component));
14800 }
14801 continue;
14802 }
14803 "}" if !child.is_missing() => {
14804 regions.extend(current.run.take());
14805 let close = Range {
14806 start_byte: child.start_byte(),
14807 end_byte: child.end_byte(),
14808 start_line: child.start_position().row + 1,
14809 end_line: child.end_position().row + 1,
14810 };
14811 match open.pop() {
14812 Some((start, namespace_len)) => {
14813 debug_assert!(
14814 start < close.start_byte,
14815 "a close pairs with an open before it: {start} then {close:?}"
14816 );
14817 lexical_scope.truncate(namespace_len);
14818 lexical_heads.truncate(namespace_len);
14819 lexical_name_ranges.truncate(namespace_len);
14820 brace_closes.insert(start, close);
14821 }
14822 None => unpaired_closes.push(close),
14823 }
14824 continue;
14825 }
14826 _ => {}
14827 }
14828 if current.node.kind() != "namespace_definition"
14832 && lexical_scope != current.parsed_scope
14833 {
14834 match &mut current.run {
14835 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14836 run => {
14837 regions.extend(run.take());
14838 *run = Some(PendingRegion {
14839 start: child.start_byte(),
14840 end: child.end_byte(),
14841 components: lexical_scope.clone(),
14842 heads: lexical_heads.clone(),
14843 name_ranges: lexical_name_ranges.clone(),
14844 });
14845 }
14846 }
14847 } else {
14848 regions.extend(current.run.take());
14849 }
14850 let descend = child.has_error()
14858 || (child.kind() == "function_definition"
14859 && child.child_by_field_name("body").is_some_and(|body| {
14860 namespace_body_name_components(child, body, source).is_some()
14861 }));
14862 if descend {
14863 let parsed_scope = current.parsed_scope.clone();
14864 frames.push(frame(child, parsed_scope, source));
14865 }
14866 }
14867 if !unpaired_closes.is_empty() {
14876 brace_closes.clear();
14877 } else {
14878 let missing_namespaces = namespace_brace_proofs
14884 .iter()
14885 .filter_map(|(_, namespace, proof)| {
14886 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14887 })
14888 .collect::<HashSet<_>>();
14889 for (open, namespace, proof) in namespace_brace_proofs {
14890 if !std::iter::successors(Some(namespace), |node| node.parent())
14891 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14892 {
14893 continue;
14894 }
14895 match proof {
14896 NamespaceBodyBraceProof::Real(close) => {
14897 brace_closes.insert(open, close);
14898 }
14899 NamespaceBodyBraceProof::Missing => {
14900 brace_closes.remove(&open);
14901 }
14902 }
14903 }
14904 }
14905 let regions = regions
14910 .into_iter()
14911 .map(|region| {
14912 debug_assert_eq!(
14913 region.components.len(),
14914 region.heads.len(),
14915 "every recovered namespace level carries the head that writes it: {:?}",
14916 region.components
14917 );
14918 debug_assert_eq!(
14919 region.components.len(),
14920 region.name_ranges.len(),
14921 "every recovered namespace level carries the name token that writes it: {:?}",
14922 region.components
14923 );
14924 let component_closes: Vec<_> = region
14925 .heads
14926 .iter()
14927 .map(|level| brace_closes.get(&level.open).copied())
14928 .collect();
14929 let component_ranges = region
14930 .heads
14931 .iter()
14932 .zip(&component_closes)
14933 .map(|(level, close)| match close {
14934 Some(close) => Range {
14935 start_byte: level.head.start_byte,
14936 end_byte: close.end_byte,
14937 start_line: level.head.start_line,
14938 end_line: close.end_line,
14939 },
14940 None => level.head,
14941 })
14942 .collect();
14943 RecoveredNamespaceRegion {
14944 start: region.start,
14945 end: region.end,
14946 components: region.components,
14947 component_ranges,
14948 component_name_ranges: region.name_ranges,
14949 component_closes,
14950 }
14951 })
14952 .collect();
14953 Self {
14954 regions,
14955 brace_closes,
14956 }
14957 }
14958
14959 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14966 self.brace_closes.get(&open).copied()
14967 }
14968
14969 pub fn is_empty(&self) -> bool {
14970 self.regions.is_empty()
14971 }
14972
14973 pub fn approximate_size(&self) -> usize {
14975 self.regions.iter().fold(
14976 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14977 |total, region| {
14978 total
14979 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14980 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14981 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14982 .saturating_add(
14983 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14984 )
14985 .saturating_add(
14986 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14987 )
14988 },
14989 )
14990 }
14991
14992 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14994 self.regions
14995 .iter()
14996 .filter(|region| region.start <= byte && byte < region.end)
14997 .min_by_key(|region| region.end - region.start)
14998 }
14999
15000 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15004 self.enclosing_namespace_levels(node, source)
15005 .into_iter()
15006 .map(|(component, ..)| component)
15007 .collect()
15008 }
15009
15010 pub fn enclosing_namespace_levels(
15019 &self,
15020 node: Node<'_>,
15021 source: &str,
15022 ) -> Vec<(String, Range, Range)> {
15023 let mut parsed = Vec::new();
15024 let mut current = node.parent();
15025 while let Some(parent) = current {
15026 if parent.kind() == "namespace_definition"
15027 && let Some(name) = parent.child_by_field_name("name")
15028 && let Some(components) = cpp_name_component_ranges(name, source)
15029 {
15030 let range = node_range(parent);
15031 parsed.push((
15032 parent.start_byte(),
15033 components
15034 .into_iter()
15035 .map(|(component, name_range)| (component, range, name_range))
15036 .collect(),
15037 namespace_definition_has_real_braces(parent),
15038 ));
15039 }
15040 current = parent.parent();
15041 }
15042 parsed.reverse();
15043 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15044 region
15045 .components
15046 .iter()
15047 .take(active_components)
15048 .cloned()
15049 .zip(
15050 region
15051 .component_ranges
15052 .iter()
15053 .take(active_components)
15054 .copied(),
15055 )
15056 .zip(
15057 region
15058 .component_name_ranges
15059 .iter()
15060 .take(active_components)
15061 .copied(),
15062 )
15063 .map(|((component, range), name_range)| (component, range, name_range))
15064 .collect()
15065 })
15066 }
15067
15068 pub fn restore_enclosing_namespaces(
15074 &self,
15075 parsed: Vec<(usize, Vec<String>, bool)>,
15076 node_start: usize,
15077 ) -> Vec<String> {
15078 self.splice_recovered_path(parsed, node_start, |region, active_components| {
15079 region
15080 .components
15081 .iter()
15082 .take(active_components)
15083 .cloned()
15084 .collect()
15085 })
15086 }
15087
15088 fn splice_recovered_path<T>(
15094 &self,
15095 parsed: Vec<(usize, Vec<T>, bool)>,
15096 node_start: usize,
15097 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15098 ) -> Vec<T> {
15099 let Some(region) = self.region_at(node_start) else {
15100 return parsed
15101 .into_iter()
15102 .flat_map(|(_, levels, _)| levels)
15103 .collect();
15104 };
15105 debug_assert_eq!(
15106 region.components.len(),
15107 region.component_closes.len(),
15108 "every recovered namespace component carries its close proof: {region:?}"
15109 );
15110 debug_assert_eq!(
15111 region.components.len(),
15112 region.component_name_ranges.len(),
15113 "every recovered namespace component carries its name token: {region:?}"
15114 );
15115 if region.components.is_empty() {
15116 return parsed
15117 .into_iter()
15118 .filter(|(_, _, complete)| *complete)
15119 .flat_map(|(_, levels, _)| levels)
15120 .collect();
15121 }
15122 let active_components = region
15123 .component_closes
15124 .iter()
15125 .take_while(|close| {
15126 close
15127 .as_ref()
15128 .is_none_or(|close| node_start < close.end_byte)
15129 })
15130 .count();
15131 recovered(region, active_components)
15132 .into_iter()
15133 .chain(
15134 parsed
15135 .into_iter()
15136 .filter(|(start, _, _)| *start >= region.start)
15137 .flat_map(|(_, levels, _)| levels),
15138 )
15139 .collect()
15140 }
15141}
15142
15143pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15147 debug_assert_eq!(namespace.kind(), "namespace_definition");
15148 let Some(body) = namespace.child_by_field_name("body") else {
15149 return false;
15150 };
15151 let mut opening = false;
15152 let mut closing = false;
15153 let mut cursor = body.walk();
15154 for child in body.children(&mut cursor) {
15155 match child.kind() {
15156 "{" if !child.is_missing() => opening = true,
15157 "}" if !child.is_missing() => closing = true,
15158 _ => {}
15159 }
15160 }
15161 opening && closing
15162}
15163
15164fn recovered_namespace_open_components<'tree>(
15180 preceding: &[Node<'tree>],
15181 source: &str,
15182) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15183 let mut head = Vec::new();
15184 for &sibling in preceding.iter().rev() {
15185 if sibling.kind() != "comment" {
15186 head.push(sibling);
15187 if head.len() == 2 {
15188 break;
15189 }
15190 }
15191 }
15192 let [name, keyword] = head[..] else {
15193 return None;
15194 };
15195 if keyword.kind() != "namespace" {
15196 return None;
15197 }
15198 let components = cpp_name_component_ranges(name, source)?;
15199 (!components.is_empty()).then_some((keyword, components))
15200}
15201
15202fn namespace_body_name_components<'tree>(
15207 parent: Node<'tree>,
15208 body: Node<'tree>,
15209 source: &str,
15210) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15211 if parent.child_by_field_name("body") != Some(body) {
15212 return None;
15213 }
15214 let (written_at, name) = match (parent.kind(), body.kind()) {
15215 ("namespace_definition", "declaration_list") => {
15216 (parent, parent.child_by_field_name("name")?)
15217 }
15218 ("function_definition", "compound_statement") => {
15219 recovered_literal_namespace_head(parent, body, source)
15225 .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15226 }
15227 _ => return None,
15228 };
15229 let components = cpp_name_component_ranges(name, source)?;
15230 (!components.is_empty()).then_some((written_at, components))
15231}
15232
15233fn recovered_literal_namespace_head<'tree>(
15236 parent: Node<'tree>,
15237 body: Node<'tree>,
15238 source: &str,
15239) -> Option<(Node<'tree>, Node<'tree>)> {
15240 let mut cursor = parent.walk();
15244 let children = parent
15245 .children(&mut cursor)
15246 .filter(|child| child.kind() != "comment")
15247 .collect::<Vec<_>>();
15248 let [prefix, keyword, error, actual_body] = children.as_slice() else {
15249 return None;
15250 };
15251 if *actual_body != body
15252 || parent.child_by_field_name("type") != Some(*prefix)
15253 || parent.child_by_field_name("declarator") != Some(*keyword)
15254 || prefix.kind() != "type_identifier"
15255 || prefix.is_missing()
15256 || prefix.has_error()
15257 || prefix.child_count() != 0
15258 || keyword.kind() != "identifier"
15259 || keyword.is_missing()
15260 || keyword.has_error()
15261 || keyword.child_count() != 0
15262 || node_text(*keyword, source) != "namespace"
15263 || error.kind() != "ERROR"
15264 || error.is_missing()
15265 || body.is_missing()
15266 {
15267 return None;
15268 }
15269 let mut cursor = error.walk();
15270 let names = error
15271 .children(&mut cursor)
15272 .filter(|child| child.kind() != "comment")
15273 .collect::<Vec<_>>();
15274 let [name] = names.as_slice() else {
15275 return None;
15276 };
15277 if !matches!(name.kind(), "identifier" | "namespace_identifier")
15278 || name.is_missing()
15279 || name.has_error()
15280 || name.child_count() != 0
15281 {
15282 return None;
15283 }
15284 let open = body.child(0)?;
15285 if open.kind() != "{"
15286 || open.is_missing()
15287 || open.has_error()
15288 || open.start_byte() != body.start_byte()
15289 {
15290 return None;
15291 }
15292 Some((*keyword, *name))
15295}
15296
15297fn macro_modifier_namespace_head<'tree>(
15301 parent: Node<'tree>,
15302 body: Node<'tree>,
15303 source: &str,
15304) -> Option<(Node<'tree>, Node<'tree>)> {
15305 let mut cursor = parent.walk();
15313 let children = parent
15314 .children(&mut cursor)
15315 .filter(|child| child.kind() != "comment")
15316 .collect::<Vec<_>>();
15317 let [prefix, keyword, name, actual_body] = children.as_slice() else {
15318 return None;
15319 };
15320 if *actual_body != body
15321 || parent.child_by_field_name("type") != Some(*keyword)
15322 || parent.child_by_field_name("declarator") != Some(*name)
15323 || prefix.kind() != "macro_modifier"
15324 || prefix.is_missing()
15325 || prefix.has_error()
15326 || macro_modifier_identifier(*prefix).is_none()
15327 || keyword.kind() != "type_identifier"
15328 || keyword.is_missing()
15329 || keyword.has_error()
15330 || keyword.child_count() != 0
15331 || node_text(*keyword, source) != "namespace"
15332 || !matches!(name.kind(), "identifier" | "namespace_identifier")
15333 || name.is_missing()
15334 || name.has_error()
15335 || name.child_count() != 0
15336 || body.is_missing()
15337 {
15338 return None;
15339 }
15340 let open = body.child(0)?;
15341 if open.kind() != "{"
15342 || open.is_missing()
15343 || open.has_error()
15344 || open.start_byte() != body.start_byte()
15345 {
15346 return None;
15347 }
15348 Some((*keyword, *name))
15351}
15352
15353#[derive(Clone, Copy)]
15354enum NamespaceBodyBraceProof {
15355 Real(Range),
15356 Missing,
15357}
15358
15359fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15365 if body.kind() != "declaration_list" {
15366 return None;
15367 }
15368 let mut cursor = body.walk();
15369 let children = body.children(&mut cursor).collect::<Vec<_>>();
15370 let close_index = children
15371 .iter()
15372 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15373 let close = children[close_index];
15374 if close.is_missing() {
15375 return Some(NamespaceBodyBraceProof::Missing);
15376 }
15377 let namespace_terminator_sibling = body
15378 .parent()
15379 .filter(|namespace| namespace.kind() == "namespace_definition")
15380 .and_then(|namespace| namespace.next_sibling())
15381 .is_some_and(|sibling| {
15382 sibling.kind() == ";"
15383 || (sibling.kind() == "expression_statement"
15384 && sibling.child_count() == 1
15385 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15386 });
15387 if namespace_terminator_sibling
15392 || children
15393 .get(close_index + 1)
15394 .is_some_and(|sibling| sibling.kind() == ";")
15395 || children[..close_index]
15396 .iter()
15397 .rev()
15398 .find(|child| !child.is_extra() && child.kind() != "{")
15399 .is_some_and(|previous| {
15400 matches!(
15401 previous.kind(),
15402 "expression_statement" | "labeled_statement"
15403 )
15404 })
15405 {
15406 return None;
15407 }
15408 Some(NamespaceBodyBraceProof::Real(node_range(close)))
15409}
15410
15411#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15412pub enum RecoveredDeclaratorTypeContext {
15413 Declaration,
15414 FunctionDefinition,
15415 Parameter,
15416}
15417
15418pub fn recovered_macro_decorated_declarator_type(
15433 node: Node<'_>,
15434) -> Option<RecoveredDeclaratorTypeContext> {
15435 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15436}
15437
15438pub fn recovered_macro_decorated_type_node(
15443 node: Node<'_>,
15444) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15445 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15446 return None;
15447 }
15448 let qualified = node.parent()?;
15449 if qualified.kind() != "qualified_identifier"
15450 || qualified.child_by_field_name("scope") != Some(node)
15451 || !(0..qualified.child_count())
15452 .filter_map(|index| qualified.child(index))
15453 .any(|child| child.kind() == "::" && child.is_missing())
15454 {
15455 return None;
15456 }
15457 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15458 return None;
15459 }
15460
15461 let (declaration, context) = recovered_declarator_container(qualified)?;
15462 let type_node = declaration
15463 .child_by_field_name("type")
15464 .filter(|type_node| {
15465 *type_node != qualified
15466 && !type_node.is_missing()
15467 && type_node.start_byte() != type_node.end_byte()
15468 })?;
15469 Some((type_node, context))
15470}
15471
15472pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
15479 matches!(node.kind(), "type_identifier" | "template_type")
15480 && node.parent().is_some_and(|declaration| {
15481 matches!(
15482 declaration.kind(),
15483 "declaration"
15484 | "function_definition"
15485 | "parameter_declaration"
15486 | "optional_parameter_declaration"
15487 ) && declaration.child_by_field_name("type") == Some(node)
15488 && declaration.child_by_field_name("declarator").is_some()
15489 && crate::declarations::cpp_declaration_type_follows_macro_modifier(
15490 declaration,
15491 node,
15492 )
15493 })
15494}
15495
15496fn recovered_declarator_container(
15497 mut declarator: Node<'_>,
15498) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15499 loop {
15500 let parent = declarator.parent()?;
15501 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15502 return Some((
15503 parent
15504 .parent()
15505 .filter(|declaration| declaration.kind() == "declaration")?,
15506 RecoveredDeclaratorTypeContext::Declaration,
15507 ));
15508 }
15509 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15510 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15511 }
15512 if parent.kind() == "function_definition"
15513 && has_field_child(parent, "declarator", declarator)
15514 {
15515 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15516 }
15517 if matches!(
15523 parent.kind(),
15524 "parameter_declaration" | "optional_parameter_declaration"
15525 ) && has_field_child(parent, "declarator", declarator)
15526 {
15527 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15528 }
15529 if !matches!(
15530 parent.kind(),
15531 "array_declarator"
15532 | "function_declarator"
15533 | "parenthesized_declarator"
15534 | "pointer_declarator"
15535 | "pointer_type_declarator"
15536 | "reference_declarator"
15537 ) || !has_field_child(parent, "declarator", declarator)
15538 {
15539 return None;
15540 }
15541 declarator = parent;
15542 }
15543}
15544
15545fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15546 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15547}
15548
15549fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15550 loop {
15551 if node.is_missing() || node.start_byte() == node.end_byte() {
15552 return false;
15553 }
15554 match node.kind() {
15555 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15556 return true;
15557 }
15558 "array_declarator"
15559 | "function_declarator"
15560 | "parenthesized_declarator"
15561 | "pointer_declarator"
15562 | "pointer_type_declarator"
15563 | "reference_declarator" => {
15564 let Some(declarator) = node.child_by_field_name("declarator") else {
15565 return false;
15566 };
15567 node = declarator;
15568 }
15569 _ => return false,
15570 }
15571 }
15572}
15573
15574pub enum DesignatedInitializerOwner {
15576 Resolved(CodeUnit),
15577 Unresolved,
15578}
15579
15580enum InitializerOwnerStep {
15581 Field(String),
15582 AggregateWrapper,
15583}
15584
15585pub fn designated_initializer_owner(
15595 analyzer: &CppGraphSource<'_>,
15596 visibility: &VisibilityIndex<'_>,
15597 file: &ProjectFile,
15598 source: &str,
15599 node: Node<'_>,
15600) -> Option<DesignatedInitializerOwner> {
15601 if let Some(designator) = node
15602 .parent()
15603 .filter(|parent| parent.kind() == "field_designator")
15604 {
15605 let pair = designator.parent()?;
15606 if pair.kind() != "initializer_pair" {
15607 return None;
15608 }
15609 let mut cursor = pair.walk();
15610 let designators = pair
15611 .children_by_field_name("designator", &mut cursor)
15612 .collect::<Vec<_>>();
15613 let position = designators
15614 .iter()
15615 .position(|candidate| same_node(*candidate, designator))?;
15616 let initializer = pair.parent()?;
15617 if initializer.kind() != "initializer_list" {
15618 return None;
15619 }
15620 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15621 for prior in &designators[..position] {
15622 let field = prior
15623 .child_by_field_name("field")
15624 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15625 owner = owner.and_then(|owner| {
15626 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15627 });
15628 }
15629 return Some(classified_designated_owner(owner));
15630 }
15631
15632 let init_declarator = node.parent()?;
15633 if init_declarator.child_by_field_name("declarator") != Some(node)
15634 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15635 {
15636 return None;
15637 }
15638 Some(classified_designated_owner(declaration_owner(
15639 analyzer,
15640 visibility,
15641 file,
15642 source,
15643 init_declarator.parent()?,
15644 )))
15645}
15646
15647fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15648 owner.map_or(
15649 DesignatedInitializerOwner::Unresolved,
15650 DesignatedInitializerOwner::Resolved,
15651 )
15652}
15653
15654fn initializer_list_owner(
15655 analyzer: &CppGraphSource<'_>,
15656 visibility: &VisibilityIndex<'_>,
15657 file: &ProjectFile,
15658 source: &str,
15659 initializer: Node<'_>,
15660) -> Option<CodeUnit> {
15661 let mut current = initializer;
15662 let mut steps = Vec::new();
15663 loop {
15664 let parent = current.parent()?;
15665 match parent.kind() {
15666 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15667 let designator = parent.child_by_field_name("designator")?;
15668 let step = designator
15669 .child_by_field_name("field")
15670 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15671 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15672 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15673 steps.push(step);
15674 current = parent.parent()?;
15675 }
15676 "initializer_list" => {
15677 current = parent;
15678 }
15679 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15680 let declaration = parent.parent()?;
15681 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15682 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15683 }
15684 "compound_literal_expression"
15685 if parent.child_by_field_name("value") == Some(current) =>
15686 {
15687 let type_node = parent.child_by_field_name("type")?;
15688 let owner =
15689 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15690 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15691 }
15692 "ERROR" => current = parent,
15693 _ => return None,
15694 }
15695 }
15696}
15697
15698fn apply_initializer_owner_steps(
15699 analyzer: &CppGraphSource<'_>,
15700 visibility: &VisibilityIndex<'_>,
15701 file: &ProjectFile,
15702 mut owner: CodeUnit,
15703 steps: Vec<InitializerOwnerStep>,
15704) -> Option<CodeUnit> {
15705 for step in steps.into_iter().rev() {
15706 if let InitializerOwnerStep::Field(field_name) = step {
15707 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15708 }
15709 }
15710 Some(owner)
15711}
15712
15713fn initializer_field_owner(
15714 analyzer: &CppGraphSource<'_>,
15715 visibility: &VisibilityIndex<'_>,
15716 file: &ProjectFile,
15717 owner: CodeUnit,
15718 field_name: &str,
15719) -> Option<CodeUnit> {
15720 let fields = visibility
15721 .visible_members_for_owner_name(file, &owner, field_name)
15722 .into_iter()
15723 .filter(|field| field.is_field())
15724 .collect::<Vec<_>>();
15725 let field = match fields.as_slice() {
15726 [field] => *field,
15727 _ => return None,
15728 };
15729 field_declared_binding(analyzer, visibility, file, field)?.unit
15730}
15731
15732fn declaration_owner(
15733 analyzer: &CppGraphSource<'_>,
15734 visibility: &VisibilityIndex<'_>,
15735 file: &ProjectFile,
15736 source: &str,
15737 declaration: Node<'_>,
15738) -> Option<CodeUnit> {
15739 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15740 return None;
15741 }
15742 let type_node = declaration
15743 .child_by_field_name("type")
15744 .or_else(|| first_type_child(declaration))?;
15745 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15746}
15747
15748fn resolve_designated_owner_type(
15749 analyzer: &CppGraphSource<'_>,
15750 visibility: &VisibilityIndex<'_>,
15751 file: &ProjectFile,
15752 source: &str,
15753 type_node: Node<'_>,
15754) -> Option<CodeUnit> {
15755 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15756 return Some(owner);
15757 }
15758 let type_name = normalize_type_text(node_text(type_node, source));
15759 visibility
15760 .resolve_type(file, &type_name)
15761 .filter(CodeUnit::is_class)
15762}
15763
15764pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15765 let mut cursor = node.walk();
15766 node.named_children(&mut cursor).find(|child| {
15767 matches!(
15768 child.kind(),
15769 "type_identifier"
15770 | "primitive_type"
15771 | "qualified_identifier"
15772 | "scoped_type_identifier"
15773 | "struct_specifier"
15774 | "union_specifier"
15775 | "enum_specifier"
15776 )
15777 })
15778}
15779
15780pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15781 visibility: &VisibilityIndex<'_>,
15782 file: &ProjectFile,
15783 source: &str,
15784 declarator: Node<'_>,
15785 type_text: Option<&str>,
15786 bindings: &LocalInferenceEngine<T>,
15787) -> bool {
15788 if !has_ancestor_kind(declarator, "compound_statement") {
15789 return false;
15790 }
15791 if declarator
15792 .child_by_field_name("declarator")
15793 .is_none_or(|declarator| declarator.kind() != "identifier")
15794 {
15795 return false;
15796 }
15797 if !type_text
15798 .and_then(|text| visibility.resolve_type(file, text))
15799 .is_some_and(|unit| unit.is_class())
15800 {
15801 return false;
15802 }
15803 declarator
15804 .child_by_field_name("parameters")
15805 .is_some_and(|parameters| {
15806 constructor_parameters_look_like_expressions(parameters, source, bindings)
15807 })
15808}
15809
15810fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15811 parameters: Node<'_>,
15812 source: &str,
15813 bindings: &LocalInferenceEngine<T>,
15814) -> bool {
15815 let mut cursor = parameters.walk();
15816 parameters.named_children(&mut cursor).any(|parameter| {
15817 !matches!(
15818 parameter.kind(),
15819 "parameter_declaration" | "optional_parameter_declaration"
15820 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15821 })
15822}
15823
15824fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15825 parameter: Node<'_>,
15826 source: &str,
15827 bindings: &LocalInferenceEngine<T>,
15828) -> bool {
15829 let text = node_text(parameter, source).trim();
15830 if text
15831 .chars()
15832 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15833 && bindings.is_shadowed(text)
15834 {
15835 return true;
15836 }
15837
15838 let Some(base) = parameter
15839 .child_by_field_name("type")
15840 .filter(|base| base.kind() == "type_identifier")
15841 else {
15842 return false;
15843 };
15844 let Some(subscript) = parameter
15845 .child_by_field_name("declarator")
15846 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15847 else {
15848 return false;
15849 };
15850 subscript.child_by_field_name("size").is_some()
15851 && bindings.is_shadowed(node_text(base, source).trim())
15852}
15853
15854pub fn is_declaration_name(node: Node<'_>) -> bool {
15855 let Some(parent) = node.parent() else {
15856 return false;
15857 };
15858 if parent
15859 .child_by_field_name("name")
15860 .is_some_and(|name| same_node(name, node))
15861 {
15862 if matches!(
15863 parent.kind(),
15864 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15865 ) {
15866 return cpp_tag_specifier_declares_name(parent);
15867 }
15868 if matches!(
15869 parent.kind(),
15870 "namespace_definition"
15871 | "namespace_alias_definition"
15872 | "alias_declaration"
15873 | "enumerator"
15874 ) {
15875 return true;
15876 }
15877 }
15878
15879 let mut current = Some(parent);
15880 while let Some(ancestor) = current {
15881 let type_definition = ancestor.kind() == "type_definition";
15882 let mut child_cursor = ancestor.walk();
15883 if ancestor.named_children(&mut child_cursor).any(|child| {
15884 declaration_declarator(ancestor, child).is_some_and(|declarator| {
15885 declarator_name_path_contains(declarator, node, type_definition)
15886 })
15887 }) {
15888 return true;
15889 }
15890 if matches!(
15891 ancestor.kind(),
15892 "declaration"
15893 | "field_declaration"
15894 | "parameter_declaration"
15895 | "optional_parameter_declaration"
15896 | "function_definition"
15897 | "type_definition"
15898 | "alias_declaration"
15899 | "template_instantiation"
15900 | "class_specifier"
15901 | "struct_specifier"
15902 | "union_specifier"
15903 | "enum_specifier"
15904 ) {
15905 return false;
15906 }
15907 current = ancestor.parent();
15908 }
15909 false
15910}
15911
15912pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15921 if !matches!(
15922 node.kind(),
15923 "qualified_identifier" | "scoped_type_identifier"
15924 ) {
15925 return false;
15926 }
15927 let Some(declaration) = node
15928 .parent()
15929 .filter(|parent| parent.kind() == "declaration")
15930 else {
15931 return false;
15932 };
15933 if declaration.child_by_field_name("declarator") != Some(node)
15934 || !declaration
15935 .child_by_field_name("type")
15936 .is_some_and(|friend| {
15937 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15938 })
15939 {
15940 return false;
15941 }
15942 let mut cursor = declaration.walk();
15943 let mut errors = declaration
15944 .named_children(&mut cursor)
15945 .filter(|child| child.kind() == "ERROR");
15946 let Some(error) = errors.next() else {
15947 return false;
15948 };
15949 errors.next().is_none()
15950 && error.named_child_count() == 1
15951 && error.named_child(0).is_some_and(|class| {
15952 class.kind() == "identifier" && node_text(class, source) == "class"
15953 })
15954}
15955
15956pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15957 if !matches!(node.kind(), "identifier" | "field_identifier") {
15958 return false;
15959 }
15960 if let Some(parent) = node.parent() {
15961 if parent.kind() == "call_expression"
15962 && parent.child_by_field_name("function") == Some(node)
15963 {
15964 return false;
15965 }
15966 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15967 && parent.child_by_field_name("label") == Some(node)
15968 {
15969 return false;
15970 }
15971 }
15972 if is_declaration_name(node) {
15973 return false;
15974 }
15975 let mut current = node.parent();
15976 while let Some(ancestor) = current {
15977 match ancestor.kind() {
15978 "preproc_ifdef" | "preproc_ifndef" => {
15979 if ancestor
15980 .child_by_field_name("name")
15981 .is_some_and(|name| node_range_contains(name, node))
15982 {
15983 return false;
15984 }
15985 }
15986 "preproc_if" | "preproc_elif" => {
15987 if ancestor
15988 .child_by_field_name("condition")
15989 .is_some_and(|condition| node_range_contains(condition, node))
15990 {
15991 return false;
15992 }
15993 }
15994 "preproc_else" => {}
15995 kind if kind.starts_with("preproc_") => return false,
15996 _ => {}
15997 }
15998 if matches!(
15999 ancestor.kind(),
16000 "translation_unit" | "function_definition" | "compound_statement"
16001 ) {
16002 break;
16003 }
16004 current = ancestor.parent();
16005 }
16006 true
16007}
16008
16009fn recovered_c_reference_node(
16010 visibility: &VisibilityIndex<'_>,
16011 file: &ProjectFile,
16012 node: Node<'_>,
16013 source: &str,
16014) -> bool {
16015 if node.start_byte() >= node.end_byte()
16016 || node.is_error()
16017 || node.is_missing()
16018 || !matches!(
16019 node.kind(),
16020 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16021 )
16022 || recovered_c_macro_binding_role(node)
16023 || recovered_c_label_role(node)
16024 {
16025 return false;
16026 }
16027 let name = node_text(node, source);
16035 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16036 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16037 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16038 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16039 if is_declaration_name(node)
16040 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16041 && !recovered_function_call
16042 && !recovered_macro_call
16043 && !recovered_parenthesized_reference
16044 {
16045 return false;
16046 }
16047
16048 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16049 return true;
16050 }
16051 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16052 return true;
16053 }
16054 if recovered_parenthesized_reference {
16055 return true;
16056 }
16057 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16058 if recovered_function_call {
16059 return true;
16060 }
16061 return visibility
16062 .visible_identifier_candidates(file, name)
16063 .any(|candidate| candidate.is_class() || candidate.is_module());
16064 }
16065 let visible = visibility
16066 .visible_identifier_candidates(file, name)
16067 .next()
16068 .is_some();
16069 visible
16070 && (recovered_c_reference_anchor(node)
16071 || recovered_c_error_expression_leaf(node)
16072 || recovered_function_call)
16073}
16074
16075fn push_recovered_c_range(
16076 ranges: &mut Vec<Range>,
16077 seen: &mut HashSet<(usize, usize)>,
16078 start_byte: usize,
16079 end_byte: usize,
16080 node: Node<'_>,
16081 limit: usize,
16082) -> bool {
16083 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16084 return true;
16085 }
16086 if ranges.len() >= limit {
16087 return false;
16088 }
16089 ranges.push(Range {
16090 start_byte,
16091 end_byte,
16092 start_line: node.start_position().row,
16093 end_line: node.end_position().row,
16094 });
16095 true
16096}
16097
16098fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16103 let mut current = node.parent();
16104 while let Some(parent) = current {
16105 if parent.is_error() {
16106 let Some(anchor) = parent.parent() else {
16107 return false;
16108 };
16109 return anchor.kind().ends_with("_expression")
16110 || matches!(
16111 anchor.kind(),
16112 "argument_list"
16113 | "return_statement"
16114 | "expression_statement"
16115 | "case_statement"
16116 | "initializer_list"
16117 | "field_designator"
16118 | "enumerator"
16119 );
16120 }
16121 if matches!(
16122 parent.kind(),
16123 "translation_unit" | "function_definition" | "compound_statement"
16124 ) {
16125 return false;
16126 }
16127 current = parent.parent();
16128 }
16129 false
16130}
16131
16132fn recovered_c_function_call(
16139 visibility: &VisibilityIndex<'_>,
16140 file: &ProjectFile,
16141 node: Node<'_>,
16142 name: &str,
16143) -> bool {
16144 if !matches!(
16145 node.kind(),
16146 "identifier" | "field_identifier" | "type_identifier"
16147 ) {
16148 return false;
16149 }
16150 let error_call_prefix = node.parent().is_some_and(|error| {
16155 error.is_error()
16156 && error
16157 .parent()
16158 .is_some_and(|parent| parent.kind() == "compound_statement")
16159 }) && node
16160 .prev_sibling()
16161 .is_none_or(|previous| previous.kind() == ";")
16162 && node.next_sibling().is_some_and(|open| {
16163 open.kind() == "("
16164 && open.next_named_sibling().is_some_and(|argument| {
16165 argument.kind() == "parameter_declaration" && !argument.has_error()
16166 })
16167 });
16168 (error_call_prefix || recovered_c_function_declarator_invocation(node))
16169 && visibility
16170 .visible_identifier_candidates(file, name)
16171 .any(CodeUnit::is_function)
16172}
16173
16174pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16182 if declarator.kind() != "function_declarator" || !declarator.has_error() {
16183 return None;
16184 }
16185 let definition = declarator.parent().filter(|parent| {
16186 parent.kind() == "function_definition"
16187 && parent.child_by_field_name("declarator") == Some(declarator)
16188 })?;
16189 if definition.child_by_field_name("type").is_some()
16190 || definition.named_child(0) != Some(declarator)
16191 || definition.child_by_field_name("body").is_none()
16192 || definition.parent()?.kind() != "compound_statement"
16193 {
16194 return None;
16195 }
16196 Some(definition)
16197}
16198
16199fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16209 let mut current = if node.parent().is_some_and(|parent| {
16214 parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16215 }) {
16216 node.parent().expect("checked macro_type_specifier parent")
16217 } else {
16218 let mut function_declarator = if node.parent().is_some_and(|parent| {
16219 parent.kind() == "function_declarator"
16220 && parent.child_by_field_name("declarator") == Some(node)
16221 }) {
16222 node.parent().expect("checked function declarator parent")
16223 } else {
16224 let Some(parameter) = node.parent().filter(|parent| {
16225 parent.kind() == "parameter_declaration"
16226 && parent.child_by_field_name("type") == Some(node)
16227 }) else {
16228 return false;
16229 };
16230 if !parameter
16231 .child_by_field_name("declarator")
16232 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16233 {
16234 return false;
16235 }
16236 let Some(parameters) = parameter
16237 .parent()
16238 .filter(|parent| parent.kind() == "parameter_list")
16239 else {
16240 return false;
16241 };
16242 let Some(function_declarator) = parameters
16243 .parent()
16244 .filter(|parent| parent.kind() == "function_declarator")
16245 else {
16246 return false;
16247 };
16248 function_declarator
16249 };
16250
16251 while let Some(parent) = function_declarator.parent().filter(|parent| {
16254 parent.kind() == "function_declarator"
16255 && parent.child_by_field_name("declarator") == Some(function_declarator)
16256 }) {
16257 function_declarator = parent;
16258 }
16259 let Some(current) = function_declarator.parent().filter(|parent| {
16264 parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
16265 }) else {
16266 return false;
16267 };
16268 current
16269 };
16270 loop {
16271 let Some(parent) = current.parent() else {
16272 return false;
16273 };
16274 if matches!(
16275 parent.kind(),
16276 "translation_unit"
16277 | "compound_statement"
16278 | "preproc_if"
16279 | "preproc_ifdef"
16280 | "preproc_ifndef"
16281 | "preproc_else"
16282 | "preproc_elif"
16283 ) {
16284 return true;
16285 }
16286 if parent.kind() == "function_definition"
16287 && parent.child_by_field_name("declarator") == Some(current)
16288 && parent.named_child(0) == Some(current)
16289 && parent.child_by_field_name("body").is_some()
16290 {
16291 return true;
16292 }
16293 if parent.is_error()
16294 || matches!(
16295 parent.kind(),
16296 "parameter_declaration"
16297 | "parameter_list"
16298 | "function_declarator"
16299 | "abstract_function_declarator"
16300 | "parenthesized_declarator"
16301 )
16302 {
16303 current = parent;
16304 continue;
16305 }
16306 return false;
16307 }
16308}
16309
16310fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16316 if node
16322 .parent()
16323 .is_some_and(|parent| parent.kind() == "type_descriptor")
16324 {
16325 let Some(type_descriptor) = node.parent() else {
16326 return false;
16327 };
16328 let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
16329 return false;
16330 };
16331 if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
16332 return false;
16333 }
16334 let Some(dependent) = error
16335 .parent()
16336 .and_then(|parent| parent.parent())
16337 .filter(|parent| parent.kind() == "dependent_type")
16338 else {
16339 return false;
16340 };
16341 return dependent
16342 .child(0)
16343 .is_some_and(|keyword| keyword.kind() == "typename");
16344 }
16345 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16346 return false;
16347 };
16348 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16349 return false;
16350 }
16351 let Some(declarator) = error
16352 .parent()
16353 .filter(|parent| parent.kind() == "parenthesized_declarator")
16354 else {
16355 return false;
16356 };
16357 let Some(declaration) = declarator
16358 .parent()
16359 .filter(|parent| parent.kind() == "declaration")
16360 else {
16361 return false;
16362 };
16363 if declaration.child_by_field_name("declarator") != Some(declarator) {
16364 return false;
16365 }
16366 let Some(type_node) = declaration.child_by_field_name("type") else {
16367 return false;
16368 };
16369 type_node.kind() == "dependent_type"
16370 && type_node
16371 .child(0)
16372 .is_some_and(|keyword| keyword.kind() == "typename")
16373}
16374
16375fn recovered_c_explicit_assignment_callee(
16376 visibility: &VisibilityIndex<'_>,
16377 file: &ProjectFile,
16378 node: Node<'_>,
16379 name: &str,
16380) -> bool {
16381 let mut current = node;
16382 let error = loop {
16383 let Some(parent) = current.parent() else {
16384 return false;
16385 };
16386 if parent.is_error() {
16387 break parent;
16388 }
16389 current = parent;
16390 };
16391 let mut cursor = error.walk();
16392 let explicit_recovery_precedes_callee = error
16393 .named_children(&mut cursor)
16394 .take_while(|child| child.start_byte() < node.start_byte())
16395 .any(|child| child.kind() == "explicit_function_specifier");
16396 if !explicit_recovery_precedes_callee {
16397 return false;
16398 }
16399 visibility
16400 .visible_identifier_candidates(file, name)
16401 .any(CodeUnit::is_function)
16402}
16403
16404fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16405 while let Some(parent) = node.parent() {
16406 if matches!(
16407 parent.kind(),
16408 "preproc_def" | "preproc_function_def" | "preproc_params"
16409 ) {
16410 return true;
16411 }
16412 if parent.is_error()
16413 || matches!(
16414 parent.kind(),
16415 "translation_unit" | "function_definition" | "compound_statement"
16416 )
16417 {
16418 return false;
16419 }
16420 node = parent;
16421 }
16422 false
16423}
16424
16425fn recovered_c_label_role(node: Node<'_>) -> bool {
16426 node.parent().is_some_and(|parent| {
16427 matches!(parent.kind(), "labeled_statement" | "goto_statement")
16428 && parent.child_by_field_name("label") == Some(node)
16429 })
16430}
16431
16432fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16433 while let Some(parent) = node.parent() {
16434 if parent.is_error() {
16435 return false;
16436 }
16437 if parent.kind() == "optional_parameter_declaration"
16442 && parent
16443 .child_by_field_name("default_value")
16444 .is_some_and(|value| node_range_contains(value, node))
16445 {
16446 return true;
16447 }
16448 if parent.kind().ends_with("_expression")
16449 || matches!(
16450 parent.kind(),
16451 "argument_list"
16452 | "return_statement"
16453 | "expression_statement"
16454 | "case_statement"
16455 | "initializer_list"
16456 | "init_declarator"
16457 | "array_declarator"
16458 | "field_designator"
16459 | "enumerator"
16460 )
16461 {
16462 return true;
16463 }
16464 if matches!(
16465 parent.kind(),
16466 "translation_unit"
16467 | "function_definition"
16468 | "compound_statement"
16469 | "declaration"
16470 | "field_declaration"
16471 | "parameter_declaration"
16472 ) {
16473 return false;
16474 }
16475 node = parent;
16476 }
16477 false
16478}
16479
16480pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16488 let mut current = parameter.parent();
16489 while let Some(ancestor) = current {
16490 if ancestor.kind() == "lambda_expression" {
16491 return ancestor
16492 .child_by_field_name("declarator")
16493 .is_some_and(|declarator| {
16494 declarator.start_byte() <= parameter.start_byte()
16495 && parameter.end_byte() <= declarator.end_byte()
16496 });
16497 }
16498 if ancestor.kind() == "function_definition" {
16499 return ancestor
16500 .child_by_field_name("declarator")
16501 .is_some_and(|declarator| {
16502 declarator.start_byte() <= parameter.start_byte()
16503 && parameter.end_byte() <= declarator.end_byte()
16504 });
16505 }
16506 current = ancestor.parent();
16507 }
16508 false
16509}
16510
16511pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16512 let mut current = node.parent();
16513 while let Some(ancestor) = current {
16514 if matches!(
16515 ancestor.kind(),
16516 "parameter_declaration" | "optional_parameter_declaration"
16517 ) {
16518 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16519 }
16520 if matches!(
16521 ancestor.kind(),
16522 "function_definition" | "lambda_expression" | "compound_statement"
16523 ) {
16524 return false;
16525 }
16526 current = ancestor.parent();
16527 }
16528 false
16529}
16530
16531pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16535 specifier: Node<'_>,
16536) -> impl Iterator<Item = Node<'_>> {
16537 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16538 !matches!(
16539 ancestor.kind(),
16540 "type_descriptor"
16541 | "parameter_declaration"
16542 | "optional_parameter_declaration"
16543 | "template_argument_list"
16544 | "cast_expression"
16545 )
16546 })
16547}
16548
16549pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16550 if specifier.child_by_field_name("body").is_some() {
16551 return true;
16552 }
16553 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16554 match ancestor.kind() {
16555 "declaration" | "field_declaration" => {
16556 let mut cursor = ancestor.walk();
16557 return ancestor
16558 .children_by_field_name("declarator", &mut cursor)
16559 .next()
16560 .is_none();
16561 }
16562 "translation_unit" => return true,
16563 _ => {}
16564 }
16565 }
16566 false
16567}
16568
16569pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16570 match node.kind() {
16571 "identifier"
16572 | "field_identifier"
16573 | "qualified_identifier"
16574 | "scoped_identifier"
16575 | "operator_name"
16576 | "destructor_name"
16577 | "literal_operator_name" => Some(node),
16578 "reference_declarator" | "parenthesized_declarator" => {
16579 node.named_child(0).and_then(declarator_name_node)
16580 }
16581 _ => node
16582 .child_by_field_name("declarator")
16583 .or_else(|| node.child_by_field_name("name"))
16584 .or_else(|| node.child_by_field_name("field"))
16585 .and_then(declarator_name_node),
16586 }
16587}
16588
16589pub fn declarator_path_contains_before_name(
16591 declarator: Node<'_>,
16592 name: Node<'_>,
16593 candidate: Node<'_>,
16594) -> bool {
16595 let mut current = Some(declarator);
16596 while let Some(node) = current {
16597 if same_node(node, candidate) {
16598 return true;
16599 }
16600 if same_node(node, name) {
16601 return false;
16602 }
16603 current = node
16604 .child_by_field_name("declarator")
16605 .or_else(|| node.child_by_field_name("name"))
16606 .or_else(|| node.child_by_field_name("field"));
16607 }
16608 false
16609}
16610
16611fn declarator_name_path_contains(
16612 declarator: Node<'_>,
16613 candidate: Node<'_>,
16614 allow_type_identifier: bool,
16615) -> bool {
16616 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16617 return false;
16618 };
16619 declarator_path_contains_before_name(declarator, name, candidate)
16620}
16621
16622fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16623 match node.kind() {
16624 "identifier"
16625 | "field_identifier"
16626 | "operator_name"
16627 | "destructor_name"
16628 | "literal_operator_name" => Some(node),
16629 "type_identifier" if allow_type_identifier => Some(node),
16630 _ => node
16631 .child_by_field_name("declarator")
16632 .or_else(|| node.child_by_field_name("name"))
16633 .or_else(|| node.child_by_field_name("field"))
16634 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16635 }
16636}
16637
16638pub fn is_nested_type_node(node: Node<'_>) -> bool {
16641 node.parent().is_some_and(|parent| {
16642 matches!(
16643 parent.kind(),
16644 "qualified_identifier" | "scoped_type_identifier" | "template_type"
16645 )
16646 })
16647}
16648
16649pub struct OutOfLineMemberDefinitionOwners<'tree> {
16650 pub owners: Vec<(Node<'tree>, CodeUnit)>,
16651 innermost: Option<(Node<'tree>, CodeUnit)>,
16652}
16653
16654impl OutOfLineMemberDefinitionOwners<'_> {
16655 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16656 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16657 }
16658}
16659
16660pub struct QualifiedOwnerComponents<'tree> {
16661 pub nodes: Vec<Node<'tree>>,
16662 pub names: Vec<String>,
16663 pub global: bool,
16664}
16665
16666pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16671 let mut stack = vec![node];
16672 let mut found_separator = false;
16673 while let Some(current) = stack.pop() {
16674 if !matches!(
16675 current.kind(),
16676 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16677 ) {
16678 continue;
16679 }
16680 let mut current_has_separator = false;
16681 for child in children_iter(current) {
16682 if child.kind() == "::" {
16683 if child.is_missing() {
16684 return false;
16685 }
16686 current_has_separator = true;
16687 found_separator = true;
16688 }
16689 }
16690 if !current_has_separator {
16691 return false;
16692 }
16693 for field in ["scope", "name"] {
16694 if let Some(child) = current.child_by_field_name(field)
16695 && matches!(
16696 child.kind(),
16697 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16698 )
16699 {
16700 stack.push(child);
16701 }
16702 }
16703 }
16704 found_separator
16705}
16706
16707pub fn qualified_owner_components<'tree>(
16708 node: Node<'tree>,
16709 source: &str,
16710) -> Option<QualifiedOwnerComponents<'tree>> {
16711 if !qualified_name_has_concrete_scope_separators(node) {
16712 return None;
16713 }
16714 let mut nodes = cpp_name_component_nodes(node)?;
16715 nodes.pop()?;
16716 if nodes.is_empty() {
16717 return None;
16718 }
16719 let names = nodes
16720 .iter()
16721 .map(|component| node_text(*component, source).to_string())
16722 .collect();
16723 Some(QualifiedOwnerComponents {
16724 nodes,
16725 names,
16726 global: is_globally_qualified_cpp_name(node),
16727 })
16728}
16729
16730pub fn out_of_line_member_definition_owner<'tree>(
16731 analyzer: &CppGraphSource<'_>,
16732 visibility: &VisibilityIndex<'_>,
16733 file: &ProjectFile,
16734 source: &str,
16735 node: Node<'tree>,
16736) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16737 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16738 || !has_ancestor_kind(node, "function_definition")
16739 || !is_function_declarator_name_root(node)
16740 {
16741 return None;
16742 }
16743 let qualified = qualified_owner_components(node, source)?;
16744 let lexical_scope = enclosing_namespace_components(node, source)?;
16745 let mut owners = Vec::new();
16746 let mut innermost = None;
16747
16748 for component_count in 1..=qualified.names.len() {
16749 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16750 .resolve_type_components_lexically(
16751 analyzer,
16752 file,
16753 &qualified.names[..component_count],
16754 qualified.global,
16755 &lexical_scope,
16756 )
16757 && !owners
16758 .iter()
16759 .any(|(_, existing)| same_visible_symbol(existing, &unit))
16760 {
16761 if component_count == qualified.names.len() {
16762 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16763 }
16764 owners.push((qualified.nodes[component_count - 1], unit));
16765 }
16766 }
16767
16768 if innermost.is_none() {
16778 let indexed_owner_components = visibility
16779 .indexed_enclosing_owner_scope(analyzer, file, node)
16780 .or_else(|| {
16781 if qualified.names.len() <= 1 {
16786 return None;
16787 }
16788 let range = Range {
16789 start_byte: node.start_byte(),
16790 end_byte: node.end_byte(),
16791 start_line: node.start_position().row,
16792 end_line: node.end_position().row,
16793 };
16794 let start = analyzer.enclosing_code_unit(file, &range)?;
16795 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16796 brokk_bifrost_core::analyzer::Language::Cpp,
16797 &cpp_name_for(&start),
16798 );
16799 components.pop();
16800 Some(components)
16801 });
16802 if let Some(indexed_owner_components) = indexed_owner_components
16803 && indexed_owner_components.len() > qualified.names.len()
16804 && indexed_owner_components.ends_with(&qualified.names)
16805 && indexed_namespace_path_is_recoverable(
16806 &lexical_scope,
16807 &indexed_owner_components,
16808 qualified.names.len(),
16809 )
16810 && (qualified.names.len() > 1 || !qualified.global)
16815 {
16816 let namespace_count = indexed_owner_components.len() - qualified.names.len();
16817 for component_count in 1..=qualified.names.len() {
16818 let expected = &indexed_owner_components[..namespace_count + component_count];
16819 let owner_node = qualified.nodes[component_count - 1];
16820 for owner in visibility
16821 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16822 .filter(|candidate| candidate.is_class())
16823 .filter(|candidate| {
16824 canonical_cpp_scope_components(candidate) == expected
16825 && visibility.external_type_candidate_visible_in_context(
16826 analyzer, file, candidate, node,
16827 )
16828 })
16829 {
16830 if component_count == qualified.names.len() && innermost.is_none() {
16831 innermost = Some((owner_node, owner.clone()));
16832 }
16833 if !owners
16834 .iter()
16835 .any(|(_, existing)| same_symbol(existing, owner))
16836 {
16837 owners.push((owner_node, owner.clone()));
16838 }
16839 }
16840 }
16841 }
16842 }
16843 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16844}
16845
16846fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16847 let mut current = node;
16848 while let Some(parent) = current.parent() {
16849 if parent.kind() == "function_declarator" {
16850 return parent.child_by_field_name("declarator") == Some(current);
16851 }
16852 if matches!(
16853 parent.kind(),
16854 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16855 ) && parent.child_by_field_name("declarator") == Some(current)
16856 {
16857 current = parent;
16858 continue;
16859 }
16860 return false;
16861 }
16862 false
16863}
16864
16865pub fn append_cpp_name_components(
16866 node: Node<'_>,
16867 source: &str,
16868 out: &mut Vec<String>,
16869) -> Option<()> {
16870 out.extend(
16871 cpp_name_component_nodes(node)?
16872 .into_iter()
16873 .map(|component| node_text(component, source).to_string()),
16874 );
16875 Some(())
16876}
16877
16878fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16881 cpp_name_component_nodes(node).map(|components| {
16882 components
16883 .into_iter()
16884 .map(|component| {
16885 (
16886 node_text(component, source).to_string(),
16887 node_range(component),
16888 )
16889 })
16890 .collect()
16891 })
16892}
16893
16894pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16895 let mut components = Vec::new();
16896 append_cpp_name_components(node, source, &mut components)?;
16897 Some(components)
16898}
16899
16900pub fn unique_macro_replacement_type_candidate(
16909 analyzer: &CppGraphSource<'_>,
16910 visibility: &VisibilityIndex<'_>,
16911 file: &ProjectFile,
16912 components: &[String],
16913) -> Option<CodeUnit> {
16914 let terminal = components.last()?;
16915 let mut candidates = Vec::new();
16916 for candidate in visibility
16917 .visible_identifier_candidates(file, terminal)
16918 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16919 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16920 {
16921 if !candidates
16922 .iter()
16923 .any(|existing| same_logical_symbol(existing, candidate))
16924 {
16925 candidates.push(candidate.clone());
16926 }
16927 }
16928 (candidates.len() == 1).then(|| candidates.remove(0))
16929}
16930
16931pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16939 let mut parser = Parser::new();
16940 if parser
16941 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16942 .is_err()
16943 {
16944 return Vec::new();
16945 }
16946 let Some(tree) = parser.parse(source, None) else {
16947 return Vec::new();
16948 };
16949 let mut scopes = Vec::new();
16950 let mut pending = vec![tree.root_node()];
16951 while let Some(node) = pending.pop() {
16952 if node.kind() == "using_declaration" {
16953 let Some(imported) = node.named_child(0) else {
16954 continue;
16955 };
16956 let Some(mut components) = cpp_type_name_components(imported, source) else {
16957 continue;
16958 };
16959 if components.pop().as_deref() == Some(member) && !components.is_empty() {
16960 scopes.push(components.join("::"));
16961 }
16962 continue;
16963 }
16964 push_named_children_reversed(node, &mut pending);
16965 }
16966 scopes
16967}
16968
16969pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16973 qualified == scope
16974 || qualified
16975 .strip_suffix(scope)
16976 .is_some_and(|prefix| prefix.ends_with("::"))
16977}
16978
16979pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16984 let Some(type_descriptor) = node.parent() else {
16985 return false;
16986 };
16987 if type_descriptor.kind() != "type_descriptor"
16988 || type_descriptor.child_by_field_name("type") != Some(node)
16989 {
16990 return false;
16991 }
16992 let Some(arguments) = type_descriptor.parent() else {
16993 return false;
16994 };
16995 if arguments.kind() != "template_argument_list" {
16996 return false;
16997 }
16998 arguments.parent().is_some_and(|parent| {
16999 matches!(parent.kind(), "template_type" | "template_function")
17000 && parent.child_by_field_name("arguments") == Some(arguments)
17001 })
17002}
17003
17004pub fn cpp_template_reference_arguments(
17005 mut node: Node<'_>,
17006 source: &str,
17007) -> Option<Vec<CppTemplateExpression>> {
17008 loop {
17009 match node.kind() {
17010 "template_type" | "template_function" => {
17011 let arguments = node.child_by_field_name("arguments")?;
17012 let mut cursor = arguments.walk();
17013 return Some(
17014 arguments
17015 .named_children(&mut cursor)
17016 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17017 .map(|argument| CppTemplateExpression {
17018 text: normalize_cpp_whitespace(node_text(argument, source)),
17019 term: cpp_template_term(
17021 argument,
17022 source,
17023 &[],
17024 &ParentIndex::unindexed(),
17025 ),
17026 })
17027 .collect(),
17028 );
17029 }
17030 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17031 node = node
17032 .child_by_field_name("name")
17033 .or_else(|| node.child_by_field_name("type"))?;
17034 }
17035 _ => return None,
17036 }
17037 }
17038}
17039
17040fn cpp_reconcile_primary_template_parameters(
17041 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17042 preferred: &CodeUnit,
17043) -> Option<Vec<CppTemplateParameterMetadata>> {
17044 let canonical = candidates
17045 .iter()
17046 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17047 let mut merged = canonical
17048 .parameters
17049 .iter()
17050 .map(|parameter| CppTemplateParameterMetadata {
17051 name: parameter.name.clone(),
17052 kind: parameter.kind,
17053 variadic: parameter.variadic,
17054 default: None,
17055 })
17056 .collect::<Vec<_>>();
17057
17058 for (_, metadata) in candidates {
17059 if metadata.parameters.len() != merged.len() {
17060 return None;
17061 }
17062 let rename_bindings = metadata
17063 .parameters
17064 .iter()
17065 .zip(&merged)
17066 .map(|(parameter, canonical)| {
17067 (
17068 parameter.name.clone(),
17069 CppTemplateTerm::Parameter(canonical.name.clone()),
17070 )
17071 })
17072 .collect::<HashMap<_, _>>();
17073 for ((parameter, canonical), merged_parameter) in metadata
17074 .parameters
17075 .iter()
17076 .zip(&canonical.parameters)
17077 .zip(&mut merged)
17078 {
17079 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17080 return None;
17081 }
17082 let Some(default) = ¶meter.default else {
17083 continue;
17084 };
17085 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17086 if let Some(existing) = &merged_parameter.default {
17087 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17088 return None;
17089 }
17090 } else {
17091 merged_parameter.default = Some(CppTemplateExpression {
17092 text: default.text.clone(),
17093 term: normalized_term,
17094 });
17095 }
17096 }
17097 }
17098 Some(merged)
17099}
17100
17101pub fn cpp_bind_template_arguments(
17102 parameters: &[CppTemplateParameterMetadata],
17103 explicit_arguments: &[CppTemplateExpression],
17104) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17105 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17106 if variadic_index.is_some_and(|index| {
17107 index + 1 != parameters.len()
17108 || parameters[index + 1..]
17109 .iter()
17110 .any(|parameter| parameter.variadic)
17111 }) {
17112 return None;
17113 }
17114 let fixed_count = variadic_index.unwrap_or(parameters.len());
17115 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17116 return None;
17117 }
17118 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17119 let mut expanded = explicit_arguments[..explicit_fixed_count]
17120 .iter()
17121 .map(cpp_clone_template_expression_iterative)
17122 .collect::<Vec<_>>();
17123 let mut bindings = HashMap::default();
17124 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17125 bindings.insert(
17126 parameter.name.clone(),
17127 cpp_clone_template_term_iterative(&argument.term),
17128 );
17129 }
17130 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
17131 let default = parameter.default.as_ref()?;
17132 let term = cpp_substitute_template_term(&default.term, &bindings)?;
17133 bindings.insert(parameter.name.clone(), term.clone());
17134 expanded.push(CppTemplateExpression {
17135 text: default.text.clone(),
17136 term,
17137 });
17138 }
17139 if let Some(index) = variadic_index {
17140 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17141 expanded.extend(
17142 packed_arguments
17143 .iter()
17144 .map(cpp_clone_template_expression_iterative),
17145 );
17146 bindings.insert(
17147 parameters[index].name.clone(),
17148 CppTemplateTerm::Node {
17149 kind: "parameter_pack".to_string(),
17150 children: packed_arguments
17151 .iter()
17152 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17153 .collect(),
17154 },
17155 );
17156 }
17157 Some((expanded, bindings))
17158}
17159
17160fn cpp_specialization_matches(
17161 metadata: &CppTemplateMetadata,
17162 arguments: &[CppTemplateExpression],
17163) -> bool {
17164 cpp_specialization_terms_match(
17165 &metadata.parameters,
17166 &metadata.specialization_arguments,
17167 arguments,
17168 )
17169}
17170
17171fn cpp_specialization_terms_match(
17172 parameters: &[CppTemplateParameterMetadata],
17173 patterns: &[CppTemplateExpression],
17174 arguments: &[CppTemplateExpression],
17175) -> bool {
17176 if patterns.len() != arguments.len() {
17177 return false;
17178 }
17179 let parameter_names = parameters
17180 .iter()
17181 .map(|parameter| parameter.name.as_str())
17182 .collect::<HashSet<_>>();
17183 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17184 patterns.iter().zip(arguments).all(|(pattern, argument)| {
17185 cpp_unify_template_term(
17186 &pattern.term,
17187 &argument.term,
17188 ¶meter_names,
17189 &mut bindings,
17190 )
17191 })
17192}
17193
17194fn cpp_specialization_more_specialized(
17195 candidate: &CppTemplateMetadata,
17196 other: &CppTemplateMetadata,
17197) -> bool {
17198 cpp_specialization_pattern_accepts(other, candidate)
17199 && !cpp_specialization_pattern_accepts(candidate, other)
17200}
17201
17202fn cpp_specialization_pattern_accepts(
17203 broader: &CppTemplateMetadata,
17204 narrower: &CppTemplateMetadata,
17205) -> bool {
17206 cpp_specialization_terms_match(
17207 &broader.parameters,
17208 &broader.specialization_arguments,
17209 &narrower.specialization_arguments,
17210 )
17211}
17212
17213pub fn cpp_substitute_template_term(
17214 term: &CppTemplateTerm,
17215 bindings: &HashMap<String, CppTemplateTerm>,
17216) -> Option<CppTemplateTerm> {
17217 enum Work<'a> {
17218 Visit(&'a CppTemplateTerm),
17219 Build { kind: String, child_count: usize },
17220 }
17221
17222 let mut work = vec![Work::Visit(term)];
17223 let mut substituted = Vec::new();
17224 while let Some(next) = work.pop() {
17225 match next {
17226 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17227 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17228 }
17229 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17230 substituted.push(CppTemplateTerm::Atom {
17231 kind: kind.clone(),
17232 text: text.clone(),
17233 });
17234 }
17235 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17236 work.push(Work::Build {
17237 kind: kind.clone(),
17238 child_count: children.len(),
17239 });
17240 work.extend(children.iter().rev().map(Work::Visit));
17241 }
17242 Work::Build { kind, child_count } => {
17243 let children = substituted.split_off(substituted.len() - child_count);
17244 substituted.push(CppTemplateTerm::Node { kind, children });
17245 }
17246 }
17247 }
17248 substituted.pop()
17249}
17250
17251pub fn cpp_substitute_template_arguments(
17252 arguments: &[CppTemplateExpression],
17253 bindings: &HashMap<String, CppTemplateTerm>,
17254) -> Option<Vec<CppTemplateExpression>> {
17255 let mut substituted = Vec::new();
17256 for argument in arguments {
17257 let CppTemplateTerm::Node { kind, children } = &argument.term else {
17258 substituted.push(CppTemplateExpression {
17259 text: argument.text.clone(),
17260 term: cpp_substitute_template_term(&argument.term, bindings)?,
17261 });
17262 continue;
17263 };
17264 if kind != "parameter_pack_expansion" {
17265 substituted.push(CppTemplateExpression {
17266 text: argument.text.clone(),
17267 term: cpp_substitute_template_term(&argument.term, bindings)?,
17268 });
17269 continue;
17270 }
17271 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17272 return None;
17273 };
17274 if ellipsis != "..." {
17275 return None;
17276 }
17277
17278 let mut pack_names = Vec::new();
17279 let mut work = vec![pattern];
17280 while let Some(term) = work.pop() {
17281 match term {
17282 CppTemplateTerm::Parameter(name)
17283 if matches!(
17284 bindings.get(name),
17285 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
17286 ) =>
17287 {
17288 if !pack_names.contains(name) {
17289 pack_names.push(name.clone());
17290 }
17291 }
17292 CppTemplateTerm::Node { children, .. } => work.extend(children),
17293 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
17294 }
17295 }
17296 let first_pack = pack_names.first()?;
17297 let CppTemplateTerm::Node {
17298 children: first_elements,
17299 ..
17300 } = bindings.get(first_pack)?
17301 else {
17302 return None;
17303 };
17304 let pack_len = first_elements.len();
17305 for pack_name in &pack_names {
17306 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17307 return None;
17308 };
17309 if children.len() != pack_len {
17310 return None;
17311 }
17312 }
17313 for index in 0..pack_len {
17314 let mut element_bindings = bindings.clone();
17315 for pack_name in &pack_names {
17316 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
17317 return None;
17318 };
17319 element_bindings.insert(
17320 pack_name.clone(),
17321 cpp_clone_template_term_iterative(&children[index]),
17322 );
17323 }
17324 substituted.push(CppTemplateExpression {
17325 text: argument.text.clone(),
17326 term: cpp_substitute_template_term(pattern, &element_bindings)?,
17327 });
17328 }
17329 }
17330 Some(substituted)
17331}
17332
17333fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17334 enum Work<'a> {
17335 Visit(&'a CppTemplateTerm),
17336 Build { kind: String, child_count: usize },
17337 }
17338
17339 let mut work = vec![Work::Visit(term)];
17340 let mut cloned = Vec::new();
17341 while let Some(next) = work.pop() {
17342 match next {
17343 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17344 cloned.push(CppTemplateTerm::Parameter(name.clone()));
17345 }
17346 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17347 cloned.push(CppTemplateTerm::Atom {
17348 kind: kind.clone(),
17349 text: text.clone(),
17350 });
17351 }
17352 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17353 work.push(Work::Build {
17354 kind: kind.clone(),
17355 child_count: children.len(),
17356 });
17357 work.extend(children.iter().rev().map(Work::Visit));
17358 }
17359 Work::Build { kind, child_count } => {
17360 let children = cloned.split_off(cloned.len() - child_count);
17361 cloned.push(CppTemplateTerm::Node { kind, children });
17362 }
17363 }
17364 }
17365 cloned
17366 .pop()
17367 .expect("template term traversal emits one root")
17368}
17369
17370fn cpp_clone_template_expression_iterative(
17371 expression: &CppTemplateExpression,
17372) -> CppTemplateExpression {
17373 CppTemplateExpression {
17374 text: expression.text.clone(),
17375 term: cpp_clone_template_term_iterative(&expression.term),
17376 }
17377}
17378
17379pub fn cpp_unify_template_term(
17380 pattern: &CppTemplateTerm,
17381 argument: &CppTemplateTerm,
17382 parameters: &HashSet<&str>,
17383 bindings: &mut HashMap<String, CppTemplateTerm>,
17384) -> bool {
17385 let mut work = vec![(pattern, argument)];
17386 while let Some((pattern, argument)) = work.pop() {
17387 match pattern {
17388 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17389 if let Some(bound) = bindings.get(name) {
17390 if !cpp_template_terms_equal(bound, argument) {
17391 return false;
17392 }
17393 } else {
17394 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17395 }
17396 }
17397 CppTemplateTerm::Atom {
17398 kind: pattern_kind,
17399 text: pattern_text,
17400 } => {
17401 if !matches!(
17402 argument,
17403 CppTemplateTerm::Atom { kind, text }
17404 if kind == pattern_kind && text == pattern_text
17405 ) {
17406 return false;
17407 }
17408 }
17409 CppTemplateTerm::Node {
17410 kind: pattern_kind,
17411 children: pattern_children,
17412 } => {
17413 let CppTemplateTerm::Node { kind, children } = argument else {
17414 return false;
17415 };
17416 if kind != pattern_kind || children.len() != pattern_children.len() {
17417 return false;
17418 }
17419 work.extend(pattern_children.iter().zip(children).rev());
17420 }
17421 CppTemplateTerm::Parameter(_) => return false,
17422 }
17423 }
17424 true
17425}
17426
17427fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17428 let mut work = vec![(left, right)];
17429 while let Some((left, right)) = work.pop() {
17430 match (left, right) {
17431 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17432 if left != right {
17433 return false;
17434 }
17435 }
17436 (
17437 CppTemplateTerm::Atom {
17438 kind: left_kind,
17439 text: left_text,
17440 },
17441 CppTemplateTerm::Atom {
17442 kind: right_kind,
17443 text: right_text,
17444 },
17445 ) => {
17446 if left_kind != right_kind || left_text != right_text {
17447 return false;
17448 }
17449 }
17450 (
17451 CppTemplateTerm::Node {
17452 kind: left_kind,
17453 children: left_children,
17454 },
17455 CppTemplateTerm::Node {
17456 kind: right_kind,
17457 children: right_children,
17458 },
17459 ) => {
17460 if left_kind != right_kind || left_children.len() != right_children.len() {
17461 return false;
17462 }
17463 work.extend(left_children.iter().zip(right_children).rev());
17464 }
17465 _ => return false,
17466 }
17467 }
17468 true
17469}
17470
17471pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17472 let mut components = Vec::new();
17473 let mut stack = vec![node];
17474 while let Some(current) = stack.pop() {
17475 match current.kind() {
17476 "identifier"
17477 | "field_identifier"
17478 | "namespace_identifier"
17479 | "type_identifier"
17480 | "operator_name"
17481 | "destructor_name" => components.push(current),
17482 "template_type" | "template_function" => {
17483 stack.push(current.child_by_field_name("name")?);
17484 }
17485 "dependent_name" => stack.push(current.named_child(0)?),
17486 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17487 stack.push(current.child_by_field_name("name")?);
17488 if let Some(scope) = current.child_by_field_name("scope") {
17489 stack.push(scope);
17490 }
17491 }
17492 "nested_namespace_specifier" => {
17493 for index in (0..current.named_child_count()).rev() {
17494 stack.push(current.named_child(index)?);
17495 }
17496 }
17497 _ => return None,
17498 }
17499 }
17500 Some(components)
17501}
17502
17503pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17504 node.child_by_field_name("scope").is_none()
17505 && node.child(0).is_some_and(|child| child.kind() == "::")
17506}
17507
17508fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17509 let mut namespaces = Vec::new();
17510 for parent in enclosing_cpp_namespace_nodes(node) {
17511 if let Some(name) = parent.child_by_field_name("name") {
17512 let mut components = Vec::new();
17513 append_cpp_name_components(name, source, &mut components)?;
17514 namespaces.push(components);
17515 }
17516 }
17517 Some(namespaces.into_iter().flatten().collect())
17518}
17519
17520fn indexed_namespace_path_is_recoverable(
17531 lexical_scope: &[String],
17532 indexed_owner_scope: &[String],
17533 explicit_owner_component_count: usize,
17534) -> bool {
17535 if lexical_scope.is_empty() {
17536 return explicit_owner_component_count > 1;
17537 }
17538 if lexical_scope.len() >= indexed_owner_scope.len() {
17539 return false;
17540 }
17541 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17542}
17543
17544pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17545
17546pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17552 let mut current = Some(node);
17553 while let Some(candidate) = current {
17554 if candidate.kind() == "declaration" {
17555 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17556 return false;
17557 }
17558 let mut cursor = candidate.walk();
17559 return candidate.named_children(&mut cursor).any(|child| {
17560 child.kind() == "init_declarator"
17561 && child
17562 .child_by_field_name("value")
17563 .is_some_and(|value| value.kind() == "cast_expression")
17564 });
17565 }
17566 current = candidate.parent();
17567 }
17568 false
17569}
17570
17571#[derive(Clone, Copy, PartialEq, Eq)]
17572pub(crate) enum QualifiedAliasReferenceKind {
17573 Ordinary,
17574 ConstructorWithExpressionArgument,
17575 ExhaustiveTemplate,
17576}
17577
17578pub(crate) fn qualified_alias_reference_preserves_target(
17585 node: Node<'_>,
17586 target: &CodeUnit,
17587 analyzer: &CppGraphSource<'_>,
17588 visibility: &VisibilityIndex<'_>,
17589 file: &ProjectFile,
17590 source: &str,
17591) -> Option<QualifiedAliasReferenceKind> {
17592 if !matches!(
17593 node.kind(),
17594 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17595 ) {
17596 return None;
17597 }
17598 let components = cpp_type_name_components(node, source)?;
17599 let name = components.last()?;
17600 analyzer.type_alias_provider().and_then(|provider| {
17601 visibility
17602 .visible_identifier_candidates(file, name)
17603 .find_map(|candidate| {
17604 let proof = provider.is_type_alias(candidate)
17605 && canonical_cpp_scope_components(candidate) == components
17606 && visibility.external_type_candidate_visible_in_context(
17607 analyzer, file, candidate, node,
17608 )
17609 && match cpp_template_reference_arguments(node, source) {
17610 Some(arguments) => visibility.template_alias_arguments_preserve_target(
17611 analyzer, file, candidate, &arguments, target,
17612 ),
17613 None => visibility.structured_alias_primary_preserves_target(
17614 analyzer, file, candidate, target,
17615 ),
17616 };
17617 proof.then(|| {
17618 if cpp_template_reference_arguments(node, source).is_some()
17619 && visibility.is_exhaustive_same_fqn_type_declaration_family(
17620 analyzer, file, candidate,
17621 )
17622 {
17623 QualifiedAliasReferenceKind::ExhaustiveTemplate
17624 } else if qualified_alias_constructor_has_expression_argument(node)
17625 || qualified_alias_local_constructor_declaration(node)
17626 {
17627 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17628 } else {
17629 QualifiedAliasReferenceKind::Ordinary
17630 }
17631 })
17632 })
17633 })
17634}
17635
17636pub(crate) fn qualified_alias_reference_requires_terminal(
17637 reference: Option<QualifiedAliasReferenceKind>,
17638) -> bool {
17639 matches!(
17640 reference,
17641 Some(
17642 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17643 | QualifiedAliasReferenceKind::ExhaustiveTemplate
17644 )
17645 )
17646}
17647
17648fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17649 let Some(declaration) = node.parent().filter(|parent| {
17650 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17651 }) else {
17652 return false;
17653 };
17654 let mut cursor = declaration.walk();
17655 declaration.named_children(&mut cursor).any(|child| {
17656 child.kind() == "init_declarator"
17657 && child
17658 .child_by_field_name("value")
17659 .filter(|value| value.kind() == "argument_list")
17660 .is_some_and(|arguments| {
17661 let mut cursor = arguments.walk();
17662 arguments.named_children(&mut cursor).any(|argument| {
17663 let is_parameter = matches!(
17664 argument.kind(),
17665 "parameter_declaration" | "optional_parameter_declaration"
17666 );
17667 if is_parameter {
17668 argument
17669 .child_by_field_name("type")
17670 .is_some_and(|type_node| {
17671 type_node.kind() == "type_identifier"
17672 && argument.child_by_field_name("declarator").is_none()
17673 })
17674 } else {
17675 !argument.kind().ends_with("_literal")
17676 && !matches!(argument.kind(), "true" | "false" | "nullptr")
17677 }
17678 })
17679 })
17680 })
17681}
17682
17683fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17688 let Some(declaration) = node.parent().filter(|parent| {
17689 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17690 }) else {
17691 return false;
17692 };
17693 if declaration
17694 .parent()
17695 .is_none_or(|parent| parent.kind() != "compound_statement")
17696 {
17697 return false;
17698 }
17699 let mut cursor = declaration.walk();
17700 declaration
17701 .named_children(&mut cursor)
17702 .any(|child| child.kind() == "function_declarator")
17703}
17704
17705pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17711 loop {
17712 let next = match node.kind() {
17713 "qualified_identifier"
17714 | "scoped_identifier"
17715 | "template_method"
17716 | "template_function"
17717 | "template_type" => node.child_by_field_name("name"),
17718 "field_expression" => node.child_by_field_name("field"),
17719 _ => None,
17720 };
17721 let Some(next) = next else {
17722 return node;
17723 };
17724 node = next;
17725 }
17726}
17727
17728#[derive(Clone, Copy)]
17729pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17730 pub receiver: Node<'tree>,
17731 pub member: Node<'tree>,
17732 pub arity: usize,
17733}
17734
17735pub fn recovered_relational_template_member_call(
17743 field: Node<'_>,
17744) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17745 if field.kind() != "field_expression" {
17746 return None;
17747 }
17748 let receiver = field
17749 .child_by_field_name("argument")
17750 .or_else(|| field.child_by_field_name("object"))?;
17751 let member = field.child_by_field_name("field")?;
17752 let less = field.parent()?;
17753 if less.kind() != "binary_expression"
17754 || less.child_by_field_name("left") != Some(field)
17755 || less
17756 .child_by_field_name("operator")
17757 .is_none_or(|operator| operator.kind() != "<")
17758 || less.child_by_field_name("right").is_none()
17759 {
17760 return None;
17761 }
17762 let greater = less.parent()?;
17763 if greater.kind() != "binary_expression"
17764 || greater.child_by_field_name("left") != Some(less)
17765 || greater
17766 .child_by_field_name("operator")
17767 .is_none_or(|operator| operator.kind() != ">")
17768 {
17769 return None;
17770 }
17771 let arguments = greater.child_by_field_name("right")?;
17772 if arguments.kind() != "parenthesized_expression" {
17773 return None;
17774 }
17775 let arity = parenthesized_call_argument_arity(arguments)?;
17776 Some(RecoveredRelationalTemplateMemberCall {
17777 receiver,
17778 member,
17779 arity,
17780 })
17781}
17782
17783fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17784 let expression = arguments.named_child(0)?;
17785 if expression.kind() != "comma_expression" {
17786 return Some(1);
17787 }
17788 let mut arity = 0usize;
17789 let mut stack = vec![expression];
17790 while let Some(node) = stack.pop() {
17791 if node.kind() == "comma_expression" {
17792 stack.push(node.child_by_field_name("right")?);
17793 stack.push(node.child_by_field_name("left")?);
17794 } else {
17795 arity += 1;
17796 }
17797 }
17798 Some(arity)
17799}
17800
17801pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17804 while let Some(parent) = node.parent() {
17805 match parent.kind() {
17806 "call_expression" => {
17807 return parent
17808 .child_by_field_name("function")
17809 .or_else(|| parent.named_child(0))
17810 == Some(node);
17811 }
17812 "qualified_identifier"
17813 | "scoped_identifier"
17814 | "template_function"
17815 | "template_type"
17816 | "field_expression" => node = parent,
17817 _ => return false,
17818 }
17819 }
17820 false
17821}
17822
17823pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17827 loop {
17828 let Some(parent) = node.parent() else {
17829 return node;
17830 };
17831 if parent.kind() == "template_type"
17832 && parent
17833 .child_by_field_name("name")
17834 .is_some_and(|name| same_node(name, node))
17835 {
17836 node = parent;
17837 continue;
17838 }
17839 if matches!(
17840 parent.kind(),
17841 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17842 ) && (parent
17843 .child_by_field_name("scope")
17844 .is_some_and(|scope| same_node(scope, node))
17845 || parent
17846 .child_by_field_name("name")
17847 .is_some_and(|name| same_node(name, node)))
17848 && parent.end_byte() == node.end_byte()
17849 {
17850 node = parent;
17851 continue;
17852 }
17853 return node;
17854 }
17855}
17856
17857pub fn normalize_type_text(value: &str) -> String {
17858 strip_tag_type_prefix(
17859 normalize_cpp_whitespace(value)
17860 .trim_start_matches("const ")
17861 .trim_end_matches('*')
17862 .trim_end_matches('&')
17863 .trim(),
17864 )
17865 .to_string()
17866}
17867
17868use crate::declarations::strip_tag_type_prefix;
17869
17870pub fn normalize_reference_name(value: &str) -> Option<String> {
17871 let normalized = normalize_cpp_reference_text(value);
17872 (!normalized.is_empty()).then_some(normalized)
17873}
17874
17875pub fn normalize_cpp_reference_text(value: &str) -> String {
17876 let mut text = normalize_cpp_whitespace(value)
17877 .trim_start_matches("new ")
17878 .trim()
17879 .to_string();
17880 if let Some(index) = text.find(['(', '{']) {
17881 text.truncate(index);
17882 }
17883 if let Some(index) = text.find('<') {
17884 text.truncate(index);
17885 }
17886 let normalized = text
17887 .trim()
17888 .trim_start_matches("const ")
17889 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17890 .trim_matches(':')
17891 .trim();
17892 strip_tag_type_prefix(normalized).to_string()
17893}
17894
17895pub fn cpp_name_for(unit: &CodeUnit) -> String {
17896 let short = unit.short_name().replace(['.', '$'], "::");
17897 if unit.package_name().is_empty() {
17898 short
17899 } else {
17900 format!("{}::{}", unit.package_name(), short)
17901 }
17902}
17903
17904fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17908 let fq = unit.fq();
17909 if fq.is_empty() {
17910 return None;
17911 }
17912 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17913 Some(
17914 fq.segments()
17915 .iter()
17916 .map(|&segment| interner.resolve(segment).0)
17917 .collect::<Vec<_>>()
17918 .join("::"),
17919 )
17920}
17921
17922fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17923 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17924 || unit.fq().is_empty() && cpp_name_for(unit) == expected
17925}
17926
17927pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17936 let fq = unit.fq();
17937 if !fq.is_empty() {
17938 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17939 let scope = fq
17940 .segments()
17941 .iter()
17942 .filter_map(|&segment| {
17943 let (text, kind) = interner.resolve(segment);
17944 matches!(
17945 kind,
17946 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17947 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17948 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17949 )
17950 .then(|| text.to_string())
17951 })
17952 .collect();
17953 return scope;
17954 }
17955 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17956 brokk_bifrost_core::analyzer::Language::Cpp,
17957 &cpp_name_for(unit),
17958 )
17959}
17960
17961pub fn terminal_name(value: &str) -> &str {
17972 value
17973 .rsplit("::")
17974 .next()
17975 .unwrap_or(value)
17976 .rsplit(['.', '-', '>'])
17977 .next()
17978 .unwrap_or(value)
17979 .trim()
17980}
17981
17982pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17983 terminal_name(&normalize_cpp_reference_text(value)) == expected
17984}
17985
17986pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17987 name_matches_terminal(value, expected)
17988 || expected.starts_with("operator")
17989 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17990}
17991
17992pub fn name_mentions(value: &str, expected: &str) -> bool {
17993 normalize_cpp_reference_text(value)
17994 .split("::")
17995 .any(|part| part == expected)
17996}
17997
17998pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17999 let cpp_name = cpp_name_for(unit);
18000 if reference.contains("::") {
18001 return reference == cpp_name;
18002 }
18003 reference == cpp_name
18004 || terminal_name(reference) == unit.identifier()
18005 && (unit.package_name().is_empty() || reference == unit.identifier())
18006}
18007
18008pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18009 match kind {
18010 TargetKind::Type
18011 | TargetKind::Constructor
18012 | TargetKind::Method
18013 | TargetKind::MemberField => true,
18014 TargetKind::FreeFunction => unit.is_function(),
18015 TargetKind::GlobalField => unit.is_field(),
18016 TargetKind::Macro => unit.is_macro(),
18017 }
18018}
18019
18020fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18021 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18022 let target_name = cpp_name_for(target);
18023 if normalized.contains("::") {
18024 return normalized == target_name;
18025 }
18026 if let Some(namespace) = alias.namespace.as_deref() {
18027 return namespace_prefixes(namespace)
18028 .into_iter()
18029 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18030 }
18031 target.package_name().is_empty() && normalized == target.identifier()
18032}
18033
18034pub fn cpp_function_return_type_text(
18037 analyzer: &CppGraphSource<'_>,
18038 function: &CodeUnit,
18039) -> Option<String> {
18040 let metadata = analyzer.signature_metadata(function);
18041 let first = metadata.first()?.return_type_text()?;
18042 metadata
18043 .iter()
18044 .all(|metadata| metadata.return_type_text() == Some(first))
18045 .then(|| first.to_owned())
18046}
18047
18048pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18049 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18059 namespace
18060 .strip_prefix("anonymous_namespace::")
18061 .unwrap_or(namespace)
18062 .to_string()
18063 })
18064}
18065
18066pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18068 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18074 brokk_bifrost_core::analyzer::Language::Cpp,
18075 namespace,
18076 );
18077 let mut prefixes = Vec::new();
18078 while !parts.is_empty() {
18079 prefixes.push(parts.join("::"));
18080 parts.pop();
18081 }
18082 prefixes
18083}
18084
18085fn nearest_namespace_candidates(
18086 candidates: Vec<CodeUnit>,
18087 normalized: &str,
18088 lexical_namespace: Option<&str>,
18089) -> Vec<CodeUnit> {
18090 if normalized.contains("::") {
18091 return candidates;
18092 }
18093 if let Some(namespace) = lexical_namespace {
18094 for prefix in namespace_prefixes(namespace) {
18095 let scoped = candidates
18096 .iter()
18097 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18098 .cloned()
18099 .collect::<Vec<_>>();
18100 if !scoped.is_empty() {
18101 return scoped;
18102 }
18103 }
18104 }
18105 candidates
18106 .into_iter()
18107 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18108 .collect()
18109}
18110
18111pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18112 let mut namespaces = Vec::new();
18113 for parent in enclosing_cpp_namespace_nodes(node) {
18114 if let Some(name) = parent.child_by_field_name("name") {
18115 let namespace = normalize_cpp_reference_text(node_text(name, source));
18116 if !namespace.is_empty() {
18117 namespaces.push(namespace);
18118 }
18119 }
18120 }
18121 if namespaces.is_empty() {
18122 None
18123 } else {
18124 Some(namespaces.join("::"))
18125 }
18126}
18127
18128pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18132 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18133}
18134
18135fn type_owner_resolution(
18136 analyzer: &CppGraphSource<'_>,
18137 code_unit: &CodeUnit,
18138) -> Option<ResolvedTypeOwner> {
18139 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18140}
18141
18142fn target_type_owner_resolution(
18143 analyzer: &CppGraphSource<'_>,
18144 code_unit: &CodeUnit,
18145) -> Option<ResolvedTypeOwner> {
18146 match type_owner_resolution(analyzer, code_unit) {
18147 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18148 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18149 }
18150}
18151
18152fn target_forward_owner_resolution(
18164 analyzer: &CppGraphSource<'_>,
18165 code_unit: &CodeUnit,
18166) -> Option<ResolvedTypeOwner> {
18167 if !code_unit.is_function() {
18168 return None;
18169 }
18170 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18176 let cpp = analyzer.cpp?;
18177 let mut visible_files = HashSet::default();
18178 collect_include_closure(
18179 analyzer,
18180 cpp.include_target_index(),
18181 code_unit.source(),
18182 &mut visible_files,
18183 None,
18184 );
18185 let candidates = analyzer.workspace_definitions().exact(&owner_name);
18186 let visible_candidates = candidates
18187 .iter()
18188 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18189 .cloned()
18190 .collect::<Vec<_>>();
18191 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18192 DirectOwnerResolution::UniqueFull(unit) => {
18193 return Some(ResolvedTypeOwner {
18194 unit,
18195 is_forward_declaration: false,
18196 });
18197 }
18198 DirectOwnerResolution::ForwardsOnly(forwards) => {
18199 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18200 unit: forwards.into_iter().next().unwrap(),
18201 is_forward_declaration: true,
18202 });
18203 }
18204 DirectOwnerResolution::Ambiguous => return None,
18205 DirectOwnerResolution::None => {}
18206 }
18207
18208 let candidates = candidates
18209 .into_iter()
18210 .filter(|candidate| candidate.is_class())
18211 .collect::<Vec<_>>();
18212 let (unit, is_forward_declaration) =
18213 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18214 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18215 DirectOwnerResolution::ForwardsOnly(forwards) => {
18216 (unique_logical_forward_owner(forwards)?, true)
18217 }
18218 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18219 };
18220 Some(ResolvedTypeOwner {
18221 unit,
18222 is_forward_declaration,
18223 })
18224}
18225
18226pub fn precise_parent_of(
18227 analyzer: &CppGraphSource<'_>,
18228 visibility: &VisibilityIndex<'_>,
18229 code_unit: &CodeUnit,
18230) -> Option<CodeUnit> {
18231 visibility.cached_precise_parent_of(analyzer, code_unit)
18232}
18233
18234fn precise_parent_resolution(
18235 analyzer: &CppGraphSource<'_>,
18236 code_unit: &CodeUnit,
18237) -> Option<ResolvedTypeOwner> {
18238 #[cfg(any(test, feature = "test-support"))]
18239 if let Some(cpp) = analyzer.cpp {
18240 cpp.record_cpp_parent_resolution_for_test();
18241 }
18242 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18243 return Some(ResolvedTypeOwner {
18244 unit,
18245 is_forward_declaration: false,
18246 });
18247 }
18248 let fallback = analyzer.parent_of(code_unit);
18249 if !code_unit.owner_is_type_scope() {
18250 return fallback.map(|unit| ResolvedTypeOwner {
18251 unit,
18252 is_forward_declaration: false,
18253 });
18254 }
18255 let owner_fq = code_unit
18256 .fq()
18257 .parent()
18258 .expect("a unit with an owner identifier has a structured parent");
18259 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18260 match same_source_owner(analyzer, code_unit, &owner_candidates) {
18261 DirectOwnerResolution::UniqueFull(owner) => {
18262 return Some(ResolvedTypeOwner {
18263 unit: owner,
18264 is_forward_declaration: false,
18265 });
18266 }
18267 DirectOwnerResolution::Ambiguous => return None,
18268 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18269 }
18270 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18271 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18272 unit: owner,
18273 is_forward_declaration: false,
18274 }),
18275 DirectOwnerResolution::Ambiguous => None,
18276 DirectOwnerResolution::ForwardsOnly(forwards) => {
18277 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18278 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18279 unit: owner,
18280 is_forward_declaration: false,
18281 }),
18282 FullOwnerResolution::None => {
18283 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
18284 unit,
18285 is_forward_declaration: true,
18286 })
18287 }
18288 FullOwnerResolution::Ambiguous => None,
18289 }
18290 }
18291 DirectOwnerResolution::None => {
18292 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18293 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18294 unit: owner,
18295 is_forward_declaration: false,
18296 }),
18297 FullOwnerResolution::Ambiguous => None,
18298 FullOwnerResolution::None => fallback
18299 .filter(|parent| {
18300 parent.source() == code_unit.source()
18301 && parent.fq() == &owner_fq
18302 && (!parent.is_class()
18303 || cpp_class_declaration_strength(analyzer, parent)
18304 == CppClassDeclarationStrength::Full)
18305 })
18306 .map(|unit| ResolvedTypeOwner {
18307 unit,
18308 is_forward_declaration: false,
18309 }),
18310 }
18311 }
18312 }
18313}
18314
18315fn exact_structural_type_parent(
18316 analyzer: &CppGraphSource<'_>,
18317 code_unit: &CodeUnit,
18318) -> Option<CodeUnit> {
18319 if !code_unit.is_function() && !code_unit.is_field() {
18320 return None;
18321 }
18322 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
18324 let parent = cpp.structural_parent_of(code_unit)?;
18325 (!parent.is_module()
18326 && parent.source() == code_unit.source()
18327 && parent.package_name() == code_unit.package_name()
18328 && parent.short_name() == encoded_owner)
18329 .then_some(parent)
18330}
18331
18332fn same_source_owner(
18333 analyzer: &CppGraphSource<'_>,
18334 code_unit: &CodeUnit,
18335 owner_candidates: &[CodeUnit],
18336) -> DirectOwnerResolution {
18337 let candidates = owner_candidates
18338 .iter()
18339 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18340 .cloned()
18341 .collect::<Vec<_>>();
18342 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18343 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18344}
18345
18346fn visible_full_cpp_owner(
18347 analyzer: &CppGraphSource<'_>,
18348 code_unit: &CodeUnit,
18349 owner_candidates: &[CodeUnit],
18350) -> FullOwnerResolution {
18351 let Some(cpp) = analyzer.cpp else {
18352 return FullOwnerResolution::None;
18353 };
18354 let mut visible_files = HashSet::default();
18355 collect_include_closure(
18356 analyzer,
18357 cpp.include_target_index(),
18358 code_unit.source(),
18359 &mut visible_files,
18360 None,
18361 );
18362 let candidates = owner_candidates
18363 .iter()
18364 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18365 .cloned()
18366 .collect::<Vec<_>>();
18367 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18368 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18369 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18370 (candidate, strength)
18371 })) {
18372 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18373 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18374 FullOwnerResolution::None
18375 }
18376 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18377 }
18378}
18379
18380pub enum DirectOwnerResolution {
18381 None,
18382 ForwardsOnly(Vec<CodeUnit>),
18383 UniqueFull(CodeUnit),
18384 Ambiguous,
18385}
18386
18387enum FullOwnerResolution {
18388 None,
18389 Unique(CodeUnit),
18390 Ambiguous,
18391}
18392
18393fn directly_included_owner(
18394 analyzer: &CppGraphSource<'_>,
18395 code_unit: &CodeUnit,
18396 owner_candidates: &[CodeUnit],
18397) -> DirectOwnerResolution {
18398 let Some(cpp) = analyzer.cpp else {
18399 return DirectOwnerResolution::None;
18400 };
18401 let direct_includes: HashSet<ProjectFile> = analyzer
18402 .canonical_include_paths(code_unit.source())
18403 .unwrap_or_default()
18404 .into_iter()
18405 .flat_map(|include| {
18406 resolve_include_targets_with_index(
18407 code_unit.source(),
18408 &include,
18409 cpp.include_target_index(),
18410 )
18411 })
18412 .collect();
18413 let candidates = owner_candidates
18414 .iter()
18415 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18416 .cloned()
18417 .collect::<Vec<_>>();
18418 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18419 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18420}
18421
18422fn prefer_member_declaring_owners(
18423 analyzer: &CppGraphSource<'_>,
18424 member: &CodeUnit,
18425 candidates: Vec<CodeUnit>,
18426) -> Vec<CodeUnit> {
18427 let matching = candidates
18428 .iter()
18429 .filter(|owner| owner_declares_member(analyzer, owner, member))
18430 .cloned()
18431 .collect::<Vec<_>>();
18432 if matching.is_empty() {
18433 candidates
18434 } else {
18435 matching
18436 }
18437}
18438
18439fn owner_declares_member(
18440 analyzer: &CppGraphSource<'_>,
18441 owner: &CodeUnit,
18442 member: &CodeUnit,
18443) -> bool {
18444 analyzer.direct_children(owner).into_iter().any(|child| {
18445 child.kind() == member.kind()
18446 && child.identifier() == member.identifier()
18447 && child.signature() == member.signature()
18448 })
18449}
18450
18451fn classify_direct_owner_candidates(
18452 analyzer: &CppGraphSource<'_>,
18453 candidates: impl Iterator<Item = CodeUnit>,
18454) -> DirectOwnerResolution {
18455 collapse_owner_candidates(candidates.map(|candidate| {
18456 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18457 (candidate, strength)
18458 }))
18459}
18460
18461pub fn collapse_owner_candidates(
18462 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18463) -> DirectOwnerResolution {
18464 let mut full_definition = None;
18465 let mut forwards = Vec::new();
18466 for (candidate, strength) in candidates {
18467 match strength {
18468 CppClassDeclarationStrength::Full if full_definition.is_some() => {
18469 return DirectOwnerResolution::Ambiguous;
18470 }
18471 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18472 CppClassDeclarationStrength::Forward => forwards.push(candidate),
18473 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18474 }
18475 }
18476 if let Some(owner) = full_definition {
18477 DirectOwnerResolution::UniqueFull(owner)
18478 } else if !forwards.is_empty() {
18479 DirectOwnerResolution::ForwardsOnly(forwards)
18480 } else {
18481 DirectOwnerResolution::None
18482 }
18483}
18484
18485#[cfg(any(test, feature = "test-support"))]
18486pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18487 unique_logical_forward_owner(forwards)
18488}
18489
18490fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18491 let first = forwards.pop()?;
18492 forwards
18493 .iter()
18494 .all(|forward| same_logical_symbol(forward, &first))
18495 .then_some(first)
18496}
18497
18498pub fn cpp_class_declaration_strength(
18499 analyzer: &CppGraphSource<'_>,
18500 candidate: &CodeUnit,
18501) -> CppClassDeclarationStrength {
18502 let Some(cpp) = analyzer.cpp else {
18510 return uncached_cpp_class_declaration_strength(analyzer, candidate);
18511 };
18512 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18513 return strength;
18514 }
18515 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18516 cpp.cache_class_declaration_strength(candidate, strength);
18517 strength
18518}
18519
18520fn uncached_cpp_class_declaration_strength(
18521 analyzer: &CppGraphSource<'_>,
18522 candidate: &CodeUnit,
18523) -> CppClassDeclarationStrength {
18524 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18525 if facts
18526 .iter()
18527 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18528 {
18529 return CppClassDeclarationStrength::Full;
18530 }
18531 if facts
18532 .iter()
18533 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18534 {
18535 return CppClassDeclarationStrength::Forward;
18536 }
18537 }
18538 if let Some(cpp) = analyzer.cpp
18539 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18540 {
18541 return cpp_class_declaration_strength_in_tree(
18542 analyzer,
18543 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18544 candidate,
18545 prepared.source(),
18546 prepared.tree().root_node(),
18547 );
18548 }
18549 let Some(source) = analyzer.indexed_source(candidate.source()) else {
18550 return CppClassDeclarationStrength::Unknown;
18551 };
18552 let mut parser = Parser::new();
18553 if parser
18554 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18555 .is_err()
18556 {
18557 return CppClassDeclarationStrength::Unknown;
18558 }
18559 let Some(tree) = parser.parse(&source, None) else {
18560 return CppClassDeclarationStrength::Unknown;
18561 };
18562 let recovered_export_classes =
18565 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18566 cpp_class_declaration_strength_in_tree(
18567 analyzer,
18568 &recovered_export_classes,
18569 candidate,
18570 &source,
18571 tree.root_node(),
18572 )
18573}
18574
18575fn cpp_class_declaration_strength_in_tree(
18576 analyzer: &CppGraphSource<'_>,
18577 recovered_export_classes: &CppRecoveredExportClassIndex,
18578 candidate: &CodeUnit,
18579 source: &str,
18580 root: Node<'_>,
18581) -> CppClassDeclarationStrength {
18582 let ranges = analyzer.ranges(candidate);
18583 let mut saw_forward = false;
18584 for range in ranges {
18585 match recovered_class_body_at(
18588 recovered_export_classes,
18589 root,
18590 source,
18591 candidate.identifier(),
18592 &range,
18593 ) {
18594 Some(true) => return CppClassDeclarationStrength::Full,
18595 Some(false) => {
18596 saw_forward = true;
18597 continue;
18598 }
18599 None => {}
18600 }
18601 let covers_range_start = |node: &Node<'_>| {
18608 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18609 };
18610 let mut stack = Vec::new();
18611 if covers_range_start(&root) {
18612 stack.push(root);
18613 }
18614 while let Some(node) = stack.pop() {
18615 if node.start_byte() == range.start_byte
18616 && node.end_byte() == range.end_byte
18617 && matches!(
18618 node.kind(),
18619 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18620 )
18621 {
18622 if cpp_class_node_has_body(node) {
18623 return CppClassDeclarationStrength::Full;
18624 }
18625 saw_forward = true;
18626 }
18627 let mut cursor = node.walk();
18628 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18629 }
18630 }
18631 if saw_forward {
18632 CppClassDeclarationStrength::Forward
18633 } else {
18634 CppClassDeclarationStrength::Unknown
18635 }
18636}
18637
18638fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18639 child_by_field_or_named_child_of_kinds(
18640 node,
18641 "body",
18642 &[
18643 "declaration_list",
18644 "field_declaration_list",
18645 "enumerator_list",
18646 ],
18647 )
18648 .is_some()
18649}
18650
18651#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18652enum CppCTagKind {
18653 Struct,
18654 Union,
18655}
18656
18657fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18658 let declaration = analyzer.get_source(code_unit, false)?;
18659 let mut parser = Parser::new();
18660 parser
18661 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18662 .ok()?;
18663 let tree = parser.parse(&declaration, None)?;
18664 let mut stack = vec![tree.root_node()];
18665 while let Some(node) = stack.pop() {
18666 let kind = match node.kind() {
18667 "struct_specifier" => CppCTagKind::Struct,
18668 "union_specifier" => CppCTagKind::Union,
18669 _ => {
18670 let mut cursor = node.walk();
18671 stack.extend(node.named_children(&mut cursor));
18672 continue;
18673 }
18674 };
18675 if node
18676 .child_by_field_name("name")
18677 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18678 {
18679 return Some(kind);
18680 }
18681 let mut cursor = node.walk();
18682 stack.extend(node.named_children(&mut cursor));
18683 }
18684 None
18685}
18686
18687pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18688 if !code_unit.owner_is_type_scope() {
18689 return None;
18690 }
18691 let owner_fq = code_unit.fq().parent()?;
18692 ctx.analyzer
18693 .workspace_definitions()
18694 .exact(&owner_fq)
18695 .into_iter()
18696 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18697}
18698
18699pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18700 same_logical_symbol(left, right) && left.source() == right.source()
18701}
18702
18703pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18704 same_symbol(left, right) || same_logical_symbol(left, right)
18705}
18706
18707pub fn same_visible_global_field_symbol(
18708 analyzer: &CppGraphSource<'_>,
18709 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18710 left: &CodeUnit,
18711 right: &CodeUnit,
18712) -> bool {
18713 if same_symbol(left, right) {
18714 return true;
18715 }
18716 if !same_logical_symbol(left, right) {
18717 return false;
18718 }
18719 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18720 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18721 {
18722 left.source() == right.source()
18723 } else {
18724 true
18725 }
18726}
18727
18728fn cpp_global_field_has_internal_linkage_cached(
18729 analyzer: &CppGraphSource<'_>,
18730 cache: &mut HashMap<CodeUnit, bool>,
18731 candidate: &CodeUnit,
18732) -> bool {
18733 if let Some(internal) = cache.get(candidate) {
18734 return *internal;
18735 }
18736 #[cfg(any(test, feature = "test-support"))]
18737 note_cpp_global_field_internal_linkage_classification_for_test();
18738 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18739 cache.insert(candidate.clone(), internal);
18740 internal
18741}
18742
18743#[cfg(any(test, feature = "test-support"))]
18744thread_local! {
18745 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18746}
18747
18748#[cfg(any(test, feature = "test-support"))]
18749fn note_cpp_global_field_internal_linkage_classification_for_test() {
18750 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18751 count.set(count.get() + 1);
18752 });
18753}
18754
18755#[cfg(any(test, feature = "test-support"))]
18756pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18757 body: impl FnOnce() -> T,
18758) -> (T, usize) {
18759 with_thread_local_counter_for_test(
18760 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18761 body,
18762 )
18763}
18764
18765#[cfg(any(test, feature = "test-support"))]
18766fn with_thread_local_counter_for_test<T>(
18767 counter: &'static std::thread::LocalKey<Cell<usize>>,
18768 body: impl FnOnce() -> T,
18769) -> (T, usize) {
18770 counter.with(|count| {
18771 count.set(0);
18772 let result = body();
18773 let observed = count.get();
18774 count.set(0);
18775 (result, observed)
18776 })
18777}
18778
18779pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18780 left.kind() == right.kind()
18781 && left.fq_name() == right.fq_name()
18782 && left.signature() == right.signature()
18783}
18784
18785pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18788 candidates: &[&'a CodeUnit],
18789 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18790) -> Option<&'a CodeUnit> {
18791 let first = *candidates.first()?;
18792 if candidates.len() == 1 {
18793 return Some(first);
18794 }
18795 let first_target = structured_target(first)?;
18796 candidates
18797 .iter()
18798 .skip(1)
18799 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18800 .then_some(first)
18801}
18802
18803pub fn cpp_global_field_has_internal_linkage(
18804 analyzer: &CppGraphSource<'_>,
18805 candidate: &CodeUnit,
18806) -> bool {
18807 if !candidate.is_field() || candidate.short_name().contains('.') {
18808 return false;
18809 }
18810 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18811 return false;
18812 };
18813 match local_linkage {
18814 CppFieldLinkage::Internal => true,
18815 CppFieldLinkage::External => false,
18816 CppFieldLinkage::InternalUnlessExternalPeer => {
18817 !cpp_global_field_linkage_peers(analyzer, candidate)
18818 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18819 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18820 }
18821 }
18822}
18823
18824fn cpp_global_field_linkage_peers<'a>(
18825 analyzer: &CppGraphSource<'a>,
18826 candidate: &'a CodeUnit,
18827) -> impl Iterator<Item = CodeUnit> + 'a {
18828 let name = candidate.fq().clone();
18829 analyzer
18830 .workspace_definitions()
18831 .exact(&name)
18832 .into_iter()
18833 .filter(move |peer| {
18834 if peer == candidate {
18835 return false;
18836 }
18837 #[cfg(any(test, feature = "test-support"))]
18838 note_cpp_global_field_linkage_peer_inspection_for_test();
18839 same_logical_symbol(peer, candidate)
18840 })
18841}
18842
18843#[cfg(any(test, feature = "test-support"))]
18844thread_local! {
18845 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18846}
18847
18848#[cfg(any(test, feature = "test-support"))]
18849fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18850 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18851 count.set(count.get() + 1);
18852 });
18853}
18854
18855#[cfg(any(test, feature = "test-support"))]
18856pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18857 body: impl FnOnce() -> T,
18858) -> (T, usize) {
18859 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18860}
18861
18862fn cpp_global_field_declaration_linkage(
18863 analyzer: &CppGraphSource<'_>,
18864 candidate: &CodeUnit,
18865) -> Option<CppFieldLinkage> {
18866 analyzer.cpp_field_linkage(candidate)
18867}
18868
18869pub fn cpp_canonical_alias_target_text(
18871 analyzer: &CppGraphSource<'_>,
18872 unit: &CodeUnit,
18873) -> Option<String> {
18874 let facts = analyzer.declaration_source_properties(unit)?;
18875 let first = facts.first()?.alias_target_text.as_ref()?;
18876 facts
18877 .iter()
18878 .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18879 .then(|| first.clone())
18880}
18881
18882#[cfg(test)]
18883mod tests {
18884 #[test]
18885 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18886 fn issue_3089_statement_formal_at_end_of_replacement() {
18887 let parameters = vec!["handle".to_owned(), "block".to_owned()];
18888 for replacement in [
18889 "do { header_event_t* event; if ((handle)->active) block } while (0)",
18890 "do { header_event_t* event; block } while (0)",
18891 ] {
18892 assert!(
18893 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
18894 .is_some(),
18895 "{replacement}"
18896 );
18897 }
18898 }
18899 use super::*;
18900 use brokk_bifrost_test_macros::hardwon;
18901
18902 #[test]
18903 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18904 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18905 let source = "int size(void) { return sizeof(((Payload))); }\n";
18906 let mut parser = Parser::new();
18907 parser
18908 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18909 .expect("C++ grammar");
18910 let tree = parser.parse(source, None).expect("fixture tree");
18911 let start = source.find("Payload").expect("sizeof operand");
18912 let node = tree
18913 .root_node()
18914 .named_descendant_for_byte_range(start, start + "Payload".len())
18915 .expect("focused operand");
18916 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18917 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18918
18919 assert_eq!(node.kind(), "identifier");
18920 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18921 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18922 }
18923
18924 fn parse_cpp(source: &str) -> Tree {
18925 crate::diagnostics::parse_cpp_tree(source)
18926 }
18927
18928 #[test]
18929 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18930 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18931 let source = "auto value = new ns::Box<int> [ 3 ];";
18932 let tree = parse_cpp(source);
18933 let mut pending = vec![tree.root_node()];
18934 let allocation = loop {
18935 let node = pending.pop().expect("new expression in fixture");
18936 if node.kind() == "new_expression" {
18937 break node;
18938 }
18939 let mut cursor = node.walk();
18940 pending.extend(
18941 node.named_children(&mut cursor)
18942 .collect::<Vec<_>>()
18943 .into_iter()
18944 .rev(),
18945 );
18946 };
18947
18948 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18949 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18950 assert_eq!(node_text(type_node, source), "ns::Box<int>");
18951 assert_eq!(
18952 allocation
18953 .child_by_field_name("declarator")
18954 .expect("array declarator")
18955 .kind(),
18956 "new_declarator"
18957 );
18958
18959 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18960 let old_split = old_split
18961 .strip_prefix("new ")
18962 .unwrap_or(old_split.as_str())
18963 .split(['(', '{'])
18964 .next()
18965 .expect("old split result");
18966 assert_ne!(old_split.trim(), node_text(type_node, source));
18967 }
18968
18969 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18970 let start = source.find(needle).expect("fixture needle");
18971 tree.root_node()
18972 .named_descendant_for_byte_range(start, start + needle.len())
18973 .expect("node at needle")
18974 }
18975
18976 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18977 let tree = crate::diagnostics::parse_cpp_tree(source);
18978 PreparedSyntaxTree::new(
18979 PreparedSyntaxSource::Exact(Arc::from(source)),
18980 tree,
18981 compute_line_starts(source),
18982 LanguageDialect::Standard(Language::Cpp),
18983 PreparedSourceOrigin::Disk,
18984 None,
18985 )
18986 }
18987
18988 fn unresolved_include_before(source: &str, reference: &str) -> bool {
18989 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18990 let prepared = prepared_cpp(source);
18991 let facts = collect_structured_include_facts(&prepared);
18992 let include_targets = IncludeTargetIndex::build([&file]);
18993 has_unresolved_include_visible_before_in_prepared(
18994 &file,
18995 &prepared,
18996 &include_targets,
18997 &facts,
18998 source.find(reference).expect("reference fixture"),
18999 )
19000 }
19001
19002 #[test]
19003 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19004 fn unresolved_include_before_reference_is_visible() {
19005 let source = "#include \"missing.h\"\nint use = Missing;\n";
19006 assert!(unresolved_include_before(source, "Missing"));
19007 }
19008
19009 #[test]
19010 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19011 fn unresolved_include_after_reference_is_not_visible() {
19012 let source = "int use = Missing;\n#include \"missing.h\"\n";
19013 assert!(!unresolved_include_before(source, "Missing"));
19014 }
19015
19016 #[test]
19017 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19018 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
19019 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
19020 assert!(!unresolved_include_before(source, "Missing"));
19021 }
19022
19023 #[test]
19024 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
19025 fn unresolved_include_in_current_branch_is_visible() {
19026 let source =
19027 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
19028 assert!(unresolved_include_before(source, "Missing"));
19029 }
19030
19031 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
19036namespace matchers {
19037 namespace detail {
19038 class API [[nodiscard]] First {
19039 public:
19040 int value() const { return count_ + 1; }
19041 private:
19042 int count_;
19043 };
19044 class API [[nodiscard]] Second {
19045 public:
19046 int value() const { return count_ + 2; }
19047 private:
19048 int count_;
19049 };
19050 } // namespace detail
19051
19052 template <typename T>
19053 void tail_function(MatcherBase<T> const& value);
19054
19055 class TailClass {};
19056} // namespace matchers
19057} // namespace app
19058
19059struct AfterAll {};
19060"#;
19061
19062 #[test]
19063 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19064 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
19065 let source = STOLEN_BRACE_CASCADE;
19066 let tree = parse_cpp(source);
19067 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19068
19069 let tail_class = named_node_at(&tree, source, "TailClass");
19070 assert!(
19071 !has_ancestor_kind(tail_class, "namespace_definition"),
19072 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
19073 );
19074 let displaced = named_node_at(&tree, source, "Second");
19075 assert_eq!(
19076 enclosing_namespace_components(displaced, source),
19077 Some(vec!["app".to_string(), "matchers".to_string()]),
19078 "the fixture must displace the second class out of detail"
19079 );
19080
19081 let components = |needle: &str| {
19082 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
19083 };
19084 assert_eq!(components("First"), ["app", "matchers", "detail"]);
19085 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
19086 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
19087 assert_eq!(components("tail_function"), ["app", "matchers"]);
19088 assert_eq!(components("TailClass"), ["app", "matchers"]);
19089 assert!(components("AfterAll").is_empty());
19090 }
19091
19092 #[test]
19093 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
19094 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
19095 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
19096 let tree = parse_cpp(clean);
19097 assert!(!tree.root_node().has_error());
19098 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
19099
19100 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
19103 let tree = parse_cpp(damaged);
19104 assert!(tree.root_node().has_error());
19105 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
19106 assert_eq!(
19107 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
19108 ["a"]
19109 );
19110 }
19111
19112 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
19119
19120 class Target {
19121 int value_;
19122 };
19123
19124 template <typename T>
19125 class Holder {
19126 public:
19127 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
19128
19129#define HOLDER_DEFINE_OP( id, op ) \
19130 template <typename U> \
19131 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
19132 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
19133 return Target{}; \
19134 }
19135
19136 HOLDER_DEFINE_OP( equal, == )
19137#undef HOLDER_DEFINE_OP
19138 T m_lhs;
19139 };
19140
19141 class Tail {};
19142}
19143"#;
19144
19145 #[test]
19146 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
19147 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
19148 let source = COLLAPSED_NAMESPACE_HEAD;
19149 let tree = parse_cpp(source);
19150 let target = named_node_at(&tree, source, "class Target");
19151
19152 assert!(
19158 tree.root_node().has_error(),
19159 "the fixture must retain the macro invocation's parse damage"
19160 );
19161 assert!(
19162 has_ancestor_kind(target, "namespace_definition"),
19163 "the namespace head survives: the class keeps its parsed namespace ancestor"
19164 );
19165
19166 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19167 assert_eq!(
19168 index.enclosing_namespace_components(target, source),
19169 ["app"]
19170 );
19171 assert_eq!(
19172 index.enclosing_namespace_levels(target, source),
19173 vec![(
19174 "app".to_string(),
19175 Range {
19176 start_byte: 0,
19177 end_byte: source.len() - 1,
19178 start_line: 1,
19179 end_line: 25,
19180 },
19181 Range {
19182 start_byte: 10,
19183 end_byte: 13,
19184 start_line: 1,
19185 end_line: 1,
19186 },
19187 )],
19188 "the recovered namespace owns its full construct, not its first member or just its header"
19189 );
19190 }
19191
19192 #[test]
19193 #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
19194 fn namespace_body_name_components_recovers_literal_function_head() {
19195 let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
19201 let tree = parse_cpp(source);
19202 let keyword_start = source.find("namespace recovered").unwrap();
19203 let keyword = tree
19204 .root_node()
19205 .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
19206 .expect("literal keyword node");
19207 assert_eq!(keyword.kind(), "type_identifier");
19212 assert_eq!(keyword.child_count(), 0);
19213 assert!(!keyword.is_missing() && !keyword.has_error());
19214 assert_eq!(node_text(keyword, source), "namespace");
19215 let wrapper = keyword.parent().expect("actual wrapper");
19216 assert_eq!(wrapper.kind(), "function_definition");
19217 let mut cursor = wrapper.walk();
19218 let children = wrapper
19219 .children(&mut cursor)
19220 .filter(|child| child.kind() != "comment")
19221 .collect::<Vec<_>>();
19222 assert_eq!(
19223 children.len(),
19224 4,
19225 "all named and unnamed children accounted: {children:?}"
19226 );
19227 let prefix = children[0];
19228 assert_eq!(prefix.kind(), "macro_modifier");
19229 assert!(!prefix.is_missing() && !prefix.has_error());
19230 assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
19231 assert_eq!(children[1], keyword);
19232 assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
19233 let name = children[2];
19234 assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
19235 assert_eq!(name.child_count(), 0);
19236 assert!(!name.is_missing() && !name.has_error());
19237 assert_eq!(node_text(name, source), "recovered");
19238 assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
19239 let body = wrapper.child_by_field_name("body").expect("actual body");
19240 assert_eq!(children[3], body);
19241 assert_eq!(body.kind(), "compound_statement");
19242 assert!(!body.is_missing());
19243 let open = body.child(0).expect("actual first child");
19244 assert_eq!(open.kind(), "{");
19245 assert!(!open.is_missing() && !open.has_error());
19246 assert_eq!(open.start_byte(), body.start_byte());
19247 let (head, names) = namespace_body_name_components(wrapper, body, source)
19248 .expect("strict synthetic literal head recognized");
19249 assert_eq!(head, keyword);
19250 assert_eq!(
19251 names
19252 .iter()
19253 .map(|(name, _)| name.as_str())
19254 .collect::<Vec<_>>(),
19255 ["recovered"]
19256 );
19257 assert_eq!(
19258 (names[0].1.start_byte, names[0].1.end_byte),
19259 (name.start_byte(), name.end_byte())
19260 );
19261 let inside = named_node_at(&tree, source, "int member;");
19262 assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
19263 }
19264
19265 #[test]
19266 #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
19267 fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
19268 for refusal in [
19269 "int ordinary(int value) { return value; }",
19270 "OPAQUE_PREFIX namespace_ recovered { int member; }",
19271 "OPAQUE_PREFIX namespace recovered other { int member; }",
19272 "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
19273 ] {
19274 let tree = parse_cpp(refusal);
19275 let mut stack = vec![tree.root_node()];
19276 let mut helper_calls = 0;
19277 while let Some(node) = stack.pop() {
19278 if node.kind() == "function_definition"
19279 && let Some(body) = node.child_by_field_name("body")
19280 {
19281 helper_calls += 1;
19282 assert_eq!(
19283 (node.start_byte(), node.end_byte()),
19284 (0, refusal.len()),
19285 "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
19286 );
19287 assert!(
19288 namespace_body_name_components(node, body, refusal).is_none(),
19289 "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
19290 );
19291 }
19292 let mut cursor = node.walk();
19293 stack.extend(node.named_children(&mut cursor));
19294 }
19295 assert_eq!(
19296 helper_calls,
19297 1,
19298 "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
19299 tree.root_node()
19300 );
19301 }
19302
19303 for absent in [
19309 "OPAQUE_PREFIX namespace recovered = other { int member; }",
19310 "OPAQUE_PREFIX namespace recovered int member;",
19311 ] {
19312 let tree = parse_cpp(absent);
19313 let mut stack = vec![tree.root_node()];
19314 let mut helper_candidates = 0;
19315 while let Some(node) = stack.pop() {
19316 if node.kind() == "function_definition"
19317 && node.child_by_field_name("body").is_some()
19318 {
19319 helper_candidates += 1;
19320 }
19321 let mut cursor = node.walk();
19322 stack.extend(node.named_children(&mut cursor));
19323 }
19324 assert_eq!(
19325 helper_candidates,
19326 0,
19327 "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
19328 tree.root_node()
19329 );
19330 }
19331 }
19332
19333 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
19342
19343namespace app {
19344namespace detail {
19345
19346bool check(bool active) {
19347 APP_IF_ACTIVE
19348 return true;
19349 }
19350 return false;
19351}
19352
19353class Tail {};
19354
19355} // namespace detail
19356} // namespace app
19357"#;
19358
19359 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
19366namespace compile_time {
19367namespace number_parsing {
19368consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
19369 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
19370 auto digit = [](uint8_t c) -> uint32_t {
19371 }
19372 }
19373 if (a) {
19374 while (a) {
19375 }
19376 }
19377 switch (c) {
19378 case '{': {
19379 }
19380 }
19381 }
19382 while (a) {
19383 cursor += field.second;
19384 if (a) {
19385 case '{': {
19386 if (a) {
19387 }
19388 }
19389 case 'n': {
19390 if (a) {
19391 }
19392 }
19393 }
19394 }
19395 if (a) {
19396 simdjson_consteval_error("Expected '}'");
19397 }
19398}
19399}
19400}
19401"#;
19402
19403 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
19405 let mut stack = vec![root];
19406 let mut counts = (0, 0);
19407 while let Some(node) = stack.pop() {
19408 match (node.kind(), node.is_missing()) {
19409 ("{", false) => counts.0 += 1,
19410 ("}", false) => counts.1 += 1,
19411 _ => {}
19412 }
19413 let mut cursor = node.walk();
19414 stack.extend(node.children(&mut cursor));
19415 }
19416 counts
19417 }
19418
19419 fn namespace_open_byte(source: &str, head: &str) -> usize {
19421 let at = source.find(head).expect("fixture namespace head");
19422 at + head.len() - 1
19423 }
19424
19425 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
19426 let index = OrphanedNamespaceScopeIndex::build(root, source);
19427 for head in heads {
19428 assert_eq!(
19429 index.matching_close_brace(namespace_open_byte(source, head)),
19430 None,
19431 "{head} must not claim a close belonging to an inner scope"
19432 );
19433 }
19434 }
19435
19436 #[test]
19437 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19438 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
19439 let source = MACRO_OPENED_SCOPE;
19440 let tree = parse_cpp(source);
19441 let root = tree.root_node();
19442 assert_eq!(
19443 real_brace_token_counts(root),
19444 (4, 5),
19445 "the fixture must reproduce the loss: one scope the source opens \
19446 has no `{{` token, so the stream holds an extra real close"
19447 );
19448
19449 assert_namespace_heads_have_no_matching_close(
19450 root,
19451 source,
19452 &["namespace app {", "namespace detail {"],
19453 );
19454 }
19455
19456 #[test]
19457 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
19458 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
19459 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
19463 let tree = parse_cpp(&source);
19464 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
19465 let after_all = named_node_at(&tree, &source, "struct AfterAll");
19466 assert!(
19467 index
19468 .enclosing_namespace_components(after_all, &source)
19469 .is_empty()
19470 );
19471 }
19472
19473 #[test]
19474 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19475 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
19476 let source = REFLECTION_CASE_LABEL_RECOVERY;
19477 let tree = parse_cpp(source);
19478 let root = tree.root_node();
19479 let mut stack = vec![root];
19480 let mut mis_lexed_opens = Vec::new();
19481 while let Some(node) = stack.pop() {
19482 if node.kind() == "character"
19483 && &source[node.start_byte()..node.end_byte()] == "{"
19484 && node
19485 .parent()
19486 .is_some_and(|parent| parent.kind() != "char_literal")
19487 {
19488 mis_lexed_opens.push(node.start_position().row + 1);
19489 }
19490 let mut cursor = node.walk();
19491 stack.extend(node.children(&mut cursor));
19492 }
19493 assert!(
19494 mis_lexed_opens.is_empty(),
19495 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
19496 the old ABI14 mis-lex is no longer a valid recovery witness"
19497 );
19498 assert_eq!(
19499 real_brace_token_counts(root),
19500 (17, 17),
19501 "the improved parse retains balanced real brace tokens"
19502 );
19503 assert_eq!(root.kind(), "translation_unit");
19504 assert!(root.has_error(), "the malformed body remains incomplete");
19505 let index = OrphanedNamespaceScopeIndex::build(root, source);
19506 assert_eq!(
19507 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
19508 None,
19509 "a missing outer close must not borrow an inner namespace's real close"
19510 );
19511 for (head, close) in [
19512 ("namespace compile_time {", source.len() - 2),
19513 ("namespace number_parsing {", source.len() - 4),
19514 ] {
19515 assert_eq!(
19516 index
19517 .matching_close_brace(namespace_open_byte(source, head))
19518 .map(|range| range.start_byte),
19519 Some(close),
19520 "{head} retains its own real close"
19521 );
19522 }
19523 }
19524
19525 #[test]
19526 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
19527 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
19528 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
19529 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
19530 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
19531 assert!(indexed_namespace_path_is_recoverable(
19532 &["cache".to_string()],
19533 &indexed,
19534 1,
19535 ));
19536 }
19537
19538 #[test]
19539 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
19540 fn sort_lookup_units_totally_orders_every_identity_field() {
19541 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
19542 let base = CodeUnit::with_signature(
19543 file.clone(),
19544 CodeUnitType::Function,
19545 "scope",
19546 "value",
19547 Some("()".to_string()),
19548 false,
19549 );
19550 let different_kind = CodeUnit::with_signature(
19551 file.clone(),
19552 CodeUnitType::Field,
19553 "scope",
19554 "value",
19555 Some("()".to_string()),
19556 false,
19557 );
19558 let synthetic = base.with_synthetic(true);
19559
19560 let interner = segment_interner();
19561 let mut member_fq = FqName::new();
19562 member_fq.push(interner.intern("scope", SegmentKind::Package));
19563 member_fq.push(interner.intern("value", SegmentKind::Member));
19564 let different_package_boundary = CodeUnit::from_fq(
19565 file.clone(),
19566 CodeUnitType::Function,
19567 member_fq,
19568 0,
19569 Some("()".to_string()),
19570 false,
19571 );
19572
19573 let mut unknown_fq = FqName::new();
19574 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19575 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19576 let different_segment_kind = CodeUnit::from_fq(
19577 file,
19578 CodeUnitType::Function,
19579 unknown_fq,
19580 1,
19581 Some("()".to_string()),
19582 false,
19583 );
19584
19585 let input = vec![
19586 base,
19587 different_kind,
19588 synthetic,
19589 different_package_boundary,
19590 different_segment_kind,
19591 ];
19592 let mut expected = input.clone();
19593 sort_lookup_units(&mut expected);
19594 assert!(expected.windows(2).all(|pair| {
19595 let mut ordered = pair.to_vec();
19596 sort_lookup_units(&mut ordered);
19597 ordered == pair && pair[0] != pair[1]
19598 }));
19599
19600 let mut reversed = input.clone();
19601 reversed.reverse();
19602 sort_lookup_units(&mut reversed);
19603 assert_eq!(reversed, expected);
19604
19605 let mut rotated = input;
19606 rotated.rotate_left(2);
19607 sort_lookup_units(&mut rotated);
19608 assert_eq!(rotated, expected);
19609 }
19610
19611 #[test]
19612 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19613 fn structured_conditional_array_bound_preserves_the_real_guard() {
19614 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";
19615 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19616 let parse = |source: &str| {
19617 let mut parser = Parser::new();
19618 parser
19619 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19620 .expect("C++ grammar");
19621 parser.parse(source, None).expect("fixture tree")
19622 };
19623
19624 let tree = parse(damaged);
19625 let root = tree.root_node();
19626 assert!(
19627 !root.has_error(),
19628 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19629 root.to_sexp()
19630 );
19631 let target = damaged.find("target").expect("target byte");
19632 let declaration = root
19633 .descendant_for_byte_range(target, target + "target".len())
19634 .and_then(|mut node| {
19635 loop {
19636 if node.kind() == "declaration" {
19637 break Some(node);
19638 }
19639 node = node.parent()?;
19640 }
19641 })
19642 .expect("declaration after the displaced terminator");
19643 let outer = declaration
19644 .parent()
19645 .filter(|node| node.kind() == "preproc_ifdef")
19646 .expect("ordinary outer include guard");
19647 let bound = outer
19648 .named_children(&mut outer.walk())
19649 .filter(|node| node.kind() == "declaration")
19650 .filter_map(|node| node.child_by_field_name("declarator"))
19651 .find(|node| node.kind() == "array_declarator")
19652 .and_then(|node| node.child_by_field_name("size"))
19653 .expect("structured conditional array bound");
19654 assert_eq!(bound.kind(), "preproc_array_size");
19655 let conditional = bound
19656 .named_children(&mut bound.walk())
19657 .find(|node| node.kind() == "preproc_ifdef")
19658 .expect("array-size conditional");
19659 assert!(conditional.end_byte() <= declaration.start_byte());
19660 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19661 assert!(
19662 !std::iter::successors(Some(declaration), |node| node.parent())
19663 .any(|ancestor| same_node(ancestor, conditional)),
19664 "the declaration must not inherit the array-size feature guard"
19665 );
19666 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19667 assert!(preprocessor_conditional_contains_descendant(
19668 outer,
19669 declaration
19670 ));
19671
19672 let tree = parse(guarded);
19673 let conditional = tree
19674 .root_node()
19675 .named_child(0)
19676 .filter(|node| node.kind() == "preproc_ifdef")
19677 .expect("ordinary conditional");
19678 let declaration = conditional
19679 .named_children(&mut conditional.walk())
19680 .find(|node| node.kind() == "declaration")
19681 .expect("guarded declaration");
19682 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19683 assert!(preprocessor_conditional_contains_descendant(
19684 conditional,
19685 declaration
19686 ));
19687
19688 let damaged_alternative = format!(
19689 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19690 "UNUSED(value)\n".repeat(64)
19691 );
19692 let tree = parse(&damaged_alternative);
19693 let conditional = tree
19694 .root_node()
19695 .named_child(0)
19696 .filter(|node| node.kind() == "preproc_ifdef")
19697 .expect("outer conditional with an alternative");
19698 assert!(conditional.has_error());
19699 assert!(conditional.child_by_field_name("alternative").is_some());
19700 assert!(
19701 conditional
19702 .child(conditional.child_count() - 1)
19703 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19704 );
19705 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19706
19707 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";
19708 let tree = parse(split_declaration);
19709 let root = tree.root_node();
19710 let split = root
19717 .named_children(&mut root.walk())
19718 .find(|node| node.kind() == "type_definition")
19719 .expect("the split declaration folds into one type definition");
19720 assert_eq!(
19721 split.end_position().row + 1,
19722 9,
19723 "the split declaration ends at its own declarator line"
19724 );
19725 let target = split_declaration
19726 .find("static int target")
19727 .expect("target byte");
19728 assert!(split.end_byte() <= target);
19729 let target_node = root
19730 .descendant_for_byte_range(target, target + "static".len())
19731 .expect("target node");
19732 assert!(
19733 !std::iter::successors(Some(target_node), |node| node.parent())
19734 .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
19735 "the declaration after the shredded split declaration must not \
19736 inherit a damaged conditional ancestor"
19737 );
19738 }
19739
19740 #[test]
19741 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19742 fn fragmented_reference_guard_is_recovered() {
19743 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";
19744 let mut parser = Parser::new();
19745 parser
19746 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19747 .expect("C++ grammar");
19748 let tree = parser.parse(source, None).expect("fixture tree");
19749 let start = source.rfind("helper").expect("reference byte");
19750 let node = tree
19751 .root_node()
19752 .descendant_for_byte_range(start, start + "helper".len())
19753 .expect("reference node");
19754 let mut expected = HashSet::default();
19755 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19756 vec![
19757 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19758 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19759 ],
19760 )));
19761 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19762 }
19763
19764 #[test]
19765 #[hardwon(
19766 "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
19767 )]
19768 fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
19769 let parse = |source: &str| {
19770 let mut parser = Parser::new();
19771 parser
19772 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19773 .expect("C++ grammar");
19774 parser.parse(source, None).expect("fixture tree")
19775 };
19776 fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
19777 let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
19778 root.descendant_for_byte_range(start, start + token.len())
19779 .expect("token node")
19780 }
19781 let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
19782 let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
19783
19784 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";
19788 let tree = parse(source);
19789 let root = tree.root_node();
19790 let x = at(root, source, "int x", "x");
19791 let conditional = std::iter::successors(Some(x), |node| node.parent())
19792 .find(|node| node.kind() == "preproc_ifdef")
19793 .expect("ifdef");
19794 assert!(conditional.child_by_field_name("alternative").is_none());
19795 let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
19796 assert_eq!(directive.kind(), "#else");
19797 assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
19798 let defined = PreprocessorGuard::Defined("A".to_string());
19799 assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
19800 let class_name = at(root, source, "class T", "T");
19801 assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
19802 let define = at(root, source, "#define MODE", "MODE")
19803 .parent()
19804 .filter(|node| node.kind() == "preproc_def")
19805 .expect("macro event");
19806 let mut cache = PreprocessorContextCache::default();
19807 assert!(matches!(
19808 preprocessor_branch_of_descendant(conditional, define, &mut cache),
19809 PreprocessorBranch::DisplacedElse
19810 ));
19811 assert_eq!(
19812 preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
19813 Some(conditional),
19814 "a displaced #else completes the family"
19815 );
19816 assert_eq!(
19817 callable_guard_completion_byte(class_name, source),
19818 Some(conditional.end_byte())
19819 );
19820
19821 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";
19825 let tree = parse(source);
19826 let root = tree.root_node();
19827 let x = at(root, source, "int x", "x");
19828 let conditional = std::iter::successors(Some(x), |node| node.parent())
19829 .find(|node| node.kind() == "preproc_if")
19830 .expect("if");
19831 assert!(conditional.child_by_field_name("alternative").is_none());
19832 assert_eq!(
19833 cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
19834 Some("#elif")
19835 );
19836 let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
19837 assert_eq!(guards(x, source), Some(set(&[truthy])));
19838 let mut cache = PreprocessorContextCache::default();
19839 for needle in ["class T", "struct T"] {
19840 let name = at(root, source, needle, "T");
19841 assert_eq!(
19842 guards(name, source),
19843 None,
19844 "{needle} after a displaced #elif"
19845 );
19846 assert!(matches!(
19847 preprocessor_branch_of_descendant(conditional, name, &mut cache),
19848 PreprocessorBranch::Unreadable
19849 ));
19850 assert_eq!(
19851 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
19852 None,
19853 "{needle}: an unreadable #elif branch does not complete the family"
19854 );
19855 assert_eq!(callable_guard_completion_byte(name, source), None);
19856 }
19857
19858 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";
19861 let tree = parse(source);
19862 let root = tree.root_node();
19863 let start = source.rfind("class T").expect("else class") + "class ".len();
19864 let name = root
19865 .descendant_for_byte_range(start, start + 1)
19866 .expect("else class name");
19867 let elif = std::iter::successors(Some(name), |node| node.parent())
19868 .find(|node| node.kind() == "preproc_elif")
19869 .expect("structured elif");
19870 assert_eq!(
19871 cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
19872 Some("#else")
19873 );
19874 assert_eq!(
19875 guards(name, source),
19876 Some(set(&[
19877 PreprocessorGuard::Defined("A".to_string()).negated(),
19878 PreprocessorGuard::Defined("B".to_string()).negated(),
19879 ]))
19880 );
19881 let mut cache = PreprocessorContextCache::default();
19882 assert!(
19883 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
19884 );
19885 }
19886
19887 #[test]
19888 #[hardwon(
19889 "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
19890 )]
19891 fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
19892 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";
19893 let mut parser = Parser::new();
19894 parser
19895 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19896 .expect("C++ grammar");
19897 let tree = parser.parse(source, None).expect("fixture tree");
19898 let root = tree.root_node();
19899 let type_before = |declared: &str| {
19900 let end = source.find(declared).expect("declared name");
19901 let start = source[..end].rfind('T').expect("type token");
19902 root.descendant_for_byte_range(start, start + 1)
19903 .expect("type node")
19904 };
19905 for declared in ["make(", "value;", "p)", "body("] {
19906 let node = type_before(declared);
19907 assert_eq!(node.kind(), "type_identifier", "{declared}");
19908 assert!(
19909 macro_modifier_decorated_type_node(node),
19910 "{declared}: {}",
19911 node.parent().expect("declaration").to_sexp()
19912 );
19913 }
19914 for declared in ["plain(", "qualified("] {
19915 let node = type_before(declared);
19916 assert!(
19917 !macro_modifier_decorated_type_node(node),
19918 "{declared} is outside the macro-decorated head scope: {}",
19919 node.parent().expect("parent").to_sexp()
19920 );
19921 }
19922 }
19923
19924 #[test]
19925 fn expression_defined_and_ifndef_guards_are_incompatible() {
19926 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19927 let mut parser = Parser::new();
19928 parser
19929 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19930 .expect("C++ grammar");
19931 let tree = parser.parse(source, None).expect("fixture tree");
19932 let root = tree.root_node();
19933 let selected_start = source.find("selected").expect("selected declaration");
19934 let rejected_start = source.find("rejected").expect("rejected declaration");
19935 let selected = root
19936 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19937 .expect("selected node");
19938 let rejected = root
19939 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19940 .expect("rejected node");
19941 let selected_guards =
19942 preprocessor_guard_environment(selected, source).expect("selected guards");
19943 let rejected_guards =
19944 preprocessor_guard_environment(rejected, source).expect("rejected guards");
19945
19946 assert!(
19947 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19948 "opposite spellings of one macro guard must contradict"
19949 );
19950 }
19951
19952 #[test]
19953 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19954 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19955 let source = r#"#ifdef _WIN32
19956#if defined(__cplusplus)
19957extern "C"
19958#endif
19959int platform_api(void);
19960#endif
19961
19962#ifdef _WIN32
19963static int entropy_target(void) { return 0; }
19964#else
19965#ifdef HAVE_COMMON_RANDOM
19966static int other_target(void) { return 0; }
19967#elif defined(HAVE_GETENTROPY)
19968static int entropy_target(void) { return 1; }
19969static int use_entropy(void) { return entropy_target(); }
19970#endif
19971#endif
19972"#;
19973 let mut parser = Parser::new();
19974 parser
19975 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19976 .expect("C++ grammar");
19977 let tree = parser.parse(source, None).expect("fixture tree");
19978 let start = source.rfind("entropy_target()").expect("reference");
19979 let node = tree
19980 .root_node()
19981 .descendant_for_byte_range(start, start + "entropy_target".len())
19982 .expect("reference node");
19983 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19984 assert!(
19985 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19986 "{guards:#?}"
19987 );
19988 assert!(
19989 guards.contains(&PreprocessorGuard::Undefined(
19990 "HAVE_COMMON_RANDOM".to_string()
19991 )),
19992 "{guards:#?}"
19993 );
19994 assert!(
19995 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19996 "{guards:#?}"
19997 );
19998 assert!(
19999 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
20000 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
20001 );
20002 }
20003
20004 #[test]
20005 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
20006 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
20007 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";
20008 let mut parser = Parser::new();
20009 parser
20010 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20011 .expect("C++ grammar");
20012 let tree = parser.parse(source, None).expect("fixture tree");
20013 let root = tree.root_node();
20014 let node_at = |text: &str, start: usize| {
20015 root.descendant_for_byte_range(start, start + text.len())
20016 .expect("token node")
20017 };
20018
20019 let key_start = source.find("case KEY").expect("case label") + "case ".len();
20020 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
20021 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
20022 assert!(!is_ordinary_macro_reference_node(node_at(
20023 "ENABLE_KEYS",
20024 guard_start,
20025 )));
20026 }
20027
20028 #[test]
20029 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20030 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
20031 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";
20032 let mut parser = Parser::new();
20033 parser
20034 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20035 .expect("C++ grammar");
20036 let tree = parser.parse(source, None).expect("fixture tree");
20037 let root = tree.root_node();
20038 let definition_start = source.find("target(void)").expect("definition");
20039 let reference_start = source.rfind("target()").expect("reference");
20040 let definition = root
20041 .descendant_for_byte_range(definition_start, definition_start + "target".len())
20042 .expect("definition node");
20043 let reference = root
20044 .descendant_for_byte_range(reference_start, reference_start + "target".len())
20045 .expect("reference node");
20046 let required =
20047 preprocessor_guard_environment(definition, source).expect("definition guard");
20048 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
20049 assert!(guard_requirements_hold_at_reference(
20050 &required,
20051 Some(&active)
20052 ));
20053 }
20054
20055 #[test]
20056 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
20057 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
20058 let source = "g_autoptr(FuChunkArray) self = make_array();";
20059 let mut parser = Parser::new();
20060 parser
20061 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20062 .expect("C++ grammar");
20063 let tree = parser.parse(source, None).expect("fixture tree");
20064 let statement = tree.root_node().named_child(0).expect("statement");
20065 let binding =
20066 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
20067 assert_eq!(binding.name, "self");
20068 assert_eq!(binding.type_name, "FuChunkArray");
20069 assert_eq!(binding.pointer_depth, 1);
20070
20071 let near_miss = "holder(FuChunkArray) self = make_array();";
20072 let tree = parser.parse(near_miss, None).expect("near-miss tree");
20073 let statement = tree.root_node().named_child(0).expect("statement");
20074 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
20075 }
20076
20077 #[test]
20078 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
20079 fn boolean_guard_normalization_proves_equivalence_and_implication() {
20080 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
20081 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
20082 let negated_windows_branch =
20083 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
20084 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
20085 assert_eq!(negated_windows_branch, portable);
20086
20087 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
20088 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
20089 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
20090 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
20091 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
20092 assert!(fallback_branch.implies(&fallback_declaration));
20093 assert!(
20094 BooleanGuardExpression::Truthy("FEATURE".to_string())
20095 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
20096 );
20097 assert!(
20098 BooleanGuardExpression::Undefined("FEATURE".to_string())
20099 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
20100 );
20101 assert!(
20102 !BooleanGuardExpression::Defined("FEATURE".to_string())
20103 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
20104 );
20105 assert!(!fallback_declaration.implies(&fallback_branch));
20106 }
20107
20108 #[test]
20109 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
20110 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
20111 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";
20112 let mut parser = Parser::new();
20113 parser
20114 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20115 .expect("C++ grammar");
20116 let tree = parser.parse(source, None).expect("fixture tree");
20117 let root = tree.root_node();
20118 let call = |marker: &str| {
20119 let start = source.find(marker).expect("call marker");
20120 let mut node = root
20121 .descendant_for_byte_range(start, start + "helper".len())
20122 .expect("call name node");
20123 loop {
20124 if node.kind() == "call_expression" {
20125 break node;
20126 }
20127 node = node.parent().expect("call expression ancestor");
20128 }
20129 };
20130 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
20131 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
20132 let keyword_call = call("helper(NULL, template); /* bound */");
20133 let keyword_arguments = keyword_call
20134 .child_by_field_name("arguments")
20135 .expect("keyword argument list");
20136 assert_eq!(
20137 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
20138 1
20139 );
20140
20141 let nested_call = call("helper(NULL, template); /* nested bound */");
20142 let nested_arguments = nested_call
20143 .child_by_field_name("arguments")
20144 .expect("nested keyword argument list");
20145 assert_eq!(
20146 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
20147 1
20148 );
20149
20150 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
20151 let lambda_arguments = lambda_call
20152 .child_by_field_name("arguments")
20153 .expect("lambda keyword argument list");
20154 assert_eq!(
20155 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
20156 0,
20157 "a lambda body must not borrow the enclosing collapsed C parameter"
20158 );
20159
20160 assert_eq!(
20161 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
20162 0
20163 );
20164
20165 let unbound_call = call("helper(NULL, template); /* unbound */");
20166 let unbound_arguments = unbound_call
20167 .child_by_field_name("arguments")
20168 .expect("unbound argument list");
20169 assert_eq!(
20170 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
20171 0
20172 );
20173 }
20174
20175 #[test]
20176 #[brokk_bifrost_test_macros::hardwon(
20177 "#4407: collapsed C keyword parameters need structural position proof"
20178 )]
20179 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
20180 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
20181int type_keyword(template *value) {
20182 return helper(NULL, template); /* type */
20183}
20184int qualifier_keyword(const *template) {
20185 return helper(NULL, template); /* qualifier */
20186}
20187int annotation_keyword(wchar_t *value __attribute__((template))) {
20188 return helper(NULL, template); /* annotation */
20189}
20190int default_keyword(wchar_t *value = template) {
20191 return helper(NULL, template); /* default */
20192}
20193int nested_expression(wchar_t *value, (template)) {
20194 return helper(NULL, template); /* nested */
20195}
20196int malformed_head = (wchar_t *template) {
20197 return helper(NULL, template); /* malformed */
20198};
20199"#;
20200 let tree = parse_cpp(source);
20201 let root = tree.root_node();
20202 let call = |marker: &str| {
20203 let start = source.find(marker).expect("call marker");
20204 let mut node = root
20205 .descendant_for_byte_range(start, start + "helper".len())
20206 .expect("call name node");
20207 loop {
20208 if node.kind() == "call_expression" {
20209 break Some(node);
20210 }
20211 let Some(parent) = node.parent() else {
20212 break None;
20213 };
20214 node = parent;
20215 }
20216 };
20217 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
20218 let markers = [
20219 ("helper(NULL, template); /* type */", true),
20220 ("helper(NULL, template); /* qualifier */", false),
20221 ("helper(NULL, template); /* annotation */", false),
20222 ("helper(NULL, template); /* default */", false),
20223 ("helper(NULL, template); /* nested */", false),
20224 ("helper(NULL, template); /* malformed */", false),
20225 ];
20226 let mut exercised = 0;
20227 for (marker, expect_call) in markers {
20228 let Some(keyword_call) = call(marker) else {
20235 assert!(
20236 !expect_call,
20237 "{marker} lost its call_expression; the near-miss matrix changed"
20238 );
20239 continue;
20240 };
20241 assert!(
20242 expect_call,
20243 "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
20244 );
20245 exercised += 1;
20246 let keyword_arguments = keyword_call
20247 .child_by_field_name("arguments")
20248 .expect("keyword argument list");
20249 assert_eq!(
20250 recovered_c_keyword_argument_count(
20251 &c_file,
20252 keyword_call,
20253 keyword_arguments,
20254 source,
20255 ),
20256 0,
20257 "keyword in {marker} must not prove a parameter binding"
20258 );
20259 }
20260 assert!(
20261 exercised > 0,
20262 "at least one near-miss marker must exercise the count itself"
20263 );
20264 }
20265
20266 #[test]
20267 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20268 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
20269 let source = r#"#define MAKE(type) type *value
20270MAKE(int *);
20271typedef struct Item Item;
20272struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
20273void gen_op_movl(void *s, int first, int second) { }
20274const char *strZ(const char *value) { return value; }
20275int body(void *s) {
20276 MAKE(int *);
20277 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
20278 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
20279 execvp(strZ(value), UNCONSTIFY(char **, args));
20280}
20281 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
20282 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
20283int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
20284 if (d->units) {
20285 switch (target->nodetype) {
20286 case 1:
20287 case 2:
20288 break;
20289 default:
20290 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
20291 }
20292 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
20293 lysdict_remove(ctx, ((struct Item *)target)->units);
20294 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
20295 }
20296 return 0;
20297 }
20298STATIC EFI_STATUS Encode () { return 0; }
20299"#;
20300 let tree = parse_cpp(source);
20301 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
20302 let top_macro = tree
20303 .root_node()
20304 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
20305 .expect("top macro node");
20306 let body_macro_start = source
20307 .match_indices("MAKE(int *);")
20308 .nth(1)
20309 .expect("body macro")
20310 .0;
20311 let body_macro = tree
20312 .root_node()
20313 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
20314 .expect("body macro node");
20315 let function_call_start = source
20316 .find("gen_op_movl(s, offsetof(CPUX86State")
20317 .expect("function call");
20318 let function_call = tree
20319 .root_node()
20320 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
20321 .expect("function call node");
20322 let strz_start = source.find("strZ(value)").expect("nested function call");
20323 let strz = tree
20324 .root_node()
20325 .named_descendant_for_byte_range(strz_start, strz_start + 4)
20326 .expect("nested function call node");
20327 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
20328 let recovered_call = tree
20329 .root_node()
20330 .named_descendant_for_byte_range(
20331 recovered_call_start,
20332 recovered_call_start + "lysdict_remove".len(),
20333 )
20334 .expect("recovered call node");
20335 let binder_start = source.find("Encode").expect("binder");
20336 let binder = tree
20337 .root_node()
20338 .named_descendant_for_byte_range(binder_start, binder_start + 6)
20339 .expect("binder node");
20340
20341 assert!(recovered_c_function_declarator_invocation(top_macro));
20342 assert!(recovered_c_function_declarator_invocation(body_macro));
20343 assert!(recovered_c_function_declarator_invocation(function_call));
20344 assert!(recovered_c_function_declarator_invocation(strz));
20345 assert!(recovered_c_function_declarator_invocation(recovered_call));
20346 assert!(!recovered_c_function_declarator_invocation(binder));
20347 }
20348
20349 #[test]
20350 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
20351 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
20352 let source = r#"typedef int krb5_context;
20353int helper(int first, int second) { return first + second; }
20354static krb5_context ctx;
20355int main(int argc, char **argv) {
20356 int ccinitial;
20357 const char *collection_name, *typename;
20358 typename = helper(ctx, ccinitial);
20359 return 0;
20360}
20361"#;
20362 let tree = parse_cpp(source);
20363 let ctx = tree
20364 .root_node()
20365 .descendant_for_byte_range(
20366 source.find("ctx, ccinitial").expect("ctx argument"),
20367 source.find("ctx, ccinitial").expect("ctx argument") + 3,
20368 )
20369 .expect("ctx node");
20370 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
20371 let ccinitial = tree
20372 .root_node()
20373 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
20374 .expect("sibling node");
20375 let typename = named_node_at(&tree, source, "typename = helper");
20376 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
20377
20378 assert_eq!(ctx.kind(), "type_identifier");
20382 assert!(recovered_c_parenthesized_declarator_reference(ctx));
20383 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
20384 assert!(!recovered_c_parenthesized_declarator_reference(typename));
20385 assert!(!recovered_c_parenthesized_declarator_reference(helper));
20386 }
20387
20388 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
20389 let mut parser = Parser::new();
20390 parser
20391 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20392 .expect("C++ grammar");
20393 let tree = parser.parse(source, None).expect("C++ fixture tree");
20394 let mut stack = vec![tree.root_node()];
20395 while let Some(node) = stack.pop() {
20396 if node.kind() == "enum_specifier" {
20397 return flattened_macro_namespace_components(node, source);
20398 }
20399 let mut cursor = node.walk();
20400 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
20401 stack.extend(children.into_iter().rev());
20402 }
20403 None
20404 }
20405
20406 #[test]
20407 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
20408 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
20409 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
20410namespace detail
20411{
20412enum class value_t { null };
20413}
20414NLOHMANN_JSON_NAMESPACE_END
20415NLOHMANN_JSON_NAMESPACE_BEGIN
20416namespace next
20417{
20418struct next_type {};
20419}
20420NLOHMANN_JSON_NAMESPACE_END
20421"#;
20422 assert_eq!(
20423 first_enum_flattened_namespace(complete),
20424 Some(vec!["detail".to_string()])
20425 );
20426
20427 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
20428 assert_eq!(
20429 first_enum_flattened_namespace(&stale_end),
20430 Some(vec!["detail".to_string()]),
20431 "a stale end marker before the begin marker must not replace the intended namespace"
20432 );
20433
20434 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
20435namespace detail
20436{
20437enum class value_t { null };
20438}
20439struct next_type {};
20440"#;
20441 assert_eq!(first_enum_flattened_namespace(incomplete), None);
20442 }
20443}
20444
20445#[cfg(test)]
20461mod lookup_order_properties {
20462 use super::*;
20463 use proptest::prelude::*;
20464
20465 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
20469 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
20470 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
20474 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
20475 const KINDS: [CodeUnitType; 6] = [
20476 CodeUnitType::Class,
20477 CodeUnitType::Function,
20478 CodeUnitType::Field,
20479 CodeUnitType::Module,
20480 CodeUnitType::Macro,
20481 CodeUnitType::FileScope,
20482 ];
20483
20484 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
20487 enum ProbedOrder {
20488 Before,
20489 Tied,
20490 After,
20491 Contradictory,
20494 }
20495
20496 impl ProbedOrder {
20497 fn mirror(self) -> Self {
20498 match self {
20499 ProbedOrder::Before => ProbedOrder::After,
20500 ProbedOrder::After => ProbedOrder::Before,
20501 other => other,
20502 }
20503 }
20504
20505 fn signum(self) -> i8 {
20507 match self {
20508 ProbedOrder::Before => -1,
20509 ProbedOrder::Tied => 0,
20510 ProbedOrder::After => 1,
20511 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
20512 }
20513 }
20514 }
20515
20516 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
20524 if left == right {
20525 return ProbedOrder::Tied;
20528 }
20529 let mut forward = vec![left.clone(), right.clone()];
20530 sort_lookup_units(&mut forward);
20531 let mut backward = vec![right.clone(), left.clone()];
20532 sort_lookup_units(&mut backward);
20533 let left_first = backward[0] == *left;
20534 let right_first = forward[0] == *right;
20535 match (left_first, right_first) {
20536 (true, true) => ProbedOrder::Contradictory,
20537 (true, false) => ProbedOrder::Before,
20538 (false, true) => ProbedOrder::After,
20539 (false, false) => ProbedOrder::Tied,
20540 }
20541 }
20542
20543 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
20544 (
20545 0..ROOT_NAMES.len(),
20546 0..REL_PATHS.len(),
20547 0..KINDS.len(),
20548 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
20549 0..3usize,
20550 0..SIGNATURES.len(),
20551 any::<bool>(),
20552 )
20553 .prop_map(
20554 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
20555 let source = ProjectFile::new(
20556 std::env::temp_dir().join(ROOT_NAMES[root]),
20557 REL_PATHS[rel_path],
20558 );
20559 let interner = segment_interner();
20560 let mut fq = FqName::new();
20561 for (atom, segment_kind) in &segments {
20562 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
20563 }
20564 let package_segment_count = package_prefix % fq.len();
20566 CodeUnit::from_fq(
20567 source,
20568 KINDS[kind],
20569 fq,
20570 package_segment_count,
20571 SIGNATURES[signature].map(str::to_string),
20572 synthetic,
20573 )
20574 },
20575 )
20576 }
20577
20578 proptest! {
20579 #![proptest_config(ProptestConfig::with_cases(256))]
20580
20581 #[test]
20584 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20585 fn lookup_order_is_reflexive_and_dual(
20586 left in code_unit_strategy(),
20587 right in code_unit_strategy(),
20588 ) {
20589 prop_assert_eq!(
20590 probe_order(&left, &left),
20591 ProbedOrder::Tied,
20592 "a unit must tie with itself: {:?}",
20593 left
20594 );
20595 let forward = probe_order(&left, &right);
20596 prop_assert_ne!(
20597 forward,
20598 ProbedOrder::Contradictory,
20599 "comparator put each of these strictly first: left={:?} right={:?}",
20600 left,
20601 right
20602 );
20603 prop_assert_eq!(
20604 probe_order(&right, &left),
20605 forward.mirror(),
20606 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
20607 left,
20608 right
20609 );
20610 }
20611
20612 #[test]
20614 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20615 fn lookup_order_is_transitive(
20616 a in code_unit_strategy(),
20617 b in code_unit_strategy(),
20618 c in code_unit_strategy(),
20619 ) {
20620 let ab = probe_order(&a, &b);
20621 let bc = probe_order(&b, &c);
20622 let ac = probe_order(&a, &c);
20623 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
20624 prop_assert_ne!(
20625 probed,
20626 ProbedOrder::Contradictory,
20627 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
20628 pair,
20629 a,
20630 b,
20631 c
20632 );
20633 }
20634 if ab.signum() <= 0 && bc.signum() <= 0 {
20635 prop_assert!(
20636 ac.signum() <= 0,
20637 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
20638 a={:?} b={:?} c={:?}",
20639 ab,
20640 bc,
20641 ac,
20642 a,
20643 b,
20644 c
20645 );
20646 }
20647 }
20648
20649 #[test]
20652 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20653 fn lookup_order_separates_distinct_identities(
20654 left in code_unit_strategy(),
20655 right in code_unit_strategy(),
20656 ) {
20657 if probe_order(&left, &right) == ProbedOrder::Tied {
20658 prop_assert_eq!(
20659 &left,
20660 &right,
20661 "distinct identities tied, so their order is whatever order they \
20662 arrived in: left_segments={:?} right_segments={:?}",
20663 left.fq_segments_debug(),
20664 right.fq_segments_debug()
20665 );
20666 }
20667 }
20668
20669 #[test]
20672 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20673 fn lookup_sort_is_permutation_invariant(
20674 units in prop::collection::vec(code_unit_strategy(), 1..=8),
20675 ) {
20676 let mut sorted = units.clone();
20677 sort_lookup_units(&mut sorted);
20678 for rotation in 0..units.len() {
20679 for reversed in [false, true] {
20680 let mut permuted = units.clone();
20681 permuted.rotate_left(rotation);
20682 if reversed {
20683 permuted.reverse();
20684 }
20685 sort_lookup_units(&mut permuted);
20686 prop_assert_eq!(
20687 &permuted,
20688 &sorted,
20689 "sorting a permutation gave a different list \
20690 (rotation={}, reversed={}): input={:?}",
20691 rotation,
20692 reversed,
20693 units
20694 );
20695 }
20696 }
20697 }
20698 }
20699}