1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::{CompiledLanguage, CppCompileContext};
3use crate::declarations::cpp_displaced_preprocessor_branch;
4#[cfg(test)]
5use crate::declarations::cpp_displaced_preprocessor_terminator;
6use crate::declarations::{
7 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
8 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
9 enclosing_cpp_namespace_nodes, macro_modifier_identifier, node_text, normalize_cpp_whitespace,
10 recovered_class_body_at, recovered_function_like_field_declarator,
11 recovered_pyobject_head_field,
12};
13use crate::graph::CppGraphSource;
14use crate::graph::extractor::ScanCtx;
15use crate::graph::syntax::{
16 SentinelInput, function_macro_replacement_span, object_macro_replacement_type_references,
17 parse_sentinel, qualified_name_parts,
18};
19use crate::graph_support::CppSource;
20use crate::imports::{
21 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
22};
23use crate::source_context::cpp_guard_set_to_runtime;
24use crate::structural::is_cpp_preprocessor_conditional;
25use crate::template_heads::{
26 CppTemplateBinding, CppTemplateContext, CppTemplateReconciliation,
27 cpp_declaration_template_context,
28};
29pub use brokk_bifrost_core::analyzer::cpp_facts::{
30 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
31};
32use brokk_bifrost_core::analyzer::cpp_facts::{
33 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
34 CppStructuredAliasTarget as StructuredAliasTarget,
35};
36use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
37use brokk_bifrost_core::analyzer::model::{
38 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
39 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
40 SignatureMetadata, StructuredTypeName,
41};
42use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
43use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
44#[cfg(test)]
45use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
46use brokk_bifrost_core::analyzer::query_token::QueryToken;
47use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
48use brokk_bifrost_core::analyzer::source_facts::SourceDeclarationId;
49use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
50use brokk_bifrost_core::analyzer::tree_walk::{
51 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
52 named_children_iter, node_for_exact_range, push_named_children_reversed,
53 single_named_child_matching,
54};
55use brokk_bifrost_core::analyzer::usages::common::same_node;
56use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
57use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
58use brokk_bifrost_core::cancellation::CancellationToken;
59use brokk_bifrost_core::hash::{HashMap, HashSet};
60#[cfg(test)]
61use brokk_bifrost_core::text_utils::compute_line_starts;
62use std::borrow::Cow;
63#[cfg(any(test, feature = "test-support"))]
64use std::cell::Cell;
65use std::cell::OnceCell;
66use std::cmp::Ordering as CmpOrdering;
67use std::collections::BTreeSet;
68use std::hash::Hash;
69use std::sync::atomic::{AtomicUsize, Ordering};
70use std::sync::{Arc, Mutex, OnceLock, RwLock};
71use std::time::{Duration, Instant};
72use tree_sitter::{Node, Parser, Tree};
73
74const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
75const CALLABLE_METADATA_CACHE_CAP: usize = 512;
76const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
77
78#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
79struct PreprocessorContextNodeKey {
80 id: usize,
81 start_byte: usize,
82 end_byte: usize,
83}
84
85#[derive(Default)]
94pub(crate) struct PreprocessorContextCache {
95 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
96 displaced_branches: HashMap<PreprocessorContextNodeKey, Option<DisplacedBranchDirective>>,
97}
98
99#[derive(Clone, Copy)]
102struct DisplacedBranchDirective {
103 end_byte: usize,
104 is_else: bool,
105}
106
107impl PreprocessorContextCache {
108 fn displaced_branch(&mut self, conditional: Node<'_>) -> Option<DisplacedBranchDirective> {
109 let key = PreprocessorContextNodeKey {
110 id: conditional.id(),
111 start_byte: conditional.start_byte(),
112 end_byte: conditional.end_byte(),
113 };
114 if let Some(directive) = self.displaced_branches.get(&key) {
115 return *directive;
116 }
117 let directive = cpp_displaced_preprocessor_branch(conditional).map(|directive| {
118 DisplacedBranchDirective {
119 end_byte: directive.end_byte(),
120 is_else: directive.kind() == "#else",
121 }
122 });
123 if self.displaced_branches.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
124 self.displaced_branches.insert(key, directive);
125 }
126 directive
127 }
128
129 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
130 let key = PreprocessorContextNodeKey {
131 id: conditional.id(),
132 start_byte: conditional.start_byte(),
133 end_byte: conditional.end_byte(),
134 };
135 if let Some(boundary) = self.displaced_boundaries.get(&key) {
136 return *boundary;
137 }
138 let boundary =
139 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
140 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
141 self.displaced_boundaries.insert(key, boundary);
142 }
143 boundary
144 }
145}
146
147#[cfg(any(test, feature = "test-support"))]
148thread_local! {
149 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
150 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
151}
152
153#[derive(Clone, Copy, PartialEq, Eq)]
154pub enum TargetKind {
155 Type,
156 Constructor,
157 FreeFunction,
158 Method,
159 GlobalField,
160 MemberField,
161 Macro,
162}
163
164pub enum LexicalTypeResolution {
165 Resolved {
166 unit: CodeUnit,
167 components: Vec<String>,
168 candidates: Vec<CodeUnit>,
169 },
170 Ambiguous,
171 Missing,
172}
173
174pub enum GuardedAliasSelection {
177 Proven {
180 unit: CodeUnit,
181 candidates: Vec<CodeUnit>,
182 },
183 Alternatives(Vec<CodeUnit>),
185}
186
187#[derive(Clone, Copy, PartialEq, Eq)]
188enum AliasArmEvidence {
189 Implied,
190 Compatible,
191 Contradicted,
192}
193
194#[derive(Clone, Copy)]
195enum TypeCandidateResolution<'a> {
196 Canonical,
197 PreserveAlias,
198 PreserveTarget(&'a CodeUnit),
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq)]
210enum TypeCandidateFailure {
211 Ambiguous,
212 Unresolvable,
213}
214
215impl TypeCandidateFailure {
216 fn lexical_resolution(self) -> LexicalTypeResolution {
217 match self {
218 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
219 Self::Unresolvable => LexicalTypeResolution::Missing,
220 }
221 }
222}
223
224pub enum LexicalCallableValueResolution {
225 Type(CodeUnit),
226 FreeFunction(CodeUnit),
227 Ambiguous,
228 Missing,
229}
230
231pub enum UsingEnumMemberResolution {
232 Resolved { owner: CodeUnit, member: CodeUnit },
233 Ambiguous,
234 Missing,
235}
236
237pub enum NamespaceValueResolution {
238 Resolved,
239 Ambiguous,
240 Missing,
241}
242
243#[derive(Clone, Debug, PartialEq, Eq)]
244pub enum OrdinaryMacroReferenceResolution {
245 Resolved(CodeUnit),
246 Ambiguous,
247 Missing,
248}
249
250#[derive(Clone, Debug, PartialEq, Eq)]
251pub enum RecoveredCReferenceRanges {
252 Complete(Vec<Range>),
253 LimitExceeded,
254}
255
256pub fn resolve_namespace_value(
257 analyzer: &CppGraphSource<'_>,
258 visibility: &VisibilityIndex<'_>,
259 file: &ProjectFile,
260 namespace: &str,
261 name: &str,
262 before_byte: usize,
263) -> NamespaceValueResolution {
264 let mut matches = Vec::new();
265 for candidate in visibility.visible_identifier_candidates(file, name) {
266 if type_owner_of(analyzer, candidate).is_some()
267 || candidate.package_name() != namespace
268 || (candidate.source() == file
269 && !analyzer
270 .ranges(candidate)
271 .iter()
272 .any(|range| range.start_byte < before_byte))
273 || matches
274 .iter()
275 .any(|existing| same_visible_symbol(existing, candidate))
276 {
277 continue;
278 }
279 matches.push(candidate.clone());
280 if matches.len() > 1 {
281 return NamespaceValueResolution::Ambiguous;
282 }
283 }
284 matches
285 .pop()
286 .map(|_| NamespaceValueResolution::Resolved)
287 .unwrap_or(NamespaceValueResolution::Missing)
288}
289
290pub(crate) struct ScopedUsingEnumOwners {
291 scopes: Vec<Vec<CodeUnit>>,
292}
293
294pub(crate) struct SemanticUsingEnumOwners {
299 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
300 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
301}
302
303pub(crate) enum SemanticUsingEnumMemberResolution {
304 Class(UsingEnumMemberResolution),
305 Namespace(UsingEnumMemberResolution),
306 Missing,
307}
308
309impl SemanticUsingEnumOwners {
310 pub(crate) fn new() -> Self {
311 Self {
312 class_imports: HashMap::default(),
313 namespace_imports: HashMap::default(),
314 }
315 }
316
317 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
318 let imports = self.class_imports.entry(class).or_default();
319 if !imports
320 .iter()
321 .any(|existing| same_visible_symbol(existing, &enum_owner))
322 {
323 imports.push(enum_owner);
324 }
325 }
326
327 pub fn import_namespace(
328 &mut self,
329 namespace: Vec<String>,
330 declaration_byte: usize,
331 enum_owner: CodeUnit,
332 ) {
333 let imports = self.namespace_imports.entry(namespace).or_default();
334 if !imports
335 .iter()
336 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
337 {
338 imports.push((declaration_byte, enum_owner));
339 }
340 }
341
342 pub fn resolve_member(
343 &self,
344 visibility: &VisibilityIndex<'_>,
345 file: &ProjectFile,
346 class: Option<&CodeUnit>,
347 namespace: &[String],
348 before_byte: usize,
349 name: &str,
350 ) -> SemanticUsingEnumMemberResolution {
351 if let Some(class) = class
352 && let Some((_, imports)) = self
353 .class_imports
354 .iter()
355 .find(|(owner, _)| same_visible_symbol(owner, class))
356 {
357 let resolution =
358 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
359 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
360 return SemanticUsingEnumMemberResolution::Class(resolution);
361 }
362 }
363 for prefix_len in (0..=namespace.len()).rev() {
364 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
365 continue;
366 };
367 let owners = imports
368 .iter()
369 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
370 .map(|(_, owner)| owner);
371 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
372 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
373 return SemanticUsingEnumMemberResolution::Namespace(resolution);
374 }
375 }
376 SemanticUsingEnumMemberResolution::Missing
377 }
378}
379
380fn resolve_using_enum_member_for_owners<'a>(
381 visibility: &VisibilityIndex<'_>,
382 file: &ProjectFile,
383 owners: impl IntoIterator<Item = &'a CodeUnit>,
384 name: &str,
385) -> UsingEnumMemberResolution {
386 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
387 for owner in owners {
388 for member in visibility.visible_members_for_owner_name(file, owner, name) {
389 if !member.is_field()
390 || matches.iter().any(|(existing_owner, existing_member)| {
391 same_visible_symbol(existing_owner, owner)
392 && same_visible_symbol(existing_member, member)
393 })
394 {
395 continue;
396 }
397 matches.push((owner.clone(), member.clone()));
398 }
399 }
400 match matches.len() {
401 0 => UsingEnumMemberResolution::Missing,
402 1 => {
403 let (owner, member) = matches.pop().expect("one using-enum match");
404 UsingEnumMemberResolution::Resolved { owner, member }
405 }
406 _ => UsingEnumMemberResolution::Ambiguous,
407 }
408}
409
410impl ScopedUsingEnumOwners {
411 pub(crate) fn new() -> Self {
412 Self {
413 scopes: vec![Vec::new()],
414 }
415 }
416
417 pub fn enter_scope(&mut self) {
418 self.scopes.push(Vec::new());
419 }
420
421 pub fn exit_scope(&mut self) {
422 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
423 }
424
425 pub fn import(&mut self, owner: CodeUnit) {
426 let scope = self
427 .scopes
428 .last_mut()
429 .expect("using-enum scope stack is never empty");
430 if !scope
431 .iter()
432 .any(|existing| same_visible_symbol(existing, &owner))
433 {
434 scope.push(owner);
435 }
436 }
437
438 pub fn resolve_member(
439 &self,
440 visibility: &VisibilityIndex<'_>,
441 file: &ProjectFile,
442 name: &str,
443 ) -> UsingEnumMemberResolution {
444 for scope in self.scopes.iter().rev() {
445 let resolution =
446 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
447 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
448 return resolution;
449 }
450 }
451 UsingEnumMemberResolution::Missing
452 }
453}
454
455#[derive(Clone)]
456pub struct TargetSpec {
457 pub target: CodeUnit,
458 pub kind: TargetKind,
459 pub owner: Option<CodeUnit>,
460 pub member_name: String,
461 pub callable_arity: Option<CallableArity>,
462 pub activated_callable_arities: Vec<ActivatedCallableArity>,
463 pub param_types: Option<Vec<String>>,
464 pub enum_owner_kind: EnumOwnerKind,
465 pub owner_is_forward_declaration: bool,
466 pub callable_has_definition_body: bool,
467}
468
469#[derive(Clone, Copy)]
470pub struct ActivatedCallableArity {
471 pub activation_byte: usize,
472 pub arity: CallableArity,
473}
474
475#[derive(Debug, PartialEq, Eq, Hash)]
476pub struct TypeScanKey {
477 target: LogicalSymbolKey,
478 member_name: String,
479}
480
481#[derive(Clone, Debug, PartialEq, Eq, Hash)]
482struct LogicalSymbolKey {
483 kind: CodeUnitType,
484 fq_name: String,
485 signature: Option<String>,
486}
487
488struct ResolvedTypeOwner {
489 unit: CodeUnit,
490 is_forward_declaration: bool,
491}
492
493impl TargetSpec {
494 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
495 (self.kind == TargetKind::Type).then(|| TypeScanKey {
496 target: logical_symbol_key(&self.target),
497 member_name: self.member_name.clone(),
498 })
499 }
500
501 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
502 if target.is_class() {
503 return Some(Self::new(
504 target.clone(),
505 TargetKind::Type,
506 Some(target.clone()),
507 target.identifier().to_string(),
508 None,
509 None,
510 ));
511 }
512
513 if target.is_field() {
514 let owner = type_owner_of(analyzer, target);
520 let kind = if owner.is_some() {
521 TargetKind::MemberField
522 } else {
523 TargetKind::GlobalField
524 };
525 let enum_owner_kind = owner
526 .as_ref()
527 .map(|owner| classify_enum_owner(analyzer, owner))
528 .unwrap_or(EnumOwnerKind::NonEnum);
529 let mut spec = Self::new(
530 target.clone(),
531 kind,
532 owner,
533 target.identifier().to_string(),
534 None,
535 None,
536 );
537 spec.enum_owner_kind = enum_owner_kind;
538 return Some(spec);
539 }
540
541 if target.is_function() {
542 let owner_resolution = target_type_owner_resolution(analyzer, target);
545 let owner_is_forward_declaration = owner_resolution
546 .as_ref()
547 .is_some_and(|owner| owner.is_forward_declaration);
548 let owner = owner_resolution.map(|owner| owner.unit);
549 let kind = if owner.as_ref().is_some_and(|owner| {
550 target.identifier() == owner.identifier()
551 || analyzer
552 .cpp
553 .and_then(|cpp| cpp.template_metadata(owner))
554 .is_some_and(|metadata| metadata.primary_name == target.identifier())
555 }) {
556 TargetKind::Constructor
557 } else if owner.is_some() {
558 TargetKind::Method
559 } else {
560 TargetKind::FreeFunction
561 };
562 let mut spec = Self::new(
563 target.clone(),
564 kind,
565 owner,
566 target.identifier().to_string(),
567 cpp_callable_arity(analyzer, target),
568 cpp_callable_parameter_types(analyzer, target),
569 );
570 spec.owner_is_forward_declaration = owner_is_forward_declaration;
571 spec.callable_has_definition_body =
572 callable_target_has_definition_body(analyzer, target);
573 return Some(spec);
574 }
575
576 if target.is_macro() {
577 return Some(Self::new(
578 target.clone(),
579 TargetKind::Macro,
580 None,
581 target.identifier().to_string(),
582 None,
583 None,
584 ));
585 }
586
587 None
588 }
589
590 pub fn with_visible_callable_arities<'a>(
591 &'a self,
592 analyzer: &CppGraphSource<'_>,
593 cpp: &dyn CppSource,
594 visibility: &VisibilityIndex<'_>,
595 file: &ProjectFile,
596 prepared: &PreparedSyntaxTree,
597 ) -> Cow<'a, Self> {
598 let macro_parameter_arity =
599 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
600 let activated_callable_arities =
601 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
602 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
603 return Cow::Borrowed(self);
604 }
605 let mut effective = self.clone();
606 if let Some(macro_parameter_arity) = macro_parameter_arity {
607 effective.callable_arity = Some(macro_parameter_arity);
608 }
609 effective.activated_callable_arities = activated_callable_arities;
610 Cow::Owned(effective)
611 }
612
613 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
614 let base = self.callable_arity?;
615 Some(
616 self.activated_callable_arities
617 .iter()
618 .filter(|candidate| candidate.activation_byte <= byte)
619 .fold(base, |arity, candidate| {
620 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
621 }),
622 )
623 }
624
625 pub fn new(
626 target: CodeUnit,
627 kind: TargetKind,
628 owner: Option<CodeUnit>,
629 member_name: String,
630 callable_arity: Option<CallableArity>,
631 param_types: Option<Vec<String>>,
632 ) -> Self {
633 Self {
634 target,
635 kind,
636 owner,
637 member_name,
638 callable_arity,
639 activated_callable_arities: Vec::new(),
640 param_types,
641 enum_owner_kind: EnumOwnerKind::NonEnum,
642 owner_is_forward_declaration: false,
643 callable_has_definition_body: false,
644 }
645 }
646}
647
648fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
649 analyzer
650 .signature_metadata(target)
651 .iter()
652 .any(|metadata| !metadata.is_declaration_only())
653}
654
655fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
656 LogicalSymbolKey {
657 kind: unit.kind(),
658 fq_name: unit.fq_name(),
659 signature: unit.signature().map(str::to_string),
660 }
661}
662
663fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
664 let Some(facts) = analyzer.declaration_source_properties(owner) else {
665 return EnumOwnerKind::NonEnum;
666 };
667 let Some(first) = facts.first() else {
668 return EnumOwnerKind::NonEnum;
669 };
670 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
671 first.enum_kind
672 } else {
673 EnumOwnerKind::NonEnum
674 }
675}
676
677#[derive(Clone, PartialEq, Eq, Hash)]
678pub struct CppScanBinding {
679 pub unit: Option<CodeUnit>,
680 pub type_name: Option<String>,
681 pub indirection: i32,
682}
683
684impl CppScanBinding {
685 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
686 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
687 }
688
689 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
690 Self {
691 type_name: Some(type_name),
692 unit,
693 indirection,
694 }
695 }
696
697 pub fn as_arg_type(&self) -> Option<CppArgType> {
698 let name = self
699 .type_name
700 .clone()
701 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
702 Some(CppArgType {
703 name,
704 unit: self.unit.clone(),
705 indirection: self.indirection,
706 pointee_const: false,
707 })
708 }
709}
710
711type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
712pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
713pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
714type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
715type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
716type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
717type MacroLexicalTemplateCache =
718 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
719
720type MacroLocalBindingTemplateCache =
721 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
722type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
723type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
724type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
725
726struct StructuredIncludeFact {
727 start_byte: usize,
728 end_byte: usize,
729 path: String,
730}
731
732#[derive(Clone, Default)]
733pub struct MacroEnvironment {
734 bindings: HashMap<String, MacroBinding>,
735 known_undefined_names: HashSet<String>,
736 build_proven_defines: HashSet<String>,
741 unknown_names: bool,
742 applied_pragma_once_files: HashSet<ProjectFile>,
743 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
744}
745
746pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
755
756struct MacroEnvironmentCheckpoint {
758 frontier: usize,
760 environment: Arc<MacroEnvironment>,
761}
762
763struct MacroEnvironmentCheckpoints {
772 checkpoints: Vec<MacroEnvironmentCheckpoint>,
773}
774
775impl MacroEnvironmentCheckpoints {
776 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
778 let index = self
779 .checkpoints
780 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
781 assert!(
782 index > 0,
783 "a checkpoint vector starts at frontier zero, which precedes every request"
784 );
785 &self.checkpoints[index - 1]
786 }
787}
788
789fn clone_or_default_file_cell<T: Default + Clone>(
790 cells: &Mutex<HashMap<ProjectFile, T>>,
791 file: &ProjectFile,
792 poison_message: &str,
793) -> T {
794 cells
795 .lock()
796 .expect(poison_message)
797 .entry(file.clone())
798 .or_default()
799 .clone()
800}
801
802impl MacroEnvironment {
803 fn binding(&self, name: &str) -> Option<&MacroBinding> {
804 self.bindings.get(name)
805 }
806
807 fn may_bind(&self, name: &str) -> bool {
808 self.bindings.contains_key(name) || self.unknown_names
809 }
810
811 fn insert(&mut self, name: String, binding: MacroBinding) {
812 self.known_undefined_names.remove(&name);
813 self.bindings.insert(name, binding);
814 }
815
816 fn remove(&mut self, name: &str) {
817 self.bindings.remove(name);
818 self.known_undefined_names.insert(name.to_string());
819 }
820
821 fn remove_known_undefined(&mut self, name: &str) {
822 self.known_undefined_names.remove(name);
823 }
824
825 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
826 for binding in self.bindings.values_mut() {
827 *binding = MacroBinding::uncertain_from(binding, source, byte);
828 }
829 self.known_undefined_names.clear();
830 self.build_proven_defines.clear();
835 self.unknown_names = true;
836 }
837
838 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
839 guards.iter().all(|guard| self.guard_may_hold(guard))
840 }
841
842 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
843 self.boolean_guard_may_hold(&guard.as_boolean_expression())
844 }
845
846 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
847 match expression {
848 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
849 BooleanGuardExpression::Undefined(name) => {
850 self.bindings
851 .get(name)
852 .is_none_or(|binding| !binding.is_exact())
853 && (!self.build_proven_defines.contains(name)
854 || self.known_undefined_names.contains(name))
855 }
856 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
857 BooleanGuardExpression::Opaque(_)
858 | BooleanGuardExpression::NegatedOpaque(_)
859 | BooleanGuardExpression::Constant(true) => true,
860 BooleanGuardExpression::Constant(false) => false,
861 BooleanGuardExpression::All(expressions) => expressions
862 .iter()
863 .all(|expression| self.boolean_guard_may_hold(expression)),
864 BooleanGuardExpression::Any(expressions) => expressions
865 .iter()
866 .any(|expression| self.boolean_guard_may_hold(expression)),
867 }
868 }
869}
870
871fn any_visible_source_file_matches(
872 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
873 file: &ProjectFile,
874 mut predicate: impl FnMut(&ProjectFile) -> bool,
875) -> bool {
876 match visible_source_files_by_root.get(file) {
877 Some(files) => files.iter().any(predicate),
878 None => predicate(file),
879 }
880}
881
882#[derive(Clone)]
883pub enum EffectiveUsingTarget {
884 Ordinary {
885 name: String,
886 target_components: Vec<String>,
887 global: bool,
888 },
889 Namespace {
890 namespace_components: Vec<String>,
891 global: bool,
892 },
893}
894
895#[derive(Clone)]
896pub struct OrdinaryTypeImport {
897 pub target: EffectiveUsingTarget,
898 pub source: ProjectFile,
899 pub declaration_byte: usize,
900 pub scope_start: usize,
901 pub scope_end: usize,
902 pub scope_depth: usize,
903 pub block_scope: bool,
904 pub lexical_depth: usize,
905 pub declaration_namespace: Vec<String>,
906 pub namespace_scope: Option<Vec<String>>,
907 pub resolved_target_components: Option<Vec<String>>,
908 pub required_guards: HashSet<PreprocessorGuard>,
909}
910
911#[derive(Clone)]
912pub struct ConditionalIncludeProjection {
913 pub activation_byte: usize,
914 pub required_guards: HashSet<PreprocessorGuard>,
915 pub partial_guards: HashSet<PreprocessorGuard>,
919}
920
921#[derive(Default)]
922pub struct SourceUsingIndex {
923 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
924 pub directives: Vec<OrdinaryTypeImport>,
925}
926
927#[derive(Default)]
928pub struct ProjectUsingIndex {
929 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
930 pub directives: Vec<OrdinaryTypeImport>,
931}
932
933type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
934
935pub struct EffectiveUsingIndex {
936 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
937}
938
939impl EffectiveUsingIndex {
940 fn new(_root: ProjectFile) -> Self {
941 Self {
942 projected_by_name: Mutex::new(HashMap::default()),
943 }
944 }
945
946 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
947 self.projected_by_name
948 .lock()
949 .expect("C++ effective-using projection cache poisoned")
950 .entry(name.to_string())
951 .or_default()
952 .clone()
953 }
954}
955
956pub enum OrdinaryTypeImportResolution {
957 Resolved {
958 target: CodeUnit,
959 target_components: Vec<String>,
960 lexical_depth: usize,
961 is_direct: bool,
962 },
963 Ambiguous {
964 lexical_depth: usize,
965 },
966 Missing,
967}
968
969type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
970type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
971type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
972type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
973type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
974type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
975type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
976type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
977
978struct ExtractedComparable {
983 shapes: Vec<CppComparableSlot>,
984 suffix: String,
985 template: Option<CppTemplateContext>,
988}
989
990const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
994
995#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004enum IncludePathAdmission {
1005 Proven,
1006 Compatible,
1007}
1008
1009impl IncludePathAdmission {
1010 fn admits(
1011 self,
1012 required: &HashSet<PreprocessorGuard>,
1013 partial: &HashSet<PreprocessorGuard>,
1014 reference_guards: Option<&HashSet<PreprocessorGuard>>,
1015 ) -> bool {
1016 match self {
1017 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
1018 Self::Compatible => {
1019 guard_requirements_hold_at_reference(partial, reference_guards)
1020 && guards_compatible_at_reference(required, reference_guards)
1021 }
1022 }
1023 }
1024}
1025
1026pub struct VisibilityIndex<'a> {
1030 cpp: &'a dyn CppSource,
1031 token: QueryToken<'a>,
1032 data: Arc<VisibilityIndexData>,
1033}
1034
1035impl std::ops::Deref for VisibilityIndex<'_> {
1036 type Target = VisibilityIndexData;
1037
1038 fn deref(&self) -> &Self::Target {
1039 &self.data
1040 }
1041}
1042
1043pub struct VisibilityIndexData {
1046 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1047 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1048 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1049 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1050 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1051 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1052 parser_alias_target_matches:
1053 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1054 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1055 project_using_index: OnceLock<ProjectUsingIndex>,
1056 namespace_alias_sites: Mutex<HashMap<ProjectFile, super::namespace_alias::NamespaceAliasCell>>,
1057 callable_reference_specs:
1058 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1059 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1060 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1061 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1062 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1063 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1064 #[cfg(any(test, feature = "test-support"))]
1065 conditional_include_projection_index_build_count: AtomicUsize,
1066 #[cfg(any(test, feature = "test-support"))]
1067 conditional_include_projection_state_count: AtomicUsize,
1068 #[cfg(any(test, feature = "test-support"))]
1069 conditional_include_target_state_count: AtomicUsize,
1070 #[cfg(any(test, feature = "test-support"))]
1071 include_activation_build_count: AtomicUsize,
1072 #[cfg(any(test, feature = "test-support"))]
1073 using_donor_activation_count: AtomicUsize,
1074 #[cfg(any(test, feature = "test-support"))]
1075 using_namespace_lookup_count: AtomicUsize,
1076 #[cfg(any(test, feature = "test-support"))]
1077 using_name_candidate_inspection_count: AtomicUsize,
1078 #[cfg(any(test, feature = "test-support"))]
1079 callable_reference_spec_build_count: AtomicUsize,
1080 #[cfg(any(test, feature = "test-support"))]
1081 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1082 #[cfg(any(test, feature = "test-support"))]
1083 visible_parser_alias_name_set_build_count: AtomicUsize,
1084 parser_alias_fallback_calls: AtomicUsize,
1085 parser_alias_fallback_files: AtomicUsize,
1086 parser_alias_source_parses: AtomicUsize,
1087 parser_alias_fallback_elapsed_micros: AtomicUsize,
1088 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1089 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1090 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1091 class_definition_agreements: Mutex<HashMap<(CodeUnit, CodeUnit), bool>>,
1092 callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1098 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1099 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1100 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1101 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1102 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1103 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1104 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1105 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1106 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1107 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1114 macro_replacements: Mutex<MacroReplacementCache>,
1115 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1116 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1117 macro_lexical_indexes:
1118 Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1119 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1120 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1121 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1122 #[cfg(any(test, feature = "test-support"))]
1123 pub macro_replacement_parse_count: AtomicUsize,
1124 #[cfg(any(test, feature = "test-support"))]
1125 pub macro_event_application_count: AtomicUsize,
1126 #[cfg(any(test, feature = "test-support"))]
1129 pub macro_environment_checkpoint_build_count: AtomicUsize,
1130 #[cfg(any(test, feature = "test-support"))]
1133 pub macro_environment_copy_count: AtomicUsize,
1134 #[cfg(any(test, feature = "test-support"))]
1135 pub macro_environment_request_count: AtomicUsize,
1136 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1137 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1138 #[cfg(any(test, feature = "test-support"))]
1139 qualified_candidate_inspections: AtomicUsize,
1140 #[cfg(any(test, feature = "test-support"))]
1141 target_preserving_type_resolution_count: AtomicUsize,
1142 #[cfg(any(test, feature = "test-support"))]
1143 visibility_identifier_lookup_count: usize,
1144 #[cfg(any(test, feature = "test-support"))]
1145 visibility_identifier_batch_count: usize,
1146}
1147
1148impl Drop for VisibilityIndexData {
1149 fn drop(&mut self) {
1150 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1151 return;
1152 }
1153 #[cfg(any(test, feature = "test-support"))]
1154 eprintln!(
1155 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1156 self.macro_environment_request_count.load(Ordering::Relaxed),
1157 self.macro_environment_copy_count.load(Ordering::Relaxed),
1158 self.macro_environment_checkpoint_build_count
1159 .load(Ordering::Relaxed),
1160 self.macro_event_application_count.load(Ordering::Relaxed),
1161 );
1162 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1163 if calls == 0 {
1164 return;
1165 }
1166 eprintln!(
1167 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1168 calls,
1169 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1170 self.parser_alias_source_parses.load(Ordering::Relaxed),
1171 self.parser_alias_fallback_elapsed_micros
1172 .load(Ordering::Relaxed)
1173 / 1_000,
1174 );
1175 }
1176}
1177
1178#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1179pub enum PreprocessorGuard {
1180 Defined(String),
1181 Undefined(String),
1182 Boolean(BooleanGuardExpression),
1183 Expression(String),
1184 NegatedExpression(String),
1185 Constant(bool),
1186}
1187
1188#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1189pub enum BooleanGuardExpression {
1190 Defined(String),
1191 Undefined(String),
1192 Truthy(String),
1193 Falsy(String),
1194 Opaque(String),
1195 NegatedOpaque(String),
1196 All(Vec<BooleanGuardExpression>),
1197 Any(Vec<BooleanGuardExpression>),
1198 Constant(bool),
1199}
1200
1201impl BooleanGuardExpression {
1202 fn negated(&self) -> Self {
1203 match self {
1204 Self::Defined(name) => Self::Undefined(name.clone()),
1205 Self::Undefined(name) => Self::Defined(name.clone()),
1206 Self::Truthy(name) => Self::Falsy(name.clone()),
1207 Self::Falsy(name) => Self::Truthy(name.clone()),
1208 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1209 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1210 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1211 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1212 Self::Constant(value) => Self::Constant(!value),
1213 }
1214 }
1215
1216 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1217 Self::normalized(expressions, true)
1218 }
1219
1220 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1221 Self::normalized(expressions, false)
1222 }
1223
1224 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1225 let mut normalized = Vec::new();
1226 for expression in expressions {
1227 match expression {
1228 Self::All(nested) if conjunction => normalized.extend(nested),
1229 Self::Any(nested) if !conjunction => normalized.extend(nested),
1230 Self::Constant(value) if value == conjunction => {}
1231 Self::Constant(value) => return Self::Constant(value),
1232 expression => normalized.push(expression),
1233 }
1234 }
1235 normalized.sort_unstable();
1236 normalized.dedup();
1237 match normalized.len() {
1238 0 => Self::Constant(conjunction),
1239 1 => normalized.pop().expect("one Boolean guard expression"),
1240 _ if conjunction => Self::All(normalized),
1241 _ => Self::Any(normalized),
1242 }
1243 }
1244
1245 fn implies(&self, required: &Self) -> bool {
1246 if self == required
1247 || matches!(self, Self::Constant(false))
1248 || matches!(required, Self::Constant(true))
1249 {
1250 return true;
1251 }
1252 if matches!(
1253 (self, required),
1254 (Self::Truthy(active), Self::Defined(required))
1255 | (Self::Undefined(active), Self::Falsy(required))
1256 if active == required
1257 ) {
1258 return true;
1259 }
1260 match self {
1261 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1262 Self::All(active) => match required {
1263 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1264 _ => active.iter().any(|expression| expression.implies(required)),
1265 },
1266 _ => match required {
1267 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1268 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1269 _ => false,
1270 },
1271 }
1272 }
1273
1274 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1275 match self {
1276 Self::Defined(name)
1277 | Self::Undefined(name)
1278 | Self::Truthy(name)
1279 | Self::Falsy(name) => name == macro_name,
1280 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1283 Self::All(expressions) | Self::Any(expressions) => expressions
1284 .iter()
1285 .any(|expression| expression.may_depend_on_macro(macro_name)),
1286 Self::Constant(_) => false,
1287 }
1288 }
1289
1290 pub fn heap_size(&self) -> usize {
1291 match self {
1292 Self::Defined(value)
1293 | Self::Undefined(value)
1294 | Self::Truthy(value)
1295 | Self::Falsy(value)
1296 | Self::Opaque(value)
1297 | Self::NegatedOpaque(value) => value.len(),
1298 Self::All(expressions) | Self::Any(expressions) => {
1299 expressions
1300 .iter()
1301 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1302 size.saturating_add(std::mem::size_of::<Self>())
1303 .saturating_add(expression.heap_size())
1304 })
1305 }
1306 Self::Constant(_) => 0,
1307 }
1308 }
1309}
1310
1311impl PreprocessorGuard {
1312 fn as_boolean_expression(&self) -> BooleanGuardExpression {
1313 match self {
1314 Self::Defined(name) => BooleanGuardExpression::Defined(name.clone()),
1315 Self::Undefined(name) => BooleanGuardExpression::Undefined(name.clone()),
1316 Self::Boolean(expression) => expression.clone(),
1317 Self::Constant(value) => BooleanGuardExpression::Constant(*value),
1318 Self::Expression(expression) => BooleanGuardExpression::Opaque(expression.clone()),
1319 Self::NegatedExpression(expression) => {
1320 BooleanGuardExpression::NegatedOpaque(expression.clone())
1321 }
1322 }
1323 }
1324
1325 fn negated(&self) -> Self {
1326 match self {
1327 Self::Defined(name) => Self::Undefined(name.clone()),
1328 Self::Undefined(name) => Self::Defined(name.clone()),
1329 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1330 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1331 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1332 Self::Constant(value) => Self::Constant(!value),
1333 }
1334 }
1335
1336 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1337 match self {
1338 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1339 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1340 Self::Expression(_) | Self::NegatedExpression(_) => true,
1343 Self::Constant(_) => false,
1344 }
1345 }
1346}
1347
1348#[derive(Clone, PartialEq, Eq)]
1349pub enum MacroDefinition {
1350 Object {
1351 replacement: String,
1352 },
1353 Function {
1354 parameters: Vec<String>,
1355 replacement: String,
1356 },
1357 VariadicFunction {
1358 parameters: Vec<String>,
1359 replacement: String,
1360 },
1361 Unsupported,
1362}
1363
1364#[derive(Clone, Debug, PartialEq, Eq)]
1365pub enum MacroIncludeProtection {
1366 MacroGuard(String),
1367 PragmaOnce,
1368 None,
1369}
1370
1371enum ParsedMacroReplacement {
1372 Parsed { source: String, tree: Tree },
1373 Unsupported,
1374}
1375
1376const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1380const MACRO_BODY_SENTINEL_SUFFIX: &str = "; }";
1381
1382type MacroOriginMap = Box<[std::ops::Range<usize>]>;
1385
1386pub struct ParsedReplacementBody {
1395 pub source: String,
1396 pub tree: Tree,
1397 pub body_offset: usize,
1398 pub parameters: Vec<String>,
1399 original_offsets: MacroOriginMap,
1403}
1404
1405impl ParsedReplacementBody {
1406 pub fn statements(&self) -> Option<Node<'_>> {
1408 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1409 .child_by_field_name("body")
1410 }
1411
1412 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1418 assert!(
1419 node.start_byte() >= self.body_offset,
1420 "synthetic sentinel node cannot be mapped to a macro replacement"
1421 );
1422 assert!(
1423 node.end_byte() >= self.body_offset,
1424 "synthetic sentinel node cannot be mapped to a macro replacement"
1425 );
1426 let start_offset = node.start_byte() - self.body_offset;
1427 let end_offset = node.end_byte() - self.body_offset;
1428 assert!(start_offset <= end_offset);
1429 let start_origin = self
1430 .original_offsets
1431 .get(start_offset)
1432 .expect("replacement node start must have a source mapping")
1433 .start;
1434 let end_origin = if start_offset == end_offset {
1435 start_origin
1436 } else {
1437 self.original_offsets
1438 .get(end_offset - 1)
1439 .expect("replacement node end must have a source mapping")
1440 .end
1441 };
1442 assert!(start_origin <= end_origin);
1443 let start = replacement_start + start_origin;
1444 let end = replacement_start + end_origin;
1445 start..end
1446 }
1447
1448 fn expands_variadic_arguments(&self) -> bool {
1455 let mut stack = vec![self.tree.root_node()];
1456 while let Some(node) = stack.pop() {
1457 if matches!(
1458 node.kind(),
1459 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1460 ) && node_text(node, &self.source) == "__VA_ARGS__"
1461 {
1462 return true;
1463 }
1464 push_named_children_reversed(node, &mut stack);
1465 }
1466 false
1467 }
1468}
1469
1470pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1472 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1473 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1474 if compact.starts_with("0x") || compact.starts_with("0X") {
1475 (16, 2, |ch| ch.is_ascii_hexdigit())
1476 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1477 (2, 2, |ch| matches!(ch, '0' | '1'))
1478 } else if compact.starts_with('0') && compact.len() > 1 {
1479 (8, 0, |ch| matches!(ch, '0'..='7'))
1480 } else {
1481 (10, 0, |ch| ch.is_ascii_digit())
1482 };
1483 let digit_len = compact[digits_start..]
1484 .chars()
1485 .take_while(|ch| digit_matches(*ch))
1486 .map(char::len_utf8)
1487 .sum::<usize>();
1488 if digit_len == 0 {
1489 return None;
1490 }
1491 let digits_end = digits_start + digit_len;
1492 if !compact[digits_end..]
1493 .chars()
1494 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1495 {
1496 return None;
1497 }
1498 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1499}
1500
1501#[derive(Clone)]
1502enum MacroLocalBindingTypeTemplate {
1503 Parameter(usize),
1504 Fixed(String),
1505}
1506
1507#[derive(Clone)]
1508struct MacroLocalBindingTemplate {
1509 name: String,
1510 declared_type: MacroLocalBindingTypeTemplate,
1511 pointer_depth: i32,
1512}
1513
1514pub struct MacroLocalBinding<'tree> {
1522 pub name: String,
1523 pub type_name: String,
1524 pub type_node: Option<Node<'tree>>,
1525 pub pointer_depth: i32,
1526 pub proven_unit: Option<CodeUnit>,
1527}
1528
1529#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1535pub enum MacroLexicalBindingKind {
1536 Parameter,
1537 Local,
1538}
1539
1540#[derive(Clone, Debug, PartialEq, Eq)]
1541pub struct MacroLexicalBinding {
1542 pub definition: ProjectFile,
1543 pub kind: MacroLexicalBindingKind,
1544 pub name: String,
1545 pub name_range: std::ops::Range<usize>,
1546 pub declaration_range: std::ops::Range<usize>,
1547}
1548
1549#[derive(Clone, Debug, Default, PartialEq, Eq)]
1553pub struct MacroLexicalReferences {
1554 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1555 pub truncated: bool,
1556 pub cancelled: bool,
1557}
1558
1559fn macro_replacement_type_parameters(
1560 body: &ParsedReplacementBody,
1561 parameters: &[String],
1562) -> Option<Vec<usize>> {
1563 let mut found = Vec::new();
1564 let mut stack = vec![body.tree.root_node()];
1565 while let Some(node) = stack.pop() {
1566 let type_position = node.kind() == "type_identifier"
1567 || (node.kind() == "identifier"
1568 && node.parent().is_some_and(|parent| {
1569 parent.kind() == "type_descriptor"
1570 && parent.child_by_field_name("type") == Some(node)
1571 }));
1572 let offsetof_type_position = node.kind() == "identifier"
1573 && node
1574 .parent()
1575 .filter(|parent| parent.kind() == "argument_list")
1576 .and_then(|arguments| arguments.parent())
1577 .is_some_and(|call| {
1578 call.kind() == "call_expression"
1579 && call
1580 .child_by_field_name("function")
1581 .is_some_and(|function| {
1582 function.kind() == "identifier"
1583 && node_text(function, &body.source) == "offsetof"
1584 })
1585 && call
1586 .child_by_field_name("arguments")
1587 .is_some_and(|arguments| {
1588 argument_children(arguments).next() == Some(node)
1589 })
1590 });
1591 if (type_position || offsetof_type_position)
1592 && let Some(index) = parameters
1593 .iter()
1594 .position(|parameter| parameter == node_text(node, &body.source))
1595 && !found.contains(&index)
1596 {
1597 found.push(index);
1598 }
1599 push_named_children_reversed(node, &mut stack);
1600 }
1601 (!found.is_empty()).then_some(found)
1602}
1603
1604fn macro_replacement_type_parameter(
1605 body: &ParsedReplacementBody,
1606 parameters: &[String],
1607) -> Option<usize> {
1608 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1609 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1610}
1611
1612pub(crate) fn function_macro_invocation_parts(node: Node<'_>) -> Option<(Node<'_>, Vec<Node<'_>>)> {
1616 let (name, arguments) = match node.kind() {
1617 "call_expression" => (
1618 node.child_by_field_name("function")?,
1619 argument_children(node.child_by_field_name("arguments")?).collect(),
1620 ),
1621 "macro_type_specifier" => (
1622 node.child_by_field_name("name")?,
1623 vec![node.child_by_field_name("type")?],
1624 ),
1625 _ => return None,
1626 };
1627 (name.kind() == "identifier").then_some((name, arguments))
1628}
1629
1630pub(crate) fn macro_type_argument_node<'tree>(
1631 node: Node<'tree>,
1632 source: &str,
1633) -> Option<Node<'tree>> {
1634 match node.kind() {
1635 "type_descriptor" => {
1636 let type_child = node
1637 .child_by_field_name("type")
1638 .or_else(|| first_type_child(node))?;
1639 for index in (0..node.named_child_count()).rev() {
1640 let child = node.named_child(index)?;
1641 if child != type_child
1642 && matches!(
1643 child.kind(),
1644 "identifier"
1645 | "type_identifier"
1646 | "qualified_identifier"
1647 | "scoped_type_identifier"
1648 )
1649 {
1650 return Some(child);
1651 }
1652 }
1653 macro_type_argument_node(type_child, source)
1654 }
1655 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1656 node.child_by_field_name("name")
1657 }
1658 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1659 .next_named_sibling()
1660 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1661 .and_then(|error| error.named_child(0))
1662 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1663 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1664 }
1665}
1666
1667fn c_function_macro_argument<'tree>(
1668 node: Node<'tree>,
1669) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1670 let mut current = node;
1671 while let Some(parent) = current.parent() {
1672 if parent.kind() == "argument_list" {
1673 let call = parent.parent().filter(|call| {
1674 call.kind() == "call_expression"
1675 && call.child_by_field_name("arguments") == Some(parent)
1676 && call
1677 .child_by_field_name("function")
1678 .is_some_and(|function| {
1679 function.kind() == "identifier"
1680 && !node_range_contains(function, current)
1681 })
1682 })?;
1683 let mut actuals = argument_children(parent).enumerate();
1684 let (index, argument) = actuals.find(|(_, argument)| {
1685 node_range_contains(*argument, node)
1686 && (argument.start_byte() == current.start_byte()
1687 || argument.end_byte() == current.end_byte())
1688 })?;
1689 return Some((call, index, argument));
1690 }
1691 current = parent;
1692 }
1693 None
1694}
1695
1696fn recognized_c_macro_declarator_binding<'tree>(
1704 statement: Node<'tree>,
1705 source: &str,
1706) -> Option<MacroLocalBinding<'tree>> {
1707 if statement.kind() == "declaration" {
1708 let specifier = statement
1709 .child_by_field_name("type")
1710 .filter(|type_node| type_node.kind() == "macro_type_specifier")?;
1711 let macro_name = specifier
1712 .child_by_field_name("name")
1713 .filter(|name| name.kind() == "identifier")?;
1714 if node_text(macro_name, source) != "g_autoptr" {
1715 return None;
1716 }
1717 let type_node = specifier
1718 .child_by_field_name("type")
1719 .and_then(|descriptor| descriptor.child_by_field_name("type"))?;
1720 let declarator = statement
1721 .child_by_field_name("declarator")
1722 .filter(|declarator| declarator.kind() == "init_declarator")?;
1723 let name_node = declarator
1724 .child_by_field_name("declarator")
1725 .filter(|name| name.kind() == "identifier")?;
1726 declarator.child_by_field_name("value")?;
1727 let name = node_text(name_node, source).trim();
1728 let type_name = node_text(type_node, source).trim();
1729 if name.is_empty() || type_name.is_empty() {
1730 return None;
1731 }
1732 return Some(MacroLocalBinding {
1733 name: name.to_string(),
1734 type_name: type_name.to_string(),
1735 type_node: Some(type_node),
1736 pointer_depth: 1,
1737 proven_unit: None,
1738 });
1739 }
1740 let assignment = match statement.kind() {
1741 "assignment_expression" => statement,
1742 "expression_statement" => {
1743 single_named_child_matching(statement, |child| !child.is_extra())?
1744 }
1745 _ => return None,
1746 };
1747 if assignment.kind() != "assignment_expression" {
1748 return None;
1749 }
1750 let call = assignment.child_by_field_name("left")?;
1751 if call.kind() != "call_expression" {
1752 return None;
1753 }
1754 let function = call.child_by_field_name("function")?;
1755 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1756 return None;
1757 }
1758 let arguments = call.child_by_field_name("arguments")?;
1759 let mut actuals = argument_children(arguments);
1760 let type_node = actuals.next()?;
1761 if actuals.next().is_some()
1762 || !matches!(
1763 type_node.kind(),
1764 "identifier"
1765 | "type_identifier"
1766 | "qualified_identifier"
1767 | "scoped_type_identifier"
1768 | "template_type"
1769 )
1770 {
1771 return None;
1772 }
1773 let name_node = (0..assignment.named_child_count())
1774 .filter_map(|index| assignment.named_child(index))
1775 .filter(|child| child.kind() == "ERROR")
1776 .filter_map(|error| {
1777 (error.named_child_count() == 1)
1778 .then(|| error.named_child(0))
1779 .flatten()
1780 })
1781 .find(|node| node.kind() == "identifier")?;
1782 let name = node_text(name_node, source).trim();
1783 let type_name = node_text(type_node, source).trim();
1784 if name.is_empty() || type_name.is_empty() {
1785 return None;
1786 }
1787 Some(MacroLocalBinding {
1788 name: name.to_string(),
1789 type_name: type_name.to_string(),
1790 type_node: Some(type_node),
1791 pointer_depth: 1,
1792 proven_unit: None,
1793 })
1794}
1795
1796#[derive(Clone, PartialEq, Eq)]
1797pub struct MacroBinding {
1798 source: ProjectFile,
1799 declaration_byte: usize,
1800 definition: MacroDefinition,
1801 exact: bool,
1802}
1803
1804impl MacroBinding {
1805 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1806 Self {
1807 source: source.clone(),
1808 declaration_byte,
1809 definition: MacroDefinition::Unsupported,
1810 exact: false,
1811 }
1812 }
1813
1814 fn is_exact(&self) -> bool {
1815 self.exact
1816 }
1817
1818 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1819 Self {
1820 source: source.clone(),
1821 declaration_byte,
1822 definition: current.definition.clone(),
1823 exact: false,
1824 }
1825 }
1826}
1827
1828type OwningPreprocessorConditionals = Box<[usize]>;
1840
1841#[derive(Clone)]
1842pub enum MacroEvent {
1843 Define {
1844 name: String,
1845 binding: MacroBinding,
1846 byte: usize,
1847 conditionals: OwningPreprocessorConditionals,
1848 },
1849 Undef {
1850 name: String,
1851 byte: usize,
1852 conditionals: OwningPreprocessorConditionals,
1853 },
1854 Include {
1855 targets: Vec<ProjectFile>,
1856 byte: usize,
1857 conditionals: OwningPreprocessorConditionals,
1858 },
1859 Invalidate {
1860 byte: usize,
1861 },
1862}
1863
1864impl MacroEvent {
1865 pub fn byte(&self) -> usize {
1866 match self {
1867 Self::Define { byte, .. }
1868 | Self::Undef { byte, .. }
1869 | Self::Include { byte, .. }
1870 | Self::Invalidate { byte } => *byte,
1871 }
1872 }
1873}
1874
1875#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1876pub enum CallArityEvidence {
1877 Exact(usize),
1878 Unknown,
1879}
1880
1881impl CallArityEvidence {
1882 pub fn exact(self) -> Option<usize> {
1883 match self {
1884 Self::Exact(arity) => Some(arity),
1885 Self::Unknown => None,
1886 }
1887 }
1888
1889 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1890 self.exact().map(|arity| expected.accepts(arity))
1891 }
1892}
1893
1894struct CppAlias {
1895 name: String,
1896 target: String,
1897 namespace: Option<String>,
1898}
1899
1900type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1901
1902#[derive(Debug, Clone, PartialEq, Eq)]
1906pub enum CppTemplateResolutionError {
1907 AliasCycle { alias: CodeUnit },
1909 ArgumentBinding,
1911 Substitution,
1913 PrimarySelection,
1916 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1919}
1920
1921fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1924 let mut distinct: Vec<CodeUnit> = Vec::new();
1925 for unit in units {
1926 if !distinct
1927 .iter()
1928 .any(|existing| same_visible_symbol(existing, unit))
1929 {
1930 distinct.push(unit.clone());
1931 }
1932 }
1933 distinct
1934}
1935
1936pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1939 visibility: Factory,
1940 file: &ProjectFile,
1941 root: Node<'_>,
1942 source: &str,
1943 max_references: usize,
1944 cancelled: impl FnMut() -> bool,
1945) -> MacroLexicalReferences
1946where
1947 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1948{
1949 crate::graph::macro_lexical::all_references(
1950 visibility,
1951 file,
1952 root,
1953 source,
1954 max_references,
1955 cancelled,
1956 )
1957}
1958
1959impl<'a> VisibilityIndex<'a> {
1960 pub fn data(&self) -> Arc<VisibilityIndexData> {
1961 Arc::clone(&self.data)
1962 }
1963
1964 pub fn from_data(
1965 cpp: &'a dyn CppSource,
1966 token: QueryToken<'a>,
1967 data: Arc<VisibilityIndexData>,
1968 ) -> Self {
1969 Self { cpp, token, data }
1970 }
1971
1972 pub fn cpp(&self) -> &'a dyn CppSource {
1973 self.cpp
1974 }
1975
1976 pub fn token(&self) -> QueryToken<'a> {
1978 self.token
1979 }
1980
1981 pub(crate) fn macro_lexical_index(
1985 &self,
1986 file: &ProjectFile,
1987 root: Node<'_>,
1988 source: &str,
1989 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1990 if let Some(index) = self
1991 .macro_lexical_indexes
1992 .lock()
1993 .expect("macro lexical index cache poisoned")
1994 .get(file)
1995 .cloned()
1996 {
1997 debug_assert_eq!(
1998 index.source_len,
1999 source.len(),
2000 "macro lexical index for {file:?} was built from a different source"
2001 );
2002 return index;
2003 }
2004 let index = Arc::new(crate::graph::macro_lexical::build_index(
2005 self, file, root, source,
2006 ));
2007 let mut cache = self
2008 .macro_lexical_indexes
2009 .lock()
2010 .expect("macro lexical index cache poisoned");
2011 if let Some(existing) = cache.get(file).cloned() {
2012 return existing;
2013 }
2014 if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
2015 cache.insert(file.clone(), Arc::clone(&index));
2016 }
2017 index
2018 }
2019
2020 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
2026 if let Some(metadata) = self
2027 .callable_metadata
2028 .lock()
2029 .expect("C++ callable metadata cache poisoned")
2030 .get(unit)
2031 .cloned()
2032 {
2033 return metadata;
2034 }
2035 let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
2036 let mut cache = self
2037 .callable_metadata
2038 .lock()
2039 .expect("C++ callable metadata cache poisoned");
2040 if cache.len() < CALLABLE_METADATA_CACHE_CAP {
2041 cache.insert(unit.clone(), Arc::clone(&metadata));
2042 }
2043 metadata
2044 }
2045
2046 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
2048 crate::identity::cpp_callable_linkage_from_metadata(
2049 self.callable_metadata(unit).iter().cloned(),
2050 )
2051 }
2052
2053 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
2055 brokk_bifrost_core::analyzer::model::first_callable_arity(
2056 self.callable_metadata(unit).iter().cloned(),
2057 )
2058 }
2059
2060 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
2065 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
2066 self.callable_metadata(unit).iter().cloned(),
2067 )
2068 }
2069
2070 pub fn has_unresolved_include_visible_before(
2078 &self,
2079 file: &ProjectFile,
2080 before_byte: usize,
2081 ) -> bool {
2082 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
2083 return false;
2084 };
2085 let facts = self.structured_include_facts(file, prepared.as_ref());
2086 has_unresolved_include_visible_before_in_prepared(
2087 file,
2088 prepared.as_ref(),
2089 self.cpp.include_target_index(),
2090 &facts,
2091 before_byte,
2092 &|guards: Option<&HashSet<PreprocessorGuard>>| {
2093 self.effective_reference_guards(file, guards)
2094 },
2095 )
2096 }
2097
2098 fn structured_include_facts(
2099 &self,
2100 file: &ProjectFile,
2101 prepared: &PreparedSyntaxTree,
2102 ) -> Arc<[StructuredIncludeFact]> {
2103 let cell = self
2104 .structured_include_fact_cells
2105 .lock()
2106 .expect("C++ structured include-fact cache poisoned")
2107 .entry(file.clone())
2108 .or_default()
2109 .clone();
2110 Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
2111 }
2112
2113 #[cfg(any(test, feature = "test-support"))]
2121 pub fn from_visible_files_for_test(
2122 cpp: &'a dyn CppSource,
2123 token: QueryToken<'a>,
2124 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2125 ) -> Self {
2126 let visible_source_files_by_root = visible_by_file
2127 .iter()
2128 .map(|(file, visible)| {
2129 (
2130 file.clone(),
2131 visible
2132 .iter()
2133 .map(|unit| unit.source().clone())
2134 .chain(std::iter::once(file.clone()))
2135 .collect(),
2136 )
2137 })
2138 .collect();
2139 let mut global_field_internal_linkage = HashMap::default();
2140 Self {
2141 cpp,
2142 token,
2143 data: Arc::new(VisibilityIndexData {
2144 visible_by_identifier: build_visible_identifier_index(
2145 &CppGraphSource::from_source(cpp, token),
2146 &visible_by_file,
2147 &visible_source_files_by_root,
2148 &mut global_field_internal_linkage,
2149 ),
2150 global_field_internal_linkage,
2151 visible_by_file,
2152 visible_source_files_by_root,
2153 alias_cells: Mutex::new(HashMap::default()),
2154 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2155 parser_alias_target_matches: RwLock::new(HashMap::default()),
2156 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2157 project_using_index: OnceLock::new(),
2158 namespace_alias_sites: Mutex::new(HashMap::default()),
2159 callable_reference_specs: Mutex::new(HashMap::default()),
2160 structured_include_fact_cells: Mutex::new(HashMap::default()),
2161 include_activation_cells: Mutex::new(HashMap::default()),
2162 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2163 include_path_admission_cells: Mutex::new(HashMap::default()),
2164 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2165 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2166 conditional_include_projection_state_count: AtomicUsize::new(0),
2167 conditional_include_target_state_count: AtomicUsize::new(0),
2168 include_activation_build_count: AtomicUsize::new(0),
2169 using_donor_activation_count: AtomicUsize::new(0),
2170 using_namespace_lookup_count: AtomicUsize::new(0),
2171 using_name_candidate_inspection_count: AtomicUsize::new(0),
2172 callable_reference_spec_build_count: AtomicUsize::new(0),
2173 alias_source_parse_counts: Mutex::new(HashMap::default()),
2174 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2175 parser_alias_fallback_calls: AtomicUsize::new(0),
2176 parser_alias_fallback_files: AtomicUsize::new(0),
2177 parser_alias_source_parses: AtomicUsize::new(0),
2178 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2179 field_type_facts: Mutex::new(HashMap::default()),
2180 structured_alias_targets: Mutex::new(HashMap::default()),
2181 callable_comparables: Mutex::new(HashMap::default()),
2182 class_definition_agreements: Mutex::new(HashMap::default()),
2183 callable_metadata: Mutex::new(HashMap::default()),
2184 comparable_name_declarations: Mutex::new(HashMap::default()),
2185 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2186 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2187 precise_parent_cache: Mutex::new(HashMap::default()),
2188 c_tag_kind_cache: Mutex::new(HashMap::default()),
2189 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2190 macro_event_cells: Mutex::new(HashMap::default()),
2191 macro_event_name_sets: Mutex::new(HashMap::default()),
2192 macro_include_protection_cells: Mutex::new(HashMap::default()),
2193 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2194 macro_replacements: Mutex::new(HashMap::default()),
2195 macro_local_binding_templates: Mutex::new(HashMap::default()),
2196 macro_lexical_templates: Mutex::new(HashMap::default()),
2197 macro_lexical_indexes: Mutex::new(HashMap::default()),
2198 macro_replacement_bodies: Mutex::new(HashMap::default()),
2199 macro_type_parameters: Mutex::new(HashMap::default()),
2200 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2201 macro_replacement_parse_count: AtomicUsize::new(0),
2202 macro_event_application_count: AtomicUsize::new(0),
2203 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2204 macro_environment_copy_count: AtomicUsize::new(0),
2205 macro_environment_request_count: AtomicUsize::new(0),
2206 cpp_template_metadata: HashMap::default(),
2207 cpp_template_families: HashMap::default(),
2208 qualified_candidate_inspections: AtomicUsize::new(0),
2209 target_preserving_type_resolution_count: AtomicUsize::new(0),
2210 visibility_identifier_lookup_count: 0,
2211 visibility_identifier_batch_count: 0,
2212 }),
2213 }
2214 }
2215
2216 fn cpp_source(&self) -> CppGraphSource<'a> {
2223 CppGraphSource::from_source(self.cpp, self.token)
2224 }
2225
2226 pub fn build(
2227 cpp: &'a dyn CppSource,
2228 token: QueryToken<'a>,
2229 analyzer: &CppGraphSource<'_>,
2230 roots: &HashSet<ProjectFile>,
2231 ) -> Self {
2232 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2233 }
2234
2235 pub fn build_with_cancellation(
2236 cpp: &'a dyn CppSource,
2237 token: QueryToken<'a>,
2238 analyzer: &CppGraphSource<'_>,
2239 roots: &HashSet<ProjectFile>,
2240 cancellation: Option<&CancellationToken>,
2241 ) -> Self {
2242 let visibility_started = Instant::now();
2243 let include_targets = cpp.include_target_index();
2244 let includes_started = Instant::now();
2245 let mut include_graph = IncludeGraph::default();
2246 for root in roots {
2247 include_graph.extend_with(root, cancellation, &mut |file| {
2248 cpp.canonical_include_paths(token, file)
2249 .unwrap_or_default()
2250 .into_iter()
2251 .flat_map(|include| {
2252 resolve_include_targets_with_index(file, &include, include_targets)
2253 })
2254 .collect()
2255 });
2256 }
2257 let include_elapsed = includes_started.elapsed();
2258 let include_file_count = include_graph.files().count();
2259 let visible_source_files_by_root = roots
2260 .iter()
2261 .map(|root| {
2262 (
2263 root.clone(),
2264 include_graph.reachable_files(root, cancellation),
2265 )
2266 })
2267 .collect::<HashMap<_, _>>();
2268 let mut visibility_stats = BoundedVisibilityStats::default();
2269 let mut visible_by_file = build_bounded_visible_declarations(
2270 cpp,
2271 token,
2272 analyzer,
2273 roots,
2274 &visible_source_files_by_root,
2275 cancellation,
2276 &mut visibility_stats,
2277 );
2278 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2279 eprintln!(
2280 "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={}",
2281 visibility_started.elapsed().as_millis(),
2282 include_elapsed.as_millis(),
2283 include_file_count,
2284 visibility_stats.rounds,
2285 visibility_stats.root_names,
2286 visibility_stats.identifier_lookups,
2287 visibility_stats.identifier_batches,
2288 visibility_stats.candidate_units,
2289 visibility_stats.candidate_sources,
2290 visibility_stats.declaration_reads,
2291 visibility_stats.declaration_units,
2292 visibility_stats.selected_units,
2293 visibility_stats.dependency_source_names,
2294 visibility_stats.dependency_names,
2295 visibility_stats.lookup_elapsed.as_millis(),
2296 visibility_stats.declaration_elapsed.as_millis(),
2297 visibility_stats.dependency_source_elapsed.as_millis(),
2298 );
2299 }
2300 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2301 let finalize_started = Instant::now();
2302 if report_stats {
2303 eprintln!(
2304 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2305 visible_by_file.len(),
2306 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2307 );
2308 }
2309 let owner_started = Instant::now();
2310 if report_stats {
2311 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2312 }
2313 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2314 if report_stats {
2315 eprintln!(
2316 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2317 owner_stats.unseen_owners,
2318 owner_stats.definition_lookups,
2319 owner_stats.admitted,
2320 owner_started.elapsed().as_millis(),
2321 );
2322 }
2323 let mut global_field_internal_linkage = HashMap::default();
2324 let identifier_started = Instant::now();
2325 if report_stats {
2326 eprintln!(
2327 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2328 );
2329 }
2330 let visible_by_identifier = build_visible_identifier_index(
2331 analyzer,
2332 &visible_by_file,
2333 &visible_source_files_by_root,
2334 &mut global_field_internal_linkage,
2335 );
2336 if report_stats {
2337 eprintln!(
2338 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2339 visible_by_identifier.len(),
2340 visible_by_identifier
2341 .values()
2342 .map(HashMap::len)
2343 .sum::<usize>(),
2344 visible_by_identifier
2345 .values()
2346 .flat_map(HashMap::values)
2347 .map(Vec::len)
2348 .sum::<usize>(),
2349 identifier_started.elapsed().as_millis(),
2350 );
2351 }
2352 let mut cpp_template_metadata = HashMap::default();
2353 let metadata_started = Instant::now();
2354 let mut template_classes = 0usize;
2355 if report_stats {
2356 eprintln!(
2357 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2358 );
2359 }
2360 for unit in visible_by_file
2361 .values()
2362 .flatten()
2363 .filter(|unit| unit.is_class())
2364 {
2365 template_classes += 1;
2366 if cpp_template_metadata.contains_key(unit) {
2367 continue;
2368 }
2369 if let Some(metadata) = cpp.template_metadata(unit) {
2370 cpp_template_metadata.insert(unit.clone(), metadata);
2371 }
2372 }
2373 if report_stats {
2374 eprintln!(
2375 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2376 template_classes,
2377 cpp_template_metadata.len(),
2378 metadata_started.elapsed().as_millis(),
2379 );
2380 }
2381 let families_started = Instant::now();
2382 if report_stats {
2383 eprintln!(
2384 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2385 );
2386 }
2387 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2388 for (unit, metadata) in &cpp_template_metadata {
2389 cpp_template_families
2390 .entry(metadata.primary_fq_name.clone())
2391 .or_default()
2392 .push(unit.clone());
2393 }
2394 for family in cpp_template_families.values_mut() {
2403 sort_lookup_units(family);
2404 }
2405 if report_stats {
2406 eprintln!(
2407 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2408 cpp_template_families.len(),
2409 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2410 families_started.elapsed().as_millis(),
2411 );
2412 eprintln!(
2413 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2414 visible_by_file.len(),
2415 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2416 finalize_started.elapsed().as_millis(),
2417 visibility_started.elapsed().as_millis(),
2418 );
2419 }
2420 Self {
2421 cpp,
2422 token,
2423 data: Arc::new(VisibilityIndexData {
2424 visible_by_file,
2425 visible_by_identifier,
2426 global_field_internal_linkage,
2427 visible_source_files_by_root,
2428 alias_cells: Mutex::new(HashMap::default()),
2429 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2430 parser_alias_target_matches: RwLock::new(HashMap::default()),
2431 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2432 project_using_index: OnceLock::new(),
2433 namespace_alias_sites: Mutex::new(HashMap::default()),
2434 callable_reference_specs: Mutex::new(HashMap::default()),
2435 structured_include_fact_cells: Mutex::new(HashMap::default()),
2436 include_activation_cells: Mutex::new(HashMap::default()),
2437 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2438 include_path_admission_cells: Mutex::new(HashMap::default()),
2439 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2440 #[cfg(any(test, feature = "test-support"))]
2441 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2442 #[cfg(any(test, feature = "test-support"))]
2443 conditional_include_projection_state_count: AtomicUsize::new(0),
2444 #[cfg(any(test, feature = "test-support"))]
2445 conditional_include_target_state_count: AtomicUsize::new(0),
2446 #[cfg(any(test, feature = "test-support"))]
2447 include_activation_build_count: AtomicUsize::new(0),
2448 #[cfg(any(test, feature = "test-support"))]
2449 using_donor_activation_count: AtomicUsize::new(0),
2450 #[cfg(any(test, feature = "test-support"))]
2451 using_namespace_lookup_count: AtomicUsize::new(0),
2452 #[cfg(any(test, feature = "test-support"))]
2453 using_name_candidate_inspection_count: AtomicUsize::new(0),
2454 #[cfg(any(test, feature = "test-support"))]
2455 callable_reference_spec_build_count: AtomicUsize::new(0),
2456 #[cfg(any(test, feature = "test-support"))]
2457 alias_source_parse_counts: Mutex::new(HashMap::default()),
2458 #[cfg(any(test, feature = "test-support"))]
2459 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2460 parser_alias_fallback_calls: AtomicUsize::new(0),
2461 parser_alias_fallback_files: AtomicUsize::new(0),
2462 parser_alias_source_parses: AtomicUsize::new(0),
2463 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2464 field_type_facts: Mutex::new(HashMap::default()),
2465 structured_alias_targets: Mutex::new(HashMap::default()),
2466 callable_comparables: Mutex::new(HashMap::default()),
2467 class_definition_agreements: Mutex::new(HashMap::default()),
2468 callable_metadata: Mutex::new(HashMap::default()),
2469 comparable_name_declarations: Mutex::new(HashMap::default()),
2470 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2471 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2472 precise_parent_cache: Mutex::new(HashMap::default()),
2473 c_tag_kind_cache: Mutex::new(HashMap::default()),
2474 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2475 macro_event_cells: Mutex::new(HashMap::default()),
2476 macro_event_name_sets: Mutex::new(HashMap::default()),
2477 macro_include_protection_cells: Mutex::new(HashMap::default()),
2478 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2479 macro_replacements: Mutex::new(HashMap::default()),
2480 macro_local_binding_templates: Mutex::new(HashMap::default()),
2481 macro_lexical_templates: Mutex::new(HashMap::default()),
2482 macro_lexical_indexes: Mutex::new(HashMap::default()),
2483 macro_replacement_bodies: Mutex::new(HashMap::default()),
2484 macro_type_parameters: Mutex::new(HashMap::default()),
2485 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2486 #[cfg(any(test, feature = "test-support"))]
2487 macro_replacement_parse_count: AtomicUsize::new(0),
2488 #[cfg(any(test, feature = "test-support"))]
2489 macro_event_application_count: AtomicUsize::new(0),
2490 #[cfg(any(test, feature = "test-support"))]
2491 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2492 #[cfg(any(test, feature = "test-support"))]
2493 macro_environment_copy_count: AtomicUsize::new(0),
2494 #[cfg(any(test, feature = "test-support"))]
2495 macro_environment_request_count: AtomicUsize::new(0),
2496 cpp_template_metadata,
2497 cpp_template_families,
2498 #[cfg(any(test, feature = "test-support"))]
2499 qualified_candidate_inspections: AtomicUsize::new(0),
2500 #[cfg(any(test, feature = "test-support"))]
2501 target_preserving_type_resolution_count: AtomicUsize::new(0),
2502 #[cfg(any(test, feature = "test-support"))]
2503 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2504 #[cfg(any(test, feature = "test-support"))]
2505 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2506 }),
2507 }
2508 }
2509
2510 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2511 if file == target.source() {
2512 return true;
2513 }
2514 if self.global_field_has_internal_linkage(target) {
2515 return self
2516 .visible_source_files_by_root
2517 .get(file)
2518 .is_some_and(|sources| sources.contains(target.source()));
2519 }
2520 self.visible_by_file
2521 .get(file)
2522 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2523 }
2524
2525 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2526 self.global_field_internal_linkage
2527 .get(unit)
2528 .copied()
2529 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2530 }
2531
2532 pub fn call_arity_evidence(
2533 &self,
2534 file: &ProjectFile,
2535 call: Node<'_>,
2536 source: &str,
2537 ) -> CallArityEvidence {
2538 self.call_arity_evidence_at(file, call, source, call.start_byte())
2539 }
2540
2541 pub fn call_arity_evidence_at(
2549 &self,
2550 file: &ProjectFile,
2551 call: Node<'_>,
2552 source: &str,
2553 environment_byte: usize,
2554 ) -> CallArityEvidence {
2555 let Some(arguments) = call
2556 .child_by_field_name("arguments")
2557 .or_else(|| call.child_by_field_name("parameters"))
2558 .or_else(|| call.child_by_field_name("value"))
2559 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2560 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2561 else {
2562 return CallArityEvidence::Exact(0);
2563 };
2564 let recovered_c_keyword_arguments =
2565 recovered_c_keyword_argument_count(file, call, arguments, source);
2566 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2567 let arguments = argument_children(arguments)
2568 .flat_map(|argument| {
2569 recovered_c_new_expression_arguments(argument, c_semantics)
2570 .map(Vec::from)
2571 .unwrap_or_else(|| vec![argument])
2572 })
2573 .collect::<Vec<_>>();
2574 if arguments
2575 .iter()
2576 .all(|argument| !argument_shape_may_change_arity(*argument))
2577 {
2578 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2579 }
2580 let environment = self.macro_environment(file, environment_byte);
2581 let mut stack = Vec::new();
2582 let mut total = recovered_c_keyword_arguments;
2583 for argument in arguments {
2584 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2585 return CallArityEvidence::Unknown;
2586 }
2587 let CallArityEvidence::Exact(spread) =
2588 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2589 else {
2590 return CallArityEvidence::Unknown;
2591 };
2592 total += spread;
2593 }
2594 CallArityEvidence::Exact(total)
2595 }
2596
2597 fn argument_arity_evidence(
2598 &self,
2599 argument: Node<'_>,
2600 source: &str,
2601 environment: &MacroEnvironment,
2602 stack: &mut Vec<(ProjectFile, usize)>,
2603 ) -> CallArityEvidence {
2604 let (name, invocation_arguments, function_like) = match argument.kind() {
2605 "identifier" => (node_text(argument, source), None, false),
2606 "call_expression" => {
2607 let Some(function) = argument.child_by_field_name("function") else {
2608 return CallArityEvidence::Exact(1);
2609 };
2610 if function.kind() != "identifier" {
2611 return CallArityEvidence::Exact(1);
2612 }
2613 let Some(arguments) = argument.child_by_field_name("arguments") else {
2614 return CallArityEvidence::Exact(1);
2615 };
2616 (node_text(function, source), Some(arguments), true)
2617 }
2618 _ => return CallArityEvidence::Exact(1),
2619 };
2620 let Some(binding) = environment.binding(name) else {
2621 return if environment.unknown_names {
2622 CallArityEvidence::Unknown
2623 } else {
2624 CallArityEvidence::Exact(1)
2625 };
2626 };
2627 if !binding.is_exact() {
2628 return CallArityEvidence::Unknown;
2629 }
2630 match (&binding.definition, invocation_arguments, function_like) {
2631 (MacroDefinition::Object { replacement }, None, false) => self
2632 .replacement_arity_evidence(
2633 replacement,
2634 &[],
2635 &[],
2636 source,
2637 environment,
2638 stack,
2639 binding,
2640 ),
2641 (
2642 MacroDefinition::Function {
2643 parameters,
2644 replacement,
2645 },
2646 Some(arguments),
2647 true,
2648 ) => {
2649 let actuals = argument_children(arguments).collect::<Vec<_>>();
2650 if actuals.len() != parameters.len() {
2651 CallArityEvidence::Unknown
2652 } else {
2653 self.replacement_arity_evidence(
2654 replacement,
2655 parameters,
2656 &actuals,
2657 source,
2658 environment,
2659 stack,
2660 binding,
2661 )
2662 }
2663 }
2664 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2665 _ => CallArityEvidence::Unknown,
2666 }
2667 }
2668
2669 #[allow(clippy::too_many_arguments)]
2670 fn replacement_arity_evidence(
2671 &self,
2672 replacement: &str,
2673 parameters: &[String],
2674 actuals: &[Node<'_>],
2675 actual_source: &str,
2676 environment: &MacroEnvironment,
2677 stack: &mut Vec<(ProjectFile, usize)>,
2678 binding: &MacroBinding,
2679 ) -> CallArityEvidence {
2680 let identity = (binding.source.clone(), binding.declaration_byte);
2681 if stack.contains(&identity) || replacement.trim().is_empty() {
2682 return CallArityEvidence::Unknown;
2683 }
2684 stack.push(identity);
2685 let parsed = self.parsed_macro_replacement(binding, replacement);
2686 let evidence = (|| {
2687 let ParsedMacroReplacement::Parsed {
2688 source: sentinel,
2689 tree,
2690 } = parsed.as_ref()
2691 else {
2692 return None;
2693 };
2694 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2695 let arguments = call.child_by_field_name("arguments")?;
2696 let mut total = 0usize;
2697 for argument in argument_children(arguments) {
2698 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2699 return None;
2700 }
2701 if argument.kind() == "identifier"
2702 && let Some(parameter_index) = parameters
2703 .iter()
2704 .position(|parameter| parameter == node_text(argument, sentinel))
2705 {
2706 if !macro_expansion_shape_is_safe(
2707 actuals[parameter_index],
2708 actual_source,
2709 &[],
2710 environment,
2711 ) {
2712 return None;
2713 }
2714 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2715 actuals[parameter_index],
2716 actual_source,
2717 environment,
2718 stack,
2719 ) else {
2720 return None;
2721 };
2722 total += spread;
2723 continue;
2724 }
2725 let CallArityEvidence::Exact(spread) =
2726 self.argument_arity_evidence(argument, sentinel, environment, stack)
2727 else {
2728 return None;
2729 };
2730 total += spread;
2731 }
2732 Some(CallArityEvidence::Exact(total))
2733 })()
2734 .unwrap_or(CallArityEvidence::Unknown);
2735 stack.pop();
2736 evidence
2737 }
2738
2739 fn parsed_macro_replacement(
2740 &self,
2741 binding: &MacroBinding,
2742 replacement: &str,
2743 ) -> Arc<ParsedMacroReplacement> {
2744 let key = (binding.source.clone(), binding.declaration_byte);
2745 let mut cache = self
2746 .macro_replacements
2747 .lock()
2748 .expect("C++ macro replacement cache poisoned");
2749 if let Some(parsed) = cache.get(&key) {
2750 return Arc::clone(parsed);
2751 }
2752 #[cfg(any(test, feature = "test-support"))]
2753 self.macro_replacement_parse_count
2754 .fetch_add(1, Ordering::Relaxed);
2755 let parsed = parse_sentinel(
2756 "void __bifrost_macro_arity() { __bifrost_macro_call(",
2757 SentinelInput::replacement(replacement),
2758 "); }",
2759 );
2760 let parsed = if parsed.tree.root_node().has_error() {
2761 ParsedMacroReplacement::Unsupported
2762 } else {
2763 ParsedMacroReplacement::Parsed {
2764 source: parsed.source,
2765 tree: parsed.tree,
2766 }
2767 };
2768 let parsed = Arc::new(parsed);
2769 cache.insert(key, Arc::clone(&parsed));
2770 parsed
2771 }
2772
2773 pub fn function_macro_local_binding<'tree>(
2783 &self,
2784 file: &ProjectFile,
2785 statement: Node<'tree>,
2786 source: &str,
2787 ) -> Option<MacroLocalBinding<'tree>> {
2788 if !is_c_source_file(file) {
2789 return None;
2790 }
2791 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2792 return Some(binding);
2793 }
2794 let call = match statement.kind() {
2795 "expression_statement" => {
2796 single_named_child_matching(statement, |child| !child.is_extra())?
2797 }
2798 _ => statement,
2799 };
2800 let (function, actuals) = function_macro_invocation_parts(call)?;
2801 let environment = self.macro_environment(file, call.start_byte());
2802 let function_name = node_text(function, source);
2803 let binding = environment.binding(function_name)?;
2804 let MacroDefinition::Function {
2805 parameters,
2806 replacement,
2807 } = &binding.definition
2808 else {
2809 return None;
2810 };
2811 if actuals.len() != parameters.len() {
2812 return None;
2813 }
2814 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2815 let (type_name, type_node) = match &template.declared_type {
2816 MacroLocalBindingTypeTemplate::Parameter(index) => {
2817 let actual = *actuals.get(*index)?;
2818 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2819 return None;
2820 }
2821 let type_node = macro_type_argument_node(actual, source).unwrap_or(actual);
2822 (
2823 node_text(actual, source).trim().to_string(),
2824 Some(type_node),
2825 )
2826 }
2827 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2828 };
2829 if type_name.is_empty() {
2830 return None;
2831 }
2832 Some(MacroLocalBinding {
2833 name: template.name.clone(),
2834 type_name,
2835 type_node,
2836 pointer_depth: template.pointer_depth,
2837 proven_unit: None,
2838 })
2839 }
2840
2841 pub fn function_macro_container_binding<'tree>(
2850 &self,
2851 analyzer: &CppGraphSource<'_>,
2852 file: &ProjectFile,
2853 assignment: Node<'tree>,
2854 source: &str,
2855 ) -> Option<MacroLocalBinding<'tree>> {
2856 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2857 return None;
2858 }
2859 let name_node = assignment.child_by_field_name("left")?;
2860 if name_node.kind() != "identifier" {
2861 return None;
2862 }
2863 let call = assignment.child_by_field_name("right")?;
2864 if call.kind() != "call_expression" {
2865 return None;
2866 }
2867 let function = call.child_by_field_name("function")?;
2868 if function.kind() != "identifier" {
2869 return None;
2870 }
2871 let arguments = call.child_by_field_name("arguments")?;
2872 let actuals = argument_children(arguments).collect::<Vec<_>>();
2873 let function_name = node_text(function, source);
2874 let environment = self.macro_environment(file, call.start_byte());
2875 let binding = environment.binding(function_name)?;
2876 if !binding.is_exact() {
2877 return None;
2878 }
2879 let MacroDefinition::Function {
2880 parameters,
2881 replacement,
2882 } = &binding.definition
2883 else {
2884 return None;
2885 };
2886 if actuals.len() != parameters.len() {
2887 return None;
2888 }
2889 let body = self.parsed_macro_replacement_body(
2890 &(binding.source.clone(), binding.declaration_byte),
2891 parameters,
2892 replacement,
2893 )?;
2894 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2895 let type_argument = *actuals.get(type_parameter)?;
2896 let type_node = macro_type_argument_node(type_argument, source)?;
2897 let type_name = node_text(type_node, source).trim().to_string();
2898 if type_name.is_empty() {
2899 return None;
2900 }
2901 let explicit_tag = match node_text(type_argument, source) {
2902 "struct" => Some(CppCTagKind::Struct),
2903 "union" => Some(CppCTagKind::Union),
2904 _ => None,
2905 };
2906 let resolved_type = if let Some(tag) = explicit_tag {
2907 let candidates = self
2908 .visible_identifier_candidates(file, &type_name)
2909 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2910 .collect::<Vec<_>>();
2911 self.resolve_type_candidates(
2912 analyzer,
2913 file,
2914 &candidates,
2915 TypeCandidateResolution::Canonical,
2916 )
2917 .ok()
2918 } else {
2919 self.resolve_type_node_result(file, type_node, source)
2920 .ok()
2921 .flatten()
2922 };
2923 let proven_unit = resolved_type?;
2924 Some(MacroLocalBinding {
2925 name: node_text(name_node, source).to_string(),
2926 type_name,
2927 type_node: Some(type_node),
2928 pointer_depth: 1,
2929 proven_unit: Some(proven_unit),
2930 })
2931 }
2932
2933 pub fn macro_local_binding_at<'tree>(
2936 &self,
2937 file: &ProjectFile,
2938 root: Node<'tree>,
2939 source: &str,
2940 start_byte: usize,
2941 end_byte: usize,
2942 ) -> Option<MacroLocalBinding<'tree>> {
2943 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2944 }
2945
2946 pub fn macro_lexical_binding(
2950 &self,
2951 file: &ProjectFile,
2952 root: Node<'_>,
2953 source: &str,
2954 start_byte: usize,
2955 end_byte: usize,
2956 ) -> Option<MacroLexicalBinding> {
2957 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2958 }
2959
2960 pub fn macro_lexical_references(
2965 &self,
2966 file: &ProjectFile,
2967 root: Node<'_>,
2968 source: &str,
2969 max_references: usize,
2970 cancelled: impl FnMut() -> bool,
2971 ) -> MacroLexicalReferences {
2972 crate::graph::macro_lexical::all_references(
2973 || self,
2974 file,
2975 root,
2976 source,
2977 max_references,
2978 cancelled,
2979 )
2980 }
2981
2982 pub(crate) fn function_macro_binding_at(
2987 &self,
2988 file: &ProjectFile,
2989 name: &str,
2990 before_byte: usize,
2991 ) -> Option<(ProjectFile, usize)> {
2992 let environment = self.macro_environment(file, before_byte);
2993 let binding = environment.binding(name)?;
2994 if binding.is_exact()
2995 && matches!(
2996 binding.definition,
2997 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2998 )
2999 {
3000 return Some((binding.source.clone(), binding.declaration_byte));
3001 }
3002 if binding.source != *file {
3008 return None;
3009 }
3010 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3011 let root = prepared.tree().root_node();
3012 let source = prepared.source();
3013 let reference = root.descendant_for_byte_range(
3014 before_byte,
3015 before_byte.saturating_add(1).min(source.len()),
3016 )?;
3017 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
3018 let cell = self.macro_event_cell(file);
3019 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3020 let event = events
3021 .iter()
3022 .find(|event| event.byte() == binding.declaration_byte)?;
3023 let MacroEvent::Define {
3024 name: defined_name,
3025 binding: definition,
3026 conditionals,
3027 ..
3028 } = event
3029 else {
3030 return None;
3031 };
3032 if defined_name != name
3033 || conditionals.is_empty()
3034 || !conditionals
3035 .iter()
3036 .all(|condition| reference_conditions.contains(condition))
3037 || !matches!(
3038 definition.definition,
3039 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
3040 )
3041 {
3042 return None;
3043 }
3044 Some((definition.source.clone(), definition.declaration_byte))
3045 }
3046
3047 pub fn function_macro_type_argument<'tree>(
3054 &self,
3055 file: &ProjectFile,
3056 node: Node<'tree>,
3057 source: &str,
3058 ) -> Option<Node<'tree>> {
3059 if !is_c_source_file(file) {
3060 return None;
3061 }
3062 let (call, argument_index, argument) = c_function_macro_argument(node)?;
3063 let function = call.child_by_field_name("function")?;
3064 let function_name = node_text(function, source);
3065 if function_name.is_empty() {
3066 return None;
3067 }
3068 if !self.file_defines_macro_name(file, function_name)
3069 && !self
3070 .visible_identifier_candidates(file, function_name)
3071 .any(|candidate| candidate.is_macro())
3072 {
3073 return None;
3074 }
3075 let environment = self.macro_environment(file, call.start_byte());
3076 let binding = environment.binding(function_name)?;
3077 if !binding.is_exact() {
3078 return None;
3079 }
3080 let (parameters, replacement, variadic) = match &binding.definition {
3081 MacroDefinition::Function {
3082 parameters,
3083 replacement,
3084 } => (parameters, replacement, false),
3085 MacroDefinition::VariadicFunction {
3086 parameters,
3087 replacement,
3088 } => (parameters, replacement, true),
3089 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3090 };
3091 let actuals = call
3092 .child_by_field_name("arguments")
3093 .map(argument_children)
3094 .into_iter()
3095 .flatten()
3096 .collect::<Vec<_>>();
3097 let arity_matches = if variadic {
3098 actuals.len() >= parameters.len()
3099 } else {
3100 actuals.len() == parameters.len()
3101 };
3102 let type_parameters = self.macro_type_parameter_indices(
3103 &(binding.source.clone(), binding.declaration_byte),
3104 parameters,
3105 replacement,
3106 )?;
3107 if !arity_matches || !type_parameters.contains(&argument_index) {
3108 return None;
3109 }
3110 let type_node = macro_type_argument_node(argument, source)?;
3111 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
3112 return None;
3113 }
3114 Some(type_node)
3115 }
3116
3117 fn macro_local_binding_template(
3118 &self,
3119 binding: &MacroBinding,
3120 parameters: &[String],
3121 replacement: &str,
3122 ) -> Option<Arc<MacroLocalBindingTemplate>> {
3123 let key = (binding.source.clone(), binding.declaration_byte);
3124 if let Some(template) = self
3125 .macro_local_binding_templates
3126 .lock()
3127 .expect("C++ macro local-binding cache poisoned")
3128 .get(&key)
3129 {
3130 return template.clone();
3131 }
3132 let template = (|| {
3133 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3134 let sentinel = body.source.as_str();
3135 let statements = body.statements()?;
3136 let declaration = single_named_child_matching(statements, |child| !child.is_extra())?;
3137 if declaration.kind() != "declaration" {
3138 return None;
3139 }
3140 let type_node = declaration
3141 .child_by_field_name("type")
3142 .or_else(|| first_type_child(declaration))?;
3143 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3144 let mut cursor = declaration.walk();
3145 declaration.named_children(&mut cursor).find_map(|child| {
3146 if child.kind() == "init_declarator" {
3147 child.child_by_field_name("declarator")
3148 } else {
3149 is_declarator_node(child).then_some(child)
3150 }
3151 })
3152 })?;
3153 let name = extract_variable_name(declarator, sentinel)?;
3154 let pointer_depth =
3155 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3156 let type_text = node_text(type_node, sentinel).trim();
3157 let declared_type = parameters
3158 .iter()
3159 .position(|parameter| parameter == type_text)
3160 .map(MacroLocalBindingTypeTemplate::Parameter)
3161 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3162 Some(Arc::new(MacroLocalBindingTemplate {
3163 name,
3164 declared_type,
3165 pointer_depth,
3166 }))
3167 })();
3168 self.macro_local_binding_templates
3169 .lock()
3170 .expect("C++ macro local-binding cache poisoned")
3171 .insert(key, template.clone());
3172 template
3173 }
3174
3175 pub fn function_macro_replacement_body(
3182 &self,
3183 file: &ProjectFile,
3184 definition: Node<'_>,
3185 source: &str,
3186 ) -> Option<Arc<ParsedReplacementBody>> {
3187 debug_assert_eq!(definition.kind(), "preproc_function_def");
3188 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3189 MacroDefinition::Function {
3190 parameters,
3191 replacement,
3192 }
3193 | MacroDefinition::VariadicFunction {
3194 parameters,
3195 replacement,
3196 } => (parameters, replacement),
3197 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3198 };
3199 self.parsed_macro_replacement_body(
3200 &(file.clone(), definition.start_byte()),
3201 ¶meters,
3202 &replacement,
3203 )
3204 }
3205
3206 fn parsed_macro_replacement_body(
3214 &self,
3215 key: &(ProjectFile, usize),
3216 parameters: &[String],
3217 replacement: &str,
3218 ) -> Option<Arc<ParsedReplacementBody>> {
3219 if let Some(body) = self
3220 .macro_replacement_bodies
3221 .lock()
3222 .expect("C++ macro replacement body cache poisoned")
3223 .get(key)
3224 {
3225 return body.clone();
3226 }
3227 let body = (|| {
3228 if replacement.trim().is_empty() {
3229 return None;
3230 }
3231 let (source, tree, original_offsets) =
3232 Self::parse_macro_replacement_body(replacement, parameters)?;
3233 let body = ParsedReplacementBody {
3234 source,
3235 tree,
3236 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3237 parameters: parameters.to_vec(),
3238 original_offsets,
3239 };
3240 body.statements()?;
3241 if body.expands_variadic_arguments() {
3242 return None;
3243 }
3244 Some(Arc::new(body))
3245 })();
3246 self.macro_replacement_bodies
3247 .lock()
3248 .expect("C++ macro replacement body cache poisoned")
3249 .insert(key.clone(), body.clone());
3250 body
3251 }
3252
3253 fn parse_macro_replacement_body(
3261 replacement: &str,
3262 parameters: &[String],
3263 ) -> Option<(String, Tree, MacroOriginMap)> {
3264 let parsed = parse_sentinel(
3265 MACRO_BODY_SENTINEL_PREFIX,
3266 SentinelInput::replacement(replacement),
3267 MACRO_BODY_SENTINEL_SUFFIX,
3268 );
3269 let normalized_end = parsed.source.len() - MACRO_BODY_SENTINEL_SUFFIX.len() - 1;
3270 let normalized = &parsed.source[MACRO_BODY_SENTINEL_PREFIX.len()..normalized_end];
3271 let normalized_offsets = &parsed.original_offsets;
3272 let source = parsed.source.as_str();
3273 let tree = &parsed.tree;
3274 if !tree.root_node().has_error() {
3275 return Some((
3276 parsed.source,
3277 parsed.tree,
3278 parsed.original_offsets.into_boxed_slice(),
3279 ));
3280 }
3281
3282 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3283 let mut insertion_points = Vec::new();
3284 let mut stack = vec![tree.root_node()];
3285 while let Some(node) = stack.pop() {
3286 if matches!(
3287 node.kind(),
3288 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3289 ) && parameters
3290 .iter()
3291 .any(|parameter| parameter.as_str() == node_text(node, source))
3292 && Self::macro_formal_needs_statement_separator(node)
3293 {
3294 let point = node.end_byte().saturating_sub(body_offset);
3295 if point <= normalized.len() && !insertion_points.contains(&point) {
3296 insertion_points.push(point);
3297 }
3298 }
3299 push_named_children_reversed(node, &mut stack);
3300 }
3301 if insertion_points.is_empty() {
3302 return None;
3303 }
3304 insertion_points.sort_unstable();
3305 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3306 let mut original_offsets =
3307 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3308 let mut next_insertion = 0;
3309 for (index, byte) in normalized.bytes().enumerate() {
3310 recovered.push(byte);
3311 original_offsets.push(normalized_offsets[index].clone());
3312 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3313 recovered.push(b';');
3314 let boundary = normalized_offsets[index].end;
3315 original_offsets.push(boundary..boundary);
3316 next_insertion += 1;
3317 }
3318 }
3319 original_offsets.push(replacement.len()..replacement.len());
3320 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3321 let parsed = parse_sentinel(
3322 MACRO_BODY_SENTINEL_PREFIX,
3323 SentinelInput {
3324 source: recovered,
3325 original_offsets,
3326 },
3327 MACRO_BODY_SENTINEL_SUFFIX,
3328 );
3329 let (source, tree, original_offsets) =
3330 (parsed.source, parsed.tree, parsed.original_offsets);
3331 if tree.root_node().has_error() {
3332 return None;
3333 }
3334 Some((source, tree, original_offsets.into_boxed_slice()))
3335 }
3336
3337 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3342 let mut current = node;
3343 let mut crossed_recovery = false;
3344 while let Some(parent) = current.parent() {
3345 if parent.is_error() {
3346 crossed_recovery = true;
3347 current = parent;
3348 continue;
3349 }
3350 if matches!(
3351 parent.kind(),
3352 "call_expression"
3353 | "argument_list"
3354 | "field_expression"
3355 | "binary_expression"
3356 | "unary_expression"
3357 | "assignment_expression"
3358 | "conditional_expression"
3359 | "parenthesized_expression"
3360 | "subscript_expression"
3361 ) {
3362 return false;
3363 }
3364 if parent.kind() == "expression_statement" {
3365 return single_named_child_matching(parent, |child| !child.is_extra()).is_some();
3366 }
3367 if parent.kind() == "compound_statement" && current == node {
3368 return true;
3369 }
3370 if matches!(
3371 parent.kind(),
3372 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3373 ) {
3374 return crossed_recovery
3375 || parent.child_by_field_name("consequence") == Some(current);
3376 }
3377 if matches!(parent.kind(), "declaration" | "init_declarator") {
3378 return false;
3379 }
3380 current = parent;
3381 }
3382 false
3383 }
3384
3385 fn macro_type_parameter_indices(
3393 &self,
3394 key: &(ProjectFile, usize),
3395 parameters: &[String],
3396 replacement: &str,
3397 ) -> Option<Arc<[usize]>> {
3398 if let Some(indices) = self
3399 .macro_type_parameters
3400 .lock()
3401 .expect("C++ macro type-parameter cache poisoned")
3402 .get(key)
3403 {
3404 return indices.clone();
3405 }
3406 #[cfg(any(test, feature = "test-support"))]
3407 self.macro_replacement_parse_count
3408 .fetch_add(1, Ordering::Relaxed);
3409 let indices = (|| {
3410 if replacement.trim().is_empty() {
3411 return None;
3412 }
3413 let parsed = parse_sentinel(
3417 MACRO_BODY_SENTINEL_PREFIX,
3418 SentinelInput::replacement(replacement),
3419 MACRO_BODY_SENTINEL_SUFFIX,
3420 );
3421 let (source, tree, original_offsets) =
3422 (parsed.source, parsed.tree, parsed.original_offsets);
3423 let body = ParsedReplacementBody {
3424 source,
3425 tree,
3426 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3427 parameters: parameters.to_vec(),
3428 original_offsets: original_offsets.into_boxed_slice(),
3429 };
3430 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3431 })();
3432 self.macro_type_parameters
3433 .lock()
3434 .expect("C++ macro type-parameter cache poisoned")
3435 .insert(key.clone(), indices.clone());
3436 indices
3437 }
3438
3439 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3440 let replacement = if node.kind() == "preproc_function_def" {
3441 function_macro_replacement_span(node, source)
3442 .and_then(|span| source.get(span))
3443 .map(str::to_owned)
3444 .or_else(|| {
3445 node.child_by_field_name("value")
3446 .map(|value| node_text(value, source).to_string())
3447 })
3448 .unwrap_or_default()
3449 } else {
3450 node.child_by_field_name("value")
3451 .map(|value| node_text(value, source).to_string())
3452 .unwrap_or_default()
3453 };
3454 if node.kind() == "preproc_def" {
3455 return MacroDefinition::Object { replacement };
3456 }
3457 let Some(parameters) = node.child_by_field_name("parameters") else {
3458 return MacroDefinition::Unsupported;
3459 };
3460 let variadic = (0..parameters.child_count()).any(|index| {
3461 parameters
3462 .child(index)
3463 .is_some_and(|child| child.kind() == "...")
3464 });
3465 let parameters = (0..parameters.named_child_count())
3466 .filter_map(|index| parameters.named_child(index))
3467 .map(|parameter| node_text(parameter, source).to_string())
3468 .collect::<Vec<_>>();
3469 if variadic {
3470 MacroDefinition::VariadicFunction {
3471 parameters,
3472 replacement,
3473 }
3474 } else {
3475 MacroDefinition::Function {
3476 parameters,
3477 replacement,
3478 }
3479 }
3480 }
3481
3482 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3483 clone_or_default_file_cell(
3484 &self.macro_event_cells,
3485 file,
3486 "C++ macro event cache poisoned",
3487 )
3488 }
3489
3490 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3491 if let Some(names) = self
3492 .macro_event_name_sets
3493 .lock()
3494 .expect("C++ macro event-name cache poisoned")
3495 .get(file)
3496 .cloned()
3497 {
3498 return names.contains(name);
3499 }
3500 let cell = self.macro_event_cell(file);
3501 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3502 let names = Arc::new(
3503 events
3504 .iter()
3505 .filter_map(|event| match event {
3506 MacroEvent::Define { name, .. } => Some(name.clone()),
3507 MacroEvent::Undef { .. }
3508 | MacroEvent::Include { .. }
3509 | MacroEvent::Invalidate { .. } => None,
3510 })
3511 .collect(),
3512 );
3513 self.macro_event_name_sets
3514 .lock()
3515 .expect("C++ macro event-name cache poisoned")
3516 .insert(file.clone(), Arc::clone(&names));
3517 names.contains(name)
3518 }
3519
3520 fn macro_environment_checkpoint_cell(
3521 &self,
3522 file: &ProjectFile,
3523 ) -> MacroEnvironmentCheckpointCell {
3524 clone_or_default_file_cell(
3525 &self.macro_environment_checkpoints,
3526 file,
3527 "C++ macro environment checkpoint cache poisoned",
3528 )
3529 }
3530
3531 pub fn macro_environment(
3533 &self,
3534 file: &ProjectFile,
3535 before_byte: usize,
3536 ) -> Arc<MacroEnvironment> {
3537 #[cfg(any(test, feature = "test-support"))]
3538 self.macro_environment_request_count
3539 .fetch_add(1, Ordering::Relaxed);
3540 let cell = self.macro_event_cell(file);
3541 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3542 let frontier = events.partition_point(|event| event.byte() < before_byte);
3543 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3544 let checkpoints =
3545 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3546 let checkpoint = checkpoints.at_or_before(frontier);
3547 if checkpoint.frontier == frontier {
3548 return Arc::clone(&checkpoint.environment);
3549 }
3550 #[cfg(any(test, feature = "test-support"))]
3551 self.macro_environment_copy_count
3552 .fetch_add(1, Ordering::Relaxed);
3553 let mut environment = checkpoint.environment.as_ref().clone();
3554 let mut include_stack = HashSet::from_iter([file.clone()]);
3555 for event in &events[checkpoint.frontier..frontier] {
3556 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3557 }
3558 Arc::new(environment)
3559 }
3560
3561 fn build_macro_environment_checkpoints(
3564 &self,
3565 file: &ProjectFile,
3566 events: &[MacroEvent],
3567 ) -> MacroEnvironmentCheckpoints {
3568 #[cfg(any(test, feature = "test-support"))]
3569 self.macro_environment_checkpoint_build_count
3570 .fetch_add(1, Ordering::Relaxed);
3571 let mut environment = MacroEnvironment {
3576 build_proven_defines: self
3577 .compile_proven_guards(file)
3578 .iter()
3579 .filter_map(|guard| match guard {
3580 PreprocessorGuard::Defined(name) => Some(name.clone()),
3581 _ => None,
3582 })
3583 .collect(),
3584 ..MacroEnvironment::default()
3585 };
3586 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3587 frontier: 0,
3588 environment: Arc::new(environment.clone()),
3589 }];
3590 let checkpoint_stride = events
3597 .len()
3598 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3599 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3600 let mut include_stack = HashSet::from_iter([file.clone()]);
3601 for (index, event) in events.iter().enumerate() {
3602 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3603 let frontier = index + 1;
3604 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3605 checkpoints.push(MacroEnvironmentCheckpoint {
3606 frontier,
3607 environment: Arc::new(environment.clone()),
3608 });
3609 }
3610 }
3611 MacroEnvironmentCheckpoints { checkpoints }
3612 }
3613
3614 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3623 self.macro_environment(file, before_byte)
3624 .binding(name)
3625 .is_some()
3626 }
3627
3628 pub fn macro_name_may_be_bound_at(
3629 &self,
3630 file: &ProjectFile,
3631 name: &str,
3632 before_byte: usize,
3633 ) -> bool {
3634 self.macro_environment(file, before_byte).may_bind(name)
3635 }
3636
3637 pub fn macro_binding_matches_target_at(
3641 &self,
3642 analyzer: &CppGraphSource<'_>,
3643 file: &ProjectFile,
3644 name: &str,
3645 before_byte: usize,
3646 target: &CodeUnit,
3647 ) -> bool {
3648 let ranges = analyzer.ranges(target);
3649 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3650 self.macro_binding_matches_target_declaration_at(
3651 file,
3652 name,
3653 before_byte,
3654 target.source(),
3655 &declaration_bytes,
3656 )
3657 }
3658
3659 pub(crate) fn macro_declaration_bytes(
3664 &self,
3665 target: &CodeUnit,
3666 ranges: &[Range],
3667 ) -> Vec<usize> {
3668 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3669 return Vec::new();
3670 };
3671 ranges
3672 .iter()
3673 .filter_map(|range| {
3674 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3675 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3676 node = node.parent()?;
3677 }
3678 Some(node.start_byte())
3679 })
3680 .collect()
3681 }
3682
3683 pub(crate) fn macro_binding_matches_target_declaration_at(
3684 &self,
3685 file: &ProjectFile,
3686 name: &str,
3687 before_byte: usize,
3688 target_source: &ProjectFile,
3689 target_declaration_bytes: &[usize],
3690 ) -> bool {
3691 let environment = self.macro_environment(file, before_byte);
3692 let Some(binding) = environment.binding(name) else {
3693 return false;
3694 };
3695 if binding.definition == MacroDefinition::Unsupported {
3696 return false;
3697 }
3698 if binding.source != *target_source {
3702 return false;
3703 }
3704 target_declaration_bytes.contains(&binding.declaration_byte)
3705 }
3706
3707 pub fn resolve_ordinary_macro_reference(
3714 &self,
3715 analyzer: &CppGraphSource<'_>,
3716 file: &ProjectFile,
3717 node: Node<'_>,
3718 source: &str,
3719 ) -> OrdinaryMacroReferenceResolution {
3720 if !is_ordinary_macro_reference_node(node) {
3721 return OrdinaryMacroReferenceResolution::Missing;
3722 }
3723 let name = node_text(node, source);
3724 if name.is_empty() {
3725 return OrdinaryMacroReferenceResolution::Missing;
3726 }
3727 let visible = self
3728 .visible_identifier_candidates(file, name)
3729 .filter(|candidate| candidate.is_macro())
3730 .cloned()
3731 .collect::<Vec<_>>();
3732 let mut exact = Vec::new();
3733 for candidate in &visible {
3734 if self.macro_binding_matches_target_at(
3735 analyzer,
3736 file,
3737 name,
3738 node.start_byte(),
3739 candidate,
3740 ) && !exact
3741 .iter()
3742 .any(|existing| same_visible_symbol(existing, candidate))
3743 {
3744 exact.push(candidate.clone());
3745 }
3746 }
3747 match exact.len() {
3748 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3749 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3750 0 if !visible.is_empty()
3751 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3752 {
3753 OrdinaryMacroReferenceResolution::Ambiguous
3754 }
3755 0 => OrdinaryMacroReferenceResolution::Missing,
3756 }
3757 }
3758
3759 pub fn recovered_c_reference_ranges(
3767 &self,
3768 file: &ProjectFile,
3769 root: Node<'_>,
3770 source: &str,
3771 limit: usize,
3772 ) -> RecoveredCReferenceRanges {
3773 if !is_c_source_file(file) {
3774 return RecoveredCReferenceRanges::Complete(Vec::new());
3775 }
3776 let mut ranges = Vec::new();
3777 let mut seen = HashSet::default();
3778 let mut stack = vec![(root, root.is_error())];
3779 while let Some((node, inside_error)) = stack.pop() {
3780 let inside_error =
3785 inside_error || node.is_error() || node.kind() == "macro_type_specifier";
3786 if node.kind() == "preproc_arg" {
3787 let macro_value_kind = node.parent().and_then(|parent| {
3792 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3793 });
3794 if matches!(
3795 macro_value_kind,
3796 Some("preproc_def" | "preproc_function_def")
3797 ) {
3798 let name = node_text(node, source);
3799 if !name.is_empty()
3800 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3801 && !push_recovered_c_range(
3802 &mut ranges,
3803 &mut seen,
3804 node.start_byte(),
3805 node.end_byte(),
3806 node,
3807 limit,
3808 )
3809 {
3810 return RecoveredCReferenceRanges::LimitExceeded;
3811 }
3812 }
3813 if macro_value_kind == Some("preproc_def") {
3814 for reference in object_macro_replacement_type_references(node, source) {
3815 for range in reference.component_ranges {
3816 let visible = self
3817 .visible_identifier_candidates(file, &source[range.clone()])
3818 .any(|candidate| candidate.is_class() || candidate.is_module());
3819 if visible
3820 && !push_recovered_c_range(
3821 &mut ranges,
3822 &mut seen,
3823 range.start,
3824 range.end,
3825 node,
3826 limit,
3827 )
3828 {
3829 return RecoveredCReferenceRanges::LimitExceeded;
3830 }
3831 }
3832 }
3833 }
3834 }
3835 if inside_error
3836 && recovered_c_reference_node(self, file, node, source)
3837 && !push_recovered_c_range(
3838 &mut ranges,
3839 &mut seen,
3840 node.start_byte(),
3841 node.end_byte(),
3842 node,
3843 limit,
3844 )
3845 {
3846 return RecoveredCReferenceRanges::LimitExceeded;
3847 }
3848 let mut cursor = node.walk();
3849 for child in node.named_children(&mut cursor) {
3850 stack.push((child, inside_error));
3851 }
3852 }
3853 ranges.sort_unstable();
3854 RecoveredCReferenceRanges::Complete(ranges)
3855 }
3856
3857 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3863 self.visible_identifier_candidates(file, target.identifier())
3864 .filter(|candidate| candidate.is_macro())
3865 .any(|candidate| {
3866 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3867 })
3868 }
3869
3870 pub fn object_macro_replacement_at(
3871 &self,
3872 file: &ProjectFile,
3873 name: &str,
3874 before_byte: usize,
3875 ) -> Option<String> {
3876 let environment = self.macro_environment(file, before_byte);
3877 let binding = environment.binding(name)?;
3878 if !binding.exact {
3879 return None;
3880 }
3881 match &binding.definition {
3882 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3883 MacroDefinition::Function { .. }
3884 | MacroDefinition::VariadicFunction { .. }
3885 | MacroDefinition::Unsupported => None,
3886 }
3887 }
3888
3889 fn apply_macro_events(
3890 &self,
3891 file: &ProjectFile,
3892 environment: &mut MacroEnvironment,
3893 include_stack: &mut HashSet<ProjectFile>,
3894 ) {
3895 if !include_stack.insert(file.clone()) {
3896 return;
3897 }
3898 if self.cpp.prepared_syntax(self.token, file).is_none() {
3899 environment.mark_unknown_names(file, 0);
3900 include_stack.remove(file);
3901 return;
3902 }
3903 match self.macro_include_protection(file) {
3904 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3905 Some(binding) if binding.is_exact() => {
3906 include_stack.remove(file);
3907 return;
3908 }
3909 Some(_) | None if environment.unknown_names => {
3910 let mut ambiguous_seen = HashSet::default();
3911 self.mark_macro_events_ambiguous(
3912 file,
3913 environment,
3914 &mut ambiguous_seen,
3915 file,
3916 0,
3917 );
3918 include_stack.remove(file);
3919 return;
3920 }
3921 Some(_) => {
3922 let mut ambiguous_seen = HashSet::default();
3923 self.mark_macro_events_ambiguous(
3924 file,
3925 environment,
3926 &mut ambiguous_seen,
3927 file,
3928 0,
3929 );
3930 include_stack.remove(file);
3931 return;
3932 }
3933 None => {}
3934 },
3935 MacroIncludeProtection::PragmaOnce => {
3936 if !environment.applied_pragma_once_files.insert(file.clone()) {
3937 include_stack.remove(file);
3938 return;
3939 }
3940 if environment.maybe_applied_pragma_once_files.remove(file) {
3941 let mut ambiguous_seen = HashSet::default();
3946 environment.applied_pragma_once_files.remove(file);
3947 self.mark_macro_events_ambiguous(
3948 file,
3949 environment,
3950 &mut ambiguous_seen,
3951 file,
3952 0,
3953 );
3954 environment.maybe_applied_pragma_once_files.remove(file);
3955 environment.applied_pragma_once_files.insert(file.clone());
3956 include_stack.remove(file);
3957 return;
3958 }
3959 }
3960 MacroIncludeProtection::None => {}
3961 }
3962 let cell = self.macro_event_cell(file);
3963 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3964 for event in events {
3965 self.apply_macro_event(file, event, environment, include_stack);
3966 }
3967 include_stack.remove(file);
3968 }
3969
3970 fn apply_macro_event(
3971 &self,
3972 file: &ProjectFile,
3973 event: &MacroEvent,
3974 environment: &mut MacroEnvironment,
3975 include_stack: &mut HashSet<ProjectFile>,
3976 ) {
3977 #[cfg(any(test, feature = "test-support"))]
3978 self.macro_event_application_count
3979 .fetch_add(1, Ordering::Relaxed);
3980 match event {
3981 MacroEvent::Define {
3982 name,
3983 binding,
3984 conditionals,
3985 byte,
3986 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3987 Some(true) => environment.insert(name.clone(), binding.clone()),
3988 Some(false) => {}
3989 None => Self::merge_conditional_macro_definition(
3990 environment,
3991 name,
3992 binding,
3993 file,
3994 *byte,
3995 ),
3996 },
3997 MacroEvent::Undef {
3998 name,
3999 conditionals,
4000 byte,
4001 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
4002 Some(true) => environment.remove(name),
4003 Some(false) => {}
4004 None => {
4005 if environment.binding(name).is_some() {
4006 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
4007 }
4008 }
4009 },
4010 MacroEvent::Include {
4011 targets,
4012 conditionals,
4013 byte,
4014 } => {
4015 let condition =
4016 self.macro_event_condition_value(file, *byte, environment, conditionals);
4017 if condition == Some(false) {
4018 return;
4019 }
4020 if targets.is_empty() {
4021 environment.mark_unknown_names(file, *byte);
4022 return;
4023 }
4024 if condition.is_none() || targets.len() > 1 {
4025 let mut ambiguous_seen = HashSet::default();
4026 for target in targets {
4027 self.mark_macro_events_ambiguous(
4028 target,
4029 environment,
4030 &mut ambiguous_seen,
4031 file,
4032 *byte,
4033 );
4034 }
4035 } else if let Some(target) = targets.first() {
4036 self.apply_macro_events(target, environment, include_stack);
4037 }
4038 }
4039 MacroEvent::Invalidate { byte } => {
4040 for binding in environment.bindings.values_mut() {
4041 *binding = MacroBinding::uncertain_from(binding, file, *byte);
4042 }
4043 }
4044 }
4045 }
4046
4047 fn macro_event_condition_value(
4058 &self,
4059 file: &ProjectFile,
4060 event_byte: usize,
4061 environment: &MacroEnvironment,
4062 conditionals: &OwningPreprocessorConditionals,
4063 ) -> Option<bool> {
4064 if conditionals.is_empty() {
4065 return Some(true);
4066 }
4067 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4068 let source = prepared.source();
4069 let root = prepared.tree().root_node();
4070 let descendant = root.descendant_for_byte_range(
4071 event_byte,
4072 event_byte.saturating_add(1).min(source.len()),
4073 )?;
4074 let mut unknown = false;
4075 let mut current = descendant.parent();
4076 while let Some(conditional) = current {
4077 if matches!(
4078 conditional.kind(),
4079 "preproc_if" | "preproc_ifdef" | "preproc_elif"
4080 ) && conditionals.contains(&conditional.start_byte())
4081 {
4082 let mut value = match conditional.kind() {
4083 "preproc_ifdef" => {
4084 let name = conditional.child_by_field_name("name")?;
4085 let defined =
4086 self.macro_name_defined_value(environment, node_text(name, source));
4087 match conditional.child(0)?.kind() {
4088 "#ifdef" => defined,
4089 "#ifndef" => defined.map(|defined| !defined),
4090 _ => None,
4091 }
4092 }
4093 "preproc_if" | "preproc_elif" => conditional
4094 .child_by_field_name("condition")
4095 .and_then(|condition| {
4096 self.preprocessor_integer_value(
4097 condition,
4098 source,
4099 environment,
4100 &mut Vec::new(),
4101 0,
4102 )
4103 })
4104 .map(|value| value != 0),
4105 _ => unreachable!(),
4106 };
4107 match preprocessor_branch_of_descendant(
4108 conditional,
4109 descendant,
4110 &mut PreprocessorContextCache::default(),
4111 ) {
4112 PreprocessorBranch::Consequence => {}
4113 PreprocessorBranch::Structured(_) | PreprocessorBranch::DisplacedElse => {
4114 value = value.map(|value| !value);
4115 }
4116 PreprocessorBranch::Unreadable => value = None,
4117 }
4118 match value {
4119 Some(true) => {}
4120 Some(false) => return Some(false),
4121 None => unknown = true,
4122 }
4123 }
4124 current = conditional.parent();
4125 }
4126 (!unknown).then_some(true)
4127 }
4128
4129 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4130 if environment.known_undefined_names.contains(name) {
4131 return Some(false);
4132 }
4133 if let Some(binding) = environment.binding(name) {
4134 return binding.is_exact().then_some(true);
4135 }
4136 environment
4137 .build_proven_defines
4138 .contains(name)
4139 .then_some(true)
4140 }
4141
4142 fn preprocessor_integer_value(
4143 &self,
4144 expression: Node<'_>,
4145 source: &str,
4146 environment: &MacroEnvironment,
4147 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4148 depth: usize,
4149 ) -> Option<i128> {
4150 if depth >= 64 {
4153 return None;
4154 }
4155 match expression.kind() {
4156 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4157 "identifier" | "type_identifier" => {
4158 let binding = environment.binding(node_text(expression, source))?;
4159 if !binding.is_exact() {
4160 return None;
4161 }
4162 let MacroDefinition::Object { replacement } = &binding.definition else {
4163 return None;
4164 };
4165 let identity = (binding.source.clone(), binding.declaration_byte);
4166 if expansion_stack.contains(&identity) {
4167 return None;
4168 }
4169 expansion_stack.push(identity);
4170 let parsed = self.parsed_macro_replacement(binding, replacement);
4171 let value = match parsed.as_ref() {
4172 ParsedMacroReplacement::Parsed {
4173 source: replacement_source,
4174 tree,
4175 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4176 .and_then(|call| call.child_by_field_name("arguments"))
4177 .and_then(|arguments| argument_children(arguments).next())
4178 .and_then(|argument| {
4179 self.preprocessor_integer_value(
4180 argument,
4181 replacement_source,
4182 environment,
4183 expansion_stack,
4184 depth + 1,
4185 )
4186 }),
4187 ParsedMacroReplacement::Unsupported => None,
4188 };
4189 expansion_stack.pop();
4190 value
4191 }
4192 "preproc_defined" => {
4193 let mut cursor = expression.walk();
4194 let name = expression
4195 .named_children(&mut cursor)
4196 .find(|child| child.kind() == "identifier")?;
4197 self.macro_name_defined_value(environment, node_text(name, source))
4198 .map(i128::from)
4199 }
4200 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4201 self.preprocessor_integer_value(
4202 child,
4203 source,
4204 environment,
4205 expansion_stack,
4206 depth + 1,
4207 )
4208 }),
4209 "unary_expression" => {
4210 let operator = expression.child_by_field_name("operator")?.kind();
4211 let argument = expression.child_by_field_name("argument")?;
4212 let value = self.preprocessor_integer_value(
4213 argument,
4214 source,
4215 environment,
4216 expansion_stack,
4217 depth + 1,
4218 )?;
4219 match operator {
4220 "+" => Some(value),
4221 "-" => value.checked_neg(),
4222 "!" => Some(i128::from(value == 0)),
4223 "~" => Some(!value),
4224 _ => None,
4225 }
4226 }
4227 "binary_expression" => {
4228 let left = self.preprocessor_integer_value(
4229 expression.child_by_field_name("left")?,
4230 source,
4231 environment,
4232 expansion_stack,
4233 depth + 1,
4234 )?;
4235 let right = self.preprocessor_integer_value(
4236 expression.child_by_field_name("right")?,
4237 source,
4238 environment,
4239 expansion_stack,
4240 depth + 1,
4241 )?;
4242 match expression.child_by_field_name("operator")?.kind() {
4243 "+" => left.checked_add(right),
4244 "-" => left.checked_sub(right),
4245 "*" => left.checked_mul(right),
4246 "/" => left.checked_div(right),
4247 "%" => left.checked_rem(right),
4248 "<<" => u32::try_from(right)
4249 .ok()
4250 .and_then(|shift| left.checked_shl(shift)),
4251 ">>" => u32::try_from(right)
4252 .ok()
4253 .and_then(|shift| left.checked_shr(shift)),
4254 "<" => Some(i128::from(left < right)),
4255 "<=" => Some(i128::from(left <= right)),
4256 ">" => Some(i128::from(left > right)),
4257 ">=" => Some(i128::from(left >= right)),
4258 "==" => Some(i128::from(left == right)),
4259 "!=" => Some(i128::from(left != right)),
4260 "&" => Some(left & right),
4261 "|" => Some(left | right),
4262 "^" => Some(left ^ right),
4263 "&&" => Some(i128::from(left != 0 && right != 0)),
4264 "||" => Some(i128::from(left != 0 || right != 0)),
4265 _ => None,
4266 }
4267 }
4268 _ => None,
4269 }
4270 }
4271
4272 fn mark_macro_events_ambiguous(
4273 &self,
4274 file: &ProjectFile,
4275 environment: &mut MacroEnvironment,
4276 include_stack: &mut HashSet<ProjectFile>,
4277 conditional_file: &ProjectFile,
4278 conditional_byte: usize,
4279 ) {
4280 if !include_stack.insert(file.clone()) {
4281 return;
4282 }
4283 if self.cpp.prepared_syntax(self.token, file).is_none() {
4284 environment.mark_unknown_names(conditional_file, conditional_byte);
4285 return;
4286 }
4287 match self.macro_include_protection(file) {
4288 MacroIncludeProtection::MacroGuard(guard) => {
4289 if environment
4290 .binding(&guard)
4291 .is_some_and(MacroBinding::is_exact)
4292 {
4293 return;
4294 }
4295 }
4296 MacroIncludeProtection::PragmaOnce => {
4297 if environment.applied_pragma_once_files.contains(file) {
4298 return;
4299 }
4300 environment
4301 .maybe_applied_pragma_once_files
4302 .insert(file.clone());
4303 }
4304 MacroIncludeProtection::None => {}
4305 }
4306 let cell = self.macro_event_cell(file);
4307 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4308 for event in events {
4309 #[cfg(any(test, feature = "test-support"))]
4310 self.macro_event_application_count
4311 .fetch_add(1, Ordering::Relaxed);
4312 match event {
4313 MacroEvent::Define { name, binding, .. } => {
4314 Self::merge_conditional_macro_definition(
4315 environment,
4316 name,
4317 binding,
4318 conditional_file,
4319 conditional_byte,
4320 );
4321 }
4322 MacroEvent::Undef { name, .. } => {
4323 if environment.binding(name).is_some() {
4324 environment.insert(
4325 name.clone(),
4326 MacroBinding::ambiguous(conditional_file, conditional_byte),
4327 );
4328 } else {
4329 environment.remove_known_undefined(name);
4330 }
4331 }
4332 MacroEvent::Include { targets, .. } => {
4333 if targets.is_empty() {
4334 environment.mark_unknown_names(conditional_file, conditional_byte);
4335 continue;
4336 }
4337 for target in targets {
4338 self.mark_macro_events_ambiguous(
4339 target,
4340 environment,
4341 include_stack,
4342 conditional_file,
4343 conditional_byte,
4344 );
4345 }
4346 }
4347 MacroEvent::Invalidate { .. } => {
4348 for binding in environment.bindings.values_mut() {
4349 *binding = MacroBinding::uncertain_from(
4350 binding,
4351 conditional_file,
4352 conditional_byte,
4353 );
4354 }
4355 }
4356 }
4357 }
4358 }
4359
4360 fn merge_conditional_macro_definition(
4361 environment: &mut MacroEnvironment,
4362 name: &str,
4363 possible_binding: &MacroBinding,
4364 conditional_file: &ProjectFile,
4365 conditional_byte: usize,
4366 ) {
4367 if environment.binding(name).is_some_and(|current| {
4372 current.definition != MacroDefinition::Unsupported
4373 && current.definition == possible_binding.definition
4374 }) {
4375 return;
4376 }
4377 environment.insert(
4378 name.to_string(),
4379 MacroBinding::ambiguous(conditional_file, conditional_byte),
4380 );
4381 }
4382
4383 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4384 let cell = self
4385 .macro_include_protection_cells
4386 .lock()
4387 .expect("C++ include protection cache poisoned")
4388 .entry(file.clone())
4389 .or_default()
4390 .clone();
4391 cell.get_or_init(|| {
4392 self.cpp.prepared_syntax(self.token, file).map_or(
4393 MacroIncludeProtection::None,
4394 |prepared| {
4395 top_level_macro_include_protection(
4396 prepared.tree().root_node(),
4397 prepared.source(),
4398 )
4399 },
4400 )
4401 })
4402 .clone()
4403 }
4404
4405 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4406 collect_macro_events(self.cpp, self.token, file)
4407 }
4408
4409 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4410 self.ordinary_type_import_cells
4411 .lock()
4412 .expect("C++ ordinary type import cache poisoned")
4413 .entry(file.clone())
4414 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4415 .clone()
4416 }
4417
4418 pub fn project_using_index(
4419 &self,
4420 build: impl FnOnce() -> ProjectUsingIndex,
4421 ) -> &ProjectUsingIndex {
4422 self.project_using_index.get_or_init(build)
4423 }
4424
4425 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4426 let mut files = self
4427 .visible_source_files_by_root
4428 .values()
4429 .flatten()
4430 .cloned()
4431 .collect::<HashSet<_>>()
4432 .into_iter()
4433 .collect::<Vec<_>>();
4434 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4435 files
4436 }
4437
4438 pub(super) fn namespace_alias_cell(
4440 &self,
4441 root: &ProjectFile,
4442 ) -> super::namespace_alias::NamespaceAliasCell {
4443 self.namespace_alias_sites
4444 .lock()
4445 .expect("C++ namespace alias cache poisoned")
4446 .entry(root.clone())
4447 .or_default()
4448 .clone()
4449 }
4450
4451 pub(super) fn visible_source_files(&self, root: &ProjectFile) -> Vec<ProjectFile> {
4453 self.visible_source_files_by_root.get(root).map_or_else(
4454 || vec![root.clone()],
4455 |files| {
4456 files
4457 .iter()
4458 .chain([root])
4459 .cloned()
4460 .collect::<HashSet<_>>()
4461 .into_iter()
4462 .collect()
4463 },
4464 )
4465 }
4466
4467 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4468 self.visible_source_files_by_root
4469 .get(root)
4470 .is_some_and(|files| files.contains(source))
4471 }
4472
4473 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4474 let cached = self
4475 .visible_parser_alias_name_sets
4476 .read()
4477 .expect("visible parser alias-name cache poisoned")
4478 .get(file)
4479 .cloned();
4480 let cell = if let Some(cached) = cached {
4481 cached
4482 } else {
4483 let mut cells = self
4484 .visible_parser_alias_name_sets
4485 .write()
4486 .expect("visible parser alias-name cache poisoned");
4487 Arc::clone(
4488 cells
4489 .entry(file.clone())
4490 .or_insert_with(|| Arc::new(OnceLock::new())),
4491 )
4492 };
4493 if let Some(names) = cell.get() {
4494 return names.contains(name);
4495 }
4496 let mut names = HashSet::default();
4497 let visible_files = self
4498 .visible_source_files_by_root
4499 .get(file)
4500 .cloned()
4501 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4502 for visible_file in visible_files {
4503 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4504 return false;
4505 };
4506 for alias in aliases.get().expect("published canonical aliases").iter() {
4507 names.insert(alias.name.clone());
4508 }
4509 }
4510 #[cfg(any(test, feature = "test-support"))]
4511 self.visible_parser_alias_name_set_build_count
4512 .fetch_add(1, Ordering::Relaxed);
4513 cell.get_or_init(|| names).contains(name)
4514 }
4515
4516 pub fn parser_alias_name_may_resolve_to_target(
4517 &self,
4518 file: &ProjectFile,
4519 alias_name: &str,
4520 target: &CodeUnit,
4521 ) -> bool {
4522 let started = std::time::Instant::now();
4523 self.parser_alias_fallback_calls
4524 .fetch_add(1, Ordering::Relaxed);
4525 let key = (
4526 file.clone(),
4527 alias_name.to_string(),
4528 logical_symbol_key(target),
4529 );
4530 let cached = self
4531 .parser_alias_target_matches
4532 .read()
4533 .expect("parser alias target-match cache poisoned")
4534 .get(&key)
4535 .cloned();
4536 let cell = if let Some(cached) = cached {
4537 cached
4538 } else {
4539 let mut cells = self
4540 .parser_alias_target_matches
4541 .write()
4542 .expect("parser alias target-match cache poisoned");
4543 Arc::clone(
4544 cells
4545 .entry(key)
4546 .or_insert_with(|| Arc::new(OnceLock::new())),
4547 )
4548 };
4549 let matched = *cell.get_or_init(|| {
4550 any_visible_source_file_matches(
4551 &self.visible_source_files_by_root,
4552 file,
4553 |visible_file| {
4554 self.parser_alias_fallback_files
4555 .fetch_add(1, Ordering::Relaxed);
4556 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4557 },
4558 )
4559 });
4560 self.parser_alias_fallback_elapsed_micros.fetch_add(
4561 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4562 Ordering::Relaxed,
4563 );
4564 matched
4565 }
4566
4567 fn callable_arities_for_target(
4568 &self,
4569 analyzer: &CppGraphSource<'_>,
4570 cpp: &dyn CppSource,
4571 file: &ProjectFile,
4572 prepared: &PreparedSyntaxTree,
4573 spec: &TargetSpec,
4574 ) -> Vec<ActivatedCallableArity> {
4575 let Some(signature) = spec.target.signature() else {
4576 return Vec::new();
4577 };
4578 let Some(candidates) = self
4579 .visible_by_identifier
4580 .get(file)
4581 .and_then(|by_name| by_name.get(&spec.member_name))
4582 else {
4583 return Vec::new();
4584 };
4585 let differing_candidates = candidates
4586 .iter()
4587 .filter(|candidate| {
4588 candidate.is_function()
4589 && candidate.fq_name() == spec.target.fq_name()
4590 && candidate.signature() == Some(signature)
4591 })
4592 .filter_map(|candidate| {
4593 analyzer
4594 .signature_metadata(candidate)
4595 .into_iter()
4596 .find_map(|metadata| metadata.callable_arity())
4597 .filter(|arity| Some(*arity) != spec.callable_arity)
4598 .map(|arity| (candidate, arity))
4599 })
4600 .collect::<Vec<_>>();
4601 if differing_candidates.is_empty() {
4602 return Vec::new();
4603 }
4604 let mut arities = Vec::with_capacity(differing_candidates.len());
4605 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
4608 self.effective_reference_guards(file, guards)
4609 };
4610 let reference = CallableReferenceContext {
4611 effective_guards: &effective_guards,
4612 file_guards: OnceCell::new(),
4613 file,
4614 position: None,
4615 };
4616 for (candidate, candidate_arity) in differing_candidates {
4617 let declaration_activation =
4618 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4619 let Some(declaration_activation) = declaration_activation else {
4620 continue;
4621 };
4622 let activation_byte = if candidate.source() == file {
4623 Some(declaration_activation)
4624 } else {
4625 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4626 };
4627 if let Some(activation_byte) = activation_byte {
4628 arities.push(ActivatedCallableArity {
4629 activation_byte,
4630 arity: candidate_arity,
4631 });
4632 }
4633 }
4634 arities
4635 }
4636
4637 fn callable_parameter_macro_arity(
4638 &self,
4639 target: &CodeUnit,
4640 parameter_types: Option<&[String]>,
4641 ) -> Option<CallableArity> {
4642 let parameter_types = parameter_types?;
4643 let [macro_name] = parameter_types else {
4644 return None;
4645 };
4646 if macro_name.is_empty()
4647 || !macro_name
4648 .chars()
4649 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4650 {
4651 return None;
4652 }
4653 let cache_key = (target.source().clone(), macro_name.clone());
4654 if let Some(cached) = self
4655 .callable_parameter_macro_arities
4656 .lock()
4657 .expect("C++ callable parameter-macro arity cache poisoned")
4658 .get(&cache_key)
4659 .copied()
4660 {
4661 return cached;
4662 }
4663 let mut visible_files = HashSet::default();
4664 collect_include_closure(
4665 &self.cpp_source(),
4666 self.cpp.include_target_index(),
4667 target.source(),
4668 &mut visible_files,
4669 None,
4670 );
4671 let mut arities = Vec::new();
4672 for visible_file in visible_files {
4673 let cell = self.macro_event_cell(&visible_file);
4674 for event in
4675 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4676 {
4677 let MacroEvent::Define { name, binding, .. } = event else {
4678 continue;
4679 };
4680 if name != macro_name {
4681 continue;
4682 }
4683 let MacroDefinition::Object { replacement } = &binding.definition else {
4684 continue;
4685 };
4686 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4687 continue;
4688 };
4689 if !arities.contains(&arity) {
4690 arities.push(arity);
4691 }
4692 }
4693 }
4694 let resolved = (|| {
4695 let required = arities
4696 .iter()
4697 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4698 .min()?;
4699 let total = arities.iter().map(|arity| arity.total()).max()?;
4700 let repeated = arities
4701 .iter()
4702 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4703 Some(CallableArity::new(required, total, repeated))
4708 })();
4709 self.callable_parameter_macro_arities
4710 .lock()
4711 .expect("C++ callable parameter-macro arity cache poisoned")
4712 .insert(cache_key, resolved);
4713 resolved
4714 }
4715
4716 pub fn include_activation_for_source(
4717 &self,
4718 cpp: &dyn CppSource,
4719 file: &ProjectFile,
4720 prepared: &PreparedSyntaxTree,
4721 donor_source: &ProjectFile,
4722 ) -> Option<usize> {
4723 let key = (file.clone(), donor_source.clone());
4724 if let Some(cached) = self
4725 .include_activation_cells
4726 .lock()
4727 .expect("C++ include activation cache poisoned")
4728 .get(&key)
4729 .copied()
4730 {
4731 return cached;
4732 }
4733 #[cfg(any(test, feature = "test-support"))]
4734 self.include_activation_build_count
4735 .fetch_add(1, Ordering::Relaxed);
4736 let effective = OnceCell::new();
4737 let activation =
4738 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4739 effective
4740 .get_or_init(|| {
4741 self.effective_reference_guards(file, Some(&HashSet::default()))
4742 })
4743 .as_ref()
4744 });
4745 let mut cells = self
4746 .include_activation_cells
4747 .lock()
4748 .expect("C++ include activation cache poisoned");
4749 *cells.entry(key).or_insert(activation)
4750 }
4751
4752 pub fn conditional_include_projections_for_source(
4753 &self,
4754 file: &ProjectFile,
4755 prepared: &PreparedSyntaxTree,
4756 donor_source: &ProjectFile,
4757 ) -> Arc<[ConditionalIncludeProjection]> {
4758 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4759 let cell = self
4760 .conditional_include_projection_cells
4761 .lock()
4762 .expect("C++ conditional include projection cache poisoned")
4763 .entry(file.clone())
4764 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4765 .clone();
4766 let index = cell.get_or_build_pool_independent(|| {
4767 #[cfg(any(test, feature = "test-support"))]
4768 self.conditional_include_projection_index_build_count
4769 .fetch_add(1, Ordering::Relaxed);
4770 let effective = OnceCell::new();
4771 find_conditional_include_projection_index(
4772 self.cpp,
4773 self.token,
4774 file,
4775 prepared,
4776 &|| {
4777 effective
4778 .get_or_init(|| {
4779 self.effective_reference_guards(file, Some(&HashSet::default()))
4780 })
4781 .as_ref()
4782 },
4783 &|| {
4784 #[cfg(any(test, feature = "test-support"))]
4785 self.conditional_include_projection_state_count
4786 .fetch_add(1, Ordering::Relaxed);
4787 },
4788 )
4789 });
4790 index
4791 .get(donor_source)
4792 .cloned()
4793 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4794 }
4795
4796 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4797 let mut macro_events = |file: &ProjectFile| {
4798 let cell = self.macro_event_cell(file);
4799 cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4800 .clone()
4801 };
4802 include_language_guard_is_stable_with(
4803 self.cpp,
4804 self.token,
4805 file,
4806 &mut macro_events,
4807 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4808 self.structured_include_facts(file, prepared)
4809 },
4810 )
4811 }
4812
4813 #[cfg(any(test, feature = "test-support"))]
4814 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4815 (
4816 self.conditional_include_projection_index_build_count
4817 .load(Ordering::Relaxed),
4818 self.conditional_include_projection_state_count
4819 .load(Ordering::Relaxed),
4820 )
4821 }
4822
4823 #[cfg(any(test, feature = "test-support"))]
4824 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4825 self.conditional_include_target_state_count
4826 .load(Ordering::Relaxed)
4827 }
4828
4829 #[cfg(any(test, feature = "test-support"))]
4830 pub fn include_activation_build_count_for_test(&self) -> usize {
4831 self.include_activation_build_count.load(Ordering::Relaxed)
4832 }
4833
4834 #[cfg(any(test, feature = "test-support"))]
4835 pub fn note_using_donor_activation_for_test(&self) {
4836 self.using_donor_activation_count
4837 .fetch_add(1, Ordering::Relaxed);
4838 }
4839
4840 #[cfg(not(any(test, feature = "test-support")))]
4841 pub fn note_using_donor_activation_for_test(&self) {}
4842
4843 #[cfg(any(test, feature = "test-support"))]
4844 pub fn note_using_namespace_lookup_for_test(&self) {
4845 self.using_namespace_lookup_count
4846 .fetch_add(1, Ordering::Relaxed);
4847 }
4848
4849 #[cfg(not(any(test, feature = "test-support")))]
4850 pub fn note_using_namespace_lookup_for_test(&self) {}
4851
4852 #[cfg(any(test, feature = "test-support"))]
4853 pub fn note_using_name_candidate_inspection_for_test(&self) {
4854 self.using_name_candidate_inspection_count
4855 .fetch_add(1, Ordering::Relaxed);
4856 }
4857
4858 #[cfg(not(any(test, feature = "test-support")))]
4859 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4860
4861 #[cfg(any(test, feature = "test-support"))]
4862 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4863 (
4864 self.using_donor_activation_count.load(Ordering::Relaxed),
4865 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4866 self.callable_reference_spec_build_count
4867 .load(Ordering::Relaxed),
4868 self.using_name_candidate_inspection_count
4869 .load(Ordering::Relaxed),
4870 )
4871 }
4872
4873 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4874 file == target.source()
4875 || self
4876 .visible_by_file
4877 .get(file)
4878 .is_some_and(|visible| visible.contains(target))
4879 }
4880
4881 pub fn declaration_visible_at(
4893 &self,
4894 analyzer: &CppGraphSource<'_>,
4895 file: &ProjectFile,
4896 declaration: &CodeUnit,
4897 reference_byte: usize,
4898 ) -> bool {
4899 let reference_guards = OnceCell::new();
4900 if declaration.is_callable() && !callable_target_has_definition_body(analyzer, declaration)
4903 {
4904 return self.physical_declaration_visible_at(
4905 analyzer,
4906 file,
4907 declaration,
4908 reference_byte,
4909 &reference_guards,
4910 );
4911 }
4912 self.visible_identifier_candidates(file, declaration.identifier())
4913 .filter(|candidate| {
4914 (if declaration.is_callable()
4919 && reference_uses_c_semantics(self.cpp, file)
4920 != reference_uses_c_semantics(self.cpp, declaration.source())
4921 {
4922 crate::identity::cpp_callable_definitions_share_identity_evidence_with_visibility(
4923 analyzer,
4924 self,
4925 candidate,
4926 declaration,
4927 |left, right| {
4928 crate::identity::cpp_header_body_files_are_related(
4929 self.cpp, self.token, left, right,
4930 )
4931 },
4932 )
4933 } else {
4934 self.same_logical_callable(analyzer, candidate, declaration)
4935 })
4936 || flattened_macro_namespace_declaration_matches(
4937 analyzer,
4938 self.cpp,
4939 file,
4940 candidate,
4941 declaration,
4942 reference_byte,
4943 )
4944 })
4945 .any(|candidate| {
4946 self.physical_declaration_visible_at(
4947 analyzer,
4948 file,
4949 candidate,
4950 reference_byte,
4951 &reference_guards,
4952 )
4953 })
4954 }
4955
4956 pub(crate) fn declaration_guards_compatible_at_reference(
4960 &self,
4961 analyzer: &CppGraphSource<'_>,
4962 file: &ProjectFile,
4963 declaration: &CodeUnit,
4964 reference: Node<'_>,
4965 ) -> bool {
4966 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4967 return false;
4968 };
4969 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4970 let reference_guards = self.effective_reference_guards(file, reference_guards.as_ref());
4971 declaration_guard_requirements(analyzer, self.cpp, declaration)
4972 .iter()
4973 .any(|(_, required)| {
4974 guards_compatible_at_reference(required, reference_guards.as_ref())
4975 })
4976 }
4977
4978 pub fn declaration_visible_at_reference(
4985 &self,
4986 analyzer: &CppGraphSource<'_>,
4987 file: &ProjectFile,
4988 declaration: &CodeUnit,
4989 reference: Node<'_>,
4990 ) -> bool {
4991 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4992 return false;
4993 };
4994 if !self.declaration_guards_compatible_at_reference(analyzer, file, declaration, reference)
4995 {
4996 return false;
4997 }
4998 if declaration.source() == file
4999 && !analyzer.reference_uses_c_semantics(file)
5000 && (declaration.is_field() || declaration.is_callable())
5001 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
5002 self.indexed_enclosing_owner_scope(analyzer, file, reference)
5003 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
5004 })
5005 {
5006 return true;
5010 }
5011 if declaration.is_field() && declaration.source() == file {
5012 let reference_byte = reference.start_byte();
5013 let reference_function =
5014 real_function_definition_ancestor(reference, prepared.source());
5015 let guards = OnceCell::new();
5016 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5017 self.effective_reference_guards(file, guards)
5018 };
5019 let field_reference = CallableReferenceContext {
5020 effective_guards: &effective_guards,
5021 file_guards: OnceCell::new(),
5022 file,
5023 position: Some(CallableReferencePosition {
5024 prepared: prepared.as_ref(),
5025 byte: reference_byte,
5026 guards: &guards,
5027 }),
5028 };
5029 let mut has_local_declaration = false;
5030 let mut local_declaration_visible = false;
5031 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
5032 {
5033 let Some(declaration_function) =
5034 real_function_definition_ancestor(declaration, prepared.source())
5035 else {
5036 continue;
5037 };
5038 has_local_declaration = true;
5039 if reference_function.is_some_and(|reference_function| {
5040 reference_function.start_byte() == declaration_function.start_byte()
5041 && reference_function.end_byte() == declaration_function.end_byte()
5042 }) && callable_preprocessor_context_is_visible_for_reference(
5043 declaration,
5044 prepared.source(),
5045 &field_reference,
5046 ) && declaration.end_byte() < reference_byte
5047 {
5048 local_declaration_visible = true;
5049 break;
5050 }
5051 }
5052 if has_local_declaration {
5053 return local_declaration_visible;
5054 }
5055 }
5056 let guards = OnceCell::new();
5057 self.physical_declaration_visible_at(
5058 analyzer,
5059 file,
5060 declaration,
5061 reference.start_byte(),
5062 &guards,
5063 )
5064 }
5065
5066 pub fn declaration_visible_for_c_forward_call(
5072 &self,
5073 analyzer: &CppGraphSource<'_>,
5074 file: &ProjectFile,
5075 declaration: &CodeUnit,
5076 reference_byte: usize,
5077 ) -> bool {
5078 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
5079 return true;
5080 }
5081 if declaration.source() != file {
5082 return false;
5083 }
5084 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5085 return false;
5086 };
5087 let reference_guards = prepared
5088 .tree()
5089 .root_node()
5090 .descendant_for_byte_range(reference_byte, reference_byte)
5091 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
5092 declaration_guard_requirements(analyzer, self.cpp, declaration)
5093 .into_iter()
5094 .any(|(_, required)| {
5095 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
5096 })
5097 }
5098
5099 pub fn callable_arity_at_reference(
5100 &self,
5101 analyzer: &CppGraphSource<'_>,
5102 file: &ProjectFile,
5103 candidate: &CodeUnit,
5104 reference_byte: usize,
5105 ) -> Option<CallableArity> {
5106 let key = (file.clone(), logical_symbol_key(candidate));
5107 let cell = self
5108 .callable_reference_specs
5109 .lock()
5110 .expect("C++ callable reference-spec cache poisoned")
5111 .entry(key)
5112 .or_default()
5113 .clone();
5114 let spec = cell.get_or_init(|| {
5115 let prepared = self.cpp.prepared_syntax(self.token, file)?;
5116 let spec = TargetSpec::from_target(analyzer, candidate)?;
5117 let spec = spec
5118 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
5119 .into_owned();
5120 #[cfg(any(test, feature = "test-support"))]
5121 self.callable_reference_spec_build_count
5122 .fetch_add(1, Ordering::Relaxed);
5123 Some(spec)
5124 });
5125 spec.as_ref()?.callable_arity_at(reference_byte)
5126 }
5127
5128 fn physical_declaration_visible_at(
5129 &self,
5130 analyzer: &CppGraphSource<'_>,
5131 file: &ProjectFile,
5132 declaration: &CodeUnit,
5133 reference_byte: usize,
5134 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
5135 ) -> bool {
5136 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5137 return false;
5138 };
5139 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5140 self.effective_reference_guards(file, guards)
5141 };
5142 let reference = CallableReferenceContext {
5143 effective_guards: &effective_guards,
5144 file_guards: OnceCell::new(),
5145 file,
5146 position: Some(CallableReferencePosition {
5147 prepared: prepared.as_ref(),
5148 byte: reference_byte,
5149 guards: reference_guards,
5150 }),
5151 };
5152 if declaration.source() == file {
5153 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
5154 .or_else(|| {
5155 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
5156 })
5157 .is_some_and(|activation| activation < reference_byte);
5158 }
5159 if self
5160 .exhaustive_guard_family_activation(analyzer, declaration, &reference)
5161 .is_some()
5162 {
5163 return self.foreign_declaration_reachable_at_reference(
5166 file,
5167 prepared.as_ref(),
5168 declaration.source(),
5169 &HashSet::default(),
5170 reference.guards(),
5171 reference_byte,
5172 );
5173 }
5174 if !self.foreign_callable_declaration_is_active(analyzer, declaration, &reference) {
5175 return false;
5176 }
5177 declaration_guard_requirements(analyzer, self.cpp, declaration)
5178 .into_iter()
5179 .any(|(_, declaration_guards)| {
5180 self.foreign_declaration_reachable_at_reference(
5181 file,
5182 prepared.as_ref(),
5183 declaration.source(),
5184 &declaration_guards,
5185 reference.guards(),
5186 reference_byte,
5187 )
5188 })
5189 }
5190
5191 fn foreign_callable_declaration_is_active(
5196 &self,
5197 analyzer: &CppGraphSource<'_>,
5198 declaration: &CodeUnit,
5199 reference: &CallableReferenceContext<'_>,
5200 ) -> bool {
5201 let build_decides = !self.compile_context_is_absent(reference.file);
5202 let active = reference.guards();
5203 analyzer
5204 .declaration_source_occurrences(declaration)
5205 .into_iter()
5206 .flatten()
5207 .any(|occurrence| {
5208 if occurrence.fact.callable_activation.is_none() {
5209 return false;
5210 }
5211 let Some(required) = occurrence
5212 .fact
5213 .callable_guards
5214 .as_ref()
5215 .and_then(cpp_guard_set_to_runtime)
5216 else {
5217 return false;
5218 };
5219 required.is_empty()
5220 || if build_decides {
5221 guard_requirements_hold_at_reference(&required, active)
5222 } else {
5223 guards_compatible_at_reference(&required, active)
5224 }
5225 })
5226 }
5227
5228 pub fn external_type_candidate_visible_at(
5229 &self,
5230 file: &ProjectFile,
5231 candidate: &CodeUnit,
5232 reference_byte: usize,
5233 ) -> bool {
5234 if candidate.source() == file {
5235 return true;
5236 }
5237 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5238 return false;
5239 };
5240 self.visible_identifier_candidates(file, candidate.identifier())
5241 .filter(|peer| same_logical_symbol(candidate, peer))
5242 .any(|peer| {
5243 peer.source() == file
5244 || self
5245 .include_activation_for_source(
5246 self.cpp,
5247 file,
5248 prepared.as_ref(),
5249 peer.source(),
5250 )
5251 .is_some_and(|activation| activation <= reference_byte)
5252 })
5253 }
5254
5255 pub fn external_type_declaration_visible_at(
5256 &self,
5257 file: &ProjectFile,
5258 candidate: &CodeUnit,
5259 reference_byte: usize,
5260 ) -> bool {
5261 if candidate.source() == file {
5262 return true;
5263 }
5264 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5265 return false;
5266 };
5267 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5268 .is_some_and(|activation| activation <= reference_byte)
5269 }
5270
5271 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5290 if let Some(cached) = self
5291 .compile_proven_guard_cells
5292 .lock()
5293 .expect("C++ compile-proven guard cache poisoned")
5294 .get(file)
5295 {
5296 return Arc::clone(cached);
5297 }
5298 let proven = Arc::new(compile_proven_guard_facts(self.cpp, file));
5299 self.compile_proven_guard_cells
5300 .lock()
5301 .expect("C++ compile-proven guard cache poisoned")
5302 .insert(file.clone(), Arc::clone(&proven));
5303 proven
5304 }
5305
5306 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5316 if let Some(cached) = self
5317 .include_path_admission_cells
5318 .lock()
5319 .expect("C++ include-path admission cache poisoned")
5320 .get(file)
5321 .copied()
5322 {
5323 return cached;
5324 }
5325 let admission = if self.compile_context_is_absent(file) {
5326 IncludePathAdmission::Compatible
5327 } else {
5328 IncludePathAdmission::Proven
5329 };
5330 self.include_path_admission_cells
5331 .lock()
5332 .expect("C++ include-path admission cache poisoned")
5333 .insert(file.clone(), admission);
5334 admission
5335 }
5336
5337 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5344 if !self.cpp.compile_contexts_for(file).is_empty() {
5345 return false;
5346 }
5347 let translation_units = self.cpp.reaching_translation_units(file);
5348 translation_units.is_empty()
5349 || translation_units
5350 .iter()
5351 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5352 }
5353
5354 pub fn miss_requires_compile_context(
5366 &self,
5367 file: &ProjectFile,
5368 identifier: &str,
5369 reference: Node<'_>,
5370 ) -> bool {
5371 if !self.compile_context_is_absent(file) {
5372 return false;
5373 }
5374 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5375 return false;
5376 };
5377 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5378 let reference_byte = reference.start_byte();
5379 let mut sources = self
5380 .visible_identifier_candidates(file, identifier)
5381 .map(CodeUnit::source)
5382 .filter(|source| *source != file)
5383 .collect::<Vec<_>>();
5384 sources.sort();
5385 sources.dedup();
5386 sources.into_iter().any(|declaration_source| {
5387 self.conditional_include_projections_for_source(
5388 file,
5389 prepared.as_ref(),
5390 declaration_source,
5391 )
5392 .iter()
5393 .any(|projection| {
5394 projection.activation_byte <= reference_byte
5395 && !guard_requirements_hold_at_reference(
5396 &projection.required_guards,
5397 reference_guards.as_ref(),
5398 )
5399 && guards_compatible_at_reference(
5400 &projection.required_guards,
5401 reference_guards.as_ref(),
5402 )
5403 })
5404 })
5405 }
5406
5407 fn effective_reference_guards(
5409 &self,
5410 file: &ProjectFile,
5411 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5412 ) -> Option<HashSet<PreprocessorGuard>> {
5413 effective_reference_guards(
5414 self.cpp,
5415 file,
5416 reference_guards,
5417 &self.compile_proven_guards(file),
5418 &|| self.include_language_guard_is_stable(file),
5419 )
5420 }
5421
5422 fn foreign_declaration_reachable_at_reference(
5430 &self,
5431 file: &ProjectFile,
5432 prepared: &PreparedSyntaxTree,
5433 declaration_source: &ProjectFile,
5434 declaration_guards: &HashSet<PreprocessorGuard>,
5435 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5436 reference_byte: usize,
5437 ) -> bool {
5438 let effective = self.effective_reference_guards(file, reference_guards);
5439 let reference_guards = effective.as_ref();
5440 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5441 eprintln!(
5442 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5443 declaration_source.rel_path().display(),
5444 );
5445 }
5446 if !guard_requirements_hold_at_reference(declaration_guards, reference_guards) {
5447 return false;
5448 }
5449 if self
5450 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5451 .is_some_and(|activation| activation <= reference_byte)
5452 {
5453 return true;
5454 }
5455 let projections =
5456 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5457 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5458 eprintln!(
5459 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5460 declaration_source.rel_path().display(),
5461 declaration_guards.len(),
5462 self.compile_proven_guards(file).len(),
5463 projections.len(),
5464 );
5465 }
5466 let admission = IncludePathAdmission::Proven;
5467 projections.iter().any(|projection| {
5468 projection.activation_byte <= reference_byte
5469 && admission.admits(
5470 &projection.required_guards,
5471 &projection.partial_guards,
5472 reference_guards,
5473 )
5474 && self.preprocessor_guards_stable_between(
5475 file,
5476 projection.activation_byte,
5477 reference_byte,
5478 &projection.required_guards,
5479 )
5480 })
5481 }
5482
5483 fn foreign_declaration_may_be_reachable_from_raw_guards(
5484 &self,
5485 file: &ProjectFile,
5486 prepared: &PreparedSyntaxTree,
5487 declaration_source: &ProjectFile,
5488 declaration_guards: &HashSet<PreprocessorGuard>,
5489 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5490 reference_byte: usize,
5491 ) -> bool {
5492 let proven = self.compile_proven_guards(file);
5493 let augmented;
5494 let reference_guards = match reference_guards {
5495 Some(active) if !proven.is_empty() => {
5496 augmented = active.union(&proven).cloned().collect();
5497 Some(&augmented)
5498 }
5499 other => other,
5500 };
5501 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5502 return false;
5503 }
5504 if self
5505 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5506 .is_some_and(|activation| activation <= reference_byte)
5507 {
5508 return true;
5509 }
5510 let effective = OnceCell::new();
5511 let reachable = find_conditional_include_projection_for_source(
5512 self.cpp,
5513 self.token,
5514 file,
5515 prepared,
5516 declaration_source,
5517 self.include_path_admission(file),
5518 reference_guards,
5519 reference_byte,
5520 &|| {
5521 effective
5522 .get_or_init(|| {
5523 self.effective_reference_guards(file, Some(&HashSet::default()))
5524 })
5525 .as_ref()
5526 },
5527 &|| {
5528 #[cfg(any(test, feature = "test-support"))]
5529 self.conditional_include_target_state_count
5530 .fetch_add(1, Ordering::Relaxed);
5531 },
5532 );
5533 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5534 eprintln!(
5535 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5536 declaration_source.rel_path().display(),
5537 declaration_guards.len(),
5538 proven.len(),
5539 reference_guards.map_or(0, HashSet::len),
5540 );
5541 }
5542 reachable
5543 }
5544
5545 fn foreign_declaration_reachable_from_compile_proven_guards(
5546 &self,
5547 file: &ProjectFile,
5548 prepared: &PreparedSyntaxTree,
5549 declaration_source: &ProjectFile,
5550 declaration_guards: &HashSet<PreprocessorGuard>,
5551 reference_byte: usize,
5552 ) -> bool {
5553 let proven = self.compile_proven_guards(file);
5554 if proven.is_empty()
5555 || !guard_requirements_hold_at_reference(declaration_guards, Some(proven.as_ref()))
5556 {
5557 return false;
5558 }
5559 let projections =
5560 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5561 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5562 eprintln!(
5563 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5564 declaration_source.rel_path().display(),
5565 declaration_guards.len(),
5566 proven.len(),
5567 projections.len(),
5568 );
5569 }
5570 projections.iter().any(|projection| {
5571 projection.activation_byte <= reference_byte
5572 && guard_requirements_hold_at_reference(
5573 &projection.required_guards,
5574 Some(proven.as_ref()),
5575 )
5576 && self.preprocessor_guards_stable_between(
5581 file,
5582 0,
5583 projection.activation_byte,
5584 &projection.required_guards,
5585 )
5586 })
5587 }
5588
5589 fn declaration_site_visible_without_reference_environment(
5595 &self,
5596 file: &ProjectFile,
5597 prepared: &PreparedSyntaxTree,
5598 site_source: &ProjectFile,
5599 site_byte: usize,
5600 site_guards: &HashSet<PreprocessorGuard>,
5601 reference_byte: usize,
5602 ) -> bool {
5603 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5604 if site_source == file {
5605 let visible = site_byte < reference_byte && site_guards.is_empty();
5606 if report_stats {
5607 eprintln!(
5608 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5609 site_source.rel_path().display(),
5610 site_guards.len(),
5611 );
5612 }
5613 return visible;
5614 }
5615 let direct = site_guards.is_empty()
5616 && self
5617 .include_activation_for_source(self.cpp, file, prepared, site_source)
5618 .is_some_and(|activation| activation <= reference_byte);
5619 let compile_proven = !direct
5620 && self.foreign_declaration_reachable_from_compile_proven_guards(
5621 file,
5622 prepared,
5623 site_source,
5624 site_guards,
5625 reference_byte,
5626 );
5627 if report_stats {
5628 eprintln!(
5629 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5630 site_source.rel_path().display(),
5631 site_guards.len(),
5632 );
5633 }
5634 direct || compile_proven
5635 }
5636
5637 #[allow(clippy::too_many_arguments)]
5642 fn declaration_site_visible_at_reference<'g>(
5643 &self,
5644 file: &ProjectFile,
5645 prepared: &PreparedSyntaxTree,
5646 site_source: &ProjectFile,
5647 site: &DeclarationGuardSite,
5648 reference_byte: usize,
5649 raw_reference_guards: Option<&HashSet<PreprocessorGuard>>,
5650 reference_guards_at_site: impl Fn() -> Option<&'g HashSet<PreprocessorGuard>>,
5651 ) -> bool {
5652 if site_source == file {
5653 if site.byte >= reference_byte {
5654 return false;
5655 }
5656 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5661 guard_requirements_hold_at_reference(&site.guards, guards)
5662 || (site.completed_family_before_reference
5663 && guards_compatible_at_reference(&site.guards, guards))
5664 };
5665 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
5666 self.effective_reference_guards(file, guards)
5667 };
5668 let raw_reference_guards = effective_guards(raw_reference_guards);
5669 if !active(raw_reference_guards.as_ref()) {
5670 return false;
5671 }
5672 let reference_guards_at_site = effective_guards(reference_guards_at_site());
5673 return active(reference_guards_at_site.as_ref())
5674 && self.preprocessor_guards_stable_between(
5675 file,
5676 site.byte,
5677 reference_byte,
5678 &site.guards,
5679 );
5680 }
5681 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5682 file,
5683 prepared,
5684 site_source,
5685 &site.guards,
5686 raw_reference_guards,
5687 reference_byte,
5688 );
5689 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5690 eprintln!(
5691 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5692 site_source.rel_path().display(),
5693 site.guards.len(),
5694 );
5695 }
5696 raw_feasible
5697 && self.foreign_declaration_reachable_at_reference(
5698 file,
5699 prepared,
5700 site_source,
5701 &site.guards,
5702 reference_guards_at_site(),
5703 reference_byte,
5704 )
5705 }
5706
5707 pub fn binding_site_visible_at_reference(
5712 &self,
5713 file: &ProjectFile,
5714 site_source: &ProjectFile,
5715 site_byte: usize,
5716 site_guards: &HashSet<PreprocessorGuard>,
5717 reference: Node<'_>,
5718 ) -> bool {
5719 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5720 return false;
5721 };
5722 if self.declaration_site_visible_without_reference_environment(
5723 file,
5724 prepared.as_ref(),
5725 site_source,
5726 site_byte,
5727 site_guards,
5728 reference.start_byte(),
5729 ) {
5730 return true;
5731 }
5732 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5733 let reference_guards = OnceCell::new();
5734 self.declaration_site_visible_at_reference(
5735 file,
5736 prepared.as_ref(),
5737 site_source,
5738 &DeclarationGuardSite {
5739 byte: site_byte,
5740 guards: site_guards.clone(),
5741 completed_family_before_reference: false,
5742 },
5743 reference.start_byte(),
5744 raw_reference_guards.as_ref(),
5745 || {
5746 reference_guards
5747 .get_or_init(|| {
5748 let environment = self.macro_environment(file, reference.start_byte());
5749 raw_reference_guards
5750 .clone()
5751 .filter(|guards| environment.guard_requirements_may_hold(guards))
5752 })
5753 .as_ref()
5754 },
5755 )
5756 }
5757
5758 pub fn external_type_candidate_visible_in_context(
5759 &self,
5760 analyzer: &CppGraphSource<'_>,
5761 file: &ProjectFile,
5762 candidate: &CodeUnit,
5763 reference: Node<'_>,
5764 ) -> bool {
5765 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5766 if report_stats {
5767 eprintln!(
5768 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5769 candidate.fq_name(),
5770 candidate.source().rel_path().display(),
5771 file.rel_path().display(),
5772 reference.start_byte(),
5773 );
5774 }
5775 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5776 return false;
5777 };
5778 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5779 let reference_guards = OnceCell::new();
5780 let reference_guards_at_site = || {
5781 reference_guards.get_or_init(|| {
5782 let started = Instant::now();
5783 if report_stats {
5784 eprintln!(
5785 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5786 file.rel_path().display(),
5787 reference.start_byte(),
5788 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5789 );
5790 }
5791 let macro_environment = self.macro_environment(file, reference.start_byte());
5792 let filtered = raw_reference_guards
5793 .clone()
5794 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5795 if report_stats {
5796 eprintln!(
5797 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5798 filtered.is_some(),
5799 started.elapsed().as_millis(),
5800 );
5801 }
5802 filtered
5803 })
5804 };
5805
5806 let peers = self
5807 .visible_identifier_candidates(file, candidate.identifier())
5808 .filter(|peer| same_logical_symbol(candidate, peer))
5809 .collect::<Vec<_>>();
5810 if report_stats {
5811 let peer_sources = peers
5812 .iter()
5813 .map(|peer| peer.source().rel_path().display().to_string())
5814 .collect::<Vec<_>>();
5815 eprintln!(
5816 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5817 candidate.fq_name(),
5818 );
5819 }
5820 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5821 declaration_guard_requirements(analyzer, self.cpp, peer)
5822 .into_iter()
5823 .any(|(declaration_byte, declaration_guards)| {
5824 self.declaration_site_visible_without_reference_environment(
5825 file,
5826 prepared.as_ref(),
5827 peer.source(),
5828 declaration_byte,
5829 &declaration_guards,
5830 reference.start_byte(),
5831 )
5832 })
5833 });
5834 if directly_visible_without_reference_environment {
5835 if report_stats {
5836 eprintln!(
5837 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5838 candidate.fq_name(),
5839 );
5840 }
5841 return true;
5842 }
5843 let directly_visible = peers.iter().any(|peer| {
5844 declaration_guard_sites(analyzer, self.cpp, peer, reference.start_byte())
5845 .into_iter()
5846 .any(|site| {
5847 self.declaration_site_visible_at_reference(
5848 file,
5849 prepared.as_ref(),
5850 peer.source(),
5851 &site,
5852 reference.start_byte(),
5853 raw_reference_guards.as_ref(),
5854 || reference_guards_at_site().as_ref(),
5855 )
5856 })
5857 });
5858 if directly_visible {
5859 if report_stats {
5860 eprintln!(
5861 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5862 candidate.fq_name(),
5863 );
5864 }
5865 return true;
5866 }
5867 let complementary = self
5868 .visible_identifier_candidates(file, candidate.identifier())
5869 .filter(|peer| {
5870 peer.kind() == candidate.kind()
5871 && peer.fq_name() == candidate.fq_name()
5872 && peer.source() == candidate.source()
5873 })
5874 .collect::<Vec<_>>();
5875 let complementary_family =
5880 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5881 let raw_candidate_branch_compatible = complementary_family
5882 && raw_reference_guards.as_ref().is_some_and(|active| {
5883 declaration_guard_requirements(analyzer, self.cpp, candidate)
5884 .iter()
5885 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5886 });
5887 if report_stats {
5888 eprintln!(
5889 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5890 candidate.fq_name(),
5891 complementary.len(),
5892 complementary_family,
5893 raw_candidate_branch_compatible,
5894 );
5895 }
5896 let candidate_branch_compatible = raw_candidate_branch_compatible
5897 && reference_guards_at_site().as_ref().is_some_and(|active| {
5898 declaration_guard_requirements(analyzer, self.cpp, candidate)
5899 .iter()
5900 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5901 });
5902 let complementary_visible = candidate_branch_compatible
5903 && if candidate.source() == file {
5904 declaration_guard_requirements(analyzer, self.cpp, candidate)
5905 .iter()
5906 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5907 } else {
5908 self.include_activation_for_source(
5909 self.cpp,
5910 file,
5911 prepared.as_ref(),
5912 candidate.source(),
5913 )
5914 .is_some_and(|activation| activation <= reference.start_byte())
5915 };
5916 if report_stats {
5917 eprintln!(
5918 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5919 if complementary_visible {
5920 "complementary"
5921 } else {
5922 "missing"
5923 },
5924 candidate.fq_name(),
5925 );
5926 }
5927 complementary_visible
5928 }
5929
5930 pub fn is_exhaustive_same_fqn_type_declaration_family(
5931 &self,
5932 analyzer: &CppGraphSource<'_>,
5933 file: &ProjectFile,
5934 candidate: &CodeUnit,
5935 ) -> bool {
5936 let candidates = self
5937 .visible_identifier_candidates(file, candidate.identifier())
5938 .filter(|peer| {
5939 peer.kind() == candidate.kind()
5940 && peer.fq_name() == candidate.fq_name()
5941 && peer.source() == candidate.source()
5942 })
5943 .collect::<Vec<_>>();
5944 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5945 }
5946
5947 pub fn dependent_member_pointer_alias_visible_in_context(
5962 &self,
5963 analyzer: &CppGraphSource<'_>,
5964 file: &ProjectFile,
5965 candidate: &CodeUnit,
5966 owner_components: &[String],
5967 reference: Node<'_>,
5968 ) -> bool {
5969 if !analyzer
5970 .type_alias_provider()
5971 .is_some_and(|provider| provider.is_type_alias(candidate))
5972 {
5973 return false;
5974 }
5975 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5976 return false;
5977 };
5978 if terminal != candidate.identifier()
5979 || canonical_cpp_scope_components(candidate) != owner_components
5980 {
5981 return false;
5982 }
5983 let Some(expected_parent_fq_name) =
5984 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5985 else {
5986 return false;
5987 };
5988 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5989 return false;
5990 };
5991 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5992 || parent_anchor.source() != candidate.source()
5993 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5994 {
5995 return false;
5996 }
5997
5998 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
6004 || candidate.source() == file
6005 && !analyzer
6006 .ranges(candidate)
6007 .iter()
6008 .any(|range| range.start_byte < reference.start_byte())
6009 {
6010 return false;
6011 }
6012
6013 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
6014 if candidate_guards.is_empty() {
6015 return false;
6016 }
6017 let same_guard_sets =
6018 |left: &[(usize, HashSet<PreprocessorGuard>)],
6019 right: &[(usize, HashSet<PreprocessorGuard>)]| {
6020 left.iter().all(|(_, left_guards)| {
6021 right
6022 .iter()
6023 .any(|(_, right_guards)| left_guards == right_guards)
6024 })
6025 };
6026 let parent_candidates = self
6027 .visible_identifier_candidates(file, parent_anchor.identifier())
6028 .filter(|peer| {
6029 peer.kind() == parent_anchor.kind()
6030 && peer.fq_name() == expected_parent_fq_name.as_str()
6031 && peer.source() == parent_anchor.source()
6032 && canonical_cpp_scope_components(peer) == owner_prefix
6033 })
6034 .filter_map(|peer| {
6035 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
6036 (candidate_guards.len() == parent_guards.len()
6037 && same_guard_sets(&candidate_guards, &parent_guards)
6038 && same_guard_sets(&parent_guards, &candidate_guards))
6039 .then(|| (peer.clone(), parent_guards))
6040 })
6041 .collect::<Vec<_>>();
6042 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
6043 return false;
6044 };
6045
6046 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6047 return false;
6048 };
6049 let raw = preprocessor_guard_environment(reference, prepared.source());
6050 let Some(reference_guards) = self.effective_reference_guards(file, raw.as_ref()) else {
6051 return false;
6052 };
6053 if !candidate_guards.iter().any(|(_, target_guards)| {
6058 guards_compatible_at_reference(target_guards, Some(&reference_guards))
6059 && (candidate.source() != file
6060 || self.preprocessor_guards_stable_between(
6061 file,
6062 0,
6063 reference.start_byte(),
6064 target_guards,
6065 ))
6066 }) {
6067 return false;
6068 }
6069
6070 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
6071 }
6072
6073 pub fn external_type_candidate_guard_compatible_in_context(
6083 &self,
6084 analyzer: &CppGraphSource<'_>,
6085 file: &ProjectFile,
6086 candidate: &CodeUnit,
6087 reference: Node<'_>,
6088 ) -> bool {
6089 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6090 return false;
6091 };
6092 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
6093
6094 self.visible_identifier_candidates(file, candidate.identifier())
6095 .filter(|peer| same_logical_symbol(candidate, peer))
6096 .any(|peer| {
6097 declaration_guard_requirements(analyzer, self.cpp, peer)
6098 .into_iter()
6099 .any(|(declaration_byte, declaration_guards)| {
6100 if peer.source() == file {
6101 let (start, end) = if declaration_byte <= reference.start_byte() {
6102 (declaration_byte, reference.start_byte())
6103 } else {
6104 (reference.start_byte(), declaration_byte)
6105 };
6106 return guard_requirements_hold_at_reference(
6107 &declaration_guards,
6108 reference_guards.as_ref(),
6109 ) && self.preprocessor_guards_stable_between(
6110 file,
6111 start,
6112 end,
6113 &declaration_guards,
6114 );
6115 }
6116 self.foreign_declaration_reachable_at_reference(
6117 file,
6118 prepared.as_ref(),
6119 peer.source(),
6120 &declaration_guards,
6121 reference_guards.as_ref(),
6122 reference.start_byte(),
6123 )
6124 })
6125 })
6126 }
6127
6128 pub fn same_file_callable_guard_compatible_ignoring_order(
6136 &self,
6137 analyzer: &CppGraphSource<'_>,
6138 file: &ProjectFile,
6139 candidate: &CodeUnit,
6140 reference: Node<'_>,
6141 ) -> bool {
6142 if candidate.source() != file || !candidate.is_callable() {
6143 return false;
6144 }
6145 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6146 return false;
6147 };
6148 let guards = OnceCell::new();
6149 let effective_guards = |guards: Option<&HashSet<PreprocessorGuard>>| {
6150 self.effective_reference_guards(file, guards)
6151 };
6152 let context = CallableReferenceContext {
6153 effective_guards: &effective_guards,
6154 file_guards: OnceCell::new(),
6155 file,
6156 position: Some(CallableReferencePosition {
6157 prepared: prepared.as_ref(),
6158 byte: reference.start_byte(),
6159 guards: &guards,
6160 }),
6161 };
6162 analyzer
6163 .declaration_source_occurrences(candidate)
6164 .into_iter()
6165 .flatten()
6166 .filter_map(|occurrence| {
6167 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
6168 Some(callable_guard_requirements_are_visible(
6169 &guards,
6170 occurrence.fact.callable_guard_completion_byte,
6171 &context,
6172 ))
6173 })
6174 .any(|visible| visible)
6175 }
6176
6177 pub fn type_candidate_may_be_visible_before_reference(
6178 &self,
6179 analyzer: &CppGraphSource<'_>,
6180 file: &ProjectFile,
6181 candidate: &CodeUnit,
6182 reference_byte: usize,
6183 ) -> bool {
6184 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6185 return false;
6186 };
6187 let root = prepared.tree().root_node();
6188 let end_byte = reference_byte
6189 .saturating_add(1)
6190 .min(prepared.source().len());
6191 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
6192 return false;
6193 };
6194 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
6195 }
6196
6197 pub fn preprocessor_guards_stable_between(
6198 &self,
6199 file: &ProjectFile,
6200 start_byte: usize,
6201 end_byte: usize,
6202 guards: &HashSet<PreprocessorGuard>,
6203 ) -> bool {
6204 if guards.is_empty() || start_byte >= end_byte {
6205 return true;
6206 }
6207 let cell = self.macro_event_cell(file);
6208 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6209 let mut visited = HashSet::from_iter([file.clone()]);
6210 !events.iter().any(|event| {
6211 event.byte() >= start_byte
6212 && event.byte() < end_byte
6213 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
6214 })
6215 }
6216
6217 fn macro_event_may_mutate_guards(
6218 &self,
6219 event: &MacroEvent,
6220 guards: &HashSet<PreprocessorGuard>,
6221 visited: &mut HashSet<ProjectFile>,
6222 ) -> bool {
6223 match event {
6224 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
6225 guards.iter().any(|guard| guard.may_depend_on_macro(name))
6226 }
6227 MacroEvent::Include { targets, .. } => {
6228 targets.is_empty()
6229 || targets
6230 .iter()
6231 .any(|target| self.source_may_mutate_guards(target, guards, visited))
6232 }
6233 MacroEvent::Invalidate { .. } => true,
6234 }
6235 }
6236
6237 fn source_may_mutate_guards(
6238 &self,
6239 file: &ProjectFile,
6240 guards: &HashSet<PreprocessorGuard>,
6241 visited: &mut HashSet<ProjectFile>,
6242 ) -> bool {
6243 if !visited.insert(file.clone()) {
6244 return false;
6245 }
6246 let cell = self.macro_event_cell(file);
6247 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
6248 events
6249 .iter()
6250 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
6251 }
6252
6253 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
6254 let normalized = normalize_reference_name(raw_name)?;
6255 self.type_candidates(file, &normalized)
6256 .into_iter()
6257 .next()
6258 .cloned()
6259 }
6260
6261 pub fn unique_visible_parameter_type_fallback(
6270 &self,
6271 analyzer: &CppGraphSource<'_>,
6272 file: &ProjectFile,
6273 node: Node<'_>,
6274 source: &str,
6275 ) -> Option<CodeUnit> {
6276 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
6277 return None;
6278 }
6279 let name = node_text(node, source);
6280 let candidates = self
6281 .visible_identifier_candidates(file, name)
6282 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
6283 .filter(|candidate| {
6284 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6285 })
6286 .collect::<Vec<_>>();
6287 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6288 }
6289
6290 pub fn resolve_type_node_result(
6291 &self,
6292 file: &ProjectFile,
6293 node: Node<'_>,
6294 source: &str,
6295 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6296 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6297 return Ok(None);
6298 };
6299 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6300 return Ok(Some(primary));
6301 };
6302 self.resolve_template_arguments(file, primary, &arguments)
6303 .map(Some)
6304 }
6305
6306 pub fn resolve_type_node_primary(
6307 &self,
6308 file: &ProjectFile,
6309 node: Node<'_>,
6310 source: &str,
6311 ) -> Option<CodeUnit> {
6312 let components = cpp_type_name_components(node, source)?;
6313 if !is_globally_qualified_cpp_name(node)
6314 && let Some(resolution) =
6315 function_local_type_resolution(&self.cpp_source(), file, node, &components, source)
6316 {
6317 return match resolution {
6318 LexicalTypeResolution::Resolved { unit, .. } => Some(unit),
6319 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
6320 };
6321 }
6322 self.resolve_type(file, &components.join("::"))
6323 }
6324
6325 pub fn resolve_template_arguments(
6326 &self,
6327 file: &ProjectFile,
6328 primary: CodeUnit,
6329 arguments: &[CppTemplateExpression],
6330 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6331 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6332 }
6333
6334 fn resolve_template_arguments_inner(
6335 &self,
6336 file: &ProjectFile,
6337 primary: CodeUnit,
6338 arguments: &[CppTemplateExpression],
6339 seen_aliases: &mut HashSet<CodeUnit>,
6340 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6341 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6342 && let Some(alias_target) = &metadata.alias_target
6343 {
6344 if !seen_aliases.insert(primary.clone()) {
6345 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6346 }
6347 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6348 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6349 let target_name = alias_target.components.join("::");
6350 let target_primary = if alias_target.global {
6351 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6352 } else {
6353 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6354 };
6355 let Some(target_primary) = target_primary else {
6356 return Ok(primary);
6360 };
6361 let Some(target_arguments) = &alias_target.arguments else {
6362 return Ok(target_primary);
6363 };
6364 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6365 .ok_or(CppTemplateResolutionError::Substitution)?;
6366 return self.resolve_template_arguments_inner(
6367 file,
6368 target_primary,
6369 &target_arguments,
6370 seen_aliases,
6371 );
6372 }
6373
6374 let primary_fq_name = self
6375 .cpp_template_metadata
6376 .get(&primary)
6377 .map(|metadata| metadata.primary_fq_name.clone())
6378 .unwrap_or_else(|| primary.fq_name());
6379 let has_specialization_metadata = self
6380 .cpp_template_families
6381 .get(&primary_fq_name)
6382 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6383 if !has_specialization_metadata {
6384 return Ok(primary);
6385 }
6386 self.select_template_specialization(file, &primary, arguments)
6387 }
6388
6389 fn select_template_specialization(
6390 &self,
6391 file: &ProjectFile,
6392 resolved: &CodeUnit,
6393 explicit_arguments: &[CppTemplateExpression],
6394 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6395 let primary_fq_name = self
6396 .cpp_template_metadata
6397 .get(resolved)
6398 .map(|metadata| metadata.primary_fq_name.clone())
6399 .unwrap_or_else(|| resolved.fq_name());
6400 let family = self
6401 .cpp_template_families
6402 .get(&primary_fq_name)
6403 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6404 let primary_candidates = family
6405 .iter()
6406 .filter_map(|unit| {
6407 let metadata = self.cpp_template_metadata.get(unit)?;
6408 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6409 })
6410 .collect::<Vec<_>>();
6411 let primary_unit = primary_candidates
6412 .iter()
6413 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6414 .or_else(|| {
6415 primary_candidates
6416 .iter()
6417 .map(|(unit, _)| *unit)
6418 .min_by_key(|unit| {
6419 (
6420 unit.source().to_string(),
6421 unit.signature().unwrap_or_default(),
6422 )
6423 })
6424 })
6425 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6426 let primary_parameters =
6427 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6428 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6429 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6430 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6431
6432 let mut applicable = Vec::new();
6433 for unit in family {
6434 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6435 continue;
6436 };
6437 if metadata.is_primary() || !self.is_visible(file, unit) {
6438 continue;
6439 }
6440 if !cpp_specialization_matches(metadata, &expanded) {
6441 continue;
6442 }
6443 applicable.push((unit, metadata));
6444 }
6445 if applicable.is_empty() {
6446 return Ok(primary_unit.clone());
6447 }
6448
6449 let winners = applicable
6454 .iter()
6455 .filter(|(candidate, candidate_metadata)| {
6456 applicable.iter().all(|(other, other_metadata)| {
6457 same_visible_symbol(candidate, other)
6458 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6459 })
6460 })
6461 .copied()
6462 .collect::<Vec<_>>();
6463 let Some((selected, _)) = winners.first() else {
6464 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6467 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6468 });
6469 };
6470 if winners
6471 .iter()
6472 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6473 {
6474 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6475 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6476 });
6477 }
6478 Ok((*selected).clone())
6479 }
6480
6481 pub fn resolve_type_components_lexically(
6482 &self,
6483 analyzer: &CppGraphSource<'_>,
6484 file: &ProjectFile,
6485 components: &[String],
6486 global: bool,
6487 lexical_scope: &[String],
6488 ) -> LexicalTypeResolution {
6489 self.resolve_type_components_lexically_inner(
6490 analyzer,
6491 file,
6492 components,
6493 global,
6494 lexical_scope,
6495 TypeCandidateResolution::Canonical,
6496 )
6497 }
6498
6499 pub fn resolve_type_components_lexically_for_forward(
6500 &self,
6501 analyzer: &CppGraphSource<'_>,
6502 file: &ProjectFile,
6503 components: &[String],
6504 global: bool,
6505 lexical_scope: &[String],
6506 ) -> LexicalTypeResolution {
6507 self.resolve_type_components_lexically_inner(
6508 analyzer,
6509 file,
6510 components,
6511 global,
6512 lexical_scope,
6513 TypeCandidateResolution::PreserveAlias,
6514 )
6515 }
6516
6517 pub fn resolve_type_components_lexically_for_target(
6518 &self,
6519 analyzer: &CppGraphSource<'_>,
6520 file: &ProjectFile,
6521 components: &[String],
6522 global: bool,
6523 lexical_scope: &[String],
6524 target: &CodeUnit,
6525 ) -> LexicalTypeResolution {
6526 #[cfg(any(test, feature = "test-support"))]
6527 self.target_preserving_type_resolution_count
6528 .fetch_add(1, Ordering::Relaxed);
6529 self.resolve_type_components_lexically_inner(
6530 analyzer,
6531 file,
6532 components,
6533 global,
6534 lexical_scope,
6535 TypeCandidateResolution::PreserveTarget(target),
6536 )
6537 }
6538
6539 pub fn coarse_unqualified_type_reference_may_resolve(
6540 &self,
6541 file: &ProjectFile,
6542 name: &str,
6543 ) -> bool {
6544 if name.is_empty() {
6545 return true;
6546 }
6547 self.visible_identifier_candidates(file, name)
6548 .any(|candidate| {
6549 candidate.kind() == CodeUnitType::Class
6550 || declared_type_alias(&self.cpp_source(), candidate)
6551 })
6552 || self.visible_parser_alias_name_is_visible(file, name)
6553 }
6554
6555 #[allow(clippy::too_many_arguments)]
6556 pub fn structured_type_reference_may_resolve_to_target(
6557 &self,
6558 analyzer: &CppGraphSource<'_>,
6559 file: &ProjectFile,
6560 components: &[String],
6561 global: bool,
6562 lexical_scope: &[String],
6563 target: &CodeUnit,
6564 ) -> bool {
6565 if components.is_empty() {
6566 return true;
6567 }
6568 let Some(terminal) = components.last() else {
6569 return true;
6570 };
6571 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6572 .map(|qualified| qualified.join("::"))
6573 .collect::<Vec<_>>();
6574 let target_name = cpp_name_for(target);
6575 if qualified_tiers
6576 .iter()
6577 .any(|qualified| qualified == &target_name)
6578 {
6579 return true;
6580 }
6581
6582 let mut saw_shape_candidate = false;
6583 for candidate in self.visible_identifier_candidates(file, terminal) {
6584 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6585 {
6586 continue;
6587 }
6588 let candidate_name = cpp_name_for(candidate);
6589 let shape_matches = if global || components.len() > 1 {
6590 qualified_tiers
6591 .iter()
6592 .any(|qualified| qualified == &candidate_name)
6593 } else {
6594 true
6595 };
6596 if !shape_matches {
6597 continue;
6598 }
6599 saw_shape_candidate = true;
6600 if same_visible_symbol(candidate, target)
6601 || self.c_tag_declaration_family_matches_target(
6602 analyzer,
6603 file,
6604 std::slice::from_ref(&candidate),
6605 target,
6606 )
6607 || self.compatible_primary_template_redeclarations(candidate, target)
6608 || (declared_type_alias(analyzer, candidate)
6609 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6610 {
6611 return true;
6612 }
6613 }
6614
6615 !saw_shape_candidate
6616 }
6617
6618 fn cached_c_tag_kind(
6625 &self,
6626 analyzer: &CppGraphSource<'_>,
6627 candidate: &CodeUnit,
6628 ) -> Option<CppCTagKind> {
6629 if let Some(kind) = self
6630 .c_tag_kind_cache
6631 .lock()
6632 .expect("C tag kind cache poisoned")
6633 .get(candidate)
6634 {
6635 return *kind;
6636 }
6637 let kind = indexed_c_tag_kind(analyzer, candidate);
6638 self.c_tag_kind_cache
6639 .lock()
6640 .expect("C tag kind cache poisoned")
6641 .insert(candidate.clone(), kind);
6642 kind
6643 }
6644
6645 fn cached_unique_c_tag_complete_definition(
6646 &self,
6647 analyzer: &CppGraphSource<'_>,
6648 target: &CodeUnit,
6649 target_tag: CppCTagKind,
6650 ) -> Option<CodeUnit> {
6651 if let Some(definition) = self
6652 .c_tag_complete_definition_cache
6653 .lock()
6654 .expect("C tag complete-definition cache poisoned")
6655 .get(target)
6656 {
6657 return definition.clone();
6658 }
6659 let complete_definitions = analyzer
6660 .definitions(&target.fq_name())
6661 .filter(|candidate| {
6662 candidate.is_class()
6663 && !declared_type_alias(analyzer, candidate)
6664 && is_c_source_file(candidate.source())
6665 && analyzer.parent_of(candidate).is_none()
6666 && cpp_class_declaration_strength(analyzer, candidate)
6667 == CppClassDeclarationStrength::Full
6668 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6669 })
6670 .collect::<HashSet<_>>();
6671 let definition = (complete_definitions.len() == 1)
6672 .then(|| complete_definitions.into_iter().next())
6673 .flatten()
6674 .filter(|candidate| same_visible_symbol(candidate, target));
6675 self.c_tag_complete_definition_cache
6676 .lock()
6677 .expect("C tag complete-definition cache poisoned")
6678 .insert(target.clone(), definition.clone());
6679 definition
6680 }
6681
6682 pub fn c_tag_declaration_family_matches_target(
6683 &self,
6684 analyzer: &CppGraphSource<'_>,
6685 visible_from: &ProjectFile,
6686 candidates: &[&CodeUnit],
6687 target: &CodeUnit,
6688 ) -> bool {
6689 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6690 let candidate_evidence = candidates
6691 .iter()
6692 .map(|candidate| {
6693 (
6694 candidate.fq_name(),
6695 candidate.source().rel_path().to_path_buf(),
6696 cpp_class_declaration_strength(analyzer, candidate),
6697 indexed_c_tag_kind(analyzer, candidate),
6698 self.is_physically_visible(visible_from, candidate),
6699 )
6700 })
6701 .collect::<Vec<_>>();
6702 eprintln!(
6703 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6704 visible_from.rel_path().display(),
6705 target.fq_name(),
6706 target.source().rel_path().display(),
6707 cpp_class_declaration_strength(analyzer, target),
6708 indexed_c_tag_kind(analyzer, target),
6709 );
6710 }
6711 if candidates.is_empty()
6712 || !target.is_class()
6713 || declared_type_alias(analyzer, target)
6714 || !is_c_source_file(target.source())
6715 || analyzer.parent_of(target).is_some()
6716 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6717 {
6718 return false;
6719 }
6720 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6721 return false;
6722 };
6723 if self
6724 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6725 .is_none()
6726 {
6727 return false;
6728 }
6729 let mut saw_visible_forward = false;
6730 for candidate in candidates.iter().copied() {
6731 if candidate == target {
6732 continue;
6733 }
6734 if !candidate.is_class()
6735 || declared_type_alias(analyzer, candidate)
6736 || candidate.fq_name() != target.fq_name()
6737 || analyzer.parent_of(candidate).is_some()
6738 || cpp_class_declaration_strength(analyzer, candidate)
6739 != CppClassDeclarationStrength::Forward
6740 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6741 || !self.is_physically_visible(visible_from, candidate)
6742 {
6743 return false;
6744 }
6745 saw_visible_forward = true;
6746 }
6747 saw_visible_forward
6748 }
6749
6750 fn unique_c_tag_declaration_family(
6755 &self,
6756 analyzer: &CppGraphSource<'_>,
6757 visible_from: &ProjectFile,
6758 candidates: &[&CodeUnit],
6759 ) -> Option<CodeUnit> {
6760 let first = candidates.first()?;
6761 let target_fq_name = first.fq_name();
6762 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6763 let mut full = None;
6764 let mut saw_forward = false;
6765 for candidate in candidates.iter().copied() {
6766 if !candidate.is_class()
6767 || declared_type_alias(analyzer, candidate)
6768 || candidate.fq_name() != target_fq_name
6769 || analyzer.parent_of(candidate).is_some()
6770 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6771 {
6772 return None;
6773 }
6774 match cpp_class_declaration_strength(analyzer, candidate) {
6775 CppClassDeclarationStrength::Full
6776 if is_c_source_file(candidate.source())
6777 && full.replace(candidate.clone()).is_none() => {}
6778 CppClassDeclarationStrength::Forward
6779 if self.is_physically_visible(visible_from, candidate) =>
6780 {
6781 saw_forward = true
6782 }
6783 _ => return None,
6784 }
6785 }
6786 if saw_forward { full } else { None }
6787 }
6788
6789 pub fn target_preserving_reference_namespace(
6790 &self,
6791 analyzer: &CppGraphSource<'_>,
6792 file: &ProjectFile,
6793 identifier: &str,
6794 target: &CodeUnit,
6795 ) -> Option<Vec<String>> {
6796 let mut namespace = None;
6797 for candidate in self.visible_identifier_candidates(file, identifier) {
6798 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6799 {
6800 continue;
6801 }
6802 if !(same_visible_symbol(candidate, target)
6803 || self.compatible_primary_template_redeclarations(candidate, target)
6804 || declared_type_alias(analyzer, candidate)
6805 && self.structured_alias_primary_preserves_target(
6806 analyzer, file, candidate, target,
6807 ))
6808 {
6809 continue;
6810 }
6811 if namespace
6812 .as_ref()
6813 .is_some_and(|existing| existing != candidate.package_name())
6814 {
6815 return None;
6816 }
6817 namespace = Some(candidate.package_name().to_string());
6818 }
6819 let namespace = namespace?;
6820 Some(
6821 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6822 brokk_bifrost_core::analyzer::Language::Cpp,
6823 &namespace,
6824 ),
6825 )
6826 }
6827
6828 pub fn resolve_imported_type_candidate(
6829 &self,
6830 analyzer: &CppGraphSource<'_>,
6831 file: &ProjectFile,
6832 target: &CodeUnit,
6833 target_components: &[String],
6834 direct_target: Option<&CodeUnit>,
6835 preserve_alias: bool,
6836 ) -> LexicalTypeResolution {
6837 let candidates = [target];
6838 let resolution = if preserve_alias {
6839 TypeCandidateResolution::PreserveAlias
6840 } else {
6841 direct_target.map_or(
6842 TypeCandidateResolution::Canonical,
6843 TypeCandidateResolution::PreserveTarget,
6844 )
6845 };
6846 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6850 Ok(unit) => LexicalTypeResolution::Resolved {
6851 unit,
6852 components: target_components.to_vec(),
6853 candidates: vec![target.clone()],
6854 },
6855 Err(failure) => failure.lexical_resolution(),
6856 }
6857 }
6858
6859 fn resolve_type_components_lexically_inner(
6860 &self,
6861 analyzer: &CppGraphSource<'_>,
6862 file: &ProjectFile,
6863 components: &[String],
6864 global: bool,
6865 lexical_scope: &[String],
6866 resolution: TypeCandidateResolution<'_>,
6867 ) -> LexicalTypeResolution {
6868 if components.is_empty() {
6869 return LexicalTypeResolution::Missing;
6870 }
6871 let mut injected = self.resolve_injected_class_name(
6881 analyzer,
6882 file,
6883 components,
6884 global,
6885 lexical_scope,
6886 resolution,
6887 );
6888 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6889 let prefix_len = qualified.len().saturating_sub(components.len());
6890 if injected
6891 .as_ref()
6892 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6893 {
6894 return injected
6895 .take()
6896 .expect("injected class resolution was just present")
6897 .1;
6898 }
6899 let qualified_name = qualified.join("::");
6900 let candidates = self
6901 .type_candidates(file, &qualified_name)
6902 .into_iter()
6903 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6904 .collect::<Vec<_>>();
6905 if candidates.is_empty() {
6906 if !global && components.len() == 1 {
6907 match self.resolve_inherited_type_for_lexical_scope(
6908 analyzer,
6909 file,
6910 &qualified[..prefix_len],
6911 &components[0],
6912 resolution,
6913 ) {
6914 LexicalTypeResolution::Missing => {}
6915 inherited => return inherited,
6916 }
6917 }
6918 continue;
6919 }
6920 let candidates =
6921 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6922 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6923 Ok(unit) => unit,
6924 Err(failure) => return failure.lexical_resolution(),
6925 };
6926 return LexicalTypeResolution::Resolved {
6927 unit,
6928 components: qualified,
6929 candidates: candidates.into_iter().cloned().collect(),
6930 };
6931 }
6932 LexicalTypeResolution::Missing
6933 }
6934
6935 fn resolve_injected_class_name(
6936 &self,
6937 analyzer: &CppGraphSource<'_>,
6938 file: &ProjectFile,
6939 components: &[String],
6940 global: bool,
6941 lexical_scope: &[String],
6942 resolution: TypeCandidateResolution<'_>,
6943 ) -> Option<(usize, LexicalTypeResolution)> {
6944 if global
6945 || components.len() != 1
6946 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6947 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6948 {
6949 return None;
6950 }
6951 let name = components.first()?;
6952 let mut matches: Vec<&CodeUnit> = Vec::new();
6953 let mut owner_len = 0;
6954 for candidate in self.visible_identifier_candidates(file, name) {
6955 if !candidate.is_class()
6956 || declared_type_alias(analyzer, candidate)
6957 || candidate.identifier() != name
6958 {
6959 continue;
6960 }
6961 let candidate_scope = canonical_cpp_scope_components(candidate);
6962 if candidate_scope.len() > lexical_scope.len()
6963 || !lexical_scope.starts_with(&candidate_scope)
6964 || candidate_scope.last().is_none_or(|last| last != name)
6965 {
6966 continue;
6967 }
6968 if candidate_scope.len() > owner_len {
6969 owner_len = candidate_scope.len();
6970 matches.clear();
6971 }
6972 if candidate_scope.len() == owner_len
6973 && !matches
6974 .iter()
6975 .any(|existing| same_logical_symbol(existing, candidate))
6976 {
6977 matches.push(candidate);
6978 }
6979 }
6980 if matches.is_empty() {
6981 return None;
6982 }
6983 let owner_components = lexical_scope[..owner_len].to_vec();
6990 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6991 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6992 Ok(unit) => LexicalTypeResolution::Resolved {
6993 unit,
6994 components: owner_components,
6995 candidates: matches.into_iter().cloned().collect(),
6996 },
6997 Err(failure) => failure.lexical_resolution(),
6998 };
6999 Some((owner_len, resolution))
7000 }
7001
7002 fn resolve_inherited_type_for_lexical_scope(
7003 &self,
7004 analyzer: &CppGraphSource<'_>,
7005 file: &ProjectFile,
7006 lexical_scope: &[String],
7007 name: &str,
7008 resolution: TypeCandidateResolution<'_>,
7009 ) -> LexicalTypeResolution {
7010 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
7011 return LexicalTypeResolution::Missing;
7012 };
7013 let lexical_owner_name = lexical_scope.join("::");
7014 if lexical_owner_name.is_empty() {
7015 return LexicalTypeResolution::Missing;
7016 }
7017 let owner_candidates = self
7018 .type_candidates(file, &lexical_owner_name)
7019 .into_iter()
7020 .filter(|candidate| {
7021 canonical_cpp_name_matches(candidate, &lexical_owner_name)
7022 && !declared_type_alias(analyzer, candidate)
7023 })
7024 .collect::<Vec<_>>();
7025 if owner_candidates.is_empty() {
7026 return LexicalTypeResolution::Missing;
7027 }
7028 let physical_owner_candidates = owner_candidates
7033 .iter()
7034 .copied()
7035 .filter(|candidate| candidate.source() == file)
7036 .collect::<Vec<_>>();
7037 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
7038 owner_candidates
7039 } else {
7040 physical_owner_candidates
7041 };
7042 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
7043 return LexicalTypeResolution::Ambiguous;
7044 };
7045
7046 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
7047 let mut visited_owners = HashSet::default();
7048 while !frontier.is_empty() {
7049 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
7050 let mut next_frontier = Vec::new();
7051 for owner in frontier {
7052 if !visited_owners.insert(owner.fq_name()) {
7053 continue;
7054 }
7055 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
7056 let candidates = self
7057 .type_candidates(file, &qualified_name)
7058 .into_iter()
7059 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
7060 .collect::<Vec<_>>();
7061 if candidates.is_empty() {
7062 for ancestor in hierarchy.get_direct_ancestors(&owner) {
7063 if !next_frontier
7064 .iter()
7065 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
7066 {
7067 next_frontier.push(ancestor);
7068 }
7069 }
7070 continue;
7071 }
7072 let candidates =
7073 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
7074 let unit =
7075 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
7076 Ok(unit) => unit,
7077 Err(failure) => return failure.lexical_resolution(),
7078 };
7079 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
7080 }
7081 if let Some((unit, candidates)) = level_matches.first().cloned() {
7082 let Some(first_declaration) = candidates.first() else {
7083 return LexicalTypeResolution::Ambiguous;
7084 };
7085 if !level_matches.iter().all(|(_, declarations)| {
7086 declarations
7087 .iter()
7088 .all(|declaration| same_logical_symbol(first_declaration, declaration))
7089 }) {
7090 return LexicalTypeResolution::Ambiguous;
7091 }
7092 let mut components = lexical_scope.to_vec();
7093 components.push(name.to_string());
7094 return LexicalTypeResolution::Resolved {
7095 unit,
7096 components,
7097 candidates,
7098 };
7099 }
7100 frontier = next_frontier;
7101 }
7102 LexicalTypeResolution::Missing
7103 }
7104
7105 pub fn inherited_injected_class_owner(
7122 &self,
7123 analyzer: &CppGraphSource<'_>,
7124 file: &ProjectFile,
7125 enclosing_owner: &CodeUnit,
7126 injected_name: &str,
7127 ) -> Option<CodeUnit> {
7128 let hierarchy = analyzer.type_hierarchy_provider()?;
7129 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
7130 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
7131 while !frontier.is_empty() {
7132 let mut level_matches = Vec::new();
7133 let mut next_frontier = Vec::new();
7134 for raw_owner in frontier {
7135 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
7136 else {
7137 if raw_owner.identifier() == injected_name {
7138 return None;
7139 }
7140 continue;
7141 };
7142 let propagated = propagated_counts.entry(owner.clone()).or_default();
7143 if *propagated == 2 {
7144 continue;
7145 }
7146 *propagated += 1;
7147 if owner.identifier() == injected_name {
7148 level_matches.push(owner.clone());
7149 }
7150 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
7151 }
7152 match level_matches.as_slice() {
7153 [owner] => return Some(owner.clone()),
7154 [_, ..] => return None,
7155 [] => {}
7156 }
7157 frontier = next_frontier;
7158 }
7159 None
7160 }
7161
7162 fn resolve_type_candidates(
7167 &self,
7168 analyzer: &CppGraphSource<'_>,
7169 file: &ProjectFile,
7170 candidates: &[&CodeUnit],
7171 resolution: TypeCandidateResolution<'_>,
7172 ) -> Result<CodeUnit, TypeCandidateFailure> {
7173 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
7174 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
7175 {
7176 return Ok(unit);
7177 }
7178 match resolution {
7179 TypeCandidateResolution::Canonical => {
7180 self.canonical_type_candidate_resolution(analyzer, file, candidates)
7181 }
7182 TypeCandidateResolution::PreserveAlias => {
7183 let same_fqn_alias_family = candidates.len() > 1
7190 && candidates.iter().all(|candidate| {
7191 declared_type_alias(analyzer, candidate)
7192 && same_logical_symbol(candidates[0], candidate)
7193 })
7194 && candidates
7195 .iter()
7196 .any(|candidate| candidate.source() != candidates[0].source());
7197 if same_fqn_alias_family {
7198 let physically_visible = candidates
7199 .iter()
7200 .copied()
7201 .filter(|candidate| self.is_physically_visible(file, candidate))
7202 .collect::<Vec<_>>();
7203 if let Some(alias) = select_alias_spelling_with_agreed_targets(
7213 &physically_visible,
7214 |candidate| self.structured_alias_target(analyzer, candidate),
7215 ) {
7216 return Ok(alias.clone());
7217 }
7218 }
7219 unique_type_candidate_preserving_alias(analyzer, file, candidates)
7220 .ok_or(TypeCandidateFailure::Ambiguous)
7221 }
7222 TypeCandidateResolution::PreserveTarget(target) => self
7223 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
7224 .ok_or(TypeCandidateFailure::Ambiguous),
7225 }
7226 }
7227
7228 fn candidates_for_type_resolution<'b>(
7229 &self,
7230 analyzer: &CppGraphSource<'_>,
7231 file: &ProjectFile,
7232 candidates: &[&'b CodeUnit],
7233 resolution: TypeCandidateResolution<'_>,
7234 ) -> Vec<&'b CodeUnit> {
7235 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
7236 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
7237 if compile_proven.len() == 1 {
7238 return compile_proven;
7239 }
7240 }
7241 candidates.to_vec()
7242 }
7243
7244 fn compile_proven_type_candidates<'b>(
7251 &self,
7252 analyzer: &CppGraphSource<'_>,
7253 file: &ProjectFile,
7254 candidates: &[&'b CodeUnit],
7255 ) -> Vec<&'b CodeUnit> {
7256 let proven = self.compile_proven_guards(file);
7257 if proven.is_empty() {
7258 return Vec::new();
7259 }
7260 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7261 return Vec::new();
7262 };
7263 candidates
7264 .iter()
7265 .copied()
7266 .filter(|candidate| {
7267 let declaration_guards =
7268 declaration_guard_requirements(analyzer, self.cpp, candidate);
7269 if declaration_guards.is_empty() {
7270 return false;
7271 }
7272 if candidate.source() == file {
7273 return declaration_guards.iter().any(|(_, required)| {
7274 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
7275 });
7276 }
7277 declaration_guards.iter().any(|(_, required)| {
7278 self.foreign_declaration_reachable_from_compile_proven_guards(
7279 file,
7280 prepared.as_ref(),
7281 candidate.source(),
7282 required,
7283 usize::MAX,
7284 )
7285 })
7286 })
7287 .collect()
7288 }
7289
7290 pub fn resolve_callable_value_components_lexically(
7291 &self,
7292 analyzer: &CppGraphSource<'_>,
7293 file: &ProjectFile,
7294 owner_components: &[String],
7295 member_name: &str,
7296 global: bool,
7297 lexical_scope: &[String],
7298 ) -> LexicalCallableValueResolution {
7299 if owner_components.is_empty() || member_name.is_empty() {
7300 return LexicalCallableValueResolution::Missing;
7301 }
7302 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7303 let owner_name = qualified_owner.join("::");
7304 let type_candidates = self
7305 .type_candidates(file, &owner_name)
7306 .into_iter()
7307 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7308 .collect::<Vec<_>>();
7309 let resolved_type = if type_candidates.is_empty() {
7310 None
7311 } else {
7312 let Some(unit) =
7313 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7314 else {
7315 return LexicalCallableValueResolution::Ambiguous;
7316 };
7317 Some(unit)
7318 };
7319
7320 let mut qualified_callable = qualified_owner;
7321 qualified_callable.push(member_name.to_string());
7322 let callable_name = qualified_callable.join("::");
7323 let free_function = self
7324 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7325 .into_iter()
7326 .find(|candidate| {
7327 canonical_cpp_name_matches(candidate, &callable_name)
7328 && type_owner_of(analyzer, candidate).is_none()
7329 })
7330 .cloned();
7331
7332 match (resolved_type, free_function) {
7333 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7334 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7335 (None, Some(function)) => {
7336 return LexicalCallableValueResolution::FreeFunction(function);
7337 }
7338 (None, None) => {}
7339 }
7340 }
7341 LexicalCallableValueResolution::Missing
7342 }
7343
7344 fn resolve_type_for_declaration(
7345 &self,
7346 visible_from: &ProjectFile,
7347 declaration: &CodeUnit,
7348 raw_name: &str,
7349 ) -> Option<CodeUnit> {
7350 let normalized = normalize_reference_name(raw_name)?;
7351 if !normalized.contains("::")
7352 && let Some(namespace) = cpp_namespace_for(declaration)
7353 {
7354 for prefix in namespace_prefixes(&namespace) {
7355 let qualified = format!("{prefix}::{normalized}");
7356 if let Some(unit) = self
7357 .type_candidates(visible_from, &qualified)
7358 .into_iter()
7359 .next()
7360 {
7361 return Some(unit.clone());
7362 }
7363 }
7364 }
7365 self.resolve_type(visible_from, raw_name)
7366 }
7367
7368 fn resolve_unique_canonical_type_for_declaration(
7369 &self,
7370 analyzer: &CppGraphSource<'_>,
7371 visible_from: &ProjectFile,
7372 declaration: &CodeUnit,
7373 raw_name: &str,
7374 ) -> Option<CodeUnit> {
7375 let current = self.resolve_defining_type_for_declaration(
7376 analyzer,
7377 visible_from,
7378 declaration,
7379 raw_name,
7380 )?;
7381 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7382 .ok()
7383 }
7384
7385 pub fn canonical_type_unit(
7386 &self,
7387 analyzer: &CppGraphSource<'_>,
7388 visible_from: &ProjectFile,
7389 unit: &CodeUnit,
7390 ) -> Option<CodeUnit> {
7391 self.canonical_type_resolution(analyzer, visible_from, unit)
7392 .ok()
7393 }
7394
7395 pub fn canonical_type_unit_in_context(
7403 &self,
7404 analyzer: &CppGraphSource<'_>,
7405 visible_from: &ProjectFile,
7406 reference: Node<'_>,
7407 unit: &CodeUnit,
7408 ) -> Option<CodeUnit> {
7409 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7410 {
7411 return None;
7412 }
7413 self.canonical_type_resolution(analyzer, visible_from, unit)
7414 .ok()
7415 }
7416
7417 pub fn guarded_alias_selection_at_reference(
7433 &self,
7434 analyzer: &CppGraphSource<'_>,
7435 file: &ProjectFile,
7436 reference: Node<'_>,
7437 candidates: &[CodeUnit],
7438 ) -> Option<GuardedAliasSelection> {
7439 const MAX_GUARDED_ALIAS_STEPS: usize = 64;
7440 let mut conditional = false;
7441 let mut complete = true;
7442 let mut selected = Vec::new();
7443 let mut terminals: Vec<(CodeUnit, bool)> = Vec::new();
7444 let mut visited = HashSet::default();
7445 let mut pending = candidates
7448 .iter()
7449 .map(|candidate| (candidate.clone(), true, 0, true))
7450 .collect::<Vec<_>>();
7451 let mut steps = 0;
7452 while let Some((unit, proven, depth, top_level)) = pending.pop() {
7453 if !visited.insert((unit.clone(), proven)) {
7454 continue;
7455 }
7456 steps += 1;
7457 if steps > MAX_GUARDED_ALIAS_STEPS || depth > MAX_COMPARABLE_ALIAS_HOPS {
7458 return None;
7459 }
7460 let arms = match self.conditional_alias_arms(analyzer, file, &unit) {
7461 Some(arms) => {
7462 conditional = true;
7463 let evidence = arms
7464 .into_iter()
7465 .map(|arm| {
7466 let evidence = self.alias_arm_evidence(analyzer, file, &arm, reference);
7467 (arm, evidence)
7468 })
7469 .collect::<Vec<_>>();
7470 let implied = evidence
7474 .iter()
7475 .any(|(_, evidence)| *evidence == AliasArmEvidence::Implied);
7476 for (arm, _) in &evidence {
7477 visited.insert((arm.clone(), proven));
7478 }
7479 evidence
7480 .into_iter()
7481 .filter_map(|(arm, evidence)| match evidence {
7482 AliasArmEvidence::Implied => Some((arm, proven)),
7483 AliasArmEvidence::Compatible if !implied => Some((arm, false)),
7484 AliasArmEvidence::Compatible | AliasArmEvidence::Contradicted => None,
7485 })
7486 .collect::<Vec<_>>()
7487 }
7488 None => vec![(unit, proven)],
7489 };
7490 for (arm, proven) in arms {
7491 if top_level {
7492 selected.push(arm.clone());
7493 }
7494 match self.structured_alias_target(analyzer, &arm) {
7495 None | Some(StructuredAliasTarget::Builtin) if arm.is_class() => {
7496 terminals.push((arm, proven));
7497 }
7498 None | Some(StructuredAliasTarget::Builtin) => complete = false,
7499 Some(target) => {
7500 match self.structured_alias_target_resolution(file, &arm, &target) {
7501 Ok(next) => pending.push((next, proven, depth + 1, false)),
7502 Err(_) => complete = false,
7503 }
7504 }
7505 }
7506 }
7507 }
7508 if !conditional {
7509 return None;
7510 }
7511 let mut targets: Vec<CodeUnit> = Vec::new();
7512 for (terminal, _) in &terminals {
7513 if !targets
7514 .iter()
7515 .any(|existing| same_visible_symbol(existing, terminal))
7516 {
7517 targets.push(terminal.clone());
7518 }
7519 }
7520 if complete
7521 && let [unit] = targets.as_slice()
7522 && terminals.iter().all(|(_, proven)| *proven)
7523 {
7524 return Some(GuardedAliasSelection::Proven {
7525 unit: unit.clone(),
7526 candidates: selected,
7527 });
7528 }
7529 Some(GuardedAliasSelection::Alternatives(targets))
7530 }
7531
7532 fn conditional_alias_arms(
7535 &self,
7536 analyzer: &CppGraphSource<'_>,
7537 file: &ProjectFile,
7538 unit: &CodeUnit,
7539 ) -> Option<Vec<CodeUnit>> {
7540 if !declared_type_alias(analyzer, unit) || self.cpp_template_metadata.contains_key(unit) {
7541 return None;
7542 }
7543 let mut arms = Vec::new();
7544 for candidate in self
7545 .visible_identifier_candidates(file, unit.identifier())
7546 .filter(|candidate| {
7547 candidate.kind() == unit.kind()
7548 && candidate.fq_name() == unit.fq_name()
7549 && declared_type_alias(analyzer, candidate)
7550 })
7551 {
7552 if self.cpp_template_metadata.contains_key(candidate) {
7553 return None;
7554 }
7555 if !arms.contains(candidate) {
7556 arms.push(candidate.clone());
7557 }
7558 }
7559 let refs = arms.iter().collect::<Vec<_>>();
7560 (arms.len() > 1
7561 && select_alias_spelling_with_agreed_targets(&refs, |candidate| {
7562 self.structured_alias_target(analyzer, candidate)
7563 })
7564 .is_none())
7565 .then_some(arms)
7566 }
7567
7568 fn alias_arm_evidence(
7572 &self,
7573 analyzer: &CppGraphSource<'_>,
7574 file: &ProjectFile,
7575 arm: &CodeUnit,
7576 reference: Node<'_>,
7577 ) -> AliasArmEvidence {
7578 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
7579 return AliasArmEvidence::Compatible;
7580 };
7581 let effective = self.effective_reference_guards(
7582 file,
7583 preprocessor_guard_environment(reference, prepared.source()).as_ref(),
7584 );
7585 let requirements = declaration_guard_requirements(analyzer, self.cpp, arm);
7586 if requirements
7587 .iter()
7588 .any(|(_, required)| guard_requirements_hold_at_reference(required, effective.as_ref()))
7589 && self.external_type_candidate_visible_in_context(analyzer, file, arm, reference)
7590 {
7591 AliasArmEvidence::Implied
7592 } else if requirements.is_empty()
7593 || requirements
7594 .iter()
7595 .any(|(_, required)| guards_compatible_at_reference(required, effective.as_ref()))
7596 {
7597 AliasArmEvidence::Compatible
7598 } else {
7599 AliasArmEvidence::Contradicted
7600 }
7601 }
7602
7603 fn canonical_type_resolution(
7611 &self,
7612 analyzer: &CppGraphSource<'_>,
7613 visible_from: &ProjectFile,
7614 unit: &CodeUnit,
7615 ) -> Result<CodeUnit, TypeCandidateFailure> {
7616 let mut current = unit.clone();
7617 let mut seen_aliases = HashSet::default();
7618 loop {
7619 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7620 return current
7621 .is_class()
7622 .then_some(current)
7623 .ok_or(TypeCandidateFailure::Unresolvable);
7624 };
7625 if matches!(target, StructuredAliasTarget::Builtin) {
7626 return current
7627 .is_class()
7628 .then_some(current)
7629 .ok_or(TypeCandidateFailure::Unresolvable);
7630 }
7631 if !seen_aliases.insert(current.clone()) {
7632 return Err(TypeCandidateFailure::Unresolvable);
7633 }
7634 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7635 }
7636 }
7637
7638 pub fn canonical_visible_full_type_unit(
7639 &self,
7640 analyzer: &CppGraphSource<'_>,
7641 visible_from: &ProjectFile,
7642 unit: &CodeUnit,
7643 ) -> Option<CodeUnit> {
7644 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7645 if cpp_class_declaration_strength(analyzer, &canonical)
7646 != CppClassDeclarationStrength::Forward
7647 {
7648 return Some(canonical);
7649 }
7650 let mut full = Vec::new();
7651 for candidate in self
7652 .visible_identifier_candidates(visible_from, canonical.identifier())
7653 .filter(|candidate| {
7654 candidate.is_class()
7655 && candidate.fq_name() == canonical.fq_name()
7656 && cpp_class_declaration_strength(analyzer, candidate)
7657 == CppClassDeclarationStrength::Full
7658 })
7659 {
7660 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7661 full.push(candidate.clone());
7662 }
7663 }
7664 match full.len() {
7665 0 => Some(canonical),
7666 1 => full.pop(),
7667 _ => None,
7668 }
7669 }
7670
7671 fn resolve_structured_alias_target(
7672 &self,
7673 visible_from: &ProjectFile,
7674 declaration: &CodeUnit,
7675 target: &StructuredAliasTarget,
7676 ) -> Option<CodeUnit> {
7677 self.structured_alias_target_resolution(visible_from, declaration, target)
7678 .ok()
7679 }
7680
7681 fn structured_alias_target_resolution(
7682 &self,
7683 visible_from: &ProjectFile,
7684 declaration: &CodeUnit,
7685 target: &StructuredAliasTarget,
7686 ) -> Result<CodeUnit, TypeCandidateFailure> {
7687 let primary =
7688 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7689 let StructuredAliasTarget::Named { arguments, .. } = target else {
7690 return Err(TypeCandidateFailure::Unresolvable);
7691 };
7692 match arguments {
7693 Some(arguments) => self
7694 .resolve_template_arguments(visible_from, primary, arguments)
7695 .map_err(|error| match error {
7696 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7697 TypeCandidateFailure::Ambiguous
7698 }
7699 _ => TypeCandidateFailure::Unresolvable,
7700 }),
7701 None => Ok(primary),
7702 }
7703 }
7704
7705 fn resolve_structured_alias_primary(
7706 &self,
7707 visible_from: &ProjectFile,
7708 declaration: &CodeUnit,
7709 target: &StructuredAliasTarget,
7710 ) -> Option<CodeUnit> {
7711 self.structured_alias_primary_resolution(visible_from, declaration, target)
7712 .ok()
7713 }
7714
7715 fn structured_alias_primary_resolution(
7716 &self,
7717 visible_from: &ProjectFile,
7718 declaration: &CodeUnit,
7719 target: &StructuredAliasTarget,
7720 ) -> Result<CodeUnit, TypeCandidateFailure> {
7721 let StructuredAliasTarget::Named {
7722 components, global, ..
7723 } = target
7724 else {
7725 return Err(TypeCandidateFailure::Unresolvable);
7726 };
7727 let qualified = components.join("::");
7728 let candidates = if *global {
7729 let mut candidates = self.type_candidates(visible_from, &qualified);
7736 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7737 candidates
7738 } else {
7739 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7740 };
7741 logical_type_candidate(candidates)
7742 }
7743
7744 pub fn structured_alias_primary_preserves_target(
7745 &self,
7746 analyzer: &CppGraphSource<'_>,
7747 visible_from: &ProjectFile,
7748 candidate: &CodeUnit,
7749 target: &CodeUnit,
7750 ) -> bool {
7751 let mut current = candidate.clone();
7752 let mut seen = HashSet::default();
7753 let mut matched_target = false;
7754 loop {
7755 if same_visible_symbol(¤t, target)
7756 || self.compatible_primary_template_redeclarations(¤t, target)
7757 {
7758 matched_target = true;
7759 }
7760 if !seen.insert(current.clone()) {
7761 return false;
7762 }
7763 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7764 return matched_target;
7765 };
7766 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7767 return matched_target;
7768 };
7769 let Some(primary) =
7770 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7771 else {
7772 return matched_target;
7778 };
7779 current = primary;
7780 }
7781 }
7782
7783 fn lexical_type_candidates_match_target(
7784 &self,
7785 analyzer: &CppGraphSource<'_>,
7786 unit: &CodeUnit,
7787 candidates: &[CodeUnit],
7788 target: &CodeUnit,
7789 ) -> bool {
7790 same_visible_symbol(unit, target)
7791 || self.same_template_member_identity(analyzer, unit, target)
7792 || candidates.iter().any(|candidate| {
7793 same_visible_symbol(candidate, target)
7794 || self.same_template_member_identity(analyzer, candidate, target)
7795 })
7796 }
7797
7798 pub fn structured_class_alias_resolves_to_target(
7799 &self,
7800 analyzer: &CppGraphSource<'_>,
7801 visible_from: &ProjectFile,
7802 alias: &CodeUnit,
7803 target: &CodeUnit,
7804 ) -> bool {
7805 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7806 return false;
7807 };
7808 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7809 return false;
7810 };
7811 let StructuredAliasTarget::Named {
7812 components, global, ..
7813 } = &alias_target
7814 else {
7815 return false;
7816 };
7817 let lexical_scope = canonical_cpp_scope_components(&owner);
7818 match self.resolve_type_components_lexically_for_target(
7819 analyzer,
7820 visible_from,
7821 components,
7822 *global,
7823 &lexical_scope,
7824 target,
7825 ) {
7826 LexicalTypeResolution::Resolved {
7827 unit, candidates, ..
7828 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7829 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7830 self.structured_alias_primary_preserves_target(
7831 analyzer,
7832 visible_from,
7833 alias,
7834 target,
7835 ) || self.flattened_macro_namespace_alias_target_matches(
7836 analyzer,
7837 visible_from,
7838 alias,
7839 &alias_target,
7840 target,
7841 )
7842 }
7843 }
7844 }
7845
7846 pub fn structured_class_alias_path_preserves_target(
7854 &self,
7855 analyzer: &CppGraphSource<'_>,
7856 visible_from: &ProjectFile,
7857 alias: &CodeUnit,
7858 target: &CodeUnit,
7859 ) -> bool {
7860 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7861 return false;
7862 };
7863 let Some(StructuredAliasTarget::Named {
7864 components, global, ..
7865 }) = self.structured_alias_target(analyzer, alias)
7866 else {
7867 return false;
7868 };
7869 let lexical_scope = canonical_cpp_scope_components(&owner);
7870 (1..components.len()).rev().any(|component_count| {
7871 matches!(
7872 self.resolve_type_components_lexically_for_target(
7873 analyzer,
7874 visible_from,
7875 &components[..component_count],
7876 global,
7877 &lexical_scope,
7878 target,
7879 ),
7880 LexicalTypeResolution::Resolved {
7881 ref unit,
7882 ref candidates,
7883 ..
7884 } if self.lexical_type_candidates_match_target(
7885 analyzer,
7886 unit,
7887 candidates,
7888 target,
7889 )
7890 )
7891 })
7892 }
7893
7894 fn flattened_macro_namespace_alias_target_matches(
7895 &self,
7896 analyzer: &CppGraphSource<'_>,
7897 visible_from: &ProjectFile,
7898 alias: &CodeUnit,
7899 alias_target: &StructuredAliasTarget,
7900 target: &CodeUnit,
7901 ) -> bool {
7902 let StructuredAliasTarget::Named {
7903 components,
7904 global: false,
7905 arguments: None,
7906 } = alias_target
7907 else {
7908 return false;
7909 };
7910 let Some((target_name, namespace_components)) = components.split_last() else {
7911 return false;
7912 };
7913 if namespace_components.is_empty()
7914 || target_name != target.identifier()
7915 || alias.source() != target.source()
7916 || alias.source() != visible_from
7917 || !target.is_class()
7918 || declared_type_alias(analyzer, target)
7919 {
7920 return false;
7921 }
7922 if self
7923 .resolve_structured_alias_target(visible_from, alias, alias_target)
7924 .is_some()
7925 {
7926 return false;
7927 }
7928
7929 let Some(alias_start) = analyzer
7930 .declaration_source_occurrences(alias)
7931 .into_iter()
7932 .flatten()
7933 .map(|occurrence| occurrence.range.start_byte)
7934 .min()
7935 else {
7936 return false;
7937 };
7938 let has_matching_declaration = analyzer
7939 .declaration_source_occurrences(target)
7940 .into_iter()
7941 .flatten()
7942 .any(|occurrence| {
7943 occurrence.range.end_byte <= alias_start
7944 && occurrence.fact.flattened_macro_namespace.as_deref()
7945 == Some(namespace_components)
7946 });
7947 if !has_matching_declaration {
7948 return false;
7949 }
7950
7951 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7952 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7953 guard_requirement_sets_match(&alias_guards, &target_guards)
7954 }
7955
7956 pub fn template_alias_arguments_preserve_target(
7957 &self,
7958 analyzer: &CppGraphSource<'_>,
7959 visible_from: &ProjectFile,
7960 alias: &CodeUnit,
7961 arguments: &[CppTemplateExpression],
7962 target: &CodeUnit,
7963 ) -> bool {
7964 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7965 return false;
7966 };
7967 if metadata.alias_target.is_none()
7968 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7969 {
7970 return false;
7971 }
7972 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7973 }
7974
7975 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7976 self.cpp_template_metadata
7977 .get(unit)
7978 .is_some_and(CppTemplateMetadata::is_primary)
7979 }
7980
7981 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7982 self.cpp_template_metadata
7983 .get(unit)
7984 .is_some_and(CppTemplateMetadata::is_specialization)
7985 }
7986
7987 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7988 same_visible_symbol(left, right)
7989 || self.compatible_primary_template_redeclarations(left, right)
7990 }
7991
7992 pub fn same_template_member_identity(
7993 &self,
7994 analyzer: &CppGraphSource<'_>,
7995 left: &CodeUnit,
7996 right: &CodeUnit,
7997 ) -> bool {
7998 if same_visible_symbol(left, right) {
7999 return true;
8000 }
8001 if left.kind() != right.kind()
8002 || left.identifier() != right.identifier()
8003 || left.signature() != right.signature()
8004 {
8005 return false;
8006 }
8007 let (Some(left_owner), Some(right_owner)) =
8008 (analyzer.parent_of(left), analyzer.parent_of(right))
8009 else {
8010 return false;
8011 };
8012 left_owner.is_class()
8013 && right_owner.is_class()
8014 && self.same_template_owner_identity(&left_owner, &right_owner)
8015 }
8016
8017 fn unique_canonical_type_candidate(
8018 &self,
8019 analyzer: &CppGraphSource<'_>,
8020 visible_from: &ProjectFile,
8021 candidates: &[&CodeUnit],
8022 ) -> Option<CodeUnit> {
8023 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
8024 .ok()
8025 }
8026
8027 fn canonical_type_candidate_resolution(
8028 &self,
8029 analyzer: &CppGraphSource<'_>,
8030 visible_from: &ProjectFile,
8031 candidates: &[&CodeUnit],
8032 ) -> Result<CodeUnit, TypeCandidateFailure> {
8033 let mut canonical = Vec::new();
8034 for candidate in candidates {
8035 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
8036 if canonical
8037 .iter()
8038 .any(|existing| same_visible_symbol(existing, &resolved))
8039 {
8040 continue;
8041 }
8042 if let Some(existing) = canonical.iter_mut().find(|existing| {
8043 self.compatible_primary_template_redeclarations(existing, &resolved)
8044 }) {
8045 if matches!(
8054 (
8055 cpp_class_declaration_strength(analyzer, existing),
8056 cpp_class_declaration_strength(analyzer, &resolved),
8057 ),
8058 (
8059 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
8060 CppClassDeclarationStrength::Full,
8061 ) | (
8062 CppClassDeclarationStrength::Unknown,
8063 CppClassDeclarationStrength::Forward,
8064 )
8065 ) {
8066 *existing = resolved;
8067 }
8068 continue;
8069 }
8070 canonical.push(resolved);
8071 if canonical.len() > 1 {
8072 return Err(TypeCandidateFailure::Ambiguous);
8073 }
8074 }
8075 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
8076 }
8077
8078 pub fn unique_type_candidate_preserving_target(
8079 &self,
8080 analyzer: &CppGraphSource<'_>,
8081 visible_from: &ProjectFile,
8082 candidates: &[&CodeUnit],
8083 target: &CodeUnit,
8084 ) -> Option<CodeUnit> {
8085 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
8096 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8097 {
8098 return Some(target.clone());
8099 }
8100 let mut resolved_candidates = Vec::new();
8101 for candidate in candidates {
8102 let Some(resolved) =
8108 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8109 else {
8110 continue;
8111 };
8112 if resolved_candidates
8113 .iter()
8114 .any(|existing| same_visible_symbol(existing, &resolved))
8115 {
8116 continue;
8117 }
8118 resolved_candidates.push(resolved);
8119 }
8120 match resolved_candidates.as_slice() {
8121 [] => None,
8122 [single] => Some(single.clone()),
8123 _ => self
8128 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
8129 .map(|_| target.clone()),
8130 }
8131 }
8132
8133 pub fn same_fqn_type_spelling_for_target<'b>(
8150 &self,
8151 analyzer: &CppGraphSource<'_>,
8152 visible_from: &ProjectFile,
8153 candidates: &[&'b CodeUnit],
8154 target: &CodeUnit,
8155 ) -> Option<&'b CodeUnit> {
8156 let [first, rest @ ..] = candidates else {
8157 return None;
8158 };
8159 if rest.is_empty()
8160 || !rest.iter().all(|candidate| {
8161 candidate.kind() == first.kind()
8162 && candidate.fq_name() == first.fq_name()
8163 && candidate.source() == first.source()
8164 })
8165 {
8166 return None;
8167 }
8168 candidates
8169 .iter()
8170 .copied()
8171 .find(|candidate| same_symbol(candidate, target))
8172 .or_else(|| {
8173 candidates.iter().copied().find(|candidate| {
8174 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
8175 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
8176 })
8177 })
8178 }
8179
8180 pub fn alternate_same_fqn_type_declarations(
8181 &self,
8182 analyzer: &CppGraphSource<'_>,
8183 candidates: &[&CodeUnit],
8184 target: &CodeUnit,
8185 ) -> bool {
8186 let Some(first) = candidates.first() else {
8187 return false;
8188 };
8189 let same_api = first.kind() == target.kind()
8190 && first.fq_name() == target.fq_name()
8191 && first.source() == target.source()
8192 && candidates.iter().all(|candidate| {
8193 candidate.kind() == target.kind()
8194 && candidate.fq_name() == target.fq_name()
8195 && candidate.source() == target.source()
8196 })
8197 && candidates
8198 .iter()
8199 .any(|candidate| same_symbol(candidate, target))
8200 && candidates
8201 .iter()
8202 .any(|candidate| !same_logical_symbol(candidate, target));
8203 if !same_api {
8204 return false;
8205 }
8206
8207 let requirements = candidates
8208 .iter()
8209 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8210 .collect::<Vec<_>>();
8211 requirements.len() > 1
8212 && requirements
8213 .iter()
8214 .all(|requirement| !requirement.is_empty())
8215 && requirements.iter().enumerate().all(|(index, left)| {
8216 requirements[index + 1..].iter().all(|right| {
8217 left.iter().all(|(_, left_guards)| {
8218 right.iter().all(|(_, right_guards)| {
8219 merge_preprocessor_guards(left_guards, right_guards).is_none()
8220 })
8221 })
8222 })
8223 })
8224 }
8225
8226 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
8227 let mut pending = vec![terms.to_vec()];
8228 while let Some(branch_terms) = pending.pop() {
8229 let mut normalized = Vec::new();
8230 let mut covers_branch = false;
8231 for term in branch_terms {
8232 if term.iter().any(|guard| term.contains(&guard.negated())) {
8233 continue;
8234 }
8235 if term.is_empty() {
8236 covers_branch = true;
8237 break;
8238 }
8239 if !normalized.iter().any(|existing| existing == &term) {
8240 normalized.push(term);
8241 }
8242 }
8243 if covers_branch {
8244 continue;
8245 }
8246 let Some(split_guard) = normalized
8247 .iter()
8248 .flat_map(|term| term.iter())
8249 .next()
8250 .cloned()
8251 else {
8252 return false;
8253 };
8254 let negated_guard = split_guard.negated();
8255 let mut when_defined = Vec::new();
8256 let mut when_undefined = Vec::new();
8257 for term in normalized {
8258 if term.contains(&negated_guard) {
8259 } else if term.contains(&split_guard) {
8261 let mut reduced = term.clone();
8262 reduced.remove(&split_guard);
8263 when_defined.push(reduced);
8264 } else {
8265 when_defined.push(term.clone());
8266 }
8267 if term.contains(&split_guard) {
8268 } else if term.contains(&negated_guard) {
8270 let mut reduced = term;
8271 reduced.remove(&negated_guard);
8272 when_undefined.push(reduced);
8273 } else {
8274 when_undefined.push(term);
8275 }
8276 }
8277 pending.push(when_defined);
8278 pending.push(when_undefined);
8279 }
8280 true
8281 }
8282
8283 fn declarations_share_exhaustive_conditional_family(
8292 &self,
8293 analyzer: &CppGraphSource<'_>,
8294 candidates: &[&CodeUnit],
8295 ) -> Option<(usize, usize)> {
8296 let mut family_range = None;
8297 for candidate in candidates {
8298 let mut candidate_family = None;
8299 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
8300 let key = occurrence.exhaustive_family?;
8301 if candidate_family.is_some_and(|existing| existing != key) {
8302 return None;
8303 }
8304 candidate_family = Some(key);
8305 }
8306 let candidate_family = candidate_family?;
8307 if family_range.is_some_and(|existing| existing != candidate_family) {
8308 return None;
8309 }
8310 family_range = Some(candidate_family);
8311 }
8312 family_range
8313 }
8314
8315 pub fn complementary_same_fqn_type_declarations(
8316 &self,
8317 analyzer: &CppGraphSource<'_>,
8318 candidates: &[&CodeUnit],
8319 target: &CodeUnit,
8320 ) -> bool {
8321 if candidates.len() < 2
8322 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
8323 || self
8324 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
8325 .is_none()
8326 {
8327 return false;
8328 }
8329 Self::preprocessor_guard_terms_cover_all_paths(
8330 &self.declaration_family_guard_terms(analyzer, candidates),
8331 )
8332 }
8333
8334 fn declaration_family_guard_terms(
8335 &self,
8336 analyzer: &CppGraphSource<'_>,
8337 candidates: &[&CodeUnit],
8338 ) -> Vec<HashSet<PreprocessorGuard>> {
8339 candidates
8340 .iter()
8341 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
8342 .map(|(_, guards)| guards)
8343 .collect()
8344 }
8345
8346 fn exhaustive_guard_family_activation(
8362 &self,
8363 analyzer: &CppGraphSource<'_>,
8364 candidate: &CodeUnit,
8365 reference: &CallableReferenceContext<'_>,
8366 ) -> Option<usize> {
8367 if !analyzer
8370 .declaration_source_occurrences(candidate)?
8371 .iter()
8372 .any(|occurrence| occurrence.fact.callable_activation.is_some())
8373 {
8374 return None;
8375 }
8376 let family = self
8377 .visible_identifier_candidates(reference.file, candidate.identifier())
8378 .filter(|peer| {
8379 peer.kind() == candidate.kind()
8380 && peer.fq_name() == candidate.fq_name()
8381 && peer.source() == candidate.source()
8382 })
8383 .collect::<Vec<_>>();
8384 let (_, family_end) =
8385 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
8386 if !Self::preprocessor_guard_terms_cover_all_paths(
8387 &self.declaration_family_guard_terms(analyzer, &family),
8388 ) {
8389 return None;
8390 }
8391 let effective = self.effective_reference_guards(reference.file, reference.guards());
8399 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
8400 .iter()
8401 .any(|(_, guards)| guards_compatible_at_reference(guards, effective.as_ref()))
8402 {
8403 return None;
8404 }
8405 (first_declaration_byte(analyzer, candidate)?
8406 == family
8407 .iter()
8408 .filter_map(|peer| first_declaration_byte(analyzer, peer))
8409 .min()?)
8410 .then_some(family_end)
8411 }
8412
8413 fn type_candidate_preserving_target(
8414 &self,
8415 analyzer: &CppGraphSource<'_>,
8416 visible_from: &ProjectFile,
8417 candidate: &CodeUnit,
8418 target: &CodeUnit,
8419 ) -> Option<CodeUnit> {
8420 let mut current = candidate.clone();
8421 let mut matched_target = same_visible_symbol(¤t, target)
8422 || self.compatible_primary_template_redeclarations(¤t, target);
8423 let mut seen = HashSet::default();
8424 loop {
8425 if !seen.insert(current.clone()) {
8426 return None;
8427 }
8428 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8429 return matched_target
8430 .then(|| target.clone())
8431 .or_else(|| current.is_class().then_some(current));
8432 };
8433 if self.flattened_macro_namespace_alias_target_matches(
8434 analyzer,
8435 visible_from,
8436 ¤t,
8437 &alias_target,
8438 target,
8439 ) {
8440 return Some(target.clone());
8441 }
8442 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8443 return matched_target
8444 .then(|| target.clone())
8445 .or_else(|| current.is_class().then_some(current));
8446 }
8447 if !self.cpp_template_metadata.contains_key(¤t)
8455 && let Some(primary) =
8456 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8457 && (same_visible_symbol(&primary, target)
8458 || self.compatible_primary_template_redeclarations(&primary, target))
8459 {
8460 return Some(target.clone());
8461 }
8462 if same_visible_symbol(¤t, target) {
8463 return Some(target.clone());
8464 }
8465 if self.cpp_template_metadata.contains_key(¤t) {
8466 return None;
8467 }
8468 let Some(next) =
8469 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
8470 else {
8471 return matched_target.then(|| target.clone());
8472 };
8473 current = next;
8474 matched_target |= same_visible_symbol(¤t, target)
8475 || self.compatible_primary_template_redeclarations(¤t, target);
8476 }
8477 }
8478
8479 fn compatible_primary_template_redeclarations(
8480 &self,
8481 left: &CodeUnit,
8482 right: &CodeUnit,
8483 ) -> bool {
8484 let (Some(left_metadata), Some(right_metadata)) = (
8485 self.cpp_template_metadata.get(left),
8486 self.cpp_template_metadata.get(right),
8487 ) else {
8488 return false;
8489 };
8490 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8491 && left_metadata.is_primary()
8492 && right_metadata.is_primary()
8493 && cpp_reconcile_primary_template_parameters(
8494 &[(left, left_metadata), (right, right_metadata)],
8495 right,
8496 )
8497 .is_some()
8498 }
8499
8500 fn alias_candidate_may_preserve_target(
8501 &self,
8502 analyzer: &CppGraphSource<'_>,
8503 visible_from: &ProjectFile,
8504 candidate: &CodeUnit,
8505 target: &CodeUnit,
8506 ) -> bool {
8507 let mut pending = vec![candidate.clone()];
8511 let mut seen = HashSet::default();
8512 while let Some(current) = pending.pop() {
8513 if same_visible_symbol(¤t, target)
8514 || self.compatible_primary_template_redeclarations(¤t, target)
8515 {
8516 return true;
8517 }
8518 if self.cpp_template_metadata.contains_key(¤t) {
8519 return true;
8520 }
8521 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8522 continue;
8523 };
8524 let StructuredAliasTarget::Named {
8525 components,
8526 global,
8527 arguments,
8528 } = alias_target
8529 else {
8530 continue;
8531 };
8532 if arguments.is_some() || !seen.insert(current.clone()) {
8533 return true;
8534 }
8535 let qualified = components.join("::");
8536 let next = if global {
8537 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8538 } else {
8539 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8540 };
8541 let Some(next) = next else {
8542 return true;
8543 };
8544 match self.conditional_alias_arms(analyzer, visible_from, &next) {
8545 Some(arms) => pending.extend(arms),
8546 None => pending.push(next),
8547 }
8548 }
8549 false
8550 }
8551
8552 fn alias_candidate_structurally_reaches_target(
8553 &self,
8554 analyzer: &CppGraphSource<'_>,
8555 visible_from: &ProjectFile,
8556 candidate: &CodeUnit,
8557 target: &CodeUnit,
8558 ) -> bool {
8559 let mut pending = vec![candidate.clone()];
8562 let mut seen = HashSet::default();
8563 while let Some(current) = pending.pop() {
8564 if same_visible_symbol(¤t, target)
8565 || self.compatible_primary_template_redeclarations(¤t, target)
8566 || self
8567 .cpp_template_metadata
8568 .get(¤t)
8569 .zip(self.cpp_template_metadata.get(target))
8570 .is_some_and(|(current, target)| {
8571 current.primary_fq_name == target.primary_fq_name
8572 })
8573 {
8574 return true;
8575 }
8576 if !seen.insert(current.clone()) {
8577 continue;
8578 }
8579 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8580 continue;
8581 };
8582 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8583 && components[..components.len().saturating_sub(1)]
8584 .iter()
8585 .any(|component| {
8586 self.type_reference_component_directly_names_target(component, target)
8587 })
8588 && self.structured_class_alias_path_preserves_target(
8589 analyzer,
8590 visible_from,
8591 ¤t,
8592 target,
8593 )
8594 {
8595 return true;
8596 }
8597 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8598 continue;
8599 }
8600 let Some(primary) =
8601 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8602 else {
8603 continue;
8604 };
8605 match self.conditional_alias_arms(analyzer, visible_from, &primary) {
8606 Some(arms) => pending.extend(arms),
8607 None => pending.push(primary),
8608 }
8609 }
8610 false
8611 }
8612
8613 pub fn qualified_alias_reference_may_reach_target(
8625 &self,
8626 analyzer: &CppGraphSource<'_>,
8627 file: &ProjectFile,
8628 components: &[String],
8629 global: bool,
8630 target: &CodeUnit,
8631 ) -> bool {
8632 for end in 0..components.len() {
8633 let spelled = &components[..=end];
8634 for candidate in self
8635 .visible_identifier_candidates(file, &components[end])
8636 .filter(|candidate| declared_type_alias(analyzer, candidate))
8637 {
8638 let candidate_components = canonical_cpp_scope_components(candidate);
8639 let shape_matches = if global {
8640 candidate_components == spelled
8641 } else {
8642 candidate_components.ends_with(spelled)
8643 };
8644 if shape_matches
8645 && self.alias_candidate_structurally_reaches_target(
8646 analyzer, file, candidate, target,
8647 )
8648 {
8649 return true;
8650 }
8651 }
8652 }
8653 false
8654 }
8655
8656 fn type_candidates_for_declaration<'b>(
8660 &'b self,
8661 visible_from: &ProjectFile,
8662 declaration: &CodeUnit,
8663 raw_name: &str,
8664 ) -> Vec<&'b CodeUnit> {
8665 let Some(normalized) = normalize_reference_name(raw_name) else {
8666 return Vec::new();
8667 };
8668 if let Some(namespace) = cpp_namespace_for(declaration) {
8669 for prefix in namespace_prefixes(&namespace) {
8670 let qualified = format!("{prefix}::{normalized}");
8671 let candidates = self.type_candidates(visible_from, &qualified);
8672 if !candidates.is_empty() {
8673 return candidates;
8674 }
8675 }
8676 }
8677 self.type_candidates(visible_from, &normalized)
8678 }
8679
8680 fn resolve_unique_type_for_declaration(
8681 &self,
8682 visible_from: &ProjectFile,
8683 declaration: &CodeUnit,
8684 raw_name: &str,
8685 ) -> Option<CodeUnit> {
8686 unique_logical_type_candidate(self.type_candidates_for_declaration(
8687 visible_from,
8688 declaration,
8689 raw_name,
8690 ))
8691 }
8692
8693 fn resolve_defining_type_for_declaration(
8711 &self,
8712 analyzer: &CppGraphSource<'_>,
8713 visible_from: &ProjectFile,
8714 declaration: &CodeUnit,
8715 raw_name: &str,
8716 ) -> Option<CodeUnit> {
8717 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8718 let logical = unique_logical_type_candidate(candidates.clone())?;
8719 let mut defining = candidates.into_iter().filter(|candidate| {
8720 candidate.is_class()
8721 && cpp_class_declaration_strength(analyzer, candidate)
8722 == CppClassDeclarationStrength::Full
8723 });
8724 match (defining.next(), defining.next()) {
8725 (Some(unique_definition), None) => Some(unique_definition.clone()),
8726 _ => Some(logical),
8727 }
8728 }
8729
8730 pub fn resolves_to_type(
8731 &self,
8732 analyzer: &CppGraphSource<'_>,
8733 file: &ProjectFile,
8734 raw_name: &str,
8735 target: &CodeUnit,
8736 ) -> bool {
8737 let Some(normalized) = normalize_reference_name(raw_name) else {
8738 return false;
8739 };
8740 let candidates = self.type_candidates(file, &normalized);
8741 if candidates.is_empty() {
8742 return self.parser_alias_resolves_to_type(file, raw_name, target);
8743 }
8744 let Some(resolved) =
8745 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8746 else {
8747 return false;
8748 };
8749 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8750 }
8751
8752 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8753 let mut current = alias.clone();
8754 let mut visited = HashSet::default();
8755 while visited.insert(current.clone()) {
8756 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8757 let resolved =
8758 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8759 if resolved.is_class() {
8760 return Some(resolved);
8761 }
8762 if !declared_type_alias(&self.cpp_source(), &resolved) {
8763 return None;
8764 }
8765 current = resolved;
8766 }
8767 None
8768 }
8769
8770 pub fn same_logical_callable(
8785 &self,
8786 analyzer: &CppGraphSource<'_>,
8787 left: &CodeUnit,
8788 right: &CodeUnit,
8789 ) -> bool {
8790 if same_logical_symbol(left, right) {
8791 return true;
8792 }
8793 if left.kind() != right.kind()
8794 || !left.is_callable()
8795 || !right.is_callable()
8796 || left.fq_name() != right.fq_name()
8797 {
8798 return false;
8799 }
8800 let (Some(left_comparable), Some(right_comparable)) = (
8801 self.callable_comparable(analyzer, left),
8802 self.callable_comparable(analyzer, right),
8803 ) else {
8804 return false;
8805 };
8806 let binding = match (&left_comparable.template, &right_comparable.template) {
8814 (Some(left_template), Some(right_template))
8815 if left_template.is_templated() || right_template.is_templated() =>
8816 {
8817 match left_template.reconcile(right_template) {
8818 CppTemplateReconciliation::Equivalent(binding) => binding,
8819 CppTemplateReconciliation::Distinct
8820 | CppTemplateReconciliation::Unsupported => return false,
8821 }
8822 }
8823 (Some(_), Some(_)) => CppTemplateBinding::default(),
8824 _ if self.callable_is_template_declaration(analyzer, left)
8825 || self.callable_is_template_declaration(analyzer, right) =>
8826 {
8827 return false;
8828 }
8829 _ => CppTemplateBinding::default(),
8830 };
8831 if left_comparable.suffix != right_comparable.suffix
8836 || left_comparable.shapes.len() != right_comparable.shapes.len()
8837 {
8838 return false;
8839 }
8840 left_comparable
8841 .shapes
8842 .iter()
8843 .zip(right_comparable.shapes.iter())
8844 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8845 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8846 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8847 self.comparable_shapes_agree(analyzer, &binding, left_shape, right_shape)
8848 }
8849 _ => false,
8853 })
8854 }
8855
8856 pub fn same_logical_class_definition(
8869 &self,
8870 analyzer: &CppGraphSource<'_>,
8871 left: &CodeUnit,
8872 right: &CodeUnit,
8873 ) -> bool {
8874 if same_symbol(left, right) {
8875 return true;
8876 }
8877 if !left.is_class() || !right.is_class() || left.fq_name() != right.fq_name() {
8878 return false;
8879 }
8880 let key = if left <= right {
8881 (left.clone(), right.clone())
8882 } else {
8883 (right.clone(), left.clone())
8884 };
8885 if let Some(cached) = self
8886 .class_definition_agreements
8887 .lock()
8888 .expect("C++ class definition agreement cache poisoned")
8889 .get(&key)
8890 {
8891 return *cached;
8892 }
8893 let agree = self.class_definitions_agree(analyzer, left, right);
8894 self.class_definition_agreements
8895 .lock()
8896 .expect("C++ class definition agreement cache poisoned")
8897 .insert(key, agree);
8898 agree
8899 }
8900
8901 fn class_definitions_agree(
8902 &self,
8903 analyzer: &CppGraphSource<'_>,
8904 left: &CodeUnit,
8905 right: &CodeUnit,
8906 ) -> bool {
8907 let complete_definition = |class: &CodeUnit| {
8908 let mut facts = analyzer
8909 .declaration_source_properties(class)?
8910 .into_iter()
8911 .filter(|fact| fact.class_strength == CppClassDeclarationStrength::Full);
8912 let fact = facts.next()?;
8913 facts.next().is_none().then_some(fact)
8916 };
8917 let (Some(left_fact), Some(right_fact)) =
8918 (complete_definition(left), complete_definition(right))
8919 else {
8920 return false;
8921 };
8922 if left_fact.bases != right_fact.bases {
8923 return false;
8924 }
8925 let (Some(left_template), Some(right_template)) = (
8926 self.declaration_template_context(left, left_fact.declaration),
8927 self.declaration_template_context(right, right_fact.declaration),
8928 ) else {
8929 return false;
8930 };
8931 if !matches!(
8932 left_template.reconcile(&right_template),
8933 CppTemplateReconciliation::Equivalent(_)
8934 ) {
8935 return false;
8936 }
8937 let members = |class: &CodeUnit| {
8938 let mut members = analyzer
8939 .direct_children(class)
8940 .into_iter()
8941 .filter(|member| member.source() == class.source())
8942 .map(|member| {
8943 (
8944 member.kind(),
8945 member.identifier().to_owned(),
8946 member.signature().map(str::to_owned),
8947 )
8948 })
8949 .collect::<Vec<_>>();
8950 members.sort();
8951 members
8952 };
8953 members(left) == members(right)
8954 }
8955
8956 pub fn class_definition_bridges_to_file(
8966 &self,
8967 analyzer: &CppGraphSource<'_>,
8968 class: &CodeUnit,
8969 file: &ProjectFile,
8970 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
8971 ) -> bool {
8972 class.is_class()
8973 && class.source() != file
8974 && crate::identity::cpp_source_path_is_header(class.source())
8975 && analyzer
8976 .index
8977 .definitions_by_structured_name(class.fq(), Language::Cpp)
8978 .into_iter()
8979 .any(|copy| {
8980 copy.is_class()
8984 && copy.source() != class.source()
8985 && copy.source() != file
8986 && crate::identity::cpp_source_path_is_header(copy.source())
8987 && header_body_related(copy.source(), file)
8988 && self.same_logical_class_definition(analyzer, class, ©)
8989 })
8990 }
8991
8992 pub fn member_declaration_bridges_to_body(
8996 &self,
8997 analyzer: &CppGraphSource<'_>,
8998 declaration: &CodeUnit,
8999 body: &CodeUnit,
9000 header_body_related: &dyn Fn(&ProjectFile, &ProjectFile) -> bool,
9001 ) -> bool {
9002 declaration.is_callable()
9003 && self
9004 .cpp
9005 .structural_parent_of(declaration)
9006 .is_some_and(|owner| {
9007 self.class_definition_bridges_to_file(
9008 analyzer,
9009 &owner,
9010 body.source(),
9011 header_body_related,
9012 )
9013 })
9014 }
9015
9016 fn comparable_shapes_agree(
9022 &self,
9023 analyzer: &CppGraphSource<'_>,
9024 binding: &CppTemplateBinding,
9025 left: &CppComparableParameter,
9026 right: &CppComparableParameter,
9027 ) -> bool {
9028 let mut stack = vec![(left.root(), right.root())];
9029 while let Some((left_index, right_index)) = stack.pop() {
9030 match (left.node(left_index), right.node(right_index)) {
9031 (
9032 CppComparableNode::Named {
9033 name: left_name,
9034 primitive: left_primitive,
9035 konst: left_konst,
9036 volatil: left_volatil,
9037 },
9038 CppComparableNode::Named {
9039 name: right_name,
9040 primitive: right_primitive,
9041 konst: right_konst,
9042 volatil: right_volatil,
9043 },
9044 ) => {
9045 if left_konst != right_konst
9046 || left_volatil != right_volatil
9047 || left_primitive != right_primitive
9048 || !self.comparable_names_agree(
9049 analyzer,
9050 binding,
9051 left_name,
9052 right_name,
9053 *left_primitive,
9054 )
9055 {
9056 return false;
9057 }
9058 }
9059 (
9060 CppComparableNode::Pointer {
9061 inner: left_inner,
9062 konst: left_konst,
9063 volatil: left_volatil,
9064 },
9065 CppComparableNode::Pointer {
9066 inner: right_inner,
9067 konst: right_konst,
9068 volatil: right_volatil,
9069 },
9070 ) => {
9071 if left_konst != right_konst || left_volatil != right_volatil {
9072 return false;
9073 }
9074 stack.push((*left_inner, *right_inner));
9075 }
9076 (
9077 CppComparableNode::Reference { inner: left_inner },
9078 CppComparableNode::Reference { inner: right_inner },
9079 )
9080 | (
9081 CppComparableNode::Array { inner: left_inner },
9082 CppComparableNode::Array { inner: right_inner },
9083 ) => stack.push((*left_inner, *right_inner)),
9084 (
9085 CppComparableNode::Generic {
9086 base: left_base,
9087 arguments: left_arguments,
9088 },
9089 CppComparableNode::Generic {
9090 base: right_base,
9091 arguments: right_arguments,
9092 },
9093 ) => {
9094 if left_arguments.len() != right_arguments.len() {
9095 return false;
9096 }
9097 stack.push((*left_base, *right_base));
9098 stack.extend(
9099 left_arguments.iter().zip(right_arguments.iter()).map(
9100 |(left_argument, right_argument)| (*left_argument, *right_argument),
9101 ),
9102 );
9103 }
9104 _ => return false,
9105 }
9106 }
9107 true
9108 }
9109
9110 fn comparable_names_agree(
9125 &self,
9126 analyzer: &CppGraphSource<'_>,
9127 binding: &CppTemplateBinding,
9128 left: &StructuredTypeName,
9129 right: &StructuredTypeName,
9130 primitive: bool,
9131 ) -> bool {
9132 if primitive {
9133 return left.path() == right.path();
9134 }
9135 if !binding.is_empty() {
9136 let (Some(left_head), Some(right_head)) = (left.path().first(), right.path().first())
9137 else {
9138 return false;
9139 };
9140 match (
9141 binding.is_left_parameter(left_head),
9142 binding.is_right_parameter(right_head),
9143 ) {
9144 (true, true) => {
9145 return !left.is_absolute()
9146 && !right.is_absolute()
9147 && binding.binds(right_head, left_head)
9148 && left.path()[1..] == right.path()[1..];
9149 }
9150 (false, false) => {}
9151 _ => return false,
9152 }
9153 }
9154 match (
9155 self.comparable_name_terminal(analyzer, left),
9156 self.comparable_name_terminal(analyzer, right),
9157 ) {
9158 (Some(left_terminal), Some(right_terminal)) => {
9159 same_logical_symbol(&left_terminal, &right_terminal)
9160 }
9161 (None, None) => {
9162 left.path() == right.path() && left.is_absolute() == right.is_absolute()
9163 }
9164 _ => false,
9165 }
9166 }
9167
9168 fn comparable_name_terminal(
9178 &self,
9179 analyzer: &CppGraphSource<'_>,
9180 name: &StructuredTypeName,
9181 ) -> Option<CodeUnit> {
9182 let mut current = self.comparable_name_declaration(analyzer, name)?;
9183 let mut visited = HashSet::default();
9184 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
9185 if !declared_type_alias(analyzer, ¤t) {
9192 return current.is_class().then_some(current);
9193 }
9194 if !visited.insert(current.clone()) {
9195 return None;
9196 }
9197 let facts = analyzer.declaration_source_properties(¤t)?;
9198 if facts.iter().any(|fact| fact.adds_indirection) {
9199 return None;
9200 }
9201 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
9202 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
9203 }
9204 None
9205 }
9206
9207 fn comparable_alias_target(
9218 &self,
9219 analyzer: &CppGraphSource<'_>,
9220 alias: &CodeUnit,
9221 raw_target: &str,
9222 ) -> Option<CodeUnit> {
9223 let absolute = raw_target.trim_start().starts_with("::");
9228 let normalized = normalize_reference_name(raw_target)?;
9229 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9230 brokk_bifrost_core::analyzer::Language::Cpp,
9231 &normalized,
9232 );
9233 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
9234 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9235 brokk_bifrost_core::analyzer::Language::Cpp,
9236 &namespace,
9237 )
9238 });
9239 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
9240 self.comparable_name_declaration(analyzer, &name)
9241 }
9242
9243 fn comparable_name_declaration(
9251 &self,
9252 analyzer: &CppGraphSource<'_>,
9253 name: &StructuredTypeName,
9254 ) -> Option<CodeUnit> {
9255 let mut cache = self
9262 .comparable_name_declarations
9263 .lock()
9264 .expect("C++ comparable name declaration cache poisoned");
9265 if let Some(cached) = cache.get(name) {
9266 return cached.clone();
9267 }
9268 let interner = segment_interner();
9269 let identifier = name.path().last()?;
9270 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
9271 let first_depth = if name.is_absolute() {
9272 0
9273 } else {
9274 name.lexical_scope().len()
9275 };
9276 let mut resolved = None;
9277 for depth in (0..=first_depth).rev() {
9278 let mut structured = FqName::new();
9279 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
9280 structured.push(interner.intern(component, SegmentKind::Unknown));
9281 }
9282 let mut candidates = candidates_by_identifier
9283 .iter()
9284 .filter(|unit| unit.fq().same_segment_texts(&structured))
9285 .filter(|unit| {
9286 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
9287 })
9288 .cloned();
9289 let Some(first) = candidates.next() else {
9290 continue;
9291 };
9292 resolved = candidates
9293 .all(|unit| same_logical_symbol(&unit, &first))
9294 .then_some(first);
9295 break;
9296 }
9297 cache.insert(name.clone(), resolved.clone());
9298 resolved
9299 }
9300
9301 fn callable_comparable(
9307 &self,
9308 analyzer: &CppGraphSource<'_>,
9309 unit: &CodeUnit,
9310 ) -> Option<Arc<ExtractedComparable>> {
9311 if let Some(cached) = self
9312 .callable_comparables
9313 .lock()
9314 .expect("C++ callable comparable cache poisoned")
9315 .get(unit)
9316 .cloned()
9317 {
9318 return cached;
9319 }
9320 let extracted = self
9321 .extract_callable_comparable(analyzer, unit)
9322 .map(Arc::new);
9323 self.callable_comparables
9324 .lock()
9325 .expect("C++ callable comparable cache poisoned")
9326 .insert(unit.clone(), extracted.clone());
9327 extracted
9328 }
9329
9330 fn extract_callable_comparable(
9331 &self,
9332 analyzer: &CppGraphSource<'_>,
9333 unit: &CodeUnit,
9334 ) -> Option<ExtractedComparable> {
9335 let fact = analyzer
9336 .declaration_source_properties(unit)?
9337 .into_iter()
9338 .find(|fact| fact.callable_comparable_shapes.is_some())?;
9339 let template = self.declaration_template_context(unit, fact.declaration);
9340 Some(ExtractedComparable {
9341 shapes: fact.callable_comparable_shapes?,
9342 suffix: fact.callable_identity_suffix?,
9343 template,
9344 })
9345 }
9346
9347 pub fn declaration_template_context(
9350 &self,
9351 unit: &CodeUnit,
9352 declaration: SourceDeclarationId,
9353 ) -> Option<CppTemplateContext> {
9354 let facts = self
9355 .cpp
9356 .declaration_source_facts(self.token, unit.source())?;
9357 let occurrence = facts.source.declaration(declaration).occurrence;
9358 let range = facts.source.occurrence(occurrence).range;
9359 let syntax = self.cpp.prepared_syntax(self.token, unit.source())?;
9360 let mut node = node_for_exact_range(syntax.tree().root_node(), &range)?;
9361 while node.kind() == "template_declaration" {
9364 node = node.named_child(node.named_child_count().checked_sub(1)?)?;
9365 }
9366 cpp_declaration_template_context(node, syntax.source())
9367 }
9368
9369 pub fn canonical_type_for_reference(
9370 &self,
9371 file: &ProjectFile,
9372 raw_name: &str,
9373 ) -> Option<CodeUnit> {
9374 let resolved = self.resolve_type(file, raw_name)?;
9375 self.alias_target(&resolved).or(Some(resolved))
9376 }
9377
9378 pub fn parser_alias_resolves_to_type(
9379 &self,
9380 file: &ProjectFile,
9381 raw_name: &str,
9382 target: &CodeUnit,
9383 ) -> bool {
9384 let Some(alias_name) = normalize_reference_name(raw_name) else {
9385 return false;
9386 };
9387 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
9388 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
9389 })
9390 }
9391
9392 fn file_alias_matches(
9393 &self,
9394 cpp: &dyn CppSource,
9395 file: &ProjectFile,
9396 alias_name: &str,
9397 target: &CodeUnit,
9398 ) -> bool {
9399 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
9400 cell.get()
9401 .expect("published canonical aliases")
9402 .iter()
9403 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
9404 })
9405 }
9406
9407 fn canonical_alias_cell(
9408 &self,
9409 cpp: &dyn CppSource,
9410 file: &ProjectFile,
9411 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
9412 let cell = {
9413 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
9414 Arc::clone(
9415 cells
9416 .entry(file.clone())
9417 .or_insert_with(|| Arc::new(OnceLock::new())),
9418 )
9419 };
9420 if cell.get().is_none() {
9421 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
9422 cell.get_or_init(|| {
9423 self.parser_alias_source_parses
9424 .fetch_add(1, Ordering::Relaxed);
9425 #[cfg(any(test, feature = "test-support"))]
9426 {
9427 *self
9428 .alias_source_parse_counts
9429 .lock()
9430 .expect("alias source parse count lock")
9431 .entry(file.clone())
9432 .or_default() += 1;
9433 }
9434 aliases.into_boxed_slice()
9435 });
9436 }
9437 Some(cell)
9438 }
9439
9440 #[cfg(any(test, feature = "test-support"))]
9441 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
9442 self.visible_source_files_by_root
9443 .get(file)
9444 .cloned()
9445 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
9446 }
9447
9448 #[cfg(any(test, feature = "test-support"))]
9449 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
9450 self.alias_source_parse_counts
9451 .lock()
9452 .expect("alias source parse count lock")
9453 .get(file)
9454 .copied()
9455 .unwrap_or(0)
9456 }
9457
9458 #[cfg(any(test, feature = "test-support"))]
9459 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
9460 self.parser_alias_fallback_files.load(Ordering::Relaxed)
9461 }
9462
9463 pub fn resolve_named(
9464 &self,
9465 file: &ProjectFile,
9466 raw_name: &str,
9467 kind: TargetKind,
9468 ) -> Option<CodeUnit> {
9469 let normalized = normalize_reference_name(raw_name)?;
9470 self.named_candidates_for_normalized(file, &normalized, kind)
9471 .into_iter()
9472 .next()
9473 .cloned()
9474 }
9475
9476 pub fn contains_named_symbol(
9477 &self,
9478 file: &ProjectFile,
9479 raw_name: &str,
9480 kind: TargetKind,
9481 target: &CodeUnit,
9482 ) -> bool {
9483 let Some(normalized) = normalize_reference_name(raw_name) else {
9484 return false;
9485 };
9486 self.named_candidates_for_normalized(file, &normalized, kind)
9487 .into_iter()
9488 .any(|unit| {
9489 matches_kind_for_lookup(unit, kind)
9490 && reference_matches_unit(&normalized, unit)
9491 && same_visible_symbol(unit, target)
9492 })
9493 }
9494
9495 pub fn named_candidates(
9496 &self,
9497 file: &ProjectFile,
9498 raw_name: &str,
9499 kind: TargetKind,
9500 ) -> Vec<CodeUnit> {
9501 let Some(normalized) = normalize_reference_name(raw_name) else {
9502 return Vec::new();
9503 };
9504 self.named_candidates_for_normalized(file, &normalized, kind)
9505 .into_iter()
9506 .cloned()
9507 .collect()
9508 }
9509
9510 pub fn resolve_known_non_target(
9511 &self,
9512 file: &ProjectFile,
9513 raw_name: &str,
9514 kind: TargetKind,
9515 target: &CodeUnit,
9516 ) -> bool {
9517 let Some(normalized) = normalize_reference_name(raw_name) else {
9518 return false;
9519 };
9520 normalized.contains("::")
9521 && self
9522 .named_candidates_for_normalized(file, &normalized, kind)
9523 .into_iter()
9524 .any(|unit| {
9525 matches_kind_for_lookup(unit, kind)
9526 && reference_matches_unit(&normalized, unit)
9527 && !same_visible_symbol(unit, target)
9528 })
9529 }
9530
9531 pub fn resolve_call_return_binding(
9532 &self,
9533 analyzer: &CppGraphSource<'_>,
9534 file: &ProjectFile,
9535 raw_name: &str,
9536 arity: usize,
9537 lexical_namespace: Option<&str>,
9538 direct_type: Option<&CodeUnit>,
9539 ) -> Option<CppScanBinding> {
9540 let normalized = normalize_reference_name(raw_name)?;
9541 let mut candidates = Vec::new();
9542 for function in
9543 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9544 {
9545 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
9546 && !direct_type.is_some_and(|direct_type| {
9547 self.callable_is_constructor_declaration(analyzer, function)
9548 && type_owner_of(analyzer, function)
9549 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9550 })
9551 {
9552 candidates.push(function.clone());
9553 }
9554 }
9555 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9556 unanimous_return_binding(analyzer, self, file, &candidates)
9557 }
9558
9559 pub fn resolve_call_return_binding_without_arity(
9560 &self,
9561 analyzer: &CppGraphSource<'_>,
9562 file: &ProjectFile,
9563 raw_name: &str,
9564 lexical_namespace: Option<&str>,
9565 direct_type: Option<&CodeUnit>,
9566 ) -> (bool, Option<CppScanBinding>) {
9567 let Some(normalized) = normalize_reference_name(raw_name) else {
9568 return (false, None);
9569 };
9570 let mut candidates = self
9571 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
9572 .into_iter()
9573 .filter(|function| {
9574 function.is_function()
9575 && !direct_type.is_some_and(|direct_type| {
9576 self.callable_is_constructor_declaration(analyzer, function)
9577 && type_owner_of(analyzer, function)
9578 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
9579 })
9580 })
9581 .cloned()
9582 .collect::<Vec<_>>();
9583 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
9584 let has_candidates = !candidates.is_empty();
9585 (
9586 has_candidates,
9587 unanimous_return_binding(analyzer, self, file, &candidates),
9588 )
9589 }
9590
9591 pub fn visible_identifier_candidates<'b>(
9592 &'b self,
9593 file: &ProjectFile,
9594 identifier: &str,
9595 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
9596 self.visible_by_identifier
9597 .get(file)
9598 .and_then(|by_name| by_name.get(identifier))
9599 .into_iter()
9600 .flatten()
9601 }
9602
9603 pub fn type_reference_component_directly_names_target(
9609 &self,
9610 component: &str,
9611 target: &CodeUnit,
9612 ) -> bool {
9613 component == target.identifier()
9614 || self
9615 .cpp_template_metadata
9616 .get(target)
9617 .is_some_and(|metadata| component == metadata.primary_name)
9618 }
9619
9620 pub fn visible_type_reference_component_names_for_target(
9628 &self,
9629 analyzer: &CppGraphSource<'_>,
9630 file: &ProjectFile,
9631 target: &CodeUnit,
9632 ) -> HashSet<String> {
9633 let mut names = HashSet::from_iter([target.identifier().to_string()]);
9634 if let Some(metadata) = self.cpp_template_metadata.get(target) {
9635 names.insert(metadata.primary_name.clone());
9636 }
9637
9638 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
9639 for (identifier, candidates) in by_identifier {
9640 if candidates.iter().any(|candidate| {
9641 (candidate.is_class()
9642 && (same_visible_symbol(candidate, target)
9643 || self.compatible_primary_template_redeclarations(candidate, target)))
9644 || (declared_type_alias(analyzer, candidate)
9645 && self.alias_candidate_structurally_reaches_target(
9646 analyzer, file, candidate, target,
9647 ))
9648 }) {
9649 names.insert(identifier.clone());
9650 }
9651 }
9652 }
9653
9654 names
9655 }
9656
9657 pub fn indexed_structural_class_scope(
9658 &self,
9659 file: &ProjectFile,
9660 class: Node<'_>,
9661 source: &str,
9662 ) -> Option<Vec<String>> {
9663 let key = (file.clone(), class.start_byte(), class.end_byte());
9664 if let Some(cached) = self
9665 .indexed_structural_class_scopes
9666 .lock()
9667 .expect("C++ indexed structural-class scope cache poisoned")
9668 .get(&key)
9669 .cloned()
9670 {
9671 return cached;
9672 }
9673 let resolved = (|| {
9674 let name = class.child_by_field_name("name")?;
9675 let identifier = if name.kind() == "template_type" {
9676 node_text(name.child_by_field_name("name")?, source).to_string()
9677 } else {
9678 let mut components = Vec::new();
9679 append_cpp_name_components(name, source, &mut components)?;
9680 components.last()?.clone()
9681 };
9682 let visible = self
9683 .visible_identifier_candidates(file, &identifier)
9684 .cloned()
9685 .collect::<Vec<_>>();
9686 let mut visible = visible;
9687 for candidate in
9688 self.visible_by_file
9689 .get(file)
9690 .into_iter()
9691 .flatten()
9692 .filter(|candidate| {
9693 self.cpp_template_metadata
9694 .get(candidate)
9695 .is_some_and(|metadata| metadata.primary_name == identifier)
9696 })
9697 {
9698 if !visible
9699 .iter()
9700 .any(|existing| same_logical_symbol(existing, candidate))
9701 {
9702 visible.push(candidate.clone());
9703 }
9704 }
9705 let cpp_source = self.cpp_source();
9708 let candidates = visible
9709 .iter()
9710 .filter(|candidate| {
9711 candidate.source() == file
9712 && candidate.is_class()
9713 && !declared_type_alias(&cpp_source, candidate)
9714 && self.cpp.ranges(candidate).iter().any(|range| {
9715 range.start_byte <= class.start_byte()
9716 && class.end_byte() <= range.end_byte
9717 })
9718 })
9719 .collect::<Vec<_>>();
9720 let owner = if name.kind() == "template_type" {
9721 let expected = normalize_cpp_whitespace(node_text(name, source));
9722 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9723 let exact = candidates
9724 .iter()
9725 .copied()
9726 .filter(|candidate| {
9727 candidate
9728 .fq()
9729 .segments()
9730 .iter()
9731 .rev()
9732 .find_map(|&segment| {
9733 let (text, kind) = interner.resolve(segment);
9734 matches!(
9735 kind,
9736 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9737 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9738 )
9739 .then_some(text)
9740 })
9741 .is_some_and(|text| text == expected)
9742 })
9743 .collect::<Vec<_>>();
9744 unique_logical_type_candidate(exact)
9745 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9746 } else {
9747 unique_logical_type_candidate(candidates)?
9748 };
9749 Some(canonical_cpp_scope_components(&owner))
9750 })();
9751 self.indexed_structural_class_scopes
9752 .lock()
9753 .expect("C++ indexed structural-class scope cache poisoned")
9754 .insert(key, resolved.clone());
9755 resolved
9756 }
9757
9758 pub fn indexed_enclosing_owner_scope(
9759 &self,
9760 analyzer: &CppGraphSource<'_>,
9761 file: &ProjectFile,
9762 node: Node<'_>,
9763 ) -> Option<Vec<String>> {
9764 let anchor = std::iter::successors(Some(node), |current| current.parent())
9765 .find(|current| {
9766 matches!(
9767 current.kind(),
9768 "function_definition"
9769 | "class_specifier"
9770 | "struct_specifier"
9771 | "union_specifier"
9772 )
9773 })
9774 .unwrap_or(node);
9775 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9776 if let Some(cached) = self
9777 .indexed_enclosing_owner_scopes
9778 .lock()
9779 .expect("C++ indexed enclosing-owner scope cache poisoned")
9780 .get(&key)
9781 .cloned()
9782 {
9783 return cached;
9784 }
9785 let resolved = (|| {
9786 let range = Range {
9787 start_byte: node.start_byte(),
9788 end_byte: node.end_byte(),
9789 start_line: node.start_position().row,
9790 end_line: node.end_position().row,
9791 };
9792 let start = analyzer.enclosing_code_unit(file, &range)?;
9793 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9794 start,
9795 |unit| self.cached_precise_parent_of(analyzer, unit),
9796 )
9797 .find(|unit| {
9798 unit.is_class()
9799 && !analyzer
9800 .type_alias_provider()
9801 .is_some_and(|provider| provider.is_type_alias(unit))
9802 })?;
9803 Some(canonical_cpp_scope_components(&owner))
9804 })();
9805 self.indexed_enclosing_owner_scopes
9806 .lock()
9807 .expect("C++ indexed enclosing-owner scope cache poisoned")
9808 .insert(key, resolved.clone());
9809 resolved
9810 }
9811
9812 fn cached_precise_parent_of(
9813 &self,
9814 analyzer: &CppGraphSource<'_>,
9815 code_unit: &CodeUnit,
9816 ) -> Option<CodeUnit> {
9817 if let Some(cached) = self
9818 .precise_parent_cache
9819 .lock()
9820 .expect("C++ precise-parent cache poisoned")
9821 .get(code_unit)
9822 .cloned()
9823 {
9824 return cached;
9825 }
9826 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9827 self.precise_parent_cache
9828 .lock()
9829 .expect("C++ precise-parent cache poisoned")
9830 .insert(code_unit.clone(), resolved.clone());
9831 resolved
9832 }
9833
9834 pub fn callable_is_constructor_declaration(
9835 &self,
9836 analyzer: &CppGraphSource<'_>,
9837 candidate: &CodeUnit,
9838 ) -> bool {
9839 if !candidate.is_function() {
9840 return false;
9841 }
9842 if analyzer
9843 .declaration_source_properties(candidate)
9844 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9845 {
9846 return true;
9847 }
9848 let metadata = analyzer.signature_metadata(candidate);
9849 !metadata.is_empty()
9850 && metadata
9851 .iter()
9852 .all(|signature| signature.return_type_text().is_none())
9853 }
9854
9855 pub fn callable_is_deduction_guide_declaration(
9863 &self,
9864 analyzer: &CppGraphSource<'_>,
9865 candidate: &CodeUnit,
9866 ) -> bool {
9867 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9868 }
9869
9870 pub fn callable_is_template_declaration(
9874 &self,
9875 analyzer: &CppGraphSource<'_>,
9876 candidate: &CodeUnit,
9877 ) -> bool {
9878 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9879 }
9880
9881 pub fn type_name_candidates<'b>(
9882 &'b self,
9883 file: &ProjectFile,
9884 normalized: &str,
9885 ) -> Vec<&'b CodeUnit> {
9886 self.candidate_units(file, normalized, TargetKind::Type)
9887 }
9888
9889 pub fn visible_members_for_owner_name<'b>(
9890 &'b self,
9891 file: &ProjectFile,
9892 owner: &CodeUnit,
9893 name: &str,
9894 ) -> Vec<&'b CodeUnit> {
9895 self.visible_identifier_candidates(file, name)
9896 .filter(|unit| {
9897 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9901 .is_some_and(|parent| parent == owner.fq_name())
9902 })
9903 .collect()
9904 }
9905
9906 pub fn visible_member_for_owner_name(
9907 &self,
9908 file: &ProjectFile,
9909 owner: &CodeUnit,
9910 name: &str,
9911 ) -> VisibleMemberResolution {
9912 let candidates = self.visible_members_for_owner_name(file, owner, name);
9913 let mut callables = Vec::new();
9914 let mut non_callable = None;
9915 for candidate in candidates {
9916 if candidate.is_function() {
9917 callables.push(candidate.clone());
9918 } else if non_callable.is_none() {
9919 non_callable = Some(candidate.clone());
9920 }
9921 }
9922 match (callables.is_empty(), non_callable) {
9923 (false, None) => VisibleMemberResolution::Callable(callables),
9924 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9925 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9926 (true, None) => VisibleMemberResolution::Missing,
9927 }
9928 }
9929
9930 fn field_declared_type_fact(
9931 &self,
9932 analyzer: &CppGraphSource<'_>,
9933 field: &CodeUnit,
9934 ) -> Option<DeclaredFieldTypeFact> {
9935 if let Some(cached) = self
9936 .field_type_facts
9937 .lock()
9938 .expect("C++ field type fact cache poisoned")
9939 .get(field)
9940 .cloned()
9941 {
9942 return cached;
9943 }
9944 let decoded = decode_field_declared_type_fact(analyzer, field);
9945 self.field_type_facts
9946 .lock()
9947 .expect("C++ field type fact cache poisoned")
9948 .insert(field.clone(), decoded.clone());
9949 decoded
9950 }
9951
9952 fn structured_alias_target(
9953 &self,
9954 analyzer: &CppGraphSource<'_>,
9955 unit: &CodeUnit,
9956 ) -> Option<StructuredAliasTarget> {
9957 if let Some(cached) = self
9958 .structured_alias_targets
9959 .lock()
9960 .expect("C++ structured alias target cache poisoned")
9961 .get(unit)
9962 .cloned()
9963 {
9964 return cached;
9965 }
9966 let decoded = decode_structured_alias_target(analyzer, unit);
9967 self.structured_alias_targets
9968 .lock()
9969 .expect("C++ structured alias target cache poisoned")
9970 .insert(unit.clone(), decoded.clone());
9971 decoded
9972 }
9973
9974 pub fn type_candidates<'b>(
9975 &'b self,
9976 file: &ProjectFile,
9977 normalized: &str,
9978 ) -> Vec<&'b CodeUnit> {
9979 let qualified = normalized.contains("::");
9984 let mut candidates = self
9985 .candidate_units(file, normalized, TargetKind::Type)
9986 .into_iter()
9987 .filter(|unit| {
9988 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9989 })
9990 .filter(|unit| qualified || !is_function_local_unit(unit))
9991 .collect::<Vec<_>>();
9992 dedup_unit_refs(&mut candidates);
9993 candidates
9994 }
9995
9996 pub fn named_candidates_for_normalized<'b>(
9997 &'b self,
9998 file: &ProjectFile,
9999 normalized: &str,
10000 kind: TargetKind,
10001 ) -> Vec<&'b CodeUnit> {
10002 let mut candidates = self
10003 .candidate_units(file, normalized, kind)
10004 .into_iter()
10005 .filter(|unit| {
10006 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
10007 })
10008 .collect::<Vec<_>>();
10009 dedup_unit_refs(&mut candidates);
10010 candidates
10011 }
10012
10013 pub fn candidate_units<'b>(
10014 &'b self,
10015 file: &ProjectFile,
10016 normalized: &str,
10017 kind: TargetKind,
10018 ) -> Vec<&'b CodeUnit> {
10019 if normalized.contains("::") {
10020 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10029 brokk_bifrost_core::analyzer::Language::Cpp,
10030 normalized,
10031 );
10032 let Some(identifier) = parts.last() else {
10033 return Vec::new();
10034 };
10035 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
10036 return self
10037 .visible_identifier_candidates(file, identifier)
10038 .filter(|unit| {
10039 #[cfg(any(test, feature = "test-support"))]
10040 self.qualified_candidate_inspections
10041 .fetch_add(1, Ordering::Relaxed);
10042 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
10043 || canonical_cpp_name_matches(unit, normalized)
10044 })
10045 .collect();
10046 }
10047 self.visible_identifier_candidates(file, normalized)
10048 .collect()
10049 }
10050
10051 #[cfg(any(test, feature = "test-support"))]
10052 pub fn reset_qualified_candidate_inspections(&self) {
10053 self.qualified_candidate_inspections
10054 .store(0, Ordering::Relaxed);
10055 }
10056
10057 #[cfg(any(test, feature = "test-support"))]
10058 pub fn qualified_candidate_inspections(&self) -> usize {
10059 self.qualified_candidate_inspections.load(Ordering::Relaxed)
10060 }
10061
10062 #[cfg(any(test, feature = "test-support"))]
10063 pub fn visibility_identifier_lookup_count(&self) -> usize {
10064 self.visibility_identifier_lookup_count
10065 }
10066
10067 #[cfg(any(test, feature = "test-support"))]
10068 pub fn visibility_identifier_batch_count(&self) -> usize {
10069 self.visibility_identifier_batch_count
10070 }
10071
10072 #[cfg(any(test, feature = "test-support"))]
10073 pub fn reset_target_preserving_type_resolution_count(&self) {
10074 self.target_preserving_type_resolution_count
10075 .store(0, Ordering::Relaxed);
10076 }
10077
10078 #[cfg(any(test, feature = "test-support"))]
10079 pub fn target_preserving_type_resolution_count(&self) -> usize {
10080 self.target_preserving_type_resolution_count
10081 .load(Ordering::Relaxed)
10082 }
10083
10084 #[cfg(any(test, feature = "test-support"))]
10085 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
10086 self.visible_parser_alias_name_set_build_count
10087 .load(Ordering::Relaxed)
10088 }
10089}
10090
10091fn callable_declaration_has_fact(
10092 analyzer: &CppGraphSource<'_>,
10093 candidate: &CodeUnit,
10094 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
10095) -> bool {
10096 candidate.is_function()
10097 && analyzer
10098 .declaration_source_properties(candidate)
10099 .is_some_and(|facts| facts.iter().any(matches))
10100}
10101
10102#[derive(Default)]
10103struct IncludeGraph {
10104 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
10105}
10106
10107impl IncludeGraph {
10108 fn extend_with<F>(
10109 &mut self,
10110 root: &ProjectFile,
10111 cancellation: Option<&CancellationToken>,
10112 targets_for: &mut F,
10113 ) where
10114 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
10115 {
10116 let mut stack = vec![root.clone()];
10117 while let Some(file) = stack.pop() {
10118 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10119 break;
10120 }
10121 if self.targets_by_file.contains_key(&file) {
10122 continue;
10123 }
10124 let targets = targets_for(&file);
10125 stack.extend(targets.iter().cloned());
10126 self.targets_by_file.insert(file, targets);
10127 }
10128 }
10129
10130 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
10131 self.targets_by_file.keys()
10132 }
10133
10134 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
10135 self.targets_by_file
10136 .get(file)
10137 .map(Vec::as_slice)
10138 .unwrap_or_default()
10139 }
10140
10141 fn reachable_files(
10142 &self,
10143 root: &ProjectFile,
10144 cancellation: Option<&CancellationToken>,
10145 ) -> HashSet<ProjectFile> {
10146 let mut pending = vec![root.clone()];
10147 let mut visited = HashSet::default();
10148 while let Some(file) = pending.pop() {
10149 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10150 break;
10151 }
10152 if visited.insert(file.clone()) {
10153 pending.extend(self.targets(&file).iter().cloned());
10154 }
10155 }
10156 visited
10157 }
10158}
10159
10160fn build_bounded_visible_declarations(
10161 cpp: &dyn CppSource,
10162 token: QueryToken<'_>,
10163 analyzer: &CppGraphSource<'_>,
10164 roots: &HashSet<ProjectFile>,
10165 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10166 cancellation: Option<&CancellationToken>,
10167 stats: &mut BoundedVisibilityStats,
10168) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
10169 let mut candidates_by_identifier = HashMap::default();
10170 let mut declarations_by_source_and_reading: HashMap<
10171 (ProjectFile, bool),
10172 BoundedVisibilityDeclarations,
10173 > = HashMap::default();
10174 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
10175 roots
10176 .iter()
10177 .map(|root| {
10178 let reading_is_c = analyzer.reference_uses_c_semantics(root);
10179 let root_declarations = declarations_by_source_and_reading
10180 .entry((root.clone(), reading_is_c))
10181 .or_insert_with(|| {
10182 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
10183 });
10184 let mut visible = root_declarations
10185 .all
10186 .iter()
10187 .cloned()
10188 .collect::<HashSet<_>>();
10189 let mut pending_names = HashSet::default();
10190 if let Some(prepared) = cpp.prepared_syntax(token, root) {
10191 let mut cursor = prepared.tree().walk();
10196 let mut pending_nodes = vec![prepared.tree().root_node()];
10197 while let Some(node) = pending_nodes.pop() {
10198 if matches!(
10199 node.kind(),
10200 "identifier"
10201 | "type_identifier"
10202 | "field_identifier"
10203 | "namespace_identifier"
10204 ) {
10205 pending_names.insert(node_text(node, prepared.source()).to_string());
10206 }
10207 if node.kind() == "preproc_arg" {
10208 for reference in
10209 object_macro_replacement_type_references(node, prepared.source())
10210 {
10211 pending_names.extend(reference.components);
10212 }
10213 }
10214 pending_nodes.extend(node.named_children(&mut cursor));
10215 }
10216 }
10217 stats.root_names += pending_names.len();
10218 let mut completed_names = HashSet::default();
10219 while !pending_names.is_empty() {
10220 stats.rounds += 1;
10221 let round_names = std::mem::take(&mut pending_names);
10222 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
10223 HashMap::default();
10224 let mut identifiers = Vec::new();
10225 for identifier in round_names {
10226 if !completed_names.insert(identifier.clone())
10227 || cancellation.is_some_and(CancellationToken::is_cancelled)
10228 {
10229 continue;
10230 }
10231 identifiers.push(identifier);
10232 }
10233 let missing_identifiers = identifiers
10234 .iter()
10235 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
10236 .cloned()
10237 .collect::<HashSet<_>>();
10238 if !missing_identifiers.is_empty() {
10239 let lookup_started = Instant::now();
10240 let mut candidates = cpp
10241 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
10242 stats.lookup_elapsed += lookup_started.elapsed();
10243 stats.identifier_lookups += missing_identifiers.len();
10244 stats.identifier_batches += 1;
10245 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10246 break;
10247 }
10248 for identifier in missing_identifiers {
10249 let units = candidates.remove(&identifier).unwrap_or_default();
10250 let candidate_count = units.len();
10251 let candidate_sources = units
10252 .into_iter()
10253 .map(|unit| unit.source().clone())
10254 .collect::<HashSet<_>>();
10255 candidates_by_identifier
10256 .insert(identifier, (candidate_sources, candidate_count));
10257 }
10258 }
10259 for identifier in identifiers {
10260 let (candidate_sources, candidate_count) = candidates_by_identifier
10261 .get(&identifier)
10262 .expect("every missing identifier was inserted after the batch lookup");
10263 stats.candidate_units += *candidate_count;
10264 for source in candidate_sources.iter().cloned() {
10265 if source != *root
10266 && visible_sources
10267 .get(root)
10268 .is_some_and(|files| files.contains(&source))
10269 {
10270 requested_names_by_source
10271 .entry(source)
10272 .or_default()
10273 .insert(identifier.clone());
10274 }
10275 }
10276 }
10277 stats.candidate_sources += requested_names_by_source.len();
10278 for (source, requested_names) in requested_names_by_source {
10279 let declarations = declarations_by_source_and_reading
10280 .entry((source.clone(), reading_is_c))
10281 .or_insert_with(|| {
10282 bounded_visibility_declarations(
10283 cpp,
10284 analyzer,
10285 &source,
10286 reading_is_c,
10287 stats,
10288 )
10289 });
10290 let mut selected = HashSet::default();
10291 for name in requested_names {
10292 if let Some(units) = declarations.by_identifier.get(&name) {
10293 selected.extend(units.iter().cloned());
10294 }
10295 }
10296 for unit in selected {
10297 stats.selected_units += 1;
10298 let dependency_names = dependency_names_by_unit
10299 .entry(unit.clone())
10300 .or_insert_with(|| {
10301 let facts_started = Instant::now();
10302 let mut dependency_names = HashSet::default();
10303 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
10304 for fact in facts {
10305 for name in fact.dependency_type_names {
10306 stats.dependency_source_names += 1;
10307 #[cfg(any(test, feature = "test-support"))]
10308 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
10309 .with(|count| count.set(count.get() + 1));
10310 dependency_names.insert(name);
10311 }
10312 }
10313 stats.dependency_source_elapsed += facts_started.elapsed();
10314 }
10315 dependency_names
10316 });
10317 for name in dependency_names.iter() {
10318 if !completed_names.contains(name) && pending_names.insert(name.clone())
10319 {
10320 stats.dependency_names += 1;
10321 }
10322 }
10323 visible.insert(unit);
10324 }
10325 }
10326 }
10327 (root.clone(), visible)
10328 })
10329 .collect()
10330}
10331
10332struct BoundedVisibilityDeclarations {
10333 all: Vec<CodeUnit>,
10334 by_identifier: HashMap<String, Vec<CodeUnit>>,
10335}
10336
10337fn bounded_visibility_declarations(
10338 cpp: &dyn CppSource,
10339 analyzer: &CppGraphSource<'_>,
10340 file: &ProjectFile,
10341 c_semantics: bool,
10342 stats: &mut BoundedVisibilityStats,
10343) -> BoundedVisibilityDeclarations {
10344 let declarations_started = Instant::now();
10345 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
10346 .into_iter()
10347 .collect::<Vec<_>>();
10348 stats.declaration_elapsed += declarations_started.elapsed();
10349 stats.declaration_reads += 1;
10350 stats.declaration_units += all.len();
10351
10352 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10353 for unit in &all {
10354 by_identifier
10355 .entry(unit.identifier().to_string())
10356 .or_default()
10357 .push(unit.clone());
10358 if unit.is_class()
10359 && let Some(metadata) = cpp.template_metadata(unit)
10360 && metadata.primary_name != unit.identifier()
10361 {
10362 by_identifier
10363 .entry(metadata.primary_name)
10364 .or_default()
10365 .push(unit.clone());
10366 }
10367 }
10368 BoundedVisibilityDeclarations { all, by_identifier }
10369}
10370
10371#[derive(Default)]
10372struct BoundedVisibilityStats {
10373 rounds: usize,
10374 root_names: usize,
10375 identifier_lookups: usize,
10376 identifier_batches: usize,
10377 candidate_units: usize,
10378 candidate_sources: usize,
10379 declaration_reads: usize,
10380 declaration_units: usize,
10381 selected_units: usize,
10382 dependency_source_names: usize,
10383 dependency_names: usize,
10384 lookup_elapsed: Duration,
10385 declaration_elapsed: Duration,
10386 dependency_source_elapsed: Duration,
10387}
10388
10389fn bounded_visibility_declarations_in_reading(
10390 analyzer: &CppGraphSource<'_>,
10391 file: &ProjectFile,
10392 c_semantics: bool,
10393) -> BTreeSet<CodeUnit> {
10394 #[cfg(any(test, feature = "test-support"))]
10395 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
10396 analyzer.declarations_in_reading(file, c_semantics)
10397}
10398
10399#[cfg(any(test, feature = "test-support"))]
10400pub fn reset_bounded_visibility_declaration_read_count_for_test() {
10401 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
10402}
10403
10404#[cfg(any(test, feature = "test-support"))]
10405pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
10406 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
10407}
10408
10409#[cfg(any(test, feature = "test-support"))]
10410pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
10411 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
10412}
10413
10414#[cfg(any(test, feature = "test-support"))]
10415pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
10416 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
10417}
10418
10419#[derive(Default)]
10438struct OutOfLineOwnerBindingStats {
10439 unseen_owners: usize,
10440 definition_lookups: usize,
10441 admitted: usize,
10442}
10443
10444fn extend_with_out_of_line_owner_bindings(
10445 cpp: &dyn CppSource,
10446 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
10447) -> OutOfLineOwnerBindingStats {
10448 let mut stats = OutOfLineOwnerBindingStats::default();
10449 for (file, visible) in visible_by_file.iter_mut() {
10450 let mut unseen_owners: HashSet<String> = visible
10454 .iter()
10455 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
10456 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
10457 .collect();
10458 if unseen_owners.is_empty() {
10459 continue;
10460 }
10461 for unit in visible.iter().filter(|unit| unit.is_class()) {
10462 unseen_owners.remove(&unit.fq_name());
10463 }
10464 stats.unseen_owners += unseen_owners.len();
10465 stats.definition_lookups += unseen_owners.len();
10466 let admitted = unseen_owners
10467 .iter()
10468 .flat_map(|owner| cpp.definitions(owner))
10469 .filter(CodeUnit::is_class)
10470 .collect::<Vec<_>>();
10471 stats.admitted += admitted.len();
10472 visible.extend(admitted);
10473 }
10474 stats
10475}
10476
10477pub enum VisibleMemberResolution {
10478 Callable(Vec<CodeUnit>),
10479 NonCallable,
10480 AmbiguousKind,
10481 Missing,
10482}
10483
10484#[derive(Clone)]
10485pub enum EnclosingMemberOwnerResolution {
10486 Owner(CodeUnit),
10487 Ambiguous,
10488 Missing,
10489}
10490
10491pub fn resolve_declaring_member_owner(
10492 analyzer: &CppGraphSource<'_>,
10493 visibility: &VisibilityIndex<'_>,
10494 file: &ProjectFile,
10495 receiver_owner: &CodeUnit,
10496 member_name: &str,
10497) -> EnclosingMemberOwnerResolution {
10498 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10499 return EnclosingMemberOwnerResolution::Missing;
10500 };
10501 let Some(receiver_owner) =
10502 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
10503 else {
10504 return EnclosingMemberOwnerResolution::Ambiguous;
10505 };
10506 let resolve_level = |frontier: &[CodeUnit]| {
10507 let mut member_owners = Vec::new();
10508 for raw_owner in frontier {
10509 let Some(owner) =
10510 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
10511 else {
10512 return EnclosingMemberOwnerResolution::Ambiguous;
10513 };
10514 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
10515 if !member.is_field() && !member.is_function() {
10520 continue;
10521 }
10522 let Some(member_owner) = type_owner_of(analyzer, member) else {
10523 return EnclosingMemberOwnerResolution::Ambiguous;
10524 };
10525 if !member_owners
10526 .iter()
10527 .any(|existing| same_visible_symbol(existing, &member_owner))
10528 {
10529 member_owners.push(member_owner);
10530 }
10531 }
10532 }
10533 match member_owners.len() {
10534 0 => EnclosingMemberOwnerResolution::Missing,
10535 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
10536 _ => EnclosingMemberOwnerResolution::Ambiguous,
10537 }
10538 };
10539 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
10543 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
10544 return direct;
10545 }
10546 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
10547 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
10548 let mut path_matches = Vec::new();
10549 while let Some(raw_owner) = stack.pop() {
10550 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
10551 else {
10552 return EnclosingMemberOwnerResolution::Ambiguous;
10553 };
10554 let propagated = propagated_counts.entry(owner.clone()).or_default();
10558 if *propagated == 2 {
10559 continue;
10560 }
10561 *propagated += 1;
10562 match resolve_level(std::slice::from_ref(&owner)) {
10563 EnclosingMemberOwnerResolution::Owner(owner) => {
10564 path_matches.push(owner);
10565 if path_matches.len() == 2 {
10566 return EnclosingMemberOwnerResolution::Ambiguous;
10567 }
10568 }
10569 EnclosingMemberOwnerResolution::Ambiguous => {
10570 return EnclosingMemberOwnerResolution::Ambiguous;
10571 }
10572 EnclosingMemberOwnerResolution::Missing => {
10573 stack.extend(hierarchy.get_direct_ancestors(&owner));
10574 }
10575 }
10576 }
10577 match path_matches.len() {
10578 0 => EnclosingMemberOwnerResolution::Missing,
10579 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
10580 _ => unreachable!("base-path matches are capped at one before returning"),
10581 }
10582}
10583
10584pub fn resolve_declaring_callable_owner(
10599 analyzer: &CppGraphSource<'_>,
10600 visibility: &VisibilityIndex<'_>,
10601 file: &ProjectFile,
10602 ordinary: EnclosingMemberOwnerResolution,
10603 member_name: &str,
10604 call_arity: usize,
10605) -> EnclosingMemberOwnerResolution {
10606 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
10607 return ordinary;
10608 };
10609 if visibility
10610 .visible_members_for_owner_name(file, ordinary_owner, member_name)
10611 .into_iter()
10612 .any(|unit| {
10613 unit.is_function()
10614 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
10615 })
10616 {
10617 return ordinary;
10618 }
10619
10620 let mut pending = match member_using_declaration_bases(
10621 analyzer,
10622 visibility,
10623 file,
10624 ordinary_owner,
10625 member_name,
10626 ) {
10627 Ok(bases) => bases,
10628 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10629 };
10630 let mut visited = HashSet::default();
10631 let mut introduced_owners = Vec::new();
10632 while let Some(owner) = pending.pop() {
10633 if !visited.insert(owner.clone()) {
10634 continue;
10635 }
10636 let accepts_arity = visibility
10637 .visible_members_for_owner_name(file, &owner, member_name)
10638 .into_iter()
10639 .any(|unit| {
10640 unit.is_function()
10641 && cpp_callable_arity(analyzer, unit)
10642 .is_none_or(|known| known.accepts(call_arity))
10643 });
10644 if accepts_arity {
10645 if !introduced_owners
10646 .iter()
10647 .any(|existing| same_visible_symbol(existing, &owner))
10648 {
10649 introduced_owners.push(owner);
10650 }
10651 continue;
10652 }
10653 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10654 Ok(bases) => pending.extend(bases),
10655 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10656 }
10657 }
10658 match introduced_owners.as_slice() {
10659 [] => ordinary,
10660 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10661 _ => EnclosingMemberOwnerResolution::Ambiguous,
10662 }
10663}
10664
10665fn member_using_declaration_bases(
10666 analyzer: &CppGraphSource<'_>,
10667 visibility: &VisibilityIndex<'_>,
10668 file: &ProjectFile,
10669 owner: &CodeUnit,
10670 member_name: &str,
10671) -> Result<Vec<CodeUnit>, ()> {
10672 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10673 return Err(());
10674 };
10675 let scopes: Vec<_> = facts
10676 .iter()
10677 .flat_map(|fact| &fact.member_usings)
10678 .filter(|using| using.member == member_name)
10679 .map(|using| using.scope.join("::"))
10680 .collect();
10681 if scopes.is_empty() {
10682 return Ok(Vec::new());
10683 }
10684 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10685 return Ok(Vec::new());
10686 };
10687 let mut bases = Vec::new();
10688 for raw_ancestor in hierarchy.get_ancestors(owner) {
10689 let Some(ancestor) =
10690 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10691 else {
10692 return Err(());
10693 };
10694 let qualified = cpp_name_for(&ancestor);
10695 if scopes
10696 .iter()
10697 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10698 && !bases
10699 .iter()
10700 .any(|existing| same_visible_symbol(existing, &ancestor))
10701 {
10702 bases.push(ancestor);
10703 }
10704 }
10705 Ok(bases)
10706}
10707
10708pub fn lexical_component_tiers<'a>(
10709 components: &'a [String],
10710 global: bool,
10711 lexical_scope: &'a [String],
10712) -> impl Iterator<Item = Vec<String>> + 'a {
10713 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10714 (0..=first_prefix_len).rev().map(move |prefix_len| {
10715 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10716 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10717 qualified.extend_from_slice(components);
10718 qualified
10719 })
10720}
10721
10722pub fn build_visible_identifier_index(
10723 analyzer: &CppGraphSource<'_>,
10724 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10725 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10726 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10727) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10728 let mut out = HashMap::default();
10729 for (file, visible) in visible_by_file {
10730 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10731 for unit in visible {
10732 if unit.is_field()
10733 && !visible_source_files_by_root
10734 .get(file)
10735 .is_some_and(|sources| sources.contains(unit.source()))
10736 && cpp_global_field_has_internal_linkage_cached(
10737 analyzer,
10738 global_field_internal_linkage,
10739 unit,
10740 )
10741 {
10742 continue;
10743 }
10744 by_identifier
10745 .entry(unit.identifier().to_string())
10746 .or_default()
10747 .push(unit.clone());
10748 }
10749 for units in by_identifier.values_mut() {
10750 sort_lookup_units(units);
10751 units.dedup();
10752 }
10753 out.insert(file.clone(), by_identifier);
10754 }
10755 out
10756}
10757
10758fn sort_lookup_units(units: &mut [CodeUnit]) {
10759 units.sort_by(|left, right| {
10760 left.fq_name()
10761 .cmp(&right.fq_name())
10762 .then_with(|| left.signature().cmp(&right.signature()))
10763 .then_with(|| left.source().cmp(right.source()))
10764 .then_with(|| left.kind().cmp(&right.kind()))
10765 .then_with(|| {
10766 left.package_segment_count()
10767 .cmp(&right.package_segment_count())
10768 })
10769 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10770 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10771 });
10772}
10773
10774fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10775 let interner = segment_interner();
10776 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10777 let (left_text, left_kind) = interner.resolve(left_id);
10778 let (right_text, right_kind) = interner.resolve(right_id);
10779 let order = left_text
10780 .cmp(right_text)
10781 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10782 if order != CmpOrdering::Equal {
10783 return order;
10784 }
10785 }
10786 left.len().cmp(&right.len())
10787}
10788
10789const fn segment_kind_order(kind: SegmentKind) -> u8 {
10790 match kind {
10791 SegmentKind::Path => 0,
10792 SegmentKind::Package => 1,
10793 SegmentKind::Type => 2,
10794 SegmentKind::Companion => 3,
10795 SegmentKind::Nested => 4,
10796 SegmentKind::Member => 5,
10797 SegmentKind::Unknown => 6,
10798 }
10799}
10800
10801fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10802 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10803 std::mem::take(units),
10804 );
10805}
10806
10807pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10808 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10812 brokk_bifrost_core::analyzer::Language::Cpp,
10813 reference,
10814 );
10815 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10816}
10817
10818fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10819 if parts.is_empty() {
10820 return Vec::new();
10821 }
10822
10823 let mut candidates = Vec::new();
10824 for package_len in 0..parts.len() {
10825 let package = parts[..package_len].join("::");
10826 let rest = &parts[package_len..];
10827 if rest.is_empty() {
10828 continue;
10829 }
10830 match kind {
10831 TargetKind::Type | TargetKind::Constructor => {
10832 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10833 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10834 }
10835 TargetKind::FreeFunction
10836 | TargetKind::Method
10837 | TargetKind::GlobalField
10838 | TargetKind::MemberField
10839 | TargetKind::Macro => {
10840 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10841 if rest.len() > 1 {
10842 let owner = rest[..rest.len() - 1].join("$");
10843 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10844 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10845 }
10846 }
10847 }
10848 }
10849 candidates
10850}
10851
10852fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10853 let fqn = if package.is_empty() {
10854 short.to_string()
10855 } else {
10856 format!("{package}.{short}")
10857 };
10858 if !out.contains(&fqn) {
10859 out.push(fqn);
10860 }
10861}
10862
10863pub fn infer_cpp_initializer_type(
10864 analyzer: &CppGraphSource<'_>,
10865 visibility: &VisibilityIndex<'_>,
10866 file: &ProjectFile,
10867 source: &str,
10868 node: Node<'_>,
10869) -> Option<CodeUnit> {
10870 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10871 .and_then(|binding| binding.unit)
10872}
10873
10874pub fn infer_cpp_initializer_binding(
10875 analyzer: &CppGraphSource<'_>,
10876 visibility: &VisibilityIndex<'_>,
10877 file: &ProjectFile,
10878 source: &str,
10879 node: Node<'_>,
10880 receiver_resolver: Option<&ReceiverResolver<'_>>,
10881) -> Option<CppScanBinding> {
10882 match node.kind() {
10883 "new_expression" => {
10884 let type_node = cpp_new_expression_type_node(node)?;
10885 let type_text = node_text(type_node, source);
10886 let name = normalize_cpp_type_name(type_text);
10887 Some(CppScanBinding::from_type_name(
10888 name.clone(),
10889 visibility.resolve_type(file, &name),
10890 1,
10891 ))
10892 }
10893 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10894 if function.kind() == "field_expression" {
10902 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10903 return resolve_field_method_call_return_binding(
10904 analyzer,
10905 visibility,
10906 file,
10907 source,
10908 function,
10909 arity,
10910 receiver_resolver,
10911 );
10912 }
10913 let function_text = node_text(function, source);
10914 let direct_type_binding = visibility
10915 .resolve_type(file, function_text)
10916 .map(|unit| CppScanBinding::from_unit(unit, 0));
10917 if function.kind() == "template_function" && direct_type_binding.is_some() {
10918 let lexical_namespace = enclosing_namespace_context(node, source);
10919 let arity = visibility.call_arity_evidence(file, node, source).exact();
10920 if let Some(arity) = arity
10921 && let Some(binding) = visibility.resolve_call_return_binding(
10922 analyzer,
10923 file,
10924 function_text,
10925 arity,
10926 lexical_namespace.as_deref(),
10927 direct_type_binding
10928 .as_ref()
10929 .and_then(|binding| binding.unit.as_ref()),
10930 )
10931 {
10932 return Some(binding);
10933 }
10934 let (has_callable, callable_binding) = visibility
10935 .resolve_call_return_binding_without_arity(
10936 analyzer,
10937 file,
10938 function_text,
10939 lexical_namespace.as_deref(),
10940 direct_type_binding
10941 .as_ref()
10942 .and_then(|binding| binding.unit.as_ref()),
10943 );
10944 if let Some(binding) = callable_binding {
10945 return Some(binding);
10946 }
10947 if has_callable {
10948 return None;
10949 }
10950 return direct_type_binding;
10951 }
10952 let arity = visibility.call_arity_evidence(file, node, source).exact();
10957 if let Some(arity) = arity {
10958 let direct_type_binding_for_call = direct_type_binding.clone();
10959 if let Some(binding) = resolve_static_method_call_return_binding(
10960 analyzer, visibility, file, source, function, arity,
10961 )
10962 .or_else(|| {
10963 visibility.resolve_call_return_binding(
10968 analyzer,
10969 file,
10970 function_text,
10971 arity,
10972 enclosing_namespace_context(node, source).as_deref(),
10973 direct_type_binding_for_call
10974 .as_ref()
10975 .and_then(|binding| binding.unit.as_ref()),
10976 )
10977 }) {
10978 return Some(binding);
10979 }
10980 }
10981 direct_type_binding
10982 }),
10983 _ => None,
10984 }
10985}
10986
10987pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10993 debug_assert_eq!(expression.kind(), "new_expression");
10994 if let Some(type_node) = expression.child_by_field_name("type") {
10995 return Some(type_node);
10996 }
10997
10998 let mut pending = Vec::new();
10999 let mut cursor = expression.walk();
11000 pending.extend(
11001 expression
11002 .named_children(&mut cursor)
11003 .filter(|child| {
11004 !matches!(
11005 child.kind(),
11006 "argument_list" | "initializer_list" | "new_declarator"
11007 )
11008 })
11009 .collect::<Vec<_>>()
11010 .into_iter()
11011 .rev(),
11012 );
11013 while let Some(node) = pending.pop() {
11014 if is_cpp_type_specifier_node(node) {
11015 return Some(node);
11016 }
11017 if node.kind() == "ERROR" {
11018 let mut cursor = node.walk();
11019 pending.extend(
11020 node.named_children(&mut cursor)
11021 .collect::<Vec<_>>()
11022 .into_iter()
11023 .rev(),
11024 );
11025 }
11026 }
11027 None
11028}
11029
11030fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
11031 matches!(
11032 node.kind(),
11033 "class_specifier"
11034 | "decltype"
11035 | "dependent_type"
11036 | "enum_specifier"
11037 | "placeholder_type_specifier"
11038 | "primitive_type"
11039 | "qualified_identifier"
11040 | "sized_type_specifier"
11041 | "struct_specifier"
11042 | "template_type"
11043 | "type_identifier"
11044 | "union_specifier"
11045 )
11046}
11047
11048fn resolve_static_method_call_return_binding(
11049 analyzer: &CppGraphSource<'_>,
11050 visibility: &VisibilityIndex<'_>,
11051 file: &ProjectFile,
11052 source: &str,
11053 function: Node<'_>,
11054 arity: usize,
11055) -> Option<CppScanBinding> {
11056 if function.kind() != "qualified_identifier" {
11057 return None;
11058 }
11059 let qualified = normalize_cpp_reference_text(node_text(function, source));
11060 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
11067 brokk_bifrost_core::analyzer::Language::Cpp,
11068 &qualified,
11069 );
11070 let (owner_text, member_name) = match parts.split_last() {
11071 Some((member, owner_parts)) if !owner_parts.is_empty() => {
11072 (owner_parts.join("::"), member.clone())
11073 }
11074 _ => {
11075 let scope = function.child_by_field_name("scope")?;
11076 let name = function.child_by_field_name("name")?;
11077 (
11078 node_text(scope, source).to_string(),
11079 node_text(name, source).to_string(),
11080 )
11081 }
11082 };
11083 let owner = visibility.resolve_type(file, &owner_text)?;
11084 let candidates =
11085 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
11086 unanimous_return_binding(analyzer, visibility, file, &candidates)
11087}
11088
11089fn resolve_field_method_call_return_binding(
11090 analyzer: &CppGraphSource<'_>,
11091 visibility: &VisibilityIndex<'_>,
11092 file: &ProjectFile,
11093 source: &str,
11094 function: Node<'_>,
11095 arity: usize,
11096 receiver_resolver: Option<&ReceiverResolver<'_>>,
11097) -> Option<CppScanBinding> {
11098 debug_assert_eq!(
11099 function.kind(),
11100 "field_expression",
11101 "the member-call return binding answers only for a field-expression callee"
11102 );
11103 let receiver_resolver = receiver_resolver?;
11104 let field = function.child_by_field_name("field")?;
11105 let member_name = node_text(function_terminal_node(field), source);
11106 let receiver = function
11107 .child_by_field_name("argument")
11108 .or_else(|| function.named_child(0))?;
11109 let owners = receiver_resolver(receiver, source);
11110 let mut candidates = Vec::new();
11111 for owner in owners {
11112 let declaring_owner =
11113 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
11114 EnclosingMemberOwnerResolution::Owner(owner) => owner,
11115 EnclosingMemberOwnerResolution::Missing => continue,
11116 EnclosingMemberOwnerResolution::Ambiguous => return None,
11117 };
11118 candidates.extend(callable_members_accepting_arity(
11119 analyzer,
11120 visibility,
11121 file,
11122 &declaring_owner,
11123 member_name,
11124 arity,
11125 ));
11126 }
11127 unanimous_return_binding(analyzer, visibility, file, &candidates)
11128}
11129
11130fn callable_members_accepting_arity(
11131 analyzer: &CppGraphSource<'_>,
11132 visibility: &VisibilityIndex<'_>,
11133 file: &ProjectFile,
11134 owner: &CodeUnit,
11135 member_name: &str,
11136 arity: usize,
11137) -> Vec<CodeUnit> {
11138 visibility
11139 .visible_members_for_owner_name(file, owner, member_name)
11140 .into_iter()
11141 .filter(|unit| {
11142 unit.is_function()
11143 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
11144 })
11145 .cloned()
11146 .collect()
11147}
11148
11149fn unanimous_return_binding(
11150 analyzer: &CppGraphSource<'_>,
11151 visibility: &VisibilityIndex<'_>,
11152 file: &ProjectFile,
11153 candidates: &[CodeUnit],
11154) -> Option<CppScanBinding> {
11155 let mut resolved_return: Option<CppScanBinding> = None;
11156 for function in candidates {
11157 let metadata = analyzer.signature_metadata(function);
11158 let return_types = if metadata.is_empty() {
11159 vec![cpp_function_return_type_text(analyzer, function)?]
11160 } else {
11161 metadata
11162 .iter()
11163 .map(|metadata| metadata.return_type_text().map(str::to_string))
11164 .collect::<Option<Vec<_>>>()?
11165 };
11166 for return_text in return_types {
11167 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
11168 let name = normalize_cpp_type_name(&return_text);
11169 let binding = CppScanBinding::from_type_name(
11170 name.clone(),
11171 visibility
11172 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
11173 indirection,
11174 );
11175 if let Some(existing) = resolved_return.as_ref()
11176 && (existing.indirection != binding.indirection
11177 || match (&existing.unit, &binding.unit) {
11178 (Some(left), Some(right)) => !same_visible_symbol(left, right),
11179 (None, None) => existing.type_name != binding.type_name,
11180 (Some(_), None) | (None, Some(_)) => true,
11181 })
11182 {
11183 return None;
11184 }
11185 resolved_return = Some(binding);
11186 }
11187 }
11188 resolved_return
11189}
11190
11191fn aliases_from_canonical_source(
11192 cpp: &dyn CppSource,
11193 token: QueryToken<'_>,
11194 file: &ProjectFile,
11195) -> Option<Vec<CppAlias>> {
11196 let source = cpp.declaration_source_facts(token, file)?;
11197 Some(
11198 source
11199 .facts
11200 .declarations
11201 .iter()
11202 .filter_map(|fact| {
11203 let alias = fact.file_scope_alias.as_ref()?;
11204 Some(CppAlias {
11205 name: alias.name.clone(),
11206 target: alias.target.clone(),
11207 namespace: alias.namespace.clone(),
11208 })
11209 })
11210 .collect(),
11211 )
11212}
11213
11214pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
11215 let mut current = node.parent();
11216 while let Some(parent) = current {
11217 match parent.kind() {
11218 "translation_unit"
11219 | "namespace_definition"
11220 | "declaration_list"
11221 | "linkage_specification" => current = parent.parent(),
11222 "template_declaration" => current = parent.parent(),
11223 _ => return false,
11224 }
11225 }
11226 true
11227}
11228
11229pub fn collect_include_closure(
11230 analyzer: &CppGraphSource<'_>,
11231 include_targets: &IncludeTargetIndex,
11232 file: &ProjectFile,
11233 out: &mut HashSet<ProjectFile>,
11234 cancellation: Option<&CancellationToken>,
11235) {
11236 let mut stack = vec![file.clone()];
11237 while let Some(file) = stack.pop() {
11238 if cancellation.is_some_and(CancellationToken::is_cancelled) {
11239 break;
11240 }
11241 if !out.insert(file.clone()) {
11242 continue;
11243 }
11244 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
11245 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
11246 stack.push(target);
11247 }
11248 }
11249 }
11250}
11251
11252fn parse_macro_parameter_list<T>(
11253 replacement: &str,
11254 read: impl FnOnce(Node<'_>, &str) -> T,
11255) -> Option<T> {
11256 let parsed = parse_sentinel(
11257 "void __bifrost_macro_parameters(",
11258 SentinelInput::replacement(replacement),
11259 ");",
11260 );
11261 let (source, tree) = (parsed.source, parsed.tree);
11262 let root = tree.root_node();
11263 if root.has_error() {
11264 return None;
11265 }
11266 let declaration = single_named_child_matching(root, |child| !child.is_extra())?;
11267 let declarator = declaration.child_by_field_name("declarator")?;
11268 let parameters = declarator.child_by_field_name("parameters")?;
11269 Some(read(parameters, &source))
11270}
11271
11272fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
11273 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
11274}
11275
11276pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
11279 parse_macro_parameter_list(
11280 replacement,
11281 crate::declarations::cpp_callable_parameter_types,
11282 )
11283}
11284
11285pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
11286 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
11287}
11288
11289pub fn cpp_callable_parameter_types(
11290 analyzer: &CppGraphSource<'_>,
11291 unit: &CodeUnit,
11292) -> Option<Vec<String>> {
11293 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
11294 analyzer.signature_metadata(unit),
11295 )
11296}
11297
11298fn merge_compatible_callable_arities(
11299 left: CallableArity,
11300 right: CallableArity,
11301) -> Option<CallableArity> {
11302 let total = left.total();
11303 let left_repeated = left.accepts(total.saturating_add(1));
11304 let right_repeated = right.accepts(right.total().saturating_add(1));
11305 if total != right.total() || left_repeated != right_repeated {
11306 return None;
11307 }
11308 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
11309 Some(CallableArity::new(required, total, left_repeated))
11310}
11311
11312fn collect_macro_events(
11313 cpp: &dyn CppSource,
11314 token: QueryToken<'_>,
11315 file: &ProjectFile,
11316) -> Vec<MacroEvent> {
11317 let Some(prepared) = cpp.prepared_syntax(token, file) else {
11318 return Vec::new();
11319 };
11320 let source = prepared.source();
11321 let mut events = Vec::new();
11322 let root = prepared.tree().root_node();
11323 let mut stack = vec![root];
11324 while let Some(node) = stack.pop() {
11325 match node.kind() {
11326 "preproc_def" | "preproc_function_def" => {
11327 let Some(name) = node.child_by_field_name("name") else {
11328 continue;
11329 };
11330 let name = node_text(name, source).to_string();
11331 events.push(MacroEvent::Define {
11332 name,
11333 binding: MacroBinding {
11334 source: file.clone(),
11335 declaration_byte: node.start_byte(),
11336 definition: VisibilityIndex::decode_macro_definition(node, source),
11337 exact: true,
11338 },
11339 byte: node.start_byte(),
11340 conditionals: owning_preprocessor_conditionals(root, node, source),
11341 });
11342 continue;
11343 }
11344 "preproc_include" => {
11345 let Some(path) = node.child_by_field_name("path") else {
11346 events.push(MacroEvent::Include {
11347 targets: Vec::new(),
11348 byte: node.start_byte(),
11349 conditionals: owning_preprocessor_conditionals(root, node, source),
11350 });
11351 continue;
11352 };
11353 let include = crate::imports::include_path_literal_value(path, source);
11354 let targets = include.map_or_else(Vec::new, |include| {
11355 resolve_include_targets_with_index(file, include, cpp.include_target_index())
11356 });
11357 if targets.is_empty()
11368 && include.is_some_and(|include| {
11369 !cpp.include_target_index().names_indexed_file(include)
11370 })
11371 {
11372 continue;
11373 }
11374 events.push(MacroEvent::Include {
11375 targets,
11376 byte: node.start_byte(),
11377 conditionals: owning_preprocessor_conditionals(root, node, source),
11378 });
11379 continue;
11380 }
11381 "preproc_call" => {
11382 let Some(directive) = node.child_by_field_name("directive") else {
11383 continue;
11384 };
11385 if node_text(directive, source) != "#undef" {
11386 continue;
11387 }
11388 let name = node
11389 .child_by_field_name("argument")
11390 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
11391 if let Some(name) = name {
11392 events.push(MacroEvent::Undef {
11393 name,
11394 byte: node.start_byte(),
11395 conditionals: owning_preprocessor_conditionals(root, node, source),
11396 });
11397 } else {
11398 events.push(MacroEvent::Invalidate {
11399 byte: node.start_byte(),
11400 });
11401 }
11402 continue;
11403 }
11404 _ => {}
11405 }
11406 push_named_children_reversed(node, &mut stack);
11407 }
11408 events.sort_by_key(MacroEvent::byte);
11409 events
11410}
11411
11412fn find_include_activation<'guards>(
11413 cpp: &dyn CppSource,
11414 token: QueryToken<'_>,
11415 file: &ProjectFile,
11416 prepared: &PreparedSyntaxTree,
11417 donor_source: &ProjectFile,
11418 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11419) -> Option<usize> {
11420 let include_targets = cpp.include_target_index();
11421 let mut direct_includes = Vec::new();
11422 let mut nodes = vec![prepared.tree().root_node()];
11423 while let Some(node) = nodes.pop() {
11426 if node.kind() == "preproc_include" {
11427 let Some(required_guards) =
11428 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11429 else {
11430 continue;
11431 };
11432 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11433 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11434 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
11435 file,
11436 include,
11437 include_targets,
11438 )) {
11439 direct_includes.push((node.end_byte(), target));
11440 }
11441 }
11442 }
11443 continue;
11444 }
11445 push_named_children_reversed(node, &mut nodes);
11446 }
11447 direct_includes.sort_by_key(|(activation, _)| *activation);
11448 let mut known_missing = HashSet::default();
11449 direct_includes
11450 .into_iter()
11451 .find(|(_, direct)| {
11452 unconditional_include_reaches(
11453 cpp,
11454 token,
11455 direct,
11456 donor_source,
11457 file,
11458 &mut known_missing,
11459 effective_guards,
11460 )
11461 })
11462 .map(|(activation, _)| activation)
11463}
11464
11465fn find_conditional_include_projection_index<'guards>(
11466 cpp: &dyn CppSource,
11467 token: QueryToken<'_>,
11468 file: &ProjectFile,
11469 prepared: &PreparedSyntaxTree,
11470 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11471 on_state: &dyn Fn(),
11472) -> ConditionalIncludeProjectionIndex {
11473 let include_targets = cpp.include_target_index();
11474 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
11475 HashMap::default();
11476 let mut pending = Vec::new();
11477 let mut nodes = vec![prepared.tree().root_node()];
11478 while let Some(node) = nodes.pop() {
11479 if node.kind() == "preproc_include" {
11480 let Some(required_guards) =
11481 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11482 else {
11483 continue;
11484 };
11485 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11486 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11487 file,
11488 include,
11489 include_targets,
11490 )) else {
11491 continue;
11492 };
11493 pending.push((target, node.end_byte(), required_guards.clone()));
11494 }
11495 continue;
11496 }
11497 push_named_children_reversed(node, &mut nodes);
11498 }
11499
11500 let mut expanded: HashMap<(ProjectFile, usize), Vec<PreprocessorGuardEnvironment>> =
11506 HashMap::default();
11507 while let Some((current_file, activation_byte, path)) = pending.pop() {
11508 let guard_sets = expanded
11509 .entry((current_file.clone(), activation_byte))
11510 .or_default();
11511 if guard_sets.iter().any(|existing| existing.is_subset(&path)) {
11512 continue;
11513 }
11514 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
11515 .drain(..)
11516 .partition(|existing| path.is_subset(existing));
11517 *guard_sets = kept;
11518 guard_sets.push(path.clone());
11519 if !evicted.is_empty()
11520 && let Some(projections) = projections_by_source.get_mut(¤t_file)
11521 {
11522 projections.retain(|projection| {
11523 projection.activation_byte != activation_byte
11524 || !evicted.iter().any(|existing| {
11525 existing.all == projection.required_guards
11526 && existing.partial == projection.partial_guards
11527 })
11528 });
11529 }
11530 on_state();
11531
11532 projections_by_source
11535 .entry(current_file.clone())
11536 .or_default()
11537 .push(ConditionalIncludeProjection {
11538 activation_byte,
11539 required_guards: path.all.clone(),
11540 partial_guards: path.partial.clone(),
11541 });
11542
11543 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11544 continue;
11545 };
11546 let mut nodes = vec![current_prepared.tree().root_node()];
11547 while let Some(node) = nodes.pop() {
11548 if node.kind() == "preproc_include" {
11549 let Some(include_guards) = include_directive_guard_requirements(
11550 node,
11551 current_prepared.source(),
11552 effective_guards,
11553 ) else {
11554 continue;
11555 };
11556 let Some(reached) = path.merged(&include_guards) else {
11557 continue;
11558 };
11559 for (include, _) in
11560 include_path_from_node(node, current_prepared.source()).into_iter()
11561 {
11562 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11563 ¤t_file,
11564 include,
11565 include_targets,
11566 )) else {
11567 continue;
11568 };
11569 pending.push((target, activation_byte, reached.clone()));
11570 }
11571 continue;
11572 }
11573 push_named_children_reversed(node, &mut nodes);
11574 }
11575 }
11576
11577 projections_by_source
11578 .into_iter()
11579 .map(|(source, mut projections)| {
11580 projections.sort_by_key(|projection| projection.activation_byte);
11581 (source, Arc::from(projections))
11582 })
11583 .collect()
11584}
11585
11586#[allow(clippy::too_many_arguments)]
11592fn find_conditional_include_projection_for_source<'guards>(
11593 cpp: &dyn CppSource,
11594 token: QueryToken<'_>,
11595 file: &ProjectFile,
11596 prepared: &PreparedSyntaxTree,
11597 donor_source: &ProjectFile,
11598 admission: IncludePathAdmission,
11599 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11600 reference_byte: usize,
11601 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11602 on_state: &dyn Fn(),
11603) -> bool {
11604 let Some(reference_guards) = reference_guards else {
11605 return false;
11606 };
11607 let include_targets = cpp.include_target_index();
11608 let mut pending = Vec::new();
11609 let mut nodes = vec![prepared.tree().root_node()];
11610 while let Some(node) = nodes.pop() {
11611 if node.kind() == "preproc_include" {
11612 let Some(required_guards) =
11613 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11614 else {
11615 continue;
11616 };
11617 if node.end_byte() > reference_byte
11618 || !admission.admits(
11619 &required_guards.all,
11620 &required_guards.partial,
11621 Some(reference_guards),
11622 )
11623 {
11624 continue;
11625 }
11626 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11627 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11628 file,
11629 include,
11630 include_targets,
11631 )) else {
11632 continue;
11633 };
11634 if &target == donor_source {
11635 return true;
11636 }
11637 pending.push((target, required_guards.clone()));
11638 }
11639 continue;
11640 }
11641 push_named_children_reversed(node, &mut nodes);
11642 }
11643
11644 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11645 while let Some((current_file, path)) = pending.pop() {
11646 let guard_sets = expanded.entry(current_file.clone()).or_default();
11647 if guard_sets.contains(&path.all) {
11648 continue;
11649 }
11650 guard_sets.push(path.all.clone());
11651 on_state();
11652
11653 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11654 continue;
11655 };
11656 let mut nodes = vec![current_prepared.tree().root_node()];
11657 while let Some(node) = nodes.pop() {
11658 if node.kind() == "preproc_include" {
11659 let Some(include_guards) = include_directive_guard_requirements(
11660 node,
11661 current_prepared.source(),
11662 effective_guards,
11663 ) else {
11664 continue;
11665 };
11666 let Some(reached) = path.merged(&include_guards) else {
11667 continue;
11668 };
11669 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11670 continue;
11671 }
11672 for (include, _) in
11673 include_path_from_node(node, current_prepared.source()).into_iter()
11674 {
11675 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11676 ¤t_file,
11677 include,
11678 include_targets,
11679 )) else {
11680 continue;
11681 };
11682 if &target == donor_source {
11683 return true;
11684 }
11685 pending.push((target, reached.clone()));
11686 }
11687 continue;
11688 }
11689 push_named_children_reversed(node, &mut nodes);
11690 }
11691 }
11692 false
11693}
11694
11695pub fn cpp_include_closure_reaches(
11708 cpp: &dyn CppSource,
11709 token: QueryToken<'_>,
11710 translation_unit: &ProjectFile,
11711 header: &ProjectFile,
11712) -> bool {
11713 let language_guard_stable = OnceCell::new();
11714 let effective = OnceCell::new();
11715 let proven = compile_proven_guard_facts(cpp, translation_unit);
11716 unconditional_include_reaches(
11717 cpp,
11718 token,
11719 translation_unit,
11720 header,
11721 translation_unit,
11722 &mut HashSet::default(),
11723 &|| {
11724 effective
11725 .get_or_init(|| {
11726 effective_reference_guards(
11727 cpp,
11728 translation_unit,
11729 Some(&HashSet::default()),
11730 &proven,
11731 &|| {
11732 *language_guard_stable.get_or_init(|| {
11733 include_language_guard_is_stable_with(
11734 cpp,
11735 token,
11736 translation_unit,
11737 &mut |file: &ProjectFile| {
11738 collect_macro_events(cpp, token, file).into_boxed_slice()
11739 },
11740 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11741 collect_structured_include_facts(prepared)
11742 },
11743 )
11744 })
11745 },
11746 )
11747 })
11748 .as_ref()
11749 },
11750 )
11751}
11752
11753fn unconditional_include_reaches<'guards>(
11754 cpp: &dyn CppSource,
11755 token: QueryToken<'_>,
11756 first: &ProjectFile,
11757 donor_source: &ProjectFile,
11758 reference_file: &ProjectFile,
11759 known_missing: &mut HashSet<ProjectFile>,
11760 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
11761) -> bool {
11762 if first == donor_source {
11763 return true;
11764 }
11765 if known_missing.contains(first) {
11766 return false;
11767 }
11768 if let Some(reaches) =
11769 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11770 {
11771 return reaches;
11772 }
11773 let mut visited = HashSet::default();
11774 let mut files = vec![first.clone()];
11775 let include_targets = cpp.include_target_index();
11776 while let Some(file) = files.pop() {
11777 if file == *donor_source {
11778 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11779 return true;
11780 }
11781 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11782 continue;
11783 }
11784 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11785 continue;
11786 };
11787 let mut cursor = prepared.tree().walk();
11789 let mut nodes = vec![prepared.tree().root_node()];
11790 while let Some(node) = nodes.pop() {
11791 if node.kind() == "preproc_include" {
11792 let Some(required_guards) =
11793 include_directive_guard_requirements(node, prepared.source(), effective_guards)
11794 else {
11795 continue;
11796 };
11797 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11798 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11799 {
11800 if let Some(target) = unique_include_target(
11801 resolve_include_targets_with_index(&file, include, include_targets),
11802 ) {
11803 files.push(target);
11804 }
11805 }
11806 }
11807 continue;
11808 }
11809 let first_pushed = nodes.len();
11810 nodes.extend(node.named_children(&mut cursor));
11811 nodes[first_pushed..].reverse();
11812 }
11813 }
11814 known_missing.extend(visited);
11815 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11816 false
11817}
11818
11819fn effective_reference_guards(
11826 cpp: &dyn CppSource,
11827 file: &ProjectFile,
11828 reference_guards: Option<&HashSet<PreprocessorGuard>>,
11829 proven: &HashSet<PreprocessorGuard>,
11830 language_guard_is_stable: &dyn Fn() -> bool,
11831) -> Option<HashSet<PreprocessorGuard>> {
11832 let mut effective = reference_guards.cloned()?;
11833 effective.extend(proven.iter().cloned());
11834 if language_guard_is_stable() {
11835 effective.insert(if reference_uses_c_semantics(cpp, file) {
11836 PreprocessorGuard::Undefined("__cplusplus".to_string())
11837 } else {
11838 PreprocessorGuard::Defined("__cplusplus".to_string())
11839 });
11840 }
11841 Some(effective)
11842}
11843
11844fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11845 cpp: &dyn CppSource,
11846 token: QueryToken<'_>,
11847 file: &ProjectFile,
11848 mut macro_events: MacroEvents,
11849 mut include_facts: IncludeFacts,
11850) -> bool
11851where
11852 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11853 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11854{
11855 if !crate::imports::is_cpp_translation_unit(file) {
11859 return false;
11860 }
11861 if !cpp.compile_contexts_for(file).is_empty()
11864 || cpp
11865 .reaching_translation_units(file)
11866 .iter()
11867 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11868 {
11869 return false;
11870 }
11871 let mut pending = vec![file.clone()];
11872 let mut visited = HashSet::default();
11873 while let Some(file) = pending.pop() {
11874 if !visited.insert(file.clone()) {
11875 continue;
11876 }
11877 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11878 return false;
11879 };
11880 if macro_events(&file).iter().any(|event| match event {
11881 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11882 name == "__cplusplus"
11883 }
11884 MacroEvent::Invalidate { .. } => true,
11885 MacroEvent::Include { targets, .. } => targets.is_empty(),
11886 }) {
11887 return false;
11888 }
11889 for fact in include_facts(&file, prepared.as_ref()).iter() {
11892 let targets =
11893 resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11894 if targets.is_empty() {
11895 return false;
11896 }
11897 pending.extend(targets);
11898 }
11899 }
11900 true
11901}
11902
11903fn declaration_guard_requirements(
11904 analyzer: &CppGraphSource<'_>,
11905 cpp: &dyn CppSource,
11906 candidate: &CodeUnit,
11907) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11908 if cpp
11909 .prepared_syntax(analyzer.token, candidate.source())
11910 .is_none()
11911 {
11912 return Vec::new();
11913 }
11914 analyzer
11915 .declaration_source_occurrences(candidate)
11916 .into_iter()
11917 .flatten()
11918 .filter_map(|occurrence| {
11919 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11920 Some((occurrence.range.start_byte, guards))
11921 })
11922 .collect()
11923}
11924
11925struct DeclarationGuardSite {
11928 byte: usize,
11930 guards: HashSet<PreprocessorGuard>,
11932 completed_family_before_reference: bool,
11935}
11936
11937fn declaration_guard_sites(
11940 analyzer: &CppGraphSource<'_>,
11941 cpp: &dyn CppSource,
11942 candidate: &CodeUnit,
11943 reference_byte: usize,
11944) -> Vec<DeclarationGuardSite> {
11945 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11946 return Vec::new();
11947 };
11948 let root = prepared.tree().root_node();
11949 analyzer
11950 .declaration_source_occurrences(candidate)
11951 .into_iter()
11952 .flatten()
11953 .filter_map(|occurrence| {
11954 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11955 Some(DeclarationGuardSite {
11956 byte: occurrence.range.start_byte,
11957 guards,
11958 completed_family_before_reference: {
11959 root.descendant_for_byte_range(
11960 occurrence.range.start_byte,
11961 occurrence.range.end_byte,
11962 )
11963 .is_some_and(|node| {
11964 declaration_follows_completed_family_before_reference(
11965 node,
11966 prepared.source(),
11967 reference_byte,
11968 )
11969 })
11970 },
11971 })
11972 })
11973 .collect()
11974}
11975
11976fn declaration_follows_completed_family_before_reference(
11995 node: Node<'_>,
11996 source: &str,
11997 reference_byte: usize,
11998) -> bool {
11999 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
12000}
12001
12002pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
12005 let mut cache = PreprocessorContextCache::default();
12006 callable_guard_completion_byte_with_cache(node, source, &mut cache)
12007}
12008
12009pub(crate) fn callable_guard_completion_byte_with_cache(
12010 node: Node<'_>,
12011 source: &str,
12012 cache: &mut PreprocessorContextCache,
12013) -> Option<usize> {
12014 let mut completion_byte = 0;
12015 let mut ancestor = node.parent();
12016 while let Some(conditional) = ancestor {
12017 if matches!(
12018 conditional.kind(),
12019 "preproc_if" | "preproc_ifdef" | "preproc_elif"
12020 ) && !is_file_covering_include_guard(conditional, source)
12021 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12022 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12023 {
12024 let family = preprocessor_conditional_family_root(conditional);
12025 if !preprocessor_conditional_family_has_terminal_else(family, cache) {
12026 return None;
12027 }
12028 completion_byte = completion_byte.max(family.end_byte());
12029 }
12030 ancestor = conditional.parent();
12031 }
12032 Some(completion_byte)
12033}
12034
12035fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
12036 analyzer
12037 .ranges(candidate)
12038 .into_iter()
12039 .map(|range| range.start_byte)
12040 .min()
12041}
12042
12043fn compile_proven_guard_facts(
12045 cpp: &dyn CppSource,
12046 file: &ProjectFile,
12047) -> HashSet<PreprocessorGuard> {
12048 if let Some(facts) = context_guard_facts(cpp.compile_contexts_for(file), file) {
12049 return facts;
12050 }
12051 let mut translation_units = cpp.reaching_translation_units(file).into_iter();
12052 let Some(mut facts) = translation_units.next().and_then(|translation_unit| {
12053 context_guard_facts(
12054 cpp.compile_contexts_for(&translation_unit),
12055 &translation_unit,
12056 )
12057 }) else {
12058 return HashSet::default();
12059 };
12060 for translation_unit in translation_units {
12061 let Some(reached) = context_guard_facts(
12062 cpp.compile_contexts_for(&translation_unit),
12063 &translation_unit,
12064 ) else {
12065 facts.clear();
12066 break;
12067 };
12068 facts.retain(|fact| reached.contains(fact));
12069 if facts.is_empty() {
12070 break;
12071 }
12072 }
12073 facts
12074}
12075
12076fn context_guard_facts(
12080 contexts: &[CppCompileContext],
12081 file: &ProjectFile,
12082) -> Option<HashSet<PreprocessorGuard>> {
12083 let (first, rest) = contexts.split_first()?;
12084 let mut facts = first
12085 .defined_macros
12086 .iter()
12087 .filter(|name| {
12088 rest.iter()
12089 .all(|context| context.defined_macros.contains(*name))
12090 })
12091 .cloned()
12092 .map(PreprocessorGuard::Defined)
12093 .collect::<HashSet<_>>();
12094 let language = first.tu_language(file.rel_path());
12095 if contexts
12099 .iter()
12100 .all(|context| !context.cplusplus_macro_configured)
12101 && rest
12102 .iter()
12103 .all(|context| context.tu_language(file.rel_path()) == language)
12104 {
12105 facts.insert(match language {
12106 CompiledLanguage::C => PreprocessorGuard::Undefined("__cplusplus".to_string()),
12107 CompiledLanguage::Cpp => PreprocessorGuard::Defined("__cplusplus".to_string()),
12108 });
12109 }
12110 Some(facts)
12111}
12112
12113pub fn guard_requirements_hold_at_reference(
12114 required: &HashSet<PreprocessorGuard>,
12115 reference: Option<&HashSet<PreprocessorGuard>>,
12116) -> bool {
12117 reference.is_some_and(|active| {
12118 required
12119 .iter()
12120 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
12121 })
12122}
12123
12124fn preprocessor_guard_holds_at_reference(
12125 required: &PreprocessorGuard,
12126 active: &HashSet<PreprocessorGuard>,
12127) -> bool {
12128 if active.contains(required) {
12129 return true;
12130 }
12131 let active_expression =
12132 BooleanGuardExpression::all(active.iter().map(PreprocessorGuard::as_boolean_expression));
12133 active_expression.implies(&required.as_boolean_expression())
12134}
12135
12136fn guards_compatible_at_reference(
12140 declaration: &HashSet<PreprocessorGuard>,
12141 reference: Option<&HashSet<PreprocessorGuard>>,
12142) -> bool {
12143 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
12144}
12145
12146pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
12147 let mut ancestor = Some(node);
12148 while let Some(current) = ancestor {
12149 if is_cpp_preprocessor_conditional(current)
12150 && preprocessor_conditional_contains_descendant(current, node)
12151 {
12152 return Some(preprocessor_conditional_family_root(current));
12153 }
12154 ancestor = current.parent();
12155 }
12156 None
12157}
12158
12159pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
12160 node: Node<'tree>,
12161 cache: &mut PreprocessorContextCache,
12162) -> Option<Node<'tree>> {
12163 let mut ancestor = node.parent();
12164 while let Some(current) = ancestor {
12165 if is_cpp_preprocessor_conditional(current)
12166 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
12167 {
12168 let family = preprocessor_conditional_family_root(current);
12169 if preprocessor_conditional_family_has_terminal_else(family, cache) {
12170 return Some(family);
12171 }
12172 }
12173 ancestor = current.parent();
12174 }
12175 None
12176}
12177
12178fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
12179 while let Some(parent) = conditional.parent() {
12180 let is_alternative = parent
12181 .child_by_field_name("alternative")
12182 .is_some_and(|alternative| {
12183 alternative.start_byte() == conditional.start_byte()
12184 && alternative.end_byte() == conditional.end_byte()
12185 });
12186 if !is_alternative {
12187 break;
12188 }
12189 conditional = parent;
12190 }
12191 conditional
12192}
12193
12194fn preprocessor_conditional_family_has_terminal_else(
12195 mut conditional: Node<'_>,
12196 cache: &mut PreprocessorContextCache,
12197) -> bool {
12198 loop {
12199 let Some(alternative) = conditional.child_by_field_name("alternative") else {
12200 return cache
12201 .displaced_branch(conditional)
12202 .is_some_and(|directive| directive.is_else);
12203 };
12204 match alternative.kind() {
12205 "preproc_else" => return true,
12206 "preproc_elif" => conditional = alternative,
12207 _ => return false,
12208 }
12209 }
12210}
12211
12212fn include_directive_guard_requirements<'guards>(
12225 node: Node<'_>,
12226 source: &str,
12227 effective_guards: &dyn Fn() -> Option<&'guards HashSet<PreprocessorGuard>>,
12228) -> Option<PreprocessorGuardEnvironment> {
12229 let mut required = preprocessor_guard_environment_by_family(node, source)?;
12230 if !required.all.is_empty() {
12231 let effective = effective_guards();
12232 if !guards_compatible_at_reference(&required.all, effective) {
12233 return None;
12234 }
12235 for guards in [&mut required.all, &mut required.partial] {
12236 guards.retain(|guard| {
12237 !effective
12238 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active))
12239 });
12240 }
12241 }
12242 Some(required)
12243}
12244
12245pub fn preprocessor_guard_environment(
12246 node: Node<'_>,
12247 source: &str,
12248) -> Option<HashSet<PreprocessorGuard>> {
12249 let mut cache = PreprocessorContextCache::default();
12250 preprocessor_guard_environment_with_cache(node, source, &mut cache)
12251}
12252
12253pub(crate) fn preprocessor_guard_environment_with_cache(
12254 node: Node<'_>,
12255 source: &str,
12256 cache: &mut PreprocessorContextCache,
12257) -> Option<HashSet<PreprocessorGuard>> {
12258 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
12259 .map(|environment| environment.all)
12260}
12261
12262#[derive(Clone)]
12273struct PreprocessorGuardEnvironment {
12274 all: HashSet<PreprocessorGuard>,
12275 partial: HashSet<PreprocessorGuard>,
12276}
12277
12278impl PreprocessorGuardEnvironment {
12279 fn is_subset(&self, other: &Self) -> bool {
12280 self.all.is_subset(&other.all) && self.partial.is_subset(&other.partial)
12281 }
12282
12283 fn merged(&self, other: &Self) -> Option<Self> {
12287 Some(Self {
12288 all: merge_preprocessor_guards(&self.all, &other.all)?,
12289 partial: self.partial.union(&other.partial).cloned().collect(),
12290 })
12291 }
12292}
12293
12294fn preprocessor_guard_environment_by_family(
12295 node: Node<'_>,
12296 source: &str,
12297) -> Option<PreprocessorGuardEnvironment> {
12298 let mut cache = PreprocessorContextCache::default();
12299 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
12300}
12301
12302fn preprocessor_guard_environment_by_family_with_cache(
12303 node: Node<'_>,
12304 source: &str,
12305 cache: &mut PreprocessorContextCache,
12306) -> Option<PreprocessorGuardEnvironment> {
12307 let mut all = HashSet::default();
12308 let mut partial = HashSet::default();
12309 let mut ancestor = node.parent();
12310 while let Some(conditional) = ancestor {
12311 if matches!(
12312 conditional.kind(),
12313 "preproc_if" | "preproc_ifdef" | "preproc_elif"
12314 ) && !is_file_covering_include_guard(conditional, source)
12315 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12316 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12317 {
12318 let guard = preprocessor_guard_for_descendant(conditional, node, source, cache)?;
12319 match guard {
12320 PreprocessorGuard::Constant(true) => {
12321 ancestor = conditional.parent();
12322 continue;
12323 }
12324 PreprocessorGuard::Constant(false) => return None,
12325 _ => {}
12326 }
12327 if all.contains(&guard.negated()) {
12328 return None;
12329 }
12330 if !preprocessor_conditional_family_has_terminal_else(
12331 preprocessor_conditional_family_root(conditional),
12332 cache,
12333 ) {
12334 partial.insert(guard.clone());
12335 }
12336 all.insert(guard);
12337 }
12338 ancestor = conditional.parent();
12339 }
12340 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
12341 match guard {
12342 PreprocessorGuard::Constant(true) => {}
12343 PreprocessorGuard::Constant(false) => return None,
12344 _ => {
12345 if all.contains(&guard.negated()) {
12346 return None;
12347 }
12348 partial.insert(guard.clone());
12352 all.insert(guard);
12353 }
12354 }
12355 }
12356 Some(PreprocessorGuardEnvironment { all, partial })
12357}
12358
12359pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
12360 node: Node<'_>,
12361 source: &str,
12362 cache: &mut PreprocessorContextCache,
12363) -> Option<HashSet<PreprocessorGuard>> {
12364 let mut guards = HashSet::default();
12365 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
12366 match guard {
12367 PreprocessorGuard::Constant(true) => {}
12368 PreprocessorGuard::Constant(false) => return None,
12369 _ => {
12370 if guards.contains(&guard.negated()) {
12371 return None;
12372 }
12373 guards.insert(guard);
12374 }
12375 }
12376 }
12377 Some(guards)
12378}
12379
12380fn callable_preprocessor_ancestor_guards(
12381 node: Node<'_>,
12382 source: &str,
12383) -> Option<Vec<PreprocessorGuard>> {
12384 let mut cache = PreprocessorContextCache::default();
12385 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
12386}
12387
12388fn callable_preprocessor_ancestor_guards_with_cache(
12389 node: Node<'_>,
12390 source: &str,
12391 cache: &mut PreprocessorContextCache,
12392) -> Option<Vec<PreprocessorGuard>> {
12393 let mut guards = Vec::new();
12394 let mut ancestor = node.parent();
12395 while let Some(conditional) = ancestor {
12396 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
12397 && !is_file_covering_include_guard(conditional, source)
12398 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
12399 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
12400 {
12401 guards.push(preprocessor_guard_for_descendant(
12402 conditional,
12403 node,
12404 source,
12405 cache,
12406 )?);
12407 }
12408 ancestor = conditional.parent();
12409 }
12410 Some(guards)
12411}
12412
12413fn fragmented_statement_preprocessor_guard(
12414 descendant: Node<'_>,
12415 source: &str,
12416) -> Option<PreprocessorGuard> {
12417 let mut ancestor = descendant.parent();
12423 while let Some(statement) = ancestor {
12424 if statement.kind() == "if_statement"
12425 && let (Some(consequence), Some(alternative)) = (
12426 statement.child_by_field_name("consequence"),
12427 statement.child_by_field_name("alternative"),
12428 )
12429 && alternative.start_byte() <= descendant.start_byte()
12430 && descendant.end_byte() <= alternative.end_byte()
12431 {
12432 let mut cursor = consequence.walk();
12433 let openers = consequence
12434 .named_children(&mut cursor)
12435 .filter(|child| {
12436 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
12437 && child
12438 .child(child.child_count().saturating_sub(1))
12439 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
12440 })
12441 .collect::<Vec<_>>();
12442 if openers.len() != 1 {
12443 ancestor = statement.parent();
12444 continue;
12445 }
12446
12447 let mut terminators = Vec::new();
12448 let mut stack = vec![alternative];
12449 while let Some(node) = stack.pop() {
12450 if node.kind() == "preproc_call"
12451 && node.start_byte() >= descendant.end_byte()
12452 && node
12453 .child_by_field_name("directive")
12454 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
12455 {
12456 terminators.push(node);
12457 continue;
12458 }
12459 push_named_children_reversed(node, &mut stack);
12460 }
12461 if terminators.len() == 1 {
12462 return simple_preprocessor_guard(openers[0], source);
12463 }
12464 }
12465 ancestor = statement.parent();
12466 }
12467 None
12468}
12469
12470pub(crate) fn preprocessor_guard_for_descendant(
12471 conditional: Node<'_>,
12472 descendant: Node<'_>,
12473 source: &str,
12474 cache: &mut PreprocessorContextCache,
12475) -> Option<PreprocessorGuard> {
12476 let guard = simple_preprocessor_guard(conditional, source)?;
12477 match preprocessor_branch_of_descendant(conditional, descendant, cache) {
12478 PreprocessorBranch::Consequence => Some(guard),
12479 PreprocessorBranch::Structured(alternative)
12483 if matches!(alternative.kind(), "preproc_else" | "preproc_elif") =>
12484 {
12485 Some(guard.negated())
12486 }
12487 PreprocessorBranch::DisplacedElse => Some(guard.negated()),
12488 PreprocessorBranch::Structured(_) | PreprocessorBranch::Unreadable => None,
12489 }
12490}
12491
12492enum PreprocessorBranch<'tree> {
12494 Consequence,
12495 Structured(Node<'tree>),
12497 DisplacedElse,
12500 Unreadable,
12502}
12503
12504fn preprocessor_branch_of_descendant<'tree>(
12505 conditional: Node<'tree>,
12506 descendant: Node<'_>,
12507 cache: &mut PreprocessorContextCache,
12508) -> PreprocessorBranch<'tree> {
12509 if let Some(alternative) =
12510 conditional
12511 .child_by_field_name("alternative")
12512 .filter(|alternative| {
12513 alternative.start_byte() <= descendant.start_byte()
12514 && descendant.end_byte() <= alternative.end_byte()
12515 })
12516 {
12517 return PreprocessorBranch::Structured(alternative);
12518 }
12519 match cache.displaced_branch(conditional) {
12520 Some(directive) if directive.end_byte <= descendant.start_byte() => {
12521 if directive.is_else {
12522 PreprocessorBranch::DisplacedElse
12523 } else {
12524 PreprocessorBranch::Unreadable
12525 }
12526 }
12527 _ => PreprocessorBranch::Consequence,
12528 }
12529}
12530
12531pub(crate) fn preprocessor_conditional_contains_descendant(
12532 conditional: Node<'_>,
12533 descendant: Node<'_>,
12534) -> bool {
12535 cpp_displaced_preprocessor_boundary(conditional)
12536 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
12537}
12538
12539fn preprocessor_conditional_contains_descendant_with_cache(
12540 conditional: Node<'_>,
12541 descendant: Node<'_>,
12542 cache: &mut PreprocessorContextCache,
12543) -> bool {
12544 cache
12545 .displaced_boundary_end(conditional)
12546 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
12547}
12548
12549pub fn merge_preprocessor_guards(
12550 left: &HashSet<PreprocessorGuard>,
12551 right: &HashSet<PreprocessorGuard>,
12552) -> Option<HashSet<PreprocessorGuard>> {
12553 let mut merged = left.clone();
12554 for guard in right {
12555 let boolean_negation = guard.as_boolean_expression().negated();
12556 if merged.contains(&guard.negated())
12557 || merged
12558 .iter()
12559 .map(PreprocessorGuard::as_boolean_expression)
12560 .any(|existing| existing == boolean_negation)
12561 {
12562 return None;
12563 }
12564 merged.insert(guard.clone());
12565 }
12566 Some(merged)
12567}
12568
12569fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
12570 if conditional.kind() == "preproc_ifdef" {
12571 let name = conditional.child_by_field_name("name")?;
12572 let name = node_text(name, source).to_string();
12573 return match conditional.child(0)?.kind() {
12574 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
12575 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
12576 _ => None,
12577 };
12578 }
12579 let condition = conditional.child_by_field_name("condition")?;
12580 simple_preprocessor_expression_guard(condition, source).or_else(|| {
12581 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
12582 node_text(condition, source),
12583 )))
12584 })
12585}
12586
12587fn simple_preprocessor_expression_guard(
12588 expression: Node<'_>,
12589 source: &str,
12590) -> Option<PreprocessorGuard> {
12591 match expression.kind() {
12592 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
12593 node_text(expression, source).to_string(),
12594 ))),
12595 "number_literal" => match node_text(expression, source).trim() {
12596 "0" => Some(PreprocessorGuard::Constant(false)),
12597 "1" => Some(PreprocessorGuard::Constant(true)),
12598 _ => None,
12599 },
12600 "preproc_defined" => {
12601 let identifier = (0..expression.named_child_count())
12602 .filter_map(|index| expression.named_child(index))
12603 .find(|child| child.kind() == "identifier")?;
12604 Some(PreprocessorGuard::Defined(
12605 node_text(identifier, source).to_string(),
12606 ))
12607 }
12608 "unary_expression"
12609 if expression
12610 .child_by_field_name("operator")
12611 .is_some_and(|operator| operator.kind() == "!") =>
12612 {
12613 simple_preprocessor_expression_guard(
12614 expression.child_by_field_name("argument")?,
12615 source,
12616 )
12617 .map(|guard| guard.negated())
12618 }
12619 "parenthesized_expression" => (0..expression.named_child_count())
12620 .filter_map(|index| expression.named_child(index))
12621 .next()
12622 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
12623 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
12624 expression, source,
12625 ))),
12626 _ => None,
12627 }
12628}
12629
12630fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
12631 match expression.kind() {
12632 "number_literal" => match node_text(expression, source).trim() {
12633 "0" => BooleanGuardExpression::Constant(false),
12634 "1" => BooleanGuardExpression::Constant(true),
12635 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12636 expression, source,
12637 ))),
12638 },
12639 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
12640 "preproc_defined" => {
12641 let identifier = (0..expression.named_child_count())
12642 .filter_map(|index| expression.named_child(index))
12643 .find(|child| child.kind() == "identifier");
12644 identifier.map_or_else(
12645 || {
12646 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12647 expression, source,
12648 )))
12649 },
12650 |identifier| {
12651 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
12652 },
12653 )
12654 }
12655 "unary_expression"
12656 if expression
12657 .child_by_field_name("operator")
12658 .is_some_and(|operator| operator.kind() == "!") =>
12659 {
12660 expression.child_by_field_name("argument").map_or_else(
12661 || {
12662 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12663 expression, source,
12664 )))
12665 },
12666 |argument| boolean_preprocessor_expression(argument, source).negated(),
12667 )
12668 }
12669 "parenthesized_expression" => (0..expression.named_child_count())
12670 .filter_map(|index| expression.named_child(index))
12671 .next()
12672 .map_or_else(
12673 || {
12674 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12675 expression, source,
12676 )))
12677 },
12678 |child| boolean_preprocessor_expression(child, source),
12679 ),
12680 "binary_expression" => {
12681 let operands = || {
12682 Some((
12683 boolean_preprocessor_expression(
12684 expression.child_by_field_name("left")?,
12685 source,
12686 ),
12687 boolean_preprocessor_expression(
12688 expression.child_by_field_name("right")?,
12689 source,
12690 ),
12691 ))
12692 };
12693 match expression
12694 .child_by_field_name("operator")
12695 .map(|operator| operator.kind())
12696 {
12697 Some("&&") => operands().map_or_else(
12698 || {
12699 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12700 expression, source,
12701 )))
12702 },
12703 |(left, right)| BooleanGuardExpression::all([left, right]),
12704 ),
12705 Some("||") => operands().map_or_else(
12706 || {
12707 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12708 expression, source,
12709 )))
12710 },
12711 |(left, right)| BooleanGuardExpression::any([left, right]),
12712 ),
12713 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12714 expression, source,
12715 ))),
12716 }
12717 }
12718 _ => {
12719 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12720 }
12721 }
12722}
12723
12724fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12725 if targets.len() == 1 {
12726 targets.pop()
12727 } else {
12728 None
12729 }
12730}
12731
12732fn callable_declaration_nodes<'tree>(
12733 analyzer: &CppGraphSource<'_>,
12734 prepared: &'tree PreparedSyntaxTree,
12735 candidate: &CodeUnit,
12736) -> Vec<Node<'tree>> {
12737 let root = prepared.tree().root_node();
12738 analyzer
12739 .ranges(candidate)
12740 .into_iter()
12741 .filter_map(|range| {
12742 let mut declaration =
12743 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12744 while !matches!(
12757 declaration.kind(),
12758 "declaration" | "field_declaration" | "function_definition"
12759 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12760 declaration,
12761 prepared.source(),
12762 ) {
12763 let Some(parent) = declaration.parent() else {
12764 break;
12765 };
12766 declaration = parent;
12767 }
12768 Some(declaration)
12769 })
12770 .collect()
12771}
12772
12773fn real_function_definition_ancestor<'tree>(
12774 node: Node<'tree>,
12775 source: &str,
12776) -> Option<Node<'tree>> {
12777 let mut ancestor = node.parent();
12778 while let Some(node) = ancestor {
12779 if node.kind() == "function_definition"
12780 && !is_recovered_declaration_scope_container(node, source)
12781 {
12782 return Some(node);
12783 }
12784 ancestor = node.parent();
12785 }
12786 None
12787}
12788
12789fn callable_declaration_activation_in_file(
12790 analyzer: &CppGraphSource<'_>,
12791 candidate: &CodeUnit,
12792 reference: &CallableReferenceContext<'_>,
12793) -> Option<usize> {
12794 analyzer
12795 .declaration_source_occurrences(candidate)?
12796 .into_iter()
12797 .filter_map(|occurrence| {
12798 let activation = occurrence.fact.callable_activation?;
12799 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12800 callable_guard_requirements_are_visible(
12801 &guards,
12802 occurrence.fact.callable_guard_completion_byte,
12803 reference,
12804 )
12805 .then_some(activation)
12806 })
12807 .min()
12808}
12809
12810fn callable_guard_requirements_are_visible(
12811 required: &HashSet<PreprocessorGuard>,
12812 completion_byte: Option<usize>,
12813 reference: &CallableReferenceContext<'_>,
12814) -> bool {
12815 if required.is_empty() || guard_requirements_hold_at_reference(required, reference.guards()) {
12816 return true;
12817 }
12818 reference.position.as_ref().is_some_and(|position| {
12819 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12820 && guards_compatible_at_reference(required, reference.guards())
12821 })
12822}
12823
12824type ReferenceGuardAugmenter<'a> =
12825 dyn Fn(Option<&HashSet<PreprocessorGuard>>) -> Option<HashSet<PreprocessorGuard>> + 'a;
12826
12827struct CallableReferenceContext<'a> {
12834 effective_guards: &'a ReferenceGuardAugmenter<'a>,
12835 file: &'a ProjectFile,
12836 position: Option<CallableReferencePosition<'a>>,
12837 file_guards: OnceCell<Option<HashSet<PreprocessorGuard>>>,
12838}
12839
12840struct CallableReferencePosition<'a> {
12844 prepared: &'a PreparedSyntaxTree,
12845 byte: usize,
12846 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12847}
12848
12849impl CallableReferenceContext<'_> {
12850 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12851 let Some(position) = self.position.as_ref() else {
12852 return self
12853 .file_guards
12854 .get_or_init(|| (self.effective_guards)(Some(&HashSet::default())))
12855 .as_ref();
12856 };
12857 position
12858 .guards
12859 .get_or_init(|| {
12860 let raw = position
12861 .prepared
12862 .tree()
12863 .root_node()
12864 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12865 .and_then(|node| {
12866 preprocessor_guard_environment(node, position.prepared.source())
12867 });
12868 (self.effective_guards)(raw.as_ref())
12869 })
12870 .as_ref()
12871 }
12872}
12873
12874fn callable_declaration_guard_requirements(
12886 node: Node<'_>,
12887 source: &str,
12888 reference: &CallableReferenceContext<'_>,
12889) -> Option<HashSet<PreprocessorGuard>> {
12890 let mut required = HashSet::default();
12891 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12892 match guard {
12893 PreprocessorGuard::Constant(true) => {}
12894 PreprocessorGuard::Constant(false) => return None,
12895 guard => {
12896 required.insert(guard);
12897 }
12898 }
12899 }
12900 guards_compatible_at_reference(&required, reference.guards()).then_some(required)
12901}
12902
12903fn callable_preprocessor_context_is_visible_for_reference(
12914 node: Node<'_>,
12915 source: &str,
12916 reference: &CallableReferenceContext<'_>,
12917) -> bool {
12918 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12919 return false;
12920 };
12921 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12922 return true;
12923 }
12924 reference.position.as_ref().is_some_and(|position| {
12925 guards_compatible_at_reference(&required, reference.guards())
12926 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12927 })
12928}
12929
12930fn flattened_macro_namespace_declaration_matches(
12931 analyzer: &CppGraphSource<'_>,
12932 _cpp: &dyn CppSource,
12933 reference_file: &ProjectFile,
12934 visible_declaration: &CodeUnit,
12935 qualified_candidate: &CodeUnit,
12936 reference_byte: usize,
12937) -> bool {
12938 if visible_declaration.kind() != qualified_candidate.kind()
12944 || visible_declaration.identifier() != qualified_candidate.identifier()
12945 || visible_declaration.signature() != qualified_candidate.signature()
12946 || !visible_declaration.package_name().is_empty()
12947 || qualified_candidate.package_name().is_empty()
12948 {
12949 return false;
12950 }
12951
12952 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12953 return false;
12954 };
12955 occurrences.into_iter().any(|occurrence| {
12956 occurrence
12957 .displaced_namespace_closing_brace
12958 .is_some_and(|closing_brace| {
12959 visible_declaration.source() != reference_file || closing_brace < reference_byte
12960 })
12961 })
12962}
12963
12964pub(crate) fn flattened_macro_namespace_components(
12965 declaration: Node<'_>,
12966 source: &str,
12967) -> Option<Vec<String>> {
12968 flattened_macro_function_namespace_components(declaration, source)
12969 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12970}
12971
12972fn flattened_macro_function_namespace_components(
12973 declaration: Node<'_>,
12974 source: &str,
12975) -> Option<Vec<String>> {
12976 let body = declaration
12977 .parent()
12978 .filter(|parent| parent.kind() == "compound_statement")?;
12979 let function = body.parent()?;
12980 if function.child_by_field_name("body") != Some(body) {
12981 return None;
12982 }
12983 let namespace_name = recovered_macro_namespace_name(function, source)?;
12984 let mut components = enclosing_namespace_components(declaration, source)?;
12985 components.push(namespace_name);
12986 Some(components)
12987}
12988
12989fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
13003 if function.kind() != "function_definition" {
13004 return None;
13005 }
13006 let body = function
13007 .child_by_field_name("body")
13008 .filter(|body| body.kind() == "compound_statement")?;
13009 let mut cursor = function.walk();
13010 let prefix = function
13011 .named_children(&mut cursor)
13012 .take_while(|child| child.start_byte() < body.start_byte())
13013 .filter(|child| child.kind() != "comment")
13014 .collect::<Vec<_>>();
13015 let begin_index = prefix.iter().rposition(|child| {
13016 flattened_macro_sentinel_name(*child, source)
13017 .is_some_and(|name| is_namespace_begin_sentinel(&name))
13018 })?;
13019 let mut identifiers = Vec::new();
13020 let mut stack = prefix[begin_index + 1..]
13021 .iter()
13022 .rev()
13023 .copied()
13024 .collect::<Vec<_>>();
13025 while let Some(current) = stack.pop() {
13026 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
13027 identifiers.push(identifier);
13028 continue;
13029 }
13030 let mut cursor = current.walk();
13031 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
13032 stack.extend(children.into_iter().rev());
13033 }
13034 let [keyword, namespace_name] = identifiers.as_slice() else {
13035 return None;
13036 };
13037 if keyword != "namespace" || namespace_name.is_empty() {
13040 return None;
13041 }
13042 let mut next = function.next_named_sibling();
13043 let next = loop {
13044 let candidate = next?;
13045 next = candidate.next_named_sibling();
13046 if candidate.kind() != "comment" {
13047 break candidate;
13048 }
13049 };
13050 flattened_macro_sentinel_name(next, source)
13051 .is_some_and(|name| is_namespace_end_sentinel(&name))
13052 .then(|| namespace_name.clone())
13053}
13054
13055pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
13060 crate::declarations::is_recovered_exported_class_container(node, source)
13061 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
13062 node, source,
13063 )
13064 || recovered_macro_namespace_name(node, source).is_some()
13065}
13066
13067fn flattened_macro_error_namespace_components(
13068 declaration: Node<'_>,
13069 source: &str,
13070) -> Option<Vec<String>> {
13071 let parent = declaration
13072 .parent()
13073 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
13074 let mut cursor = parent.walk();
13075 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
13076 let declaration_index = siblings
13077 .iter()
13078 .position(|candidate| same_node(*candidate, declaration))?;
13079 let begin_index = (0..declaration_index).rev().find(|index| {
13080 flattened_macro_sentinel_name(siblings[*index], source)
13081 .is_some_and(|name| is_namespace_begin_sentinel(&name))
13082 })?;
13083
13084 let significant = siblings[begin_index + 1..declaration_index]
13085 .iter()
13086 .copied()
13087 .filter(|node| node.kind() != "comment")
13088 .collect::<Vec<_>>();
13089 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
13090 return None;
13091 };
13092 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
13093 return None;
13094 }
13095 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
13096 if significant[2..].iter().any(|node| {
13097 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
13098 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
13099 })
13100 }) {
13101 return None;
13102 }
13103
13104 let mut saw_namespace_close = false;
13105 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
13106 if sibling.kind() == "comment" {
13107 continue;
13108 }
13109 if !saw_namespace_close {
13110 if direct_unmatched_closing_brace(sibling) {
13111 saw_namespace_close = true;
13112 continue;
13113 }
13114 if flattened_macro_sentinel_name(sibling, source).is_some() {
13115 return None;
13116 }
13117 continue;
13118 }
13119 if !flattened_macro_sentinel_name(sibling, source)
13120 .is_some_and(|name| is_namespace_end_sentinel(&name))
13121 {
13122 return None;
13123 }
13124 let mut components = enclosing_namespace_components(declaration, source)?;
13125 components.push(namespace_name);
13126 return Some(components);
13127 }
13128 None
13129}
13130
13131fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
13132 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
13140 node.named_child(0)?
13141 } else {
13142 node
13143 };
13144 let candidates = [
13145 direct_cpp_identifier_name(node, source),
13146 node.child_by_field_name("type")
13147 .and_then(|type_node| direct_cpp_identifier_name(type_node, source)),
13148 macro_modifier_identifier(node)
13149 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source))),
13150 {
13151 let mut cursor = node.walk();
13152 node.named_children(&mut cursor)
13153 .find(|child| child.kind() != "comment")
13154 .and_then(macro_modifier_identifier)
13155 .map(|identifier| normalize_cpp_whitespace(node_text(identifier, source)))
13156 },
13157 ];
13158 candidates.into_iter().flatten().find(|candidate| {
13159 cpp_export_macro_token(candidate)
13160 && (is_namespace_begin_sentinel(candidate) || is_namespace_end_sentinel(candidate))
13161 })
13162}
13163
13164#[derive(Clone, Copy, PartialEq, Eq)]
13167enum NamespaceSentinel {
13168 Begin,
13169 End,
13170}
13171
13172fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
13173 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
13174 Some(NamespaceSentinel::Begin)
13175 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
13176 Some(NamespaceSentinel::End)
13177 } else {
13178 None
13179 }
13180}
13181
13182fn is_namespace_begin_sentinel(name: &str) -> bool {
13183 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
13184}
13185
13186fn is_namespace_end_sentinel(name: &str) -> bool {
13187 namespace_sentinel(name) == Some(NamespaceSentinel::End)
13188}
13189
13190fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
13191 if node.kind() != "ERROR" || node.named_child_count() != 1 {
13192 return None;
13193 }
13194 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
13195 (!cpp_export_macro_token(&name)).then_some(name)
13196}
13197
13198fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
13199 if !matches!(
13200 node.kind(),
13201 "identifier" | "namespace_identifier" | "type_identifier"
13202 ) {
13203 return None;
13204 }
13205 let name = normalize_cpp_whitespace(node_text(node, source));
13206 (!name.is_empty()).then_some(name)
13207}
13208
13209fn guard_requirement_sets_match(
13210 left: &[(usize, HashSet<PreprocessorGuard>)],
13211 right: &[(usize, HashSet<PreprocessorGuard>)],
13212) -> bool {
13213 left.len() == right.len()
13214 && left.iter().all(|(_, left_guards)| {
13215 right
13216 .iter()
13217 .any(|(_, right_guards)| left_guards == right_guards)
13218 })
13219 && right.iter().all(|(_, right_guards)| {
13220 left.iter()
13221 .any(|(_, left_guards)| right_guards == left_guards)
13222 })
13223}
13224
13225pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
13226 node.kind() == "ERROR"
13227 && (0..node.child_count())
13228 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
13229}
13230
13231pub fn binding_site_guards(node: Node<'_>, source: &str) -> Option<HashSet<PreprocessorGuard>> {
13235 if callable_preprocessor_context_is_visible(node, source) {
13236 Some(HashSet::default())
13237 } else {
13238 preprocessor_guard_environment(node, source)
13239 }
13240}
13241
13242pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
13243 let mut ancestor = node.parent();
13244 while let Some(parent) = ancestor {
13245 if is_cpp_preprocessor_conditional(parent)
13246 && !is_file_covering_include_guard(parent, source)
13247 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
13248 {
13249 return false;
13250 }
13251 ancestor = parent.parent();
13252 }
13253 true
13254}
13255
13256fn is_split_cpp_language_linkage_wrapper(
13257 conditional: Node<'_>,
13258 descendant: Node<'_>,
13259 source: &str,
13260) -> bool {
13261 if conditional.child_by_field_name("alternative").is_some()
13262 || !matches!(
13263 simple_preprocessor_guard(conditional, source),
13264 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13265 )
13266 {
13267 return false;
13268 }
13269 let mut current = descendant.parent();
13270 let linkage = loop {
13271 let Some(node) = current else {
13272 return false;
13273 };
13274 if node == conditional {
13275 return false;
13276 }
13277 if node.kind() == "linkage_specification" {
13278 break node;
13279 }
13280 current = node.parent();
13281 };
13282 if linkage
13283 .child_by_field_name("value")
13284 .is_none_or(|value| node_text(value, source) != "\"C\"")
13285 {
13286 return false;
13287 }
13288 let Some(body) = linkage.child_by_field_name("body") else {
13289 return false;
13290 };
13291 let mut cursor = body.walk();
13296 let closes_opening_branch = body
13297 .named_children(&mut cursor)
13298 .take_while(|child| child.end_byte() <= descendant.start_byte())
13299 .any(|child| {
13300 child.kind() == "preproc_call"
13301 && child
13302 .child_by_field_name("directive")
13303 .is_some_and(|directive| node_text(directive, source) == "#endif")
13304 });
13305 let mut cursor = body.walk();
13306 let reopens_for_closing_brace = body
13307 .named_children(&mut cursor)
13308 .skip_while(|child| child.start_byte() < descendant.end_byte())
13309 .any(|child| {
13310 matches!(
13311 simple_preprocessor_guard(child, source),
13312 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
13313 ) && {
13314 let mut tokens = child.walk();
13315 child
13316 .children(&mut tokens)
13317 .any(|token| token.kind() == "#endif" && token.is_missing())
13318 }
13319 });
13320 closes_opening_branch && reopens_for_closing_brace
13321}
13322
13323pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
13327 node.child_by_field_name("arguments")
13328 .or_else(|| node.child_by_field_name("parameters"))
13329 .or_else(|| node.child_by_field_name("value"))
13330 .or_else(|| first_named_child_of_kind(node, "argument_list"))
13331 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
13332}
13333
13334pub fn call_arity(node: Node<'_>) -> usize {
13335 call_arguments_node(node)
13336 .map(|args| argument_children(args).count())
13337 .unwrap_or(0)
13338}
13339
13340pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
13341 let recovered_block_arguments = recovered_block_literal_arguments(node);
13342 (0..node.child_count())
13343 .filter_map(move |index| node.child(index))
13344 .filter(|child| child.is_named() && !child.is_extra())
13345 .filter(move |child| node.kind() != "parameter_list" || !block_literal_open_error(*child))
13350 .flat_map(move |child| {
13351 if let Some((raw, left, right)) = recovered_block_arguments
13352 && child == raw
13353 {
13354 [Some(left), Some(right)]
13355 } else {
13356 [Some(child), None]
13357 }
13358 })
13359 .flatten()
13360}
13361
13362fn block_literal_open_error(node: Node<'_>) -> bool {
13366 if node.kind() != "ERROR" || node.is_missing() {
13367 return false;
13368 }
13369 let mut cursor = node.walk();
13370 let tokens = node.children(&mut cursor).collect::<Vec<_>>();
13371 matches!(tokens.as_slice(), [caret, open] if caret.kind() == "^" && open.kind() == "(")
13372}
13373
13374pub fn recovered_c_new_expression_arguments(
13383 node: Node<'_>,
13384 uses_c_semantics: bool,
13385) -> Option<[Node<'_>; 2]> {
13386 if !uses_c_semantics || node.kind() != "new_expression" {
13387 return None;
13388 }
13389 let parent = node.parent()?;
13390 if parent.kind() != "argument_list" {
13391 return None;
13392 }
13393 let keyword = node.child(0)?;
13394 let error = node.child(1)?;
13395 let trailing = node.child(2)?;
13396 if node.child(3).is_some()
13397 || keyword.kind() != "new"
13398 || keyword.is_named()
13399 || keyword.child_count() != 0
13400 || error.kind() != "ERROR"
13401 || !error.is_extra()
13402 || error.child_count() != 1
13403 || error.child(0).is_none_or(|comma| comma.kind() != ",")
13404 || node.child_by_field_name("type") != Some(trailing)
13405 || trailing.kind() != "type_identifier"
13406 {
13407 return None;
13408 }
13409 Some([keyword, trailing])
13410}
13411
13412pub fn recovered_c_new_expression_argument_at(
13415 mut node: Node<'_>,
13416 start_byte: usize,
13417 end_byte: usize,
13418 uses_c_semantics: bool,
13419) -> Option<Node<'_>> {
13420 if !uses_c_semantics {
13426 return None;
13427 }
13428 loop {
13429 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
13430 return arguments.into_iter().find(|argument| {
13431 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
13432 });
13433 }
13434 node = node.parent()?;
13435 }
13436}
13437
13438fn recovered_c_keyword_argument_count(
13439 file: &ProjectFile,
13440 call: Node<'_>,
13441 arguments: Node<'_>,
13442 source: &str,
13443) -> usize {
13444 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
13450 return 0;
13451 }
13452 let mut ancestor = Some(call);
13453 let function = loop {
13454 let Some(current) = ancestor else {
13455 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
13456 .unwrap_or(0);
13457 };
13458 if current.kind() == "function_definition" {
13459 break current;
13460 }
13461 ancestor = current.parent();
13462 };
13463 let Some(parameters) = function
13464 .child_by_field_name("declarator")
13465 .and_then(|declarator| declarator.child_by_field_name("parameters"))
13466 else {
13467 return 0;
13468 };
13469 let displaced_parameter_keywords = (0..parameters.child_count())
13470 .filter_map(|index| parameters.child(index))
13471 .filter(|error| error.kind() == "ERROR")
13472 .filter_map(|error| {
13473 let parameter = error.prev_named_sibling()?;
13474 if parameter.kind() != "parameter_declaration"
13475 || parameter.end_byte() != error.start_byte()
13476 || extract_variable_name(parameter, source).is_some()
13477 {
13478 return None;
13479 }
13480 let mut children = (0..error.child_count())
13481 .filter_map(|index| error.child(index))
13482 .filter(|child| !child.is_extra() && !child.is_missing());
13483 let keyword = children.next()?;
13484 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
13485 .then_some(keyword)
13486 })
13487 .collect::<Vec<_>>();
13488 if displaced_parameter_keywords.is_empty() {
13489 return 0;
13490 }
13491
13492 (0..arguments.child_count())
13493 .filter_map(|index| arguments.child(index))
13494 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13495 .filter(|error| {
13496 let mut children = (0..error.child_count())
13497 .filter_map(|index| error.child(index))
13498 .filter(|child| !child.is_extra() && !child.is_missing());
13499 let Some(comma) = children.next() else {
13500 return false;
13501 };
13502 let Some(keyword) = children.next() else {
13503 return false;
13504 };
13505 children.next().is_none()
13506 && comma.kind() == ","
13507 && !keyword.is_named()
13508 && keyword.child_count() == 0
13509 && displaced_parameter_keywords
13510 .iter()
13511 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13512 })
13513 .count()
13514}
13515
13516fn recovered_c_keyword_argument_count_from_collapsed_function(
13522 call: Node<'_>,
13523 arguments: Node<'_>,
13524) -> Option<usize> {
13525 let argument_keywords = children_iter(arguments)
13526 .filter(|error| error.kind() == "ERROR" && error.is_extra())
13527 .filter_map(|error| {
13528 let mut children =
13529 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
13530 let comma = children.next()?;
13531 let keyword = children.next()?;
13532 (children.next().is_none()
13533 && comma.kind() == ","
13534 && !keyword.is_named()
13535 && keyword.child_count() == 0)
13536 .then_some(keyword)
13537 })
13538 .collect::<Vec<_>>();
13539 if argument_keywords.is_empty() {
13540 return Some(0);
13541 }
13542
13543 let mut ancestor = call.parent();
13548 let body = loop {
13549 let current = ancestor?;
13550 if matches!(current.kind(), "function_definition" | "lambda_expression") {
13551 return None;
13552 }
13553 if current.kind() == "compound_statement"
13554 && current
13555 .parent()
13556 .is_some_and(|parent| parent.kind() == "ERROR")
13557 {
13558 break current;
13559 }
13560 ancestor = current.parent();
13561 };
13562 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
13563 let function_error = parameter_error
13564 .parent()
13565 .filter(|parent| parent.kind() == "ERROR")?;
13566 let function_children = children_iter(function_error).collect::<Vec<_>>();
13567 let parameter_error_index = function_children
13568 .iter()
13569 .position(|child| same_node(*child, parameter_error))?;
13570 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
13573 .iter()
13574 .enumerate()
13575 .any(|(index, child)| {
13576 function_error.field_name_for_child(index as u32) == Some("type")
13577 && !child.is_extra()
13578 && !child.is_missing()
13579 && matches!(
13580 child.kind(),
13581 "primitive_type" | "sized_type_specifier" | "type_identifier"
13582 )
13583 });
13584 if parameter_error_index < 2
13585 || !has_return_type
13586 || function_children[parameter_error_index - 1].kind() != "("
13587 || function_children[parameter_error_index - 2].kind() != "identifier"
13588 {
13589 return Some(0);
13590 }
13591
13592 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
13593 let body_index = parameter_children
13594 .iter()
13595 .position(|child| same_node(*child, body))?;
13596 let close_parenthesis_index = parameter_children[..body_index]
13597 .iter()
13598 .position(|child| child.kind() == ")")?;
13599 let parameter_keywords = parameter_children[..close_parenthesis_index]
13600 .iter()
13601 .enumerate()
13602 .filter(|(index, child)| {
13603 !child.is_named()
13604 && !child.is_extra()
13605 && !child.is_missing()
13606 && child.child_count() == 0
13607 && argument_keywords
13608 .iter()
13609 .any(|keyword| keyword.kind_id() == child.kind_id())
13610 && collapsed_c_parameter_name_position(¶meter_children, *index)
13611 })
13612 .map(|(_, child)| *child)
13613 .collect::<Vec<_>>();
13614 Some(
13615 argument_keywords
13616 .iter()
13617 .filter(|keyword| {
13618 parameter_keywords
13619 .iter()
13620 .any(|parameter| parameter.kind_id() == keyword.kind_id())
13621 })
13622 .count(),
13623 )
13624}
13625
13626fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
13631 let segment_start = children[..keyword_index]
13632 .iter()
13633 .rposition(|child| matches!(child.kind(), "(" | ","))
13634 .map_or(0, |index| index + 1);
13635 let mut saw_type = false;
13636 let mut saw_pointer = false;
13637 for child in &children[segment_start..keyword_index] {
13638 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
13639 saw_pointer = true;
13640 continue;
13641 }
13642 if saw_pointer {
13643 return false;
13644 }
13645 if !child.is_named() || child.is_extra() || child.is_missing() {
13646 return false;
13647 }
13648 if child.kind() == "type_qualifier" {
13649 continue;
13650 }
13651 if matches!(
13652 child.kind(),
13653 "class_specifier"
13654 | "dependent_type"
13655 | "enum_specifier"
13656 | "identifier"
13657 | "primitive_type"
13658 | "qualified_identifier"
13659 | "scoped_identifier"
13660 | "struct_specifier"
13661 | "template_type"
13662 | "type_identifier"
13663 | "union_specifier"
13664 ) {
13665 saw_type = true;
13666 continue;
13667 }
13668 return false;
13669 }
13670 saw_type && saw_pointer
13671}
13672
13673fn recovered_block_literal_arguments<'tree>(
13674 arguments: Node<'tree>,
13675) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
13676 if arguments.kind() != "argument_list" {
13677 return None;
13678 }
13679 let mut raw_arguments = (0..arguments.child_count())
13680 .filter_map(|index| arguments.child(index))
13681 .filter(|child| child.is_named() && !child.is_extra());
13682 let raw = raw_arguments.next()?;
13683 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
13684 return None;
13685 }
13686
13687 let left = raw.child_by_field_name("left")?;
13688 if left.is_missing() || left.start_byte() == left.end_byte() {
13689 return None;
13690 }
13691 let right = raw.child_by_field_name("right")?;
13692 if right.kind() != "compound_literal_expression"
13693 || right.is_missing()
13694 || right
13695 .child_by_field_name("type")
13696 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
13697 || right
13698 .child_by_field_name("value")
13699 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
13700 {
13701 return None;
13702 }
13703 let has_intervening_error = (0..raw.child_count())
13704 .filter_map(|index| raw.child(index))
13705 .any(|child| {
13706 child.kind() == "ERROR"
13707 && !child.is_missing()
13708 && child.start_byte() >= left.end_byte()
13709 && child.end_byte() <= right.start_byte()
13710 });
13711 has_intervening_error.then_some((raw, left, right))
13712}
13713
13714pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
13715 match node.kind() {
13716 "new_expression" => node
13717 .child_by_field_name("type")
13718 .or_else(|| node.named_child(0)),
13719 "compound_literal_expression" => node.child_by_field_name("type"),
13720 "call_expression" => node.child_by_field_name("function"),
13721 _ => None,
13722 }
13723}
13724
13725pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13731 if node.kind() != "cast_expression" {
13732 return None;
13733 }
13734 let descriptor = node.child_by_field_name("type")?;
13735 if descriptor.kind() == "type_descriptor" {
13736 descriptor.child_by_field_name("type")
13737 } else {
13738 Some(descriptor)
13739 }
13740}
13741
13742pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13757 debug_assert_eq!(field.kind(), "field_expression");
13758 let operator = field.child_by_field_name("operator")?;
13759 let mut cursor = field.walk();
13760 let receiver = field
13761 .named_children(&mut cursor)
13762 .filter(|child| child.end_byte() <= operator.start_byte())
13763 .last()?;
13764 if receiver.kind() != "ERROR" {
13765 return Some(receiver);
13766 }
13767 (receiver.named_child_count() == 1)
13768 .then(|| receiver.named_child(0))
13769 .flatten()
13770}
13771
13772pub fn field_initializer_constructs_target(
13773 node: Node<'_>,
13774 ctx: &ScanCtx<'_>,
13775 owner: &CodeUnit,
13776) -> bool {
13777 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13786 return qualified_base_initializer_constructs_target(node, ctx, owner);
13787 }
13788 let Some(name) = node
13789 .child_by_field_name("name")
13790 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13791 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13792 else {
13793 return false;
13794 };
13795 let field_name = node_text(name, ctx.source);
13796 ctx.visibility
13797 .visible_identifier_candidates(ctx.file, field_name)
13798 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13799 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13800}
13801
13802fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13812 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13813}
13814
13815fn qualified_base_initializer_constructs_target(
13816 node: Node<'_>,
13817 ctx: &ScanCtx<'_>,
13818 owner: &CodeUnit,
13819) -> bool {
13820 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13821 return false;
13822 };
13823 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13824 return false;
13825 };
13826 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13827 return false;
13828 };
13829 let resolves_target = |components: &[String]| {
13830 matches!(
13831 ctx.visibility.resolve_type_components_lexically_for_target(
13832 &ctx.analyzer,
13833 ctx.file,
13834 components,
13835 is_globally_qualified_cpp_name(qualified),
13836 &lexical_scope,
13837 owner,
13838 ),
13839 LexicalTypeResolution::Resolved { unit, .. }
13840 if same_visible_symbol(&unit, owner)
13841 )
13842 };
13843 if resolves_target(&components) {
13844 return true;
13845 }
13846
13847 components
13853 .last()
13854 .is_some_and(|terminal| terminal == owner.identifier())
13855 && resolves_target(&components[..components.len() - 1])
13856}
13857
13858fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13859 ctx.visibility
13860 .field_declared_type_fact(&ctx.analyzer, unit)
13861 .is_some_and(|fact| {
13862 ctx.visibility
13863 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13864 || ctx.visibility.resolves_to_type(
13865 &ctx.analyzer,
13866 ctx.file,
13867 &normalize_field_type_text(&fact.type_text),
13868 owner,
13869 )
13870 })
13871}
13872
13873pub fn field_declared_binding(
13874 analyzer: &CppGraphSource<'_>,
13875 visibility: &VisibilityIndex<'_>,
13876 visible_from: &ProjectFile,
13877 field: &CodeUnit,
13878) -> Option<CppScanBinding> {
13879 let (normalized, resolved, indirection) =
13880 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13881 let resolved = resolved
13882 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13883 Some(CppScanBinding::from_type_name(
13884 normalized,
13885 resolved,
13886 indirection,
13887 ))
13888}
13889
13890pub fn field_initializer_holds_subobject(
13897 analyzer: &CppGraphSource<'_>,
13898 visibility: &VisibilityIndex<'_>,
13899 field: &CodeUnit,
13900) -> bool {
13901 visibility
13902 .field_declared_type_fact(analyzer, field)
13903 .is_none_or(|fact| !fact.binds_indirectly)
13904}
13905
13906fn anonymous_aggregate_field_owner(
13913 analyzer: &CppGraphSource<'_>,
13914 visibility: &VisibilityIndex<'_>,
13915 visible_from: &ProjectFile,
13916 field: &CodeUnit,
13917) -> Option<CodeUnit> {
13918 let owner = type_owner_of(analyzer, field)?;
13919 if !owner.is_class() {
13920 return None;
13921 }
13922 let declaration = analyzer.get_source(field, false)?;
13923 let mut parser = Parser::new();
13924 parser
13925 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13926 .ok()?;
13927 let tree = parser.parse(&declaration, None)?;
13928 let mut stack = vec![tree.root_node()];
13929 while let Some(node) = stack.pop() {
13930 if matches!(node.kind(), "declaration" | "field_declaration")
13931 && let Some(type_node) = node
13932 .child_by_field_name("type")
13933 .or_else(|| first_type_child(node))
13934 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13935 && type_node.child_by_field_name("name").is_none()
13936 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13937 {
13938 let matches = visibility
13939 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13940 .into_iter()
13941 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13942 .collect::<Vec<_>>();
13943 return match matches.as_slice() {
13944 [child] => Some((*child).clone()),
13945 _ => None,
13946 };
13947 }
13948 let mut cursor = node.walk();
13949 stack.extend(node.named_children(&mut cursor));
13950 }
13951 None
13952}
13953
13954pub fn anonymous_aggregate_owner(
13961 analyzer: &CppGraphSource<'_>,
13962 file: &ProjectFile,
13963 node: Node<'_>,
13964) -> Option<CodeUnit> {
13965 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13966 || node.child_by_field_name("name").is_some()
13967 {
13968 return None;
13969 }
13970 let mut candidates = analyzer
13971 .declarations(file)
13972 .into_iter()
13973 .filter(|candidate| {
13974 candidate.is_class()
13975 && analyzer.ranges(candidate).into_iter().any(|range| {
13976 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13977 })
13978 })
13979 .collect::<Vec<_>>();
13980 candidates.sort_by_key(|candidate| candidate.fq_name());
13981 candidates.dedup();
13982 match candidates.as_slice() {
13983 [candidate] => Some(candidate.clone()),
13984 _ => None,
13985 }
13986}
13987
13988pub fn function_local_type_resolution(
14001 analyzer: &CppGraphSource<'_>,
14002 file: &ProjectFile,
14003 node: Node<'_>,
14004 components: &[String],
14005 source: &str,
14006) -> Option<LexicalTypeResolution> {
14007 let (head, rest) = components.split_first()?;
14008 if is_c_source_file(file) {
14009 return None;
14010 }
14011 let declaration = function_local_class_declaration(node, head, source)?;
14012 let Some(mut unit) = function_local_class_unit(analyzer, file, declaration, head, source)
14013 else {
14014 return Some(LexicalTypeResolution::Missing);
14015 };
14016 for component in rest {
14017 let mut nested = analyzer
14018 .declarations(file)
14019 .into_iter()
14020 .filter(|candidate| {
14021 candidate.is_class()
14022 && candidate.identifier() == component
14023 && brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
14024 .is_some_and(|parent| parent == unit.fq_name())
14025 })
14026 .collect::<Vec<_>>();
14027 nested.dedup();
14028 match nested.as_slice() {
14029 [child] => unit = child.clone(),
14030 [] => return Some(LexicalTypeResolution::Missing),
14031 _ => return Some(LexicalTypeResolution::Ambiguous),
14032 }
14033 }
14034 Some(LexicalTypeResolution::Resolved {
14035 components: canonical_cpp_scope_components(&unit),
14036 candidates: vec![unit.clone()],
14037 unit,
14038 })
14039}
14040
14041pub fn is_function_local_unit(unit: &CodeUnit) -> bool {
14045 let interner = segment_interner();
14046 unit.fq().segments().split_last().is_some_and(|(_, scope)| {
14047 scope
14048 .iter()
14049 .any(|&segment| interner.resolve(segment).1 == SegmentKind::Member)
14050 })
14051}
14052
14053fn function_local_class_declaration<'tree>(
14061 node: Node<'tree>,
14062 name: &str,
14063 source: &str,
14064) -> Option<Node<'tree>> {
14065 let mut path = Vec::new();
14069 let mut child = node;
14070 while let Some(ancestor) = child.parent() {
14071 if ancestor.kind() == "compound_statement"
14072 && ancestor.parent().is_some_and(|function| {
14073 function.kind() == "function_definition"
14074 && (is_recovered_declaration_scope_container(function, source)
14075 || crate::graph::extractor::is_malformed_wrapper_function_definition(
14076 function,
14077 ))
14078 })
14079 {
14080 break;
14081 }
14082 path.push((ancestor, child));
14083 child = ancestor;
14084 }
14085 let outermost_block = path
14086 .iter()
14087 .rposition(|(ancestor, _)| ancestor.kind() == "compound_statement")?;
14088 let mut in_member_body = false;
14089 for &(ancestor, _) in &path[..=outermost_block] {
14090 match ancestor.kind() {
14091 "compound_statement" => {
14092 if let Some(found) =
14093 scope_local_class_declaration(ancestor, name, source, Some(node.start_byte()))
14094 {
14095 return Some(found);
14096 }
14097 }
14098 "function_definition" | "lambda_expression" => in_member_body = true,
14099 "field_declaration_list" => {
14100 let class = ancestor.parent()?;
14101 if local_class_name_is(class, name, source) {
14102 return Some(class);
14103 }
14104 let before = (!in_member_body).then_some(node.start_byte());
14105 if let Some(found) = scope_local_class_declaration(ancestor, name, source, before) {
14106 return Some(found);
14107 }
14108 in_member_body = false;
14109 }
14110 _ => {}
14111 }
14112 }
14113 None
14114}
14115
14116fn scope_local_class_declaration<'tree>(
14121 scope: Node<'tree>,
14122 name: &str,
14123 source: &str,
14124 before: Option<usize>,
14125) -> Option<Node<'tree>> {
14126 named_children_iter(scope)
14127 .filter_map(scope_level_class_specifier)
14128 .filter(|specifier| before.is_none_or(|before| specifier.end_byte() <= before))
14129 .filter(|specifier| local_class_name_is(*specifier, name, source))
14130 .last()
14131}
14132
14133fn scope_level_class_specifier(statement: Node<'_>) -> Option<Node<'_>> {
14134 let is_class_like = |node: &Node<'_>| {
14135 matches!(
14136 node.kind(),
14137 "class_specifier" | "struct_specifier" | "union_specifier"
14138 )
14139 };
14140 if is_class_like(&statement) {
14141 return Some(statement);
14142 }
14143 if !matches!(
14144 statement.kind(),
14145 "declaration" | "field_declaration" | "type_definition"
14146 ) {
14147 return None;
14148 }
14149 let specifier = statement
14150 .child_by_field_name("type")
14151 .filter(is_class_like)?;
14152 (specifier.child_by_field_name("body").is_some()
14153 || statement.child_by_field_name("declarator").is_none())
14154 .then_some(specifier)
14155}
14156
14157fn local_class_name_is(specifier: Node<'_>, name: &str, source: &str) -> bool {
14158 specifier
14159 .child_by_field_name("name")
14160 .is_some_and(|name_node| {
14161 name_node.kind() == "type_identifier" && node_text(name_node, source) == name
14162 })
14163}
14164
14165fn function_local_class_unit(
14168 analyzer: &CppGraphSource<'_>,
14169 file: &ProjectFile,
14170 declaration: Node<'_>,
14171 name: &str,
14172 source: &str,
14173) -> Option<CodeUnit> {
14174 let definition = if declaration.child_by_field_name("body").is_some() {
14175 declaration
14176 } else {
14177 let mut scope = declaration.parent()?;
14178 if scope.kind() != "compound_statement" && scope.kind() != "field_declaration_list" {
14179 scope = scope.parent()?;
14180 }
14181 named_children_iter(scope)
14182 .filter_map(scope_level_class_specifier)
14183 .filter(|specifier| specifier.child_by_field_name("body").is_some())
14184 .find(|specifier| local_class_name_is(*specifier, name, source))?
14185 };
14186 let mut candidates = analyzer
14187 .declarations(file)
14188 .into_iter()
14189 .filter(|candidate| {
14190 candidate.is_class()
14191 && candidate.identifier() == name
14192 && is_function_local_unit(candidate)
14193 && analyzer.ranges(candidate).into_iter().any(|range| {
14194 range.start_byte == definition.start_byte()
14195 && range.end_byte == definition.end_byte()
14196 })
14197 })
14198 .collect::<Vec<_>>();
14199 candidates.dedup();
14200 match candidates.as_slice() {
14201 [candidate] => Some(candidate.clone()),
14202 _ => None,
14203 }
14204}
14205
14206fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
14208 let Some(first) = candidates.first() else {
14209 return Err(TypeCandidateFailure::Unresolvable);
14210 };
14211 if candidates
14212 .iter()
14213 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
14214 {
14215 Ok((*first).clone())
14216 } else {
14217 Err(TypeCandidateFailure::Ambiguous)
14218 }
14219}
14220
14221fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
14222 logical_type_candidate(candidates).ok()
14223}
14224
14225fn unique_type_candidate_preserving_alias(
14226 analyzer: &CppGraphSource<'_>,
14227 file: &ProjectFile,
14228 candidates: &[&CodeUnit],
14229) -> Option<CodeUnit> {
14230 let first = *candidates.first()?;
14231 if declared_type_alias(analyzer, first) {
14232 return candidates
14233 .iter()
14234 .all(|candidate| {
14235 declared_type_alias(analyzer, candidate)
14236 && candidate.kind() == first.kind()
14237 && candidate.fq_name() == first.fq_name()
14238 && candidate.source() == first.source()
14239 })
14240 .then(|| first.clone());
14241 }
14242 if analyzer.reference_uses_c_semantics(file)
14243 && first.is_class()
14244 && indexed_c_tag_kind(analyzer, first).is_some()
14245 {
14246 let mut full_source = None;
14247 let mut tag_kind = None;
14248 for candidate in candidates.iter().copied() {
14249 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
14250 if tag_kind
14251 .replace(candidate_tag_kind)
14252 .is_some_and(|existing| existing != candidate_tag_kind)
14253 {
14254 return None;
14255 }
14256 if cpp_class_declaration_strength(analyzer, candidate)
14257 == CppClassDeclarationStrength::Full
14258 && full_source
14259 .replace(candidate.source())
14260 .is_some_and(|existing| existing != candidate.source())
14261 {
14262 return None;
14263 }
14264 }
14265 }
14266 candidates
14267 .iter()
14268 .all(|candidate| {
14269 !declared_type_alias(analyzer, candidate)
14270 && candidate.kind() == first.kind()
14271 && candidate.fq_name() == first.fq_name()
14272 })
14273 .then(|| first.clone())
14274}
14275
14276pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
14277 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
14278 analyzer.type_alias_provider(),
14279 unit,
14280 )
14281}
14282
14283pub fn field_declared_type_binding(
14284 analyzer: &CppGraphSource<'_>,
14285 visibility: &VisibilityIndex<'_>,
14286 visible_from: &ProjectFile,
14287 field: &CodeUnit,
14288) -> Option<(String, Option<CodeUnit>, i32)> {
14289 let fact = visibility.field_declared_type_fact(analyzer, field)?;
14290 let normalized = normalize_field_type_text(&fact.type_text);
14291 let primary = visibility.resolve_unique_canonical_type_for_declaration(
14292 analyzer,
14293 visible_from,
14294 field,
14295 &normalized,
14296 );
14297 let resolved = match (primary, fact.template_arguments.as_deref()) {
14298 (Some(primary), Some(arguments)) => visibility
14299 .resolve_template_arguments(visible_from, primary, arguments)
14300 .ok(),
14301 (resolved, None) => resolved,
14302 (None, Some(_)) => None,
14303 };
14304 Some((normalized, resolved, fact.indirection))
14305}
14306
14307fn decode_field_declared_type_fact(
14308 analyzer: &CppGraphSource<'_>,
14309 field: &CodeUnit,
14310) -> Option<DeclaredFieldTypeFact> {
14311 if let Some(facts) = analyzer.declaration_source_properties(field)
14312 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
14313 && facts
14314 .iter()
14315 .all(|fact| fact.field_type.as_ref() == Some(first))
14316 {
14317 return Some(first.clone());
14318 }
14319 let Some(declaration) = analyzer.get_source(field, false) else {
14320 return decode_indexed_field_declared_type_fact(analyzer, field);
14321 };
14322 let mut parser = Parser::new();
14323 parser
14324 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14325 .ok()?;
14326 let contextual = parse_sentinel(
14330 "struct __bifrost_field_context { ",
14331 SentinelInput::region(&declaration),
14332 " };",
14333 );
14334 let (contextual_declaration, contextual_tree) = (contextual.source, contextual.tree);
14335 let mut stack = vec![contextual_tree.root_node()];
14336 while let Some(node) = stack.pop() {
14337 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
14338 && node_text(recovered.name, &contextual_declaration) == field.identifier()
14339 {
14340 return Some(DeclaredFieldTypeFact {
14341 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
14342 indirection: recovered.pointer_depth(),
14343 binds_indirectly: recovered.pointer_depth() > 0,
14344 template_arguments: None,
14345 });
14346 }
14347 if let Some(recovered) =
14348 recovered_function_like_field_declarator(node, &contextual_declaration)
14349 && node_text(recovered.name, &contextual_declaration) == field.identifier()
14350 {
14351 let type_node = node
14352 .child_by_field_name("type")
14353 .or_else(|| first_type_child(node))?;
14354 return Some(DeclaredFieldTypeFact {
14355 type_text: node_text(type_node, &contextual_declaration).to_string(),
14356 indirection: recovered.pointer_depth(),
14357 binds_indirectly: true,
14360 template_arguments: cpp_template_reference_arguments(
14361 type_node,
14362 &contextual_declaration,
14363 ),
14364 });
14365 }
14366 if let Some(fact) =
14367 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
14368 {
14369 return Some(fact);
14370 }
14371 let mut cursor = node.walk();
14372 stack.extend(node.named_children(&mut cursor));
14373 }
14374 let tree = parser.parse(&declaration, None)?;
14375 let mut stack = vec![tree.root_node()];
14376 while let Some(node) = stack.pop() {
14377 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
14378 {
14379 return Some(fact);
14380 }
14381 let mut cursor = node.walk();
14382 stack.extend(node.named_children(&mut cursor));
14383 }
14384 None
14385}
14386
14387fn decode_indexed_field_declared_type_fact(
14392 analyzer: &CppGraphSource<'_>,
14393 field: &CodeUnit,
14394) -> Option<DeclaredFieldTypeFact> {
14395 let cpp = analyzer.cpp?;
14396 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
14397 let source = prepared.source();
14398 let root = prepared.tree().root_node();
14399 for range in analyzer.ranges(field) {
14400 let end = range.start_byte.saturating_add(1).min(source.len());
14401 let mut current = root.descendant_for_byte_range(range.start_byte, end);
14402 while let Some(node) = current {
14403 if matches!(node.kind(), "declaration" | "field_declaration")
14404 && let Some(fact) =
14405 decode_declared_field_type_node(node, field.identifier(), source)
14406 {
14407 return Some(fact);
14408 }
14409 current = node.parent();
14410 }
14411 }
14412 None
14413}
14414
14415fn decode_declared_field_type_node(
14416 node: Node<'_>,
14417 field_name: &str,
14418 source: &str,
14419) -> Option<DeclaredFieldTypeFact> {
14420 if !matches!(node.kind(), "declaration" | "field_declaration") {
14421 return None;
14422 }
14423 let type_node = node
14424 .child_by_field_name("type")
14425 .or_else(|| first_type_child(node))?;
14426 cpp_declared_field_type_fact(node, type_node, field_name, source)
14427}
14428
14429pub(crate) fn cpp_declared_field_type_fact(
14430 node: Node<'_>,
14431 type_node: Node<'_>,
14432 field_name: &str,
14433 source: &str,
14434) -> Option<DeclaredFieldTypeFact> {
14435 let binding = declared_name_binding(node, type_node, field_name, source)?;
14436 let declared_type = if matches!(
14437 type_node.kind(),
14438 "class_specifier" | "struct_specifier" | "union_specifier"
14439 ) {
14440 type_node.child_by_field_name("name")
14441 } else {
14442 Some(type_node)
14443 };
14444 Some(DeclaredFieldTypeFact {
14445 type_text: declared_type.map_or_else(
14446 || field_name.to_string(),
14447 |declared_type| node_text(declared_type, source).to_string(),
14448 ),
14449 indirection: binding.pointer_depth,
14450 binds_indirectly: binding.indirect,
14451 template_arguments: declared_type
14452 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
14453 })
14454}
14455
14456pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
14462 let mut current = Some(declarator);
14463 while let Some(node) = current {
14464 match node.kind() {
14465 "function_declarator" | "abstract_function_declarator" => return true,
14466 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
14467 current = node.named_child(0);
14468 }
14469 _ => current = node.child_by_field_name("declarator"),
14470 }
14471 }
14472 false
14473}
14474
14475fn decode_structured_alias_target(
14476 analyzer: &CppGraphSource<'_>,
14477 unit: &CodeUnit,
14478) -> Option<StructuredAliasTarget> {
14479 let facts = analyzer.declaration_source_properties(unit)?;
14480 let first = facts.first()?.alias_target.as_ref()?;
14481 facts
14482 .iter()
14483 .all(|fact| fact.alias_target.as_ref() == Some(first))
14484 .then(|| first.clone())
14485}
14486
14487pub(crate) fn structured_alias_type_target(
14488 mut type_node: Node<'_>,
14489 source: &str,
14490) -> Option<StructuredAliasTarget> {
14491 while type_node.kind() == "type_descriptor" {
14492 type_node = type_node.child_by_field_name("type")?;
14493 }
14494 if type_node.kind() == "primitive_type" {
14495 return Some(StructuredAliasTarget::Builtin);
14496 }
14497 if matches!(
14498 type_node.kind(),
14499 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
14500 ) {
14501 type_node = type_node.child_by_field_name("name")?;
14502 }
14503 let global = type_node.child_by_field_name("scope").is_none()
14504 && type_node.child(0).is_some_and(|child| child.kind() == "::");
14505 let mut components = Vec::new();
14506 append_structured_type_components(type_node, source, &mut components)?;
14507 let arguments = cpp_template_reference_arguments(type_node, source);
14508 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
14509 components,
14510 global,
14511 arguments,
14512 })
14513}
14514
14515fn append_structured_type_components(
14516 node: Node<'_>,
14517 source: &str,
14518 out: &mut Vec<String>,
14519) -> Option<()> {
14520 match node.kind() {
14521 "identifier" | "namespace_identifier" | "type_identifier" => {
14522 out.push(node_text(node, source).to_string());
14523 Some(())
14524 }
14525 "template_type" | "template_function" => {
14526 append_structured_type_components(node.child_by_field_name("name")?, source, out)
14527 }
14528 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
14529 let (scope, name) = qualified_name_parts(node)?;
14530 if let Some(scope) = scope {
14531 append_structured_type_components(scope, source, out)?;
14532 }
14533 append_structured_type_components(name, source, out)
14534 }
14535 _ => None,
14536 }
14537}
14538
14539pub(crate) struct DeclaredNameBinding {
14542 pub pointer_depth: i32,
14544 pub indirect: bool,
14548}
14549
14550pub(crate) fn declared_name_binding(
14551 declaration: Node<'_>,
14552 type_node: Node<'_>,
14553 field_name: &str,
14554 source: &str,
14555) -> Option<DeclaredNameBinding> {
14556 let mut stack = Vec::new();
14557 let mut cursor = declaration.walk();
14558 stack.extend(
14559 declaration
14560 .named_children(&mut cursor)
14561 .filter(|child| !same_node(*child, type_node)),
14562 );
14563 while let Some(node) = stack.pop() {
14564 if matches!(node.kind(), "identifier" | "field_identifier")
14565 && node_text(node, source) == field_name
14566 {
14567 let mut binding = DeclaredNameBinding {
14568 pointer_depth: 0,
14569 indirect: false,
14570 };
14571 let mut current = node.parent();
14572 while let Some(parent) = current {
14573 if same_node(parent, declaration) {
14574 return Some(binding);
14575 }
14576 match parent.kind() {
14577 "pointer_declarator" => {
14578 binding.pointer_depth += 1;
14579 binding.indirect = true;
14580 }
14581 "reference_declarator" => binding.indirect = true,
14582 _ => {}
14583 }
14584 current = parent.parent();
14585 }
14586 return None;
14587 }
14588 let mut cursor = node.walk();
14589 stack.extend(node.named_children(&mut cursor));
14590 }
14591 None
14592}
14593
14594fn normalize_field_type_text(type_text: &str) -> String {
14595 const FIELD_SPECIFIERS: [&str; 8] = [
14596 "extern ",
14597 "static ",
14598 "mutable ",
14599 "constexpr ",
14600 "constinit ",
14601 "inline ",
14602 "volatile ",
14603 "const ",
14604 ];
14605
14606 let mut normalized = normalize_type_text(type_text);
14607 loop {
14608 let Some(stripped) = FIELD_SPECIFIERS
14609 .iter()
14610 .find_map(|specifier| normalized.strip_prefix(specifier))
14611 else {
14612 return normalized;
14613 };
14614 normalized = normalize_type_text(stripped);
14615 }
14616}
14617
14618pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
14619 let Some(type_node) = node.child_by_field_name("type") else {
14620 return false;
14621 };
14622 ctx.visibility.resolves_to_type(
14623 &ctx.analyzer,
14624 ctx.file,
14625 node_text(type_node, ctx.source),
14626 owner,
14627 )
14628}
14629
14630pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
14631 !ctx.analyzer
14632 .declarations(ctx.file)
14633 .into_iter()
14634 .filter(|unit| unit.is_function())
14635 .any(|unit| {
14636 ctx.analyzer.ranges(&unit).iter().any(|range| {
14637 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
14638 })
14639 })
14640}
14641
14642pub enum DeclarationConstructorInitializer<'tree> {
14644 Arguments(Node<'tree>),
14647 Expression(Node<'tree>),
14650 Empty,
14652}
14653
14654pub fn declaration_constructor_initializer(
14655 node: Node<'_>,
14656) -> DeclarationConstructorInitializer<'_> {
14657 let mut cursor = node.walk();
14658 for child in node.named_children(&mut cursor) {
14659 if child.kind() == "init_declarator" {
14660 let Some(value) = child
14661 .child_by_field_name("value")
14662 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
14663 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
14664 else {
14665 return DeclarationConstructorInitializer::Empty;
14666 };
14667 return match value.kind() {
14668 "argument_list" | "initializer_list" => {
14669 DeclarationConstructorInitializer::Arguments(value)
14670 }
14671 "compound_literal_expression" => call_arguments_node(value)
14672 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
14673 DeclarationConstructorInitializer::Arguments(arguments)
14674 }),
14675 _ => DeclarationConstructorInitializer::Expression(value),
14676 };
14677 }
14678 if let Some(declarator) = declaration_declarator(node, child) {
14679 return declarator_parameters(declarator)
14680 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
14681 DeclarationConstructorInitializer::Arguments(parameters)
14682 });
14683 }
14684 }
14685 DeclarationConstructorInitializer::Empty
14686}
14687
14688pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
14689 match declaration_constructor_initializer(node) {
14690 DeclarationConstructorInitializer::Arguments(arguments) => {
14691 argument_children(arguments).count()
14692 }
14693 DeclarationConstructorInitializer::Expression(_) => 1,
14694 DeclarationConstructorInitializer::Empty => 0,
14695 }
14696}
14697
14698fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
14702 let mut current = node;
14703 loop {
14704 if let Some(parameters) = current.child_by_field_name("parameters") {
14705 return Some(parameters);
14706 }
14707 current = current.child_by_field_name("declarator")?;
14708 }
14709}
14710
14711fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
14712 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
14713}
14714
14715fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
14716 if node.kind() == "identifier" {
14717 return true;
14718 }
14719 if node.kind() == "parenthesized_expression" {
14720 return false;
14721 }
14722 if node.kind() == "call_expression" {
14723 return node
14724 .child_by_field_name("function")
14725 .is_some_and(|function| function.kind() == "identifier");
14726 }
14727 let mut stack = vec![node];
14728 while let Some(descendant) = stack.pop() {
14729 if descendant != node && descendant.kind() == "parenthesized_expression" {
14730 continue;
14731 }
14732 if descendant.kind() == "identifier" {
14733 return true;
14734 }
14735 if descendant.kind() == "call_expression" {
14736 if descendant
14737 .child_by_field_name("function")
14738 .is_some_and(|function| function.kind() == "identifier")
14739 {
14740 return true;
14741 }
14742 continue;
14743 }
14744 push_named_children_reversed(descendant, &mut stack);
14745 }
14746 false
14747}
14748
14749fn macro_expansion_shape_is_safe(
14750 node: Node<'_>,
14751 source: &str,
14752 parameters: &[String],
14753 environment: &MacroEnvironment,
14754) -> bool {
14755 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
14756 return true;
14757 }
14758 if node.kind() == "call_expression" {
14759 let Some(function) = node.child_by_field_name("function") else {
14760 return true;
14761 };
14762 if function.kind() != "identifier" {
14763 return true;
14764 }
14765 let function_name = node_text(function, source);
14766 if parameters
14767 .iter()
14768 .any(|parameter| parameter == function_name)
14769 {
14770 return false;
14771 }
14772 if !environment.may_bind(function_name) {
14773 return true;
14774 }
14775 let Some(arguments) = node.child_by_field_name("arguments") else {
14776 return false;
14777 };
14778 return argument_children(arguments).all(|argument| {
14779 if argument.kind() == "identifier"
14780 && parameters
14781 .iter()
14782 .any(|parameter| parameter == node_text(argument, source))
14783 {
14784 return false;
14785 }
14786 macro_expansion_shape_is_safe(argument, source, parameters, environment)
14787 });
14788 }
14789 let mut stack = vec![node];
14790 while let Some(descendant) = stack.pop() {
14791 if descendant != node {
14792 if descendant.kind() == "parenthesized_expression" {
14793 continue;
14794 }
14795 if descendant.kind() == "call_expression" {
14796 let expands = descendant
14797 .child_by_field_name("function")
14798 .filter(|function| function.kind() == "identifier")
14799 .is_some_and(|function| environment.may_bind(node_text(function, source)));
14800 if expands {
14801 return false;
14802 }
14803 continue;
14804 }
14805 }
14806 if descendant.kind() == "identifier" {
14807 let identifier = node_text(descendant, source);
14808 if parameters.iter().any(|parameter| parameter == identifier)
14809 || environment.may_bind(identifier)
14810 {
14811 return false;
14812 }
14813 }
14814 push_named_children_reversed(descendant, &mut stack);
14815 }
14816 true
14817}
14818
14819fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
14820 let source = prepared.source();
14821 let mut facts = Vec::new();
14822 let mut nodes = vec![prepared.tree().root_node()];
14823 while let Some(node) = nodes.pop() {
14824 if node.kind() == "preproc_include" {
14825 let Some(path) = node
14826 .child_by_field_name("path")
14827 .and_then(|path| crate::imports::include_path_literal_value(path, source))
14828 .map(str::to_owned)
14829 else {
14830 continue;
14831 };
14832 facts.push(StructuredIncludeFact {
14833 start_byte: node.start_byte(),
14834 end_byte: node.end_byte(),
14835 path,
14836 });
14837 continue;
14838 }
14839 push_named_children_reversed(node, &mut nodes);
14840 }
14841 Arc::from(facts.into_boxed_slice())
14842}
14843
14844fn has_unresolved_include_visible_before_in_prepared(
14845 file: &ProjectFile,
14846 prepared: &PreparedSyntaxTree,
14847 include_targets: &IncludeTargetIndex,
14848 facts: &[StructuredIncludeFact],
14849 before_byte: usize,
14850 effective_guards: &ReferenceGuardAugmenter<'_>,
14851) -> bool {
14852 let guards = OnceCell::new();
14853 let reference = CallableReferenceContext {
14854 effective_guards,
14855 file_guards: OnceCell::new(),
14856 file,
14857 position: Some(CallableReferencePosition {
14858 prepared,
14859 byte: before_byte,
14860 guards: &guards,
14861 }),
14862 };
14863 let root = prepared.tree().root_node();
14864 facts
14865 .iter()
14866 .filter(|fact| fact.end_byte <= before_byte)
14867 .any(|fact| {
14868 let node = root
14869 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
14870 .expect("structured include fact range must be in prepared tree");
14871 assert_eq!(
14872 node.kind(),
14873 "preproc_include",
14874 "structured include fact range must identify its include node"
14875 );
14876 callable_preprocessor_context_is_visible_for_reference(
14877 node,
14878 prepared.source(),
14879 &reference,
14880 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
14881 })
14882}
14883
14884fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
14885 let descendant = node;
14886 while let Some(parent) = node.parent() {
14887 if is_cpp_preprocessor_conditional(parent)
14888 && !is_file_covering_include_guard(parent, source)
14889 && preprocessor_conditional_contains_descendant(parent, descendant)
14890 {
14891 return true;
14892 }
14893 node = parent;
14894 }
14895 false
14896}
14897
14898fn owning_preprocessor_conditionals(
14910 root: Node<'_>,
14911 event: Node<'_>,
14912 source: &str,
14913) -> OwningPreprocessorConditionals {
14914 if !has_preprocessor_conditional_ancestor(event, source) {
14915 return OwningPreprocessorConditionals::default();
14916 }
14917 let start = event.start_byte();
14918 let descendant = root
14919 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14920 .expect("a byte inside the parsed tree names a descendant");
14921 let mut owners = Vec::new();
14922 let mut current = descendant.parent();
14923 while let Some(conditional) = current {
14924 if is_cpp_preprocessor_conditional(conditional)
14925 && !is_file_covering_include_guard(conditional, source)
14926 && preprocessor_conditional_contains_descendant(conditional, descendant)
14927 {
14928 owners.push(conditional.start_byte());
14929 }
14930 current = conditional.parent();
14931 }
14932 owners.into_boxed_slice()
14933}
14934
14935fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14936 node.parent()
14937 .filter(|parent| parent.kind() == "translation_unit")
14938 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14939 && is_canonical_include_guard(node, source)
14940}
14941
14942fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14943 if node.kind() != "preproc_ifdef"
14944 || node
14945 .child(0)
14946 .is_none_or(|directive| directive.kind() != "#ifndef")
14947 || node.child_by_field_name("alternative").is_some()
14948 {
14949 return false;
14950 }
14951 let Some(guard_name) = node.child_by_field_name("name") else {
14952 return false;
14953 };
14954 let mut cursor = node.walk();
14955 node.named_children(&mut cursor)
14956 .find(|child| *child != guard_name && child.kind() != "comment")
14957 .filter(|child| child.kind() == "preproc_def")
14958 .and_then(|definition| definition.child_by_field_name("name"))
14959 .is_some_and(|defined_name| {
14960 node_text(defined_name, source) == node_text(guard_name, source)
14961 })
14962}
14963
14964fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14965 let mut guard = None;
14966 for child in named_children_iter(root) {
14967 if child.kind() == "comment" || is_pragma_once(child, source) {
14968 continue;
14969 }
14970 if guard.is_none() && is_canonical_include_guard(child, source) {
14971 guard = Some(child);
14972 } else {
14973 return None;
14974 }
14975 }
14976 guard
14977 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14978 .map(|name| node_text(name, source).to_string())
14979}
14980
14981fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14982 if (0..root.named_child_count())
14983 .filter_map(|index| root.named_child(index))
14984 .any(|child| is_pragma_once(child, source))
14985 {
14986 return MacroIncludeProtection::PragmaOnce;
14987 }
14988 top_level_canonical_include_guard_name(root, source)
14989 .map(MacroIncludeProtection::MacroGuard)
14990 .unwrap_or(MacroIncludeProtection::None)
14991}
14992
14993fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14994 node.kind() == "preproc_call"
14995 && node
14996 .child_by_field_name("directive")
14997 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14998 && node
14999 .child_by_field_name("argument")
15000 .is_some_and(|argument| node_text(argument, source).trim() == "once")
15001}
15002
15003fn parse_preproc_identifier(argument: &str) -> Option<String> {
15004 let parsed = parse_sentinel(
15005 "void __bifrost_undef() { ",
15006 SentinelInput::replacement(argument),
15007 "; }",
15008 );
15009 let (sentinel, tree) = (parsed.source, parsed.tree);
15010 if tree.root_node().has_error() {
15011 return None;
15012 }
15013 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
15014 let identifier = single_named_child_matching(statement, |child| !child.is_extra())?;
15017 (identifier.kind() == "identifier").then(|| node_text(identifier, &sentinel).to_string())
15018}
15019
15020pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
15021 match node.kind() {
15022 "identifier" | "field_identifier" => {
15023 let name = node_text(node, source).trim();
15024 (!name.is_empty()).then(|| name.to_string())
15025 }
15026 "abstract_array_declarator"
15027 | "abstract_function_declarator"
15028 | "abstract_parenthesized_declarator"
15029 | "abstract_pointer_declarator"
15030 | "abstract_reference_declarator" => None,
15031 "function_declarator" => node
15032 .child_by_field_name("declarator")
15033 .or_else(|| node.child_by_field_name("name"))
15034 .and_then(|child| extract_variable_name(child, source)),
15035 _ => node
15036 .child_by_field_name("declarator")
15037 .or_else(|| node.child_by_field_name("name"))
15038 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
15039 .and_then(|child| extract_variable_name(child, source)),
15040 }
15041}
15042
15043pub fn is_c_source_file(file: &ProjectFile) -> bool {
15054 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
15055}
15056
15057pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
15064 if !is_c_source_file(file) || node.kind() != "identifier" {
15065 return false;
15066 }
15067 let mut operand = node;
15068 while let Some(parent) = operand.parent().filter(|parent| {
15069 parent.kind() == "parenthesized_expression"
15070 && parent.named_child_count() == 1
15071 && parent.named_child(0) == Some(operand)
15072 }) {
15073 operand = parent;
15074 }
15075 operand.parent().is_some_and(|parent| {
15076 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
15077 })
15078}
15079
15080pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
15089 if !is_c_offsetof_member_node(node) {
15090 return None;
15091 }
15092 let expression = node.parent()?;
15093 if expression.has_error() {
15094 return None;
15095 }
15096 let closing = expression.child(expression.child_count().saturating_sub(1))?;
15097 if closing.kind() != ")" || closing.is_missing() {
15098 return None;
15099 }
15100 let type_descriptor = expression.child_by_field_name("type")?;
15101 if type_descriptor.kind() != "type_descriptor"
15102 || type_descriptor.is_missing()
15103 || type_descriptor.has_error()
15104 {
15105 return None;
15106 }
15107 let type_specifier = type_descriptor.child_by_field_name("type")?;
15108 if type_specifier.is_missing() || type_specifier.has_error() {
15109 return None;
15110 }
15111 let type_reference = match type_specifier.kind() {
15112 "class_specifier" | "struct_specifier" | "union_specifier" => {
15113 type_specifier.child_by_field_name("name")?
15114 }
15115 _ => type_specifier,
15116 };
15117 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
15118}
15119
15120pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
15125 node.kind() == "field_identifier"
15126 && node.parent().is_some_and(|parent| {
15127 parent.kind() == "offsetof_expression"
15128 && parent.child_by_field_name("member") == Some(node)
15129 })
15130}
15131
15132pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
15136 if node.kind() != "template_function" {
15137 return false;
15138 }
15139 let Some(arguments) = node
15140 .parent()
15141 .filter(|parent| parent.kind() == "template_argument_list")
15142 else {
15143 return false;
15144 };
15145 let Some(owner) = arguments.parent().filter(|parent| {
15146 parent.kind() == "template_type"
15147 && parent.child_by_field_name("arguments") == Some(arguments)
15148 }) else {
15149 return false;
15150 };
15151
15152 let mut current = owner;
15153 while let Some(parent) = current.parent() {
15154 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
15155 {
15156 return true;
15157 }
15158 if parent.kind() == "qualified_identifier"
15159 && parent.child_by_field_name("name") == Some(current)
15160 {
15161 current = parent;
15162 continue;
15163 }
15164 return false;
15165 }
15166 false
15167}
15168
15169pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
15185 if node.kind() != "type_identifier" {
15186 return false;
15187 }
15188 let Some(descriptor) = node
15189 .parent()
15190 .filter(|parent| parent.kind() == "type_descriptor")
15191 else {
15192 return false;
15193 };
15194 if descriptor.child_by_field_name("type") != Some(node) {
15195 return false;
15196 }
15197 let Some(arguments) = descriptor
15198 .parent()
15199 .filter(|parent| parent.kind() == "template_argument_list")
15200 else {
15201 return false;
15202 };
15203 arguments.parent().is_some_and(|owner| {
15204 matches!(
15205 owner.kind(),
15206 "template_type" | "template_function" | "template_method"
15207 ) && owner.child_by_field_name("arguments") == Some(arguments)
15208 })
15209}
15210
15211pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
15224 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
15225}
15226
15227pub fn is_declarator_node(node: Node<'_>) -> bool {
15228 matches!(
15229 node.kind(),
15230 "identifier"
15231 | "field_identifier"
15232 | "qualified_identifier"
15233 | "scoped_identifier"
15234 | "pointer_declarator"
15235 | "reference_declarator"
15236 | "array_declarator"
15237 | "parenthesized_declarator"
15238 | "function_declarator"
15239 )
15240}
15241
15242pub fn declaration_declarator<'tree>(
15251 declaration: Node<'tree>,
15252 child: Node<'tree>,
15253) -> Option<Node<'tree>> {
15254 if !matches!(
15255 declaration.kind(),
15256 "declaration"
15257 | "field_declaration"
15258 | "parameter_declaration"
15259 | "optional_parameter_declaration"
15260 | "function_definition"
15261 | "type_definition"
15262 | "alias_declaration"
15263 | "template_instantiation"
15264 ) {
15265 return None;
15266 }
15267 if declaration
15268 .child_by_field_name("type")
15269 .is_some_and(|type_node| same_node(type_node, child))
15270 {
15271 return None;
15272 }
15273 if child.kind() == "init_declarator" {
15274 return child.child_by_field_name("declarator");
15275 }
15276 let field = field_name_in_parent(declaration, child);
15277 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
15278 || (declaration.kind() == "type_definition"
15279 && child.kind() == "type_identifier"
15280 && matches!(field, Some("declarator") | None))
15281 {
15282 Some(child)
15283 } else {
15284 None
15285 }
15286}
15287
15288pub fn cpp_binding_parts<'tree>(
15290 node: Node<'tree>,
15291 source: &str,
15292) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
15293 let declarator = node.child_by_field_name("declarator")?;
15294 let name = extract_variable_name(declarator, source)?;
15295 let type_node = node
15296 .child_by_field_name("type")
15297 .or_else(|| first_type_child(node));
15298 Some((name, declarator, type_node))
15299}
15300
15301#[derive(Clone, Debug, PartialEq, Eq)]
15304pub struct RecoveredNamespaceRegion {
15305 pub start: usize,
15307 pub end: usize,
15309 pub components: Vec<String>,
15312 pub component_ranges: Vec<Range>,
15323 pub component_name_ranges: Vec<Range>,
15329 pub component_closes: Vec<Option<Range>>,
15335}
15336
15337#[derive(Clone, Debug, Default)]
15402pub struct OrphanedNamespaceScopeIndex {
15403 regions: Vec<RecoveredNamespaceRegion>,
15404 brace_closes: HashMap<usize, Range>,
15407}
15408
15409impl OrphanedNamespaceScopeIndex {
15410 pub fn build(root: Node<'_>, source: &str) -> Self {
15411 if !root.has_error() {
15412 return Self::default();
15413 }
15414 #[derive(Clone, Copy)]
15420 struct OpenHead {
15421 head: Range,
15422 open: usize,
15423 }
15424 struct PendingRegion {
15427 start: usize,
15428 end: usize,
15429 components: Vec<String>,
15430 heads: Vec<OpenHead>,
15431 name_ranges: Vec<Range>,
15432 }
15433 struct Frame<'tree> {
15434 node: Node<'tree>,
15435 children: Vec<Node<'tree>>,
15436 next: usize,
15437 parsed_scope: Vec<String>,
15438 run: Option<PendingRegion>,
15439 }
15440 fn frame<'tree>(
15441 node: Node<'tree>,
15442 mut parsed_scope: Vec<String>,
15443 source: &str,
15444 ) -> Frame<'tree> {
15445 if node.kind() == "namespace_definition"
15446 && let Some(name) = node.child_by_field_name("name")
15447 {
15448 let mut components = Vec::new();
15449 if append_cpp_name_components(name, source, &mut components).is_some() {
15450 parsed_scope.extend(components);
15451 }
15452 }
15453 let mut cursor = node.walk();
15454 Frame {
15455 node,
15456 children: node.children(&mut cursor).collect(),
15457 next: 0,
15458 parsed_scope,
15459 run: None,
15460 }
15461 }
15462 let mut regions = Vec::new();
15463 let mut brace_closes = HashMap::default();
15464 let mut namespace_brace_proofs = Vec::new();
15471 let mut unpaired_closes: Vec<Range> = Vec::new();
15477 let mut open = Vec::new();
15481 let mut lexical_scope = Vec::new();
15482 let mut lexical_heads: Vec<OpenHead> = Vec::new();
15487 let mut lexical_name_ranges: Vec<Range> = Vec::new();
15490 let mut frames = vec![frame(root, Vec::new(), source)];
15491 while !frames.is_empty() {
15496 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
15497 let current = frames.last_mut().expect("frames is non-empty");
15498 if current.next == current.children.len() {
15499 regions.extend(frames.pop().expect("the frame just borrowed").run);
15500 continue;
15501 }
15502 let child = current.children[current.next];
15503 current.next += 1;
15504 match child.kind() {
15505 "{" if !child.is_missing() => {
15506 regions.extend(current.run.take());
15507 if let Some(namespace) = parent_node.filter(|parent| {
15508 parent.kind() == "namespace_definition"
15509 && parent.child_by_field_name("body") == Some(current.node)
15510 && child.start_byte() == current.node.start_byte()
15511 }) && let Some(proof) = namespace_body_brace_proof(current.node)
15512 {
15513 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
15514 }
15515 let mut named = parent_node.and_then(|parent| {
15520 namespace_body_name_components(parent, current.node, source)
15521 });
15522 if named.is_none() {
15523 named = recovered_namespace_open_components(
15524 ¤t.children[..current.next - 1],
15525 source,
15526 );
15527 }
15528 open.push((child.start_byte(), lexical_scope.len()));
15529 if let Some((written_at, components)) = named {
15530 let head = OpenHead {
15531 head: Range {
15532 start_byte: written_at.start_byte(),
15533 end_byte: child.end_byte(),
15534 start_line: written_at.start_position().row + 1,
15535 end_line: child.end_position().row + 1,
15536 },
15537 open: child.start_byte(),
15538 };
15539 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
15540 lexical_name_ranges
15541 .extend(components.iter().map(|(_, name_range)| *name_range));
15542 lexical_scope
15543 .extend(components.into_iter().map(|(component, _)| component));
15544 }
15545 continue;
15546 }
15547 "}" if !child.is_missing() => {
15548 regions.extend(current.run.take());
15549 let close = Range {
15550 start_byte: child.start_byte(),
15551 end_byte: child.end_byte(),
15552 start_line: child.start_position().row + 1,
15553 end_line: child.end_position().row + 1,
15554 };
15555 match open.pop() {
15556 Some((start, namespace_len)) => {
15557 debug_assert!(
15558 start < close.start_byte,
15559 "a close pairs with an open before it: {start} then {close:?}"
15560 );
15561 lexical_scope.truncate(namespace_len);
15562 lexical_heads.truncate(namespace_len);
15563 lexical_name_ranges.truncate(namespace_len);
15564 brace_closes.insert(start, close);
15565 }
15566 None => unpaired_closes.push(close),
15567 }
15568 continue;
15569 }
15570 _ => {}
15571 }
15572 if current.node.kind() != "namespace_definition"
15576 && lexical_scope != current.parsed_scope
15577 {
15578 match &mut current.run {
15579 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
15580 run => {
15581 regions.extend(run.take());
15582 *run = Some(PendingRegion {
15583 start: child.start_byte(),
15584 end: child.end_byte(),
15585 components: lexical_scope.clone(),
15586 heads: lexical_heads.clone(),
15587 name_ranges: lexical_name_ranges.clone(),
15588 });
15589 }
15590 }
15591 } else {
15592 regions.extend(current.run.take());
15593 }
15594 let descend = child.has_error()
15602 || (child.kind() == "function_definition"
15603 && child.child_by_field_name("body").is_some_and(|body| {
15604 namespace_body_name_components(child, body, source).is_some()
15605 }));
15606 if descend {
15607 let parsed_scope = current.parsed_scope.clone();
15608 frames.push(frame(child, parsed_scope, source));
15609 }
15610 }
15611 if !unpaired_closes.is_empty() {
15620 brace_closes.clear();
15621 } else {
15622 let missing_namespaces = namespace_brace_proofs
15628 .iter()
15629 .filter_map(|(_, namespace, proof)| {
15630 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
15631 })
15632 .collect::<HashSet<_>>();
15633 for (open, namespace, proof) in namespace_brace_proofs {
15634 if !std::iter::successors(Some(namespace), |node| node.parent())
15635 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
15636 {
15637 continue;
15638 }
15639 match proof {
15640 NamespaceBodyBraceProof::Real(close) => {
15641 brace_closes.insert(open, close);
15642 }
15643 NamespaceBodyBraceProof::Missing => {
15644 brace_closes.remove(&open);
15645 }
15646 }
15647 }
15648 }
15649 let regions = regions
15654 .into_iter()
15655 .map(|region| {
15656 debug_assert_eq!(
15657 region.components.len(),
15658 region.heads.len(),
15659 "every recovered namespace level carries the head that writes it: {:?}",
15660 region.components
15661 );
15662 debug_assert_eq!(
15663 region.components.len(),
15664 region.name_ranges.len(),
15665 "every recovered namespace level carries the name token that writes it: {:?}",
15666 region.components
15667 );
15668 let component_closes: Vec<_> = region
15669 .heads
15670 .iter()
15671 .map(|level| brace_closes.get(&level.open).copied())
15672 .collect();
15673 let component_ranges = region
15674 .heads
15675 .iter()
15676 .zip(&component_closes)
15677 .map(|(level, close)| match close {
15678 Some(close) => Range {
15679 start_byte: level.head.start_byte,
15680 end_byte: close.end_byte,
15681 start_line: level.head.start_line,
15682 end_line: close.end_line,
15683 },
15684 None => level.head,
15685 })
15686 .collect();
15687 RecoveredNamespaceRegion {
15688 start: region.start,
15689 end: region.end,
15690 components: region.components,
15691 component_ranges,
15692 component_name_ranges: region.name_ranges,
15693 component_closes,
15694 }
15695 })
15696 .collect();
15697 Self {
15698 regions,
15699 brace_closes,
15700 }
15701 }
15702
15703 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
15710 self.brace_closes.get(&open).copied()
15711 }
15712
15713 pub fn is_empty(&self) -> bool {
15714 self.regions.is_empty()
15715 }
15716
15717 pub fn approximate_size(&self) -> usize {
15719 self.regions.iter().fold(
15720 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
15721 |total, region| {
15722 total
15723 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
15724 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
15725 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
15726 .saturating_add(
15727 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
15728 )
15729 .saturating_add(
15730 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
15731 )
15732 },
15733 )
15734 }
15735
15736 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
15738 self.regions
15739 .iter()
15740 .filter(|region| region.start <= byte && byte < region.end)
15741 .min_by_key(|region| region.end - region.start)
15742 }
15743
15744 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
15748 self.enclosing_namespace_levels(node, source)
15749 .into_iter()
15750 .map(|(component, ..)| component)
15751 .collect()
15752 }
15753
15754 pub fn enclosing_namespace_levels(
15763 &self,
15764 node: Node<'_>,
15765 source: &str,
15766 ) -> Vec<(String, Range, Range)> {
15767 let mut parsed = Vec::new();
15768 let mut current = node.parent();
15769 while let Some(parent) = current {
15770 if parent.kind() == "namespace_definition"
15771 && let Some(name) = parent.child_by_field_name("name")
15772 && let Some(components) = cpp_name_component_ranges(name, source)
15773 {
15774 let range = node_range(parent);
15775 parsed.push((
15776 parent.start_byte(),
15777 components
15778 .into_iter()
15779 .map(|(component, name_range)| (component, range, name_range))
15780 .collect(),
15781 namespace_definition_has_real_braces(parent),
15782 ));
15783 }
15784 current = parent.parent();
15785 }
15786 parsed.reverse();
15787 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
15788 region
15789 .components
15790 .iter()
15791 .take(active_components)
15792 .cloned()
15793 .zip(
15794 region
15795 .component_ranges
15796 .iter()
15797 .take(active_components)
15798 .copied(),
15799 )
15800 .zip(
15801 region
15802 .component_name_ranges
15803 .iter()
15804 .take(active_components)
15805 .copied(),
15806 )
15807 .map(|((component, range), name_range)| (component, range, name_range))
15808 .collect()
15809 })
15810 }
15811
15812 pub fn restore_enclosing_namespaces(
15818 &self,
15819 parsed: Vec<(usize, Vec<String>, bool)>,
15820 node_start: usize,
15821 ) -> Vec<String> {
15822 self.splice_recovered_path(parsed, node_start, |region, active_components| {
15823 region
15824 .components
15825 .iter()
15826 .take(active_components)
15827 .cloned()
15828 .collect()
15829 })
15830 }
15831
15832 fn splice_recovered_path<T>(
15838 &self,
15839 parsed: Vec<(usize, Vec<T>, bool)>,
15840 node_start: usize,
15841 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
15842 ) -> Vec<T> {
15843 let Some(region) = self.region_at(node_start) else {
15844 return parsed
15845 .into_iter()
15846 .flat_map(|(_, levels, _)| levels)
15847 .collect();
15848 };
15849 debug_assert_eq!(
15850 region.components.len(),
15851 region.component_closes.len(),
15852 "every recovered namespace component carries its close proof: {region:?}"
15853 );
15854 debug_assert_eq!(
15855 region.components.len(),
15856 region.component_name_ranges.len(),
15857 "every recovered namespace component carries its name token: {region:?}"
15858 );
15859 if region.components.is_empty() {
15860 return parsed
15861 .into_iter()
15862 .filter(|(_, _, complete)| *complete)
15863 .flat_map(|(_, levels, _)| levels)
15864 .collect();
15865 }
15866 let active_components = region
15867 .component_closes
15868 .iter()
15869 .take_while(|close| {
15870 close
15871 .as_ref()
15872 .is_none_or(|close| node_start < close.end_byte)
15873 })
15874 .count();
15875 recovered(region, active_components)
15876 .into_iter()
15877 .chain(
15878 parsed
15879 .into_iter()
15880 .filter(|(start, _, _)| *start >= region.start)
15881 .flat_map(|(_, levels, _)| levels),
15882 )
15883 .collect()
15884 }
15885}
15886
15887pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
15891 debug_assert_eq!(namespace.kind(), "namespace_definition");
15892 let Some(body) = namespace.child_by_field_name("body") else {
15893 return false;
15894 };
15895 let mut opening = false;
15896 let mut closing = false;
15897 let mut cursor = body.walk();
15898 for child in body.children(&mut cursor) {
15899 match child.kind() {
15900 "{" if !child.is_missing() => opening = true,
15901 "}" if !child.is_missing() => closing = true,
15902 _ => {}
15903 }
15904 }
15905 opening && closing
15906}
15907
15908fn recovered_namespace_open_components<'tree>(
15924 preceding: &[Node<'tree>],
15925 source: &str,
15926) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15927 let mut head = Vec::new();
15928 for &sibling in preceding.iter().rev() {
15929 if sibling.kind() != "comment" {
15930 head.push(sibling);
15931 if head.len() == 2 {
15932 break;
15933 }
15934 }
15935 }
15936 let [name, keyword] = head[..] else {
15937 return None;
15938 };
15939 if keyword.kind() != "namespace" {
15940 return None;
15941 }
15942 let components = cpp_name_component_ranges(name, source)?;
15943 (!components.is_empty()).then_some((keyword, components))
15944}
15945
15946fn namespace_body_name_components<'tree>(
15951 parent: Node<'tree>,
15952 body: Node<'tree>,
15953 source: &str,
15954) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15955 if parent.child_by_field_name("body") != Some(body) {
15956 return None;
15957 }
15958 let (written_at, name) = match (parent.kind(), body.kind()) {
15959 ("namespace_definition", "declaration_list") => {
15960 (parent, parent.child_by_field_name("name")?)
15961 }
15962 ("function_definition", "compound_statement") => {
15963 recovered_literal_namespace_head(parent, body, source)
15969 .or_else(|| macro_modifier_namespace_head(parent, body, source))?
15970 }
15971 _ => return None,
15972 };
15973 let components = cpp_name_component_ranges(name, source)?;
15974 (!components.is_empty()).then_some((written_at, components))
15975}
15976
15977fn recovered_literal_namespace_head<'tree>(
15980 parent: Node<'tree>,
15981 body: Node<'tree>,
15982 source: &str,
15983) -> Option<(Node<'tree>, Node<'tree>)> {
15984 let mut cursor = parent.walk();
15988 let children = parent
15989 .children(&mut cursor)
15990 .filter(|child| child.kind() != "comment")
15991 .collect::<Vec<_>>();
15992 let [prefix, keyword, error, actual_body] = children.as_slice() else {
15993 return None;
15994 };
15995 if *actual_body != body
15996 || parent.child_by_field_name("type") != Some(*prefix)
15997 || parent.child_by_field_name("declarator") != Some(*keyword)
15998 || prefix.kind() != "type_identifier"
15999 || prefix.is_missing()
16000 || prefix.has_error()
16001 || prefix.child_count() != 0
16002 || keyword.kind() != "identifier"
16003 || keyword.is_missing()
16004 || keyword.has_error()
16005 || keyword.child_count() != 0
16006 || node_text(*keyword, source) != "namespace"
16007 || error.kind() != "ERROR"
16008 || error.is_missing()
16009 || body.is_missing()
16010 {
16011 return None;
16012 }
16013 let mut cursor = error.walk();
16014 let names = error
16015 .children(&mut cursor)
16016 .filter(|child| child.kind() != "comment")
16017 .collect::<Vec<_>>();
16018 let [name] = names.as_slice() else {
16019 return None;
16020 };
16021 if !matches!(name.kind(), "identifier" | "namespace_identifier")
16022 || name.is_missing()
16023 || name.has_error()
16024 || name.child_count() != 0
16025 {
16026 return None;
16027 }
16028 let open = body.child(0)?;
16029 if open.kind() != "{"
16030 || open.is_missing()
16031 || open.has_error()
16032 || open.start_byte() != body.start_byte()
16033 {
16034 return None;
16035 }
16036 Some((*keyword, *name))
16039}
16040
16041fn macro_modifier_namespace_head<'tree>(
16045 parent: Node<'tree>,
16046 body: Node<'tree>,
16047 source: &str,
16048) -> Option<(Node<'tree>, Node<'tree>)> {
16049 let mut cursor = parent.walk();
16057 let children = parent
16058 .children(&mut cursor)
16059 .filter(|child| child.kind() != "comment")
16060 .collect::<Vec<_>>();
16061 let [prefix, keyword, name, actual_body] = children.as_slice() else {
16062 return None;
16063 };
16064 if *actual_body != body
16065 || parent.child_by_field_name("type") != Some(*keyword)
16066 || parent.child_by_field_name("declarator") != Some(*name)
16067 || prefix.kind() != "macro_modifier"
16068 || prefix.is_missing()
16069 || prefix.has_error()
16070 || macro_modifier_identifier(*prefix).is_none()
16071 || keyword.kind() != "type_identifier"
16072 || keyword.is_missing()
16073 || keyword.has_error()
16074 || keyword.child_count() != 0
16075 || node_text(*keyword, source) != "namespace"
16076 || !matches!(name.kind(), "identifier" | "namespace_identifier")
16077 || name.is_missing()
16078 || name.has_error()
16079 || name.child_count() != 0
16080 || body.is_missing()
16081 {
16082 return None;
16083 }
16084 let open = body.child(0)?;
16085 if open.kind() != "{"
16086 || open.is_missing()
16087 || open.has_error()
16088 || open.start_byte() != body.start_byte()
16089 {
16090 return None;
16091 }
16092 Some((*keyword, *name))
16095}
16096
16097#[derive(Clone, Copy)]
16098enum NamespaceBodyBraceProof {
16099 Real(Range),
16100 Missing,
16101}
16102
16103fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
16109 if body.kind() != "declaration_list" {
16110 return None;
16111 }
16112 let mut cursor = body.walk();
16113 let children = body.children(&mut cursor).collect::<Vec<_>>();
16114 let close_index = children
16115 .iter()
16116 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
16117 let close = children[close_index];
16118 if close.is_missing() {
16119 return Some(NamespaceBodyBraceProof::Missing);
16120 }
16121 let namespace_terminator_sibling = body
16122 .parent()
16123 .filter(|namespace| namespace.kind() == "namespace_definition")
16124 .and_then(|namespace| namespace.next_sibling())
16125 .is_some_and(|sibling| {
16126 sibling.kind() == ";"
16127 || (sibling.kind() == "expression_statement"
16128 && sibling.child_count() == 1
16129 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
16130 });
16131 if namespace_terminator_sibling
16136 || children
16137 .get(close_index + 1)
16138 .is_some_and(|sibling| sibling.kind() == ";")
16139 || children[..close_index]
16140 .iter()
16141 .rev()
16142 .find(|child| !child.is_extra() && child.kind() != "{")
16143 .is_some_and(|previous| {
16144 matches!(
16145 previous.kind(),
16146 "expression_statement" | "labeled_statement"
16147 )
16148 })
16149 {
16150 return None;
16151 }
16152 Some(NamespaceBodyBraceProof::Real(node_range(close)))
16153}
16154
16155#[derive(Clone, Copy, Debug, Eq, PartialEq)]
16156pub enum RecoveredDeclaratorTypeContext {
16157 Declaration,
16158 FunctionDefinition,
16159 Parameter,
16160}
16161
16162pub fn recovered_macro_decorated_declarator_type(
16177 node: Node<'_>,
16178) -> Option<RecoveredDeclaratorTypeContext> {
16179 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
16180}
16181
16182pub fn recovered_macro_decorated_type_node(
16187 node: Node<'_>,
16188) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16189 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
16190 return None;
16191 }
16192 let qualified = node.parent()?;
16193 if qualified.kind() != "qualified_identifier"
16194 || qualified.child_by_field_name("scope") != Some(node)
16195 || !(0..qualified.child_count())
16196 .filter_map(|index| qualified.child(index))
16197 .any(|child| child.kind() == "::" && child.is_missing())
16198 {
16199 return None;
16200 }
16201 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
16202 return None;
16203 }
16204
16205 let (declaration, context) = recovered_declarator_container(qualified)?;
16206 let type_node = declaration
16207 .child_by_field_name("type")
16208 .filter(|type_node| {
16209 *type_node != qualified
16210 && !type_node.is_missing()
16211 && type_node.start_byte() != type_node.end_byte()
16212 })?;
16213 Some((type_node, context))
16214}
16215
16216pub fn macro_modifier_decorated_type_node(node: Node<'_>) -> bool {
16223 matches!(node.kind(), "type_identifier" | "template_type")
16224 && node.parent().is_some_and(|declaration| {
16225 matches!(
16226 declaration.kind(),
16227 "declaration"
16228 | "function_definition"
16229 | "parameter_declaration"
16230 | "optional_parameter_declaration"
16231 ) && declaration.child_by_field_name("type") == Some(node)
16232 && declaration.child_by_field_name("declarator").is_some()
16233 && crate::declarations::cpp_declaration_type_follows_macro_modifier(
16234 declaration,
16235 node,
16236 )
16237 })
16238}
16239
16240fn recovered_declarator_container(
16241 mut declarator: Node<'_>,
16242) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
16243 loop {
16244 let parent = declarator.parent()?;
16245 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
16246 return Some((
16247 parent
16248 .parent()
16249 .filter(|declaration| declaration.kind() == "declaration")?,
16250 RecoveredDeclaratorTypeContext::Declaration,
16251 ));
16252 }
16253 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
16254 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
16255 }
16256 if parent.kind() == "function_definition"
16257 && has_field_child(parent, "declarator", declarator)
16258 {
16259 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
16260 }
16261 if matches!(
16267 parent.kind(),
16268 "parameter_declaration" | "optional_parameter_declaration"
16269 ) && has_field_child(parent, "declarator", declarator)
16270 {
16271 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
16272 }
16273 if !matches!(
16274 parent.kind(),
16275 "array_declarator"
16276 | "function_declarator"
16277 | "parenthesized_declarator"
16278 | "pointer_declarator"
16279 | "pointer_type_declarator"
16280 | "reference_declarator"
16281 ) || !has_field_child(parent, "declarator", declarator)
16282 {
16283 return None;
16284 }
16285 declarator = parent;
16286 }
16287}
16288
16289fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
16290 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
16291}
16292
16293fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
16294 loop {
16295 if node.is_missing() || node.start_byte() == node.end_byte() {
16296 return false;
16297 }
16298 match node.kind() {
16299 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
16300 return true;
16301 }
16302 "array_declarator"
16303 | "function_declarator"
16304 | "parenthesized_declarator"
16305 | "pointer_declarator"
16306 | "pointer_type_declarator"
16307 | "reference_declarator" => {
16308 let Some(declarator) = node.child_by_field_name("declarator") else {
16309 return false;
16310 };
16311 node = declarator;
16312 }
16313 _ => return false,
16314 }
16315 }
16316}
16317
16318pub enum DesignatedInitializerOwner {
16320 Resolved(CodeUnit),
16321 Unresolved,
16322}
16323
16324enum InitializerOwnerStep {
16325 Field(String),
16326 AggregateWrapper,
16327}
16328
16329pub fn designated_initializer_owner(
16339 analyzer: &CppGraphSource<'_>,
16340 visibility: &VisibilityIndex<'_>,
16341 file: &ProjectFile,
16342 source: &str,
16343 node: Node<'_>,
16344) -> Option<DesignatedInitializerOwner> {
16345 if let Some(designator) = node
16346 .parent()
16347 .filter(|parent| parent.kind() == "field_designator")
16348 {
16349 let pair = designator.parent()?;
16350 if pair.kind() != "initializer_pair" {
16351 return None;
16352 }
16353 let mut cursor = pair.walk();
16354 let designators = pair
16355 .children_by_field_name("designator", &mut cursor)
16356 .collect::<Vec<_>>();
16357 let position = designators
16358 .iter()
16359 .position(|candidate| same_node(*candidate, designator))?;
16360 let initializer = pair.parent()?;
16361 if initializer.kind() != "initializer_list" {
16362 return None;
16363 }
16364 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
16365 for prior in &designators[..position] {
16366 let field = prior
16367 .child_by_field_name("field")
16368 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
16369 owner = owner.and_then(|owner| {
16370 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
16371 });
16372 }
16373 return Some(classified_designated_owner(owner));
16374 }
16375
16376 let init_declarator = node.parent()?;
16377 if init_declarator.child_by_field_name("declarator") != Some(node)
16378 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
16379 {
16380 return None;
16381 }
16382 Some(classified_designated_owner(declaration_owner(
16383 analyzer,
16384 visibility,
16385 file,
16386 source,
16387 init_declarator.parent()?,
16388 )))
16389}
16390
16391fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
16392 owner.map_or(
16393 DesignatedInitializerOwner::Unresolved,
16394 DesignatedInitializerOwner::Resolved,
16395 )
16396}
16397
16398fn initializer_list_owner(
16399 analyzer: &CppGraphSource<'_>,
16400 visibility: &VisibilityIndex<'_>,
16401 file: &ProjectFile,
16402 source: &str,
16403 initializer: Node<'_>,
16404) -> Option<CodeUnit> {
16405 let mut current = initializer;
16406 let mut steps = Vec::new();
16407 loop {
16408 let parent = current.parent()?;
16409 match parent.kind() {
16410 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
16411 let designator = parent.child_by_field_name("designator")?;
16412 let step = designator
16413 .child_by_field_name("field")
16414 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
16415 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
16416 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
16417 steps.push(step);
16418 current = parent.parent()?;
16419 }
16420 "initializer_list" => {
16421 current = parent;
16422 }
16423 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
16424 let declaration = parent.parent()?;
16425 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
16426 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16427 }
16428 "compound_literal_expression"
16429 if parent.child_by_field_name("value") == Some(current) =>
16430 {
16431 let type_node = parent.child_by_field_name("type")?;
16432 let owner =
16433 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
16434 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
16435 }
16436 "ERROR" => current = parent,
16437 _ => return None,
16438 }
16439 }
16440}
16441
16442fn apply_initializer_owner_steps(
16443 analyzer: &CppGraphSource<'_>,
16444 visibility: &VisibilityIndex<'_>,
16445 file: &ProjectFile,
16446 mut owner: CodeUnit,
16447 steps: Vec<InitializerOwnerStep>,
16448) -> Option<CodeUnit> {
16449 for step in steps.into_iter().rev() {
16450 if let InitializerOwnerStep::Field(field_name) = step {
16451 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
16452 }
16453 }
16454 Some(owner)
16455}
16456
16457fn initializer_field_owner(
16458 analyzer: &CppGraphSource<'_>,
16459 visibility: &VisibilityIndex<'_>,
16460 file: &ProjectFile,
16461 owner: CodeUnit,
16462 field_name: &str,
16463) -> Option<CodeUnit> {
16464 let fields = visibility
16465 .visible_members_for_owner_name(file, &owner, field_name)
16466 .into_iter()
16467 .filter(|field| field.is_field())
16468 .collect::<Vec<_>>();
16469 let field = match fields.as_slice() {
16470 [field] => *field,
16471 _ => return None,
16472 };
16473 field_declared_binding(analyzer, visibility, file, field)?.unit
16474}
16475
16476fn declaration_owner(
16477 analyzer: &CppGraphSource<'_>,
16478 visibility: &VisibilityIndex<'_>,
16479 file: &ProjectFile,
16480 source: &str,
16481 declaration: Node<'_>,
16482) -> Option<CodeUnit> {
16483 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
16484 return None;
16485 }
16486 let type_node = declaration
16487 .child_by_field_name("type")
16488 .or_else(|| first_type_child(declaration))?;
16489 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
16490}
16491
16492fn resolve_designated_owner_type(
16493 analyzer: &CppGraphSource<'_>,
16494 visibility: &VisibilityIndex<'_>,
16495 file: &ProjectFile,
16496 source: &str,
16497 type_node: Node<'_>,
16498) -> Option<CodeUnit> {
16499 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
16500 return Some(owner);
16501 }
16502 if !is_globally_qualified_cpp_name(type_node)
16503 && let Some(components) = cpp_type_name_components(type_node, source)
16504 && let Some(resolution) =
16505 function_local_type_resolution(analyzer, file, type_node, &components, source)
16506 {
16507 return match resolution {
16508 LexicalTypeResolution::Resolved { unit, .. } => Some(unit).filter(CodeUnit::is_class),
16509 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => None,
16510 };
16511 }
16512 let type_name = normalize_type_text(node_text(type_node, source));
16513 visibility
16514 .resolve_type(file, &type_name)
16515 .filter(CodeUnit::is_class)
16516}
16517
16518pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
16519 let mut cursor = node.walk();
16520 node.named_children(&mut cursor).find(|child| {
16521 matches!(
16522 child.kind(),
16523 "type_identifier"
16524 | "primitive_type"
16525 | "qualified_identifier"
16526 | "scoped_type_identifier"
16527 | "struct_specifier"
16528 | "union_specifier"
16529 | "enum_specifier"
16530 )
16531 })
16532}
16533
16534pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
16535 visibility: &VisibilityIndex<'_>,
16536 file: &ProjectFile,
16537 source: &str,
16538 declarator: Node<'_>,
16539 type_text: Option<&str>,
16540 bindings: &LocalInferenceEngine<T>,
16541) -> bool {
16542 if !has_ancestor_kind(declarator, "compound_statement") {
16543 return false;
16544 }
16545 if declarator
16546 .child_by_field_name("declarator")
16547 .is_none_or(|declarator| declarator.kind() != "identifier")
16548 {
16549 return false;
16550 }
16551 if !type_text
16552 .and_then(|text| visibility.resolve_type(file, text))
16553 .is_some_and(|unit| unit.is_class())
16554 {
16555 return false;
16556 }
16557 declarator
16558 .child_by_field_name("parameters")
16559 .is_some_and(|parameters| {
16560 constructor_parameters_look_like_expressions(parameters, source, bindings)
16561 })
16562}
16563
16564fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
16565 parameters: Node<'_>,
16566 source: &str,
16567 bindings: &LocalInferenceEngine<T>,
16568) -> bool {
16569 let mut cursor = parameters.walk();
16570 parameters.named_children(&mut cursor).any(|parameter| {
16571 !matches!(
16572 parameter.kind(),
16573 "parameter_declaration" | "optional_parameter_declaration"
16574 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
16575 })
16576}
16577
16578fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
16579 parameter: Node<'_>,
16580 source: &str,
16581 bindings: &LocalInferenceEngine<T>,
16582) -> bool {
16583 let text = node_text(parameter, source).trim();
16584 if text
16585 .chars()
16586 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
16587 && bindings.is_shadowed(text)
16588 {
16589 return true;
16590 }
16591
16592 let Some(base) = parameter
16593 .child_by_field_name("type")
16594 .filter(|base| base.kind() == "type_identifier")
16595 else {
16596 return false;
16597 };
16598 let Some(subscript) = parameter
16599 .child_by_field_name("declarator")
16600 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
16601 else {
16602 return false;
16603 };
16604 subscript.child_by_field_name("size").is_some()
16605 && bindings.is_shadowed(node_text(base, source).trim())
16606}
16607
16608pub fn is_declaration_name(node: Node<'_>) -> bool {
16609 let Some(parent) = node.parent() else {
16610 return false;
16611 };
16612 if parent
16613 .child_by_field_name("name")
16614 .is_some_and(|name| same_node(name, node))
16615 {
16616 if matches!(
16617 parent.kind(),
16618 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
16619 ) {
16620 return cpp_tag_specifier_declares_name(parent);
16621 }
16622 if matches!(
16623 parent.kind(),
16624 "namespace_definition"
16625 | "namespace_alias_definition"
16626 | "alias_declaration"
16627 | "enumerator"
16628 ) {
16629 return true;
16630 }
16631 }
16632
16633 let mut current = Some(parent);
16634 while let Some(ancestor) = current {
16635 let type_definition = ancestor.kind() == "type_definition";
16636 let mut child_cursor = ancestor.walk();
16637 if ancestor.named_children(&mut child_cursor).any(|child| {
16638 declaration_declarator(ancestor, child).is_some_and(|declarator| {
16639 declarator_name_path_contains(declarator, node, type_definition)
16640 })
16641 }) {
16642 return true;
16643 }
16644 if matches!(
16645 ancestor.kind(),
16646 "declaration"
16647 | "field_declaration"
16648 | "parameter_declaration"
16649 | "optional_parameter_declaration"
16650 | "function_definition"
16651 | "type_definition"
16652 | "alias_declaration"
16653 | "template_instantiation"
16654 | "class_specifier"
16655 | "struct_specifier"
16656 | "union_specifier"
16657 | "enum_specifier"
16658 ) {
16659 return false;
16660 }
16661 current = ancestor.parent();
16662 }
16663 false
16664}
16665
16666pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
16675 if !matches!(
16676 node.kind(),
16677 "qualified_identifier" | "scoped_type_identifier"
16678 ) {
16679 return false;
16680 }
16681 let Some(declaration) = node
16682 .parent()
16683 .filter(|parent| parent.kind() == "declaration")
16684 else {
16685 return false;
16686 };
16687 if declaration.child_by_field_name("declarator") != Some(node)
16688 || !declaration
16689 .child_by_field_name("type")
16690 .is_some_and(|friend| {
16691 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
16692 })
16693 {
16694 return false;
16695 }
16696 let mut cursor = declaration.walk();
16697 let mut errors = declaration
16698 .named_children(&mut cursor)
16699 .filter(|child| child.kind() == "ERROR");
16700 let Some(error) = errors.next() else {
16701 return false;
16702 };
16703 errors.next().is_none()
16704 && error.named_child_count() == 1
16705 && error.named_child(0).is_some_and(|class| {
16706 class.kind() == "identifier" && node_text(class, source) == "class"
16707 })
16708}
16709
16710pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
16711 if !matches!(node.kind(), "identifier" | "field_identifier") {
16712 return false;
16713 }
16714 if let Some(parent) = node.parent() {
16715 if parent.kind() == "call_expression"
16716 && parent.child_by_field_name("function") == Some(node)
16717 {
16718 return false;
16719 }
16720 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
16721 && parent.child_by_field_name("label") == Some(node)
16722 {
16723 return false;
16724 }
16725 }
16726 if is_declaration_name(node) {
16727 return false;
16728 }
16729 let mut current = node.parent();
16730 while let Some(ancestor) = current {
16731 match ancestor.kind() {
16732 "preproc_ifdef" | "preproc_ifndef" => {
16733 if ancestor
16734 .child_by_field_name("name")
16735 .is_some_and(|name| node_range_contains(name, node))
16736 {
16737 return false;
16738 }
16739 }
16740 "preproc_if" | "preproc_elif" => {
16741 if ancestor
16742 .child_by_field_name("condition")
16743 .is_some_and(|condition| node_range_contains(condition, node))
16744 {
16745 return false;
16746 }
16747 }
16748 "preproc_else" => {}
16749 kind if kind.starts_with("preproc_") => return false,
16750 _ => {}
16751 }
16752 if matches!(
16753 ancestor.kind(),
16754 "translation_unit" | "function_definition" | "compound_statement"
16755 ) {
16756 break;
16757 }
16758 current = ancestor.parent();
16759 }
16760 true
16761}
16762
16763fn recovered_c_reference_node(
16764 visibility: &VisibilityIndex<'_>,
16765 file: &ProjectFile,
16766 node: Node<'_>,
16767 source: &str,
16768) -> bool {
16769 if node.start_byte() >= node.end_byte()
16770 || node.is_error()
16771 || node.is_missing()
16772 || !matches!(
16773 node.kind(),
16774 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
16775 )
16776 || recovered_c_macro_binding_role(node)
16777 || recovered_c_label_role(node)
16778 {
16779 return false;
16780 }
16781 let name = node_text(node, source);
16789 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
16790 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
16791 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
16792 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
16793 if is_declaration_name(node)
16794 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
16795 && !recovered_function_call
16796 && !recovered_macro_call
16797 && !recovered_parenthesized_reference
16798 {
16799 return false;
16800 }
16801
16802 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
16803 return true;
16804 }
16805 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
16806 return true;
16807 }
16808 if recovered_parenthesized_reference {
16809 return true;
16810 }
16811 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
16812 if recovered_function_call {
16813 return true;
16814 }
16815 return visibility
16816 .visible_identifier_candidates(file, name)
16817 .any(|candidate| candidate.is_class() || candidate.is_module());
16818 }
16819 let visible = visibility
16820 .visible_identifier_candidates(file, name)
16821 .next()
16822 .is_some();
16823 visible
16824 && (recovered_c_reference_anchor(node)
16825 || recovered_c_error_expression_leaf(node)
16826 || recovered_function_call)
16827}
16828
16829fn push_recovered_c_range(
16830 ranges: &mut Vec<Range>,
16831 seen: &mut HashSet<(usize, usize)>,
16832 start_byte: usize,
16833 end_byte: usize,
16834 node: Node<'_>,
16835 limit: usize,
16836) -> bool {
16837 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
16838 return true;
16839 }
16840 if ranges.len() >= limit {
16841 return false;
16842 }
16843 ranges.push(Range {
16844 start_byte,
16845 end_byte,
16846 start_line: node.start_position().row,
16847 end_line: node.end_position().row,
16848 });
16849 true
16850}
16851
16852fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
16857 let mut current = node.parent();
16858 while let Some(parent) = current {
16859 if parent.is_error() {
16860 let Some(anchor) = parent.parent() else {
16861 return false;
16862 };
16863 return anchor.kind().ends_with("_expression")
16864 || matches!(
16865 anchor.kind(),
16866 "argument_list"
16867 | "return_statement"
16868 | "expression_statement"
16869 | "case_statement"
16870 | "initializer_list"
16871 | "field_designator"
16872 | "enumerator"
16873 );
16874 }
16875 if matches!(
16876 parent.kind(),
16877 "translation_unit" | "function_definition" | "compound_statement"
16878 ) {
16879 return false;
16880 }
16881 current = parent.parent();
16882 }
16883 false
16884}
16885
16886fn recovered_c_function_call(
16893 visibility: &VisibilityIndex<'_>,
16894 file: &ProjectFile,
16895 node: Node<'_>,
16896 name: &str,
16897) -> bool {
16898 if !matches!(
16899 node.kind(),
16900 "identifier" | "field_identifier" | "type_identifier"
16901 ) {
16902 return false;
16903 }
16904 let error_call_prefix = node.parent().is_some_and(|error| {
16909 error.is_error()
16910 && error
16911 .parent()
16912 .is_some_and(|parent| parent.kind() == "compound_statement")
16913 }) && node
16914 .prev_sibling()
16915 .is_none_or(|previous| previous.kind() == ";")
16916 && node.next_sibling().is_some_and(|open| {
16917 open.kind() == "("
16918 && open.next_named_sibling().is_some_and(|argument| {
16919 argument.kind() == "parameter_declaration" && !argument.has_error()
16920 })
16921 });
16922 (error_call_prefix || recovered_c_function_declarator_invocation(node))
16923 && visibility
16924 .visible_identifier_candidates(file, name)
16925 .any(CodeUnit::is_function)
16926}
16927
16928pub fn recovered_c_block_call_envelope(declarator: Node<'_>) -> Option<Node<'_>> {
16936 if declarator.kind() != "function_declarator" || !declarator.has_error() {
16937 return None;
16938 }
16939 let definition = declarator.parent().filter(|parent| {
16940 parent.kind() == "function_definition"
16941 && parent.child_by_field_name("declarator") == Some(declarator)
16942 })?;
16943 if definition.child_by_field_name("type").is_some()
16944 || definition.named_child(0) != Some(declarator)
16945 || definition.child_by_field_name("body").is_none()
16946 || definition.parent()?.kind() != "compound_statement"
16947 {
16948 return None;
16949 }
16950 Some(definition)
16951}
16952
16953fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
16963 let mut current = if node.parent().is_some_and(|parent| {
16968 parent.kind() == "macro_type_specifier" && parent.child_by_field_name("name") == Some(node)
16969 }) {
16970 node.parent().expect("checked macro_type_specifier parent")
16971 } else {
16972 let mut function_declarator = if node.parent().is_some_and(|parent| {
16973 parent.kind() == "function_declarator"
16974 && parent.child_by_field_name("declarator") == Some(node)
16975 }) {
16976 node.parent().expect("checked function declarator parent")
16977 } else {
16978 let Some(parameter) = node.parent().filter(|parent| {
16979 parent.kind() == "parameter_declaration"
16980 && parent.child_by_field_name("type") == Some(node)
16981 }) else {
16982 return false;
16983 };
16984 if !parameter
16985 .child_by_field_name("declarator")
16986 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
16987 {
16988 return false;
16989 }
16990 let Some(parameters) = parameter
16991 .parent()
16992 .filter(|parent| parent.kind() == "parameter_list")
16993 else {
16994 return false;
16995 };
16996 let Some(function_declarator) = parameters
16997 .parent()
16998 .filter(|parent| parent.kind() == "function_declarator")
16999 else {
17000 return false;
17001 };
17002 function_declarator
17003 };
17004
17005 while let Some(parent) = function_declarator.parent().filter(|parent| {
17008 parent.kind() == "function_declarator"
17009 && parent.child_by_field_name("declarator") == Some(function_declarator)
17010 }) {
17011 function_declarator = parent;
17012 }
17013 let Some(current) = function_declarator.parent().filter(|parent| {
17018 parent.is_error() || recovered_c_block_call_envelope(function_declarator).is_some()
17019 }) else {
17020 return false;
17021 };
17022 current
17023 };
17024 loop {
17025 let Some(parent) = current.parent() else {
17026 return false;
17027 };
17028 if matches!(
17029 parent.kind(),
17030 "translation_unit"
17031 | "compound_statement"
17032 | "preproc_if"
17033 | "preproc_ifdef"
17034 | "preproc_ifndef"
17035 | "preproc_else"
17036 | "preproc_elif"
17037 ) {
17038 return true;
17039 }
17040 if parent.kind() == "function_definition"
17041 && parent.child_by_field_name("declarator") == Some(current)
17042 && parent.named_child(0) == Some(current)
17043 && parent.child_by_field_name("body").is_some()
17044 {
17045 return true;
17046 }
17047 if parent.is_error()
17048 || matches!(
17049 parent.kind(),
17050 "parameter_declaration"
17051 | "parameter_list"
17052 | "function_declarator"
17053 | "abstract_function_declarator"
17054 | "parenthesized_declarator"
17055 )
17056 {
17057 current = parent;
17058 continue;
17059 }
17060 return false;
17061 }
17062}
17063
17064fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
17070 if node
17076 .parent()
17077 .is_some_and(|parent| parent.kind() == "type_descriptor")
17078 {
17079 let Some(type_descriptor) = node.parent() else {
17080 return false;
17081 };
17082 let Some(error) = type_descriptor.parent().filter(|parent| parent.is_error()) else {
17083 return false;
17084 };
17085 if error.named_child_count() != 1 || error.named_child(0) != Some(type_descriptor) {
17086 return false;
17087 }
17088 let Some(dependent) = error
17089 .parent()
17090 .and_then(|parent| parent.parent())
17091 .filter(|parent| parent.kind() == "dependent_type")
17092 else {
17093 return false;
17094 };
17095 return dependent
17096 .child(0)
17097 .is_some_and(|keyword| keyword.kind() == "typename");
17098 }
17099 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
17100 return false;
17101 };
17102 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
17103 return false;
17104 }
17105 let Some(declarator) = error
17106 .parent()
17107 .filter(|parent| parent.kind() == "parenthesized_declarator")
17108 else {
17109 return false;
17110 };
17111 let Some(declaration) = declarator
17112 .parent()
17113 .filter(|parent| parent.kind() == "declaration")
17114 else {
17115 return false;
17116 };
17117 if declaration.child_by_field_name("declarator") != Some(declarator) {
17118 return false;
17119 }
17120 let Some(type_node) = declaration.child_by_field_name("type") else {
17121 return false;
17122 };
17123 type_node.kind() == "dependent_type"
17124 && type_node
17125 .child(0)
17126 .is_some_and(|keyword| keyword.kind() == "typename")
17127}
17128
17129fn recovered_c_explicit_assignment_callee(
17130 visibility: &VisibilityIndex<'_>,
17131 file: &ProjectFile,
17132 node: Node<'_>,
17133 name: &str,
17134) -> bool {
17135 let mut current = node;
17136 let error = loop {
17137 let Some(parent) = current.parent() else {
17138 return false;
17139 };
17140 if parent.is_error() {
17141 break parent;
17142 }
17143 current = parent;
17144 };
17145 let mut cursor = error.walk();
17146 let explicit_recovery_precedes_callee = error
17147 .named_children(&mut cursor)
17148 .take_while(|child| child.start_byte() < node.start_byte())
17149 .any(|child| child.kind() == "explicit_function_specifier");
17150 if !explicit_recovery_precedes_callee {
17151 return false;
17152 }
17153 visibility
17154 .visible_identifier_candidates(file, name)
17155 .any(CodeUnit::is_function)
17156}
17157
17158fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
17159 while let Some(parent) = node.parent() {
17160 if matches!(
17161 parent.kind(),
17162 "preproc_def" | "preproc_function_def" | "preproc_params"
17163 ) {
17164 return true;
17165 }
17166 if parent.is_error()
17167 || matches!(
17168 parent.kind(),
17169 "translation_unit" | "function_definition" | "compound_statement"
17170 )
17171 {
17172 return false;
17173 }
17174 node = parent;
17175 }
17176 false
17177}
17178
17179fn recovered_c_label_role(node: Node<'_>) -> bool {
17180 node.parent().is_some_and(|parent| {
17181 matches!(parent.kind(), "labeled_statement" | "goto_statement")
17182 && parent.child_by_field_name("label") == Some(node)
17183 })
17184}
17185
17186fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
17187 while let Some(parent) = node.parent() {
17188 if parent.is_error() {
17189 return false;
17190 }
17191 if parent.kind() == "optional_parameter_declaration"
17196 && parent
17197 .child_by_field_name("default_value")
17198 .is_some_and(|value| node_range_contains(value, node))
17199 {
17200 return true;
17201 }
17202 if parent.kind().ends_with("_expression")
17203 || matches!(
17204 parent.kind(),
17205 "argument_list"
17206 | "return_statement"
17207 | "expression_statement"
17208 | "case_statement"
17209 | "initializer_list"
17210 | "init_declarator"
17211 | "array_declarator"
17212 | "field_designator"
17213 | "enumerator"
17214 )
17215 {
17216 return true;
17217 }
17218 if matches!(
17219 parent.kind(),
17220 "translation_unit"
17221 | "function_definition"
17222 | "compound_statement"
17223 | "declaration"
17224 | "field_declaration"
17225 | "parameter_declaration"
17226 ) {
17227 return false;
17228 }
17229 node = parent;
17230 }
17231 false
17232}
17233
17234pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
17242 let mut current = parameter.parent();
17243 while let Some(ancestor) = current {
17244 if ancestor.kind() == "lambda_expression" {
17245 return ancestor
17246 .child_by_field_name("declarator")
17247 .is_some_and(|declarator| {
17248 declarator.start_byte() <= parameter.start_byte()
17249 && parameter.end_byte() <= declarator.end_byte()
17250 });
17251 }
17252 if ancestor.kind() == "function_definition" {
17253 return ancestor
17254 .child_by_field_name("declarator")
17255 .is_some_and(|declarator| {
17256 declarator.start_byte() <= parameter.start_byte()
17257 && parameter.end_byte() <= declarator.end_byte()
17258 });
17259 }
17260 current = ancestor.parent();
17261 }
17262 false
17263}
17264
17265pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
17266 let mut current = node.parent();
17267 while let Some(ancestor) = current {
17268 if matches!(
17269 ancestor.kind(),
17270 "parameter_declaration" | "optional_parameter_declaration"
17271 ) {
17272 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
17273 }
17274 if matches!(
17275 ancestor.kind(),
17276 "function_definition" | "lambda_expression" | "compound_statement"
17277 ) {
17278 return false;
17279 }
17280 current = ancestor.parent();
17281 }
17282 false
17283}
17284
17285pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
17289 specifier: Node<'_>,
17290) -> impl Iterator<Item = Node<'_>> {
17291 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
17292 !matches!(
17293 ancestor.kind(),
17294 "type_descriptor"
17295 | "parameter_declaration"
17296 | "optional_parameter_declaration"
17297 | "template_argument_list"
17298 | "cast_expression"
17299 )
17300 })
17301}
17302
17303pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
17304 if specifier.child_by_field_name("body").is_some() {
17305 return true;
17306 }
17307 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
17308 match ancestor.kind() {
17309 "declaration" | "field_declaration" => {
17310 let mut cursor = ancestor.walk();
17311 return ancestor
17312 .children_by_field_name("declarator", &mut cursor)
17313 .next()
17314 .is_none();
17315 }
17316 "translation_unit" => return true,
17317 _ => {}
17318 }
17319 }
17320 false
17321}
17322
17323pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
17324 match node.kind() {
17325 "identifier"
17326 | "field_identifier"
17327 | "qualified_identifier"
17328 | "scoped_identifier"
17329 | "operator_name"
17330 | "destructor_name"
17331 | "literal_operator_name" => Some(node),
17332 "reference_declarator" | "parenthesized_declarator" => {
17333 node.named_child(0).and_then(declarator_name_node)
17334 }
17335 _ => node
17336 .child_by_field_name("declarator")
17337 .or_else(|| node.child_by_field_name("name"))
17338 .or_else(|| node.child_by_field_name("field"))
17339 .and_then(declarator_name_node),
17340 }
17341}
17342
17343pub fn declarator_path_contains_before_name(
17345 declarator: Node<'_>,
17346 name: Node<'_>,
17347 candidate: Node<'_>,
17348) -> bool {
17349 let mut current = Some(declarator);
17350 while let Some(node) = current {
17351 if same_node(node, candidate) {
17352 return true;
17353 }
17354 if same_node(node, name) {
17355 return false;
17356 }
17357 current = node
17358 .child_by_field_name("declarator")
17359 .or_else(|| node.child_by_field_name("name"))
17360 .or_else(|| node.child_by_field_name("field"));
17361 }
17362 false
17363}
17364
17365fn declarator_name_path_contains(
17366 declarator: Node<'_>,
17367 candidate: Node<'_>,
17368 allow_type_identifier: bool,
17369) -> bool {
17370 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
17371 return false;
17372 };
17373 declarator_path_contains_before_name(declarator, name, candidate)
17374}
17375
17376fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
17377 match node.kind() {
17378 "identifier"
17379 | "field_identifier"
17380 | "operator_name"
17381 | "destructor_name"
17382 | "literal_operator_name" => Some(node),
17383 "type_identifier" if allow_type_identifier => Some(node),
17384 _ => node
17385 .child_by_field_name("declarator")
17386 .or_else(|| node.child_by_field_name("name"))
17387 .or_else(|| node.child_by_field_name("field"))
17388 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
17389 }
17390}
17391
17392pub fn is_nested_type_node(node: Node<'_>) -> bool {
17395 node.parent().is_some_and(|parent| {
17396 matches!(
17397 parent.kind(),
17398 "qualified_identifier" | "scoped_type_identifier" | "template_type"
17399 )
17400 })
17401}
17402
17403pub struct OutOfLineMemberDefinitionOwners<'tree> {
17404 pub owners: Vec<(Node<'tree>, CodeUnit)>,
17405 innermost: Option<(Node<'tree>, CodeUnit)>,
17406}
17407
17408impl OutOfLineMemberDefinitionOwners<'_> {
17409 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
17410 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
17411 }
17412}
17413
17414pub struct QualifiedOwnerComponents<'tree> {
17415 pub nodes: Vec<Node<'tree>>,
17416 pub names: Vec<String>,
17417 pub global: bool,
17418}
17419
17420pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
17425 let mut stack = vec![node];
17426 let mut found_separator = false;
17427 while let Some(current) = stack.pop() {
17428 if !matches!(
17429 current.kind(),
17430 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17431 ) {
17432 continue;
17433 }
17434 let mut current_has_separator = false;
17435 for child in children_iter(current) {
17436 if child.kind() == "::" {
17437 if child.is_missing() {
17438 return false;
17439 }
17440 current_has_separator = true;
17441 found_separator = true;
17442 }
17443 }
17444 if !current_has_separator {
17445 return false;
17446 }
17447 for field in ["scope", "name"] {
17448 if let Some(child) = current.child_by_field_name(field)
17449 && matches!(
17450 child.kind(),
17451 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17452 )
17453 {
17454 stack.push(child);
17455 }
17456 }
17457 }
17458 found_separator
17459}
17460
17461pub fn qualified_owner_components<'tree>(
17462 node: Node<'tree>,
17463 source: &str,
17464) -> Option<QualifiedOwnerComponents<'tree>> {
17465 if !qualified_name_has_concrete_scope_separators(node) {
17466 return None;
17467 }
17468 let mut nodes = cpp_name_component_nodes(node)?;
17469 nodes.pop()?;
17470 if nodes.is_empty() {
17471 return None;
17472 }
17473 let names = nodes
17474 .iter()
17475 .map(|component| node_text(*component, source).to_string())
17476 .collect();
17477 Some(QualifiedOwnerComponents {
17478 nodes,
17479 names,
17480 global: is_globally_qualified_cpp_name(node),
17481 })
17482}
17483
17484pub fn out_of_line_member_definition_owner<'tree>(
17485 analyzer: &CppGraphSource<'_>,
17486 visibility: &VisibilityIndex<'_>,
17487 file: &ProjectFile,
17488 source: &str,
17489 node: Node<'tree>,
17490) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
17491 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
17492 || !has_ancestor_kind(node, "function_definition")
17493 || !is_function_declarator_name_root(node)
17494 {
17495 return None;
17496 }
17497 let qualified = qualified_owner_components(node, source)?;
17498 let lexical_scope = enclosing_namespace_components(node, source)?;
17499 let mut owners = Vec::new();
17500 let mut innermost = None;
17501
17502 for component_count in 1..=qualified.names.len() {
17503 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
17504 .resolve_type_components_lexically(
17505 analyzer,
17506 file,
17507 &qualified.names[..component_count],
17508 qualified.global,
17509 &lexical_scope,
17510 )
17511 && !owners
17512 .iter()
17513 .any(|(_, existing)| same_visible_symbol(existing, &unit))
17514 {
17515 if component_count == qualified.names.len() {
17516 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
17517 }
17518 owners.push((qualified.nodes[component_count - 1], unit));
17519 }
17520 }
17521
17522 if innermost.is_none() {
17532 let indexed_owner_components = visibility
17533 .indexed_enclosing_owner_scope(analyzer, file, node)
17534 .or_else(|| {
17535 if qualified.names.len() <= 1 {
17540 return None;
17541 }
17542 let range = Range {
17543 start_byte: node.start_byte(),
17544 end_byte: node.end_byte(),
17545 start_line: node.start_position().row,
17546 end_line: node.end_position().row,
17547 };
17548 let start = analyzer.enclosing_code_unit(file, &range)?;
17549 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17550 brokk_bifrost_core::analyzer::Language::Cpp,
17551 &cpp_name_for(&start),
17552 );
17553 components.pop();
17554 Some(components)
17555 });
17556 if let Some(indexed_owner_components) = indexed_owner_components
17557 && indexed_owner_components.len() > qualified.names.len()
17558 && indexed_owner_components.ends_with(&qualified.names)
17559 && indexed_namespace_path_is_recoverable(
17560 &lexical_scope,
17561 &indexed_owner_components,
17562 qualified.names.len(),
17563 )
17564 && (qualified.names.len() > 1 || !qualified.global)
17569 {
17570 let namespace_count = indexed_owner_components.len() - qualified.names.len();
17571 for component_count in 1..=qualified.names.len() {
17572 let expected = &indexed_owner_components[..namespace_count + component_count];
17573 let owner_node = qualified.nodes[component_count - 1];
17574 for owner in visibility
17575 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
17576 .filter(|candidate| candidate.is_class())
17577 .filter(|candidate| {
17578 canonical_cpp_scope_components(candidate) == expected
17579 && visibility.external_type_candidate_visible_in_context(
17580 analyzer, file, candidate, node,
17581 )
17582 })
17583 {
17584 if component_count == qualified.names.len() && innermost.is_none() {
17585 innermost = Some((owner_node, owner.clone()));
17586 }
17587 if !owners
17588 .iter()
17589 .any(|(_, existing)| same_symbol(existing, owner))
17590 {
17591 owners.push((owner_node, owner.clone()));
17592 }
17593 }
17594 }
17595 }
17596 }
17597 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
17598}
17599
17600fn is_function_declarator_name_root(node: Node<'_>) -> bool {
17601 let mut current = node;
17602 while let Some(parent) = current.parent() {
17603 if parent.kind() == "function_declarator" {
17604 return parent.child_by_field_name("declarator") == Some(current);
17605 }
17606 if matches!(
17607 parent.kind(),
17608 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
17609 ) && parent.child_by_field_name("declarator") == Some(current)
17610 {
17611 current = parent;
17612 continue;
17613 }
17614 return false;
17615 }
17616 false
17617}
17618
17619pub fn append_cpp_name_components(
17620 node: Node<'_>,
17621 source: &str,
17622 out: &mut Vec<String>,
17623) -> Option<()> {
17624 out.extend(
17625 cpp_name_component_nodes(node)?
17626 .into_iter()
17627 .map(|component| node_text(component, source).to_string()),
17628 );
17629 Some(())
17630}
17631
17632fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
17635 cpp_name_component_nodes(node).map(|components| {
17636 components
17637 .into_iter()
17638 .map(|component| {
17639 (
17640 node_text(component, source).to_string(),
17641 node_range(component),
17642 )
17643 })
17644 .collect()
17645 })
17646}
17647
17648pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17649 let mut components = Vec::new();
17650 append_cpp_name_components(node, source, &mut components)?;
17651 Some(components)
17652}
17653
17654pub fn unique_macro_replacement_type_candidate(
17663 analyzer: &CppGraphSource<'_>,
17664 visibility: &VisibilityIndex<'_>,
17665 file: &ProjectFile,
17666 components: &[String],
17667) -> Option<CodeUnit> {
17668 let terminal = components.last()?;
17669 let mut candidates = Vec::new();
17670 for candidate in visibility
17671 .visible_identifier_candidates(file, terminal)
17672 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
17673 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
17674 {
17675 if !candidates
17676 .iter()
17677 .any(|existing| same_logical_symbol(existing, candidate))
17678 {
17679 candidates.push(candidate.clone());
17680 }
17681 }
17682 (candidates.len() == 1).then(|| candidates.remove(0))
17683}
17684
17685pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
17689 qualified == scope
17690 || qualified
17691 .strip_suffix(scope)
17692 .is_some_and(|prefix| prefix.ends_with("::"))
17693}
17694
17695pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
17700 let Some(type_descriptor) = node.parent() else {
17701 return false;
17702 };
17703 if type_descriptor.kind() != "type_descriptor"
17704 || type_descriptor.child_by_field_name("type") != Some(node)
17705 {
17706 return false;
17707 }
17708 let Some(arguments) = type_descriptor.parent() else {
17709 return false;
17710 };
17711 if arguments.kind() != "template_argument_list" {
17712 return false;
17713 }
17714 arguments.parent().is_some_and(|parent| {
17715 matches!(parent.kind(), "template_type" | "template_function")
17716 && parent.child_by_field_name("arguments") == Some(arguments)
17717 })
17718}
17719
17720pub fn cpp_template_reference_arguments(
17721 mut node: Node<'_>,
17722 source: &str,
17723) -> Option<Vec<CppTemplateExpression>> {
17724 loop {
17725 match node.kind() {
17726 "template_type" | "template_function" => {
17727 let arguments = node.child_by_field_name("arguments")?;
17728 let mut cursor = arguments.walk();
17729 return Some(
17730 arguments
17731 .named_children(&mut cursor)
17732 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
17733 .map(|argument| CppTemplateExpression {
17734 text: normalize_cpp_whitespace(node_text(argument, source)),
17735 term: cpp_template_term(
17737 argument,
17738 source,
17739 &[],
17740 &ParentIndex::unindexed(),
17741 ),
17742 })
17743 .collect(),
17744 );
17745 }
17746 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
17747 node = node
17748 .child_by_field_name("name")
17749 .or_else(|| node.child_by_field_name("type"))?;
17750 }
17751 _ => return None,
17752 }
17753 }
17754}
17755
17756fn cpp_reconcile_primary_template_parameters(
17757 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
17758 preferred: &CodeUnit,
17759) -> Option<Vec<CppTemplateParameterMetadata>> {
17760 let canonical = candidates
17761 .iter()
17762 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
17763 let mut merged = canonical
17764 .parameters
17765 .iter()
17766 .map(|parameter| CppTemplateParameterMetadata {
17767 name: parameter.name.clone(),
17768 kind: parameter.kind,
17769 variadic: parameter.variadic,
17770 default: None,
17771 })
17772 .collect::<Vec<_>>();
17773
17774 for (_, metadata) in candidates {
17775 if metadata.parameters.len() != merged.len() {
17776 return None;
17777 }
17778 let rename_bindings = metadata
17779 .parameters
17780 .iter()
17781 .zip(&merged)
17782 .map(|(parameter, canonical)| {
17783 (
17784 parameter.name.clone(),
17785 CppTemplateTerm::Parameter(canonical.name.clone()),
17786 )
17787 })
17788 .collect::<HashMap<_, _>>();
17789 for ((parameter, canonical), merged_parameter) in metadata
17790 .parameters
17791 .iter()
17792 .zip(&canonical.parameters)
17793 .zip(&mut merged)
17794 {
17795 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
17796 return None;
17797 }
17798 let Some(default) = ¶meter.default else {
17799 continue;
17800 };
17801 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
17802 if let Some(existing) = &merged_parameter.default {
17803 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
17804 return None;
17805 }
17806 } else {
17807 merged_parameter.default = Some(CppTemplateExpression {
17808 text: default.text.clone(),
17809 term: normalized_term,
17810 });
17811 }
17812 }
17813 }
17814 Some(merged)
17815}
17816
17817pub fn cpp_bind_template_arguments(
17818 parameters: &[CppTemplateParameterMetadata],
17819 explicit_arguments: &[CppTemplateExpression],
17820) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
17821 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
17822 if variadic_index.is_some_and(|index| {
17823 index + 1 != parameters.len()
17824 || parameters[index + 1..]
17825 .iter()
17826 .any(|parameter| parameter.variadic)
17827 }) {
17828 return None;
17829 }
17830 let fixed_count = variadic_index.unwrap_or(parameters.len());
17831 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
17832 return None;
17833 }
17834 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
17835 let mut expanded = explicit_arguments[..explicit_fixed_count]
17836 .iter()
17837 .map(cpp_clone_template_expression_iterative)
17838 .collect::<Vec<_>>();
17839 let mut bindings = HashMap::default();
17840 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
17841 bindings.insert(
17842 parameter.name.clone(),
17843 cpp_clone_template_term_iterative(&argument.term),
17844 );
17845 }
17846 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
17847 let default = parameter.default.as_ref()?;
17848 let term = cpp_substitute_template_term(&default.term, &bindings)?;
17849 bindings.insert(parameter.name.clone(), term.clone());
17850 expanded.push(CppTemplateExpression {
17851 text: default.text.clone(),
17852 term,
17853 });
17854 }
17855 if let Some(index) = variadic_index {
17856 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
17857 expanded.extend(
17858 packed_arguments
17859 .iter()
17860 .map(cpp_clone_template_expression_iterative),
17861 );
17862 bindings.insert(
17863 parameters[index].name.clone(),
17864 CppTemplateTerm::Node {
17865 kind: "parameter_pack".to_string(),
17866 children: packed_arguments
17867 .iter()
17868 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
17869 .collect(),
17870 },
17871 );
17872 }
17873 Some((expanded, bindings))
17874}
17875
17876fn cpp_specialization_matches(
17877 metadata: &CppTemplateMetadata,
17878 arguments: &[CppTemplateExpression],
17879) -> bool {
17880 cpp_specialization_terms_match(
17881 &metadata.parameters,
17882 &metadata.specialization_arguments,
17883 arguments,
17884 )
17885}
17886
17887fn cpp_specialization_terms_match(
17888 parameters: &[CppTemplateParameterMetadata],
17889 patterns: &[CppTemplateExpression],
17890 arguments: &[CppTemplateExpression],
17891) -> bool {
17892 if patterns.len() != arguments.len() {
17893 return false;
17894 }
17895 let parameter_names = parameters
17896 .iter()
17897 .map(|parameter| parameter.name.as_str())
17898 .collect::<HashSet<_>>();
17899 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
17900 patterns.iter().zip(arguments).all(|(pattern, argument)| {
17901 cpp_unify_template_term(
17902 &pattern.term,
17903 &argument.term,
17904 ¶meter_names,
17905 &mut bindings,
17906 )
17907 })
17908}
17909
17910fn cpp_specialization_more_specialized(
17911 candidate: &CppTemplateMetadata,
17912 other: &CppTemplateMetadata,
17913) -> bool {
17914 cpp_specialization_pattern_accepts(other, candidate)
17915 && !cpp_specialization_pattern_accepts(candidate, other)
17916}
17917
17918fn cpp_specialization_pattern_accepts(
17919 broader: &CppTemplateMetadata,
17920 narrower: &CppTemplateMetadata,
17921) -> bool {
17922 cpp_specialization_terms_match(
17923 &broader.parameters,
17924 &broader.specialization_arguments,
17925 &narrower.specialization_arguments,
17926 )
17927}
17928
17929pub fn cpp_substitute_template_term(
17930 term: &CppTemplateTerm,
17931 bindings: &HashMap<String, CppTemplateTerm>,
17932) -> Option<CppTemplateTerm> {
17933 enum Work<'a> {
17934 Visit(&'a CppTemplateTerm),
17935 Build { kind: String, child_count: usize },
17936 }
17937
17938 let mut work = vec![Work::Visit(term)];
17939 let mut substituted = Vec::new();
17940 while let Some(next) = work.pop() {
17941 match next {
17942 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17943 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
17944 }
17945 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17946 substituted.push(CppTemplateTerm::Atom {
17947 kind: kind.clone(),
17948 text: text.clone(),
17949 });
17950 }
17951 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17952 work.push(Work::Build {
17953 kind: kind.clone(),
17954 child_count: children.len(),
17955 });
17956 work.extend(children.iter().rev().map(Work::Visit));
17957 }
17958 Work::Build { kind, child_count } => {
17959 let children = substituted.split_off(substituted.len() - child_count);
17960 substituted.push(CppTemplateTerm::Node { kind, children });
17961 }
17962 }
17963 }
17964 substituted.pop()
17965}
17966
17967pub fn cpp_substitute_template_arguments(
17968 arguments: &[CppTemplateExpression],
17969 bindings: &HashMap<String, CppTemplateTerm>,
17970) -> Option<Vec<CppTemplateExpression>> {
17971 let mut substituted = Vec::new();
17972 for argument in arguments {
17973 let CppTemplateTerm::Node { kind, children } = &argument.term else {
17974 substituted.push(CppTemplateExpression {
17975 text: argument.text.clone(),
17976 term: cpp_substitute_template_term(&argument.term, bindings)?,
17977 });
17978 continue;
17979 };
17980 if kind != "parameter_pack_expansion" {
17981 substituted.push(CppTemplateExpression {
17982 text: argument.text.clone(),
17983 term: cpp_substitute_template_term(&argument.term, bindings)?,
17984 });
17985 continue;
17986 }
17987 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
17988 return None;
17989 };
17990 if ellipsis != "..." {
17991 return None;
17992 }
17993
17994 let mut pack_names = Vec::new();
17995 let mut work = vec![pattern];
17996 while let Some(term) = work.pop() {
17997 match term {
17998 CppTemplateTerm::Parameter(name)
17999 if matches!(
18000 bindings.get(name),
18001 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
18002 ) =>
18003 {
18004 if !pack_names.contains(name) {
18005 pack_names.push(name.clone());
18006 }
18007 }
18008 CppTemplateTerm::Node { children, .. } => work.extend(children),
18009 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
18010 }
18011 }
18012 let first_pack = pack_names.first()?;
18013 let CppTemplateTerm::Node {
18014 children: first_elements,
18015 ..
18016 } = bindings.get(first_pack)?
18017 else {
18018 return None;
18019 };
18020 let pack_len = first_elements.len();
18021 for pack_name in &pack_names {
18022 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18023 return None;
18024 };
18025 if children.len() != pack_len {
18026 return None;
18027 }
18028 }
18029 for index in 0..pack_len {
18030 let mut element_bindings = bindings.clone();
18031 for pack_name in &pack_names {
18032 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
18033 return None;
18034 };
18035 element_bindings.insert(
18036 pack_name.clone(),
18037 cpp_clone_template_term_iterative(&children[index]),
18038 );
18039 }
18040 substituted.push(CppTemplateExpression {
18041 text: argument.text.clone(),
18042 term: cpp_substitute_template_term(pattern, &element_bindings)?,
18043 });
18044 }
18045 }
18046 Some(substituted)
18047}
18048
18049fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
18050 enum Work<'a> {
18051 Visit(&'a CppTemplateTerm),
18052 Build { kind: String, child_count: usize },
18053 }
18054
18055 let mut work = vec![Work::Visit(term)];
18056 let mut cloned = Vec::new();
18057 while let Some(next) = work.pop() {
18058 match next {
18059 Work::Visit(CppTemplateTerm::Parameter(name)) => {
18060 cloned.push(CppTemplateTerm::Parameter(name.clone()));
18061 }
18062 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
18063 cloned.push(CppTemplateTerm::Atom {
18064 kind: kind.clone(),
18065 text: text.clone(),
18066 });
18067 }
18068 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
18069 work.push(Work::Build {
18070 kind: kind.clone(),
18071 child_count: children.len(),
18072 });
18073 work.extend(children.iter().rev().map(Work::Visit));
18074 }
18075 Work::Build { kind, child_count } => {
18076 let children = cloned.split_off(cloned.len() - child_count);
18077 cloned.push(CppTemplateTerm::Node { kind, children });
18078 }
18079 }
18080 }
18081 cloned
18082 .pop()
18083 .expect("template term traversal emits one root")
18084}
18085
18086fn cpp_clone_template_expression_iterative(
18087 expression: &CppTemplateExpression,
18088) -> CppTemplateExpression {
18089 CppTemplateExpression {
18090 text: expression.text.clone(),
18091 term: cpp_clone_template_term_iterative(&expression.term),
18092 }
18093}
18094
18095pub fn cpp_unify_template_term(
18096 pattern: &CppTemplateTerm,
18097 argument: &CppTemplateTerm,
18098 parameters: &HashSet<&str>,
18099 bindings: &mut HashMap<String, CppTemplateTerm>,
18100) -> bool {
18101 let mut work = vec![(pattern, argument)];
18102 while let Some((pattern, argument)) = work.pop() {
18103 match pattern {
18104 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
18105 if let Some(bound) = bindings.get(name) {
18106 if !cpp_template_terms_equal(bound, argument) {
18107 return false;
18108 }
18109 } else {
18110 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
18111 }
18112 }
18113 CppTemplateTerm::Atom {
18114 kind: pattern_kind,
18115 text: pattern_text,
18116 } => {
18117 if !matches!(
18118 argument,
18119 CppTemplateTerm::Atom { kind, text }
18120 if kind == pattern_kind && text == pattern_text
18121 ) {
18122 return false;
18123 }
18124 }
18125 CppTemplateTerm::Node {
18126 kind: pattern_kind,
18127 children: pattern_children,
18128 } => {
18129 let CppTemplateTerm::Node { kind, children } = argument else {
18130 return false;
18131 };
18132 if kind != pattern_kind || children.len() != pattern_children.len() {
18133 return false;
18134 }
18135 work.extend(pattern_children.iter().zip(children).rev());
18136 }
18137 CppTemplateTerm::Parameter(_) => return false,
18138 }
18139 }
18140 true
18141}
18142
18143pub(crate) fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
18144 let mut work = vec![(left, right)];
18145 while let Some((left, right)) = work.pop() {
18146 match (left, right) {
18147 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
18148 if left != right {
18149 return false;
18150 }
18151 }
18152 (
18153 CppTemplateTerm::Atom {
18154 kind: left_kind,
18155 text: left_text,
18156 },
18157 CppTemplateTerm::Atom {
18158 kind: right_kind,
18159 text: right_text,
18160 },
18161 ) => {
18162 if left_kind != right_kind || left_text != right_text {
18163 return false;
18164 }
18165 }
18166 (
18167 CppTemplateTerm::Node {
18168 kind: left_kind,
18169 children: left_children,
18170 },
18171 CppTemplateTerm::Node {
18172 kind: right_kind,
18173 children: right_children,
18174 },
18175 ) => {
18176 if left_kind != right_kind || left_children.len() != right_children.len() {
18177 return false;
18178 }
18179 work.extend(left_children.iter().zip(right_children).rev());
18180 }
18181 _ => return false,
18182 }
18183 }
18184 true
18185}
18186
18187pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
18188 let mut components = Vec::new();
18189 let mut stack = vec![node];
18190 while let Some(current) = stack.pop() {
18191 match current.kind() {
18192 "identifier"
18193 | "field_identifier"
18194 | "namespace_identifier"
18195 | "type_identifier"
18196 | "operator_name"
18197 | "destructor_name" => components.push(current),
18198 "template_type" | "template_function" => {
18199 stack.push(current.child_by_field_name("name")?);
18200 }
18201 "dependent_name" => stack.push(current.named_child(0)?),
18202 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
18203 stack.push(current.child_by_field_name("name")?);
18204 if let Some(scope) = current.child_by_field_name("scope") {
18205 stack.push(scope);
18206 }
18207 }
18208 "nested_namespace_specifier" => {
18209 for index in (0..current.named_child_count()).rev() {
18210 stack.push(current.named_child(index)?);
18211 }
18212 }
18213 _ => return None,
18214 }
18215 }
18216 Some(components)
18217}
18218
18219pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
18220 node.child_by_field_name("scope").is_none()
18221 && node.child(0).is_some_and(|child| child.kind() == "::")
18222}
18223
18224fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
18225 let mut namespaces = Vec::new();
18226 for parent in enclosing_cpp_namespace_nodes(node) {
18227 if let Some(name) = parent.child_by_field_name("name") {
18228 let mut components = Vec::new();
18229 append_cpp_name_components(name, source, &mut components)?;
18230 namespaces.push(components);
18231 }
18232 }
18233 Some(namespaces.into_iter().flatten().collect())
18234}
18235
18236fn indexed_namespace_path_is_recoverable(
18247 lexical_scope: &[String],
18248 indexed_owner_scope: &[String],
18249 explicit_owner_component_count: usize,
18250) -> bool {
18251 if lexical_scope.is_empty() {
18252 return explicit_owner_component_count > 1;
18253 }
18254 if lexical_scope.len() >= indexed_owner_scope.len() {
18255 return false;
18256 }
18257 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
18258}
18259
18260pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
18261
18262pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
18268 let mut current = Some(node);
18269 while let Some(candidate) = current {
18270 if candidate.kind() == "declaration" {
18271 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
18272 return false;
18273 }
18274 let mut cursor = candidate.walk();
18275 return candidate.named_children(&mut cursor).any(|child| {
18276 child.kind() == "init_declarator"
18277 && child
18278 .child_by_field_name("value")
18279 .is_some_and(|value| value.kind() == "cast_expression")
18280 });
18281 }
18282 current = candidate.parent();
18283 }
18284 false
18285}
18286
18287#[derive(Clone, Copy, PartialEq, Eq)]
18288pub(crate) enum QualifiedAliasReferenceKind {
18289 Ordinary,
18290 ConstructorWithExpressionArgument,
18291 ExhaustiveTemplate,
18292}
18293
18294pub(crate) fn qualified_alias_reference_preserves_target(
18301 node: Node<'_>,
18302 target: &CodeUnit,
18303 analyzer: &CppGraphSource<'_>,
18304 visibility: &VisibilityIndex<'_>,
18305 file: &ProjectFile,
18306 source: &str,
18307) -> Option<QualifiedAliasReferenceKind> {
18308 if !matches!(
18309 node.kind(),
18310 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18311 ) {
18312 return None;
18313 }
18314 let components = cpp_type_name_components(node, source)?;
18315 let name = components.last()?;
18316 analyzer.type_alias_provider().and_then(|provider| {
18317 visibility
18318 .visible_identifier_candidates(file, name)
18319 .find_map(|candidate| {
18320 let proof = provider.is_type_alias(candidate)
18321 && canonical_cpp_scope_components(candidate) == components
18322 && visibility.external_type_candidate_visible_in_context(
18323 analyzer, file, candidate, node,
18324 )
18325 && match cpp_template_reference_arguments(node, source) {
18326 Some(arguments) => visibility.template_alias_arguments_preserve_target(
18327 analyzer, file, candidate, &arguments, target,
18328 ),
18329 None => visibility.structured_alias_primary_preserves_target(
18330 analyzer, file, candidate, target,
18331 ),
18332 };
18333 proof.then(|| {
18334 if cpp_template_reference_arguments(node, source).is_some()
18335 && visibility.is_exhaustive_same_fqn_type_declaration_family(
18336 analyzer, file, candidate,
18337 )
18338 {
18339 QualifiedAliasReferenceKind::ExhaustiveTemplate
18340 } else if qualified_alias_constructor_has_expression_argument(node)
18341 || qualified_alias_local_constructor_declaration(node)
18342 {
18343 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18344 } else {
18345 QualifiedAliasReferenceKind::Ordinary
18346 }
18347 })
18348 })
18349 })
18350}
18351
18352pub(crate) fn qualified_alias_reference_requires_terminal(
18353 reference: Option<QualifiedAliasReferenceKind>,
18354) -> bool {
18355 matches!(
18356 reference,
18357 Some(
18358 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
18359 | QualifiedAliasReferenceKind::ExhaustiveTemplate
18360 )
18361 )
18362}
18363
18364fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
18365 let Some(declaration) = node.parent().filter(|parent| {
18366 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18367 }) else {
18368 return false;
18369 };
18370 let mut cursor = declaration.walk();
18371 declaration.named_children(&mut cursor).any(|child| {
18372 child.kind() == "init_declarator"
18373 && child
18374 .child_by_field_name("value")
18375 .filter(|value| value.kind() == "argument_list")
18376 .is_some_and(|arguments| {
18377 let mut cursor = arguments.walk();
18378 arguments.named_children(&mut cursor).any(|argument| {
18379 let is_parameter = matches!(
18380 argument.kind(),
18381 "parameter_declaration" | "optional_parameter_declaration"
18382 );
18383 if is_parameter {
18384 argument
18385 .child_by_field_name("type")
18386 .is_some_and(|type_node| {
18387 type_node.kind() == "type_identifier"
18388 && argument.child_by_field_name("declarator").is_none()
18389 })
18390 } else {
18391 !argument.kind().ends_with("_literal")
18392 && !matches!(argument.kind(), "true" | "false" | "nullptr")
18393 }
18394 })
18395 })
18396 })
18397}
18398
18399fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
18404 let Some(declaration) = node.parent().filter(|parent| {
18405 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
18406 }) else {
18407 return false;
18408 };
18409 if declaration
18410 .parent()
18411 .is_none_or(|parent| parent.kind() != "compound_statement")
18412 {
18413 return false;
18414 }
18415 let mut cursor = declaration.walk();
18416 declaration
18417 .named_children(&mut cursor)
18418 .any(|child| child.kind() == "function_declarator")
18419}
18420
18421pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
18427 loop {
18428 let next = match node.kind() {
18429 "qualified_identifier"
18430 | "scoped_identifier"
18431 | "template_method"
18432 | "template_function"
18433 | "template_type" => node.child_by_field_name("name"),
18434 "field_expression" => node.child_by_field_name("field"),
18435 _ => None,
18436 };
18437 let Some(next) = next else {
18438 return node;
18439 };
18440 node = next;
18441 }
18442}
18443
18444#[derive(Clone, Copy)]
18445pub struct RecoveredRelationalTemplateMemberCall<'tree> {
18446 pub receiver: Node<'tree>,
18447 pub member: Node<'tree>,
18448 pub arity: usize,
18449}
18450
18451pub fn recovered_relational_template_member_call(
18459 field: Node<'_>,
18460) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
18461 if field.kind() != "field_expression" {
18462 return None;
18463 }
18464 let receiver = field
18465 .child_by_field_name("argument")
18466 .or_else(|| field.child_by_field_name("object"))?;
18467 let member = field.child_by_field_name("field")?;
18468 let less = field.parent()?;
18469 if less.kind() != "binary_expression"
18470 || less.child_by_field_name("left") != Some(field)
18471 || less
18472 .child_by_field_name("operator")
18473 .is_none_or(|operator| operator.kind() != "<")
18474 || less.child_by_field_name("right").is_none()
18475 {
18476 return None;
18477 }
18478 let greater = less.parent()?;
18479 if greater.kind() != "binary_expression"
18480 || greater.child_by_field_name("left") != Some(less)
18481 || greater
18482 .child_by_field_name("operator")
18483 .is_none_or(|operator| operator.kind() != ">")
18484 {
18485 return None;
18486 }
18487 let arguments = greater.child_by_field_name("right")?;
18488 if arguments.kind() != "parenthesized_expression" {
18489 return None;
18490 }
18491 let arity = parenthesized_call_argument_arity(arguments)?;
18492 Some(RecoveredRelationalTemplateMemberCall {
18493 receiver,
18494 member,
18495 arity,
18496 })
18497}
18498
18499fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
18500 let expression = arguments.named_child(0)?;
18501 if expression.kind() != "comma_expression" {
18502 return Some(1);
18503 }
18504 let mut arity = 0usize;
18505 let mut stack = vec![expression];
18506 while let Some(node) = stack.pop() {
18507 if node.kind() == "comma_expression" {
18508 stack.push(node.child_by_field_name("right")?);
18509 stack.push(node.child_by_field_name("left")?);
18510 } else {
18511 arity += 1;
18512 }
18513 }
18514 Some(arity)
18515}
18516
18517pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
18520 while let Some(parent) = node.parent() {
18521 match parent.kind() {
18522 "call_expression" => {
18523 return parent
18524 .child_by_field_name("function")
18525 .or_else(|| parent.named_child(0))
18526 == Some(node);
18527 }
18528 "qualified_identifier"
18529 | "scoped_identifier"
18530 | "template_function"
18531 | "template_type"
18532 | "field_expression" => node = parent,
18533 _ => return false,
18534 }
18535 }
18536 false
18537}
18538
18539pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
18543 loop {
18544 let Some(parent) = node.parent() else {
18545 return node;
18546 };
18547 if parent.kind() == "template_type"
18548 && parent
18549 .child_by_field_name("name")
18550 .is_some_and(|name| same_node(name, node))
18551 {
18552 node = parent;
18553 continue;
18554 }
18555 if matches!(
18556 parent.kind(),
18557 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
18558 ) && (parent
18559 .child_by_field_name("scope")
18560 .is_some_and(|scope| same_node(scope, node))
18561 || parent
18562 .child_by_field_name("name")
18563 .is_some_and(|name| same_node(name, node)))
18564 && parent.end_byte() == node.end_byte()
18565 {
18566 node = parent;
18567 continue;
18568 }
18569 return node;
18570 }
18571}
18572
18573pub fn normalize_type_text(value: &str) -> String {
18574 strip_tag_type_prefix(
18575 normalize_cpp_whitespace(value)
18576 .trim_start_matches("const ")
18577 .trim_end_matches('*')
18578 .trim_end_matches('&')
18579 .trim(),
18580 )
18581 .to_string()
18582}
18583
18584use crate::declarations::strip_tag_type_prefix;
18585
18586pub fn normalize_reference_name(value: &str) -> Option<String> {
18587 let normalized = normalize_cpp_reference_text(value);
18588 (!normalized.is_empty()).then_some(normalized)
18589}
18590
18591pub fn normalize_cpp_reference_text(value: &str) -> String {
18592 let mut text = normalize_cpp_whitespace(value)
18593 .trim_start_matches("new ")
18594 .trim()
18595 .to_string();
18596 if let Some(index) = text.find(['(', '{']) {
18597 text.truncate(index);
18598 }
18599 if let Some(index) = text.find('<') {
18600 text.truncate(index);
18601 }
18602 let normalized = text
18603 .trim()
18604 .trim_start_matches("const ")
18605 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
18606 .trim_matches(':')
18607 .trim();
18608 strip_tag_type_prefix(normalized).to_string()
18609}
18610
18611pub fn cpp_name_for(unit: &CodeUnit) -> String {
18612 let short = unit.short_name().replace(['.', '$'], "::");
18613 if unit.package_name().is_empty() {
18614 short
18615 } else {
18616 format!("{}::{}", unit.package_name(), short)
18617 }
18618}
18619
18620fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
18624 let fq = unit.fq();
18625 if fq.is_empty() {
18626 return None;
18627 }
18628 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18629 Some(
18630 fq.segments()
18631 .iter()
18632 .map(|&segment| interner.resolve(segment).0)
18633 .collect::<Vec<_>>()
18634 .join("::"),
18635 )
18636}
18637
18638fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
18639 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
18640 || unit.fq().is_empty() && cpp_name_for(unit) == expected
18641}
18642
18643pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
18656 let fq = unit.fq();
18657 if !fq.is_empty() {
18658 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
18659 let segments = fq.segments();
18660 let scope = segments
18661 .iter()
18662 .enumerate()
18663 .filter_map(|(index, &segment)| {
18664 let (text, kind) = interner.resolve(segment);
18665 match kind {
18666 SegmentKind::Package | SegmentKind::Type | SegmentKind::Nested => true,
18667 SegmentKind::Member => index + 1 < segments.len(),
18668 SegmentKind::Path | SegmentKind::Companion | SegmentKind::Unknown => false,
18669 }
18670 .then(|| text.to_string())
18671 })
18672 .collect();
18673 return scope;
18674 }
18675 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18676 brokk_bifrost_core::analyzer::Language::Cpp,
18677 &cpp_name_for(unit),
18678 )
18679}
18680
18681pub fn terminal_name(value: &str) -> &str {
18692 value
18693 .rsplit("::")
18694 .next()
18695 .unwrap_or(value)
18696 .rsplit(['.', '-', '>'])
18697 .next()
18698 .unwrap_or(value)
18699 .trim()
18700}
18701
18702pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
18703 terminal_name(&normalize_cpp_reference_text(value)) == expected
18704}
18705
18706pub fn name_matches_callable(value: &str, expected: &str) -> bool {
18707 name_matches_terminal(value, expected)
18708 || expected.starts_with("operator")
18709 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
18710}
18711
18712pub fn name_mentions(value: &str, expected: &str) -> bool {
18713 normalize_cpp_reference_text(value)
18714 .split("::")
18715 .any(|part| part == expected)
18716}
18717
18718pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
18719 let cpp_name = cpp_name_for(unit);
18720 if reference.contains("::") {
18721 return reference == cpp_name;
18722 }
18723 reference == cpp_name
18724 || terminal_name(reference) == unit.identifier()
18725 && (unit.package_name().is_empty() || reference == unit.identifier())
18726}
18727
18728pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
18729 match kind {
18730 TargetKind::Type
18731 | TargetKind::Constructor
18732 | TargetKind::Method
18733 | TargetKind::MemberField => true,
18734 TargetKind::FreeFunction => unit.is_function(),
18735 TargetKind::GlobalField => unit.is_field(),
18736 TargetKind::Macro => unit.is_macro(),
18737 }
18738}
18739
18740fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
18741 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
18742 let target_name = cpp_name_for(target);
18743 if normalized.contains("::") {
18744 return normalized == target_name;
18745 }
18746 if let Some(namespace) = alias.namespace.as_deref() {
18747 return namespace_prefixes(namespace)
18748 .into_iter()
18749 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
18750 }
18751 target.package_name().is_empty() && normalized == target.identifier()
18752}
18753
18754pub fn cpp_function_return_type_text(
18757 analyzer: &CppGraphSource<'_>,
18758 function: &CodeUnit,
18759) -> Option<String> {
18760 let metadata = analyzer.signature_metadata(function);
18761 let first = metadata.first()?.return_type_text()?;
18762 metadata
18763 .iter()
18764 .all(|metadata| metadata.return_type_text() == Some(first))
18765 .then(|| first.to_owned())
18766}
18767
18768pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
18769 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
18779 namespace
18780 .strip_prefix("anonymous_namespace::")
18781 .unwrap_or(namespace)
18782 .to_string()
18783 })
18784}
18785
18786pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
18788 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
18794 brokk_bifrost_core::analyzer::Language::Cpp,
18795 namespace,
18796 );
18797 let mut prefixes = Vec::new();
18798 while !parts.is_empty() {
18799 prefixes.push(parts.join("::"));
18800 parts.pop();
18801 }
18802 prefixes
18803}
18804
18805fn nearest_namespace_candidates(
18806 candidates: Vec<CodeUnit>,
18807 normalized: &str,
18808 lexical_namespace: Option<&str>,
18809) -> Vec<CodeUnit> {
18810 if normalized.contains("::") {
18811 return candidates;
18812 }
18813 if let Some(namespace) = lexical_namespace {
18814 for prefix in namespace_prefixes(namespace) {
18815 let scoped = candidates
18816 .iter()
18817 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
18818 .cloned()
18819 .collect::<Vec<_>>();
18820 if !scoped.is_empty() {
18821 return scoped;
18822 }
18823 }
18824 }
18825 candidates
18826 .into_iter()
18827 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
18828 .collect()
18829}
18830
18831pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
18832 let mut namespaces = Vec::new();
18833 for parent in enclosing_cpp_namespace_nodes(node) {
18834 if let Some(name) = parent.child_by_field_name("name") {
18835 let namespace = normalize_cpp_reference_text(node_text(name, source));
18836 if !namespace.is_empty() {
18837 namespaces.push(namespace);
18838 }
18839 }
18840 }
18841 if namespaces.is_empty() {
18842 None
18843 } else {
18844 Some(namespaces.join("::"))
18845 }
18846}
18847
18848pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18852 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
18853}
18854
18855fn type_owner_resolution(
18856 analyzer: &CppGraphSource<'_>,
18857 code_unit: &CodeUnit,
18858) -> Option<ResolvedTypeOwner> {
18859 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
18860}
18861
18862fn target_type_owner_resolution(
18863 analyzer: &CppGraphSource<'_>,
18864 code_unit: &CodeUnit,
18865) -> Option<ResolvedTypeOwner> {
18866 match type_owner_resolution(analyzer, code_unit) {
18867 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
18868 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
18869 }
18870}
18871
18872fn target_forward_owner_resolution(
18884 analyzer: &CppGraphSource<'_>,
18885 code_unit: &CodeUnit,
18886) -> Option<ResolvedTypeOwner> {
18887 if !code_unit.is_function() {
18888 return None;
18889 }
18890 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
18896 let cpp = analyzer.cpp?;
18897 let mut visible_files = HashSet::default();
18898 collect_include_closure(
18899 analyzer,
18900 cpp.include_target_index(),
18901 code_unit.source(),
18902 &mut visible_files,
18903 None,
18904 );
18905 let candidates = analyzer.workspace_definitions().exact(&owner_name);
18906 let visible_candidates = candidates
18907 .iter()
18908 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18909 .cloned()
18910 .collect::<Vec<_>>();
18911 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
18912 DirectOwnerResolution::UniqueFull(unit) => {
18913 return Some(ResolvedTypeOwner {
18914 unit,
18915 is_forward_declaration: false,
18916 });
18917 }
18918 DirectOwnerResolution::ForwardsOnly(forwards) => {
18919 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
18920 unit: forwards.into_iter().next().unwrap(),
18921 is_forward_declaration: true,
18922 });
18923 }
18924 DirectOwnerResolution::Ambiguous => return None,
18925 DirectOwnerResolution::None => {}
18926 }
18927
18928 let candidates = candidates
18929 .into_iter()
18930 .filter(|candidate| candidate.is_class())
18931 .collect::<Vec<_>>();
18932 let (unit, is_forward_declaration) =
18933 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
18934 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
18935 DirectOwnerResolution::ForwardsOnly(forwards) => {
18936 (unique_logical_forward_owner(forwards)?, true)
18937 }
18938 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
18939 };
18940 Some(ResolvedTypeOwner {
18941 unit,
18942 is_forward_declaration,
18943 })
18944}
18945
18946pub fn precise_parent_of(
18947 analyzer: &CppGraphSource<'_>,
18948 visibility: &VisibilityIndex<'_>,
18949 code_unit: &CodeUnit,
18950) -> Option<CodeUnit> {
18951 visibility.cached_precise_parent_of(analyzer, code_unit)
18952}
18953
18954fn precise_parent_resolution(
18955 analyzer: &CppGraphSource<'_>,
18956 code_unit: &CodeUnit,
18957) -> Option<ResolvedTypeOwner> {
18958 #[cfg(any(test, feature = "test-support"))]
18959 if let Some(cpp) = analyzer.cpp {
18960 cpp.record_cpp_parent_resolution_for_test();
18961 }
18962 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
18963 return Some(ResolvedTypeOwner {
18964 unit,
18965 is_forward_declaration: false,
18966 });
18967 }
18968 let fallback = analyzer.parent_of(code_unit);
18969 if !code_unit.owner_is_type_scope() {
18970 return fallback.map(|unit| ResolvedTypeOwner {
18971 unit,
18972 is_forward_declaration: false,
18973 });
18974 }
18975 let owner_fq = code_unit
18976 .fq()
18977 .parent()
18978 .expect("a unit with an owner identifier has a structured parent");
18979 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
18980 match same_source_owner(analyzer, code_unit, &owner_candidates) {
18981 DirectOwnerResolution::UniqueFull(owner) => {
18982 return Some(ResolvedTypeOwner {
18983 unit: owner,
18984 is_forward_declaration: false,
18985 });
18986 }
18987 DirectOwnerResolution::Ambiguous => return None,
18988 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
18989 }
18990 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
18991 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
18992 unit: owner,
18993 is_forward_declaration: false,
18994 }),
18995 DirectOwnerResolution::Ambiguous => None,
18996 DirectOwnerResolution::ForwardsOnly(forwards) => {
18997 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
18998 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
18999 unit: owner,
19000 is_forward_declaration: false,
19001 }),
19002 FullOwnerResolution::None => {
19003 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
19004 unit,
19005 is_forward_declaration: true,
19006 })
19007 }
19008 FullOwnerResolution::Ambiguous => None,
19009 }
19010 }
19011 DirectOwnerResolution::None => {
19012 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
19013 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
19014 unit: owner,
19015 is_forward_declaration: false,
19016 }),
19017 FullOwnerResolution::Ambiguous => None,
19018 FullOwnerResolution::None => fallback
19019 .filter(|parent| {
19020 parent.source() == code_unit.source()
19021 && parent.fq() == &owner_fq
19022 && (!parent.is_class()
19023 || cpp_class_declaration_strength(analyzer, parent)
19024 == CppClassDeclarationStrength::Full)
19025 })
19026 .map(|unit| ResolvedTypeOwner {
19027 unit,
19028 is_forward_declaration: false,
19029 }),
19030 }
19031 }
19032 }
19033}
19034
19035fn exact_structural_type_parent(
19036 analyzer: &CppGraphSource<'_>,
19037 code_unit: &CodeUnit,
19038) -> Option<CodeUnit> {
19039 if !code_unit.is_function() && !code_unit.is_field() {
19040 return None;
19041 }
19042 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
19044 let parent = cpp.structural_parent_of(code_unit)?;
19045 (!parent.is_module()
19046 && parent.source() == code_unit.source()
19047 && parent.package_name() == code_unit.package_name()
19048 && parent.short_name() == encoded_owner)
19049 .then_some(parent)
19050}
19051
19052fn same_source_owner(
19053 analyzer: &CppGraphSource<'_>,
19054 code_unit: &CodeUnit,
19055 owner_candidates: &[CodeUnit],
19056) -> DirectOwnerResolution {
19057 let candidates = owner_candidates
19058 .iter()
19059 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
19060 .cloned()
19061 .collect::<Vec<_>>();
19062 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19063 classify_direct_owner_candidates(analyzer, candidates.into_iter())
19064}
19065
19066fn visible_full_cpp_owner(
19067 analyzer: &CppGraphSource<'_>,
19068 code_unit: &CodeUnit,
19069 owner_candidates: &[CodeUnit],
19070) -> FullOwnerResolution {
19071 let Some(cpp) = analyzer.cpp else {
19072 return FullOwnerResolution::None;
19073 };
19074 let mut visible_files = HashSet::default();
19075 collect_include_closure(
19076 analyzer,
19077 cpp.include_target_index(),
19078 code_unit.source(),
19079 &mut visible_files,
19080 None,
19081 );
19082 let candidates = owner_candidates
19083 .iter()
19084 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
19085 .cloned()
19086 .collect::<Vec<_>>();
19087 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19088 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
19089 let strength = cpp_class_declaration_strength(analyzer, &candidate);
19090 (candidate, strength)
19091 })) {
19092 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
19093 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
19094 FullOwnerResolution::None
19095 }
19096 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
19097 }
19098}
19099
19100pub enum DirectOwnerResolution {
19101 None,
19102 ForwardsOnly(Vec<CodeUnit>),
19103 UniqueFull(CodeUnit),
19104 Ambiguous,
19105}
19106
19107enum FullOwnerResolution {
19108 None,
19109 Unique(CodeUnit),
19110 Ambiguous,
19111}
19112
19113fn directly_included_owner(
19114 analyzer: &CppGraphSource<'_>,
19115 code_unit: &CodeUnit,
19116 owner_candidates: &[CodeUnit],
19117) -> DirectOwnerResolution {
19118 let Some(cpp) = analyzer.cpp else {
19119 return DirectOwnerResolution::None;
19120 };
19121 let direct_includes: HashSet<ProjectFile> = analyzer
19122 .canonical_include_paths(code_unit.source())
19123 .unwrap_or_default()
19124 .into_iter()
19125 .flat_map(|include| {
19126 resolve_include_targets_with_index(
19127 code_unit.source(),
19128 &include,
19129 cpp.include_target_index(),
19130 )
19131 })
19132 .collect();
19133 let candidates = owner_candidates
19134 .iter()
19135 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
19136 .cloned()
19137 .collect::<Vec<_>>();
19138 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
19139 classify_direct_owner_candidates(analyzer, candidates.into_iter())
19140}
19141
19142fn prefer_member_declaring_owners(
19143 analyzer: &CppGraphSource<'_>,
19144 member: &CodeUnit,
19145 candidates: Vec<CodeUnit>,
19146) -> Vec<CodeUnit> {
19147 let matching = candidates
19148 .iter()
19149 .filter(|owner| owner_declares_member(analyzer, owner, member))
19150 .cloned()
19151 .collect::<Vec<_>>();
19152 if matching.is_empty() {
19153 candidates
19154 } else {
19155 matching
19156 }
19157}
19158
19159fn owner_declares_member(
19160 analyzer: &CppGraphSource<'_>,
19161 owner: &CodeUnit,
19162 member: &CodeUnit,
19163) -> bool {
19164 analyzer.direct_children(owner).into_iter().any(|child| {
19165 child.kind() == member.kind()
19166 && child.identifier() == member.identifier()
19167 && child.signature() == member.signature()
19168 })
19169}
19170
19171fn classify_direct_owner_candidates(
19172 analyzer: &CppGraphSource<'_>,
19173 candidates: impl Iterator<Item = CodeUnit>,
19174) -> DirectOwnerResolution {
19175 collapse_owner_candidates(candidates.map(|candidate| {
19176 let strength = cpp_class_declaration_strength(analyzer, &candidate);
19177 (candidate, strength)
19178 }))
19179}
19180
19181pub fn collapse_owner_candidates(
19182 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
19183) -> DirectOwnerResolution {
19184 let mut full_definition = None;
19185 let mut forwards = Vec::new();
19186 for (candidate, strength) in candidates {
19187 match strength {
19188 CppClassDeclarationStrength::Full if full_definition.is_some() => {
19189 return DirectOwnerResolution::Ambiguous;
19190 }
19191 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
19192 CppClassDeclarationStrength::Forward => forwards.push(candidate),
19193 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
19194 }
19195 }
19196 if let Some(owner) = full_definition {
19197 DirectOwnerResolution::UniqueFull(owner)
19198 } else if !forwards.is_empty() {
19199 DirectOwnerResolution::ForwardsOnly(forwards)
19200 } else {
19201 DirectOwnerResolution::None
19202 }
19203}
19204
19205#[cfg(any(test, feature = "test-support"))]
19206pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19207 unique_logical_forward_owner(forwards)
19208}
19209
19210fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
19211 let first = forwards.pop()?;
19212 forwards
19213 .iter()
19214 .all(|forward| same_logical_symbol(forward, &first))
19215 .then_some(first)
19216}
19217
19218pub fn cpp_class_declaration_strength(
19219 analyzer: &CppGraphSource<'_>,
19220 candidate: &CodeUnit,
19221) -> CppClassDeclarationStrength {
19222 let Some(cpp) = analyzer.cpp else {
19230 return uncached_cpp_class_declaration_strength(analyzer, candidate);
19231 };
19232 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
19233 return strength;
19234 }
19235 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
19236 cpp.cache_class_declaration_strength(candidate, strength);
19237 strength
19238}
19239
19240fn uncached_cpp_class_declaration_strength(
19241 analyzer: &CppGraphSource<'_>,
19242 candidate: &CodeUnit,
19243) -> CppClassDeclarationStrength {
19244 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
19245 if facts
19246 .iter()
19247 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
19248 {
19249 return CppClassDeclarationStrength::Full;
19250 }
19251 if facts
19252 .iter()
19253 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
19254 {
19255 return CppClassDeclarationStrength::Forward;
19256 }
19257 }
19258 if let Some(cpp) = analyzer.cpp
19259 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
19260 {
19261 return cpp_class_declaration_strength_in_tree(
19262 analyzer,
19263 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
19264 candidate,
19265 prepared.source(),
19266 prepared.tree().root_node(),
19267 );
19268 }
19269 let Some(source) = analyzer.indexed_source(candidate.source()) else {
19270 return CppClassDeclarationStrength::Unknown;
19271 };
19272 let mut parser = Parser::new();
19273 if parser
19274 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19275 .is_err()
19276 {
19277 return CppClassDeclarationStrength::Unknown;
19278 }
19279 let Some(tree) = parser.parse(&source, None) else {
19280 return CppClassDeclarationStrength::Unknown;
19281 };
19282 let recovered_export_classes =
19285 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
19286 cpp_class_declaration_strength_in_tree(
19287 analyzer,
19288 &recovered_export_classes,
19289 candidate,
19290 &source,
19291 tree.root_node(),
19292 )
19293}
19294
19295fn cpp_class_declaration_strength_in_tree(
19296 analyzer: &CppGraphSource<'_>,
19297 recovered_export_classes: &CppRecoveredExportClassIndex,
19298 candidate: &CodeUnit,
19299 source: &str,
19300 root: Node<'_>,
19301) -> CppClassDeclarationStrength {
19302 let ranges = analyzer.ranges(candidate);
19303 let mut saw_forward = false;
19304 for range in ranges {
19305 match recovered_class_body_at(
19308 recovered_export_classes,
19309 root,
19310 source,
19311 candidate.identifier(),
19312 &range,
19313 ) {
19314 Some(true) => return CppClassDeclarationStrength::Full,
19315 Some(false) => {
19316 saw_forward = true;
19317 continue;
19318 }
19319 None => {}
19320 }
19321 let covers_range_start = |node: &Node<'_>| {
19328 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
19329 };
19330 let mut stack = Vec::new();
19331 if covers_range_start(&root) {
19332 stack.push(root);
19333 }
19334 while let Some(node) = stack.pop() {
19335 if node.start_byte() == range.start_byte
19336 && node.end_byte() == range.end_byte
19337 && matches!(
19338 node.kind(),
19339 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
19340 )
19341 {
19342 if cpp_class_node_has_body(node) {
19343 return CppClassDeclarationStrength::Full;
19344 }
19345 saw_forward = true;
19346 }
19347 let mut cursor = node.walk();
19348 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
19349 }
19350 }
19351 if saw_forward {
19352 CppClassDeclarationStrength::Forward
19353 } else {
19354 CppClassDeclarationStrength::Unknown
19355 }
19356}
19357
19358fn cpp_class_node_has_body(node: Node<'_>) -> bool {
19359 child_by_field_or_named_child_of_kinds(
19360 node,
19361 "body",
19362 &[
19363 "declaration_list",
19364 "field_declaration_list",
19365 "enumerator_list",
19366 ],
19367 )
19368 .is_some()
19369}
19370
19371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19372enum CppCTagKind {
19373 Struct,
19374 Union,
19375}
19376
19377fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
19378 let declaration = analyzer.get_source(code_unit, false)?;
19379 let mut parser = Parser::new();
19380 parser
19381 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19382 .ok()?;
19383 let tree = parser.parse(&declaration, None)?;
19384 let mut stack = vec![tree.root_node()];
19385 while let Some(node) = stack.pop() {
19386 let kind = match node.kind() {
19387 "struct_specifier" => CppCTagKind::Struct,
19388 "union_specifier" => CppCTagKind::Union,
19389 _ => {
19390 let mut cursor = node.walk();
19391 stack.extend(node.named_children(&mut cursor));
19392 continue;
19393 }
19394 };
19395 if node
19396 .child_by_field_name("name")
19397 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
19398 {
19399 return Some(kind);
19400 }
19401 let mut cursor = node.walk();
19402 stack.extend(node.named_children(&mut cursor));
19403 }
19404 None
19405}
19406
19407pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
19408 if !code_unit.owner_is_type_scope() {
19409 return None;
19410 }
19411 let owner_fq = code_unit.fq().parent()?;
19412 ctx.analyzer
19413 .workspace_definitions()
19414 .exact(&owner_fq)
19415 .into_iter()
19416 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
19417}
19418
19419pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19420 same_logical_symbol(left, right) && left.source() == right.source()
19421}
19422
19423pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19424 same_symbol(left, right) || same_logical_symbol(left, right)
19425}
19426
19427pub fn same_visible_global_field_symbol(
19428 analyzer: &CppGraphSource<'_>,
19429 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
19430 left: &CodeUnit,
19431 right: &CodeUnit,
19432) -> bool {
19433 if same_symbol(left, right) {
19434 return true;
19435 }
19436 if !same_logical_symbol(left, right) {
19437 return false;
19438 }
19439 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
19440 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
19441 {
19442 left.source() == right.source()
19443 } else {
19444 true
19445 }
19446}
19447
19448fn cpp_global_field_has_internal_linkage_cached(
19449 analyzer: &CppGraphSource<'_>,
19450 cache: &mut HashMap<CodeUnit, bool>,
19451 candidate: &CodeUnit,
19452) -> bool {
19453 if let Some(internal) = cache.get(candidate) {
19454 return *internal;
19455 }
19456 #[cfg(any(test, feature = "test-support"))]
19457 note_cpp_global_field_internal_linkage_classification_for_test();
19458 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
19459 cache.insert(candidate.clone(), internal);
19460 internal
19461}
19462
19463#[cfg(any(test, feature = "test-support"))]
19464thread_local! {
19465 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19466}
19467
19468#[cfg(any(test, feature = "test-support"))]
19469fn note_cpp_global_field_internal_linkage_classification_for_test() {
19470 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
19471 count.set(count.get() + 1);
19472 });
19473}
19474
19475#[cfg(any(test, feature = "test-support"))]
19476pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
19477 body: impl FnOnce() -> T,
19478) -> (T, usize) {
19479 with_thread_local_counter_for_test(
19480 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
19481 body,
19482 )
19483}
19484
19485#[cfg(any(test, feature = "test-support"))]
19486fn with_thread_local_counter_for_test<T>(
19487 counter: &'static std::thread::LocalKey<Cell<usize>>,
19488 body: impl FnOnce() -> T,
19489) -> (T, usize) {
19490 counter.with(|count| {
19491 count.set(0);
19492 let result = body();
19493 let observed = count.get();
19494 count.set(0);
19495 (result, observed)
19496 })
19497}
19498
19499pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
19500 left.kind() == right.kind()
19501 && left.fq_name() == right.fq_name()
19502 && left.signature() == right.signature()
19503}
19504
19505pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
19508 candidates: &[&'a CodeUnit],
19509 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
19510) -> Option<&'a CodeUnit> {
19511 let first = *candidates.first()?;
19512 if candidates.len() == 1 {
19513 return Some(first);
19514 }
19515 let first_target = structured_target(first)?;
19516 candidates
19517 .iter()
19518 .skip(1)
19519 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
19520 .then_some(first)
19521}
19522
19523pub fn cpp_global_field_has_internal_linkage(
19524 analyzer: &CppGraphSource<'_>,
19525 candidate: &CodeUnit,
19526) -> bool {
19527 if !candidate.is_field() || candidate.short_name().contains('.') {
19528 return false;
19529 }
19530 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
19531 return false;
19532 };
19533 match local_linkage {
19534 CppFieldLinkage::Internal => true,
19535 CppFieldLinkage::External => false,
19536 CppFieldLinkage::InternalUnlessExternalPeer => {
19537 !cpp_global_field_linkage_peers(analyzer, candidate)
19538 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
19539 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
19540 }
19541 }
19542}
19543
19544fn cpp_global_field_linkage_peers<'a>(
19545 analyzer: &CppGraphSource<'a>,
19546 candidate: &'a CodeUnit,
19547) -> impl Iterator<Item = CodeUnit> + 'a {
19548 let name = candidate.fq().clone();
19549 analyzer
19550 .workspace_definitions()
19551 .exact(&name)
19552 .into_iter()
19553 .filter(move |peer| {
19554 if peer == candidate {
19555 return false;
19556 }
19557 #[cfg(any(test, feature = "test-support"))]
19558 note_cpp_global_field_linkage_peer_inspection_for_test();
19559 same_logical_symbol(peer, candidate)
19560 })
19561}
19562
19563#[cfg(any(test, feature = "test-support"))]
19564thread_local! {
19565 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
19566}
19567
19568#[cfg(any(test, feature = "test-support"))]
19569fn note_cpp_global_field_linkage_peer_inspection_for_test() {
19570 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
19571 count.set(count.get() + 1);
19572 });
19573}
19574
19575#[cfg(any(test, feature = "test-support"))]
19576pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
19577 body: impl FnOnce() -> T,
19578) -> (T, usize) {
19579 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
19580}
19581
19582fn cpp_global_field_declaration_linkage(
19583 analyzer: &CppGraphSource<'_>,
19584 candidate: &CodeUnit,
19585) -> Option<CppFieldLinkage> {
19586 analyzer.cpp_field_linkage(candidate)
19587}
19588
19589pub fn cpp_canonical_alias_target_text(
19591 analyzer: &CppGraphSource<'_>,
19592 unit: &CodeUnit,
19593) -> Option<String> {
19594 let facts = analyzer.declaration_source_properties(unit)?;
19595 let first_fact = facts.first()?;
19596 let first = first_fact.alias_target_text.as_ref()?;
19597 let normalized = normalize_cpp_whitespace(first);
19598 facts
19602 .iter()
19603 .all(|fact| {
19604 fact.alias_target_text.as_ref().is_some_and(|target| {
19605 target == first
19606 || (first_fact.alias_target.is_some()
19607 && fact.alias_target == first_fact.alias_target
19608 && normalize_cpp_whitespace(target) == normalized)
19609 })
19610 })
19611 .then(|| first.clone())
19612}
19613
19614#[cfg(test)]
19615mod tests {
19616
19617 #[test]
19618 fn resolver_displaced_conditional_grammar_boundary() {
19619 let source = "#ifdef FLAG\nint values[\n#endif\n1];\nint outside;\n";
19623 let tree = parse_cpp(source);
19624 let conditional = tree.root_node().named_child(0).unwrap();
19625 assert_eq!(conditional.kind(), "preproc_ifdef");
19626 assert!(conditional.has_error());
19627 let boundary = cpp_displaced_preprocessor_boundary(conditional).unwrap();
19628 let directive = crate::grammar_test_support::nodes(conditional)
19629 .into_iter()
19630 .find(|node| node.kind() == "#endif" && !node.is_missing())
19631 .unwrap();
19632 assert_eq!(directive.parent().unwrap().kind(), "ERROR");
19633 assert_eq!(directive.end_byte(), boundary.end_byte);
19634 let inside = named_node_at(&tree, source, "values");
19635 let outside = named_node_at(&tree, source, "outside");
19636 assert!(has_ancestor_kind(inside, "preproc_ifdef"));
19637 assert!(has_ancestor_kind(outside, "preproc_ifdef"));
19638 assert!(inside.end_byte() < directive.start_byte());
19639 assert!(outside.start_byte() > directive.end_byte());
19640 assert!(preprocessor_conditional_contains_descendant(
19641 conditional,
19642 inside
19643 ));
19644 assert!(!preprocessor_conditional_contains_descendant(
19645 conditional,
19646 outside
19647 ));
19648 }
19649
19650 #[test]
19651 fn resolver_recovered_namespace_boundaries() {
19652 let source = STOLEN_BRACE_CASCADE;
19653 let tree = parse_cpp(source);
19654 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
19655 assert!(!index.regions.is_empty(), "fixture must enter recovery");
19656 for (needle, expected) in [
19657 ("First", vec!["app", "matchers", "detail"]),
19658 ("Second", vec!["app", "matchers", "detail"]),
19659 ("tail_function", vec!["app", "matchers"]),
19660 ("TailClass", vec!["app", "matchers"]),
19661 ("AfterAll", vec![]),
19662 ] {
19663 let node = named_node_at(&tree, source, needle);
19664 let mut parsed: Vec<_> = std::iter::successors(node.parent(), |node| node.parent())
19665 .filter(|node| node.kind() == "namespace_definition")
19666 .filter_map(|namespace| {
19667 let name = namespace.child_by_field_name("name")?;
19668 let mut components = Vec::new();
19669 append_cpp_name_components(name, source, &mut components)?;
19670 Some((
19671 namespace.start_byte(),
19672 components,
19673 namespace_definition_has_real_braces(namespace),
19674 ))
19675 })
19676 .collect();
19677 parsed.reverse();
19678 assert_eq!(
19679 index.restore_enclosing_namespaces(parsed, node.start_byte()),
19680 expected,
19681 "{needle}"
19682 );
19683 }
19684 for region in &index.regions {
19686 assert!(region.start < region.end);
19687 assert!(index.region_at(region.start).is_some());
19688 assert!(
19689 index
19690 .region_at(region.end)
19691 .is_none_or(|found| !std::ptr::eq(found, region)),
19692 "region leaked beyond its half-open boundary: {region:?}"
19693 );
19694 }
19695 let errors: Vec<_> = crate::grammar_test_support::nodes(tree.root_node())
19698 .into_iter()
19699 .filter(|node| node.kind() == "ERROR")
19700 .filter(|node| {
19701 let mut cursor = node.walk();
19702 node.children(&mut cursor).any(|child| child.kind() == "}")
19703 })
19704 .collect();
19705 assert!(
19706 !errors.is_empty(),
19707 "fixture must retain unmatched close tokens"
19708 );
19709 for error in errors {
19710 assert!(direct_unmatched_closing_brace(error), "{error:?}");
19711 }
19712 }
19713
19714 #[test]
19717 fn resolver_declarator_grammar_boundary_table() {
19718 for (source, name, kind, binder) in [
19719 ("int first, values[3];", "values", "identifier", true),
19720 (
19721 "void f() { int first, values[3]; }",
19722 "values",
19723 "identifier",
19724 true,
19725 ),
19726 (
19727 "typedef unsigned Underlying; enum Named : Underlying { Value };",
19728 "Underlying {",
19729 "type_identifier",
19730 false,
19731 ),
19732 ] {
19733 let tree = parse_cpp(source);
19734 assert!(!tree.root_node().has_error(), "{source}");
19735 let spelling = name.split_whitespace().next().unwrap();
19736 let start = source.find(name).unwrap();
19737 let node = tree
19738 .root_node()
19739 .named_descendant_for_byte_range(start, start + spelling.len())
19740 .unwrap();
19741 assert_eq!(node.kind(), kind, "{source}");
19742 if spelling == "values" {
19743 let array = node.parent().unwrap();
19744 assert_eq!(array.kind(), "array_declarator");
19745 assert_eq!(array.child_by_field_name("declarator"), Some(node));
19746 assert!(is_declarator_node(array), "{source}");
19747 } else {
19748 let enumeration = node.parent().unwrap();
19749 assert_eq!(enumeration.kind(), "enum_specifier");
19750 assert_eq!(enumeration.child_by_field_name("base"), Some(node));
19751 assert_eq!(
19752 enumeration.child_by_field_name("name").is_some(),
19753 source.contains("enum Named")
19754 );
19755 }
19756 assert_eq!(is_declaration_name(node), binder, "{source}");
19757 }
19758 }
19759
19760 #[test]
19761 fn resolver_alias_scope_and_argument_grammar_table() {
19762 for (source, scope) in [
19763 ("template<class T> using Alias = T;", true),
19764 ("namespace n { template<class T> using Alias = T; }", true),
19765 (
19766 "struct Owner { template<class T> using Alias = T; };",
19767 false,
19768 ),
19769 ("void f() { using Alias = int; }", false),
19770 ] {
19771 let tree = parse_cpp(source);
19772 assert!(!tree.root_node().has_error(), "{source}");
19773 let alias = crate::grammar_test_support::nodes(tree.root_node())
19774 .into_iter()
19775 .find(|node| node.kind() == "alias_declaration")
19776 .unwrap();
19777 assert_eq!(
19778 node_text(alias.child_by_field_name("name").unwrap(), source),
19779 "Alias"
19780 );
19781 assert!(alias.child_by_field_name("type").is_some());
19782 assert_eq!(alias_has_visible_file_scope(alias), scope, "{source}");
19783 }
19784 for (source, argument_kinds, expression) in [
19785 (
19786 "ns::Alias value(42, arg);",
19787 vec!["number_literal", "identifier"],
19788 true,
19789 ),
19790 (
19791 "ns::Alias value(42, 7);",
19792 vec!["number_literal", "number_literal"],
19793 false,
19794 ),
19795 ("ns::Alias value();", vec![], false),
19796 ] {
19797 let tree = parse_cpp(source);
19798 assert!(!tree.root_node().has_error(), "{source}");
19799 let declaration = tree.root_node().named_child(0).unwrap();
19800 let ty = declaration.child_by_field_name("type").unwrap();
19801 assert_eq!(node_text(ty, source), "ns::Alias");
19802 let argument_lists: Vec<_> = crate::grammar_test_support::nodes(declaration)
19803 .into_iter()
19804 .filter(|node| node.kind() == "argument_list")
19805 .collect();
19806 let kinds: Vec<_> = argument_lists
19807 .iter()
19808 .flat_map(|node| {
19809 let mut cursor = node.walk();
19810 node.named_children(&mut cursor)
19811 .map(|child| child.kind())
19812 .collect::<Vec<_>>()
19813 })
19814 .collect();
19815 assert_eq!(kinds, argument_kinds, "{source}");
19816 assert_eq!(
19817 qualified_alias_constructor_has_expression_argument(ty),
19818 expression,
19819 "{source}"
19820 );
19821 }
19822 }
19823
19824 #[test]
19825 fn resolver_namespace_brace_grammar_table() {
19826 for (source, complete, closing_missing) in [
19827 ("namespace n { int item; }", true, false),
19828 ("namespace n { int item;", false, true),
19829 ] {
19830 let tree = parse_cpp(source);
19831 let namespace = tree.root_node().named_child(0).unwrap();
19832 assert_eq!(namespace.kind(), "namespace_definition");
19833 let body = namespace.child_by_field_name("body").unwrap();
19834 assert_eq!(body.kind(), "declaration_list");
19835 let open = body.child(0).unwrap();
19836 let close = body.child(body.child_count() - 1).unwrap();
19837 assert_eq!(open.kind(), "{");
19838 assert!(!open.is_missing());
19839 assert_eq!(close.kind(), "}");
19840 assert_eq!(close.is_missing(), closing_missing);
19841 assert_eq!(
19842 namespace_definition_has_real_braces(namespace),
19843 complete,
19844 "{source}"
19845 );
19846 }
19847 let source = "namespace { namespace detail { struct Derived {}; void run() {} } }";
19848 let tree = parse_cpp(source);
19849 assert!(!tree.root_node().has_error());
19850 let anonymous = tree.root_node().named_child(0).unwrap();
19851 assert_eq!(anonymous.kind(), "namespace_definition");
19852 assert!(anonymous.child_by_field_name("name").is_none());
19853 let run = named_node_at(&tree, source, "run");
19854 assert_eq!(
19855 enclosing_namespace_components(run, source),
19856 Some(vec!["detail".to_owned()])
19857 );
19858 }
19859
19860 #[test]
19861 fn resolver_c_block_envelope_grammar_table() {
19862 for (source, name, damaged, recovered) in [
19863 ("void outer(void) { nested() {} }", "nested", false, false),
19864 (
19865 "void outer(void) { Apply(first, ^(Bucket b) { return b.value; }); }",
19866 "Apply",
19867 true,
19868 true,
19869 ),
19870 ] {
19871 let tree = parse_cpp(source);
19872 let node = named_node_at(&tree, source, name);
19873 let declarator = node.parent().unwrap();
19874 assert_eq!(
19875 declarator.kind(),
19876 "function_declarator",
19877 "{source}: {:?}",
19878 tree.root_node()
19879 );
19880 assert_eq!(declarator.child_by_field_name("declarator"), Some(node));
19881 assert_eq!(declarator.has_error(), damaged, "{source}");
19882 let definition = declarator.parent().unwrap();
19883 assert_eq!(definition.kind(), "function_definition");
19884 assert!(definition.child_by_field_name("type").is_none());
19885 assert!(definition.child_by_field_name("body").is_some());
19886 assert_eq!(
19887 recovered_c_block_call_envelope(declarator).is_some(),
19888 recovered,
19889 "{source}"
19890 );
19891 }
19892 }
19893
19894 #[test]
19895 fn resolver_linkage_guard_grammar_table() {
19896 for (source, recovered, visible) in [
19897 (
19898 "#ifdef __cplusplus\nextern \"C\" {\n#endif\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19899 false,
19900 true,
19901 ),
19902 (
19903 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n#ifdef __cplusplus\n}\n#endif\n",
19904 true,
19905 false,
19906 ),
19907 (
19908 "#ifdef __cplusplus\nextern \"C\" {\nvoid target();\n}\n#endif\n",
19909 true,
19910 false,
19911 ),
19912 ] {
19913 let tree = parse_cpp(source);
19914 let name = named_node_at(&tree, source, "target");
19915 let mut declaration = name;
19916 while declaration.kind() != "declaration" {
19917 declaration = declaration.parent().unwrap();
19918 }
19919 assert_eq!(
19920 has_ancestor_kind(declaration, "linkage_specification"),
19921 recovered,
19922 "{source}"
19923 );
19924 assert_eq!(
19925 has_ancestor_kind(declaration, "preproc_ifdef"),
19926 recovered,
19927 "{source}"
19928 );
19929 if !recovered {
19930 assert!(!tree.root_node().has_error());
19931 let open = tree.root_node().named_child(0).unwrap();
19932 assert_eq!(open.kind(), "preproc_linkage_open");
19933 assert!(open.child_by_field_name("value").is_some());
19934 }
19935 assert_eq!(
19936 callable_preprocessor_context_is_visible(declaration, source),
19937 visible,
19938 "{source}"
19939 );
19940 }
19941 }
19942
19943 #[test]
19944 #[hardwon("#6902: grammar 0.24.4 parsed an unnamed enum's base as its name")]
19945 fn resolver_anonymous_enum_base_grammar_boundary() {
19946 let source = "typedef unsigned Underlying; enum : Underlying { Value };";
19947 let tree = parse_cpp(source);
19948 assert!(
19949 !tree.root_node().has_error(),
19950 "{}",
19951 tree.root_node().to_sexp()
19952 );
19953 let start = source.find("Underlying {").unwrap();
19954 let base = tree
19955 .root_node()
19956 .named_descendant_for_byte_range(start, start + "Underlying".len())
19957 .unwrap();
19958 let enumeration = base.parent().unwrap();
19959 assert_eq!(enumeration.kind(), "enum_specifier");
19960 assert!(enumeration.child_by_field_name("name").is_none());
19961 assert_eq!(enumeration.child_by_field_name("base"), Some(base));
19962 assert!(!is_declaration_name(base));
19963 }
19964
19965 #[test]
19966 #[hardwon(
19967 "db00de0653f1: quicksilver round 4 on brokk-bifrost-cpp: spliced macro tokens need physical source coordinates"
19968 )]
19969 fn macro_splices_preserve_statement_origin_ranges() {
19970 for newline in ["\n", "\r\n"] {
19971 for (replacement, name, written) in [
19972 (
19973 format!("jo\\{newline}ined\\{newline}();\\{newline}after();"),
19974 "joined",
19975 format!("jo\\{newline}ined"),
19976 ),
19977 (
19978 format!("do {{ if (ready) bl\\{newline}ock }} while (0)"),
19979 "block",
19980 format!("bl\\{newline}ock"),
19981 ),
19982 ] {
19983 let parameters = vec!["block".to_owned()];
19984 let (source, tree, original_offsets) =
19985 VisibilityIndex::parse_macro_replacement_body(&replacement, ¶meters)
19986 .expect("joined replacement parses");
19987 let body = ParsedReplacementBody {
19988 source,
19989 tree,
19990 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
19991 parameters,
19992 original_offsets,
19993 };
19994 let nodes = crate::grammar_test_support::nodes(body.tree.root_node());
19995 let matching: Vec<_> = nodes
19996 .iter()
19997 .filter(|node| {
19998 node.kind() == "identifier" && node_text(**node, &body.source) == name
19999 })
20000 .collect();
20001 assert_eq!(matching.len(), 1, "{replacement}");
20002 let range = body.file_range(*matching[0], 0);
20003 assert_eq!(&replacement[range], written);
20004 for node in nodes.iter().filter(|node| {
20005 node.kind() == "identifier" && node_text(**node, &body.source) == "after"
20006 }) {
20007 assert_eq!(&replacement[body.file_range(*node, 0)], "after");
20008 }
20009 }
20010 }
20011 }
20012
20013 #[test]
20014 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
20015 fn issue_3089_statement_formal_at_end_of_replacement() {
20016 let parameters = vec!["handle".to_owned(), "block".to_owned()];
20017 for replacement in [
20018 "do { header_event_t* event; if ((handle)->active) block } while (0)",
20019 "do { header_event_t* event; block } while (0)",
20020 ] {
20021 assert!(
20022 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
20023 .is_some(),
20024 "{replacement}"
20025 );
20026 }
20027 }
20028 use super::*;
20029 use brokk_bifrost_test_macros::hardwon;
20030
20031 #[test]
20032 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
20033 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
20034 let source = "int size(void) { return sizeof(((Payload))); }\n";
20035 let mut parser = Parser::new();
20036 parser
20037 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20038 .expect("C++ grammar");
20039 let tree = parser.parse(source, None).expect("fixture tree");
20040 let start = source.find("Payload").expect("sizeof operand");
20041 let node = tree
20042 .root_node()
20043 .named_descendant_for_byte_range(start, start + "Payload".len())
20044 .expect("focused operand");
20045 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
20046 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
20047
20048 assert_eq!(node.kind(), "identifier");
20049 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
20050 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
20051 }
20052
20053 #[test]
20054 fn recovered_macro_actuals_keep_their_cst_boundaries() {
20055 for (source, recovered) in [
20056 ("int f() { return M(Type, value); }", false),
20057 ("int f() { return M(Type, ^(int n){ return n; }); }", true),
20058 ] {
20059 let tree = parse_cpp(source);
20060 let first = named_node_at(&tree, source, "Type");
20061 let (call, index, actual) = c_function_macro_argument(first)
20062 .unwrap_or_else(|| panic!("first actual {source}: {}", tree.root_node().to_sexp()));
20063 assert_eq!(index, 0);
20064 assert_eq!(node_text(actual, source), "Type");
20065 let arguments = call.child_by_field_name("arguments").unwrap();
20066 let raw = arguments.named_child(0).unwrap();
20067 if recovered {
20068 assert_eq!(raw.kind(), "binary_expression");
20069 assert_eq!(raw.child_by_field_name("left"), Some(actual));
20070 let inside = named_node_at(&tree, source, "return n;");
20071 let (_, index, block) = c_function_macro_argument(inside).expect("block actual");
20072 assert_eq!(index, 1);
20073 assert_eq!(block.kind(), "compound_literal_expression");
20074 assert_eq!(node_text(block, source), "(int n){ return n; }");
20075 assert_eq!(raw.child_by_field_name("right"), Some(block));
20076 } else {
20077 assert_eq!(raw, actual);
20078 let second = named_node_at(&tree, source, "value");
20079 let (_, index, actual) = c_function_macro_argument(second).expect("second actual");
20080 assert_eq!(index, 1);
20081 assert_eq!(node_text(actual, source), "value");
20082 }
20083 }
20084 }
20085
20086 fn parse_cpp(source: &str) -> Tree {
20087 crate::diagnostics::parse_cpp_tree(source)
20088 }
20089
20090 #[test]
20091 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
20092 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
20093 let source = "auto value = new ns::Box<int> [ 3 ];";
20094 let tree = parse_cpp(source);
20095 let mut pending = vec![tree.root_node()];
20096 let allocation = loop {
20097 let node = pending.pop().expect("new expression in fixture");
20098 if node.kind() == "new_expression" {
20099 break node;
20100 }
20101 let mut cursor = node.walk();
20102 pending.extend(
20103 node.named_children(&mut cursor)
20104 .collect::<Vec<_>>()
20105 .into_iter()
20106 .rev(),
20107 );
20108 };
20109
20110 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
20111 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
20112 assert_eq!(node_text(type_node, source), "ns::Box<int>");
20113 assert_eq!(
20114 allocation
20115 .child_by_field_name("declarator")
20116 .expect("array declarator")
20117 .kind(),
20118 "new_declarator"
20119 );
20120
20121 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
20122 let old_split = old_split
20123 .strip_prefix("new ")
20124 .unwrap_or(old_split.as_str())
20125 .split(['(', '{'])
20126 .next()
20127 .expect("old split result");
20128 assert_ne!(old_split.trim(), node_text(type_node, source));
20129 }
20130
20131 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
20132 let start = source.find(needle).expect("fixture needle");
20133 tree.root_node()
20134 .named_descendant_for_byte_range(start, start + needle.len())
20135 .expect("node at needle")
20136 }
20137
20138 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
20139 let tree = crate::diagnostics::parse_cpp_tree(source);
20140 PreparedSyntaxTree::new(
20141 PreparedSyntaxSource::Exact(Arc::from(source)),
20142 tree,
20143 compute_line_starts(source),
20144 LanguageDialect::Standard(Language::Cpp),
20145 PreparedSourceOrigin::Disk,
20146 None,
20147 )
20148 }
20149
20150 fn unresolved_include_before(source: &str, reference: &str) -> bool {
20151 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
20152 let prepared = prepared_cpp(source);
20153 let facts = collect_structured_include_facts(&prepared);
20154 let include_targets = IncludeTargetIndex::build([&file]);
20155 has_unresolved_include_visible_before_in_prepared(
20156 &file,
20157 &prepared,
20158 &include_targets,
20159 &facts,
20160 source.find(reference).expect("reference fixture"),
20161 &|guards| guards.cloned(),
20162 )
20163 }
20164
20165 #[test]
20166 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20167 fn unresolved_include_before_reference_is_visible() {
20168 let source = "#include \"missing.h\"\nint use = Missing;\n";
20169 assert!(unresolved_include_before(source, "Missing"));
20170 }
20171
20172 #[test]
20173 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20174 fn unresolved_include_after_reference_is_not_visible() {
20175 let source = "int use = Missing;\n#include \"missing.h\"\n";
20176 assert!(!unresolved_include_before(source, "Missing"));
20177 }
20178
20179 #[test]
20180 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20181 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
20182 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
20183 assert!(!unresolved_include_before(source, "Missing"));
20184 }
20185
20186 #[test]
20187 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
20188 fn unresolved_include_in_current_branch_is_visible() {
20189 let source =
20190 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
20191 assert!(unresolved_include_before(source, "Missing"));
20192 }
20193
20194 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
20199namespace matchers {
20200 namespace detail {
20201 class API [[nodiscard]] First {
20202 public:
20203 int value() const { return count_ + 1; }
20204 private:
20205 int count_;
20206 };
20207 class API [[nodiscard]] Second {
20208 public:
20209 int value() const { return count_ + 2; }
20210 private:
20211 int count_;
20212 };
20213 } // namespace detail
20214
20215 template <typename T>
20216 void tail_function(MatcherBase<T> const& value);
20217
20218 class TailClass {};
20219} // namespace matchers
20220} // namespace app
20221
20222struct AfterAll {};
20223"#;
20224
20225 #[test]
20226 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20227 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
20228 let source = STOLEN_BRACE_CASCADE;
20229 let tree = parse_cpp(source);
20230 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20231
20232 let tail_class = named_node_at(&tree, source, "TailClass");
20233 assert!(
20234 !has_ancestor_kind(tail_class, "namespace_definition"),
20235 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
20236 );
20237 let displaced = named_node_at(&tree, source, "Second");
20238 assert_eq!(
20239 enclosing_namespace_components(displaced, source),
20240 Some(vec!["app".to_string(), "matchers".to_string()]),
20241 "the fixture must displace the second class out of detail"
20242 );
20243
20244 let components = |needle: &str| {
20245 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
20246 };
20247 assert_eq!(components("First"), ["app", "matchers", "detail"]);
20248 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
20249 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
20250 assert_eq!(components("tail_function"), ["app", "matchers"]);
20251 assert_eq!(components("TailClass"), ["app", "matchers"]);
20252 assert!(components("AfterAll").is_empty());
20253 }
20254
20255 #[test]
20256 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
20257 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
20258 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
20259 let tree = parse_cpp(clean);
20260 assert!(!tree.root_node().has_error());
20261 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
20262
20263 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
20266 let tree = parse_cpp(damaged);
20267 assert!(tree.root_node().has_error());
20268 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
20269 assert_eq!(
20270 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
20271 ["a"]
20272 );
20273 }
20274
20275 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
20282
20283 class Target {
20284 int value_;
20285 };
20286
20287 template <typename T>
20288 class Holder {
20289 public:
20290 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
20291
20292#define HOLDER_DEFINE_OP( id, op ) \
20293 template <typename U> \
20294 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
20295 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
20296 return Target{}; \
20297 }
20298
20299 HOLDER_DEFINE_OP( equal, == )
20300#undef HOLDER_DEFINE_OP
20301 T m_lhs;
20302 };
20303
20304 class Tail {};
20305}
20306"#;
20307
20308 #[test]
20309 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
20310 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
20311 let source = COLLAPSED_NAMESPACE_HEAD;
20312 let tree = parse_cpp(source);
20313 let target = named_node_at(&tree, source, "class Target");
20314
20315 assert!(
20321 tree.root_node().has_error(),
20322 "the fixture must retain the macro invocation's parse damage"
20323 );
20324 assert!(
20325 has_ancestor_kind(target, "namespace_definition"),
20326 "the namespace head survives: the class keeps its parsed namespace ancestor"
20327 );
20328
20329 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
20330 assert_eq!(
20331 index.enclosing_namespace_components(target, source),
20332 ["app"]
20333 );
20334 assert_eq!(
20335 index.enclosing_namespace_levels(target, source),
20336 vec![(
20337 "app".to_string(),
20338 Range {
20339 start_byte: 0,
20340 end_byte: source.len() - 1,
20341 start_line: 1,
20342 end_line: 25,
20343 },
20344 Range {
20345 start_byte: 10,
20346 end_byte: 13,
20347 start_line: 1,
20348 end_line: 1,
20349 },
20350 )],
20351 "the recovered namespace owns its full construct, not its first member or just its header"
20352 );
20353 }
20354
20355 #[test]
20356 #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
20357 fn namespace_body_name_components_recovers_literal_function_head() {
20358 let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
20364 let tree = parse_cpp(source);
20365 let keyword_start = source.find("namespace recovered").unwrap();
20366 let keyword = tree
20367 .root_node()
20368 .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
20369 .expect("literal keyword node");
20370 assert_eq!(keyword.kind(), "type_identifier");
20375 assert_eq!(keyword.child_count(), 0);
20376 assert!(!keyword.is_missing() && !keyword.has_error());
20377 assert_eq!(node_text(keyword, source), "namespace");
20378 let wrapper = keyword.parent().expect("actual wrapper");
20379 assert_eq!(wrapper.kind(), "function_definition");
20380 let mut cursor = wrapper.walk();
20381 let children = wrapper
20382 .children(&mut cursor)
20383 .filter(|child| child.kind() != "comment")
20384 .collect::<Vec<_>>();
20385 assert_eq!(
20386 children.len(),
20387 4,
20388 "all named and unnamed children accounted: {children:?}"
20389 );
20390 let prefix = children[0];
20391 assert_eq!(prefix.kind(), "macro_modifier");
20392 assert!(!prefix.is_missing() && !prefix.has_error());
20393 assert_eq!(node_text(prefix, source), "OPAQUE_PREFIX");
20394 assert_eq!(children[1], keyword);
20395 assert_eq!(wrapper.child_by_field_name("type"), Some(keyword));
20396 let name = children[2];
20397 assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
20398 assert_eq!(name.child_count(), 0);
20399 assert!(!name.is_missing() && !name.has_error());
20400 assert_eq!(node_text(name, source), "recovered");
20401 assert_eq!(wrapper.child_by_field_name("declarator"), Some(name));
20402 let body = wrapper.child_by_field_name("body").expect("actual body");
20403 assert_eq!(children[3], body);
20404 assert_eq!(body.kind(), "compound_statement");
20405 assert!(!body.is_missing());
20406 let open = body.child(0).expect("actual first child");
20407 assert_eq!(open.kind(), "{");
20408 assert!(!open.is_missing() && !open.has_error());
20409 assert_eq!(open.start_byte(), body.start_byte());
20410 let (head, names) = namespace_body_name_components(wrapper, body, source)
20411 .expect("strict synthetic literal head recognized");
20412 assert_eq!(head, keyword);
20413 assert_eq!(
20414 names
20415 .iter()
20416 .map(|(name, _)| name.as_str())
20417 .collect::<Vec<_>>(),
20418 ["recovered"]
20419 );
20420 assert_eq!(
20421 (names[0].1.start_byte, names[0].1.end_byte),
20422 (name.start_byte(), name.end_byte())
20423 );
20424 let inside = named_node_at(&tree, source, "int member;");
20425 assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
20426 }
20427
20428 #[test]
20429 #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
20430 fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
20431 for refusal in [
20432 "int ordinary(int value) { return value; }",
20433 "OPAQUE_PREFIX namespace_ recovered { int member; }",
20434 "OPAQUE_PREFIX namespace recovered other { int member; }",
20435 "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
20436 ] {
20437 let tree = parse_cpp(refusal);
20438 let mut stack = vec![tree.root_node()];
20439 let mut helper_calls = 0;
20440 while let Some(node) = stack.pop() {
20441 if node.kind() == "function_definition"
20442 && let Some(body) = node.child_by_field_name("body")
20443 {
20444 helper_calls += 1;
20445 assert_eq!(
20446 (node.start_byte(), node.end_byte()),
20447 (0, refusal.len()),
20448 "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
20449 );
20450 assert!(
20451 namespace_body_name_components(node, body, refusal).is_none(),
20452 "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
20453 );
20454 }
20455 let mut cursor = node.walk();
20456 stack.extend(node.named_children(&mut cursor));
20457 }
20458 assert_eq!(
20459 helper_calls,
20460 1,
20461 "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
20462 tree.root_node()
20463 );
20464 }
20465
20466 for absent in [
20472 "OPAQUE_PREFIX namespace recovered = other { int member; }",
20473 "OPAQUE_PREFIX namespace recovered int member;",
20474 ] {
20475 let tree = parse_cpp(absent);
20476 let mut stack = vec![tree.root_node()];
20477 let mut helper_candidates = 0;
20478 while let Some(node) = stack.pop() {
20479 if node.kind() == "function_definition"
20480 && node.child_by_field_name("body").is_some()
20481 {
20482 helper_candidates += 1;
20483 }
20484 let mut cursor = node.walk();
20485 stack.extend(node.named_children(&mut cursor));
20486 }
20487 assert_eq!(
20488 helper_candidates,
20489 0,
20490 "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
20491 tree.root_node()
20492 );
20493 }
20494 }
20495
20496 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
20505
20506namespace app {
20507namespace detail {
20508
20509bool check(bool active) {
20510 APP_IF_ACTIVE
20511 return true;
20512 }
20513 return false;
20514}
20515
20516class Tail {};
20517
20518} // namespace detail
20519} // namespace app
20520"#;
20521
20522 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
20529namespace compile_time {
20530namespace number_parsing {
20531consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
20532 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
20533 auto digit = [](uint8_t c) -> uint32_t {
20534 }
20535 }
20536 if (a) {
20537 while (a) {
20538 }
20539 }
20540 switch (c) {
20541 case '{': {
20542 }
20543 }
20544 }
20545 while (a) {
20546 cursor += field.second;
20547 if (a) {
20548 case '{': {
20549 if (a) {
20550 }
20551 }
20552 case 'n': {
20553 if (a) {
20554 }
20555 }
20556 }
20557 }
20558 if (a) {
20559 simdjson_consteval_error("Expected '}'");
20560 }
20561}
20562}
20563}
20564"#;
20565
20566 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
20568 let mut stack = vec![root];
20569 let mut counts = (0, 0);
20570 while let Some(node) = stack.pop() {
20571 match (node.kind(), node.is_missing()) {
20572 ("{", false) => counts.0 += 1,
20573 ("}", false) => counts.1 += 1,
20574 _ => {}
20575 }
20576 let mut cursor = node.walk();
20577 stack.extend(node.children(&mut cursor));
20578 }
20579 counts
20580 }
20581
20582 fn namespace_open_byte(source: &str, head: &str) -> usize {
20584 let at = source.find(head).expect("fixture namespace head");
20585 at + head.len() - 1
20586 }
20587
20588 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
20589 let index = OrphanedNamespaceScopeIndex::build(root, source);
20590 for head in heads {
20591 assert_eq!(
20592 index.matching_close_brace(namespace_open_byte(source, head)),
20593 None,
20594 "{head} must not claim a close belonging to an inner scope"
20595 );
20596 }
20597 }
20598
20599 #[test]
20600 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20601 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
20602 let source = MACRO_OPENED_SCOPE;
20603 let tree = parse_cpp(source);
20604 let root = tree.root_node();
20605 assert_eq!(
20606 real_brace_token_counts(root),
20607 (4, 5),
20608 "the fixture must reproduce the loss: one scope the source opens \
20609 has no `{{` token, so the stream holds an extra real close"
20610 );
20611
20612 assert_namespace_heads_have_no_matching_close(
20613 root,
20614 source,
20615 &["namespace app {", "namespace detail {"],
20616 );
20617 }
20618
20619 #[test]
20620 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
20621 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
20622 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
20626 let tree = parse_cpp(&source);
20627 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
20628 let after_all = named_node_at(&tree, &source, "struct AfterAll");
20629 assert!(
20630 index
20631 .enclosing_namespace_components(after_all, &source)
20632 .is_empty()
20633 );
20634 }
20635
20636 #[test]
20637 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
20638 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
20639 let source = REFLECTION_CASE_LABEL_RECOVERY;
20640 let tree = parse_cpp(source);
20641 let root = tree.root_node();
20642 let mut stack = vec![root];
20643 let mut mis_lexed_opens = Vec::new();
20644 while let Some(node) = stack.pop() {
20645 if node.kind() == "character"
20646 && &source[node.start_byte()..node.end_byte()] == "{"
20647 && node
20648 .parent()
20649 .is_some_and(|parent| parent.kind() != "char_literal")
20650 {
20651 mis_lexed_opens.push(node.start_position().row + 1);
20652 }
20653 let mut cursor = node.walk();
20654 stack.extend(node.children(&mut cursor));
20655 }
20656 assert!(
20657 mis_lexed_opens.is_empty(),
20658 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
20659 the old ABI14 mis-lex is no longer a valid recovery witness"
20660 );
20661 assert_eq!(
20662 real_brace_token_counts(root),
20663 (17, 17),
20664 "the improved parse retains balanced real brace tokens"
20665 );
20666 assert_eq!(root.kind(), "translation_unit");
20667 assert!(root.has_error(), "the malformed body remains incomplete");
20668 let index = OrphanedNamespaceScopeIndex::build(root, source);
20669 assert_eq!(
20670 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
20671 None,
20672 "a missing outer close must not borrow an inner namespace's real close"
20673 );
20674 for (head, close) in [
20675 ("namespace compile_time {", source.len() - 2),
20676 ("namespace number_parsing {", source.len() - 4),
20677 ] {
20678 assert_eq!(
20679 index
20680 .matching_close_brace(namespace_open_byte(source, head))
20681 .map(|range| range.start_byte),
20682 Some(close),
20683 "{head} retains its own real close"
20684 );
20685 }
20686 }
20687
20688 #[test]
20689 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
20690 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
20691 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
20692 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
20693 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
20694 assert!(indexed_namespace_path_is_recoverable(
20695 &["cache".to_string()],
20696 &indexed,
20697 1,
20698 ));
20699 }
20700
20701 #[test]
20702 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
20703 fn sort_lookup_units_totally_orders_every_identity_field() {
20704 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
20705 let base = CodeUnit::with_signature(
20706 file.clone(),
20707 CodeUnitType::Function,
20708 "scope",
20709 "value",
20710 Some("()".to_string()),
20711 false,
20712 );
20713 let different_kind = CodeUnit::with_signature(
20714 file.clone(),
20715 CodeUnitType::Field,
20716 "scope",
20717 "value",
20718 Some("()".to_string()),
20719 false,
20720 );
20721 let synthetic = base.with_synthetic(true);
20722
20723 let interner = segment_interner();
20724 let mut member_fq = FqName::new();
20725 member_fq.push(interner.intern("scope", SegmentKind::Package));
20726 member_fq.push(interner.intern("value", SegmentKind::Member));
20727 let different_package_boundary = CodeUnit::from_fq(
20728 file.clone(),
20729 CodeUnitType::Function,
20730 member_fq,
20731 0,
20732 Some("()".to_string()),
20733 false,
20734 );
20735
20736 let mut unknown_fq = FqName::new();
20737 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
20738 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
20739 let different_segment_kind = CodeUnit::from_fq(
20740 file,
20741 CodeUnitType::Function,
20742 unknown_fq,
20743 1,
20744 Some("()".to_string()),
20745 false,
20746 );
20747
20748 let input = vec![
20749 base,
20750 different_kind,
20751 synthetic,
20752 different_package_boundary,
20753 different_segment_kind,
20754 ];
20755 let mut expected = input.clone();
20756 sort_lookup_units(&mut expected);
20757 assert!(expected.windows(2).all(|pair| {
20758 let mut ordered = pair.to_vec();
20759 sort_lookup_units(&mut ordered);
20760 ordered == pair && pair[0] != pair[1]
20761 }));
20762
20763 let mut reversed = input.clone();
20764 reversed.reverse();
20765 sort_lookup_units(&mut reversed);
20766 assert_eq!(reversed, expected);
20767
20768 let mut rotated = input;
20769 rotated.rotate_left(2);
20770 sort_lookup_units(&mut rotated);
20771 assert_eq!(rotated, expected);
20772 }
20773
20774 #[test]
20775 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
20776 fn structured_conditional_array_bound_preserves_the_real_guard() {
20777 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";
20778 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
20779 let parse = |source: &str| {
20780 let mut parser = Parser::new();
20781 parser
20782 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20783 .expect("C++ grammar");
20784 parser.parse(source, None).expect("fixture tree")
20785 };
20786
20787 let tree = parse(damaged);
20788 let root = tree.root_node();
20789 assert!(
20790 !root.has_error(),
20791 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
20792 root.to_sexp()
20793 );
20794 let target = damaged.find("target").expect("target byte");
20795 let declaration = root
20796 .descendant_for_byte_range(target, target + "target".len())
20797 .and_then(|mut node| {
20798 loop {
20799 if node.kind() == "declaration" {
20800 break Some(node);
20801 }
20802 node = node.parent()?;
20803 }
20804 })
20805 .expect("declaration after the displaced terminator");
20806 let outer = declaration
20807 .parent()
20808 .filter(|node| node.kind() == "preproc_ifdef")
20809 .expect("ordinary outer include guard");
20810 let bound = outer
20811 .named_children(&mut outer.walk())
20812 .filter(|node| node.kind() == "declaration")
20813 .filter_map(|node| node.child_by_field_name("declarator"))
20814 .find(|node| node.kind() == "array_declarator")
20815 .and_then(|node| node.child_by_field_name("size"))
20816 .expect("structured conditional array bound");
20817 assert_eq!(bound.kind(), "preproc_array_size");
20818 let conditional = bound
20819 .named_children(&mut bound.walk())
20820 .find(|node| node.kind() == "preproc_ifdef")
20821 .expect("array-size conditional");
20822 assert!(conditional.end_byte() <= declaration.start_byte());
20823 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20824 assert!(
20825 !std::iter::successors(Some(declaration), |node| node.parent())
20826 .any(|ancestor| same_node(ancestor, conditional)),
20827 "the declaration must not inherit the array-size feature guard"
20828 );
20829 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
20830 assert!(preprocessor_conditional_contains_descendant(
20831 outer,
20832 declaration
20833 ));
20834
20835 let tree = parse(guarded);
20836 let conditional = tree
20837 .root_node()
20838 .named_child(0)
20839 .filter(|node| node.kind() == "preproc_ifdef")
20840 .expect("ordinary conditional");
20841 let declaration = conditional
20842 .named_children(&mut conditional.walk())
20843 .find(|node| node.kind() == "declaration")
20844 .expect("guarded declaration");
20845 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20846 assert!(preprocessor_conditional_contains_descendant(
20847 conditional,
20848 declaration
20849 ));
20850
20851 let damaged_alternative = format!(
20852 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
20853 "UNUSED(value)\n".repeat(64)
20854 );
20855 let tree = parse(&damaged_alternative);
20856 let conditional = tree
20857 .root_node()
20858 .named_child(0)
20859 .filter(|node| node.kind() == "preproc_ifdef")
20860 .expect("outer conditional with an alternative");
20861 assert!(conditional.has_error());
20862 assert!(conditional.child_by_field_name("alternative").is_some());
20863 assert!(
20864 conditional
20865 .child(conditional.child_count() - 1)
20866 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
20867 );
20868 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
20869
20870 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";
20871 let tree = parse(split_declaration);
20872 let root = tree.root_node();
20873 let split = root
20880 .named_children(&mut root.walk())
20881 .find(|node| node.kind() == "type_definition")
20882 .expect("the split declaration folds into one type definition");
20883 assert_eq!(
20884 split.end_position().row + 1,
20885 9,
20886 "the split declaration ends at its own declarator line"
20887 );
20888 let target = split_declaration
20889 .find("static int target")
20890 .expect("target byte");
20891 assert!(split.end_byte() <= target);
20892 let target_node = root
20893 .descendant_for_byte_range(target, target + "static".len())
20894 .expect("target node");
20895 assert!(
20896 !std::iter::successors(Some(target_node), |node| node.parent())
20897 .any(|ancestor| ancestor.kind() == "preproc_ifdef" && ancestor.has_error()),
20898 "the declaration after the shredded split declaration must not \
20899 inherit a damaged conditional ancestor"
20900 );
20901 }
20902
20903 #[test]
20904 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
20905 fn fragmented_reference_guard_is_recovered() {
20906 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";
20907 let mut parser = Parser::new();
20908 parser
20909 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20910 .expect("C++ grammar");
20911 let tree = parser.parse(source, None).expect("fixture tree");
20912 let start = source.rfind("helper").expect("reference byte");
20913 let node = tree
20914 .root_node()
20915 .descendant_for_byte_range(start, start + "helper".len())
20916 .expect("reference node");
20917 let mut expected = HashSet::default();
20918 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
20919 vec![
20920 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
20921 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
20922 ],
20923 )));
20924 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
20925 }
20926
20927 #[test]
20928 #[hardwon(
20929 "#6325: grammar 0.24.4 displaced #else/#elif into a declaration ERROR and the branch boundary vanished"
20930 )]
20931 fn displaced_branch_directives_decide_guards_without_proving_unreadable_branches() {
20932 let parse = |source: &str| {
20933 let mut parser = Parser::new();
20934 parser
20935 .set_language(&tree_sitter_cpp::LANGUAGE.into())
20936 .expect("C++ grammar");
20937 parser.parse(source, None).expect("fixture tree")
20938 };
20939 fn at<'tree>(root: Node<'tree>, source: &str, needle: &str, token: &str) -> Node<'tree> {
20940 let start = source.find(needle).expect("needle") + needle.find(token).expect("token");
20941 root.descendant_for_byte_range(start, start + token.len())
20942 .expect("token node")
20943 }
20944 let guards = |node: Node<'_>, source: &str| preprocessor_guard_environment(node, source);
20945 let set = |items: &[PreprocessorGuard]| items.iter().cloned().collect::<HashSet<_>>();
20946
20947 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";
20951 let tree = parse(source);
20952 let root = tree.root_node();
20953 let x = at(root, source, "int x", "x");
20954 let conditional = std::iter::successors(Some(x), |node| node.parent())
20955 .find(|node| node.kind() == "preproc_ifdef")
20956 .expect("ifdef");
20957 assert!(conditional.child_by_field_name("alternative").is_none());
20958 let directive = cpp_displaced_preprocessor_branch(conditional).expect("displaced #else");
20959 assert_eq!(directive.kind(), "#else");
20960 assert_eq!(directive.parent().map(|node| node.kind()), Some("ERROR"));
20961 let defined = PreprocessorGuard::Defined("A".to_string());
20962 assert_eq!(guards(x, source), Some(set(std::slice::from_ref(&defined))));
20963 let class_name = at(root, source, "class T", "T");
20964 assert_eq!(guards(class_name, source), Some(set(&[defined.negated()])));
20965 let define = at(root, source, "#define MODE", "MODE")
20966 .parent()
20967 .filter(|node| node.kind() == "preproc_def")
20968 .expect("macro event");
20969 let mut cache = PreprocessorContextCache::default();
20970 assert!(matches!(
20971 preprocessor_branch_of_descendant(conditional, define, &mut cache),
20972 PreprocessorBranch::DisplacedElse
20973 ));
20974 assert_eq!(
20975 preprocessor_conditional_family_for_declaration_with_cache(class_name, &mut cache),
20976 Some(conditional),
20977 "a displaced #else completes the family"
20978 );
20979 assert_eq!(
20980 callable_guard_completion_byte(class_name, source),
20981 Some(conditional.end_byte())
20982 );
20983
20984 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";
20988 let tree = parse(source);
20989 let root = tree.root_node();
20990 let x = at(root, source, "int x", "x");
20991 let conditional = std::iter::successors(Some(x), |node| node.parent())
20992 .find(|node| node.kind() == "preproc_if")
20993 .expect("if");
20994 assert!(conditional.child_by_field_name("alternative").is_none());
20995 assert_eq!(
20996 cpp_displaced_preprocessor_branch(conditional).map(|node| node.kind()),
20997 Some("#elif")
20998 );
20999 let truthy = PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy("A".to_string()));
21000 assert_eq!(guards(x, source), Some(set(&[truthy])));
21001 let mut cache = PreprocessorContextCache::default();
21002 for needle in ["class T", "struct T"] {
21003 let name = at(root, source, needle, "T");
21004 assert_eq!(
21005 guards(name, source),
21006 None,
21007 "{needle} after a displaced #elif"
21008 );
21009 assert!(matches!(
21010 preprocessor_branch_of_descendant(conditional, name, &mut cache),
21011 PreprocessorBranch::Unreadable
21012 ));
21013 assert_eq!(
21014 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache),
21015 None,
21016 "{needle}: an unreadable #elif branch does not complete the family"
21017 );
21018 assert_eq!(callable_guard_completion_byte(name, source), None);
21019 }
21020
21021 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";
21024 let tree = parse(source);
21025 let root = tree.root_node();
21026 let start = source.rfind("class T").expect("else class") + "class ".len();
21027 let name = root
21028 .descendant_for_byte_range(start, start + 1)
21029 .expect("else class name");
21030 let elif = std::iter::successors(Some(name), |node| node.parent())
21031 .find(|node| node.kind() == "preproc_elif")
21032 .expect("structured elif");
21033 assert_eq!(
21034 cpp_displaced_preprocessor_branch(elif).map(|node| node.kind()),
21035 Some("#else")
21036 );
21037 assert_eq!(
21038 guards(name, source),
21039 Some(set(&[
21040 PreprocessorGuard::Defined("A".to_string()).negated(),
21041 PreprocessorGuard::Defined("B".to_string()).negated(),
21042 ]))
21043 );
21044 let mut cache = PreprocessorContextCache::default();
21045 assert!(
21046 preprocessor_conditional_family_for_declaration_with_cache(name, &mut cache).is_some()
21047 );
21048 }
21049
21050 #[test]
21051 #[hardwon(
21052 "#2184: grammar 0.24.4 reads a macro-decorated return type cleanly, so the recovered-only same-file alias gate stopped matching"
21053 )]
21054 fn macro_modifier_decorated_type_matches_the_recovered_gate_scope() {
21055 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";
21056 let mut parser = Parser::new();
21057 parser
21058 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21059 .expect("C++ grammar");
21060 let tree = parser.parse(source, None).expect("fixture tree");
21061 let root = tree.root_node();
21062 let type_before = |declared: &str| {
21063 let end = source.find(declared).expect("declared name");
21064 let start = source[..end].rfind('T').expect("type token");
21065 root.descendant_for_byte_range(start, start + 1)
21066 .expect("type node")
21067 };
21068 for declared in ["make(", "value;", "p)", "body("] {
21069 let node = type_before(declared);
21070 assert_eq!(node.kind(), "type_identifier", "{declared}");
21071 assert!(
21072 macro_modifier_decorated_type_node(node),
21073 "{declared}: {}",
21074 node.parent().expect("declaration").to_sexp()
21075 );
21076 }
21077 for declared in ["plain(", "qualified("] {
21078 let node = type_before(declared);
21079 assert!(
21080 !macro_modifier_decorated_type_node(node),
21081 "{declared} is outside the macro-decorated head scope: {}",
21082 node.parent().expect("parent").to_sexp()
21083 );
21084 }
21085 }
21086
21087 #[test]
21088 fn expression_defined_and_ifndef_guards_are_incompatible() {
21089 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
21090 let mut parser = Parser::new();
21091 parser
21092 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21093 .expect("C++ grammar");
21094 let tree = parser.parse(source, None).expect("fixture tree");
21095 let root = tree.root_node();
21096 let selected_start = source.find("selected").expect("selected declaration");
21097 let rejected_start = source.find("rejected").expect("rejected declaration");
21098 let selected = root
21099 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
21100 .expect("selected node");
21101 let rejected = root
21102 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
21103 .expect("rejected node");
21104 let selected_guards =
21105 preprocessor_guard_environment(selected, source).expect("selected guards");
21106 let rejected_guards =
21107 preprocessor_guard_environment(rejected, source).expect("rejected guards");
21108
21109 assert!(
21110 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
21111 "opposite spellings of one macro guard must contradict"
21112 );
21113 }
21114
21115 #[test]
21116 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
21117 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
21118 let source = r#"#ifdef _WIN32
21119#if defined(__cplusplus)
21120extern "C"
21121#endif
21122int platform_api(void);
21123#endif
21124
21125#ifdef _WIN32
21126static int entropy_target(void) { return 0; }
21127#else
21128#ifdef HAVE_COMMON_RANDOM
21129static int other_target(void) { return 0; }
21130#elif defined(HAVE_GETENTROPY)
21131static int entropy_target(void) { return 1; }
21132static int use_entropy(void) { return entropy_target(); }
21133#endif
21134#endif
21135"#;
21136 let mut parser = Parser::new();
21137 parser
21138 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21139 .expect("C++ grammar");
21140 let tree = parser.parse(source, None).expect("fixture tree");
21141 let start = source.rfind("entropy_target()").expect("reference");
21142 let node = tree
21143 .root_node()
21144 .descendant_for_byte_range(start, start + "entropy_target".len())
21145 .expect("reference node");
21146 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
21147 assert!(
21148 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
21149 "{guards:#?}"
21150 );
21151 assert!(
21152 guards.contains(&PreprocessorGuard::Undefined(
21153 "HAVE_COMMON_RANDOM".to_string()
21154 )),
21155 "{guards:#?}"
21156 );
21157 assert!(
21158 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
21159 "{guards:#?}"
21160 );
21161 assert!(
21162 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
21163 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
21164 );
21165 }
21166
21167 #[test]
21168 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
21169 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
21170 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";
21171 let mut parser = Parser::new();
21172 parser
21173 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21174 .expect("C++ grammar");
21175 let tree = parser.parse(source, None).expect("fixture tree");
21176 let root = tree.root_node();
21177 let node_at = |text: &str, start: usize| {
21178 root.descendant_for_byte_range(start, start + text.len())
21179 .expect("token node")
21180 };
21181
21182 let key_start = source.find("case KEY").expect("case label") + "case ".len();
21183 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
21184 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
21185 assert!(!is_ordinary_macro_reference_node(node_at(
21186 "ENABLE_KEYS",
21187 guard_start,
21188 )));
21189 }
21190
21191 #[test]
21192 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21193 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
21194 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";
21195 let mut parser = Parser::new();
21196 parser
21197 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21198 .expect("C++ grammar");
21199 let tree = parser.parse(source, None).expect("fixture tree");
21200 let root = tree.root_node();
21201 let definition_start = source.find("target(void)").expect("definition");
21202 let reference_start = source.rfind("target()").expect("reference");
21203 let definition = root
21204 .descendant_for_byte_range(definition_start, definition_start + "target".len())
21205 .expect("definition node");
21206 let reference = root
21207 .descendant_for_byte_range(reference_start, reference_start + "target".len())
21208 .expect("reference node");
21209 let required =
21210 preprocessor_guard_environment(definition, source).expect("definition guard");
21211 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
21212 assert!(guard_requirements_hold_at_reference(
21213 &required,
21214 Some(&active)
21215 ));
21216 }
21217
21218 #[test]
21219 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
21220 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
21221 let source = "g_autoptr(FuChunkArray) self = make_array();";
21222 let mut parser = Parser::new();
21223 parser
21224 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21225 .expect("C++ grammar");
21226 let tree = parser.parse(source, None).expect("fixture tree");
21227 let statement = tree.root_node().named_child(0).expect("statement");
21228 let binding =
21229 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
21230 assert_eq!(binding.name, "self");
21231 assert_eq!(binding.type_name, "FuChunkArray");
21232 assert_eq!(binding.pointer_depth, 1);
21233
21234 let near_miss = "holder(FuChunkArray) self = make_array();";
21235 let tree = parser.parse(near_miss, None).expect("near-miss tree");
21236 let statement = tree.root_node().named_child(0).expect("statement");
21237 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
21238 }
21239
21240 #[test]
21241 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
21242 fn boolean_guard_normalization_proves_equivalence_and_implication() {
21243 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
21244 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
21245 let negated_windows_branch =
21246 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
21247 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
21248 assert_eq!(negated_windows_branch, portable);
21249
21250 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
21251 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
21252 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
21253 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
21254 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
21255 assert!(fallback_branch.implies(&fallback_declaration));
21256 assert!(
21257 BooleanGuardExpression::Truthy("FEATURE".to_string())
21258 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
21259 );
21260 assert!(
21261 BooleanGuardExpression::Undefined("FEATURE".to_string())
21262 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
21263 );
21264 assert!(
21265 !BooleanGuardExpression::Defined("FEATURE".to_string())
21266 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
21267 );
21268 assert!(!fallback_declaration.implies(&fallback_branch));
21269 }
21270
21271 #[test]
21272 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
21273 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
21274 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";
21275 let mut parser = Parser::new();
21276 parser
21277 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21278 .expect("C++ grammar");
21279 let tree = parser.parse(source, None).expect("fixture tree");
21280 let root = tree.root_node();
21281 let call = |marker: &str| {
21282 let start = source.find(marker).expect("call marker");
21283 let mut node = root
21284 .descendant_for_byte_range(start, start + "helper".len())
21285 .expect("call name node");
21286 loop {
21287 if node.kind() == "call_expression" {
21288 break node;
21289 }
21290 node = node.parent().expect("call expression ancestor");
21291 }
21292 };
21293 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
21294 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
21295 let keyword_call = call("helper(NULL, template); /* bound */");
21296 let keyword_arguments = keyword_call
21297 .child_by_field_name("arguments")
21298 .expect("keyword argument list");
21299 assert_eq!(
21300 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
21301 1
21302 );
21303
21304 let nested_call = call("helper(NULL, template); /* nested bound */");
21305 let nested_arguments = nested_call
21306 .child_by_field_name("arguments")
21307 .expect("nested keyword argument list");
21308 assert_eq!(
21309 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
21310 1
21311 );
21312
21313 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
21314 let lambda_arguments = lambda_call
21315 .child_by_field_name("arguments")
21316 .expect("lambda keyword argument list");
21317 assert_eq!(
21318 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
21319 0,
21320 "a lambda body must not borrow the enclosing collapsed C parameter"
21321 );
21322
21323 assert_eq!(
21324 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
21325 0
21326 );
21327
21328 let unbound_call = call("helper(NULL, template); /* unbound */");
21329 let unbound_arguments = unbound_call
21330 .child_by_field_name("arguments")
21331 .expect("unbound argument list");
21332 assert_eq!(
21333 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
21334 0
21335 );
21336 }
21337
21338 #[test]
21339 #[brokk_bifrost_test_macros::hardwon(
21340 "#4407: collapsed C keyword parameters need structural position proof"
21341 )]
21342 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
21343 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
21344int type_keyword(template *value) {
21345 return helper(NULL, template); /* type */
21346}
21347int qualifier_keyword(const *template) {
21348 return helper(NULL, template); /* qualifier */
21349}
21350int annotation_keyword(wchar_t *value __attribute__((template))) {
21351 return helper(NULL, template); /* annotation */
21352}
21353int default_keyword(wchar_t *value = template) {
21354 return helper(NULL, template); /* default */
21355}
21356int nested_expression(wchar_t *value, (template)) {
21357 return helper(NULL, template); /* nested */
21358}
21359int malformed_head = (wchar_t *template) {
21360 return helper(NULL, template); /* malformed */
21361};
21362"#;
21363 let tree = parse_cpp(source);
21364 let root = tree.root_node();
21365 let call = |marker: &str| {
21366 let start = source.find(marker).expect("call marker");
21367 let mut node = root
21368 .descendant_for_byte_range(start, start + "helper".len())
21369 .expect("call name node");
21370 loop {
21371 if node.kind() == "call_expression" {
21372 break Some(node);
21373 }
21374 let Some(parent) = node.parent() else {
21375 break None;
21376 };
21377 node = parent;
21378 }
21379 };
21380 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
21381 let markers = [
21382 ("helper(NULL, template); /* type */", true),
21383 ("helper(NULL, template); /* qualifier */", false),
21384 ("helper(NULL, template); /* annotation */", false),
21385 ("helper(NULL, template); /* default */", false),
21386 ("helper(NULL, template); /* nested */", false),
21387 ("helper(NULL, template); /* malformed */", false),
21388 ];
21389 let mut exercised = 0;
21390 for (marker, expect_call) in markers {
21391 let Some(keyword_call) = call(marker) else {
21398 assert!(
21399 !expect_call,
21400 "{marker} lost its call_expression; the near-miss matrix changed"
21401 );
21402 continue;
21403 };
21404 assert!(
21405 expect_call,
21406 "{marker} unexpectedly retains a call_expression; the near-miss matrix changed"
21407 );
21408 exercised += 1;
21409 let keyword_arguments = keyword_call
21410 .child_by_field_name("arguments")
21411 .expect("keyword argument list");
21412 assert_eq!(
21413 recovered_c_keyword_argument_count(
21414 &c_file,
21415 keyword_call,
21416 keyword_arguments,
21417 source,
21418 ),
21419 0,
21420 "keyword in {marker} must not prove a parameter binding"
21421 );
21422 }
21423 assert!(
21424 exercised > 0,
21425 "at least one near-miss marker must exercise the count itself"
21426 );
21427 }
21428
21429 #[test]
21430 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21431 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
21432 let source = r#"#define MAKE(type) type *value
21433MAKE(int *);
21434typedef struct Item Item;
21435struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
21436void gen_op_movl(void *s, int first, int second) { }
21437const char *strZ(const char *value) { return value; }
21438int body(void *s) {
21439 MAKE(int *);
21440 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
21441 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
21442 execvp(strZ(value), UNCONSTIFY(char **, args));
21443}
21444 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
21445 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
21446int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
21447 if (d->units) {
21448 switch (target->nodetype) {
21449 case 1:
21450 case 2:
21451 break;
21452 default:
21453 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
21454 }
21455 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
21456 lysdict_remove(ctx, ((struct Item *)target)->units);
21457 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
21458 }
21459 return 0;
21460 }
21461STATIC EFI_STATUS Encode () { return 0; }
21462"#;
21463 let tree = parse_cpp(source);
21464 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
21465 let top_macro = tree
21466 .root_node()
21467 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
21468 .expect("top macro node");
21469 let body_macro_start = source
21470 .match_indices("MAKE(int *);")
21471 .nth(1)
21472 .expect("body macro")
21473 .0;
21474 let body_macro = tree
21475 .root_node()
21476 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
21477 .expect("body macro node");
21478 let function_call_start = source
21479 .find("gen_op_movl(s, offsetof(CPUX86State")
21480 .expect("function call");
21481 let function_call = tree
21482 .root_node()
21483 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
21484 .expect("function call node");
21485 let strz_start = source.find("strZ(value)").expect("nested function call");
21486 let strz = tree
21487 .root_node()
21488 .named_descendant_for_byte_range(strz_start, strz_start + 4)
21489 .expect("nested function call node");
21490 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
21491 let recovered_call = tree
21492 .root_node()
21493 .named_descendant_for_byte_range(
21494 recovered_call_start,
21495 recovered_call_start + "lysdict_remove".len(),
21496 )
21497 .expect("recovered call node");
21498 let binder_start = source.find("Encode").expect("binder");
21499 let binder = tree
21500 .root_node()
21501 .named_descendant_for_byte_range(binder_start, binder_start + 6)
21502 .expect("binder node");
21503
21504 assert!(recovered_c_function_declarator_invocation(top_macro));
21505 assert!(recovered_c_function_declarator_invocation(body_macro));
21506 assert!(recovered_c_function_declarator_invocation(function_call));
21507 assert!(recovered_c_function_declarator_invocation(strz));
21508 assert!(recovered_c_function_declarator_invocation(recovered_call));
21509 assert!(!recovered_c_function_declarator_invocation(binder));
21510 }
21511
21512 #[test]
21513 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
21514 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
21515 let source = r#"typedef int krb5_context;
21516int helper(int first, int second) { return first + second; }
21517static krb5_context ctx;
21518int main(int argc, char **argv) {
21519 int ccinitial;
21520 const char *collection_name, *typename;
21521 typename = helper(ctx, ccinitial);
21522 return 0;
21523}
21524"#;
21525 let tree = parse_cpp(source);
21526 let ctx = tree
21527 .root_node()
21528 .descendant_for_byte_range(
21529 source.find("ctx, ccinitial").expect("ctx argument"),
21530 source.find("ctx, ccinitial").expect("ctx argument") + 3,
21531 )
21532 .expect("ctx node");
21533 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
21534 let ccinitial = tree
21535 .root_node()
21536 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
21537 .expect("sibling node");
21538 let typename = named_node_at(&tree, source, "typename = helper");
21539 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
21540
21541 assert_eq!(ctx.kind(), "type_identifier");
21545 assert!(recovered_c_parenthesized_declarator_reference(ctx));
21546 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
21547 assert!(!recovered_c_parenthesized_declarator_reference(typename));
21548 assert!(!recovered_c_parenthesized_declarator_reference(helper));
21549 }
21550
21551 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
21552 let mut parser = Parser::new();
21553 parser
21554 .set_language(&tree_sitter_cpp::LANGUAGE.into())
21555 .expect("C++ grammar");
21556 let tree = parser.parse(source, None).expect("C++ fixture tree");
21557 let mut stack = vec![tree.root_node()];
21558 while let Some(node) = stack.pop() {
21559 if node.kind() == "enum_specifier" {
21560 return flattened_macro_namespace_components(node, source);
21561 }
21562 let mut cursor = node.walk();
21563 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
21564 stack.extend(children.into_iter().rev());
21565 }
21566 None
21567 }
21568
21569 #[test]
21570 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
21571 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
21572 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21573namespace detail
21574{
21575enum class value_t { null };
21576}
21577NLOHMANN_JSON_NAMESPACE_END
21578NLOHMANN_JSON_NAMESPACE_BEGIN
21579namespace next
21580{
21581struct next_type {};
21582}
21583NLOHMANN_JSON_NAMESPACE_END
21584"#;
21585 assert_eq!(
21586 first_enum_flattened_namespace(complete),
21587 Some(vec!["detail".to_string()])
21588 );
21589
21590 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
21591 assert_eq!(
21592 first_enum_flattened_namespace(&stale_end),
21593 Some(vec!["detail".to_string()]),
21594 "a stale end marker before the begin marker must not replace the intended namespace"
21595 );
21596
21597 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
21598namespace detail
21599{
21600enum class value_t { null };
21601}
21602struct next_type {};
21603"#;
21604 assert_eq!(first_enum_flattened_namespace(incomplete), None);
21605 }
21606}
21607
21608#[cfg(test)]
21624mod lookup_order_properties {
21625 use super::*;
21626 use proptest::prelude::*;
21627
21628 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
21632 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
21633 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
21637 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
21638 const KINDS: [CodeUnitType; 6] = [
21639 CodeUnitType::Class,
21640 CodeUnitType::Function,
21641 CodeUnitType::Field,
21642 CodeUnitType::Module,
21643 CodeUnitType::Macro,
21644 CodeUnitType::FileScope,
21645 ];
21646
21647 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
21650 enum ProbedOrder {
21651 Before,
21652 Tied,
21653 After,
21654 Contradictory,
21657 }
21658
21659 impl ProbedOrder {
21660 fn mirror(self) -> Self {
21661 match self {
21662 ProbedOrder::Before => ProbedOrder::After,
21663 ProbedOrder::After => ProbedOrder::Before,
21664 other => other,
21665 }
21666 }
21667
21668 fn signum(self) -> i8 {
21670 match self {
21671 ProbedOrder::Before => -1,
21672 ProbedOrder::Tied => 0,
21673 ProbedOrder::After => 1,
21674 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
21675 }
21676 }
21677 }
21678
21679 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
21687 if left == right {
21688 return ProbedOrder::Tied;
21691 }
21692 let mut forward = vec![left.clone(), right.clone()];
21693 sort_lookup_units(&mut forward);
21694 let mut backward = vec![right.clone(), left.clone()];
21695 sort_lookup_units(&mut backward);
21696 let left_first = backward[0] == *left;
21697 let right_first = forward[0] == *right;
21698 match (left_first, right_first) {
21699 (true, true) => ProbedOrder::Contradictory,
21700 (true, false) => ProbedOrder::Before,
21701 (false, true) => ProbedOrder::After,
21702 (false, false) => ProbedOrder::Tied,
21703 }
21704 }
21705
21706 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
21707 (
21708 0..ROOT_NAMES.len(),
21709 0..REL_PATHS.len(),
21710 0..KINDS.len(),
21711 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
21712 0..3usize,
21713 0..SIGNATURES.len(),
21714 any::<bool>(),
21715 )
21716 .prop_map(
21717 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
21718 let source = ProjectFile::new(
21719 std::env::temp_dir().join(ROOT_NAMES[root]),
21720 REL_PATHS[rel_path],
21721 );
21722 let interner = segment_interner();
21723 let mut fq = FqName::new();
21724 for (atom, segment_kind) in &segments {
21725 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
21726 }
21727 let package_segment_count = package_prefix % fq.len();
21729 CodeUnit::from_fq(
21730 source,
21731 KINDS[kind],
21732 fq,
21733 package_segment_count,
21734 SIGNATURES[signature].map(str::to_string),
21735 synthetic,
21736 )
21737 },
21738 )
21739 }
21740
21741 proptest! {
21742 #![proptest_config(ProptestConfig::with_cases(256))]
21743
21744 #[test]
21747 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21748 fn lookup_order_is_reflexive_and_dual(
21749 left in code_unit_strategy(),
21750 right in code_unit_strategy(),
21751 ) {
21752 prop_assert_eq!(
21753 probe_order(&left, &left),
21754 ProbedOrder::Tied,
21755 "a unit must tie with itself: {:?}",
21756 left
21757 );
21758 let forward = probe_order(&left, &right);
21759 prop_assert_ne!(
21760 forward,
21761 ProbedOrder::Contradictory,
21762 "comparator put each of these strictly first: left={:?} right={:?}",
21763 left,
21764 right
21765 );
21766 prop_assert_eq!(
21767 probe_order(&right, &left),
21768 forward.mirror(),
21769 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
21770 left,
21771 right
21772 );
21773 }
21774
21775 #[test]
21777 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21778 fn lookup_order_is_transitive(
21779 a in code_unit_strategy(),
21780 b in code_unit_strategy(),
21781 c in code_unit_strategy(),
21782 ) {
21783 let ab = probe_order(&a, &b);
21784 let bc = probe_order(&b, &c);
21785 let ac = probe_order(&a, &c);
21786 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
21787 prop_assert_ne!(
21788 probed,
21789 ProbedOrder::Contradictory,
21790 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
21791 pair,
21792 a,
21793 b,
21794 c
21795 );
21796 }
21797 if ab.signum() <= 0 && bc.signum() <= 0 {
21798 prop_assert!(
21799 ac.signum() <= 0,
21800 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
21801 a={:?} b={:?} c={:?}",
21802 ab,
21803 bc,
21804 ac,
21805 a,
21806 b,
21807 c
21808 );
21809 }
21810 }
21811
21812 #[test]
21815 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21816 fn lookup_order_separates_distinct_identities(
21817 left in code_unit_strategy(),
21818 right in code_unit_strategy(),
21819 ) {
21820 if probe_order(&left, &right) == ProbedOrder::Tied {
21821 prop_assert_eq!(
21822 &left,
21823 &right,
21824 "distinct identities tied, so their order is whatever order they \
21825 arrived in: left_segments={:?} right_segments={:?}",
21826 left.fq_segments_debug(),
21827 right.fq_segments_debug()
21828 );
21829 }
21830 }
21831
21832 #[test]
21835 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
21836 fn lookup_sort_is_permutation_invariant(
21837 units in prop::collection::vec(code_unit_strategy(), 1..=8),
21838 ) {
21839 let mut sorted = units.clone();
21840 sort_lookup_units(&mut sorted);
21841 for rotation in 0..units.len() {
21842 for reversed in [false, true] {
21843 let mut permuted = units.clone();
21844 permuted.rotate_left(rotation);
21845 if reversed {
21846 permuted.reverse();
21847 }
21848 sort_lookup_units(&mut permuted);
21849 prop_assert_eq!(
21850 &permuted,
21851 &sorted,
21852 "sorting a permutation gave a different list \
21853 (rotation={}, reversed={}): input={:?}",
21854 rotation,
21855 reversed,
21856 units
21857 );
21858 }
21859 }
21860 }
21861 }
21862}
21863
21864#[cfg(test)]
21865mod resolver_domain_properties {
21866 use super::*;
21867 use proptest::prelude::*;
21868
21869 proptest! {
21870 #[test]
21873 fn template_substitution_respects_free_variables(
21874 first in 0u16..1000,
21875 second in 1000u16..2000,
21876 unrelated in prop::collection::vec(0u16..1000, 1..8),
21877 rename in "[A-Z][a-z]{0,6}",
21878 ) {
21879 let atom = |value: u16| CppTemplateTerm::Atom {
21880 kind: "number_literal".to_owned(), text: value.to_string(),
21881 };
21882 let term = CppTemplateTerm::Node {
21883 kind: "template_argument_list".to_owned(),
21884 children: vec![CppTemplateTerm::Parameter("T".to_owned()), CppTemplateTerm::Parameter(format!("U{rename}"))],
21885 };
21886 let mut bindings = HashMap::default();
21887 bindings.insert("T".to_owned(), atom(first));
21888 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21889 for (index, value) in unrelated.into_iter().enumerate() {
21890 bindings.insert(format!("unrelated{index}"), atom(value));
21891 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), None);
21892 }
21893 bindings.insert(format!("U{rename}"), atom(second));
21894 let expected = CppTemplateTerm::Node {
21895 kind: "template_argument_list".to_owned(), children: vec![atom(first), atom(second)],
21896 };
21897 prop_assert_eq!(cpp_substitute_template_term(&term, &bindings), Some(expected));
21898 }
21899
21900 #[test]
21903 fn callable_arity_merge_is_a_symmetric_domain_union(
21904 total in 0usize..20,
21905 left_required in 0usize..20,
21906 right_required in 0usize..20,
21907 left_variadic in any::<bool>(),
21908 right_variadic in any::<bool>(),
21909 ) {
21910 let left = CallableArity::new(left_required.min(total), total, left_variadic);
21911 let right = CallableArity::new(right_required.min(total), total, right_variadic);
21912 let merged = merge_compatible_callable_arities(left, right);
21913 prop_assert_eq!(merged, merge_compatible_callable_arities(right, left));
21914 if let Some(merged) = merged {
21915 for count in 0..=total + 2 {
21916 prop_assert_eq!(merged.accepts(count), left.accepts(count) || right.accepts(count));
21917 }
21918 } else {
21919 prop_assert_ne!(left_variadic, right_variadic);
21920 }
21921 }
21922
21923 #[test]
21926 fn guard_environment_conjunction_is_idempotent(names in prop::collection::vec("[A-Z]{1,5}", 1..8)) {
21927 let guards: HashSet<_> = names.into_iter().map(PreprocessorGuard::Defined).collect();
21928 let environment = PreprocessorGuardEnvironment { all: guards.clone(), partial: guards.clone() };
21929 let twice = environment.merged(&environment).unwrap();
21930 prop_assert_eq!(&twice.all, &guards);
21931 prop_assert_eq!(&twice.partial, &guards);
21932 }
21933 }
21934}
21935
21936#[cfg(test)]
21937mod resolver_lookup_key_properties {
21938 use super::*;
21939 use proptest::prelude::*;
21940
21941 proptest! {
21942 #![proptest_config(ProptestConfig::with_cases(32))]
21943
21944 #[test]
21948 fn parsed_nested_types_round_trip_through_bounded_lookup_keys(
21949 namespaces in 0usize..3,
21950 owners in 1usize..5,
21951 suffix in "[a-z]{1,6}",
21952 ) {
21953 let mut source = String::new();
21954 let mut components = Vec::new();
21955 for index in 0..namespaces {
21956 let name = format!("ns{index}_{suffix}");
21957 source.push_str(&format!("namespace {name} {{ "));
21958 components.push(name);
21959 }
21960 for index in 0..owners {
21961 let name = format!("Owner{index}");
21962 source.push_str(&format!("struct {name} {{ "));
21963 components.push(name);
21964 }
21965 source.push_str("struct Leaf {}; ");
21966 for _ in 0..owners { source.push_str("}; "); }
21967 for _ in 0..namespaces { source.push_str("} "); }
21968 components.push("Leaf".to_owned());
21969 source.push_str(&format!("{}* value;", components.join("::")));
21970
21971 let tree = crate::diagnostics::parse_cpp_tree(&source);
21972 prop_assert!(!tree.root_node().has_error());
21973 let file = ProjectFile::new(std::env::temp_dir(), "lookup-keys.cpp");
21974 let parsed = crate::adapter::parse_cpp_file(&file, &source, &tree);
21975 let leaf = parsed.declarations().iter()
21976 .find(|unit| unit.is_class() && unit.identifier() == "Leaf")
21977 .expect("declaration producer indexed the nested type");
21978 let declaration = tree.root_node().named_child(tree.root_node().named_child_count() - 1).unwrap();
21979 prop_assert_eq!(declaration.kind(), "declaration");
21980 let type_node = declaration.child_by_field_name("type").unwrap();
21981 let parts = cpp_type_name_components(type_node, &source).unwrap();
21982 prop_assert_eq!(&parts, &components);
21983 let candidates = cpp_reference_fqn_candidates_from_parts(&parts, TargetKind::Type);
21984 let declaration_key = leaf.fq_name();
21985 prop_assert!(candidates.contains(&declaration_key), "declaration key {:?} absent from bounded query keys {:?}", declaration_key, candidates);
21986 }
21987 }
21988}