1use crate::call_match::{CppArgType, normalize_cpp_type_name};
2use crate::compile_context::CppCompileContext;
3#[cfg(test)]
4use crate::declarations::cpp_displaced_preprocessor_terminator;
5use crate::declarations::{
6 CppComparableNode, CppComparableParameter, CppComparableSlot, CppRecoveredExportClassIndex,
7 cpp_displaced_preprocessor_boundary, cpp_export_macro_token, cpp_template_term,
8 enclosing_cpp_namespace_nodes, node_text, normalize_cpp_whitespace, recovered_class_body_at,
9 recovered_function_like_field_declarator, recovered_pyobject_head_field,
10};
11use crate::graph::CppGraphSource;
12use crate::graph::extractor::ScanCtx;
13use crate::graph::syntax::{
14 function_macro_replacement_span, normalize_macro_continuations,
15 object_macro_replacement_type_references, qualified_name_parts,
16};
17use crate::graph_support::CppSource;
18use crate::imports::{
19 IncludeTargetIndex, include_path_from_node, resolve_include_targets_with_index,
20};
21use crate::source_context::cpp_guard_set_to_runtime;
22use crate::structural::is_cpp_preprocessor_conditional;
23pub use brokk_bifrost_core::analyzer::cpp_facts::{
24 CppClassDeclarationStrength, CppEnumOwnerKind as EnumOwnerKind,
25};
26use brokk_bifrost_core::analyzer::cpp_facts::{
27 CppDeclaredFieldTypeFact as DeclaredFieldTypeFact,
28 CppStructuredAliasTarget as StructuredAliasTarget,
29};
30use brokk_bifrost_core::analyzer::fq_name::{FqName, SegmentKind, segment_interner};
31use brokk_bifrost_core::analyzer::model::{
32 CallableArity, CallableLinkage, CodeUnitType, CppFieldLinkage, CppTemplateExpression,
33 CppTemplateMetadata, CppTemplateParameterMetadata, CppTemplateTerm, Language, LanguageDialect,
34 SignatureMetadata, StructuredTypeName,
35};
36use brokk_bifrost_core::analyzer::pool_memo::PoolSafeMemo;
37use brokk_bifrost_core::analyzer::prepared_syntax::PreparedSyntaxTree;
38#[cfg(test)]
39use brokk_bifrost_core::analyzer::prepared_syntax::{PreparedSourceOrigin, PreparedSyntaxSource};
40use brokk_bifrost_core::analyzer::query_token::QueryToken;
41use brokk_bifrost_core::analyzer::semantic_diagnostics::contains_node as node_range_contains;
42use brokk_bifrost_core::analyzer::structural::adapter_helpers::{field_name_in_parent, node_range};
43use brokk_bifrost_core::analyzer::tree_walk::{
44 ParentIndex, child_by_field_or_named_child_of_kinds, children_iter, first_named_child_of_kind,
45 named_children_iter, node_for_exact_range, push_named_children_reversed,
46};
47use brokk_bifrost_core::analyzer::usages::common::same_node;
48use brokk_bifrost_core::analyzer::usages::local_inference::LocalInferenceEngine;
49use brokk_bifrost_core::analyzer::{CodeUnit, ProjectFile, Range};
50use brokk_bifrost_core::cancellation::CancellationToken;
51use brokk_bifrost_core::hash::{HashMap, HashSet};
52#[cfg(test)]
53use brokk_bifrost_core::text_utils::compute_line_starts;
54use std::borrow::Cow;
55#[cfg(any(test, feature = "test-support"))]
56use std::cell::Cell;
57use std::cell::OnceCell;
58use std::cmp::Ordering as CmpOrdering;
59use std::collections::BTreeSet;
60use std::hash::Hash;
61use std::sync::atomic::{AtomicUsize, Ordering};
62use std::sync::{Arc, Mutex, OnceLock, RwLock};
63use std::time::{Duration, Instant};
64use tree_sitter::{Node, Parser, Tree};
65
66const PREPROCESSOR_CONTEXT_CACHE_CAP: usize = 8192;
67const CALLABLE_METADATA_CACHE_CAP: usize = 512;
68const MACRO_LEXICAL_INDEX_CACHE_CAP: usize = 64;
69
70#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
71struct PreprocessorContextNodeKey {
72 id: usize,
73 start_byte: usize,
74 end_byte: usize,
75}
76
77#[derive(Default)]
86pub(crate) struct PreprocessorContextCache {
87 displaced_boundaries: HashMap<PreprocessorContextNodeKey, Option<usize>>,
88}
89
90impl PreprocessorContextCache {
91 fn displaced_boundary_end(&mut self, conditional: Node<'_>) -> Option<usize> {
92 let key = PreprocessorContextNodeKey {
93 id: conditional.id(),
94 start_byte: conditional.start_byte(),
95 end_byte: conditional.end_byte(),
96 };
97 if let Some(boundary) = self.displaced_boundaries.get(&key) {
98 return *boundary;
99 }
100 let boundary =
101 cpp_displaced_preprocessor_boundary(conditional).map(|boundary| boundary.end_byte);
102 if self.displaced_boundaries.len() < PREPROCESSOR_CONTEXT_CACHE_CAP {
103 self.displaced_boundaries.insert(key, boundary);
104 }
105 boundary
106 }
107}
108
109#[cfg(any(test, feature = "test-support"))]
110thread_local! {
111 static BOUNDED_VISIBILITY_DECLARATION_READ_COUNT: Cell<usize> = const { Cell::new(0) };
112 static BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT: Cell<usize> = const { Cell::new(0) };
113}
114
115#[derive(Clone, Copy, PartialEq, Eq)]
116pub enum TargetKind {
117 Type,
118 Constructor,
119 FreeFunction,
120 Method,
121 GlobalField,
122 MemberField,
123 Macro,
124}
125
126pub enum LexicalTypeResolution {
127 Resolved {
128 unit: CodeUnit,
129 components: Vec<String>,
130 candidates: Vec<CodeUnit>,
131 },
132 Ambiguous,
133 Missing,
134}
135
136#[derive(Clone, Copy)]
137enum TypeCandidateResolution<'a> {
138 Canonical,
139 PreserveAlias,
140 PreserveTarget(&'a CodeUnit),
141}
142
143#[derive(Clone, Copy, Debug, PartialEq, Eq)]
152enum TypeCandidateFailure {
153 Ambiguous,
154 Unresolvable,
155}
156
157impl TypeCandidateFailure {
158 fn lexical_resolution(self) -> LexicalTypeResolution {
159 match self {
160 Self::Ambiguous => LexicalTypeResolution::Ambiguous,
161 Self::Unresolvable => LexicalTypeResolution::Missing,
162 }
163 }
164}
165
166pub enum LexicalCallableValueResolution {
167 Type(CodeUnit),
168 FreeFunction(CodeUnit),
169 Ambiguous,
170 Missing,
171}
172
173pub enum UsingEnumMemberResolution {
174 Resolved { owner: CodeUnit, member: CodeUnit },
175 Ambiguous,
176 Missing,
177}
178
179pub enum NamespaceValueResolution {
180 Resolved,
181 Ambiguous,
182 Missing,
183}
184
185#[derive(Clone, Debug, PartialEq, Eq)]
186pub enum OrdinaryMacroReferenceResolution {
187 Resolved(CodeUnit),
188 Ambiguous,
189 Missing,
190}
191
192#[derive(Clone, Debug, PartialEq, Eq)]
193pub enum RecoveredCReferenceRanges {
194 Complete(Vec<Range>),
195 LimitExceeded,
196}
197
198pub fn resolve_namespace_value(
199 analyzer: &CppGraphSource<'_>,
200 visibility: &VisibilityIndex<'_>,
201 file: &ProjectFile,
202 namespace: &str,
203 name: &str,
204 before_byte: usize,
205) -> NamespaceValueResolution {
206 let mut matches = Vec::new();
207 for candidate in visibility.visible_identifier_candidates(file, name) {
208 if type_owner_of(analyzer, candidate).is_some()
209 || candidate.package_name() != namespace
210 || (candidate.source() == file
211 && !analyzer
212 .ranges(candidate)
213 .iter()
214 .any(|range| range.start_byte < before_byte))
215 || matches
216 .iter()
217 .any(|existing| same_visible_symbol(existing, candidate))
218 {
219 continue;
220 }
221 matches.push(candidate.clone());
222 if matches.len() > 1 {
223 return NamespaceValueResolution::Ambiguous;
224 }
225 }
226 matches
227 .pop()
228 .map(|_| NamespaceValueResolution::Resolved)
229 .unwrap_or(NamespaceValueResolution::Missing)
230}
231
232pub(crate) struct ScopedUsingEnumOwners {
233 scopes: Vec<Vec<CodeUnit>>,
234}
235
236pub(crate) struct SemanticUsingEnumOwners {
241 class_imports: HashMap<CodeUnit, Vec<CodeUnit>>,
242 namespace_imports: HashMap<Vec<String>, Vec<(usize, CodeUnit)>>,
243}
244
245pub(crate) enum SemanticUsingEnumMemberResolution {
246 Class(UsingEnumMemberResolution),
247 Namespace(UsingEnumMemberResolution),
248 Missing,
249}
250
251impl SemanticUsingEnumOwners {
252 pub(crate) fn new() -> Self {
253 Self {
254 class_imports: HashMap::default(),
255 namespace_imports: HashMap::default(),
256 }
257 }
258
259 pub fn import_class(&mut self, class: CodeUnit, enum_owner: CodeUnit) {
260 let imports = self.class_imports.entry(class).or_default();
261 if !imports
262 .iter()
263 .any(|existing| same_visible_symbol(existing, &enum_owner))
264 {
265 imports.push(enum_owner);
266 }
267 }
268
269 pub fn import_namespace(
270 &mut self,
271 namespace: Vec<String>,
272 declaration_byte: usize,
273 enum_owner: CodeUnit,
274 ) {
275 let imports = self.namespace_imports.entry(namespace).or_default();
276 if !imports
277 .iter()
278 .any(|(_, existing)| same_visible_symbol(existing, &enum_owner))
279 {
280 imports.push((declaration_byte, enum_owner));
281 }
282 }
283
284 pub fn resolve_member(
285 &self,
286 visibility: &VisibilityIndex<'_>,
287 file: &ProjectFile,
288 class: Option<&CodeUnit>,
289 namespace: &[String],
290 before_byte: usize,
291 name: &str,
292 ) -> SemanticUsingEnumMemberResolution {
293 if let Some(class) = class
294 && let Some((_, imports)) = self
295 .class_imports
296 .iter()
297 .find(|(owner, _)| same_visible_symbol(owner, class))
298 {
299 let resolution =
300 resolve_using_enum_member_for_owners(visibility, file, imports.iter(), name);
301 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
302 return SemanticUsingEnumMemberResolution::Class(resolution);
303 }
304 }
305 for prefix_len in (0..=namespace.len()).rev() {
306 let Some(imports) = self.namespace_imports.get(&namespace[..prefix_len]) else {
307 continue;
308 };
309 let owners = imports
310 .iter()
311 .filter(|(declaration_byte, _)| *declaration_byte < before_byte)
312 .map(|(_, owner)| owner);
313 let resolution = resolve_using_enum_member_for_owners(visibility, file, owners, name);
314 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
315 return SemanticUsingEnumMemberResolution::Namespace(resolution);
316 }
317 }
318 SemanticUsingEnumMemberResolution::Missing
319 }
320}
321
322fn resolve_using_enum_member_for_owners<'a>(
323 visibility: &VisibilityIndex<'_>,
324 file: &ProjectFile,
325 owners: impl IntoIterator<Item = &'a CodeUnit>,
326 name: &str,
327) -> UsingEnumMemberResolution {
328 let mut matches: Vec<(CodeUnit, CodeUnit)> = Vec::new();
329 for owner in owners {
330 for member in visibility.visible_members_for_owner_name(file, owner, name) {
331 if !member.is_field()
332 || matches.iter().any(|(existing_owner, existing_member)| {
333 same_visible_symbol(existing_owner, owner)
334 && same_visible_symbol(existing_member, member)
335 })
336 {
337 continue;
338 }
339 matches.push((owner.clone(), member.clone()));
340 }
341 }
342 match matches.len() {
343 0 => UsingEnumMemberResolution::Missing,
344 1 => {
345 let (owner, member) = matches.pop().expect("one using-enum match");
346 UsingEnumMemberResolution::Resolved { owner, member }
347 }
348 _ => UsingEnumMemberResolution::Ambiguous,
349 }
350}
351
352impl ScopedUsingEnumOwners {
353 pub(crate) fn new() -> Self {
354 Self {
355 scopes: vec![Vec::new()],
356 }
357 }
358
359 pub fn enter_scope(&mut self) {
360 self.scopes.push(Vec::new());
361 }
362
363 pub fn exit_scope(&mut self) {
364 brokk_bifrost_core::analyzer::usages::exit_nested_scope(&mut self.scopes);
365 }
366
367 pub fn import(&mut self, owner: CodeUnit) {
368 let scope = self
369 .scopes
370 .last_mut()
371 .expect("using-enum scope stack is never empty");
372 if !scope
373 .iter()
374 .any(|existing| same_visible_symbol(existing, &owner))
375 {
376 scope.push(owner);
377 }
378 }
379
380 pub fn resolve_member(
381 &self,
382 visibility: &VisibilityIndex<'_>,
383 file: &ProjectFile,
384 name: &str,
385 ) -> UsingEnumMemberResolution {
386 for scope in self.scopes.iter().rev() {
387 let resolution =
388 resolve_using_enum_member_for_owners(visibility, file, scope.iter(), name);
389 if !matches!(resolution, UsingEnumMemberResolution::Missing) {
390 return resolution;
391 }
392 }
393 UsingEnumMemberResolution::Missing
394 }
395}
396
397#[derive(Clone)]
398pub struct TargetSpec {
399 pub target: CodeUnit,
400 pub kind: TargetKind,
401 pub owner: Option<CodeUnit>,
402 pub member_name: String,
403 pub callable_arity: Option<CallableArity>,
404 pub activated_callable_arities: Vec<ActivatedCallableArity>,
405 pub param_types: Option<Vec<String>>,
406 pub enum_owner_kind: EnumOwnerKind,
407 pub owner_is_forward_declaration: bool,
408 pub callable_has_definition_body: bool,
409}
410
411#[derive(Clone, Copy)]
412pub struct ActivatedCallableArity {
413 pub activation_byte: usize,
414 pub arity: CallableArity,
415}
416
417#[derive(Debug, PartialEq, Eq, Hash)]
418pub struct TypeScanKey {
419 target: LogicalSymbolKey,
420 member_name: String,
421}
422
423#[derive(Clone, Debug, PartialEq, Eq, Hash)]
424struct LogicalSymbolKey {
425 kind: CodeUnitType,
426 fq_name: String,
427 signature: Option<String>,
428}
429
430struct ResolvedTypeOwner {
431 unit: CodeUnit,
432 is_forward_declaration: bool,
433}
434
435impl TargetSpec {
436 pub fn type_scan_key(&self) -> Option<TypeScanKey> {
437 (self.kind == TargetKind::Type).then(|| TypeScanKey {
438 target: logical_symbol_key(&self.target),
439 member_name: self.member_name.clone(),
440 })
441 }
442
443 pub fn from_target(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> Option<Self> {
444 if target.is_class() {
445 return Some(Self::new(
446 target.clone(),
447 TargetKind::Type,
448 Some(target.clone()),
449 target.identifier().to_string(),
450 None,
451 None,
452 ));
453 }
454
455 if target.is_field() {
456 let owner = type_owner_of(analyzer, target);
462 let kind = if owner.is_some() {
463 TargetKind::MemberField
464 } else {
465 TargetKind::GlobalField
466 };
467 let enum_owner_kind = owner
468 .as_ref()
469 .map(|owner| classify_enum_owner(analyzer, owner))
470 .unwrap_or(EnumOwnerKind::NonEnum);
471 let mut spec = Self::new(
472 target.clone(),
473 kind,
474 owner,
475 target.identifier().to_string(),
476 None,
477 None,
478 );
479 spec.enum_owner_kind = enum_owner_kind;
480 return Some(spec);
481 }
482
483 if target.is_function() {
484 let owner_resolution = target_type_owner_resolution(analyzer, target);
487 let owner_is_forward_declaration = owner_resolution
488 .as_ref()
489 .is_some_and(|owner| owner.is_forward_declaration);
490 let owner = owner_resolution.map(|owner| owner.unit);
491 let kind = if owner.as_ref().is_some_and(|owner| {
492 target.identifier() == owner.identifier()
493 || analyzer
494 .cpp
495 .and_then(|cpp| cpp.template_metadata(owner))
496 .is_some_and(|metadata| metadata.primary_name == target.identifier())
497 }) {
498 TargetKind::Constructor
499 } else if owner.is_some() {
500 TargetKind::Method
501 } else {
502 TargetKind::FreeFunction
503 };
504 let mut spec = Self::new(
505 target.clone(),
506 kind,
507 owner,
508 target.identifier().to_string(),
509 cpp_callable_arity(analyzer, target),
510 cpp_callable_parameter_types(analyzer, target),
511 );
512 spec.owner_is_forward_declaration = owner_is_forward_declaration;
513 spec.callable_has_definition_body =
514 callable_target_has_definition_body(analyzer, target);
515 return Some(spec);
516 }
517
518 if target.is_macro() {
519 return Some(Self::new(
520 target.clone(),
521 TargetKind::Macro,
522 None,
523 target.identifier().to_string(),
524 None,
525 None,
526 ));
527 }
528
529 None
530 }
531
532 pub fn with_visible_callable_arities<'a>(
533 &'a self,
534 analyzer: &CppGraphSource<'_>,
535 cpp: &dyn CppSource,
536 visibility: &VisibilityIndex<'_>,
537 file: &ProjectFile,
538 prepared: &PreparedSyntaxTree,
539 ) -> Cow<'a, Self> {
540 let macro_parameter_arity =
541 visibility.callable_parameter_macro_arity(&self.target, self.param_types.as_deref());
542 let activated_callable_arities =
543 visibility.callable_arities_for_target(analyzer, cpp, file, prepared, self);
544 if macro_parameter_arity.is_none() && activated_callable_arities.is_empty() {
545 return Cow::Borrowed(self);
546 }
547 let mut effective = self.clone();
548 if let Some(macro_parameter_arity) = macro_parameter_arity {
549 effective.callable_arity = Some(macro_parameter_arity);
550 }
551 effective.activated_callable_arities = activated_callable_arities;
552 Cow::Owned(effective)
553 }
554
555 pub fn callable_arity_at(&self, byte: usize) -> Option<CallableArity> {
556 let base = self.callable_arity?;
557 Some(
558 self.activated_callable_arities
559 .iter()
560 .filter(|candidate| candidate.activation_byte <= byte)
561 .fold(base, |arity, candidate| {
562 merge_compatible_callable_arities(arity, candidate.arity).unwrap_or(arity)
563 }),
564 )
565 }
566
567 pub fn new(
568 target: CodeUnit,
569 kind: TargetKind,
570 owner: Option<CodeUnit>,
571 member_name: String,
572 callable_arity: Option<CallableArity>,
573 param_types: Option<Vec<String>>,
574 ) -> Self {
575 Self {
576 target,
577 kind,
578 owner,
579 member_name,
580 callable_arity,
581 activated_callable_arities: Vec::new(),
582 param_types,
583 enum_owner_kind: EnumOwnerKind::NonEnum,
584 owner_is_forward_declaration: false,
585 callable_has_definition_body: false,
586 }
587 }
588}
589
590fn callable_target_has_definition_body(analyzer: &CppGraphSource<'_>, target: &CodeUnit) -> bool {
591 analyzer
592 .signature_metadata(target)
593 .iter()
594 .any(|metadata| !metadata.is_declaration_only())
595}
596
597fn logical_symbol_key(unit: &CodeUnit) -> LogicalSymbolKey {
598 LogicalSymbolKey {
599 kind: unit.kind(),
600 fq_name: unit.fq_name(),
601 signature: unit.signature().map(str::to_string),
602 }
603}
604
605fn classify_enum_owner(analyzer: &CppGraphSource<'_>, owner: &CodeUnit) -> EnumOwnerKind {
606 let Some(facts) = analyzer.declaration_source_properties(owner) else {
607 return EnumOwnerKind::NonEnum;
608 };
609 let Some(first) = facts.first() else {
610 return EnumOwnerKind::NonEnum;
611 };
612 if facts.iter().all(|fact| fact.enum_kind == first.enum_kind) {
613 first.enum_kind
614 } else {
615 EnumOwnerKind::NonEnum
616 }
617}
618
619#[derive(Clone, PartialEq, Eq, Hash)]
620pub struct CppScanBinding {
621 pub unit: Option<CodeUnit>,
622 pub type_name: Option<String>,
623 pub indirection: i32,
624}
625
626impl CppScanBinding {
627 pub fn from_unit(unit: CodeUnit, indirection: i32) -> Self {
628 Self::from_type_name(cpp_name_for(&unit), Some(unit), indirection)
629 }
630
631 pub fn from_type_name(type_name: String, unit: Option<CodeUnit>, indirection: i32) -> Self {
632 Self {
633 type_name: Some(type_name),
634 unit,
635 indirection,
636 }
637 }
638
639 pub fn as_arg_type(&self) -> Option<CppArgType> {
640 let name = self
641 .type_name
642 .clone()
643 .or_else(|| self.unit.as_ref().map(cpp_name_for))?;
644 Some(CppArgType {
645 name,
646 unit: self.unit.clone(),
647 indirection: self.indirection,
648 pointee_const: false,
649 })
650 }
651}
652
653type AliasCell = Arc<OnceLock<Box<[CppAlias]>>>;
654pub type OrdinaryTypeImportCell = Arc<EffectiveUsingIndex>;
655pub type MacroEventCell = Arc<OnceLock<Box<[MacroEvent]>>>;
656type MacroIncludeProtectionCell = Arc<OnceLock<MacroIncludeProtection>>;
657type MacroEnvironmentCheckpointCell = Arc<OnceLock<MacroEnvironmentCheckpoints>>;
658type MacroReplacementCache = HashMap<(ProjectFile, usize), Arc<ParsedMacroReplacement>>;
659type MacroLexicalTemplateCache =
660 HashMap<(ProjectFile, usize), Option<crate::graph::macro_lexical::MacroTemplate>>;
661
662type MacroLocalBindingTemplateCache =
663 HashMap<(ProjectFile, usize), Option<Arc<MacroLocalBindingTemplate>>>;
664type MacroReplacementBodyCache = HashMap<(ProjectFile, usize), Option<Arc<ParsedReplacementBody>>>;
665type MacroTypeParameterCache = HashMap<(ProjectFile, usize), Option<Arc<[usize]>>>;
666type StructuredIncludeFactCell = Arc<OnceLock<Arc<[StructuredIncludeFact]>>>;
667
668struct StructuredIncludeFact {
669 start_byte: usize,
670 end_byte: usize,
671 path: String,
672}
673
674#[derive(Clone, Default)]
675pub struct MacroEnvironment {
676 bindings: HashMap<String, MacroBinding>,
677 known_undefined_names: HashSet<String>,
678 build_proven_defines: HashSet<String>,
683 unknown_names: bool,
684 applied_pragma_once_files: HashSet<ProjectFile>,
685 maybe_applied_pragma_once_files: HashSet<ProjectFile>,
686}
687
688pub const MACRO_ENVIRONMENT_CHECKPOINT_STRIDE: usize = 32;
697
698struct MacroEnvironmentCheckpoint {
700 frontier: usize,
702 environment: Arc<MacroEnvironment>,
703}
704
705struct MacroEnvironmentCheckpoints {
714 checkpoints: Vec<MacroEnvironmentCheckpoint>,
715}
716
717impl MacroEnvironmentCheckpoints {
718 fn at_or_before(&self, frontier: usize) -> &MacroEnvironmentCheckpoint {
720 let index = self
721 .checkpoints
722 .partition_point(|checkpoint| checkpoint.frontier <= frontier);
723 assert!(
724 index > 0,
725 "a checkpoint vector starts at frontier zero, which precedes every request"
726 );
727 &self.checkpoints[index - 1]
728 }
729}
730
731fn clone_or_default_file_cell<T: Default + Clone>(
732 cells: &Mutex<HashMap<ProjectFile, T>>,
733 file: &ProjectFile,
734 poison_message: &str,
735) -> T {
736 cells
737 .lock()
738 .expect(poison_message)
739 .entry(file.clone())
740 .or_default()
741 .clone()
742}
743
744impl MacroEnvironment {
745 fn binding(&self, name: &str) -> Option<&MacroBinding> {
746 self.bindings.get(name)
747 }
748
749 fn may_bind(&self, name: &str) -> bool {
750 self.bindings.contains_key(name) || self.unknown_names
751 }
752
753 fn insert(&mut self, name: String, binding: MacroBinding) {
754 self.known_undefined_names.remove(&name);
755 self.bindings.insert(name, binding);
756 }
757
758 fn remove(&mut self, name: &str) {
759 self.bindings.remove(name);
760 self.known_undefined_names.insert(name.to_string());
761 }
762
763 fn remove_known_undefined(&mut self, name: &str) {
764 self.known_undefined_names.remove(name);
765 }
766
767 fn mark_unknown_names(&mut self, source: &ProjectFile, byte: usize) {
768 for binding in self.bindings.values_mut() {
769 *binding = MacroBinding::uncertain_from(binding, source, byte);
770 }
771 self.known_undefined_names.clear();
772 self.build_proven_defines.clear();
777 self.unknown_names = true;
778 }
779
780 fn guard_requirements_may_hold(&self, guards: &HashSet<PreprocessorGuard>) -> bool {
781 guards.iter().all(|guard| self.guard_may_hold(guard))
782 }
783
784 fn guard_may_hold(&self, guard: &PreprocessorGuard) -> bool {
785 let Some(expression) = guard.as_boolean_expression() else {
786 return true;
787 };
788 self.boolean_guard_may_hold(&expression)
789 }
790
791 fn boolean_guard_may_hold(&self, expression: &BooleanGuardExpression) -> bool {
792 match expression {
793 BooleanGuardExpression::Defined(name) => !self.known_undefined_names.contains(name),
794 BooleanGuardExpression::Undefined(name) => {
795 self.bindings
796 .get(name)
797 .is_none_or(|binding| !binding.is_exact())
798 && (!self.build_proven_defines.contains(name)
799 || self.known_undefined_names.contains(name))
800 }
801 BooleanGuardExpression::Truthy(_) | BooleanGuardExpression::Falsy(_) => true,
802 BooleanGuardExpression::Opaque(_)
803 | BooleanGuardExpression::NegatedOpaque(_)
804 | BooleanGuardExpression::Constant(true) => true,
805 BooleanGuardExpression::Constant(false) => false,
806 BooleanGuardExpression::All(expressions) => expressions
807 .iter()
808 .all(|expression| self.boolean_guard_may_hold(expression)),
809 BooleanGuardExpression::Any(expressions) => expressions
810 .iter()
811 .any(|expression| self.boolean_guard_may_hold(expression)),
812 }
813 }
814}
815
816fn any_visible_source_file_matches(
817 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
818 file: &ProjectFile,
819 mut predicate: impl FnMut(&ProjectFile) -> bool,
820) -> bool {
821 match visible_source_files_by_root.get(file) {
822 Some(files) => files.iter().any(predicate),
823 None => predicate(file),
824 }
825}
826
827#[derive(Clone)]
828pub enum EffectiveUsingTarget {
829 Ordinary {
830 name: String,
831 target_components: Vec<String>,
832 global: bool,
833 },
834 Namespace {
835 namespace_components: Vec<String>,
836 global: bool,
837 },
838}
839
840#[derive(Clone)]
841pub struct OrdinaryTypeImport {
842 pub target: EffectiveUsingTarget,
843 pub source: ProjectFile,
844 pub declaration_byte: usize,
845 pub scope_start: usize,
846 pub scope_end: usize,
847 pub scope_depth: usize,
848 pub block_scope: bool,
849 pub lexical_depth: usize,
850 pub declaration_namespace: Vec<String>,
851 pub namespace_scope: Option<Vec<String>>,
852 pub resolved_target_components: Option<Vec<String>>,
853 pub required_guards: HashSet<PreprocessorGuard>,
854}
855
856#[derive(Clone)]
857pub struct ConditionalIncludeProjection {
858 pub activation_byte: usize,
859 pub required_guards: HashSet<PreprocessorGuard>,
860 pub partial_guards: HashSet<PreprocessorGuard>,
864}
865
866#[derive(Default)]
867pub struct SourceUsingIndex {
868 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
869 pub directives: Vec<OrdinaryTypeImport>,
870}
871
872#[derive(Default)]
873pub struct ProjectUsingIndex {
874 pub ordinary_by_name: HashMap<String, Vec<OrdinaryTypeImport>>,
875 pub directives: Vec<OrdinaryTypeImport>,
876}
877
878type EffectiveUsingProjectionCell = Arc<OnceLock<Arc<[OrdinaryTypeImport]>>>;
879
880pub struct EffectiveUsingIndex {
881 projected_by_name: Mutex<HashMap<String, EffectiveUsingProjectionCell>>,
882}
883
884impl EffectiveUsingIndex {
885 fn new(_root: ProjectFile) -> Self {
886 Self {
887 projected_by_name: Mutex::new(HashMap::default()),
888 }
889 }
890
891 pub fn projection_cell(&self, name: &str) -> EffectiveUsingProjectionCell {
892 self.projected_by_name
893 .lock()
894 .expect("C++ effective-using projection cache poisoned")
895 .entry(name.to_string())
896 .or_default()
897 .clone()
898 }
899}
900
901pub enum OrdinaryTypeImportResolution {
902 Resolved {
903 target: CodeUnit,
904 target_components: Vec<String>,
905 lexical_depth: usize,
906 is_direct: bool,
907 },
908 Ambiguous {
909 lexical_depth: usize,
910 },
911 Missing,
912}
913
914type CallableReferenceSpecCell = Arc<OnceLock<Option<TargetSpec>>>;
915type ConditionalIncludeProjectionIndex = HashMap<ProjectFile, Arc<[ConditionalIncludeProjection]>>;
916type ConditionalIncludeProjectionCell = Arc<PoolSafeMemo<ConditionalIncludeProjectionIndex>>;
917type ConditionalIncludeProjectionCache = HashMap<ProjectFile, ConditionalIncludeProjectionCell>;
918type VisibleParserAliasNameSetCell = Arc<OnceLock<HashSet<String>>>;
919type ParserAliasTargetMatchCell = Arc<OnceLock<bool>>;
920type IndexedStructuralClassScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
921type IndexedEnclosingOwnerScopeCache = HashMap<(ProjectFile, usize, usize), Option<Vec<String>>>;
922
923struct ExtractedComparable {
928 shapes: Vec<CppComparableSlot>,
929 suffix: String,
930}
931
932const MAX_COMPARABLE_ALIAS_HOPS: usize = 32;
936
937#[derive(Clone, Copy, Debug, PartialEq, Eq)]
959enum IncludePathAdmission {
960 Proven,
961 Compatible,
962}
963
964impl IncludePathAdmission {
965 fn admits(
966 self,
967 required: &HashSet<PreprocessorGuard>,
968 partial: &HashSet<PreprocessorGuard>,
969 reference_guards: Option<&HashSet<PreprocessorGuard>>,
970 ) -> bool {
971 match self {
972 Self::Proven => guard_requirements_hold_at_reference(required, reference_guards),
973 Self::Compatible => {
974 guard_requirements_hold_at_reference(partial, reference_guards)
975 && guards_compatible_at_reference(required, reference_guards)
976 }
977 }
978 }
979}
980
981pub struct VisibilityIndex<'a> {
985 cpp: &'a dyn CppSource,
986 token: QueryToken<'a>,
987 data: Arc<VisibilityIndexData>,
988}
989
990impl std::ops::Deref for VisibilityIndex<'_> {
991 type Target = VisibilityIndexData;
992
993 fn deref(&self) -> &Self::Target {
994 &self.data
995 }
996}
997
998pub struct VisibilityIndexData {
1001 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
1002 visible_by_identifier: HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>>,
1003 global_field_internal_linkage: HashMap<CodeUnit, bool>,
1004 visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
1005 alias_cells: Mutex<HashMap<ProjectFile, AliasCell>>,
1006 visible_parser_alias_name_sets: RwLock<HashMap<ProjectFile, VisibleParserAliasNameSetCell>>,
1007 parser_alias_target_matches:
1008 RwLock<HashMap<(ProjectFile, String, LogicalSymbolKey), ParserAliasTargetMatchCell>>,
1009 ordinary_type_import_cells: Mutex<HashMap<ProjectFile, OrdinaryTypeImportCell>>,
1010 project_using_index: OnceLock<ProjectUsingIndex>,
1011 callable_reference_specs:
1012 Mutex<HashMap<(ProjectFile, LogicalSymbolKey), CallableReferenceSpecCell>>,
1013 structured_include_fact_cells: Mutex<HashMap<ProjectFile, StructuredIncludeFactCell>>,
1014 include_activation_cells: Mutex<HashMap<(ProjectFile, ProjectFile), Option<usize>>>,
1015 compile_proven_guard_cells: Mutex<HashMap<ProjectFile, Arc<HashSet<PreprocessorGuard>>>>,
1016 include_path_admission_cells: Mutex<HashMap<ProjectFile, IncludePathAdmission>>,
1017 conditional_include_projection_cells: Mutex<ConditionalIncludeProjectionCache>,
1018 #[cfg(any(test, feature = "test-support"))]
1019 conditional_include_projection_index_build_count: AtomicUsize,
1020 #[cfg(any(test, feature = "test-support"))]
1021 conditional_include_projection_state_count: AtomicUsize,
1022 #[cfg(any(test, feature = "test-support"))]
1023 conditional_include_target_state_count: AtomicUsize,
1024 #[cfg(any(test, feature = "test-support"))]
1025 include_activation_build_count: AtomicUsize,
1026 #[cfg(any(test, feature = "test-support"))]
1027 using_donor_activation_count: AtomicUsize,
1028 #[cfg(any(test, feature = "test-support"))]
1029 using_namespace_lookup_count: AtomicUsize,
1030 #[cfg(any(test, feature = "test-support"))]
1031 using_name_candidate_inspection_count: AtomicUsize,
1032 #[cfg(any(test, feature = "test-support"))]
1033 callable_reference_spec_build_count: AtomicUsize,
1034 #[cfg(any(test, feature = "test-support"))]
1035 alias_source_parse_counts: Mutex<HashMap<ProjectFile, usize>>,
1036 #[cfg(any(test, feature = "test-support"))]
1037 visible_parser_alias_name_set_build_count: AtomicUsize,
1038 parser_alias_fallback_calls: AtomicUsize,
1039 parser_alias_fallback_files: AtomicUsize,
1040 parser_alias_source_parses: AtomicUsize,
1041 parser_alias_fallback_elapsed_micros: AtomicUsize,
1042 field_type_facts: Mutex<HashMap<CodeUnit, Option<DeclaredFieldTypeFact>>>,
1043 structured_alias_targets: Mutex<HashMap<CodeUnit, Option<StructuredAliasTarget>>>,
1044 callable_comparables: Mutex<HashMap<CodeUnit, Option<Arc<ExtractedComparable>>>>,
1045 callable_metadata: Mutex<HashMap<CodeUnit, Arc<Vec<SignatureMetadata>>>>,
1051 comparable_name_declarations: Mutex<HashMap<StructuredTypeName, Option<CodeUnit>>>,
1052 indexed_structural_class_scopes: Mutex<IndexedStructuralClassScopeCache>,
1053 indexed_enclosing_owner_scopes: Mutex<IndexedEnclosingOwnerScopeCache>,
1054 precise_parent_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1055 c_tag_kind_cache: Mutex<HashMap<CodeUnit, Option<CppCTagKind>>>,
1056 c_tag_complete_definition_cache: Mutex<HashMap<CodeUnit, Option<CodeUnit>>>,
1057 macro_event_cells: Mutex<HashMap<ProjectFile, MacroEventCell>>,
1058 macro_event_name_sets: Mutex<HashMap<ProjectFile, Arc<HashSet<String>>>>,
1059 pub macro_include_protection_cells: Mutex<HashMap<ProjectFile, MacroIncludeProtectionCell>>,
1060 macro_environment_checkpoints: Mutex<HashMap<ProjectFile, MacroEnvironmentCheckpointCell>>,
1067 macro_replacements: Mutex<MacroReplacementCache>,
1068 macro_local_binding_templates: Mutex<MacroLocalBindingTemplateCache>,
1069 pub(crate) macro_lexical_templates: Mutex<MacroLexicalTemplateCache>,
1070 macro_lexical_indexes:
1071 Mutex<HashMap<ProjectFile, Arc<crate::graph::macro_lexical::MacroLexicalIndex>>>,
1072 macro_replacement_bodies: Mutex<MacroReplacementBodyCache>,
1073 macro_type_parameters: Mutex<MacroTypeParameterCache>,
1074 callable_parameter_macro_arities: Mutex<HashMap<(ProjectFile, String), Option<CallableArity>>>,
1075 #[cfg(any(test, feature = "test-support"))]
1076 pub macro_replacement_parse_count: AtomicUsize,
1077 #[cfg(any(test, feature = "test-support"))]
1078 pub macro_event_application_count: AtomicUsize,
1079 #[cfg(any(test, feature = "test-support"))]
1082 pub macro_environment_checkpoint_build_count: AtomicUsize,
1083 #[cfg(any(test, feature = "test-support"))]
1086 pub macro_environment_copy_count: AtomicUsize,
1087 #[cfg(any(test, feature = "test-support"))]
1088 pub macro_environment_request_count: AtomicUsize,
1089 cpp_template_metadata: HashMap<CodeUnit, CppTemplateMetadata>,
1090 cpp_template_families: HashMap<String, Vec<CodeUnit>>,
1091 #[cfg(any(test, feature = "test-support"))]
1092 qualified_candidate_inspections: AtomicUsize,
1093 #[cfg(any(test, feature = "test-support"))]
1094 target_preserving_type_resolution_count: AtomicUsize,
1095 #[cfg(any(test, feature = "test-support"))]
1096 visibility_identifier_lookup_count: usize,
1097 #[cfg(any(test, feature = "test-support"))]
1098 visibility_identifier_batch_count: usize,
1099}
1100
1101impl Drop for VisibilityIndexData {
1102 fn drop(&mut self) {
1103 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_none() {
1104 return;
1105 }
1106 #[cfg(any(test, feature = "test-support"))]
1107 eprintln!(
1108 "BIFROST_CPP_MACRO_STATS requests={} copies={} checkpoint_builds={} applications={}",
1109 self.macro_environment_request_count.load(Ordering::Relaxed),
1110 self.macro_environment_copy_count.load(Ordering::Relaxed),
1111 self.macro_environment_checkpoint_build_count
1112 .load(Ordering::Relaxed),
1113 self.macro_event_application_count.load(Ordering::Relaxed),
1114 );
1115 let calls = self.parser_alias_fallback_calls.load(Ordering::Relaxed);
1116 if calls == 0 {
1117 return;
1118 }
1119 eprintln!(
1120 "BIFROST_CPP_ALIAS_FALLBACK_STATS calls={} files={} source_parses={} elapsed_ms={}",
1121 calls,
1122 self.parser_alias_fallback_files.load(Ordering::Relaxed),
1123 self.parser_alias_source_parses.load(Ordering::Relaxed),
1124 self.parser_alias_fallback_elapsed_micros
1125 .load(Ordering::Relaxed)
1126 / 1_000,
1127 );
1128 }
1129}
1130
1131#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1132pub enum PreprocessorGuard {
1133 Defined(String),
1134 Undefined(String),
1135 Boolean(BooleanGuardExpression),
1136 Expression(String),
1137 NegatedExpression(String),
1138 Constant(bool),
1139}
1140
1141#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1142pub enum BooleanGuardExpression {
1143 Defined(String),
1144 Undefined(String),
1145 Truthy(String),
1146 Falsy(String),
1147 Opaque(String),
1148 NegatedOpaque(String),
1149 All(Vec<BooleanGuardExpression>),
1150 Any(Vec<BooleanGuardExpression>),
1151 Constant(bool),
1152}
1153
1154impl BooleanGuardExpression {
1155 fn negated(&self) -> Self {
1156 match self {
1157 Self::Defined(name) => Self::Undefined(name.clone()),
1158 Self::Undefined(name) => Self::Defined(name.clone()),
1159 Self::Truthy(name) => Self::Falsy(name.clone()),
1160 Self::Falsy(name) => Self::Truthy(name.clone()),
1161 Self::Opaque(expression) => Self::NegatedOpaque(expression.clone()),
1162 Self::NegatedOpaque(expression) => Self::Opaque(expression.clone()),
1163 Self::All(expressions) => Self::any(expressions.iter().map(Self::negated)),
1164 Self::Any(expressions) => Self::all(expressions.iter().map(Self::negated)),
1165 Self::Constant(value) => Self::Constant(!value),
1166 }
1167 }
1168
1169 fn all(expressions: impl IntoIterator<Item = Self>) -> Self {
1170 Self::normalized(expressions, true)
1171 }
1172
1173 fn any(expressions: impl IntoIterator<Item = Self>) -> Self {
1174 Self::normalized(expressions, false)
1175 }
1176
1177 fn normalized(expressions: impl IntoIterator<Item = Self>, conjunction: bool) -> Self {
1178 let mut normalized = Vec::new();
1179 for expression in expressions {
1180 match expression {
1181 Self::All(nested) if conjunction => normalized.extend(nested),
1182 Self::Any(nested) if !conjunction => normalized.extend(nested),
1183 Self::Constant(value) if value == conjunction => {}
1184 Self::Constant(value) => return Self::Constant(value),
1185 expression => normalized.push(expression),
1186 }
1187 }
1188 normalized.sort_unstable();
1189 normalized.dedup();
1190 match normalized.len() {
1191 0 => Self::Constant(conjunction),
1192 1 => normalized.pop().expect("one Boolean guard expression"),
1193 _ if conjunction => Self::All(normalized),
1194 _ => Self::Any(normalized),
1195 }
1196 }
1197
1198 fn implies(&self, required: &Self) -> bool {
1199 if self == required
1200 || matches!(self, Self::Constant(false))
1201 || matches!(required, Self::Constant(true))
1202 {
1203 return true;
1204 }
1205 if matches!(
1206 (self, required),
1207 (Self::Truthy(active), Self::Defined(required))
1208 | (Self::Undefined(active), Self::Falsy(required))
1209 if active == required
1210 ) {
1211 return true;
1212 }
1213 match self {
1214 Self::Any(active) => active.iter().all(|expression| expression.implies(required)),
1215 Self::All(active) => match required {
1216 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1217 _ => active.iter().any(|expression| expression.implies(required)),
1218 },
1219 _ => match required {
1220 Self::Any(required) => required.iter().any(|expression| self.implies(expression)),
1221 Self::All(required) => required.iter().all(|expression| self.implies(expression)),
1222 _ => false,
1223 },
1224 }
1225 }
1226
1227 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1228 match self {
1229 Self::Defined(name)
1230 | Self::Undefined(name)
1231 | Self::Truthy(name)
1232 | Self::Falsy(name) => name == macro_name,
1233 Self::Opaque(_) | Self::NegatedOpaque(_) => true,
1236 Self::All(expressions) | Self::Any(expressions) => expressions
1237 .iter()
1238 .any(|expression| expression.may_depend_on_macro(macro_name)),
1239 Self::Constant(_) => false,
1240 }
1241 }
1242
1243 pub fn heap_size(&self) -> usize {
1244 match self {
1245 Self::Defined(value)
1246 | Self::Undefined(value)
1247 | Self::Truthy(value)
1248 | Self::Falsy(value)
1249 | Self::Opaque(value)
1250 | Self::NegatedOpaque(value) => value.len(),
1251 Self::All(expressions) | Self::Any(expressions) => {
1252 expressions
1253 .iter()
1254 .fold(std::mem::size_of::<Vec<Self>>(), |size, expression| {
1255 size.saturating_add(std::mem::size_of::<Self>())
1256 .saturating_add(expression.heap_size())
1257 })
1258 }
1259 Self::Constant(_) => 0,
1260 }
1261 }
1262}
1263
1264impl PreprocessorGuard {
1265 fn as_boolean_expression(&self) -> Option<BooleanGuardExpression> {
1266 match self {
1267 Self::Defined(name) => Some(BooleanGuardExpression::Defined(name.clone())),
1268 Self::Undefined(name) => Some(BooleanGuardExpression::Undefined(name.clone())),
1269 Self::Boolean(expression) => Some(expression.clone()),
1270 Self::Constant(value) => Some(BooleanGuardExpression::Constant(*value)),
1271 Self::Expression(_) | Self::NegatedExpression(_) => None,
1272 }
1273 }
1274
1275 fn negated(&self) -> Self {
1276 match self {
1277 Self::Defined(name) => Self::Undefined(name.clone()),
1278 Self::Undefined(name) => Self::Defined(name.clone()),
1279 Self::Boolean(expression) => Self::Boolean(expression.negated()),
1280 Self::Expression(expression) => Self::NegatedExpression(expression.clone()),
1281 Self::NegatedExpression(expression) => Self::Expression(expression.clone()),
1282 Self::Constant(value) => Self::Constant(!value),
1283 }
1284 }
1285
1286 fn may_depend_on_macro(&self, macro_name: &str) -> bool {
1287 match self {
1288 Self::Defined(name) | Self::Undefined(name) => name == macro_name,
1289 Self::Boolean(expression) => expression.may_depend_on_macro(macro_name),
1290 Self::Expression(_) | Self::NegatedExpression(_) => true,
1293 Self::Constant(_) => false,
1294 }
1295 }
1296}
1297
1298#[derive(Clone, PartialEq, Eq)]
1299pub enum MacroDefinition {
1300 Object {
1301 replacement: String,
1302 },
1303 Function {
1304 parameters: Vec<String>,
1305 replacement: String,
1306 },
1307 VariadicFunction {
1308 parameters: Vec<String>,
1309 replacement: String,
1310 },
1311 Unsupported,
1312}
1313
1314#[derive(Clone, Debug, PartialEq, Eq)]
1315pub enum MacroIncludeProtection {
1316 MacroGuard(String),
1317 PragmaOnce,
1318 None,
1319}
1320
1321enum ParsedMacroReplacement {
1322 Parsed { source: String, tree: Tree },
1323 Unsupported,
1324}
1325
1326const MACRO_BODY_SENTINEL_PREFIX: &str = "void __bifrost_macro_body() { ";
1330
1331pub struct ParsedReplacementBody {
1340 pub source: String,
1341 pub tree: Tree,
1342 pub body_offset: usize,
1343 pub parameters: Vec<String>,
1344 original_offsets: Box<[usize]>,
1349}
1350
1351impl ParsedReplacementBody {
1352 pub fn statements(&self) -> Option<Node<'_>> {
1354 first_descendant_of_kind(self.tree.root_node(), "function_definition")?
1355 .child_by_field_name("body")
1356 }
1357
1358 pub fn file_range(&self, node: Node<'_>, replacement_start: usize) -> std::ops::Range<usize> {
1364 assert!(
1365 node.start_byte() >= self.body_offset,
1366 "synthetic sentinel node cannot be mapped to a macro replacement"
1367 );
1368 assert!(
1369 node.end_byte() >= self.body_offset,
1370 "synthetic sentinel node cannot be mapped to a macro replacement"
1371 );
1372 let start_offset = node.start_byte() - self.body_offset;
1373 let end_offset = node.end_byte() - self.body_offset;
1374 assert!(start_offset <= end_offset);
1375 let start_origin = *self
1376 .original_offsets
1377 .get(start_offset)
1378 .expect("replacement node start must have a source mapping");
1379 let end_origin = *self
1380 .original_offsets
1381 .get(end_offset)
1382 .expect("replacement node end must have a source mapping");
1383 assert!(start_origin <= end_origin);
1384 let start = replacement_start + start_origin;
1385 let end = replacement_start + end_origin;
1386 start..end
1387 }
1388
1389 fn expands_variadic_arguments(&self) -> bool {
1396 let mut stack = vec![self.tree.root_node()];
1397 while let Some(node) = stack.pop() {
1398 if matches!(
1399 node.kind(),
1400 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
1401 ) && node_text(node, &self.source) == "__VA_ARGS__"
1402 {
1403 return true;
1404 }
1405 push_named_children_reversed(node, &mut stack);
1406 }
1407 false
1408 }
1409}
1410
1411pub fn parse_cpp_integer_literal(text: &str) -> Option<i128> {
1413 let compact = text.chars().filter(|ch| *ch != '\'').collect::<String>();
1414 let (radix, digits_start, digit_matches): (u32, usize, fn(char) -> bool) =
1415 if compact.starts_with("0x") || compact.starts_with("0X") {
1416 (16, 2, |ch| ch.is_ascii_hexdigit())
1417 } else if compact.starts_with("0b") || compact.starts_with("0B") {
1418 (2, 2, |ch| matches!(ch, '0' | '1'))
1419 } else if compact.starts_with('0') && compact.len() > 1 {
1420 (8, 0, |ch| matches!(ch, '0'..='7'))
1421 } else {
1422 (10, 0, |ch| ch.is_ascii_digit())
1423 };
1424 let digit_len = compact[digits_start..]
1425 .chars()
1426 .take_while(|ch| digit_matches(*ch))
1427 .map(char::len_utf8)
1428 .sum::<usize>();
1429 if digit_len == 0 {
1430 return None;
1431 }
1432 let digits_end = digits_start + digit_len;
1433 if !compact[digits_end..]
1434 .chars()
1435 .all(|ch| matches!(ch, 'u' | 'U' | 'l' | 'L' | 'z' | 'Z'))
1436 {
1437 return None;
1438 }
1439 i128::from_str_radix(&compact[digits_start..digits_end], radix).ok()
1440}
1441
1442#[derive(Clone)]
1443enum MacroLocalBindingTypeTemplate {
1444 Parameter(usize),
1445 Fixed(String),
1446}
1447
1448#[derive(Clone)]
1449struct MacroLocalBindingTemplate {
1450 name: String,
1451 declared_type: MacroLocalBindingTypeTemplate,
1452 pointer_depth: i32,
1453}
1454
1455pub struct MacroLocalBinding<'tree> {
1463 pub name: String,
1464 pub type_name: String,
1465 pub type_node: Option<Node<'tree>>,
1466 pub pointer_depth: i32,
1467 pub proven_unit: Option<CodeUnit>,
1468}
1469
1470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1476pub enum MacroLexicalBindingKind {
1477 Parameter,
1478 Local,
1479}
1480
1481#[derive(Clone, Debug, PartialEq, Eq)]
1482pub struct MacroLexicalBinding {
1483 pub definition: ProjectFile,
1484 pub kind: MacroLexicalBindingKind,
1485 pub name: String,
1486 pub name_range: std::ops::Range<usize>,
1487 pub declaration_range: std::ops::Range<usize>,
1488}
1489
1490#[derive(Clone, Debug, Default, PartialEq, Eq)]
1494pub struct MacroLexicalReferences {
1495 pub references: Vec<(std::ops::Range<usize>, MacroLexicalBinding)>,
1496 pub truncated: bool,
1497 pub cancelled: bool,
1498}
1499
1500fn macro_replacement_type_parameters(
1501 body: &ParsedReplacementBody,
1502 parameters: &[String],
1503) -> Option<Vec<usize>> {
1504 let mut found = Vec::new();
1505 let mut stack = vec![body.tree.root_node()];
1506 while let Some(node) = stack.pop() {
1507 let type_position = node.kind() == "type_identifier"
1508 || (node.kind() == "identifier"
1509 && node.parent().is_some_and(|parent| {
1510 parent.kind() == "type_descriptor"
1511 && parent.child_by_field_name("type") == Some(node)
1512 }));
1513 let offsetof_type_position = node.kind() == "identifier"
1514 && node
1515 .parent()
1516 .filter(|parent| parent.kind() == "argument_list")
1517 .and_then(|arguments| arguments.parent())
1518 .is_some_and(|call| {
1519 call.kind() == "call_expression"
1520 && call
1521 .child_by_field_name("function")
1522 .is_some_and(|function| {
1523 function.kind() == "identifier"
1524 && node_text(function, &body.source) == "offsetof"
1525 })
1526 && call
1527 .child_by_field_name("arguments")
1528 .is_some_and(|arguments| {
1529 argument_children(arguments).next() == Some(node)
1530 })
1531 });
1532 if (type_position || offsetof_type_position)
1533 && let Some(index) = parameters
1534 .iter()
1535 .position(|parameter| parameter == node_text(node, &body.source))
1536 && !found.contains(&index)
1537 {
1538 found.push(index);
1539 }
1540 push_named_children_reversed(node, &mut stack);
1541 }
1542 (!found.is_empty()).then_some(found)
1543}
1544
1545fn macro_replacement_type_parameter(
1546 body: &ParsedReplacementBody,
1547 parameters: &[String],
1548) -> Option<usize> {
1549 let mut parameters = macro_replacement_type_parameters(body, parameters)?;
1550 (parameters.len() == 1).then(|| parameters.pop().unwrap())
1551}
1552
1553pub(crate) fn macro_type_argument_node<'tree>(
1554 node: Node<'tree>,
1555 source: &str,
1556) -> Option<Node<'tree>> {
1557 match node.kind() {
1558 "type_descriptor" => {
1559 let type_child = node
1560 .child_by_field_name("type")
1561 .or_else(|| first_type_child(node))?;
1562 for index in (0..node.named_child_count()).rev() {
1563 let child = node.named_child(index)?;
1564 if child != type_child
1565 && matches!(
1566 child.kind(),
1567 "identifier"
1568 | "type_identifier"
1569 | "qualified_identifier"
1570 | "scoped_type_identifier"
1571 )
1572 {
1573 return Some(child);
1574 }
1575 }
1576 macro_type_argument_node(type_child, source)
1577 }
1578 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier" => {
1579 node.child_by_field_name("name")
1580 }
1581 "identifier" if matches!(node_text(node, source), "struct" | "union") => node
1582 .next_named_sibling()
1583 .filter(|sibling| sibling.is_error() && sibling.named_child_count() == 1)
1584 .and_then(|error| error.named_child(0))
1585 .filter(|name| matches!(name.kind(), "identifier" | "type_identifier")),
1586 _ => cpp_name_component_nodes(node).is_some().then_some(node),
1587 }
1588}
1589
1590fn c_function_macro_argument<'tree>(
1591 node: Node<'tree>,
1592) -> Option<(Node<'tree>, usize, Node<'tree>)> {
1593 let mut current = node;
1594 while let Some(parent) = current.parent() {
1595 if parent.kind() == "argument_list" {
1596 let call = parent.parent().filter(|call| {
1597 call.kind() == "call_expression"
1598 && call.child_by_field_name("arguments") == Some(parent)
1599 && call
1600 .child_by_field_name("function")
1601 .is_some_and(|function| {
1602 function.kind() == "identifier"
1603 && !node_range_contains(function, current)
1604 })
1605 })?;
1606 let mut actuals = argument_children(parent).enumerate();
1607 let (index, argument) = actuals.find(|(_, argument)| {
1608 node_range_contains(*argument, node)
1609 && (argument.start_byte() == current.start_byte()
1610 || argument.end_byte() == current.end_byte())
1611 })?;
1612 return Some((call, index, argument));
1613 }
1614 current = parent;
1615 }
1616 None
1617}
1618
1619fn recognized_c_macro_declarator_binding<'tree>(
1624 statement: Node<'tree>,
1625 source: &str,
1626) -> Option<MacroLocalBinding<'tree>> {
1627 let assignment = match statement.kind() {
1628 "assignment_expression" => statement,
1629 "expression_statement" if statement.named_child_count() == 1 => statement.named_child(0)?,
1630 _ => return None,
1631 };
1632 if assignment.kind() != "assignment_expression" {
1633 return None;
1634 }
1635 let call = assignment.child_by_field_name("left")?;
1636 if call.kind() != "call_expression" {
1637 return None;
1638 }
1639 let function = call.child_by_field_name("function")?;
1640 if function.kind() != "identifier" || node_text(function, source) != "g_autoptr" {
1641 return None;
1642 }
1643 let arguments = call.child_by_field_name("arguments")?;
1644 let mut actuals = argument_children(arguments);
1645 let type_node = actuals.next()?;
1646 if actuals.next().is_some()
1647 || !matches!(
1648 type_node.kind(),
1649 "identifier"
1650 | "type_identifier"
1651 | "qualified_identifier"
1652 | "scoped_type_identifier"
1653 | "template_type"
1654 )
1655 {
1656 return None;
1657 }
1658 let name_node = (0..assignment.named_child_count())
1659 .filter_map(|index| assignment.named_child(index))
1660 .filter(|child| child.kind() == "ERROR")
1661 .filter_map(|error| {
1662 (error.named_child_count() == 1)
1663 .then(|| error.named_child(0))
1664 .flatten()
1665 })
1666 .find(|node| node.kind() == "identifier")?;
1667 let name = node_text(name_node, source).trim();
1668 let type_name = node_text(type_node, source).trim();
1669 if name.is_empty() || type_name.is_empty() {
1670 return None;
1671 }
1672 Some(MacroLocalBinding {
1673 name: name.to_string(),
1674 type_name: type_name.to_string(),
1675 type_node: Some(type_node),
1676 pointer_depth: 1,
1677 proven_unit: None,
1678 })
1679}
1680
1681#[derive(Clone, PartialEq, Eq)]
1682pub struct MacroBinding {
1683 source: ProjectFile,
1684 declaration_byte: usize,
1685 definition: MacroDefinition,
1686 exact: bool,
1687}
1688
1689impl MacroBinding {
1690 fn ambiguous(source: &ProjectFile, declaration_byte: usize) -> Self {
1691 Self {
1692 source: source.clone(),
1693 declaration_byte,
1694 definition: MacroDefinition::Unsupported,
1695 exact: false,
1696 }
1697 }
1698
1699 fn is_exact(&self) -> bool {
1700 self.exact
1701 }
1702
1703 fn uncertain_from(current: &Self, source: &ProjectFile, declaration_byte: usize) -> Self {
1704 Self {
1705 source: source.clone(),
1706 declaration_byte,
1707 definition: current.definition.clone(),
1708 exact: false,
1709 }
1710 }
1711}
1712
1713type OwningPreprocessorConditionals = Box<[usize]>;
1725
1726#[derive(Clone)]
1727pub enum MacroEvent {
1728 Define {
1729 name: String,
1730 binding: MacroBinding,
1731 byte: usize,
1732 conditionals: OwningPreprocessorConditionals,
1733 },
1734 Undef {
1735 name: String,
1736 byte: usize,
1737 conditionals: OwningPreprocessorConditionals,
1738 },
1739 Include {
1740 targets: Vec<ProjectFile>,
1741 byte: usize,
1742 conditionals: OwningPreprocessorConditionals,
1743 },
1744 Invalidate {
1745 byte: usize,
1746 },
1747}
1748
1749impl MacroEvent {
1750 pub fn byte(&self) -> usize {
1751 match self {
1752 Self::Define { byte, .. }
1753 | Self::Undef { byte, .. }
1754 | Self::Include { byte, .. }
1755 | Self::Invalidate { byte } => *byte,
1756 }
1757 }
1758}
1759
1760#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1761pub enum CallArityEvidence {
1762 Exact(usize),
1763 Unknown,
1764}
1765
1766impl CallArityEvidence {
1767 pub fn exact(self) -> Option<usize> {
1768 match self {
1769 Self::Exact(arity) => Some(arity),
1770 Self::Unknown => None,
1771 }
1772 }
1773
1774 pub fn accepts(self, expected: CallableArity) -> Option<bool> {
1775 self.exact().map(|arity| expected.accepts(arity))
1776 }
1777}
1778
1779struct CppAlias {
1780 name: String,
1781 target: String,
1782 namespace: Option<String>,
1783}
1784
1785type ReceiverResolver<'a> = dyn for<'tree> Fn(Node<'tree>, &str) -> Vec<CodeUnit> + 'a;
1786
1787#[derive(Debug, Clone, PartialEq, Eq)]
1791pub enum CppTemplateResolutionError {
1792 AliasCycle { alias: CodeUnit },
1794 ArgumentBinding,
1796 Substitution,
1798 PrimarySelection,
1801 AmbiguousSpecialization { candidates: Vec<CodeUnit> },
1804}
1805
1806fn distinct_visible_symbols<'u>(units: impl Iterator<Item = &'u CodeUnit>) -> Vec<CodeUnit> {
1809 let mut distinct: Vec<CodeUnit> = Vec::new();
1810 for unit in units {
1811 if !distinct
1812 .iter()
1813 .any(|existing| same_visible_symbol(existing, unit))
1814 {
1815 distinct.push(unit.clone());
1816 }
1817 }
1818 distinct
1819}
1820
1821pub fn macro_lexical_references_with_visibility<'visibility, 'source: 'visibility, Factory>(
1824 visibility: Factory,
1825 file: &ProjectFile,
1826 root: Node<'_>,
1827 source: &str,
1828 max_references: usize,
1829 cancelled: impl FnMut() -> bool,
1830) -> MacroLexicalReferences
1831where
1832 Factory: FnOnce() -> &'visibility VisibilityIndex<'source>,
1833{
1834 crate::graph::macro_lexical::all_references(
1835 visibility,
1836 file,
1837 root,
1838 source,
1839 max_references,
1840 cancelled,
1841 )
1842}
1843
1844impl<'a> VisibilityIndex<'a> {
1845 pub fn data(&self) -> Arc<VisibilityIndexData> {
1846 Arc::clone(&self.data)
1847 }
1848
1849 pub fn from_data(
1850 cpp: &'a dyn CppSource,
1851 token: QueryToken<'a>,
1852 data: Arc<VisibilityIndexData>,
1853 ) -> Self {
1854 Self { cpp, token, data }
1855 }
1856
1857 pub fn cpp(&self) -> &'a dyn CppSource {
1858 self.cpp
1859 }
1860
1861 pub fn token(&self) -> QueryToken<'a> {
1863 self.token
1864 }
1865
1866 pub(crate) fn macro_lexical_index(
1870 &self,
1871 file: &ProjectFile,
1872 root: Node<'_>,
1873 source: &str,
1874 ) -> Arc<crate::graph::macro_lexical::MacroLexicalIndex> {
1875 if let Some(index) = self
1876 .macro_lexical_indexes
1877 .lock()
1878 .expect("macro lexical index cache poisoned")
1879 .get(file)
1880 .cloned()
1881 {
1882 debug_assert_eq!(
1883 index.source_len,
1884 source.len(),
1885 "macro lexical index for {file:?} was built from a different source"
1886 );
1887 return index;
1888 }
1889 let index = Arc::new(crate::graph::macro_lexical::build_index(
1890 self, file, root, source,
1891 ));
1892 let mut cache = self
1893 .macro_lexical_indexes
1894 .lock()
1895 .expect("macro lexical index cache poisoned");
1896 if let Some(existing) = cache.get(file).cloned() {
1897 return existing;
1898 }
1899 if cache.len() < MACRO_LEXICAL_INDEX_CACHE_CAP {
1900 cache.insert(file.clone(), Arc::clone(&index));
1901 }
1902 index
1903 }
1904
1905 fn callable_metadata(&self, unit: &CodeUnit) -> Arc<Vec<SignatureMetadata>> {
1911 if let Some(metadata) = self
1912 .callable_metadata
1913 .lock()
1914 .expect("C++ callable metadata cache poisoned")
1915 .get(unit)
1916 .cloned()
1917 {
1918 return metadata;
1919 }
1920 let metadata = Arc::new(self.cpp.stored_signature_metadata(unit));
1921 let mut cache = self
1922 .callable_metadata
1923 .lock()
1924 .expect("C++ callable metadata cache poisoned");
1925 if cache.len() < CALLABLE_METADATA_CACHE_CAP {
1926 cache.insert(unit.clone(), Arc::clone(&metadata));
1927 }
1928 metadata
1929 }
1930
1931 pub fn callable_linkage(&self, unit: &CodeUnit) -> Option<CallableLinkage> {
1933 crate::identity::cpp_callable_linkage_from_metadata(
1934 self.callable_metadata(unit).iter().cloned(),
1935 )
1936 }
1937
1938 pub fn callable_arity(&self, unit: &CodeUnit) -> Option<CallableArity> {
1940 brokk_bifrost_core::analyzer::model::first_callable_arity(
1941 self.callable_metadata(unit).iter().cloned(),
1942 )
1943 }
1944
1945 pub fn callable_parameter_types(&self, unit: &CodeUnit) -> Option<Vec<String>> {
1950 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
1951 self.callable_metadata(unit).iter().cloned(),
1952 )
1953 }
1954
1955 pub fn has_unresolved_include_visible_before(
1963 &self,
1964 file: &ProjectFile,
1965 before_byte: usize,
1966 ) -> bool {
1967 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
1968 return false;
1969 };
1970 let facts = self.structured_include_facts(file, prepared.as_ref());
1971 has_unresolved_include_visible_before_in_prepared(
1972 file,
1973 prepared.as_ref(),
1974 self.cpp.include_target_index(),
1975 &facts,
1976 before_byte,
1977 )
1978 }
1979
1980 fn structured_include_facts(
1981 &self,
1982 file: &ProjectFile,
1983 prepared: &PreparedSyntaxTree,
1984 ) -> Arc<[StructuredIncludeFact]> {
1985 let cell = self
1986 .structured_include_fact_cells
1987 .lock()
1988 .expect("C++ structured include-fact cache poisoned")
1989 .entry(file.clone())
1990 .or_default()
1991 .clone();
1992 Arc::clone(cell.get_or_init(|| collect_structured_include_facts(prepared)))
1993 }
1994
1995 #[cfg(any(test, feature = "test-support"))]
2003 pub fn from_visible_files_for_test(
2004 cpp: &'a dyn CppSource,
2005 token: QueryToken<'a>,
2006 visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
2007 ) -> Self {
2008 let visible_source_files_by_root = visible_by_file
2009 .iter()
2010 .map(|(file, visible)| {
2011 (
2012 file.clone(),
2013 visible
2014 .iter()
2015 .map(|unit| unit.source().clone())
2016 .chain(std::iter::once(file.clone()))
2017 .collect(),
2018 )
2019 })
2020 .collect();
2021 let mut global_field_internal_linkage = HashMap::default();
2022 Self {
2023 cpp,
2024 token,
2025 data: Arc::new(VisibilityIndexData {
2026 visible_by_identifier: build_visible_identifier_index(
2027 &CppGraphSource::from_source(cpp, token),
2028 &visible_by_file,
2029 &visible_source_files_by_root,
2030 &mut global_field_internal_linkage,
2031 ),
2032 global_field_internal_linkage,
2033 visible_by_file,
2034 visible_source_files_by_root,
2035 alias_cells: Mutex::new(HashMap::default()),
2036 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2037 parser_alias_target_matches: RwLock::new(HashMap::default()),
2038 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2039 project_using_index: OnceLock::new(),
2040 callable_reference_specs: Mutex::new(HashMap::default()),
2041 structured_include_fact_cells: Mutex::new(HashMap::default()),
2042 include_activation_cells: Mutex::new(HashMap::default()),
2043 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2044 include_path_admission_cells: Mutex::new(HashMap::default()),
2045 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2046 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2047 conditional_include_projection_state_count: AtomicUsize::new(0),
2048 conditional_include_target_state_count: AtomicUsize::new(0),
2049 include_activation_build_count: AtomicUsize::new(0),
2050 using_donor_activation_count: AtomicUsize::new(0),
2051 using_namespace_lookup_count: AtomicUsize::new(0),
2052 using_name_candidate_inspection_count: AtomicUsize::new(0),
2053 callable_reference_spec_build_count: AtomicUsize::new(0),
2054 alias_source_parse_counts: Mutex::new(HashMap::default()),
2055 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2056 parser_alias_fallback_calls: AtomicUsize::new(0),
2057 parser_alias_fallback_files: AtomicUsize::new(0),
2058 parser_alias_source_parses: AtomicUsize::new(0),
2059 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2060 field_type_facts: Mutex::new(HashMap::default()),
2061 structured_alias_targets: Mutex::new(HashMap::default()),
2062 callable_comparables: Mutex::new(HashMap::default()),
2063 callable_metadata: Mutex::new(HashMap::default()),
2064 comparable_name_declarations: Mutex::new(HashMap::default()),
2065 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2066 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2067 precise_parent_cache: Mutex::new(HashMap::default()),
2068 c_tag_kind_cache: Mutex::new(HashMap::default()),
2069 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2070 macro_event_cells: Mutex::new(HashMap::default()),
2071 macro_event_name_sets: Mutex::new(HashMap::default()),
2072 macro_include_protection_cells: Mutex::new(HashMap::default()),
2073 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2074 macro_replacements: Mutex::new(HashMap::default()),
2075 macro_local_binding_templates: Mutex::new(HashMap::default()),
2076 macro_lexical_templates: Mutex::new(HashMap::default()),
2077 macro_lexical_indexes: Mutex::new(HashMap::default()),
2078 macro_replacement_bodies: Mutex::new(HashMap::default()),
2079 macro_type_parameters: Mutex::new(HashMap::default()),
2080 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2081 macro_replacement_parse_count: AtomicUsize::new(0),
2082 macro_event_application_count: AtomicUsize::new(0),
2083 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2084 macro_environment_copy_count: AtomicUsize::new(0),
2085 macro_environment_request_count: AtomicUsize::new(0),
2086 cpp_template_metadata: HashMap::default(),
2087 cpp_template_families: HashMap::default(),
2088 qualified_candidate_inspections: AtomicUsize::new(0),
2089 target_preserving_type_resolution_count: AtomicUsize::new(0),
2090 visibility_identifier_lookup_count: 0,
2091 visibility_identifier_batch_count: 0,
2092 }),
2093 }
2094 }
2095
2096 fn cpp_source(&self) -> CppGraphSource<'a> {
2103 CppGraphSource::from_source(self.cpp, self.token)
2104 }
2105
2106 pub fn build(
2107 cpp: &'a dyn CppSource,
2108 token: QueryToken<'a>,
2109 analyzer: &CppGraphSource<'_>,
2110 roots: &HashSet<ProjectFile>,
2111 ) -> Self {
2112 Self::build_with_cancellation(cpp, token, analyzer, roots, None)
2113 }
2114
2115 pub fn build_with_cancellation(
2116 cpp: &'a dyn CppSource,
2117 token: QueryToken<'a>,
2118 analyzer: &CppGraphSource<'_>,
2119 roots: &HashSet<ProjectFile>,
2120 cancellation: Option<&CancellationToken>,
2121 ) -> Self {
2122 let visibility_started = Instant::now();
2123 let include_targets = cpp.include_target_index();
2124 let includes_started = Instant::now();
2125 let mut include_graph = IncludeGraph::default();
2126 for root in roots {
2127 include_graph.extend_with(root, cancellation, &mut |file| {
2128 cpp.canonical_include_paths(token, file)
2129 .unwrap_or_default()
2130 .into_iter()
2131 .flat_map(|include| {
2132 resolve_include_targets_with_index(file, &include, include_targets)
2133 })
2134 .collect()
2135 });
2136 }
2137 let include_elapsed = includes_started.elapsed();
2138 let include_file_count = include_graph.files().count();
2139 let visible_source_files_by_root = roots
2140 .iter()
2141 .map(|root| {
2142 (
2143 root.clone(),
2144 include_graph.reachable_files(root, cancellation),
2145 )
2146 })
2147 .collect::<HashMap<_, _>>();
2148 let mut visibility_stats = BoundedVisibilityStats::default();
2149 let mut visible_by_file = build_bounded_visible_declarations(
2150 cpp,
2151 token,
2152 analyzer,
2153 roots,
2154 &visible_source_files_by_root,
2155 cancellation,
2156 &mut visibility_stats,
2157 );
2158 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
2159 eprintln!(
2160 "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={}",
2161 visibility_started.elapsed().as_millis(),
2162 include_elapsed.as_millis(),
2163 include_file_count,
2164 visibility_stats.rounds,
2165 visibility_stats.root_names,
2166 visibility_stats.identifier_lookups,
2167 visibility_stats.identifier_batches,
2168 visibility_stats.candidate_units,
2169 visibility_stats.candidate_sources,
2170 visibility_stats.declaration_reads,
2171 visibility_stats.declaration_units,
2172 visibility_stats.selected_units,
2173 visibility_stats.dependency_source_names,
2174 visibility_stats.dependency_names,
2175 visibility_stats.lookup_elapsed.as_millis(),
2176 visibility_stats.declaration_elapsed.as_millis(),
2177 visibility_stats.dependency_source_elapsed.as_millis(),
2178 );
2179 }
2180 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
2181 let finalize_started = Instant::now();
2182 if report_stats {
2183 eprintln!(
2184 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=started roots={} visible_units={}",
2185 visible_by_file.len(),
2186 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2187 );
2188 }
2189 let owner_started = Instant::now();
2190 if report_stats {
2191 eprintln!("BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=started");
2192 }
2193 let owner_stats = extend_with_out_of_line_owner_bindings(cpp, &mut visible_by_file);
2194 if report_stats {
2195 eprintln!(
2196 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=owners status=completed unseen_owners={} definition_lookups={} admitted={} elapsed_ms={}",
2197 owner_stats.unseen_owners,
2198 owner_stats.definition_lookups,
2199 owner_stats.admitted,
2200 owner_started.elapsed().as_millis(),
2201 );
2202 }
2203 let mut global_field_internal_linkage = HashMap::default();
2204 let identifier_started = Instant::now();
2205 if report_stats {
2206 eprintln!(
2207 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=started"
2208 );
2209 }
2210 let visible_by_identifier = build_visible_identifier_index(
2211 analyzer,
2212 &visible_by_file,
2213 &visible_source_files_by_root,
2214 &mut global_field_internal_linkage,
2215 );
2216 if report_stats {
2217 eprintln!(
2218 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=identifier_index status=completed roots={} names={} candidates={} elapsed_ms={}",
2219 visible_by_identifier.len(),
2220 visible_by_identifier
2221 .values()
2222 .map(HashMap::len)
2223 .sum::<usize>(),
2224 visible_by_identifier
2225 .values()
2226 .flat_map(HashMap::values)
2227 .map(Vec::len)
2228 .sum::<usize>(),
2229 identifier_started.elapsed().as_millis(),
2230 );
2231 }
2232 let mut cpp_template_metadata = HashMap::default();
2233 let metadata_started = Instant::now();
2234 let mut template_classes = 0usize;
2235 if report_stats {
2236 eprintln!(
2237 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=started"
2238 );
2239 }
2240 for unit in visible_by_file
2241 .values()
2242 .flatten()
2243 .filter(|unit| unit.is_class())
2244 {
2245 template_classes += 1;
2246 if cpp_template_metadata.contains_key(unit) {
2247 continue;
2248 }
2249 if let Some(metadata) = cpp.template_metadata(unit) {
2250 cpp_template_metadata.insert(unit.clone(), metadata);
2251 }
2252 }
2253 if report_stats {
2254 eprintln!(
2255 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_metadata status=completed classes={} metadata={} elapsed_ms={}",
2256 template_classes,
2257 cpp_template_metadata.len(),
2258 metadata_started.elapsed().as_millis(),
2259 );
2260 }
2261 let families_started = Instant::now();
2262 if report_stats {
2263 eprintln!(
2264 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=started"
2265 );
2266 }
2267 let mut cpp_template_families: HashMap<String, Vec<CodeUnit>> = HashMap::default();
2268 for (unit, metadata) in &cpp_template_metadata {
2269 cpp_template_families
2270 .entry(metadata.primary_fq_name.clone())
2271 .or_default()
2272 .push(unit.clone());
2273 }
2274 for family in cpp_template_families.values_mut() {
2283 sort_lookup_units(family);
2284 }
2285 if report_stats {
2286 eprintln!(
2287 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS phase=template_families status=completed families={} members={} elapsed_ms={}",
2288 cpp_template_families.len(),
2289 cpp_template_families.values().map(Vec::len).sum::<usize>(),
2290 families_started.elapsed().as_millis(),
2291 );
2292 eprintln!(
2293 "BIFROST_CPP_VISIBILITY_FINALIZE_STATS status=completed roots={} visible_units={} elapsed_ms={} total_ms={}",
2294 visible_by_file.len(),
2295 visible_by_file.values().map(HashSet::len).sum::<usize>(),
2296 finalize_started.elapsed().as_millis(),
2297 visibility_started.elapsed().as_millis(),
2298 );
2299 }
2300 Self {
2301 cpp,
2302 token,
2303 data: Arc::new(VisibilityIndexData {
2304 visible_by_file,
2305 visible_by_identifier,
2306 global_field_internal_linkage,
2307 visible_source_files_by_root,
2308 alias_cells: Mutex::new(HashMap::default()),
2309 visible_parser_alias_name_sets: RwLock::new(HashMap::default()),
2310 parser_alias_target_matches: RwLock::new(HashMap::default()),
2311 ordinary_type_import_cells: Mutex::new(HashMap::default()),
2312 project_using_index: OnceLock::new(),
2313 callable_reference_specs: Mutex::new(HashMap::default()),
2314 structured_include_fact_cells: Mutex::new(HashMap::default()),
2315 include_activation_cells: Mutex::new(HashMap::default()),
2316 compile_proven_guard_cells: Mutex::new(HashMap::default()),
2317 include_path_admission_cells: Mutex::new(HashMap::default()),
2318 conditional_include_projection_cells: Mutex::new(HashMap::default()),
2319 #[cfg(any(test, feature = "test-support"))]
2320 conditional_include_projection_index_build_count: AtomicUsize::new(0),
2321 #[cfg(any(test, feature = "test-support"))]
2322 conditional_include_projection_state_count: AtomicUsize::new(0),
2323 #[cfg(any(test, feature = "test-support"))]
2324 conditional_include_target_state_count: AtomicUsize::new(0),
2325 #[cfg(any(test, feature = "test-support"))]
2326 include_activation_build_count: AtomicUsize::new(0),
2327 #[cfg(any(test, feature = "test-support"))]
2328 using_donor_activation_count: AtomicUsize::new(0),
2329 #[cfg(any(test, feature = "test-support"))]
2330 using_namespace_lookup_count: AtomicUsize::new(0),
2331 #[cfg(any(test, feature = "test-support"))]
2332 using_name_candidate_inspection_count: AtomicUsize::new(0),
2333 #[cfg(any(test, feature = "test-support"))]
2334 callable_reference_spec_build_count: AtomicUsize::new(0),
2335 #[cfg(any(test, feature = "test-support"))]
2336 alias_source_parse_counts: Mutex::new(HashMap::default()),
2337 #[cfg(any(test, feature = "test-support"))]
2338 visible_parser_alias_name_set_build_count: AtomicUsize::new(0),
2339 parser_alias_fallback_calls: AtomicUsize::new(0),
2340 parser_alias_fallback_files: AtomicUsize::new(0),
2341 parser_alias_source_parses: AtomicUsize::new(0),
2342 parser_alias_fallback_elapsed_micros: AtomicUsize::new(0),
2343 field_type_facts: Mutex::new(HashMap::default()),
2344 structured_alias_targets: Mutex::new(HashMap::default()),
2345 callable_comparables: Mutex::new(HashMap::default()),
2346 callable_metadata: Mutex::new(HashMap::default()),
2347 comparable_name_declarations: Mutex::new(HashMap::default()),
2348 indexed_structural_class_scopes: Mutex::new(HashMap::default()),
2349 indexed_enclosing_owner_scopes: Mutex::new(HashMap::default()),
2350 precise_parent_cache: Mutex::new(HashMap::default()),
2351 c_tag_kind_cache: Mutex::new(HashMap::default()),
2352 c_tag_complete_definition_cache: Mutex::new(HashMap::default()),
2353 macro_event_cells: Mutex::new(HashMap::default()),
2354 macro_event_name_sets: Mutex::new(HashMap::default()),
2355 macro_include_protection_cells: Mutex::new(HashMap::default()),
2356 macro_environment_checkpoints: Mutex::new(HashMap::default()),
2357 macro_replacements: Mutex::new(HashMap::default()),
2358 macro_local_binding_templates: Mutex::new(HashMap::default()),
2359 macro_lexical_templates: Mutex::new(HashMap::default()),
2360 macro_lexical_indexes: Mutex::new(HashMap::default()),
2361 macro_replacement_bodies: Mutex::new(HashMap::default()),
2362 macro_type_parameters: Mutex::new(HashMap::default()),
2363 callable_parameter_macro_arities: Mutex::new(HashMap::default()),
2364 #[cfg(any(test, feature = "test-support"))]
2365 macro_replacement_parse_count: AtomicUsize::new(0),
2366 #[cfg(any(test, feature = "test-support"))]
2367 macro_event_application_count: AtomicUsize::new(0),
2368 #[cfg(any(test, feature = "test-support"))]
2369 macro_environment_checkpoint_build_count: AtomicUsize::new(0),
2370 #[cfg(any(test, feature = "test-support"))]
2371 macro_environment_copy_count: AtomicUsize::new(0),
2372 #[cfg(any(test, feature = "test-support"))]
2373 macro_environment_request_count: AtomicUsize::new(0),
2374 cpp_template_metadata,
2375 cpp_template_families,
2376 #[cfg(any(test, feature = "test-support"))]
2377 qualified_candidate_inspections: AtomicUsize::new(0),
2378 #[cfg(any(test, feature = "test-support"))]
2379 target_preserving_type_resolution_count: AtomicUsize::new(0),
2380 #[cfg(any(test, feature = "test-support"))]
2381 visibility_identifier_lookup_count: visibility_stats.identifier_lookups,
2382 #[cfg(any(test, feature = "test-support"))]
2383 visibility_identifier_batch_count: visibility_stats.identifier_batches,
2384 }),
2385 }
2386 }
2387
2388 pub fn is_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
2389 if file == target.source() {
2390 return true;
2391 }
2392 if self.global_field_has_internal_linkage(target) {
2393 return self
2394 .visible_source_files_by_root
2395 .get(file)
2396 .is_some_and(|sources| sources.contains(target.source()));
2397 }
2398 self.visible_by_file
2399 .get(file)
2400 .is_some_and(|visible| visible.iter().any(|unit| same_visible_symbol(unit, target)))
2401 }
2402
2403 fn global_field_has_internal_linkage(&self, unit: &CodeUnit) -> bool {
2404 self.global_field_internal_linkage
2405 .get(unit)
2406 .copied()
2407 .unwrap_or_else(|| cpp_global_field_has_internal_linkage(&self.cpp_source(), unit))
2408 }
2409
2410 pub fn call_arity_evidence(
2411 &self,
2412 file: &ProjectFile,
2413 call: Node<'_>,
2414 source: &str,
2415 ) -> CallArityEvidence {
2416 self.call_arity_evidence_at(file, call, source, call.start_byte())
2417 }
2418
2419 pub fn call_arity_evidence_at(
2427 &self,
2428 file: &ProjectFile,
2429 call: Node<'_>,
2430 source: &str,
2431 environment_byte: usize,
2432 ) -> CallArityEvidence {
2433 let Some(arguments) = call
2434 .child_by_field_name("arguments")
2435 .or_else(|| call.child_by_field_name("parameters"))
2436 .or_else(|| call.child_by_field_name("value"))
2437 .or_else(|| first_named_child_of_kind(call, "argument_list"))
2438 .or_else(|| first_named_child_of_kind(call, "initializer_list"))
2439 else {
2440 return CallArityEvidence::Exact(0);
2441 };
2442 let recovered_c_keyword_arguments =
2443 recovered_c_keyword_argument_count(file, call, arguments, source);
2444 let c_semantics = reference_uses_c_semantics(self.cpp, file);
2445 let arguments = argument_children(arguments)
2446 .flat_map(|argument| {
2447 recovered_c_new_expression_arguments(argument, c_semantics)
2448 .map(Vec::from)
2449 .unwrap_or_else(|| vec![argument])
2450 })
2451 .collect::<Vec<_>>();
2452 if arguments
2453 .iter()
2454 .all(|argument| !argument_shape_may_change_arity(*argument))
2455 {
2456 return CallArityEvidence::Exact(arguments.len() + recovered_c_keyword_arguments);
2457 }
2458 let environment = self.macro_environment(file, environment_byte);
2459 let mut stack = Vec::new();
2460 let mut total = recovered_c_keyword_arguments;
2461 for argument in arguments {
2462 if !macro_expansion_shape_is_safe(argument, source, &[], &environment) {
2463 return CallArityEvidence::Unknown;
2464 }
2465 let CallArityEvidence::Exact(spread) =
2466 self.argument_arity_evidence(argument, source, &environment, &mut stack)
2467 else {
2468 return CallArityEvidence::Unknown;
2469 };
2470 total += spread;
2471 }
2472 CallArityEvidence::Exact(total)
2473 }
2474
2475 fn argument_arity_evidence(
2476 &self,
2477 argument: Node<'_>,
2478 source: &str,
2479 environment: &MacroEnvironment,
2480 stack: &mut Vec<(ProjectFile, usize)>,
2481 ) -> CallArityEvidence {
2482 let (name, invocation_arguments, function_like) = match argument.kind() {
2483 "identifier" => (node_text(argument, source), None, false),
2484 "call_expression" => {
2485 let Some(function) = argument.child_by_field_name("function") else {
2486 return CallArityEvidence::Exact(1);
2487 };
2488 if function.kind() != "identifier" {
2489 return CallArityEvidence::Exact(1);
2490 }
2491 let Some(arguments) = argument.child_by_field_name("arguments") else {
2492 return CallArityEvidence::Exact(1);
2493 };
2494 (node_text(function, source), Some(arguments), true)
2495 }
2496 _ => return CallArityEvidence::Exact(1),
2497 };
2498 let Some(binding) = environment.binding(name) else {
2499 return if environment.unknown_names {
2500 CallArityEvidence::Unknown
2501 } else {
2502 CallArityEvidence::Exact(1)
2503 };
2504 };
2505 if !binding.is_exact() {
2506 return CallArityEvidence::Unknown;
2507 }
2508 match (&binding.definition, invocation_arguments, function_like) {
2509 (MacroDefinition::Object { replacement }, None, false) => self
2510 .replacement_arity_evidence(
2511 replacement,
2512 &[],
2513 &[],
2514 source,
2515 environment,
2516 stack,
2517 binding,
2518 ),
2519 (
2520 MacroDefinition::Function {
2521 parameters,
2522 replacement,
2523 },
2524 Some(arguments),
2525 true,
2526 ) => {
2527 let actuals = argument_children(arguments).collect::<Vec<_>>();
2528 if actuals.len() != parameters.len() {
2529 CallArityEvidence::Unknown
2530 } else {
2531 self.replacement_arity_evidence(
2532 replacement,
2533 parameters,
2534 &actuals,
2535 source,
2536 environment,
2537 stack,
2538 binding,
2539 )
2540 }
2541 }
2542 (MacroDefinition::Function { .. }, None, false) => CallArityEvidence::Exact(1),
2543 _ => CallArityEvidence::Unknown,
2544 }
2545 }
2546
2547 #[allow(clippy::too_many_arguments)]
2548 fn replacement_arity_evidence(
2549 &self,
2550 replacement: &str,
2551 parameters: &[String],
2552 actuals: &[Node<'_>],
2553 actual_source: &str,
2554 environment: &MacroEnvironment,
2555 stack: &mut Vec<(ProjectFile, usize)>,
2556 binding: &MacroBinding,
2557 ) -> CallArityEvidence {
2558 let identity = (binding.source.clone(), binding.declaration_byte);
2559 if stack.contains(&identity) || replacement.trim().is_empty() {
2560 return CallArityEvidence::Unknown;
2561 }
2562 stack.push(identity);
2563 let parsed = self.parsed_macro_replacement(binding, replacement);
2564 let evidence = (|| {
2565 let ParsedMacroReplacement::Parsed {
2566 source: sentinel,
2567 tree,
2568 } = parsed.as_ref()
2569 else {
2570 return None;
2571 };
2572 let call = first_descendant_of_kind(tree.root_node(), "call_expression")?;
2573 let arguments = call.child_by_field_name("arguments")?;
2574 let mut total = 0usize;
2575 for argument in argument_children(arguments) {
2576 if !macro_expansion_shape_is_safe(argument, sentinel, parameters, environment) {
2577 return None;
2578 }
2579 if argument.kind() == "identifier"
2580 && let Some(parameter_index) = parameters
2581 .iter()
2582 .position(|parameter| parameter == node_text(argument, sentinel))
2583 {
2584 if !macro_expansion_shape_is_safe(
2585 actuals[parameter_index],
2586 actual_source,
2587 &[],
2588 environment,
2589 ) {
2590 return None;
2591 }
2592 let CallArityEvidence::Exact(spread) = self.argument_arity_evidence(
2593 actuals[parameter_index],
2594 actual_source,
2595 environment,
2596 stack,
2597 ) else {
2598 return None;
2599 };
2600 total += spread;
2601 continue;
2602 }
2603 let CallArityEvidence::Exact(spread) =
2604 self.argument_arity_evidence(argument, sentinel, environment, stack)
2605 else {
2606 return None;
2607 };
2608 total += spread;
2609 }
2610 Some(CallArityEvidence::Exact(total))
2611 })()
2612 .unwrap_or(CallArityEvidence::Unknown);
2613 stack.pop();
2614 evidence
2615 }
2616
2617 fn parsed_macro_replacement(
2618 &self,
2619 binding: &MacroBinding,
2620 replacement: &str,
2621 ) -> Arc<ParsedMacroReplacement> {
2622 let key = (binding.source.clone(), binding.declaration_byte);
2623 let mut cache = self
2624 .macro_replacements
2625 .lock()
2626 .expect("C++ macro replacement cache poisoned");
2627 if let Some(parsed) = cache.get(&key) {
2628 return Arc::clone(parsed);
2629 }
2630 #[cfg(any(test, feature = "test-support"))]
2631 self.macro_replacement_parse_count
2632 .fetch_add(1, Ordering::Relaxed);
2633 let source =
2634 format!("void __bifrost_macro_arity() {{ __bifrost_macro_call({replacement}); }}");
2635 let mut parser = Parser::new();
2636 let parsed = parser
2637 .set_language(&tree_sitter_cpp::LANGUAGE.into())
2638 .ok()
2639 .and_then(|()| parser.parse(&source, None))
2640 .filter(|tree| !tree.root_node().has_error())
2641 .map_or(ParsedMacroReplacement::Unsupported, |tree| {
2642 ParsedMacroReplacement::Parsed { source, tree }
2643 });
2644 let parsed = Arc::new(parsed);
2645 cache.insert(key, Arc::clone(&parsed));
2646 parsed
2647 }
2648
2649 pub fn function_macro_local_binding<'tree>(
2659 &self,
2660 file: &ProjectFile,
2661 statement: Node<'tree>,
2662 source: &str,
2663 ) -> Option<MacroLocalBinding<'tree>> {
2664 if !is_c_source_file(file) {
2665 return None;
2666 }
2667 if let Some(binding) = recognized_c_macro_declarator_binding(statement, source) {
2668 return Some(binding);
2669 }
2670 let call = match statement.kind() {
2671 "call_expression" => statement,
2672 "expression_statement" if statement.named_child_count() == 1 => {
2673 statement.named_child(0)?
2674 }
2675 _ => return None,
2676 };
2677 if call.kind() != "call_expression" {
2678 return None;
2679 }
2680 let function = call.child_by_field_name("function")?;
2681 if function.kind() != "identifier" {
2682 return None;
2683 }
2684 let arguments = call.child_by_field_name("arguments")?;
2685 let actuals = argument_children(arguments).collect::<Vec<_>>();
2686 let environment = self.macro_environment(file, call.start_byte());
2687 let function_name = node_text(function, source);
2688 let binding = environment.binding(function_name)?;
2689 let MacroDefinition::Function {
2690 parameters,
2691 replacement,
2692 } = &binding.definition
2693 else {
2694 return None;
2695 };
2696 if actuals.len() != parameters.len() {
2697 return None;
2698 }
2699 let template = self.macro_local_binding_template(binding, parameters, replacement)?;
2700 let (type_name, type_node) = match &template.declared_type {
2701 MacroLocalBindingTypeTemplate::Parameter(index) => {
2702 let actual = *actuals.get(*index)?;
2703 if !macro_expansion_shape_is_safe(actual, source, &[], &environment) {
2704 return None;
2705 }
2706 (node_text(actual, source).trim().to_string(), Some(actual))
2707 }
2708 MacroLocalBindingTypeTemplate::Fixed(type_name) => (type_name.clone(), None),
2709 };
2710 if type_name.is_empty() {
2711 return None;
2712 }
2713 Some(MacroLocalBinding {
2714 name: template.name.clone(),
2715 type_name,
2716 type_node,
2717 pointer_depth: template.pointer_depth,
2718 proven_unit: None,
2719 })
2720 }
2721
2722 pub fn function_macro_container_binding<'tree>(
2731 &self,
2732 analyzer: &CppGraphSource<'_>,
2733 file: &ProjectFile,
2734 assignment: Node<'tree>,
2735 source: &str,
2736 ) -> Option<MacroLocalBinding<'tree>> {
2737 if !is_c_source_file(file) || assignment.kind() != "assignment_expression" {
2738 return None;
2739 }
2740 let name_node = assignment.child_by_field_name("left")?;
2741 if name_node.kind() != "identifier" {
2742 return None;
2743 }
2744 let call = assignment.child_by_field_name("right")?;
2745 if call.kind() != "call_expression" {
2746 return None;
2747 }
2748 let function = call.child_by_field_name("function")?;
2749 if function.kind() != "identifier" {
2750 return None;
2751 }
2752 let arguments = call.child_by_field_name("arguments")?;
2753 let actuals = argument_children(arguments).collect::<Vec<_>>();
2754 let function_name = node_text(function, source);
2755 let environment = self.macro_environment(file, call.start_byte());
2756 let binding = environment.binding(function_name)?;
2757 if !binding.is_exact() {
2758 return None;
2759 }
2760 let MacroDefinition::Function {
2761 parameters,
2762 replacement,
2763 } = &binding.definition
2764 else {
2765 return None;
2766 };
2767 if actuals.len() != parameters.len() {
2768 return None;
2769 }
2770 let body = self.parsed_macro_replacement_body(
2771 &(binding.source.clone(), binding.declaration_byte),
2772 parameters,
2773 replacement,
2774 )?;
2775 let type_parameter = macro_replacement_type_parameter(&body, parameters)?;
2776 let type_argument = *actuals.get(type_parameter)?;
2777 let type_node = macro_type_argument_node(type_argument, source)?;
2778 let type_name = node_text(type_node, source).trim().to_string();
2779 if type_name.is_empty() {
2780 return None;
2781 }
2782 let explicit_tag = match node_text(type_argument, source) {
2783 "struct" => Some(CppCTagKind::Struct),
2784 "union" => Some(CppCTagKind::Union),
2785 _ => None,
2786 };
2787 let resolved_type = if let Some(tag) = explicit_tag {
2788 let candidates = self
2789 .visible_identifier_candidates(file, &type_name)
2790 .filter(|candidate| self.cached_c_tag_kind(analyzer, candidate) == Some(tag))
2791 .collect::<Vec<_>>();
2792 self.resolve_type_candidates(
2793 analyzer,
2794 file,
2795 &candidates,
2796 TypeCandidateResolution::Canonical,
2797 )
2798 .ok()
2799 } else {
2800 self.resolve_type_node_result(file, type_node, source)
2801 .ok()
2802 .flatten()
2803 };
2804 let proven_unit = resolved_type?;
2805 Some(MacroLocalBinding {
2806 name: node_text(name_node, source).to_string(),
2807 type_name,
2808 type_node: Some(type_node),
2809 pointer_depth: 1,
2810 proven_unit: Some(proven_unit),
2811 })
2812 }
2813
2814 pub fn macro_local_binding_at<'tree>(
2817 &self,
2818 file: &ProjectFile,
2819 root: Node<'tree>,
2820 source: &str,
2821 start_byte: usize,
2822 end_byte: usize,
2823 ) -> Option<MacroLocalBinding<'tree>> {
2824 crate::graph::macro_lexical::typed_binding(self, file, root, source, start_byte, end_byte)
2825 }
2826
2827 pub fn macro_lexical_binding(
2831 &self,
2832 file: &ProjectFile,
2833 root: Node<'_>,
2834 source: &str,
2835 start_byte: usize,
2836 end_byte: usize,
2837 ) -> Option<MacroLexicalBinding> {
2838 crate::graph::macro_lexical::binding(self, file, root, source, start_byte, end_byte)
2839 }
2840
2841 pub fn macro_lexical_references(
2846 &self,
2847 file: &ProjectFile,
2848 root: Node<'_>,
2849 source: &str,
2850 max_references: usize,
2851 cancelled: impl FnMut() -> bool,
2852 ) -> MacroLexicalReferences {
2853 crate::graph::macro_lexical::all_references(
2854 || self,
2855 file,
2856 root,
2857 source,
2858 max_references,
2859 cancelled,
2860 )
2861 }
2862
2863 pub(crate) fn function_macro_binding_at(
2868 &self,
2869 file: &ProjectFile,
2870 name: &str,
2871 before_byte: usize,
2872 ) -> Option<(ProjectFile, usize)> {
2873 let environment = self.macro_environment(file, before_byte);
2874 let binding = environment.binding(name)?;
2875 if binding.is_exact()
2876 && matches!(
2877 binding.definition,
2878 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2879 )
2880 {
2881 return Some((binding.source.clone(), binding.declaration_byte));
2882 }
2883 if binding.source != *file {
2889 return None;
2890 }
2891 let prepared = self.cpp.prepared_syntax(self.token, file)?;
2892 let root = prepared.tree().root_node();
2893 let source = prepared.source();
2894 let reference = root.descendant_for_byte_range(
2895 before_byte,
2896 before_byte.saturating_add(1).min(source.len()),
2897 )?;
2898 let reference_conditions = owning_preprocessor_conditionals(root, reference, source);
2899 let cell = self.macro_event_cell(file);
2900 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
2901 let event = events
2902 .iter()
2903 .find(|event| event.byte() == binding.declaration_byte)?;
2904 let MacroEvent::Define {
2905 name: defined_name,
2906 binding: definition,
2907 conditionals,
2908 ..
2909 } = event
2910 else {
2911 return None;
2912 };
2913 if defined_name != name
2914 || conditionals.is_empty()
2915 || !conditionals
2916 .iter()
2917 .all(|condition| reference_conditions.contains(condition))
2918 || !matches!(
2919 definition.definition,
2920 MacroDefinition::Function { .. } | MacroDefinition::VariadicFunction { .. }
2921 )
2922 {
2923 return None;
2924 }
2925 Some((definition.source.clone(), definition.declaration_byte))
2926 }
2927
2928 pub fn function_macro_type_argument<'tree>(
2935 &self,
2936 file: &ProjectFile,
2937 node: Node<'tree>,
2938 source: &str,
2939 ) -> Option<Node<'tree>> {
2940 if !is_c_source_file(file) {
2941 return None;
2942 }
2943 let (call, argument_index, argument) = c_function_macro_argument(node)?;
2944 let function = call.child_by_field_name("function")?;
2945 let function_name = node_text(function, source);
2946 if function_name.is_empty() {
2947 return None;
2948 }
2949 if !self.file_defines_macro_name(file, function_name)
2950 && !self
2951 .visible_identifier_candidates(file, function_name)
2952 .any(|candidate| candidate.is_macro())
2953 {
2954 return None;
2955 }
2956 let environment = self.macro_environment(file, call.start_byte());
2957 let binding = environment.binding(function_name)?;
2958 if !binding.is_exact() {
2959 return None;
2960 }
2961 let (parameters, replacement, variadic) = match &binding.definition {
2962 MacroDefinition::Function {
2963 parameters,
2964 replacement,
2965 } => (parameters, replacement, false),
2966 MacroDefinition::VariadicFunction {
2967 parameters,
2968 replacement,
2969 } => (parameters, replacement, true),
2970 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
2971 };
2972 let actuals = call
2973 .child_by_field_name("arguments")
2974 .map(argument_children)
2975 .into_iter()
2976 .flatten()
2977 .collect::<Vec<_>>();
2978 let arity_matches = if variadic {
2979 actuals.len() >= parameters.len()
2980 } else {
2981 actuals.len() == parameters.len()
2982 };
2983 let type_parameters = self.macro_type_parameter_indices(
2984 &(binding.source.clone(), binding.declaration_byte),
2985 parameters,
2986 replacement,
2987 )?;
2988 if !arity_matches || !type_parameters.contains(&argument_index) {
2989 return None;
2990 }
2991 let type_node = macro_type_argument_node(argument, source)?;
2992 if self.names_a_macro_at(file, node_text(type_node, source), type_node.start_byte()) {
2993 return None;
2994 }
2995 Some(type_node)
2996 }
2997
2998 fn macro_local_binding_template(
2999 &self,
3000 binding: &MacroBinding,
3001 parameters: &[String],
3002 replacement: &str,
3003 ) -> Option<Arc<MacroLocalBindingTemplate>> {
3004 let key = (binding.source.clone(), binding.declaration_byte);
3005 if let Some(template) = self
3006 .macro_local_binding_templates
3007 .lock()
3008 .expect("C++ macro local-binding cache poisoned")
3009 .get(&key)
3010 {
3011 return template.clone();
3012 }
3013 let template = (|| {
3014 let body = self.parsed_macro_replacement_body(&key, parameters, replacement)?;
3015 let sentinel = body.source.as_str();
3016 let statements = body.statements()?;
3017 if statements.named_child_count() != 1 {
3018 return None;
3019 }
3020 let declaration = statements.named_child(0)?;
3021 if declaration.kind() != "declaration" {
3022 return None;
3023 }
3024 let type_node = declaration
3025 .child_by_field_name("type")
3026 .or_else(|| first_type_child(declaration))?;
3027 let declarator = declaration.child_by_field_name("declarator").or_else(|| {
3028 let mut cursor = declaration.walk();
3029 declaration.named_children(&mut cursor).find_map(|child| {
3030 if child.kind() == "init_declarator" {
3031 child.child_by_field_name("declarator")
3032 } else {
3033 is_declarator_node(child).then_some(child)
3034 }
3035 })
3036 })?;
3037 let name = extract_variable_name(declarator, sentinel)?;
3038 let pointer_depth =
3039 declared_name_binding(declaration, type_node, &name, sentinel)?.pointer_depth;
3040 let type_text = node_text(type_node, sentinel).trim();
3041 let declared_type = parameters
3042 .iter()
3043 .position(|parameter| parameter == type_text)
3044 .map(MacroLocalBindingTypeTemplate::Parameter)
3045 .unwrap_or_else(|| MacroLocalBindingTypeTemplate::Fixed(type_text.to_string()));
3046 Some(Arc::new(MacroLocalBindingTemplate {
3047 name,
3048 declared_type,
3049 pointer_depth,
3050 }))
3051 })();
3052 self.macro_local_binding_templates
3053 .lock()
3054 .expect("C++ macro local-binding cache poisoned")
3055 .insert(key, template.clone());
3056 template
3057 }
3058
3059 pub fn function_macro_replacement_body(
3066 &self,
3067 file: &ProjectFile,
3068 definition: Node<'_>,
3069 source: &str,
3070 ) -> Option<Arc<ParsedReplacementBody>> {
3071 debug_assert_eq!(definition.kind(), "preproc_function_def");
3072 let (parameters, replacement) = match Self::decode_macro_definition(definition, source) {
3073 MacroDefinition::Function {
3074 parameters,
3075 replacement,
3076 }
3077 | MacroDefinition::VariadicFunction {
3078 parameters,
3079 replacement,
3080 } => (parameters, replacement),
3081 MacroDefinition::Object { .. } | MacroDefinition::Unsupported => return None,
3082 };
3083 self.parsed_macro_replacement_body(
3084 &(file.clone(), definition.start_byte()),
3085 ¶meters,
3086 &replacement,
3087 )
3088 }
3089
3090 fn parsed_macro_replacement_body(
3098 &self,
3099 key: &(ProjectFile, usize),
3100 parameters: &[String],
3101 replacement: &str,
3102 ) -> Option<Arc<ParsedReplacementBody>> {
3103 if let Some(body) = self
3104 .macro_replacement_bodies
3105 .lock()
3106 .expect("C++ macro replacement body cache poisoned")
3107 .get(key)
3108 {
3109 return body.clone();
3110 }
3111 let body = (|| {
3112 if replacement.trim().is_empty() {
3113 return None;
3114 }
3115 let (source, tree, original_offsets) =
3116 Self::parse_macro_replacement_body(replacement, parameters)?;
3117 let body = ParsedReplacementBody {
3118 source,
3119 tree,
3120 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3121 parameters: parameters.to_vec(),
3122 original_offsets,
3123 };
3124 body.statements()?;
3125 if body.expands_variadic_arguments() {
3126 return None;
3127 }
3128 Some(Arc::new(body))
3129 })();
3130 self.macro_replacement_bodies
3131 .lock()
3132 .expect("C++ macro replacement body cache poisoned")
3133 .insert(key.clone(), body.clone());
3134 body
3135 }
3136
3137 fn parse_macro_replacement_body(
3145 replacement: &str,
3146 parameters: &[String],
3147 ) -> Option<(String, Tree, Box<[usize]>)> {
3148 let normalized = normalize_macro_continuations(replacement);
3149 let parse = |replacement: &str| {
3150 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3151 let mut parser = Parser::new();
3152 parser
3153 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3154 .ok()?;
3155 let tree = parser.parse(&source, None)?;
3156 Some((source, tree))
3157 };
3158 let (source, tree) = parse(&normalized)?;
3159 if !tree.root_node().has_error() {
3160 let mut original_offsets = (0..=normalized.len()).collect::<Vec<_>>();
3161 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3162 return Some((source, tree, original_offsets.into_boxed_slice()));
3163 }
3164
3165 let body_offset = MACRO_BODY_SENTINEL_PREFIX.len();
3166 let mut insertion_points = Vec::new();
3167 let mut stack = vec![tree.root_node()];
3168 while let Some(node) = stack.pop() {
3169 if matches!(
3170 node.kind(),
3171 "identifier" | "type_identifier" | "field_identifier" | "namespace_identifier"
3172 ) && parameters
3173 .iter()
3174 .any(|parameter| parameter.as_str() == node_text(node, &source))
3175 && Self::macro_formal_needs_statement_separator(node)
3176 {
3177 let point = node.end_byte().saturating_sub(body_offset);
3178 if point <= normalized.len() && !insertion_points.contains(&point) {
3179 insertion_points.push(point);
3180 }
3181 }
3182 push_named_children_reversed(node, &mut stack);
3183 }
3184 if insertion_points.is_empty() {
3185 return None;
3186 }
3187 insertion_points.sort_unstable();
3188 let mut recovered = Vec::with_capacity(normalized.len() + insertion_points.len());
3189 let mut original_offsets =
3190 Vec::with_capacity(normalized.len() + insertion_points.len() + 1);
3191 let mut next_insertion = 0;
3192 for (index, byte) in normalized.bytes().enumerate() {
3193 recovered.push(byte);
3194 original_offsets.push(index);
3195 while insertion_points.get(next_insertion).copied() == Some(index + 1) {
3196 recovered.push(b';');
3197 original_offsets.push(index + 1);
3198 next_insertion += 1;
3199 }
3200 }
3201 original_offsets.push(normalized.len());
3202 Self::append_sentinel_offsets(&mut original_offsets, normalized.len());
3203 let recovered = String::from_utf8(recovered).expect("source text remains UTF-8");
3204 let (source, tree) = parse(&recovered)?;
3205 if tree.root_node().has_error() {
3206 return None;
3207 }
3208 Some((source, tree, original_offsets.into_boxed_slice()))
3209 }
3210
3211 fn append_sentinel_offsets(offsets: &mut Vec<usize>, replacement_end: usize) {
3216 offsets.extend([replacement_end; 3]);
3217 }
3218
3219 fn macro_formal_needs_statement_separator(node: Node<'_>) -> bool {
3224 let mut current = node;
3225 let mut crossed_recovery = false;
3226 while let Some(parent) = current.parent() {
3227 if parent.is_error() {
3228 crossed_recovery = true;
3229 current = parent;
3230 continue;
3231 }
3232 if matches!(
3233 parent.kind(),
3234 "call_expression"
3235 | "argument_list"
3236 | "field_expression"
3237 | "binary_expression"
3238 | "unary_expression"
3239 | "assignment_expression"
3240 | "conditional_expression"
3241 | "parenthesized_expression"
3242 | "subscript_expression"
3243 ) {
3244 return false;
3245 }
3246 if parent.kind() == "expression_statement" {
3247 return parent.named_child_count() == 1;
3248 }
3249 if parent.kind() == "compound_statement" && current == node {
3250 return true;
3251 }
3252 if matches!(
3253 parent.kind(),
3254 "compound_statement" | "if_statement" | "while_statement" | "do_statement"
3255 ) {
3256 return crossed_recovery
3257 || parent.child_by_field_name("consequence") == Some(current);
3258 }
3259 if matches!(parent.kind(), "declaration" | "init_declarator") {
3260 return false;
3261 }
3262 current = parent;
3263 }
3264 false
3265 }
3266
3267 fn macro_type_parameter_indices(
3275 &self,
3276 key: &(ProjectFile, usize),
3277 parameters: &[String],
3278 replacement: &str,
3279 ) -> Option<Arc<[usize]>> {
3280 if let Some(indices) = self
3281 .macro_type_parameters
3282 .lock()
3283 .expect("C++ macro type-parameter cache poisoned")
3284 .get(key)
3285 {
3286 return indices.clone();
3287 }
3288 #[cfg(any(test, feature = "test-support"))]
3289 self.macro_replacement_parse_count
3290 .fetch_add(1, Ordering::Relaxed);
3291 let indices = (|| {
3292 if replacement.trim().is_empty() {
3293 return None;
3294 }
3295 let source = format!("{MACRO_BODY_SENTINEL_PREFIX}{replacement}; }}");
3299 let mut parser = Parser::new();
3300 parser
3301 .set_language(&tree_sitter_cpp::LANGUAGE.into())
3302 .ok()?;
3303 let tree = parser.parse(&source, None)?;
3304 let mut original_offsets = (0..=replacement.len()).collect::<Vec<_>>();
3305 Self::append_sentinel_offsets(&mut original_offsets, replacement.len());
3306 let body = ParsedReplacementBody {
3307 source,
3308 tree,
3309 body_offset: MACRO_BODY_SENTINEL_PREFIX.len(),
3310 parameters: parameters.to_vec(),
3311 original_offsets: original_offsets.into_boxed_slice(),
3312 };
3313 macro_replacement_type_parameters(&body, parameters).map(Arc::from)
3314 })();
3315 self.macro_type_parameters
3316 .lock()
3317 .expect("C++ macro type-parameter cache poisoned")
3318 .insert(key.clone(), indices.clone());
3319 indices
3320 }
3321
3322 fn decode_macro_definition(node: Node<'_>, source: &str) -> MacroDefinition {
3323 let replacement = if node.kind() == "preproc_function_def" {
3324 function_macro_replacement_span(node, source)
3325 .and_then(|span| source.get(span))
3326 .map(str::to_owned)
3327 .or_else(|| {
3328 node.child_by_field_name("value")
3329 .map(|value| node_text(value, source).to_string())
3330 })
3331 .unwrap_or_default()
3332 } else {
3333 node.child_by_field_name("value")
3334 .map(|value| node_text(value, source).to_string())
3335 .unwrap_or_default()
3336 };
3337 if node.kind() == "preproc_def" {
3338 return MacroDefinition::Object { replacement };
3339 }
3340 let Some(parameters) = node.child_by_field_name("parameters") else {
3341 return MacroDefinition::Unsupported;
3342 };
3343 let variadic = (0..parameters.child_count()).any(|index| {
3344 parameters
3345 .child(index)
3346 .is_some_and(|child| child.kind() == "...")
3347 });
3348 let parameters = (0..parameters.named_child_count())
3349 .filter_map(|index| parameters.named_child(index))
3350 .map(|parameter| node_text(parameter, source).to_string())
3351 .collect::<Vec<_>>();
3352 if variadic {
3353 MacroDefinition::VariadicFunction {
3354 parameters,
3355 replacement,
3356 }
3357 } else {
3358 MacroDefinition::Function {
3359 parameters,
3360 replacement,
3361 }
3362 }
3363 }
3364
3365 pub fn macro_event_cell(&self, file: &ProjectFile) -> MacroEventCell {
3366 clone_or_default_file_cell(
3367 &self.macro_event_cells,
3368 file,
3369 "C++ macro event cache poisoned",
3370 )
3371 }
3372
3373 fn file_defines_macro_name(&self, file: &ProjectFile, name: &str) -> bool {
3374 if let Some(names) = self
3375 .macro_event_name_sets
3376 .lock()
3377 .expect("C++ macro event-name cache poisoned")
3378 .get(file)
3379 .cloned()
3380 {
3381 return names.contains(name);
3382 }
3383 let cell = self.macro_event_cell(file);
3384 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3385 let names = Arc::new(
3386 events
3387 .iter()
3388 .filter_map(|event| match event {
3389 MacroEvent::Define { name, .. } => Some(name.clone()),
3390 MacroEvent::Undef { .. }
3391 | MacroEvent::Include { .. }
3392 | MacroEvent::Invalidate { .. } => None,
3393 })
3394 .collect(),
3395 );
3396 self.macro_event_name_sets
3397 .lock()
3398 .expect("C++ macro event-name cache poisoned")
3399 .insert(file.clone(), Arc::clone(&names));
3400 names.contains(name)
3401 }
3402
3403 fn macro_environment_checkpoint_cell(
3404 &self,
3405 file: &ProjectFile,
3406 ) -> MacroEnvironmentCheckpointCell {
3407 clone_or_default_file_cell(
3408 &self.macro_environment_checkpoints,
3409 file,
3410 "C++ macro environment checkpoint cache poisoned",
3411 )
3412 }
3413
3414 pub fn macro_environment(
3416 &self,
3417 file: &ProjectFile,
3418 before_byte: usize,
3419 ) -> Arc<MacroEnvironment> {
3420 #[cfg(any(test, feature = "test-support"))]
3421 self.macro_environment_request_count
3422 .fetch_add(1, Ordering::Relaxed);
3423 let cell = self.macro_event_cell(file);
3424 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3425 let frontier = events.partition_point(|event| event.byte() < before_byte);
3426 let checkpoint_cell = self.macro_environment_checkpoint_cell(file);
3427 let checkpoints =
3428 checkpoint_cell.get_or_init(|| self.build_macro_environment_checkpoints(file, events));
3429 let checkpoint = checkpoints.at_or_before(frontier);
3430 if checkpoint.frontier == frontier {
3431 return Arc::clone(&checkpoint.environment);
3432 }
3433 #[cfg(any(test, feature = "test-support"))]
3434 self.macro_environment_copy_count
3435 .fetch_add(1, Ordering::Relaxed);
3436 let mut environment = checkpoint.environment.as_ref().clone();
3437 let mut include_stack = HashSet::from_iter([file.clone()]);
3438 for event in &events[checkpoint.frontier..frontier] {
3439 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3440 }
3441 Arc::new(environment)
3442 }
3443
3444 fn build_macro_environment_checkpoints(
3447 &self,
3448 file: &ProjectFile,
3449 events: &[MacroEvent],
3450 ) -> MacroEnvironmentCheckpoints {
3451 #[cfg(any(test, feature = "test-support"))]
3452 self.macro_environment_checkpoint_build_count
3453 .fetch_add(1, Ordering::Relaxed);
3454 let mut environment = MacroEnvironment {
3459 build_proven_defines: self
3460 .compile_proven_guards(file)
3461 .iter()
3462 .filter_map(|guard| match guard {
3463 PreprocessorGuard::Defined(name) => Some(name.clone()),
3464 _ => None,
3465 })
3466 .collect(),
3467 ..MacroEnvironment::default()
3468 };
3469 let mut checkpoints = vec![MacroEnvironmentCheckpoint {
3470 frontier: 0,
3471 environment: Arc::new(environment.clone()),
3472 }];
3473 let checkpoint_stride = events
3480 .len()
3481 .div_ceil(MACRO_ENVIRONMENT_CHECKPOINT_STRIDE)
3482 .clamp(1, MACRO_ENVIRONMENT_CHECKPOINT_STRIDE);
3483 let mut include_stack = HashSet::from_iter([file.clone()]);
3484 for (index, event) in events.iter().enumerate() {
3485 self.apply_macro_event(file, event, &mut environment, &mut include_stack);
3486 let frontier = index + 1;
3487 if frontier % checkpoint_stride == 0 || matches!(event, MacroEvent::Include { .. }) {
3488 checkpoints.push(MacroEnvironmentCheckpoint {
3489 frontier,
3490 environment: Arc::new(environment.clone()),
3491 });
3492 }
3493 }
3494 MacroEnvironmentCheckpoints { checkpoints }
3495 }
3496
3497 pub fn names_a_macro_at(&self, file: &ProjectFile, name: &str, before_byte: usize) -> bool {
3506 self.macro_environment(file, before_byte)
3507 .binding(name)
3508 .is_some()
3509 }
3510
3511 pub fn macro_name_may_be_bound_at(
3512 &self,
3513 file: &ProjectFile,
3514 name: &str,
3515 before_byte: usize,
3516 ) -> bool {
3517 self.macro_environment(file, before_byte).may_bind(name)
3518 }
3519
3520 pub fn macro_binding_matches_target_at(
3524 &self,
3525 analyzer: &CppGraphSource<'_>,
3526 file: &ProjectFile,
3527 name: &str,
3528 before_byte: usize,
3529 target: &CodeUnit,
3530 ) -> bool {
3531 let ranges = analyzer.ranges(target);
3532 let declaration_bytes = self.macro_declaration_bytes(target, &ranges);
3533 self.macro_binding_matches_target_declaration_at(
3534 file,
3535 name,
3536 before_byte,
3537 target.source(),
3538 &declaration_bytes,
3539 )
3540 }
3541
3542 pub(crate) fn macro_declaration_bytes(
3547 &self,
3548 target: &CodeUnit,
3549 ranges: &[Range],
3550 ) -> Vec<usize> {
3551 let Some(prepared) = self.cpp.prepared_syntax(self.token, target.source()) else {
3552 return Vec::new();
3553 };
3554 ranges
3555 .iter()
3556 .filter_map(|range| {
3557 let mut node = node_for_exact_range(prepared.tree().root_node(), range)?;
3558 while !matches!(node.kind(), "preproc_def" | "preproc_function_def") {
3559 node = node.parent()?;
3560 }
3561 Some(node.start_byte())
3562 })
3563 .collect()
3564 }
3565
3566 pub(crate) fn macro_binding_matches_target_declaration_at(
3567 &self,
3568 file: &ProjectFile,
3569 name: &str,
3570 before_byte: usize,
3571 target_source: &ProjectFile,
3572 target_declaration_bytes: &[usize],
3573 ) -> bool {
3574 let environment = self.macro_environment(file, before_byte);
3575 let Some(binding) = environment.binding(name) else {
3576 return false;
3577 };
3578 if binding.definition == MacroDefinition::Unsupported {
3579 return false;
3580 }
3581 if binding.source != *target_source {
3585 return false;
3586 }
3587 target_declaration_bytes.contains(&binding.declaration_byte)
3588 }
3589
3590 pub fn resolve_ordinary_macro_reference(
3597 &self,
3598 analyzer: &CppGraphSource<'_>,
3599 file: &ProjectFile,
3600 node: Node<'_>,
3601 source: &str,
3602 ) -> OrdinaryMacroReferenceResolution {
3603 if !is_ordinary_macro_reference_node(node) {
3604 return OrdinaryMacroReferenceResolution::Missing;
3605 }
3606 let name = node_text(node, source);
3607 if name.is_empty() {
3608 return OrdinaryMacroReferenceResolution::Missing;
3609 }
3610 let visible = self
3611 .visible_identifier_candidates(file, name)
3612 .filter(|candidate| candidate.is_macro())
3613 .cloned()
3614 .collect::<Vec<_>>();
3615 let mut exact = Vec::new();
3616 for candidate in &visible {
3617 if self.macro_binding_matches_target_at(
3618 analyzer,
3619 file,
3620 name,
3621 node.start_byte(),
3622 candidate,
3623 ) && !exact
3624 .iter()
3625 .any(|existing| same_visible_symbol(existing, candidate))
3626 {
3627 exact.push(candidate.clone());
3628 }
3629 }
3630 match exact.len() {
3631 1 => OrdinaryMacroReferenceResolution::Resolved(exact.pop().unwrap()),
3632 2.. => OrdinaryMacroReferenceResolution::Ambiguous,
3633 0 if !visible.is_empty()
3634 && self.macro_name_may_be_bound_at(file, name, node.start_byte()) =>
3635 {
3636 OrdinaryMacroReferenceResolution::Ambiguous
3637 }
3638 0 => OrdinaryMacroReferenceResolution::Missing,
3639 }
3640 }
3641
3642 pub fn recovered_c_reference_ranges(
3650 &self,
3651 file: &ProjectFile,
3652 root: Node<'_>,
3653 source: &str,
3654 limit: usize,
3655 ) -> RecoveredCReferenceRanges {
3656 if !is_c_source_file(file) {
3657 return RecoveredCReferenceRanges::Complete(Vec::new());
3658 }
3659 let mut ranges = Vec::new();
3660 let mut seen = HashSet::default();
3661 let mut stack = vec![(root, root.is_error())];
3662 while let Some((node, inside_error)) = stack.pop() {
3663 let inside_error = inside_error || node.is_error();
3664 if node.kind() == "preproc_arg" {
3665 let macro_value_kind = node.parent().and_then(|parent| {
3670 (parent.child_by_field_name("value") == Some(node)).then_some(parent.kind())
3671 });
3672 if matches!(
3673 macro_value_kind,
3674 Some("preproc_def" | "preproc_function_def")
3675 ) {
3676 let name = node_text(node, source);
3677 if !name.is_empty()
3678 && self.macro_name_may_be_bound_at(file, name, node.start_byte())
3679 && !push_recovered_c_range(
3680 &mut ranges,
3681 &mut seen,
3682 node.start_byte(),
3683 node.end_byte(),
3684 node,
3685 limit,
3686 )
3687 {
3688 return RecoveredCReferenceRanges::LimitExceeded;
3689 }
3690 }
3691 if macro_value_kind == Some("preproc_def") {
3692 for reference in object_macro_replacement_type_references(node, source) {
3693 for range in reference.component_ranges {
3694 let visible = self
3695 .visible_identifier_candidates(file, &source[range.clone()])
3696 .any(|candidate| candidate.is_class() || candidate.is_module());
3697 if visible
3698 && !push_recovered_c_range(
3699 &mut ranges,
3700 &mut seen,
3701 range.start,
3702 range.end,
3703 node,
3704 limit,
3705 )
3706 {
3707 return RecoveredCReferenceRanges::LimitExceeded;
3708 }
3709 }
3710 }
3711 }
3712 }
3713 if inside_error
3714 && recovered_c_reference_node(self, file, node, source)
3715 && !push_recovered_c_range(
3716 &mut ranges,
3717 &mut seen,
3718 node.start_byte(),
3719 node.end_byte(),
3720 node,
3721 limit,
3722 )
3723 {
3724 return RecoveredCReferenceRanges::LimitExceeded;
3725 }
3726 let mut cursor = node.walk();
3727 for child in node.named_children(&mut cursor) {
3728 stack.push((child, inside_error));
3729 }
3730 }
3731 ranges.sort_unstable();
3732 RecoveredCReferenceRanges::Complete(ranges)
3733 }
3734
3735 pub fn macro_target_is_visible_candidate(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
3741 self.visible_identifier_candidates(file, target.identifier())
3742 .filter(|candidate| candidate.is_macro())
3743 .any(|candidate| {
3744 candidate.source() == target.source() && candidate.fq_name() == target.fq_name()
3745 })
3746 }
3747
3748 pub fn object_macro_replacement_at(
3749 &self,
3750 file: &ProjectFile,
3751 name: &str,
3752 before_byte: usize,
3753 ) -> Option<String> {
3754 let environment = self.macro_environment(file, before_byte);
3755 let binding = environment.binding(name)?;
3756 if !binding.exact {
3757 return None;
3758 }
3759 match &binding.definition {
3760 MacroDefinition::Object { replacement } => Some(replacement.clone()),
3761 MacroDefinition::Function { .. }
3762 | MacroDefinition::VariadicFunction { .. }
3763 | MacroDefinition::Unsupported => None,
3764 }
3765 }
3766
3767 fn apply_macro_events(
3768 &self,
3769 file: &ProjectFile,
3770 before_byte: Option<usize>,
3771 environment: &mut MacroEnvironment,
3772 include_stack: &mut HashSet<ProjectFile>,
3773 ) {
3774 if !include_stack.insert(file.clone()) {
3775 return;
3776 }
3777 if self.cpp.prepared_syntax(self.token, file).is_none() {
3778 environment.mark_unknown_names(file, before_byte.unwrap_or_default());
3779 include_stack.remove(file);
3780 return;
3781 }
3782 match self.macro_include_protection(file) {
3783 MacroIncludeProtection::MacroGuard(guard) => match environment.binding(&guard) {
3784 Some(binding) if binding.is_exact() => {
3785 include_stack.remove(file);
3786 return;
3787 }
3788 Some(_) | None if environment.unknown_names => {
3789 let mut ambiguous_seen = HashSet::default();
3790 self.mark_macro_events_ambiguous(
3791 file,
3792 environment,
3793 &mut ambiguous_seen,
3794 file,
3795 before_byte.unwrap_or_default(),
3796 );
3797 include_stack.remove(file);
3798 return;
3799 }
3800 Some(_) => {
3801 let mut ambiguous_seen = HashSet::default();
3802 self.mark_macro_events_ambiguous(
3803 file,
3804 environment,
3805 &mut ambiguous_seen,
3806 file,
3807 before_byte.unwrap_or_default(),
3808 );
3809 include_stack.remove(file);
3810 return;
3811 }
3812 None => {}
3813 },
3814 MacroIncludeProtection::PragmaOnce => {
3815 if !environment.applied_pragma_once_files.insert(file.clone()) {
3816 include_stack.remove(file);
3817 return;
3818 }
3819 if environment.maybe_applied_pragma_once_files.remove(file) {
3820 let mut ambiguous_seen = HashSet::default();
3825 environment.applied_pragma_once_files.remove(file);
3826 self.mark_macro_events_ambiguous(
3827 file,
3828 environment,
3829 &mut ambiguous_seen,
3830 file,
3831 before_byte.unwrap_or_default(),
3832 );
3833 environment.maybe_applied_pragma_once_files.remove(file);
3834 environment.applied_pragma_once_files.insert(file.clone());
3835 include_stack.remove(file);
3836 return;
3837 }
3838 }
3839 MacroIncludeProtection::None => {}
3840 }
3841 let cell = self.macro_event_cell(file);
3842 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
3843 for event in events {
3844 if before_byte.is_some_and(|limit| event.byte() >= limit) {
3845 break;
3846 }
3847 self.apply_macro_event(file, event, environment, include_stack);
3848 }
3849 include_stack.remove(file);
3850 }
3851
3852 fn apply_macro_event(
3853 &self,
3854 file: &ProjectFile,
3855 event: &MacroEvent,
3856 environment: &mut MacroEnvironment,
3857 include_stack: &mut HashSet<ProjectFile>,
3858 ) {
3859 #[cfg(any(test, feature = "test-support"))]
3860 self.macro_event_application_count
3861 .fetch_add(1, Ordering::Relaxed);
3862 match event {
3863 MacroEvent::Define {
3864 name,
3865 binding,
3866 conditionals,
3867 byte,
3868 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3869 Some(true) => environment.insert(name.clone(), binding.clone()),
3870 Some(false) => {}
3871 None => Self::merge_conditional_macro_definition(
3872 environment,
3873 name,
3874 binding,
3875 file,
3876 *byte,
3877 ),
3878 },
3879 MacroEvent::Undef {
3880 name,
3881 conditionals,
3882 byte,
3883 } => match self.macro_event_condition_value(file, *byte, environment, conditionals) {
3884 Some(true) => environment.remove(name),
3885 Some(false) => {}
3886 None => {
3887 if environment.binding(name).is_some() {
3888 environment.insert(name.clone(), MacroBinding::ambiguous(file, *byte));
3889 }
3890 }
3891 },
3892 MacroEvent::Include {
3893 targets,
3894 conditionals,
3895 byte,
3896 } => {
3897 let condition =
3898 self.macro_event_condition_value(file, *byte, environment, conditionals);
3899 if condition == Some(false) {
3900 return;
3901 }
3902 if targets.is_empty() {
3903 environment.mark_unknown_names(file, *byte);
3904 return;
3905 }
3906 if condition.is_none() || targets.len() > 1 {
3907 let mut ambiguous_seen = HashSet::default();
3908 for target in targets {
3909 self.mark_macro_events_ambiguous(
3910 target,
3911 environment,
3912 &mut ambiguous_seen,
3913 file,
3914 *byte,
3915 );
3916 }
3917 } else if let Some(target) = targets.first() {
3918 self.apply_macro_events(target, None, environment, include_stack);
3919 }
3920 }
3921 MacroEvent::Invalidate { byte } => {
3922 for binding in environment.bindings.values_mut() {
3923 *binding = MacroBinding::uncertain_from(binding, file, *byte);
3924 }
3925 }
3926 }
3927 }
3928
3929 fn macro_event_condition_value(
3940 &self,
3941 file: &ProjectFile,
3942 event_byte: usize,
3943 environment: &MacroEnvironment,
3944 conditionals: &OwningPreprocessorConditionals,
3945 ) -> Option<bool> {
3946 if conditionals.is_empty() {
3947 return Some(true);
3948 }
3949 let prepared = self.cpp.prepared_syntax(self.token, file)?;
3950 let source = prepared.source();
3951 let root = prepared.tree().root_node();
3952 let descendant = root.descendant_for_byte_range(
3953 event_byte,
3954 event_byte.saturating_add(1).min(source.len()),
3955 )?;
3956 let mut unknown = false;
3957 let mut current = descendant.parent();
3958 while let Some(conditional) = current {
3959 if matches!(
3960 conditional.kind(),
3961 "preproc_if" | "preproc_ifdef" | "preproc_elif"
3962 ) && conditionals.contains(&conditional.start_byte())
3963 {
3964 let mut value = match conditional.kind() {
3965 "preproc_ifdef" => {
3966 let name = conditional.child_by_field_name("name")?;
3967 let defined =
3968 self.macro_name_defined_value(environment, node_text(name, source));
3969 match conditional.child(0)?.kind() {
3970 "#ifdef" => defined,
3971 "#ifndef" => defined.map(|defined| !defined),
3972 _ => None,
3973 }
3974 }
3975 "preproc_if" | "preproc_elif" => conditional
3976 .child_by_field_name("condition")
3977 .and_then(|condition| {
3978 self.preprocessor_integer_value(
3979 condition,
3980 source,
3981 environment,
3982 &mut Vec::new(),
3983 0,
3984 )
3985 })
3986 .map(|value| value != 0),
3987 _ => unreachable!(),
3988 };
3989 if conditional
3990 .child_by_field_name("alternative")
3991 .is_some_and(|alternative| {
3992 alternative.start_byte() <= descendant.start_byte()
3993 && descendant.end_byte() <= alternative.end_byte()
3994 })
3995 {
3996 value = value.map(|value| !value);
3997 }
3998 match value {
3999 Some(true) => {}
4000 Some(false) => return Some(false),
4001 None => unknown = true,
4002 }
4003 }
4004 current = conditional.parent();
4005 }
4006 (!unknown).then_some(true)
4007 }
4008
4009 fn macro_name_defined_value(&self, environment: &MacroEnvironment, name: &str) -> Option<bool> {
4010 if environment.known_undefined_names.contains(name) {
4011 return Some(false);
4012 }
4013 if let Some(binding) = environment.binding(name) {
4014 return binding.is_exact().then_some(true);
4015 }
4016 environment
4017 .build_proven_defines
4018 .contains(name)
4019 .then_some(true)
4020 }
4021
4022 fn preprocessor_integer_value(
4023 &self,
4024 expression: Node<'_>,
4025 source: &str,
4026 environment: &MacroEnvironment,
4027 expansion_stack: &mut Vec<(ProjectFile, usize)>,
4028 depth: usize,
4029 ) -> Option<i128> {
4030 if depth >= 64 {
4033 return None;
4034 }
4035 match expression.kind() {
4036 "number_literal" => parse_cpp_integer_literal(node_text(expression, source)),
4037 "identifier" | "type_identifier" => {
4038 let binding = environment.binding(node_text(expression, source))?;
4039 if !binding.is_exact() {
4040 return None;
4041 }
4042 let MacroDefinition::Object { replacement } = &binding.definition else {
4043 return None;
4044 };
4045 let identity = (binding.source.clone(), binding.declaration_byte);
4046 if expansion_stack.contains(&identity) {
4047 return None;
4048 }
4049 expansion_stack.push(identity);
4050 let parsed = self.parsed_macro_replacement(binding, replacement);
4051 let value = match parsed.as_ref() {
4052 ParsedMacroReplacement::Parsed {
4053 source: replacement_source,
4054 tree,
4055 } => first_descendant_of_kind(tree.root_node(), "call_expression")
4056 .and_then(|call| call.child_by_field_name("arguments"))
4057 .and_then(|arguments| argument_children(arguments).next())
4058 .and_then(|argument| {
4059 self.preprocessor_integer_value(
4060 argument,
4061 replacement_source,
4062 environment,
4063 expansion_stack,
4064 depth + 1,
4065 )
4066 }),
4067 ParsedMacroReplacement::Unsupported => None,
4068 };
4069 expansion_stack.pop();
4070 value
4071 }
4072 "preproc_defined" => {
4073 let mut cursor = expression.walk();
4074 let name = expression
4075 .named_children(&mut cursor)
4076 .find(|child| child.kind() == "identifier")?;
4077 self.macro_name_defined_value(environment, node_text(name, source))
4078 .map(i128::from)
4079 }
4080 "parenthesized_expression" => expression.named_child(0).and_then(|child| {
4081 self.preprocessor_integer_value(
4082 child,
4083 source,
4084 environment,
4085 expansion_stack,
4086 depth + 1,
4087 )
4088 }),
4089 "unary_expression" => {
4090 let operator = expression.child_by_field_name("operator")?.kind();
4091 let argument = expression.child_by_field_name("argument")?;
4092 let value = self.preprocessor_integer_value(
4093 argument,
4094 source,
4095 environment,
4096 expansion_stack,
4097 depth + 1,
4098 )?;
4099 match operator {
4100 "+" => Some(value),
4101 "-" => value.checked_neg(),
4102 "!" => Some(i128::from(value == 0)),
4103 "~" => Some(!value),
4104 _ => None,
4105 }
4106 }
4107 "binary_expression" => {
4108 let left = self.preprocessor_integer_value(
4109 expression.child_by_field_name("left")?,
4110 source,
4111 environment,
4112 expansion_stack,
4113 depth + 1,
4114 )?;
4115 let right = self.preprocessor_integer_value(
4116 expression.child_by_field_name("right")?,
4117 source,
4118 environment,
4119 expansion_stack,
4120 depth + 1,
4121 )?;
4122 match expression.child_by_field_name("operator")?.kind() {
4123 "+" => left.checked_add(right),
4124 "-" => left.checked_sub(right),
4125 "*" => left.checked_mul(right),
4126 "/" => left.checked_div(right),
4127 "%" => left.checked_rem(right),
4128 "<<" => u32::try_from(right)
4129 .ok()
4130 .and_then(|shift| left.checked_shl(shift)),
4131 ">>" => u32::try_from(right)
4132 .ok()
4133 .and_then(|shift| left.checked_shr(shift)),
4134 "<" => Some(i128::from(left < right)),
4135 "<=" => Some(i128::from(left <= right)),
4136 ">" => Some(i128::from(left > right)),
4137 ">=" => Some(i128::from(left >= right)),
4138 "==" => Some(i128::from(left == right)),
4139 "!=" => Some(i128::from(left != right)),
4140 "&" => Some(left & right),
4141 "|" => Some(left | right),
4142 "^" => Some(left ^ right),
4143 "&&" => Some(i128::from(left != 0 && right != 0)),
4144 "||" => Some(i128::from(left != 0 || right != 0)),
4145 _ => None,
4146 }
4147 }
4148 _ => None,
4149 }
4150 }
4151
4152 fn mark_macro_events_ambiguous(
4153 &self,
4154 file: &ProjectFile,
4155 environment: &mut MacroEnvironment,
4156 include_stack: &mut HashSet<ProjectFile>,
4157 conditional_file: &ProjectFile,
4158 conditional_byte: usize,
4159 ) {
4160 if !include_stack.insert(file.clone()) {
4161 return;
4162 }
4163 if self.cpp.prepared_syntax(self.token, file).is_none() {
4164 environment.mark_unknown_names(conditional_file, conditional_byte);
4165 return;
4166 }
4167 match self.macro_include_protection(file) {
4168 MacroIncludeProtection::MacroGuard(guard) => {
4169 if environment
4170 .binding(&guard)
4171 .is_some_and(MacroBinding::is_exact)
4172 {
4173 return;
4174 }
4175 }
4176 MacroIncludeProtection::PragmaOnce => {
4177 if environment.applied_pragma_once_files.contains(file) {
4178 return;
4179 }
4180 environment
4181 .maybe_applied_pragma_once_files
4182 .insert(file.clone());
4183 }
4184 MacroIncludeProtection::None => {}
4185 }
4186 let cell = self.macro_event_cell(file);
4187 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
4188 for event in events {
4189 #[cfg(any(test, feature = "test-support"))]
4190 self.macro_event_application_count
4191 .fetch_add(1, Ordering::Relaxed);
4192 match event {
4193 MacroEvent::Define { name, binding, .. } => {
4194 Self::merge_conditional_macro_definition(
4195 environment,
4196 name,
4197 binding,
4198 conditional_file,
4199 conditional_byte,
4200 );
4201 }
4202 MacroEvent::Undef { name, .. } => {
4203 if environment.binding(name).is_some() {
4204 environment.insert(
4205 name.clone(),
4206 MacroBinding::ambiguous(conditional_file, conditional_byte),
4207 );
4208 } else {
4209 environment.remove_known_undefined(name);
4210 }
4211 }
4212 MacroEvent::Include { targets, .. } => {
4213 if targets.is_empty() {
4214 environment.mark_unknown_names(conditional_file, conditional_byte);
4215 continue;
4216 }
4217 for target in targets {
4218 self.mark_macro_events_ambiguous(
4219 target,
4220 environment,
4221 include_stack,
4222 conditional_file,
4223 conditional_byte,
4224 );
4225 }
4226 }
4227 MacroEvent::Invalidate { .. } => {
4228 for binding in environment.bindings.values_mut() {
4229 *binding = MacroBinding::uncertain_from(
4230 binding,
4231 conditional_file,
4232 conditional_byte,
4233 );
4234 }
4235 }
4236 }
4237 }
4238 }
4239
4240 fn merge_conditional_macro_definition(
4241 environment: &mut MacroEnvironment,
4242 name: &str,
4243 possible_binding: &MacroBinding,
4244 conditional_file: &ProjectFile,
4245 conditional_byte: usize,
4246 ) {
4247 if environment.binding(name).is_some_and(|current| {
4252 current.definition != MacroDefinition::Unsupported
4253 && current.definition == possible_binding.definition
4254 }) {
4255 return;
4256 }
4257 environment.insert(
4258 name.to_string(),
4259 MacroBinding::ambiguous(conditional_file, conditional_byte),
4260 );
4261 }
4262
4263 pub fn macro_include_protection(&self, file: &ProjectFile) -> MacroIncludeProtection {
4264 let cell = self
4265 .macro_include_protection_cells
4266 .lock()
4267 .expect("C++ include protection cache poisoned")
4268 .entry(file.clone())
4269 .or_default()
4270 .clone();
4271 cell.get_or_init(|| {
4272 self.cpp.prepared_syntax(self.token, file).map_or(
4273 MacroIncludeProtection::None,
4274 |prepared| {
4275 top_level_macro_include_protection(
4276 prepared.tree().root_node(),
4277 prepared.source(),
4278 )
4279 },
4280 )
4281 })
4282 .clone()
4283 }
4284
4285 fn collect_macro_events(&self, file: &ProjectFile) -> Vec<MacroEvent> {
4286 collect_macro_events(self.cpp, self.token, file)
4287 }
4288
4289 pub fn ordinary_type_import_cell(&self, file: &ProjectFile) -> OrdinaryTypeImportCell {
4290 self.ordinary_type_import_cells
4291 .lock()
4292 .expect("C++ ordinary type import cache poisoned")
4293 .entry(file.clone())
4294 .or_insert_with(|| Arc::new(EffectiveUsingIndex::new(file.clone())))
4295 .clone()
4296 }
4297
4298 pub fn project_using_index(
4299 &self,
4300 build: impl FnOnce() -> ProjectUsingIndex,
4301 ) -> &ProjectUsingIndex {
4302 self.project_using_index.get_or_init(build)
4303 }
4304
4305 pub fn all_visible_source_files(&self) -> Vec<ProjectFile> {
4306 let mut files = self
4307 .visible_source_files_by_root
4308 .values()
4309 .flatten()
4310 .cloned()
4311 .collect::<HashSet<_>>()
4312 .into_iter()
4313 .collect::<Vec<_>>();
4314 files.sort_by(|left, right| left.rel_path().cmp(right.rel_path()));
4315 files
4316 }
4317
4318 pub fn source_is_visible(&self, root: &ProjectFile, source: &ProjectFile) -> bool {
4319 self.visible_source_files_by_root
4320 .get(root)
4321 .is_some_and(|files| files.contains(source))
4322 }
4323
4324 fn visible_parser_alias_name_is_visible(&self, file: &ProjectFile, name: &str) -> bool {
4325 let cached = self
4326 .visible_parser_alias_name_sets
4327 .read()
4328 .expect("visible parser alias-name cache poisoned")
4329 .get(file)
4330 .cloned();
4331 let cell = if let Some(cached) = cached {
4332 cached
4333 } else {
4334 let mut cells = self
4335 .visible_parser_alias_name_sets
4336 .write()
4337 .expect("visible parser alias-name cache poisoned");
4338 Arc::clone(
4339 cells
4340 .entry(file.clone())
4341 .or_insert_with(|| Arc::new(OnceLock::new())),
4342 )
4343 };
4344 if let Some(names) = cell.get() {
4345 return names.contains(name);
4346 }
4347 let mut names = HashSet::default();
4348 let visible_files = self
4349 .visible_source_files_by_root
4350 .get(file)
4351 .cloned()
4352 .unwrap_or_else(|| HashSet::from_iter([file.clone()]));
4353 for visible_file in visible_files {
4354 let Some(aliases) = self.canonical_alias_cell(self.cpp, &visible_file) else {
4355 return false;
4356 };
4357 for alias in aliases.get().expect("published canonical aliases").iter() {
4358 names.insert(alias.name.clone());
4359 }
4360 }
4361 #[cfg(any(test, feature = "test-support"))]
4362 self.visible_parser_alias_name_set_build_count
4363 .fetch_add(1, Ordering::Relaxed);
4364 cell.get_or_init(|| names).contains(name)
4365 }
4366
4367 pub fn parser_alias_name_may_resolve_to_target(
4368 &self,
4369 file: &ProjectFile,
4370 alias_name: &str,
4371 target: &CodeUnit,
4372 ) -> bool {
4373 let started = std::time::Instant::now();
4374 self.parser_alias_fallback_calls
4375 .fetch_add(1, Ordering::Relaxed);
4376 let key = (
4377 file.clone(),
4378 alias_name.to_string(),
4379 logical_symbol_key(target),
4380 );
4381 let cached = self
4382 .parser_alias_target_matches
4383 .read()
4384 .expect("parser alias target-match cache poisoned")
4385 .get(&key)
4386 .cloned();
4387 let cell = if let Some(cached) = cached {
4388 cached
4389 } else {
4390 let mut cells = self
4391 .parser_alias_target_matches
4392 .write()
4393 .expect("parser alias target-match cache poisoned");
4394 Arc::clone(
4395 cells
4396 .entry(key)
4397 .or_insert_with(|| Arc::new(OnceLock::new())),
4398 )
4399 };
4400 let matched = *cell.get_or_init(|| {
4401 any_visible_source_file_matches(
4402 &self.visible_source_files_by_root,
4403 file,
4404 |visible_file| {
4405 self.parser_alias_fallback_files
4406 .fetch_add(1, Ordering::Relaxed);
4407 self.file_alias_matches(self.cpp, visible_file, alias_name, target)
4408 },
4409 )
4410 });
4411 self.parser_alias_fallback_elapsed_micros.fetch_add(
4412 started.elapsed().as_micros().min(usize::MAX as u128) as usize,
4413 Ordering::Relaxed,
4414 );
4415 matched
4416 }
4417
4418 fn callable_arities_for_target(
4419 &self,
4420 analyzer: &CppGraphSource<'_>,
4421 cpp: &dyn CppSource,
4422 file: &ProjectFile,
4423 prepared: &PreparedSyntaxTree,
4424 spec: &TargetSpec,
4425 ) -> Vec<ActivatedCallableArity> {
4426 let Some(signature) = spec.target.signature() else {
4427 return Vec::new();
4428 };
4429 let Some(candidates) = self
4430 .visible_by_identifier
4431 .get(file)
4432 .and_then(|by_name| by_name.get(&spec.member_name))
4433 else {
4434 return Vec::new();
4435 };
4436 let differing_candidates = candidates
4437 .iter()
4438 .filter(|candidate| {
4439 candidate.is_function()
4440 && candidate.fq_name() == spec.target.fq_name()
4441 && candidate.signature() == Some(signature)
4442 })
4443 .filter_map(|candidate| {
4444 analyzer
4445 .signature_metadata(candidate)
4446 .into_iter()
4447 .find_map(|metadata| metadata.callable_arity())
4448 .filter(|arity| Some(*arity) != spec.callable_arity)
4449 .map(|arity| (candidate, arity))
4450 })
4451 .collect::<Vec<_>>();
4452 if differing_candidates.is_empty() {
4453 return Vec::new();
4454 }
4455 let mut arities = Vec::with_capacity(differing_candidates.len());
4456 let reference = CallableReferenceContext {
4459 file,
4460 position: None,
4461 };
4462 for (candidate, candidate_arity) in differing_candidates {
4463 let declaration_activation =
4464 callable_declaration_activation_in_file(analyzer, candidate, &reference);
4465 let Some(declaration_activation) = declaration_activation else {
4466 continue;
4467 };
4468 let activation_byte = if candidate.source() == file {
4469 Some(declaration_activation)
4470 } else {
4471 self.include_activation_for_source(cpp, file, prepared, candidate.source())
4472 };
4473 if let Some(activation_byte) = activation_byte {
4474 arities.push(ActivatedCallableArity {
4475 activation_byte,
4476 arity: candidate_arity,
4477 });
4478 }
4479 }
4480 arities
4481 }
4482
4483 fn callable_parameter_macro_arity(
4484 &self,
4485 target: &CodeUnit,
4486 parameter_types: Option<&[String]>,
4487 ) -> Option<CallableArity> {
4488 let parameter_types = parameter_types?;
4489 let [macro_name] = parameter_types else {
4490 return None;
4491 };
4492 if macro_name.is_empty()
4493 || !macro_name
4494 .chars()
4495 .all(|ch| ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_')
4496 {
4497 return None;
4498 }
4499 let cache_key = (target.source().clone(), macro_name.clone());
4500 if let Some(cached) = self
4501 .callable_parameter_macro_arities
4502 .lock()
4503 .expect("C++ callable parameter-macro arity cache poisoned")
4504 .get(&cache_key)
4505 .copied()
4506 {
4507 return cached;
4508 }
4509 let mut visible_files = HashSet::default();
4510 collect_include_closure(
4511 &self.cpp_source(),
4512 self.cpp.include_target_index(),
4513 target.source(),
4514 &mut visible_files,
4515 None,
4516 );
4517 let mut arities = Vec::new();
4518 for visible_file in visible_files {
4519 let cell = self.macro_event_cell(&visible_file);
4520 for event in
4521 cell.get_or_init(|| self.collect_macro_events(&visible_file).into_boxed_slice())
4522 {
4523 let MacroEvent::Define { name, binding, .. } = event else {
4524 continue;
4525 };
4526 if name != macro_name {
4527 continue;
4528 }
4529 let MacroDefinition::Object { replacement } = &binding.definition else {
4530 continue;
4531 };
4532 let Some(arity) = parse_macro_parameter_list_arity(replacement) else {
4533 continue;
4534 };
4535 if !arities.contains(&arity) {
4536 arities.push(arity);
4537 }
4538 }
4539 }
4540 let resolved = (|| {
4541 let required = arities
4542 .iter()
4543 .filter_map(|arity| (0..=arity.total()).find(|count| arity.accepts(*count)))
4544 .min()?;
4545 let total = arities.iter().map(|arity| arity.total()).max()?;
4546 let repeated = arities
4547 .iter()
4548 .any(|arity| arity.accepts(arity.total().saturating_add(1)));
4549 Some(CallableArity::new(required, total, repeated))
4554 })();
4555 self.callable_parameter_macro_arities
4556 .lock()
4557 .expect("C++ callable parameter-macro arity cache poisoned")
4558 .insert(cache_key, resolved);
4559 resolved
4560 }
4561
4562 pub fn include_activation_for_source(
4563 &self,
4564 cpp: &dyn CppSource,
4565 file: &ProjectFile,
4566 prepared: &PreparedSyntaxTree,
4567 donor_source: &ProjectFile,
4568 ) -> Option<usize> {
4569 let key = (file.clone(), donor_source.clone());
4570 if let Some(cached) = self
4571 .include_activation_cells
4572 .lock()
4573 .expect("C++ include activation cache poisoned")
4574 .get(&key)
4575 .copied()
4576 {
4577 return cached;
4578 }
4579 #[cfg(any(test, feature = "test-support"))]
4580 self.include_activation_build_count
4581 .fetch_add(1, Ordering::Relaxed);
4582 let language_guard_stable = OnceCell::new();
4583 let activation =
4584 find_include_activation(cpp, self.token, file, prepared, donor_source, &|| {
4585 *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file))
4586 });
4587 let mut cells = self
4588 .include_activation_cells
4589 .lock()
4590 .expect("C++ include activation cache poisoned");
4591 *cells.entry(key).or_insert(activation)
4592 }
4593
4594 pub fn conditional_include_projections_for_source(
4595 &self,
4596 file: &ProjectFile,
4597 prepared: &PreparedSyntaxTree,
4598 donor_source: &ProjectFile,
4599 ) -> Arc<[ConditionalIncludeProjection]> {
4600 static EMPTY: OnceLock<Arc<[ConditionalIncludeProjection]>> = OnceLock::new();
4601 let cell = self
4602 .conditional_include_projection_cells
4603 .lock()
4604 .expect("C++ conditional include projection cache poisoned")
4605 .entry(file.clone())
4606 .or_insert_with(|| Arc::new(PoolSafeMemo::new()))
4607 .clone();
4608 let index = cell.get_or_build_pool_independent(|| {
4609 #[cfg(any(test, feature = "test-support"))]
4610 self.conditional_include_projection_index_build_count
4611 .fetch_add(1, Ordering::Relaxed);
4612 let language_guard_stable = OnceCell::new();
4613 find_conditional_include_projection_index(
4614 self.cpp,
4615 self.token,
4616 file,
4617 prepared,
4618 &|| {
4619 *language_guard_stable
4620 .get_or_init(|| self.include_language_guard_is_stable(file))
4621 },
4622 &|| {
4623 #[cfg(any(test, feature = "test-support"))]
4624 self.conditional_include_projection_state_count
4625 .fetch_add(1, Ordering::Relaxed);
4626 },
4627 )
4628 });
4629 index
4630 .get(donor_source)
4631 .cloned()
4632 .unwrap_or_else(|| Arc::clone(EMPTY.get_or_init(|| Arc::from([]))))
4633 }
4634
4635 fn include_language_guard_is_stable(&self, file: &ProjectFile) -> bool {
4636 let mut macro_events = |file: &ProjectFile| {
4637 let cell = self.macro_event_cell(file);
4638 cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice())
4639 .clone()
4640 };
4641 include_language_guard_is_stable_with(
4642 self.cpp,
4643 self.token,
4644 file,
4645 &mut macro_events,
4646 &mut |file: &ProjectFile, prepared: &PreparedSyntaxTree| {
4647 self.structured_include_facts(file, prepared)
4648 },
4649 )
4650 }
4651
4652 #[cfg(any(test, feature = "test-support"))]
4653 pub fn conditional_include_projection_work_counts_for_test(&self) -> (usize, usize) {
4654 (
4655 self.conditional_include_projection_index_build_count
4656 .load(Ordering::Relaxed),
4657 self.conditional_include_projection_state_count
4658 .load(Ordering::Relaxed),
4659 )
4660 }
4661
4662 #[cfg(any(test, feature = "test-support"))]
4663 pub fn conditional_include_target_state_count_for_test(&self) -> usize {
4664 self.conditional_include_target_state_count
4665 .load(Ordering::Relaxed)
4666 }
4667
4668 #[cfg(any(test, feature = "test-support"))]
4669 pub fn include_activation_build_count_for_test(&self) -> usize {
4670 self.include_activation_build_count.load(Ordering::Relaxed)
4671 }
4672
4673 #[cfg(any(test, feature = "test-support"))]
4674 pub fn note_using_donor_activation_for_test(&self) {
4675 self.using_donor_activation_count
4676 .fetch_add(1, Ordering::Relaxed);
4677 }
4678
4679 #[cfg(not(any(test, feature = "test-support")))]
4680 pub fn note_using_donor_activation_for_test(&self) {}
4681
4682 #[cfg(any(test, feature = "test-support"))]
4683 pub fn note_using_namespace_lookup_for_test(&self) {
4684 self.using_namespace_lookup_count
4685 .fetch_add(1, Ordering::Relaxed);
4686 }
4687
4688 #[cfg(not(any(test, feature = "test-support")))]
4689 pub fn note_using_namespace_lookup_for_test(&self) {}
4690
4691 #[cfg(any(test, feature = "test-support"))]
4692 pub fn note_using_name_candidate_inspection_for_test(&self) {
4693 self.using_name_candidate_inspection_count
4694 .fetch_add(1, Ordering::Relaxed);
4695 }
4696
4697 #[cfg(not(any(test, feature = "test-support")))]
4698 pub fn note_using_name_candidate_inspection_for_test(&self) {}
4699
4700 #[cfg(any(test, feature = "test-support"))]
4701 pub fn using_work_counts_for_test(&self) -> (usize, usize, usize, usize) {
4702 (
4703 self.using_donor_activation_count.load(Ordering::Relaxed),
4704 self.using_namespace_lookup_count.load(Ordering::Relaxed),
4705 self.callable_reference_spec_build_count
4706 .load(Ordering::Relaxed),
4707 self.using_name_candidate_inspection_count
4708 .load(Ordering::Relaxed),
4709 )
4710 }
4711
4712 pub fn is_physically_visible(&self, file: &ProjectFile, target: &CodeUnit) -> bool {
4713 file == target.source()
4714 || self
4715 .visible_by_file
4716 .get(file)
4717 .is_some_and(|visible| visible.contains(target))
4718 }
4719
4720 pub fn declaration_visible_at(
4732 &self,
4733 analyzer: &CppGraphSource<'_>,
4734 file: &ProjectFile,
4735 declaration: &CodeUnit,
4736 reference_byte: usize,
4737 ) -> bool {
4738 let reference_guards = OnceCell::new();
4739 self.visible_identifier_candidates(file, declaration.identifier())
4740 .filter(|candidate| {
4741 self.same_logical_callable(analyzer, candidate, declaration)
4742 || flattened_macro_namespace_declaration_matches(
4743 analyzer,
4744 self.cpp,
4745 file,
4746 candidate,
4747 declaration,
4748 reference_byte,
4749 )
4750 })
4751 .any(|candidate| {
4752 self.physical_declaration_visible_at(
4753 analyzer,
4754 file,
4755 candidate,
4756 reference_byte,
4757 &reference_guards,
4758 )
4759 })
4760 }
4761
4762 pub fn declaration_visible_at_reference(
4769 &self,
4770 analyzer: &CppGraphSource<'_>,
4771 file: &ProjectFile,
4772 declaration: &CodeUnit,
4773 reference: Node<'_>,
4774 ) -> bool {
4775 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4776 return false;
4777 };
4778 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
4779 let declaration_guards = declaration_guard_requirements(analyzer, self.cpp, declaration);
4780 if !declaration_guards.iter().any(|(_, required)| {
4781 guards_compatible_at_reference(required, reference_guards.as_ref())
4782 }) {
4783 return false;
4784 }
4785 if declaration.source() == file
4786 && !analyzer.reference_uses_c_semantics(file)
4787 && (declaration.is_field() || declaration.is_callable())
4788 && type_owner_of(analyzer, declaration).is_some_and(|owner| {
4789 self.indexed_enclosing_owner_scope(analyzer, file, reference)
4790 .is_some_and(|scope| scope == canonical_cpp_scope_components(&owner))
4791 })
4792 {
4793 return true;
4797 }
4798 if declaration.is_field() && declaration.source() == file {
4799 let reference_byte = reference.start_byte();
4800 let reference_function =
4801 real_function_definition_ancestor(reference, prepared.source());
4802 let guards = OnceCell::new();
4803 let field_reference = CallableReferenceContext {
4804 file,
4805 position: Some(CallableReferencePosition {
4806 prepared: prepared.as_ref(),
4807 byte: reference_byte,
4808 guards: &guards,
4809 }),
4810 };
4811 let mut has_local_declaration = false;
4812 let mut local_declaration_visible = false;
4813 for declaration in callable_declaration_nodes(analyzer, prepared.as_ref(), declaration)
4814 {
4815 let Some(declaration_function) =
4816 real_function_definition_ancestor(declaration, prepared.source())
4817 else {
4818 continue;
4819 };
4820 has_local_declaration = true;
4821 if reference_function.is_some_and(|reference_function| {
4822 reference_function.start_byte() == declaration_function.start_byte()
4823 && reference_function.end_byte() == declaration_function.end_byte()
4824 }) && callable_preprocessor_context_is_visible_for_reference(
4825 declaration,
4826 prepared.source(),
4827 &field_reference,
4828 ) && declaration.end_byte() < reference_byte
4829 {
4830 local_declaration_visible = true;
4831 break;
4832 }
4833 }
4834 if has_local_declaration {
4835 return local_declaration_visible;
4836 }
4837 }
4838 let guards = OnceCell::new();
4839 self.physical_declaration_visible_at(
4840 analyzer,
4841 file,
4842 declaration,
4843 reference.start_byte(),
4844 &guards,
4845 )
4846 }
4847
4848 pub fn declaration_visible_for_c_forward_call(
4854 &self,
4855 analyzer: &CppGraphSource<'_>,
4856 file: &ProjectFile,
4857 declaration: &CodeUnit,
4858 reference_byte: usize,
4859 ) -> bool {
4860 if self.declaration_visible_at(analyzer, file, declaration, reference_byte) {
4861 return true;
4862 }
4863 if declaration.source() != file {
4864 return false;
4865 }
4866 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4867 return false;
4868 };
4869 let reference_guards = prepared
4870 .tree()
4871 .root_node()
4872 .descendant_for_byte_range(reference_byte, reference_byte)
4873 .and_then(|node| preprocessor_guard_environment(node, prepared.source()));
4874 declaration_guard_requirements(analyzer, self.cpp, declaration)
4875 .into_iter()
4876 .any(|(_, required)| {
4877 guard_requirements_hold_at_reference(&required, reference_guards.as_ref())
4878 })
4879 }
4880
4881 pub fn callable_arity_at_reference(
4882 &self,
4883 analyzer: &CppGraphSource<'_>,
4884 file: &ProjectFile,
4885 candidate: &CodeUnit,
4886 reference_byte: usize,
4887 ) -> Option<CallableArity> {
4888 let key = (file.clone(), logical_symbol_key(candidate));
4889 let cell = self
4890 .callable_reference_specs
4891 .lock()
4892 .expect("C++ callable reference-spec cache poisoned")
4893 .entry(key)
4894 .or_default()
4895 .clone();
4896 let spec = cell.get_or_init(|| {
4897 let prepared = self.cpp.prepared_syntax(self.token, file)?;
4898 let spec = TargetSpec::from_target(analyzer, candidate)?;
4899 let spec = spec
4900 .with_visible_callable_arities(analyzer, self.cpp, self, file, prepared.as_ref())
4901 .into_owned();
4902 #[cfg(any(test, feature = "test-support"))]
4903 self.callable_reference_spec_build_count
4904 .fetch_add(1, Ordering::Relaxed);
4905 Some(spec)
4906 });
4907 spec.as_ref()?.callable_arity_at(reference_byte)
4908 }
4909
4910 fn physical_declaration_visible_at(
4911 &self,
4912 analyzer: &CppGraphSource<'_>,
4913 file: &ProjectFile,
4914 declaration: &CodeUnit,
4915 reference_byte: usize,
4916 reference_guards: &OnceCell<Option<HashSet<PreprocessorGuard>>>,
4917 ) -> bool {
4918 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
4919 return false;
4920 };
4921 let reference = CallableReferenceContext {
4922 file,
4923 position: Some(CallableReferencePosition {
4924 prepared: prepared.as_ref(),
4925 byte: reference_byte,
4926 guards: reference_guards,
4927 }),
4928 };
4929 if declaration.source() == file {
4930 return callable_declaration_activation_in_file(analyzer, declaration, &reference)
4931 .or_else(|| {
4932 self.exhaustive_guard_family_activation(analyzer, declaration, &reference)
4933 })
4934 .is_some_and(|activation| activation < reference_byte);
4935 }
4936 if self
4937 .foreign_callable_declaration_activation(analyzer, declaration, &reference)
4938 .or_else(|| self.exhaustive_guard_family_activation(analyzer, declaration, &reference))
4939 .is_none()
4940 {
4941 return false;
4942 }
4943 declaration_guard_requirements(analyzer, self.cpp, declaration)
4944 .into_iter()
4945 .any(|(_, declaration_guards)| {
4946 self.foreign_declaration_reachable_at_reference(
4947 file,
4948 prepared.as_ref(),
4949 declaration.source(),
4950 &declaration_guards,
4951 reference.guards(),
4952 reference_byte,
4953 )
4954 })
4955 }
4956
4957 fn foreign_callable_declaration_activation(
4972 &self,
4973 analyzer: &CppGraphSource<'_>,
4974 declaration: &CodeUnit,
4975 reference: &CallableReferenceContext<'_>,
4976 ) -> Option<usize> {
4977 let build_decides = !self.compile_context_is_absent(reference.file);
4978 let proven = self.compile_proven_guards(reference.file);
4979 let augmented;
4980 let active = match reference.guards() {
4981 Some(active) if !proven.is_empty() => {
4982 augmented = active.union(&proven).cloned().collect();
4983 Some(&augmented)
4984 }
4985 other => other,
4986 };
4987 analyzer
4988 .declaration_source_occurrences(declaration)?
4989 .into_iter()
4990 .filter_map(|occurrence| {
4991 let activation = occurrence.fact.callable_activation?;
4992 let mut required =
4993 cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
4994 if required.iter().any(|guard| match guard {
4995 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
4996 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => {
4997 !reference.is_c()
4998 }
4999 _ => false,
5000 }) {
5001 return None;
5002 }
5003 required.retain(|guard| {
5004 !matches!(guard,
5005 PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
5006 if name == "__cplusplus")
5007 });
5008 let visible = required.is_empty()
5009 || if build_decides {
5010 guard_requirements_hold_at_reference(&required, active)
5011 } else {
5012 guards_compatible_at_reference(&required, reference.guards())
5013 };
5014 visible.then_some(activation)
5015 })
5016 .min()
5017 }
5018
5019 pub fn external_type_candidate_visible_at(
5020 &self,
5021 file: &ProjectFile,
5022 candidate: &CodeUnit,
5023 reference_byte: usize,
5024 ) -> bool {
5025 if candidate.source() == file {
5026 return true;
5027 }
5028 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5029 return false;
5030 };
5031 self.visible_identifier_candidates(file, candidate.identifier())
5032 .filter(|peer| same_logical_symbol(candidate, peer))
5033 .any(|peer| {
5034 peer.source() == file
5035 || self
5036 .include_activation_for_source(
5037 self.cpp,
5038 file,
5039 prepared.as_ref(),
5040 peer.source(),
5041 )
5042 .is_some_and(|activation| activation <= reference_byte)
5043 })
5044 }
5045
5046 pub fn external_type_declaration_visible_at(
5047 &self,
5048 file: &ProjectFile,
5049 candidate: &CodeUnit,
5050 reference_byte: usize,
5051 ) -> bool {
5052 if candidate.source() == file {
5053 return true;
5054 }
5055 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5056 return false;
5057 };
5058 self.include_activation_for_source(self.cpp, file, prepared.as_ref(), candidate.source())
5059 .is_some_and(|activation| activation <= reference_byte)
5060 }
5061
5062 pub fn compile_proven_guards(&self, file: &ProjectFile) -> Arc<HashSet<PreprocessorGuard>> {
5081 if let Some(cached) = self
5082 .compile_proven_guard_cells
5083 .lock()
5084 .expect("C++ compile-proven guard cache poisoned")
5085 .get(file)
5086 {
5087 return Arc::clone(cached);
5088 }
5089 let names = match context_fact_names(self.cpp.compile_contexts_for(file)) {
5090 Some(names) => names,
5091 None => {
5092 let mut translation_units = self.cpp.reaching_translation_units(file).into_iter();
5093 let seed = translation_units.next().and_then(|translation_unit| {
5094 context_fact_names(self.cpp.compile_contexts_for(&translation_unit))
5095 });
5096 match seed {
5097 None => HashSet::default(),
5098 Some(mut names) => {
5099 for translation_unit in translation_units {
5100 let Some(reached) = context_fact_names(
5101 self.cpp.compile_contexts_for(&translation_unit),
5102 ) else {
5103 names.clear();
5104 break;
5105 };
5106 names.retain(|name| reached.contains(name));
5107 if names.is_empty() {
5108 break;
5109 }
5110 }
5111 names
5112 }
5113 }
5114 }
5115 };
5116 let proven = Arc::new(
5117 names
5118 .into_iter()
5119 .map(PreprocessorGuard::Defined)
5120 .collect::<HashSet<_>>(),
5121 );
5122 self.compile_proven_guard_cells
5123 .lock()
5124 .expect("C++ compile-proven guard cache poisoned")
5125 .insert(file.clone(), Arc::clone(&proven));
5126 proven
5127 }
5128
5129 fn include_path_admission(&self, file: &ProjectFile) -> IncludePathAdmission {
5139 if let Some(cached) = self
5140 .include_path_admission_cells
5141 .lock()
5142 .expect("C++ include-path admission cache poisoned")
5143 .get(file)
5144 .copied()
5145 {
5146 return cached;
5147 }
5148 let admission = if self.compile_context_is_absent(file) {
5149 IncludePathAdmission::Compatible
5150 } else {
5151 IncludePathAdmission::Proven
5152 };
5153 self.include_path_admission_cells
5154 .lock()
5155 .expect("C++ include-path admission cache poisoned")
5156 .insert(file.clone(), admission);
5157 admission
5158 }
5159
5160 fn compile_context_is_absent(&self, file: &ProjectFile) -> bool {
5167 if !self.cpp.compile_contexts_for(file).is_empty() {
5168 return false;
5169 }
5170 let translation_units = self.cpp.reaching_translation_units(file);
5171 translation_units.is_empty()
5172 || translation_units
5173 .iter()
5174 .any(|translation_unit| self.cpp.compile_contexts_for(translation_unit).is_empty())
5175 }
5176
5177 pub fn miss_requires_compile_context(
5189 &self,
5190 file: &ProjectFile,
5191 identifier: &str,
5192 reference: Node<'_>,
5193 ) -> bool {
5194 if !self.compile_context_is_absent(file) {
5195 return false;
5196 }
5197 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5198 return false;
5199 };
5200 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5201 let reference_byte = reference.start_byte();
5202 let mut sources = self
5203 .visible_identifier_candidates(file, identifier)
5204 .map(CodeUnit::source)
5205 .filter(|source| *source != file)
5206 .collect::<Vec<_>>();
5207 sources.sort();
5208 sources.dedup();
5209 sources.into_iter().any(|declaration_source| {
5210 self.conditional_include_projections_for_source(
5211 file,
5212 prepared.as_ref(),
5213 declaration_source,
5214 )
5215 .iter()
5216 .any(|projection| {
5217 projection.activation_byte <= reference_byte
5218 && !guard_requirements_hold_at_reference(
5219 &projection.required_guards,
5220 reference_guards.as_ref(),
5221 )
5222 && guards_compatible_at_reference(
5223 &projection.required_guards,
5224 reference_guards.as_ref(),
5225 )
5226 })
5227 })
5228 }
5229
5230 fn foreign_declaration_reachable_at_reference(
5242 &self,
5243 file: &ProjectFile,
5244 prepared: &PreparedSyntaxTree,
5245 declaration_source: &ProjectFile,
5246 declaration_guards: &HashSet<PreprocessorGuard>,
5247 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5248 reference_byte: usize,
5249 ) -> bool {
5250 let proven = self.compile_proven_guards(file);
5256 let augmented;
5257 let reference_guards = match reference_guards {
5258 Some(active) if !proven.is_empty() => {
5259 augmented = active.union(&proven).cloned().collect();
5260 Some(&augmented)
5261 }
5262 other => other,
5263 };
5264 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5265 eprintln!(
5266 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=foreign_guard_compatibility declaration_source={} declaration_guards={declaration_guards:?} reference_guards={reference_guards:?}",
5267 declaration_source.rel_path().display(),
5268 );
5269 }
5270 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5271 return false;
5272 }
5273 if self
5274 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5275 .is_some_and(|activation| activation <= reference_byte)
5276 {
5277 return true;
5278 }
5279 let projections =
5280 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5281 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5282 eprintln!(
5283 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=filtered_projection source={} declaration_guards={} proven_guards={} projections={}",
5284 declaration_source.rel_path().display(),
5285 declaration_guards.len(),
5286 proven.len(),
5287 projections.len(),
5288 );
5289 }
5290 let admission = self.include_path_admission(file);
5291 projections.iter().any(|projection| {
5292 projection.activation_byte <= reference_byte
5293 && admission.admits(
5294 &projection.required_guards,
5295 &projection.partial_guards,
5296 reference_guards,
5297 )
5298 && self.preprocessor_guards_stable_between(
5299 file,
5300 projection.activation_byte,
5301 reference_byte,
5302 &projection.required_guards,
5303 )
5304 })
5305 }
5306
5307 fn foreign_declaration_may_be_reachable_from_raw_guards(
5308 &self,
5309 file: &ProjectFile,
5310 prepared: &PreparedSyntaxTree,
5311 declaration_source: &ProjectFile,
5312 declaration_guards: &HashSet<PreprocessorGuard>,
5313 reference_guards: Option<&HashSet<PreprocessorGuard>>,
5314 reference_byte: usize,
5315 ) -> bool {
5316 let proven = self.compile_proven_guards(file);
5317 let augmented;
5318 let reference_guards = match reference_guards {
5319 Some(active) if !proven.is_empty() => {
5320 augmented = active.union(&proven).cloned().collect();
5321 Some(&augmented)
5322 }
5323 other => other,
5324 };
5325 if !guards_compatible_at_reference(declaration_guards, reference_guards) {
5326 return false;
5327 }
5328 if self
5329 .include_activation_for_source(self.cpp, file, prepared, declaration_source)
5330 .is_some_and(|activation| activation <= reference_byte)
5331 {
5332 return true;
5333 }
5334 let language_guard_stable = OnceCell::new();
5335 let reachable = find_conditional_include_projection_for_source(
5336 self.cpp,
5337 self.token,
5338 file,
5339 prepared,
5340 declaration_source,
5341 self.include_path_admission(file),
5342 reference_guards,
5343 reference_byte,
5344 &|| *language_guard_stable.get_or_init(|| self.include_language_guard_is_stable(file)),
5345 &|| {
5346 #[cfg(any(test, feature = "test-support"))]
5347 self.conditional_include_target_state_count
5348 .fetch_add(1, Ordering::Relaxed);
5349 },
5350 );
5351 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5352 eprintln!(
5353 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_projection source={} declaration_guards={} proven_guards={} raw_guards={} reachable={reachable}",
5354 declaration_source.rel_path().display(),
5355 declaration_guards.len(),
5356 proven.len(),
5357 reference_guards.map_or(0, HashSet::len),
5358 );
5359 }
5360 reachable
5361 }
5362
5363 fn foreign_declaration_reachable_from_compile_proven_guards(
5364 &self,
5365 file: &ProjectFile,
5366 prepared: &PreparedSyntaxTree,
5367 declaration_source: &ProjectFile,
5368 declaration_guards: &HashSet<PreprocessorGuard>,
5369 reference_byte: usize,
5370 ) -> bool {
5371 let proven = self.compile_proven_guards(file);
5372 if proven.is_empty()
5373 || !guards_compatible_at_reference(declaration_guards, Some(proven.as_ref()))
5374 {
5375 return false;
5376 }
5377 let projections =
5378 self.conditional_include_projections_for_source(file, prepared, declaration_source);
5379 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
5380 eprintln!(
5381 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=compile_proven_projection source={} declaration_guards={} proven_guards={} projections={}",
5382 declaration_source.rel_path().display(),
5383 declaration_guards.len(),
5384 proven.len(),
5385 projections.len(),
5386 );
5387 }
5388 projections.iter().any(|projection| {
5389 projection.activation_byte <= reference_byte
5390 && guard_requirements_hold_at_reference(
5391 &projection.required_guards,
5392 Some(proven.as_ref()),
5393 )
5394 && self.preprocessor_guards_stable_between(
5399 file,
5400 0,
5401 projection.activation_byte,
5402 &projection.required_guards,
5403 )
5404 })
5405 }
5406
5407 pub fn external_type_candidate_visible_in_context(
5408 &self,
5409 analyzer: &CppGraphSource<'_>,
5410 file: &ProjectFile,
5411 candidate: &CodeUnit,
5412 reference: Node<'_>,
5413 ) -> bool {
5414 let report_stats = std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some();
5415 if report_stats {
5416 eprintln!(
5417 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=started fqn={} candidate_source={} reference_file={} reference_byte={}",
5418 candidate.fq_name(),
5419 candidate.source().rel_path().display(),
5420 file.rel_path().display(),
5421 reference.start_byte(),
5422 );
5423 }
5424 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5425 return false;
5426 };
5427 let raw_reference_guards = preprocessor_guard_environment(reference, prepared.source());
5428 let reference_guards = OnceCell::new();
5429 let reference_guards_at_site = || {
5430 reference_guards.get_or_init(|| {
5431 let started = Instant::now();
5432 if report_stats {
5433 eprintln!(
5434 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=started file={} reference_byte={} raw_guards={}",
5435 file.rel_path().display(),
5436 reference.start_byte(),
5437 raw_reference_guards.as_ref().map_or(0, HashSet::len),
5438 );
5439 }
5440 let macro_environment = self.macro_environment(file, reference.start_byte());
5441 let filtered = raw_reference_guards
5442 .clone()
5443 .filter(|guards| macro_environment.guard_requirements_may_hold(guards));
5444 if report_stats {
5445 eprintln!(
5446 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=macro_environment status=completed retained={} elapsed_ms={}",
5447 filtered.is_some(),
5448 started.elapsed().as_millis(),
5449 );
5450 }
5451 filtered
5452 })
5453 };
5454
5455 let peers = self
5456 .visible_identifier_candidates(file, candidate.identifier())
5457 .filter(|peer| same_logical_symbol(candidate, peer))
5458 .collect::<Vec<_>>();
5459 if report_stats {
5460 let peer_sources = peers
5461 .iter()
5462 .map(|peer| peer.source().rel_path().display().to_string())
5463 .collect::<Vec<_>>();
5464 eprintln!(
5465 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=peers fqn={} sources={peer_sources:?}",
5466 candidate.fq_name(),
5467 );
5468 }
5469 let directly_visible_without_reference_environment = peers.iter().any(|peer| {
5470 declaration_guard_requirements(analyzer, self.cpp, peer)
5471 .into_iter()
5472 .any(|(declaration_byte, declaration_guards)| {
5473 if peer.source() == file {
5474 let visible = declaration_byte < reference.start_byte()
5475 && declaration_guards.is_empty();
5476 if report_stats {
5477 eprintln!(
5478 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=true visible={visible}",
5479 peer.source().rel_path().display(),
5480 declaration_guards.len(),
5481 );
5482 }
5483 return visible;
5484 }
5485 let direct = declaration_guards.is_empty()
5486 && self
5487 .include_activation_for_source(
5488 self.cpp,
5489 file,
5490 prepared.as_ref(),
5491 peer.source(),
5492 )
5493 .is_some_and(|activation| activation <= reference.start_byte());
5494 let compile_proven = !direct
5495 && self.foreign_declaration_reachable_from_compile_proven_guards(
5496 file,
5497 prepared.as_ref(),
5498 peer.source(),
5499 &declaration_guards,
5500 reference.start_byte(),
5501 );
5502 if report_stats {
5503 eprintln!(
5504 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=direct_peer source={} declaration_guards={} same_file=false direct={direct} compile_proven={compile_proven}",
5505 peer.source().rel_path().display(),
5506 declaration_guards.len(),
5507 );
5508 }
5509 direct || compile_proven
5510 })
5511 });
5512 if directly_visible_without_reference_environment {
5513 if report_stats {
5514 eprintln!(
5515 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=direct_or_compile_proven fqn={}",
5516 candidate.fq_name(),
5517 );
5518 }
5519 return true;
5520 }
5521 let directly_visible = peers.iter().any(|peer| {
5522 declaration_guard_sites(analyzer, self.cpp, peer, Some(reference.start_byte()))
5523 .into_iter()
5524 .any(|site| {
5525 let declaration_byte = site.byte;
5526 let declaration_guards = site.guards;
5527 if peer.source() == file {
5528 if declaration_byte >= reference.start_byte() {
5529 return false;
5530 }
5531 let active = |guards: Option<&HashSet<PreprocessorGuard>>| {
5536 guard_requirements_hold_at_reference(&declaration_guards, guards)
5537 || (site.completed_family_before_reference
5538 && guards_compatible_at_reference(&declaration_guards, guards))
5539 };
5540 if !active(raw_reference_guards.as_ref()) {
5541 return false;
5542 }
5543 return active(reference_guards_at_site().as_ref())
5544 && self.preprocessor_guards_stable_between(
5545 file,
5546 declaration_byte,
5547 reference.start_byte(),
5548 &declaration_guards,
5549 );
5550 }
5551 let raw_feasible = self.foreign_declaration_may_be_reachable_from_raw_guards(
5552 file,
5553 prepared.as_ref(),
5554 peer.source(),
5555 &declaration_guards,
5556 raw_reference_guards.as_ref(),
5557 reference.start_byte(),
5558 );
5559 if report_stats {
5560 eprintln!(
5561 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=raw_feasibility source={} declaration_guards={} feasible={raw_feasible}",
5562 peer.source().rel_path().display(),
5563 declaration_guards.len(),
5564 );
5565 }
5566 if !raw_feasible {
5567 return false;
5568 }
5569 self.foreign_declaration_reachable_at_reference(
5570 file,
5571 prepared.as_ref(),
5572 peer.source(),
5573 &declaration_guards,
5574 reference_guards_at_site().as_ref(),
5575 reference.start_byte(),
5576 )
5577 })
5578 });
5579 if directly_visible {
5580 if report_stats {
5581 eprintln!(
5582 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome=filtered_reference fqn={}",
5583 candidate.fq_name(),
5584 );
5585 }
5586 return true;
5587 }
5588 let complementary = self
5589 .visible_identifier_candidates(file, candidate.identifier())
5590 .filter(|peer| {
5591 peer.kind() == candidate.kind()
5592 && peer.fq_name() == candidate.fq_name()
5593 && peer.source() == candidate.source()
5594 })
5595 .collect::<Vec<_>>();
5596 let complementary_family =
5601 self.complementary_same_fqn_type_declarations(analyzer, &complementary, candidate);
5602 let raw_candidate_branch_compatible = complementary_family
5603 && raw_reference_guards.as_ref().is_some_and(|active| {
5604 declaration_guard_requirements(analyzer, self.cpp, candidate)
5605 .iter()
5606 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5607 });
5608 if report_stats {
5609 eprintln!(
5610 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=complementary fqn={} candidates={} family={} raw_compatible={}",
5611 candidate.fq_name(),
5612 complementary.len(),
5613 complementary_family,
5614 raw_candidate_branch_compatible,
5615 );
5616 }
5617 let candidate_branch_compatible = raw_candidate_branch_compatible
5618 && reference_guards_at_site().as_ref().is_some_and(|active| {
5619 declaration_guard_requirements(analyzer, self.cpp, candidate)
5620 .iter()
5621 .any(|(_, required)| merge_preprocessor_guards(required, active).is_some())
5622 });
5623 let complementary_visible = candidate_branch_compatible
5624 && if candidate.source() == file {
5625 declaration_guard_requirements(analyzer, self.cpp, candidate)
5626 .iter()
5627 .any(|(declaration_byte, _)| *declaration_byte < reference.start_byte())
5628 } else {
5629 self.include_activation_for_source(
5630 self.cpp,
5631 file,
5632 prepared.as_ref(),
5633 candidate.source(),
5634 )
5635 .is_some_and(|activation| activation <= reference.start_byte())
5636 };
5637 if report_stats {
5638 eprintln!(
5639 "BIFROST_CPP_TYPE_VISIBILITY_STATS phase=candidate status=completed outcome={} fqn={}",
5640 if complementary_visible {
5641 "complementary"
5642 } else {
5643 "missing"
5644 },
5645 candidate.fq_name(),
5646 );
5647 }
5648 complementary_visible
5649 }
5650
5651 pub fn is_exhaustive_same_fqn_type_declaration_family(
5652 &self,
5653 analyzer: &CppGraphSource<'_>,
5654 file: &ProjectFile,
5655 candidate: &CodeUnit,
5656 ) -> bool {
5657 let candidates = self
5658 .visible_identifier_candidates(file, candidate.identifier())
5659 .filter(|peer| {
5660 peer.kind() == candidate.kind()
5661 && peer.fq_name() == candidate.fq_name()
5662 && peer.source() == candidate.source()
5663 })
5664 .collect::<Vec<_>>();
5665 self.complementary_same_fqn_type_declarations(analyzer, &candidates, candidate)
5666 }
5667
5668 pub fn dependent_member_pointer_alias_visible_in_context(
5683 &self,
5684 analyzer: &CppGraphSource<'_>,
5685 file: &ProjectFile,
5686 candidate: &CodeUnit,
5687 owner_components: &[String],
5688 reference: Node<'_>,
5689 ) -> bool {
5690 if !analyzer
5691 .type_alias_provider()
5692 .is_some_and(|provider| provider.is_type_alias(candidate))
5693 {
5694 return false;
5695 }
5696 let Some((terminal, owner_prefix)) = owner_components.split_last() else {
5697 return false;
5698 };
5699 if terminal != candidate.identifier()
5700 || canonical_cpp_scope_components(candidate) != owner_components
5701 {
5702 return false;
5703 }
5704 let Some(expected_parent_fq_name) =
5705 brokk_bifrost_core::analyzer::default_parent_fq_name(candidate)
5706 else {
5707 return false;
5708 };
5709 let Some(parent_anchor) = type_owner_of(analyzer, candidate) else {
5710 return false;
5711 };
5712 if parent_anchor.fq_name() != expected_parent_fq_name.as_str()
5713 || parent_anchor.source() != candidate.source()
5714 || canonical_cpp_scope_components(&parent_anchor) != owner_prefix
5715 {
5716 return false;
5717 }
5718
5719 if !self.external_type_candidate_visible_at(file, candidate, reference.start_byte())
5725 || candidate.source() == file
5726 && !analyzer
5727 .ranges(candidate)
5728 .iter()
5729 .any(|range| range.start_byte < reference.start_byte())
5730 {
5731 return false;
5732 }
5733
5734 let candidate_guards = declaration_guard_requirements(analyzer, self.cpp, candidate);
5735 if candidate_guards.is_empty() {
5736 return false;
5737 }
5738 let same_guard_sets =
5739 |left: &[(usize, HashSet<PreprocessorGuard>)],
5740 right: &[(usize, HashSet<PreprocessorGuard>)]| {
5741 left.iter().all(|(_, left_guards)| {
5742 right
5743 .iter()
5744 .any(|(_, right_guards)| left_guards == right_guards)
5745 })
5746 };
5747 let parent_candidates = self
5748 .visible_identifier_candidates(file, parent_anchor.identifier())
5749 .filter(|peer| {
5750 peer.kind() == parent_anchor.kind()
5751 && peer.fq_name() == expected_parent_fq_name.as_str()
5752 && peer.source() == parent_anchor.source()
5753 && canonical_cpp_scope_components(peer) == owner_prefix
5754 })
5755 .filter_map(|peer| {
5756 let parent_guards = declaration_guard_requirements(analyzer, self.cpp, peer);
5757 (candidate_guards.len() == parent_guards.len()
5758 && same_guard_sets(&candidate_guards, &parent_guards)
5759 && same_guard_sets(&parent_guards, &candidate_guards))
5760 .then(|| (peer.clone(), parent_guards))
5761 })
5762 .collect::<Vec<_>>();
5763 let [(parent, _parent_guards)] = parent_candidates.as_slice() else {
5764 return false;
5765 };
5766
5767 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5768 return false;
5769 };
5770 let Some(reference_guards) = preprocessor_guard_environment(reference, prepared.source())
5771 else {
5772 return false;
5773 };
5774 if !candidate_guards.iter().any(|(_, target_guards)| {
5779 guards_compatible_at_reference(target_guards, Some(&reference_guards))
5780 && (candidate.source() != file
5781 || self.preprocessor_guards_stable_between(
5782 file,
5783 0,
5784 reference.start_byte(),
5785 target_guards,
5786 ))
5787 }) {
5788 return false;
5789 }
5790
5791 self.external_type_candidate_visible_in_context(analyzer, file, parent, reference)
5792 }
5793
5794 pub fn external_type_candidate_guard_compatible_in_context(
5804 &self,
5805 analyzer: &CppGraphSource<'_>,
5806 file: &ProjectFile,
5807 candidate: &CodeUnit,
5808 reference: Node<'_>,
5809 ) -> bool {
5810 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5811 return false;
5812 };
5813 let reference_guards = preprocessor_guard_environment(reference, prepared.source());
5814
5815 self.visible_identifier_candidates(file, candidate.identifier())
5816 .filter(|peer| same_logical_symbol(candidate, peer))
5817 .any(|peer| {
5818 declaration_guard_requirements(analyzer, self.cpp, peer)
5819 .into_iter()
5820 .any(|(declaration_byte, declaration_guards)| {
5821 if peer.source() == file {
5822 let (start, end) = if declaration_byte <= reference.start_byte() {
5823 (declaration_byte, reference.start_byte())
5824 } else {
5825 (reference.start_byte(), declaration_byte)
5826 };
5827 return guard_requirements_hold_at_reference(
5828 &declaration_guards,
5829 reference_guards.as_ref(),
5830 ) && self.preprocessor_guards_stable_between(
5831 file,
5832 start,
5833 end,
5834 &declaration_guards,
5835 );
5836 }
5837 self.foreign_declaration_reachable_at_reference(
5838 file,
5839 prepared.as_ref(),
5840 peer.source(),
5841 &declaration_guards,
5842 reference_guards.as_ref(),
5843 reference.start_byte(),
5844 )
5845 })
5846 })
5847 }
5848
5849 pub fn same_file_callable_guard_compatible_ignoring_order(
5857 &self,
5858 analyzer: &CppGraphSource<'_>,
5859 file: &ProjectFile,
5860 candidate: &CodeUnit,
5861 reference: Node<'_>,
5862 ) -> bool {
5863 if candidate.source() != file || !candidate.is_callable() {
5864 return false;
5865 }
5866 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5867 return false;
5868 };
5869 let guards = OnceCell::new();
5870 let context = CallableReferenceContext {
5871 file,
5872 position: Some(CallableReferencePosition {
5873 prepared: prepared.as_ref(),
5874 byte: reference.start_byte(),
5875 guards: &guards,
5876 }),
5877 };
5878 analyzer
5879 .declaration_source_occurrences(candidate)
5880 .into_iter()
5881 .flatten()
5882 .filter_map(|occurrence| {
5883 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
5884 Some(callable_guard_requirements_are_visible(
5885 &guards,
5886 occurrence.fact.callable_guard_completion_byte,
5887 &context,
5888 ))
5889 })
5890 .any(|visible| visible)
5891 }
5892
5893 pub fn type_candidate_may_be_visible_before_reference(
5894 &self,
5895 analyzer: &CppGraphSource<'_>,
5896 file: &ProjectFile,
5897 candidate: &CodeUnit,
5898 reference_byte: usize,
5899 ) -> bool {
5900 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
5901 return false;
5902 };
5903 let root = prepared.tree().root_node();
5904 let end_byte = reference_byte
5905 .saturating_add(1)
5906 .min(prepared.source().len());
5907 let Some(reference) = root.descendant_for_byte_range(reference_byte, end_byte) else {
5908 return false;
5909 };
5910 self.external_type_candidate_visible_in_context(analyzer, file, candidate, reference)
5911 }
5912
5913 pub fn preprocessor_guards_stable_between(
5914 &self,
5915 file: &ProjectFile,
5916 start_byte: usize,
5917 end_byte: usize,
5918 guards: &HashSet<PreprocessorGuard>,
5919 ) -> bool {
5920 if guards.is_empty() || start_byte >= end_byte {
5921 return true;
5922 }
5923 let cell = self.macro_event_cell(file);
5924 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5925 let mut visited = HashSet::from_iter([file.clone()]);
5926 !events.iter().any(|event| {
5927 event.byte() >= start_byte
5928 && event.byte() < end_byte
5929 && self.macro_event_may_mutate_guards(event, guards, &mut visited)
5930 })
5931 }
5932
5933 fn macro_event_may_mutate_guards(
5934 &self,
5935 event: &MacroEvent,
5936 guards: &HashSet<PreprocessorGuard>,
5937 visited: &mut HashSet<ProjectFile>,
5938 ) -> bool {
5939 match event {
5940 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
5941 guards.iter().any(|guard| guard.may_depend_on_macro(name))
5942 }
5943 MacroEvent::Include { targets, .. } => {
5944 targets.is_empty()
5945 || targets
5946 .iter()
5947 .any(|target| self.source_may_mutate_guards(target, guards, visited))
5948 }
5949 MacroEvent::Invalidate { .. } => true,
5950 }
5951 }
5952
5953 fn source_may_mutate_guards(
5954 &self,
5955 file: &ProjectFile,
5956 guards: &HashSet<PreprocessorGuard>,
5957 visited: &mut HashSet<ProjectFile>,
5958 ) -> bool {
5959 if !visited.insert(file.clone()) {
5960 return false;
5961 }
5962 let cell = self.macro_event_cell(file);
5963 let events = cell.get_or_init(|| self.collect_macro_events(file).into_boxed_slice());
5964 events
5965 .iter()
5966 .any(|event| self.macro_event_may_mutate_guards(event, guards, visited))
5967 }
5968
5969 pub fn resolve_type(&self, file: &ProjectFile, raw_name: &str) -> Option<CodeUnit> {
5970 let normalized = normalize_reference_name(raw_name)?;
5971 self.type_candidates(file, &normalized)
5972 .into_iter()
5973 .next()
5974 .cloned()
5975 }
5976
5977 pub fn unique_visible_parameter_type_fallback(
5986 &self,
5987 analyzer: &CppGraphSource<'_>,
5988 file: &ProjectFile,
5989 node: Node<'_>,
5990 source: &str,
5991 ) -> Option<CodeUnit> {
5992 if node.kind() != "type_identifier" || !is_parameter_type_reference(node) {
5993 return None;
5994 }
5995 let name = node_text(node, source);
5996 let candidates = self
5997 .visible_identifier_candidates(file, name)
5998 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
5999 .filter(|candidate| {
6000 self.external_type_candidate_visible_in_context(analyzer, file, candidate, node)
6001 })
6002 .collect::<Vec<_>>();
6003 self.unique_canonical_type_candidate(analyzer, file, &candidates)
6004 }
6005
6006 pub fn resolve_type_node_result(
6007 &self,
6008 file: &ProjectFile,
6009 node: Node<'_>,
6010 source: &str,
6011 ) -> std::result::Result<Option<CodeUnit>, CppTemplateResolutionError> {
6012 let Some(primary) = self.resolve_type_node_primary(file, node, source) else {
6013 return Ok(None);
6014 };
6015 let Some(arguments) = cpp_template_reference_arguments(node, source) else {
6016 return Ok(Some(primary));
6017 };
6018 self.resolve_template_arguments(file, primary, &arguments)
6019 .map(Some)
6020 }
6021
6022 pub fn resolve_type_node_primary(
6023 &self,
6024 file: &ProjectFile,
6025 node: Node<'_>,
6026 source: &str,
6027 ) -> Option<CodeUnit> {
6028 let components = cpp_type_name_components(node, source)?;
6029 self.resolve_type(file, &components.join("::"))
6030 }
6031
6032 pub fn resolve_template_arguments(
6033 &self,
6034 file: &ProjectFile,
6035 primary: CodeUnit,
6036 arguments: &[CppTemplateExpression],
6037 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6038 self.resolve_template_arguments_inner(file, primary, arguments, &mut HashSet::default())
6039 }
6040
6041 fn resolve_template_arguments_inner(
6042 &self,
6043 file: &ProjectFile,
6044 primary: CodeUnit,
6045 arguments: &[CppTemplateExpression],
6046 seen_aliases: &mut HashSet<CodeUnit>,
6047 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6048 if let Some(metadata) = self.cpp_template_metadata.get(&primary)
6049 && let Some(alias_target) = &metadata.alias_target
6050 {
6051 if !seen_aliases.insert(primary.clone()) {
6052 return Err(CppTemplateResolutionError::AliasCycle { alias: primary });
6053 }
6054 let (_, bindings) = cpp_bind_template_arguments(&metadata.parameters, arguments)
6055 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6056 let target_name = alias_target.components.join("::");
6057 let target_primary = if alias_target.global {
6058 unique_logical_type_candidate(self.type_candidates(file, &target_name))
6059 } else {
6060 self.resolve_unique_type_for_declaration(file, &primary, &target_name)
6061 };
6062 let Some(target_primary) = target_primary else {
6063 return Ok(primary);
6067 };
6068 let Some(target_arguments) = &alias_target.arguments else {
6069 return Ok(target_primary);
6070 };
6071 let target_arguments = cpp_substitute_template_arguments(target_arguments, &bindings)
6072 .ok_or(CppTemplateResolutionError::Substitution)?;
6073 return self.resolve_template_arguments_inner(
6074 file,
6075 target_primary,
6076 &target_arguments,
6077 seen_aliases,
6078 );
6079 }
6080
6081 let primary_fq_name = self
6082 .cpp_template_metadata
6083 .get(&primary)
6084 .map(|metadata| metadata.primary_fq_name.clone())
6085 .unwrap_or_else(|| primary.fq_name());
6086 let has_specialization_metadata = self
6087 .cpp_template_families
6088 .get(&primary_fq_name)
6089 .is_some_and(|family| family.iter().any(|unit| self.is_visible(file, unit)));
6090 if !has_specialization_metadata {
6091 return Ok(primary);
6092 }
6093 self.select_template_specialization(file, &primary, arguments)
6094 }
6095
6096 fn select_template_specialization(
6097 &self,
6098 file: &ProjectFile,
6099 resolved: &CodeUnit,
6100 explicit_arguments: &[CppTemplateExpression],
6101 ) -> std::result::Result<CodeUnit, CppTemplateResolutionError> {
6102 let primary_fq_name = self
6103 .cpp_template_metadata
6104 .get(resolved)
6105 .map(|metadata| metadata.primary_fq_name.clone())
6106 .unwrap_or_else(|| resolved.fq_name());
6107 let family = self
6108 .cpp_template_families
6109 .get(&primary_fq_name)
6110 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6111 let primary_candidates = family
6112 .iter()
6113 .filter_map(|unit| {
6114 let metadata = self.cpp_template_metadata.get(unit)?;
6115 (metadata.is_primary() && self.is_visible(file, unit)).then_some((unit, metadata))
6116 })
6117 .collect::<Vec<_>>();
6118 let primary_unit = primary_candidates
6119 .iter()
6120 .find_map(|(unit, _)| (*unit == resolved).then_some(*unit))
6121 .or_else(|| {
6122 primary_candidates
6123 .iter()
6124 .map(|(unit, _)| *unit)
6125 .min_by_key(|unit| {
6126 (
6127 unit.source().to_string(),
6128 unit.signature().unwrap_or_default(),
6129 )
6130 })
6131 })
6132 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6133 let primary_parameters =
6134 cpp_reconcile_primary_template_parameters(&primary_candidates, primary_unit)
6135 .ok_or(CppTemplateResolutionError::PrimarySelection)?;
6136 let (expanded, _) = cpp_bind_template_arguments(&primary_parameters, explicit_arguments)
6137 .ok_or(CppTemplateResolutionError::ArgumentBinding)?;
6138
6139 let mut applicable = Vec::new();
6140 for unit in family {
6141 let Some(metadata) = self.cpp_template_metadata.get(unit) else {
6142 continue;
6143 };
6144 if metadata.is_primary() || !self.is_visible(file, unit) {
6145 continue;
6146 }
6147 if !cpp_specialization_matches(metadata, &expanded) {
6148 continue;
6149 }
6150 applicable.push((unit, metadata));
6151 }
6152 if applicable.is_empty() {
6153 return Ok(primary_unit.clone());
6154 }
6155
6156 let winners = applicable
6161 .iter()
6162 .filter(|(candidate, candidate_metadata)| {
6163 applicable.iter().all(|(other, other_metadata)| {
6164 same_visible_symbol(candidate, other)
6165 || cpp_specialization_more_specialized(candidate_metadata, other_metadata)
6166 })
6167 })
6168 .copied()
6169 .collect::<Vec<_>>();
6170 let Some((selected, _)) = winners.first() else {
6171 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6174 candidates: distinct_visible_symbols(applicable.iter().map(|(unit, _)| *unit)),
6175 });
6176 };
6177 if winners
6178 .iter()
6179 .any(|(unit, _)| !same_visible_symbol(unit, selected))
6180 {
6181 return Err(CppTemplateResolutionError::AmbiguousSpecialization {
6182 candidates: distinct_visible_symbols(winners.iter().map(|(unit, _)| *unit)),
6183 });
6184 }
6185 Ok((*selected).clone())
6186 }
6187
6188 pub fn resolve_type_components_lexically(
6189 &self,
6190 analyzer: &CppGraphSource<'_>,
6191 file: &ProjectFile,
6192 components: &[String],
6193 global: bool,
6194 lexical_scope: &[String],
6195 ) -> LexicalTypeResolution {
6196 self.resolve_type_components_lexically_inner(
6197 analyzer,
6198 file,
6199 components,
6200 global,
6201 lexical_scope,
6202 TypeCandidateResolution::Canonical,
6203 )
6204 }
6205
6206 pub fn resolve_type_components_lexically_for_forward(
6207 &self,
6208 analyzer: &CppGraphSource<'_>,
6209 file: &ProjectFile,
6210 components: &[String],
6211 global: bool,
6212 lexical_scope: &[String],
6213 ) -> LexicalTypeResolution {
6214 self.resolve_type_components_lexically_inner(
6215 analyzer,
6216 file,
6217 components,
6218 global,
6219 lexical_scope,
6220 TypeCandidateResolution::PreserveAlias,
6221 )
6222 }
6223
6224 pub fn resolve_type_components_lexically_for_target(
6225 &self,
6226 analyzer: &CppGraphSource<'_>,
6227 file: &ProjectFile,
6228 components: &[String],
6229 global: bool,
6230 lexical_scope: &[String],
6231 target: &CodeUnit,
6232 ) -> LexicalTypeResolution {
6233 #[cfg(any(test, feature = "test-support"))]
6234 self.target_preserving_type_resolution_count
6235 .fetch_add(1, Ordering::Relaxed);
6236 self.resolve_type_components_lexically_inner(
6237 analyzer,
6238 file,
6239 components,
6240 global,
6241 lexical_scope,
6242 TypeCandidateResolution::PreserveTarget(target),
6243 )
6244 }
6245
6246 pub fn coarse_unqualified_type_reference_may_resolve(
6247 &self,
6248 file: &ProjectFile,
6249 name: &str,
6250 ) -> bool {
6251 if name.is_empty() {
6252 return true;
6253 }
6254 self.visible_identifier_candidates(file, name)
6255 .any(|candidate| {
6256 candidate.kind() == CodeUnitType::Class
6257 || declared_type_alias(&self.cpp_source(), candidate)
6258 })
6259 || self.visible_parser_alias_name_is_visible(file, name)
6260 }
6261
6262 #[allow(clippy::too_many_arguments)]
6263 pub fn structured_type_reference_may_resolve_to_target(
6264 &self,
6265 analyzer: &CppGraphSource<'_>,
6266 file: &ProjectFile,
6267 components: &[String],
6268 global: bool,
6269 lexical_scope: &[String],
6270 target: &CodeUnit,
6271 ) -> bool {
6272 if components.is_empty() {
6273 return true;
6274 }
6275 let Some(terminal) = components.last() else {
6276 return true;
6277 };
6278 let qualified_tiers = lexical_component_tiers(components, global, lexical_scope)
6279 .map(|qualified| qualified.join("::"))
6280 .collect::<Vec<_>>();
6281 let target_name = cpp_name_for(target);
6282 if qualified_tiers
6283 .iter()
6284 .any(|qualified| qualified == &target_name)
6285 {
6286 return true;
6287 }
6288
6289 let mut saw_shape_candidate = false;
6290 for candidate in self.visible_identifier_candidates(file, terminal) {
6291 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6292 {
6293 continue;
6294 }
6295 let candidate_name = cpp_name_for(candidate);
6296 let shape_matches = if global || components.len() > 1 {
6297 qualified_tiers
6298 .iter()
6299 .any(|qualified| qualified == &candidate_name)
6300 } else {
6301 true
6302 };
6303 if !shape_matches {
6304 continue;
6305 }
6306 saw_shape_candidate = true;
6307 if same_visible_symbol(candidate, target)
6308 || self.c_tag_declaration_family_matches_target(
6309 analyzer,
6310 file,
6311 std::slice::from_ref(&candidate),
6312 target,
6313 )
6314 || self.compatible_primary_template_redeclarations(candidate, target)
6315 || (declared_type_alias(analyzer, candidate)
6316 && self.alias_candidate_may_preserve_target(analyzer, file, candidate, target))
6317 {
6318 return true;
6319 }
6320 }
6321
6322 !saw_shape_candidate
6323 }
6324
6325 fn cached_c_tag_kind(
6332 &self,
6333 analyzer: &CppGraphSource<'_>,
6334 candidate: &CodeUnit,
6335 ) -> Option<CppCTagKind> {
6336 if let Some(kind) = self
6337 .c_tag_kind_cache
6338 .lock()
6339 .expect("C tag kind cache poisoned")
6340 .get(candidate)
6341 {
6342 return *kind;
6343 }
6344 let kind = indexed_c_tag_kind(analyzer, candidate);
6345 self.c_tag_kind_cache
6346 .lock()
6347 .expect("C tag kind cache poisoned")
6348 .insert(candidate.clone(), kind);
6349 kind
6350 }
6351
6352 fn cached_unique_c_tag_complete_definition(
6353 &self,
6354 analyzer: &CppGraphSource<'_>,
6355 target: &CodeUnit,
6356 target_tag: CppCTagKind,
6357 ) -> Option<CodeUnit> {
6358 if let Some(definition) = self
6359 .c_tag_complete_definition_cache
6360 .lock()
6361 .expect("C tag complete-definition cache poisoned")
6362 .get(target)
6363 {
6364 return definition.clone();
6365 }
6366 let complete_definitions = analyzer
6367 .definitions(&target.fq_name())
6368 .filter(|candidate| {
6369 candidate.is_class()
6370 && !declared_type_alias(analyzer, candidate)
6371 && is_c_source_file(candidate.source())
6372 && analyzer.parent_of(candidate).is_none()
6373 && cpp_class_declaration_strength(analyzer, candidate)
6374 == CppClassDeclarationStrength::Full
6375 && self.cached_c_tag_kind(analyzer, candidate) == Some(target_tag)
6376 })
6377 .collect::<HashSet<_>>();
6378 let definition = (complete_definitions.len() == 1)
6379 .then(|| complete_definitions.into_iter().next())
6380 .flatten()
6381 .filter(|candidate| same_visible_symbol(candidate, target));
6382 self.c_tag_complete_definition_cache
6383 .lock()
6384 .expect("C tag complete-definition cache poisoned")
6385 .insert(target.clone(), definition.clone());
6386 definition
6387 }
6388
6389 pub fn c_tag_declaration_family_matches_target(
6390 &self,
6391 analyzer: &CppGraphSource<'_>,
6392 visible_from: &ProjectFile,
6393 candidates: &[&CodeUnit],
6394 target: &CodeUnit,
6395 ) -> bool {
6396 if std::env::var_os("BIFROST_CPP_VISIBILITY_STATS").is_some() {
6397 let candidate_evidence = candidates
6398 .iter()
6399 .map(|candidate| {
6400 (
6401 candidate.fq_name(),
6402 candidate.source().rel_path().to_path_buf(),
6403 cpp_class_declaration_strength(analyzer, candidate),
6404 indexed_c_tag_kind(analyzer, candidate),
6405 self.is_physically_visible(visible_from, candidate),
6406 )
6407 })
6408 .collect::<Vec<_>>();
6409 eprintln!(
6410 "BIFROST_CPP_C_TAG_FAMILY_STATS visible_from={} target=({}, {}, {:?}, {:?}) candidates={candidate_evidence:?}",
6411 visible_from.rel_path().display(),
6412 target.fq_name(),
6413 target.source().rel_path().display(),
6414 cpp_class_declaration_strength(analyzer, target),
6415 indexed_c_tag_kind(analyzer, target),
6416 );
6417 }
6418 if candidates.is_empty()
6419 || !target.is_class()
6420 || declared_type_alias(analyzer, target)
6421 || !is_c_source_file(target.source())
6422 || analyzer.parent_of(target).is_some()
6423 || cpp_class_declaration_strength(analyzer, target) != CppClassDeclarationStrength::Full
6424 {
6425 return false;
6426 }
6427 let Some(target_tag) = self.cached_c_tag_kind(analyzer, target) else {
6428 return false;
6429 };
6430 if self
6431 .cached_unique_c_tag_complete_definition(analyzer, target, target_tag)
6432 .is_none()
6433 {
6434 return false;
6435 }
6436 let mut saw_visible_forward = false;
6437 for candidate in candidates.iter().copied() {
6438 if candidate == target {
6439 continue;
6440 }
6441 if !candidate.is_class()
6442 || declared_type_alias(analyzer, candidate)
6443 || candidate.fq_name() != target.fq_name()
6444 || analyzer.parent_of(candidate).is_some()
6445 || cpp_class_declaration_strength(analyzer, candidate)
6446 != CppClassDeclarationStrength::Forward
6447 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6448 || !self.is_physically_visible(visible_from, candidate)
6449 {
6450 return false;
6451 }
6452 saw_visible_forward = true;
6453 }
6454 saw_visible_forward
6455 }
6456
6457 fn unique_c_tag_declaration_family(
6462 &self,
6463 analyzer: &CppGraphSource<'_>,
6464 visible_from: &ProjectFile,
6465 candidates: &[&CodeUnit],
6466 ) -> Option<CodeUnit> {
6467 let first = candidates.first()?;
6468 let target_fq_name = first.fq_name();
6469 let target_tag = self.cached_c_tag_kind(analyzer, first)?;
6470 let mut full = None;
6471 let mut saw_forward = false;
6472 for candidate in candidates.iter().copied() {
6473 if !candidate.is_class()
6474 || declared_type_alias(analyzer, candidate)
6475 || candidate.fq_name() != target_fq_name
6476 || analyzer.parent_of(candidate).is_some()
6477 || self.cached_c_tag_kind(analyzer, candidate) != Some(target_tag)
6478 {
6479 return None;
6480 }
6481 match cpp_class_declaration_strength(analyzer, candidate) {
6482 CppClassDeclarationStrength::Full
6483 if is_c_source_file(candidate.source())
6484 && full.replace(candidate.clone()).is_none() => {}
6485 CppClassDeclarationStrength::Forward
6486 if self.is_physically_visible(visible_from, candidate) =>
6487 {
6488 saw_forward = true
6489 }
6490 _ => return None,
6491 }
6492 }
6493 if saw_forward { full } else { None }
6494 }
6495
6496 pub fn target_preserving_reference_namespace(
6497 &self,
6498 analyzer: &CppGraphSource<'_>,
6499 file: &ProjectFile,
6500 identifier: &str,
6501 target: &CodeUnit,
6502 ) -> Option<Vec<String>> {
6503 let mut namespace = None;
6504 for candidate in self.visible_identifier_candidates(file, identifier) {
6505 if candidate.kind() != CodeUnitType::Class && !declared_type_alias(analyzer, candidate)
6506 {
6507 continue;
6508 }
6509 if !(same_visible_symbol(candidate, target)
6510 || self.compatible_primary_template_redeclarations(candidate, target)
6511 || declared_type_alias(analyzer, candidate)
6512 && self.structured_alias_primary_preserves_target(
6513 analyzer, file, candidate, target,
6514 ))
6515 {
6516 continue;
6517 }
6518 if namespace
6519 .as_ref()
6520 .is_some_and(|existing| existing != candidate.package_name())
6521 {
6522 return None;
6523 }
6524 namespace = Some(candidate.package_name().to_string());
6525 }
6526 let namespace = namespace?;
6527 Some(
6528 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
6529 brokk_bifrost_core::analyzer::Language::Cpp,
6530 &namespace,
6531 ),
6532 )
6533 }
6534
6535 pub fn resolve_imported_type_candidate(
6536 &self,
6537 analyzer: &CppGraphSource<'_>,
6538 file: &ProjectFile,
6539 target: &CodeUnit,
6540 target_components: &[String],
6541 direct_target: Option<&CodeUnit>,
6542 preserve_alias: bool,
6543 ) -> LexicalTypeResolution {
6544 let candidates = [target];
6545 let resolution = if preserve_alias {
6546 TypeCandidateResolution::PreserveAlias
6547 } else {
6548 direct_target.map_or(
6549 TypeCandidateResolution::Canonical,
6550 TypeCandidateResolution::PreserveTarget,
6551 )
6552 };
6553 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6557 Ok(unit) => LexicalTypeResolution::Resolved {
6558 unit,
6559 components: target_components.to_vec(),
6560 candidates: vec![target.clone()],
6561 },
6562 Err(failure) => failure.lexical_resolution(),
6563 }
6564 }
6565
6566 fn resolve_type_components_lexically_inner(
6567 &self,
6568 analyzer: &CppGraphSource<'_>,
6569 file: &ProjectFile,
6570 components: &[String],
6571 global: bool,
6572 lexical_scope: &[String],
6573 resolution: TypeCandidateResolution<'_>,
6574 ) -> LexicalTypeResolution {
6575 if components.is_empty() {
6576 return LexicalTypeResolution::Missing;
6577 }
6578 let mut injected = self.resolve_injected_class_name(
6588 analyzer,
6589 file,
6590 components,
6591 global,
6592 lexical_scope,
6593 resolution,
6594 );
6595 for qualified in lexical_component_tiers(components, global, lexical_scope) {
6596 let prefix_len = qualified.len().saturating_sub(components.len());
6597 if injected
6598 .as_ref()
6599 .is_some_and(|(owner_len, _)| prefix_len <= *owner_len)
6600 {
6601 return injected
6602 .take()
6603 .expect("injected class resolution was just present")
6604 .1;
6605 }
6606 let qualified_name = qualified.join("::");
6607 let candidates = self
6608 .type_candidates(file, &qualified_name)
6609 .into_iter()
6610 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6611 .collect::<Vec<_>>();
6612 if candidates.is_empty() {
6613 if !global && components.len() == 1 {
6614 match self.resolve_inherited_type_for_lexical_scope(
6615 analyzer,
6616 file,
6617 &qualified[..prefix_len],
6618 &components[0],
6619 resolution,
6620 ) {
6621 LexicalTypeResolution::Missing => {}
6622 inherited => return inherited,
6623 }
6624 }
6625 continue;
6626 }
6627 let candidates =
6628 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6629 let unit = match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6630 Ok(unit) => unit,
6631 Err(failure) => return failure.lexical_resolution(),
6632 };
6633 return LexicalTypeResolution::Resolved {
6634 unit,
6635 components: qualified,
6636 candidates: candidates.into_iter().cloned().collect(),
6637 };
6638 }
6639 LexicalTypeResolution::Missing
6640 }
6641
6642 fn resolve_injected_class_name(
6643 &self,
6644 analyzer: &CppGraphSource<'_>,
6645 file: &ProjectFile,
6646 components: &[String],
6647 global: bool,
6648 lexical_scope: &[String],
6649 resolution: TypeCandidateResolution<'_>,
6650 ) -> Option<(usize, LexicalTypeResolution)> {
6651 if global
6652 || components.len() != 1
6653 || file.rel_path().extension().is_some_and(|ext| ext == "c")
6654 || matches!(resolution, TypeCandidateResolution::PreserveTarget(target) if !target.is_class())
6655 {
6656 return None;
6657 }
6658 let name = components.first()?;
6659 let mut matches: Vec<&CodeUnit> = Vec::new();
6660 let mut owner_len = 0;
6661 for candidate in self.visible_identifier_candidates(file, name) {
6662 if !candidate.is_class()
6663 || declared_type_alias(analyzer, candidate)
6664 || candidate.identifier() != name
6665 {
6666 continue;
6667 }
6668 let candidate_scope = canonical_cpp_scope_components(candidate);
6669 if candidate_scope.len() > lexical_scope.len()
6670 || !lexical_scope.starts_with(&candidate_scope)
6671 || candidate_scope.last().is_none_or(|last| last != name)
6672 {
6673 continue;
6674 }
6675 if candidate_scope.len() > owner_len {
6676 owner_len = candidate_scope.len();
6677 matches.clear();
6678 }
6679 if candidate_scope.len() == owner_len
6680 && !matches
6681 .iter()
6682 .any(|existing| same_logical_symbol(existing, candidate))
6683 {
6684 matches.push(candidate);
6685 }
6686 }
6687 if matches.is_empty() {
6688 return None;
6689 }
6690 let owner_components = lexical_scope[..owner_len].to_vec();
6697 let matches = self.candidates_for_type_resolution(analyzer, file, &matches, resolution);
6698 let resolution = match self.resolve_type_candidates(analyzer, file, &matches, resolution) {
6699 Ok(unit) => LexicalTypeResolution::Resolved {
6700 unit,
6701 components: owner_components,
6702 candidates: matches.into_iter().cloned().collect(),
6703 },
6704 Err(failure) => failure.lexical_resolution(),
6705 };
6706 Some((owner_len, resolution))
6707 }
6708
6709 fn resolve_inherited_type_for_lexical_scope(
6710 &self,
6711 analyzer: &CppGraphSource<'_>,
6712 file: &ProjectFile,
6713 lexical_scope: &[String],
6714 name: &str,
6715 resolution: TypeCandidateResolution<'_>,
6716 ) -> LexicalTypeResolution {
6717 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
6718 return LexicalTypeResolution::Missing;
6719 };
6720 let lexical_owner_name = lexical_scope.join("::");
6721 if lexical_owner_name.is_empty() {
6722 return LexicalTypeResolution::Missing;
6723 }
6724 let owner_candidates = self
6725 .type_candidates(file, &lexical_owner_name)
6726 .into_iter()
6727 .filter(|candidate| {
6728 canonical_cpp_name_matches(candidate, &lexical_owner_name)
6729 && !declared_type_alias(analyzer, candidate)
6730 })
6731 .collect::<Vec<_>>();
6732 if owner_candidates.is_empty() {
6733 return LexicalTypeResolution::Missing;
6734 }
6735 let physical_owner_candidates = owner_candidates
6740 .iter()
6741 .copied()
6742 .filter(|candidate| candidate.source() == file)
6743 .collect::<Vec<_>>();
6744 let lexical_owner_candidates = if physical_owner_candidates.is_empty() {
6745 owner_candidates
6746 } else {
6747 physical_owner_candidates
6748 };
6749 let Some(lexical_owner) = unique_logical_type_candidate(lexical_owner_candidates) else {
6750 return LexicalTypeResolution::Ambiguous;
6751 };
6752
6753 let mut frontier = hierarchy.get_direct_ancestors(&lexical_owner);
6754 let mut visited_owners = HashSet::default();
6755 while !frontier.is_empty() {
6756 let mut level_matches: Vec<(CodeUnit, Vec<CodeUnit>)> = Vec::new();
6757 let mut next_frontier = Vec::new();
6758 for owner in frontier {
6759 if !visited_owners.insert(owner.fq_name()) {
6760 continue;
6761 }
6762 let qualified_name = format!("{}::{name}", cpp_name_for(&owner));
6763 let candidates = self
6764 .type_candidates(file, &qualified_name)
6765 .into_iter()
6766 .filter(|candidate| canonical_cpp_name_matches(candidate, &qualified_name))
6767 .collect::<Vec<_>>();
6768 if candidates.is_empty() {
6769 for ancestor in hierarchy.get_direct_ancestors(&owner) {
6770 if !next_frontier
6771 .iter()
6772 .any(|existing: &CodeUnit| existing.fq_name() == ancestor.fq_name())
6773 {
6774 next_frontier.push(ancestor);
6775 }
6776 }
6777 continue;
6778 }
6779 let candidates =
6780 self.candidates_for_type_resolution(analyzer, file, &candidates, resolution);
6781 let unit =
6782 match self.resolve_type_candidates(analyzer, file, &candidates, resolution) {
6783 Ok(unit) => unit,
6784 Err(failure) => return failure.lexical_resolution(),
6785 };
6786 level_matches.push((unit, candidates.into_iter().cloned().collect::<Vec<_>>()));
6787 }
6788 if let Some((unit, candidates)) = level_matches.first().cloned() {
6789 let Some(first_declaration) = candidates.first() else {
6790 return LexicalTypeResolution::Ambiguous;
6791 };
6792 if !level_matches.iter().all(|(_, declarations)| {
6793 declarations
6794 .iter()
6795 .all(|declaration| same_logical_symbol(first_declaration, declaration))
6796 }) {
6797 return LexicalTypeResolution::Ambiguous;
6798 }
6799 let mut components = lexical_scope.to_vec();
6800 components.push(name.to_string());
6801 return LexicalTypeResolution::Resolved {
6802 unit,
6803 components,
6804 candidates,
6805 };
6806 }
6807 frontier = next_frontier;
6808 }
6809 LexicalTypeResolution::Missing
6810 }
6811
6812 pub fn inherited_injected_class_owner(
6829 &self,
6830 analyzer: &CppGraphSource<'_>,
6831 file: &ProjectFile,
6832 enclosing_owner: &CodeUnit,
6833 injected_name: &str,
6834 ) -> Option<CodeUnit> {
6835 let hierarchy = analyzer.type_hierarchy_provider()?;
6836 let mut frontier = hierarchy.get_direct_ancestors(enclosing_owner);
6837 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
6838 while !frontier.is_empty() {
6839 let mut level_matches = Vec::new();
6840 let mut next_frontier = Vec::new();
6841 for raw_owner in frontier {
6842 let Some(owner) = self.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
6843 else {
6844 if raw_owner.identifier() == injected_name {
6845 return None;
6846 }
6847 continue;
6848 };
6849 let propagated = propagated_counts.entry(owner.clone()).or_default();
6850 if *propagated == 2 {
6851 continue;
6852 }
6853 *propagated += 1;
6854 if owner.identifier() == injected_name {
6855 level_matches.push(owner.clone());
6856 }
6857 next_frontier.extend(hierarchy.get_direct_ancestors(&owner));
6858 }
6859 match level_matches.as_slice() {
6860 [owner] => return Some(owner.clone()),
6861 [_, ..] => return None,
6862 [] => {}
6863 }
6864 frontier = next_frontier;
6865 }
6866 None
6867 }
6868
6869 fn resolve_type_candidates(
6874 &self,
6875 analyzer: &CppGraphSource<'_>,
6876 file: &ProjectFile,
6877 candidates: &[&CodeUnit],
6878 resolution: TypeCandidateResolution<'_>,
6879 ) -> Result<CodeUnit, TypeCandidateFailure> {
6880 if !matches!(resolution, TypeCandidateResolution::PreserveTarget(_))
6881 && let Some(unit) = self.unique_c_tag_declaration_family(analyzer, file, candidates)
6882 {
6883 return Ok(unit);
6884 }
6885 match resolution {
6886 TypeCandidateResolution::Canonical => {
6887 self.canonical_type_candidate_resolution(analyzer, file, candidates)
6888 }
6889 TypeCandidateResolution::PreserveAlias => {
6890 let same_fqn_alias_family = candidates.len() > 1
6897 && candidates.iter().all(|candidate| {
6898 declared_type_alias(analyzer, candidate)
6899 && same_logical_symbol(candidates[0], candidate)
6900 })
6901 && candidates
6902 .iter()
6903 .any(|candidate| candidate.source() != candidates[0].source());
6904 if same_fqn_alias_family {
6905 let physically_visible = candidates
6906 .iter()
6907 .copied()
6908 .filter(|candidate| self.is_physically_visible(file, candidate))
6909 .collect::<Vec<_>>();
6910 if let Some(alias) = select_alias_spelling_with_agreed_targets(
6920 &physically_visible,
6921 |candidate| self.structured_alias_target(analyzer, candidate),
6922 ) {
6923 return Ok(alias.clone());
6924 }
6925 }
6926 unique_type_candidate_preserving_alias(analyzer, file, candidates)
6927 .ok_or(TypeCandidateFailure::Ambiguous)
6928 }
6929 TypeCandidateResolution::PreserveTarget(target) => self
6930 .unique_type_candidate_preserving_target(analyzer, file, candidates, target)
6931 .ok_or(TypeCandidateFailure::Ambiguous),
6932 }
6933 }
6934
6935 fn candidates_for_type_resolution<'b>(
6936 &self,
6937 analyzer: &CppGraphSource<'_>,
6938 file: &ProjectFile,
6939 candidates: &[&'b CodeUnit],
6940 resolution: TypeCandidateResolution<'_>,
6941 ) -> Vec<&'b CodeUnit> {
6942 if matches!(resolution, TypeCandidateResolution::PreserveAlias) && candidates.len() > 1 {
6943 let compile_proven = self.compile_proven_type_candidates(analyzer, file, candidates);
6944 if compile_proven.len() == 1 {
6945 return compile_proven;
6946 }
6947 }
6948 candidates.to_vec()
6949 }
6950
6951 fn compile_proven_type_candidates<'b>(
6958 &self,
6959 analyzer: &CppGraphSource<'_>,
6960 file: &ProjectFile,
6961 candidates: &[&'b CodeUnit],
6962 ) -> Vec<&'b CodeUnit> {
6963 let proven = self.compile_proven_guards(file);
6964 if proven.is_empty() {
6965 return Vec::new();
6966 }
6967 let Some(prepared) = self.cpp.prepared_syntax(self.token, file) else {
6968 return Vec::new();
6969 };
6970 candidates
6971 .iter()
6972 .copied()
6973 .filter(|candidate| {
6974 let declaration_guards =
6975 declaration_guard_requirements(analyzer, self.cpp, candidate);
6976 if declaration_guards.is_empty() {
6977 return false;
6978 }
6979 if candidate.source() == file {
6980 return declaration_guards.iter().any(|(_, required)| {
6981 guard_requirements_hold_at_reference(required, Some(proven.as_ref()))
6982 });
6983 }
6984 declaration_guards.iter().any(|(_, required)| {
6985 self.foreign_declaration_reachable_from_compile_proven_guards(
6986 file,
6987 prepared.as_ref(),
6988 candidate.source(),
6989 required,
6990 usize::MAX,
6991 )
6992 })
6993 })
6994 .collect()
6995 }
6996
6997 pub fn resolve_callable_value_components_lexically(
6998 &self,
6999 analyzer: &CppGraphSource<'_>,
7000 file: &ProjectFile,
7001 owner_components: &[String],
7002 member_name: &str,
7003 global: bool,
7004 lexical_scope: &[String],
7005 ) -> LexicalCallableValueResolution {
7006 if owner_components.is_empty() || member_name.is_empty() {
7007 return LexicalCallableValueResolution::Missing;
7008 }
7009 for qualified_owner in lexical_component_tiers(owner_components, global, lexical_scope) {
7010 let owner_name = qualified_owner.join("::");
7011 let type_candidates = self
7012 .type_candidates(file, &owner_name)
7013 .into_iter()
7014 .filter(|candidate| canonical_cpp_name_matches(candidate, &owner_name))
7015 .collect::<Vec<_>>();
7016 let resolved_type = if type_candidates.is_empty() {
7017 None
7018 } else {
7019 let Some(unit) =
7020 self.unique_canonical_type_candidate(analyzer, file, &type_candidates)
7021 else {
7022 return LexicalCallableValueResolution::Ambiguous;
7023 };
7024 Some(unit)
7025 };
7026
7027 let mut qualified_callable = qualified_owner;
7028 qualified_callable.push(member_name.to_string());
7029 let callable_name = qualified_callable.join("::");
7030 let free_function = self
7031 .named_candidates_for_normalized(file, &callable_name, TargetKind::FreeFunction)
7032 .into_iter()
7033 .find(|candidate| {
7034 canonical_cpp_name_matches(candidate, &callable_name)
7035 && type_owner_of(analyzer, candidate).is_none()
7036 })
7037 .cloned();
7038
7039 match (resolved_type, free_function) {
7040 (Some(_), Some(_)) => return LexicalCallableValueResolution::Ambiguous,
7041 (Some(owner), None) => return LexicalCallableValueResolution::Type(owner),
7042 (None, Some(function)) => {
7043 return LexicalCallableValueResolution::FreeFunction(function);
7044 }
7045 (None, None) => {}
7046 }
7047 }
7048 LexicalCallableValueResolution::Missing
7049 }
7050
7051 fn resolve_type_for_declaration(
7052 &self,
7053 visible_from: &ProjectFile,
7054 declaration: &CodeUnit,
7055 raw_name: &str,
7056 ) -> Option<CodeUnit> {
7057 let normalized = normalize_reference_name(raw_name)?;
7058 if !normalized.contains("::")
7059 && let Some(namespace) = cpp_namespace_for(declaration)
7060 {
7061 for prefix in namespace_prefixes(&namespace) {
7062 let qualified = format!("{prefix}::{normalized}");
7063 if let Some(unit) = self
7064 .type_candidates(visible_from, &qualified)
7065 .into_iter()
7066 .next()
7067 {
7068 return Some(unit.clone());
7069 }
7070 }
7071 }
7072 self.resolve_type(visible_from, raw_name)
7073 }
7074
7075 fn resolve_unique_canonical_type_for_declaration(
7076 &self,
7077 analyzer: &CppGraphSource<'_>,
7078 visible_from: &ProjectFile,
7079 declaration: &CodeUnit,
7080 raw_name: &str,
7081 ) -> Option<CodeUnit> {
7082 let current = self.resolve_defining_type_for_declaration(
7083 analyzer,
7084 visible_from,
7085 declaration,
7086 raw_name,
7087 )?;
7088 self.canonical_type_resolution(analyzer, visible_from, ¤t)
7089 .ok()
7090 }
7091
7092 pub fn canonical_type_unit(
7093 &self,
7094 analyzer: &CppGraphSource<'_>,
7095 visible_from: &ProjectFile,
7096 unit: &CodeUnit,
7097 ) -> Option<CodeUnit> {
7098 self.canonical_type_resolution(analyzer, visible_from, unit)
7099 .ok()
7100 }
7101
7102 pub fn canonical_type_unit_in_context(
7110 &self,
7111 analyzer: &CppGraphSource<'_>,
7112 visible_from: &ProjectFile,
7113 reference: Node<'_>,
7114 unit: &CodeUnit,
7115 ) -> Option<CodeUnit> {
7116 if !self.external_type_candidate_visible_in_context(analyzer, visible_from, unit, reference)
7117 {
7118 return None;
7119 }
7120 self.canonical_type_resolution(analyzer, visible_from, unit)
7121 .ok()
7122 }
7123
7124 fn canonical_type_resolution(
7132 &self,
7133 analyzer: &CppGraphSource<'_>,
7134 visible_from: &ProjectFile,
7135 unit: &CodeUnit,
7136 ) -> Result<CodeUnit, TypeCandidateFailure> {
7137 let mut current = unit.clone();
7138 let mut seen_aliases = HashSet::default();
7139 loop {
7140 let Some(target) = self.structured_alias_target(analyzer, ¤t) else {
7141 return current
7142 .is_class()
7143 .then_some(current)
7144 .ok_or(TypeCandidateFailure::Unresolvable);
7145 };
7146 if matches!(target, StructuredAliasTarget::Builtin) {
7147 return current
7148 .is_class()
7149 .then_some(current)
7150 .ok_or(TypeCandidateFailure::Unresolvable);
7151 }
7152 if !seen_aliases.insert(current.clone()) {
7153 return Err(TypeCandidateFailure::Unresolvable);
7154 }
7155 current = self.structured_alias_target_resolution(visible_from, ¤t, &target)?;
7156 }
7157 }
7158
7159 pub fn canonical_visible_full_type_unit(
7160 &self,
7161 analyzer: &CppGraphSource<'_>,
7162 visible_from: &ProjectFile,
7163 unit: &CodeUnit,
7164 ) -> Option<CodeUnit> {
7165 let canonical = self.canonical_type_unit(analyzer, visible_from, unit)?;
7166 if cpp_class_declaration_strength(analyzer, &canonical)
7167 != CppClassDeclarationStrength::Forward
7168 {
7169 return Some(canonical);
7170 }
7171 let mut full = Vec::new();
7172 for candidate in self
7173 .visible_identifier_candidates(visible_from, canonical.identifier())
7174 .filter(|candidate| {
7175 candidate.is_class()
7176 && candidate.fq_name() == canonical.fq_name()
7177 && cpp_class_declaration_strength(analyzer, candidate)
7178 == CppClassDeclarationStrength::Full
7179 })
7180 {
7181 if !full.iter().any(|existing| same_symbol(existing, candidate)) {
7182 full.push(candidate.clone());
7183 }
7184 }
7185 match full.len() {
7186 0 => Some(canonical),
7187 1 => full.pop(),
7188 _ => None,
7189 }
7190 }
7191
7192 fn resolve_structured_alias_target(
7193 &self,
7194 visible_from: &ProjectFile,
7195 declaration: &CodeUnit,
7196 target: &StructuredAliasTarget,
7197 ) -> Option<CodeUnit> {
7198 self.structured_alias_target_resolution(visible_from, declaration, target)
7199 .ok()
7200 }
7201
7202 fn structured_alias_target_resolution(
7203 &self,
7204 visible_from: &ProjectFile,
7205 declaration: &CodeUnit,
7206 target: &StructuredAliasTarget,
7207 ) -> Result<CodeUnit, TypeCandidateFailure> {
7208 let primary =
7209 self.structured_alias_primary_resolution(visible_from, declaration, target)?;
7210 let StructuredAliasTarget::Named { arguments, .. } = target else {
7211 return Err(TypeCandidateFailure::Unresolvable);
7212 };
7213 match arguments {
7214 Some(arguments) => self
7215 .resolve_template_arguments(visible_from, primary, arguments)
7216 .map_err(|error| match error {
7217 CppTemplateResolutionError::AmbiguousSpecialization { .. } => {
7218 TypeCandidateFailure::Ambiguous
7219 }
7220 _ => TypeCandidateFailure::Unresolvable,
7221 }),
7222 None => Ok(primary),
7223 }
7224 }
7225
7226 fn resolve_structured_alias_primary(
7227 &self,
7228 visible_from: &ProjectFile,
7229 declaration: &CodeUnit,
7230 target: &StructuredAliasTarget,
7231 ) -> Option<CodeUnit> {
7232 self.structured_alias_primary_resolution(visible_from, declaration, target)
7233 .ok()
7234 }
7235
7236 fn structured_alias_primary_resolution(
7237 &self,
7238 visible_from: &ProjectFile,
7239 declaration: &CodeUnit,
7240 target: &StructuredAliasTarget,
7241 ) -> Result<CodeUnit, TypeCandidateFailure> {
7242 let StructuredAliasTarget::Named {
7243 components, global, ..
7244 } = target
7245 else {
7246 return Err(TypeCandidateFailure::Unresolvable);
7247 };
7248 let qualified = components.join("::");
7249 let candidates = if *global {
7250 let mut candidates = self.type_candidates(visible_from, &qualified);
7257 candidates.retain(|candidate| canonical_cpp_scope_components(candidate) == *components);
7258 candidates
7259 } else {
7260 self.type_candidates_for_declaration(visible_from, declaration, &qualified)
7261 };
7262 logical_type_candidate(candidates)
7263 }
7264
7265 pub fn structured_alias_primary_preserves_target(
7266 &self,
7267 analyzer: &CppGraphSource<'_>,
7268 visible_from: &ProjectFile,
7269 candidate: &CodeUnit,
7270 target: &CodeUnit,
7271 ) -> bool {
7272 let mut current = candidate.clone();
7273 let mut seen = HashSet::default();
7274 let mut matched_target = false;
7275 loop {
7276 if same_visible_symbol(¤t, target)
7277 || self.compatible_primary_template_redeclarations(¤t, target)
7278 {
7279 matched_target = true;
7280 }
7281 if !seen.insert(current.clone()) {
7282 return false;
7283 }
7284 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7285 return matched_target;
7286 };
7287 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7288 return matched_target;
7289 };
7290 let Some(primary) =
7291 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7292 else {
7293 return matched_target;
7299 };
7300 current = primary;
7301 }
7302 }
7303
7304 fn lexical_type_candidates_match_target(
7305 &self,
7306 analyzer: &CppGraphSource<'_>,
7307 unit: &CodeUnit,
7308 candidates: &[CodeUnit],
7309 target: &CodeUnit,
7310 ) -> bool {
7311 same_visible_symbol(unit, target)
7312 || self.same_template_member_identity(analyzer, unit, target)
7313 || candidates.iter().any(|candidate| {
7314 same_visible_symbol(candidate, target)
7315 || self.same_template_member_identity(analyzer, candidate, target)
7316 })
7317 }
7318
7319 pub fn structured_class_alias_resolves_to_target(
7320 &self,
7321 analyzer: &CppGraphSource<'_>,
7322 visible_from: &ProjectFile,
7323 alias: &CodeUnit,
7324 target: &CodeUnit,
7325 ) -> bool {
7326 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7327 return false;
7328 };
7329 let Some(alias_target) = self.structured_alias_target(analyzer, alias) else {
7330 return false;
7331 };
7332 let StructuredAliasTarget::Named {
7333 components, global, ..
7334 } = &alias_target
7335 else {
7336 return false;
7337 };
7338 let lexical_scope = canonical_cpp_scope_components(&owner);
7339 match self.resolve_type_components_lexically_for_target(
7340 analyzer,
7341 visible_from,
7342 components,
7343 *global,
7344 &lexical_scope,
7345 target,
7346 ) {
7347 LexicalTypeResolution::Resolved {
7348 unit, candidates, ..
7349 } => self.lexical_type_candidates_match_target(analyzer, &unit, &candidates, target),
7350 LexicalTypeResolution::Ambiguous | LexicalTypeResolution::Missing => {
7351 self.structured_alias_primary_preserves_target(
7352 analyzer,
7353 visible_from,
7354 alias,
7355 target,
7356 ) || self.flattened_macro_namespace_alias_target_matches(
7357 analyzer,
7358 visible_from,
7359 alias,
7360 &alias_target,
7361 target,
7362 )
7363 }
7364 }
7365 }
7366
7367 pub fn structured_class_alias_path_preserves_target(
7375 &self,
7376 analyzer: &CppGraphSource<'_>,
7377 visible_from: &ProjectFile,
7378 alias: &CodeUnit,
7379 target: &CodeUnit,
7380 ) -> bool {
7381 let Some(owner) = type_owner_of(analyzer, alias).filter(CodeUnit::is_class) else {
7382 return false;
7383 };
7384 let Some(StructuredAliasTarget::Named {
7385 components, global, ..
7386 }) = self.structured_alias_target(analyzer, alias)
7387 else {
7388 return false;
7389 };
7390 let lexical_scope = canonical_cpp_scope_components(&owner);
7391 (1..components.len()).rev().any(|component_count| {
7392 matches!(
7393 self.resolve_type_components_lexically_for_target(
7394 analyzer,
7395 visible_from,
7396 &components[..component_count],
7397 global,
7398 &lexical_scope,
7399 target,
7400 ),
7401 LexicalTypeResolution::Resolved {
7402 ref unit,
7403 ref candidates,
7404 ..
7405 } if self.lexical_type_candidates_match_target(
7406 analyzer,
7407 unit,
7408 candidates,
7409 target,
7410 )
7411 )
7412 })
7413 }
7414
7415 fn flattened_macro_namespace_alias_target_matches(
7416 &self,
7417 analyzer: &CppGraphSource<'_>,
7418 visible_from: &ProjectFile,
7419 alias: &CodeUnit,
7420 alias_target: &StructuredAliasTarget,
7421 target: &CodeUnit,
7422 ) -> bool {
7423 let StructuredAliasTarget::Named {
7424 components,
7425 global: false,
7426 arguments: None,
7427 } = alias_target
7428 else {
7429 return false;
7430 };
7431 let Some((target_name, namespace_components)) = components.split_last() else {
7432 return false;
7433 };
7434 if namespace_components.is_empty()
7435 || target_name != target.identifier()
7436 || alias.source() != target.source()
7437 || alias.source() != visible_from
7438 || !target.is_class()
7439 || declared_type_alias(analyzer, target)
7440 {
7441 return false;
7442 }
7443 if self
7444 .resolve_structured_alias_target(visible_from, alias, alias_target)
7445 .is_some()
7446 {
7447 return false;
7448 }
7449
7450 let Some(alias_start) = analyzer
7451 .declaration_source_occurrences(alias)
7452 .into_iter()
7453 .flatten()
7454 .map(|occurrence| occurrence.range.start_byte)
7455 .min()
7456 else {
7457 return false;
7458 };
7459 let has_matching_declaration = analyzer
7460 .declaration_source_occurrences(target)
7461 .into_iter()
7462 .flatten()
7463 .any(|occurrence| {
7464 occurrence.range.end_byte <= alias_start
7465 && occurrence.fact.flattened_macro_namespace.as_deref()
7466 == Some(namespace_components)
7467 });
7468 if !has_matching_declaration {
7469 return false;
7470 }
7471
7472 let alias_guards = declaration_guard_requirements(analyzer, self.cpp, alias);
7473 let target_guards = declaration_guard_requirements(analyzer, self.cpp, target);
7474 guard_requirement_sets_match(&alias_guards, &target_guards)
7475 }
7476
7477 pub fn template_alias_arguments_preserve_target(
7478 &self,
7479 analyzer: &CppGraphSource<'_>,
7480 visible_from: &ProjectFile,
7481 alias: &CodeUnit,
7482 arguments: &[CppTemplateExpression],
7483 target: &CodeUnit,
7484 ) -> bool {
7485 let Some(metadata) = self.cpp_template_metadata.get(alias) else {
7486 return false;
7487 };
7488 if metadata.alias_target.is_none()
7489 || cpp_bind_template_arguments(&metadata.parameters, arguments).is_none()
7490 {
7491 return false;
7492 }
7493 self.structured_alias_primary_preserves_target(analyzer, visible_from, alias, target)
7494 }
7495
7496 pub fn is_primary_template(&self, unit: &CodeUnit) -> bool {
7497 self.cpp_template_metadata
7498 .get(unit)
7499 .is_some_and(CppTemplateMetadata::is_primary)
7500 }
7501
7502 pub fn is_template_specialization(&self, unit: &CodeUnit) -> bool {
7503 self.cpp_template_metadata
7504 .get(unit)
7505 .is_some_and(CppTemplateMetadata::is_specialization)
7506 }
7507
7508 pub fn same_template_owner_identity(&self, left: &CodeUnit, right: &CodeUnit) -> bool {
7509 same_visible_symbol(left, right)
7510 || self.compatible_primary_template_redeclarations(left, right)
7511 }
7512
7513 pub fn same_template_member_identity(
7514 &self,
7515 analyzer: &CppGraphSource<'_>,
7516 left: &CodeUnit,
7517 right: &CodeUnit,
7518 ) -> bool {
7519 if same_visible_symbol(left, right) {
7520 return true;
7521 }
7522 if left.kind() != right.kind()
7523 || left.identifier() != right.identifier()
7524 || left.signature() != right.signature()
7525 {
7526 return false;
7527 }
7528 let (Some(left_owner), Some(right_owner)) =
7529 (analyzer.parent_of(left), analyzer.parent_of(right))
7530 else {
7531 return false;
7532 };
7533 left_owner.is_class()
7534 && right_owner.is_class()
7535 && self.same_template_owner_identity(&left_owner, &right_owner)
7536 }
7537
7538 fn unique_canonical_type_candidate(
7539 &self,
7540 analyzer: &CppGraphSource<'_>,
7541 visible_from: &ProjectFile,
7542 candidates: &[&CodeUnit],
7543 ) -> Option<CodeUnit> {
7544 self.canonical_type_candidate_resolution(analyzer, visible_from, candidates)
7545 .ok()
7546 }
7547
7548 fn canonical_type_candidate_resolution(
7549 &self,
7550 analyzer: &CppGraphSource<'_>,
7551 visible_from: &ProjectFile,
7552 candidates: &[&CodeUnit],
7553 ) -> Result<CodeUnit, TypeCandidateFailure> {
7554 let mut canonical = Vec::new();
7555 for candidate in candidates {
7556 let resolved = self.canonical_type_resolution(analyzer, visible_from, candidate)?;
7557 if canonical
7558 .iter()
7559 .any(|existing| same_visible_symbol(existing, &resolved))
7560 {
7561 continue;
7562 }
7563 if let Some(existing) = canonical.iter_mut().find(|existing| {
7564 self.compatible_primary_template_redeclarations(existing, &resolved)
7565 }) {
7566 if matches!(
7575 (
7576 cpp_class_declaration_strength(analyzer, existing),
7577 cpp_class_declaration_strength(analyzer, &resolved),
7578 ),
7579 (
7580 CppClassDeclarationStrength::Forward | CppClassDeclarationStrength::Unknown,
7581 CppClassDeclarationStrength::Full,
7582 ) | (
7583 CppClassDeclarationStrength::Unknown,
7584 CppClassDeclarationStrength::Forward,
7585 )
7586 ) {
7587 *existing = resolved;
7588 }
7589 continue;
7590 }
7591 canonical.push(resolved);
7592 if canonical.len() > 1 {
7593 return Err(TypeCandidateFailure::Ambiguous);
7594 }
7595 }
7596 canonical.pop().ok_or(TypeCandidateFailure::Unresolvable)
7597 }
7598
7599 pub fn unique_type_candidate_preserving_target(
7600 &self,
7601 analyzer: &CppGraphSource<'_>,
7602 visible_from: &ProjectFile,
7603 candidates: &[&CodeUnit],
7604 target: &CodeUnit,
7605 ) -> Option<CodeUnit> {
7606 if self.c_tag_declaration_family_matches_target(analyzer, visible_from, candidates, target)
7617 || self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7618 {
7619 return Some(target.clone());
7620 }
7621 let mut resolved_candidates = Vec::new();
7622 for candidate in candidates {
7623 let Some(resolved) =
7629 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7630 else {
7631 continue;
7632 };
7633 if resolved_candidates
7634 .iter()
7635 .any(|existing| same_visible_symbol(existing, &resolved))
7636 {
7637 continue;
7638 }
7639 resolved_candidates.push(resolved);
7640 }
7641 match resolved_candidates.as_slice() {
7642 [] => None,
7643 [single] => Some(single.clone()),
7644 _ => self
7649 .same_fqn_type_spelling_for_target(analyzer, visible_from, candidates, target)
7650 .map(|_| target.clone()),
7651 }
7652 }
7653
7654 pub fn same_fqn_type_spelling_for_target<'b>(
7671 &self,
7672 analyzer: &CppGraphSource<'_>,
7673 visible_from: &ProjectFile,
7674 candidates: &[&'b CodeUnit],
7675 target: &CodeUnit,
7676 ) -> Option<&'b CodeUnit> {
7677 let [first, rest @ ..] = candidates else {
7678 return None;
7679 };
7680 if rest.is_empty()
7681 || !rest.iter().all(|candidate| {
7682 candidate.kind() == first.kind()
7683 && candidate.fq_name() == first.fq_name()
7684 && candidate.source() == first.source()
7685 })
7686 {
7687 return None;
7688 }
7689 candidates
7690 .iter()
7691 .copied()
7692 .find(|candidate| same_symbol(candidate, target))
7693 .or_else(|| {
7694 candidates.iter().copied().find(|candidate| {
7695 self.type_candidate_preserving_target(analyzer, visible_from, candidate, target)
7696 .is_some_and(|resolved| same_visible_symbol(&resolved, target))
7697 })
7698 })
7699 }
7700
7701 pub fn alternate_same_fqn_type_declarations(
7702 &self,
7703 analyzer: &CppGraphSource<'_>,
7704 candidates: &[&CodeUnit],
7705 target: &CodeUnit,
7706 ) -> bool {
7707 let Some(first) = candidates.first() else {
7708 return false;
7709 };
7710 let same_api = first.kind() == target.kind()
7711 && first.fq_name() == target.fq_name()
7712 && first.source() == target.source()
7713 && candidates.iter().all(|candidate| {
7714 candidate.kind() == target.kind()
7715 && candidate.fq_name() == target.fq_name()
7716 && candidate.source() == target.source()
7717 })
7718 && candidates
7719 .iter()
7720 .any(|candidate| same_symbol(candidate, target))
7721 && candidates
7722 .iter()
7723 .any(|candidate| !same_logical_symbol(candidate, target));
7724 if !same_api {
7725 return false;
7726 }
7727
7728 let requirements = candidates
7729 .iter()
7730 .map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7731 .collect::<Vec<_>>();
7732 requirements.len() > 1
7733 && requirements
7734 .iter()
7735 .all(|requirement| !requirement.is_empty())
7736 && requirements.iter().enumerate().all(|(index, left)| {
7737 requirements[index + 1..].iter().all(|right| {
7738 left.iter().all(|(_, left_guards)| {
7739 right.iter().all(|(_, right_guards)| {
7740 merge_preprocessor_guards(left_guards, right_guards).is_none()
7741 })
7742 })
7743 })
7744 })
7745 }
7746
7747 fn preprocessor_guard_terms_cover_all_paths(terms: &[HashSet<PreprocessorGuard>]) -> bool {
7748 let mut pending = vec![terms.to_vec()];
7749 while let Some(branch_terms) = pending.pop() {
7750 let mut normalized = Vec::new();
7751 let mut covers_branch = false;
7752 for term in branch_terms {
7753 if term.iter().any(|guard| term.contains(&guard.negated())) {
7754 continue;
7755 }
7756 if term.is_empty() {
7757 covers_branch = true;
7758 break;
7759 }
7760 if !normalized.iter().any(|existing| existing == &term) {
7761 normalized.push(term);
7762 }
7763 }
7764 if covers_branch {
7765 continue;
7766 }
7767 let Some(split_guard) = normalized
7768 .iter()
7769 .flat_map(|term| term.iter())
7770 .next()
7771 .cloned()
7772 else {
7773 return false;
7774 };
7775 let negated_guard = split_guard.negated();
7776 let mut when_defined = Vec::new();
7777 let mut when_undefined = Vec::new();
7778 for term in normalized {
7779 if term.contains(&negated_guard) {
7780 } else if term.contains(&split_guard) {
7782 let mut reduced = term.clone();
7783 reduced.remove(&split_guard);
7784 when_defined.push(reduced);
7785 } else {
7786 when_defined.push(term.clone());
7787 }
7788 if term.contains(&split_guard) {
7789 } else if term.contains(&negated_guard) {
7791 let mut reduced = term;
7792 reduced.remove(&negated_guard);
7793 when_undefined.push(reduced);
7794 } else {
7795 when_undefined.push(term);
7796 }
7797 }
7798 pending.push(when_defined);
7799 pending.push(when_undefined);
7800 }
7801 true
7802 }
7803
7804 fn declarations_share_exhaustive_conditional_family(
7813 &self,
7814 analyzer: &CppGraphSource<'_>,
7815 candidates: &[&CodeUnit],
7816 ) -> Option<(usize, usize)> {
7817 let mut family_range = None;
7818 for candidate in candidates {
7819 let mut candidate_family = None;
7820 for occurrence in analyzer.declaration_source_occurrences(candidate)? {
7821 let key = occurrence.exhaustive_family?;
7822 if candidate_family.is_some_and(|existing| existing != key) {
7823 return None;
7824 }
7825 candidate_family = Some(key);
7826 }
7827 let candidate_family = candidate_family?;
7828 if family_range.is_some_and(|existing| existing != candidate_family) {
7829 return None;
7830 }
7831 family_range = Some(candidate_family);
7832 }
7833 family_range
7834 }
7835
7836 pub fn complementary_same_fqn_type_declarations(
7837 &self,
7838 analyzer: &CppGraphSource<'_>,
7839 candidates: &[&CodeUnit],
7840 target: &CodeUnit,
7841 ) -> bool {
7842 if candidates.len() < 2
7843 || !self.alternate_same_fqn_type_declarations(analyzer, candidates, target)
7844 || self
7845 .declarations_share_exhaustive_conditional_family(analyzer, candidates)
7846 .is_none()
7847 {
7848 return false;
7849 }
7850 Self::preprocessor_guard_terms_cover_all_paths(
7851 &self.declaration_family_guard_terms(analyzer, candidates),
7852 )
7853 }
7854
7855 fn declaration_family_guard_terms(
7856 &self,
7857 analyzer: &CppGraphSource<'_>,
7858 candidates: &[&CodeUnit],
7859 ) -> Vec<HashSet<PreprocessorGuard>> {
7860 candidates
7861 .iter()
7862 .flat_map(|candidate| declaration_guard_requirements(analyzer, self.cpp, candidate))
7863 .map(|(_, guards)| guards)
7864 .collect()
7865 }
7866
7867 fn exhaustive_guard_family_activation(
7883 &self,
7884 analyzer: &CppGraphSource<'_>,
7885 candidate: &CodeUnit,
7886 reference: &CallableReferenceContext<'_>,
7887 ) -> Option<usize> {
7888 if !analyzer
7891 .declaration_source_occurrences(candidate)?
7892 .iter()
7893 .any(|occurrence| occurrence.fact.callable_activation.is_some())
7894 {
7895 return None;
7896 }
7897 let family = self
7898 .visible_identifier_candidates(candidate.source(), candidate.identifier())
7899 .filter(|peer| {
7900 peer.kind() == candidate.kind()
7901 && peer.fq_name() == candidate.fq_name()
7902 && peer.source() == candidate.source()
7903 })
7904 .collect::<Vec<_>>();
7905 let (_, family_end) =
7906 self.declarations_share_exhaustive_conditional_family(analyzer, &family)?;
7907 if !Self::preprocessor_guard_terms_cover_all_paths(
7908 &self.declaration_family_guard_terms(analyzer, &family),
7909 ) {
7910 return None;
7911 }
7912 if !declaration_guard_requirements(analyzer, self.cpp, candidate)
7916 .iter()
7917 .any(|(_, guards)| guards_compatible_at_reference(guards, reference.guards()))
7918 {
7919 return None;
7920 }
7921 (first_declaration_byte(analyzer, candidate)?
7922 == family
7923 .iter()
7924 .filter_map(|peer| first_declaration_byte(analyzer, peer))
7925 .min()?)
7926 .then_some(family_end)
7927 }
7928
7929 fn type_candidate_preserving_target(
7930 &self,
7931 analyzer: &CppGraphSource<'_>,
7932 visible_from: &ProjectFile,
7933 candidate: &CodeUnit,
7934 target: &CodeUnit,
7935 ) -> Option<CodeUnit> {
7936 let mut current = candidate.clone();
7937 let mut matched_target = same_visible_symbol(¤t, target)
7938 || self.compatible_primary_template_redeclarations(¤t, target);
7939 let mut seen = HashSet::default();
7940 loop {
7941 if !seen.insert(current.clone()) {
7942 return None;
7943 }
7944 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
7945 return matched_target
7946 .then(|| target.clone())
7947 .or_else(|| current.is_class().then_some(current));
7948 };
7949 if self.flattened_macro_namespace_alias_target_matches(
7950 analyzer,
7951 visible_from,
7952 ¤t,
7953 &alias_target,
7954 target,
7955 ) {
7956 return Some(target.clone());
7957 }
7958 if matches!(alias_target, StructuredAliasTarget::Builtin) {
7959 return matched_target
7960 .then(|| target.clone())
7961 .or_else(|| current.is_class().then_some(current));
7962 }
7963 if !self.cpp_template_metadata.contains_key(¤t)
7971 && let Some(primary) =
7972 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
7973 && (same_visible_symbol(&primary, target)
7974 || self.compatible_primary_template_redeclarations(&primary, target))
7975 {
7976 return Some(target.clone());
7977 }
7978 if same_visible_symbol(¤t, target) {
7979 return Some(target.clone());
7980 }
7981 if self.cpp_template_metadata.contains_key(¤t) {
7982 return None;
7983 }
7984 let Some(next) =
7985 self.resolve_structured_alias_target(visible_from, ¤t, &alias_target)
7986 else {
7987 return matched_target.then(|| target.clone());
7988 };
7989 current = next;
7990 matched_target |= same_visible_symbol(¤t, target)
7991 || self.compatible_primary_template_redeclarations(¤t, target);
7992 }
7993 }
7994
7995 fn compatible_primary_template_redeclarations(
7996 &self,
7997 left: &CodeUnit,
7998 right: &CodeUnit,
7999 ) -> bool {
8000 let (Some(left_metadata), Some(right_metadata)) = (
8001 self.cpp_template_metadata.get(left),
8002 self.cpp_template_metadata.get(right),
8003 ) else {
8004 return false;
8005 };
8006 left_metadata.primary_fq_name == right_metadata.primary_fq_name
8007 && left_metadata.is_primary()
8008 && right_metadata.is_primary()
8009 && cpp_reconcile_primary_template_parameters(
8010 &[(left, left_metadata), (right, right_metadata)],
8011 right,
8012 )
8013 .is_some()
8014 }
8015
8016 fn alias_candidate_may_preserve_target(
8017 &self,
8018 analyzer: &CppGraphSource<'_>,
8019 visible_from: &ProjectFile,
8020 candidate: &CodeUnit,
8021 target: &CodeUnit,
8022 ) -> bool {
8023 let mut current = candidate.clone();
8024 let mut seen = HashSet::default();
8025 loop {
8026 if same_visible_symbol(¤t, target)
8027 || self.compatible_primary_template_redeclarations(¤t, target)
8028 {
8029 return true;
8030 }
8031 if self.cpp_template_metadata.contains_key(¤t) {
8032 return true;
8033 }
8034 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8035 return false;
8036 };
8037 let StructuredAliasTarget::Named {
8038 components,
8039 global,
8040 arguments,
8041 } = alias_target
8042 else {
8043 return false;
8044 };
8045 if arguments.is_some() || !seen.insert(current.clone()) {
8046 return true;
8047 }
8048 let qualified = components.join("::");
8049 let next = if global {
8050 unique_logical_type_candidate(self.type_candidates(visible_from, &qualified))
8051 } else {
8052 self.resolve_unique_type_for_declaration(visible_from, ¤t, &qualified)
8053 };
8054 let Some(next) = next else {
8055 return true;
8056 };
8057 current = next;
8058 }
8059 }
8060
8061 fn alias_candidate_structurally_reaches_target(
8062 &self,
8063 analyzer: &CppGraphSource<'_>,
8064 visible_from: &ProjectFile,
8065 candidate: &CodeUnit,
8066 target: &CodeUnit,
8067 ) -> bool {
8068 let mut current = candidate.clone();
8069 let mut seen = HashSet::default();
8070 loop {
8071 if same_visible_symbol(¤t, target)
8072 || self.compatible_primary_template_redeclarations(¤t, target)
8073 || self
8074 .cpp_template_metadata
8075 .get(¤t)
8076 .zip(self.cpp_template_metadata.get(target))
8077 .is_some_and(|(current, target)| {
8078 current.primary_fq_name == target.primary_fq_name
8079 })
8080 {
8081 return true;
8082 }
8083 if !seen.insert(current.clone()) {
8084 return false;
8085 }
8086 let Some(alias_target) = self.structured_alias_target(analyzer, ¤t) else {
8087 return false;
8088 };
8089 if let StructuredAliasTarget::Named { components, .. } = &alias_target
8090 && components[..components.len().saturating_sub(1)]
8091 .iter()
8092 .any(|component| {
8093 self.type_reference_component_directly_names_target(component, target)
8094 })
8095 && self.structured_class_alias_path_preserves_target(
8096 analyzer,
8097 visible_from,
8098 ¤t,
8099 target,
8100 )
8101 {
8102 return true;
8103 }
8104 if matches!(alias_target, StructuredAliasTarget::Builtin) {
8105 return false;
8106 }
8107 let Some(primary) =
8108 self.resolve_structured_alias_primary(visible_from, ¤t, &alias_target)
8109 else {
8110 return false;
8111 };
8112 current = primary;
8113 }
8114 }
8115
8116 pub fn qualified_alias_reference_may_reach_target(
8128 &self,
8129 analyzer: &CppGraphSource<'_>,
8130 file: &ProjectFile,
8131 components: &[String],
8132 global: bool,
8133 target: &CodeUnit,
8134 ) -> bool {
8135 for end in 0..components.len() {
8136 let spelled = &components[..=end];
8137 for candidate in self
8138 .visible_identifier_candidates(file, &components[end])
8139 .filter(|candidate| declared_type_alias(analyzer, candidate))
8140 {
8141 let candidate_components = canonical_cpp_scope_components(candidate);
8142 let shape_matches = if global {
8143 candidate_components == spelled
8144 } else {
8145 candidate_components.ends_with(spelled)
8146 };
8147 if shape_matches
8148 && self.alias_candidate_structurally_reaches_target(
8149 analyzer, file, candidate, target,
8150 )
8151 {
8152 return true;
8153 }
8154 }
8155 }
8156 false
8157 }
8158
8159 fn type_candidates_for_declaration<'b>(
8163 &'b self,
8164 visible_from: &ProjectFile,
8165 declaration: &CodeUnit,
8166 raw_name: &str,
8167 ) -> Vec<&'b CodeUnit> {
8168 let Some(normalized) = normalize_reference_name(raw_name) else {
8169 return Vec::new();
8170 };
8171 if let Some(namespace) = cpp_namespace_for(declaration) {
8172 for prefix in namespace_prefixes(&namespace) {
8173 let qualified = format!("{prefix}::{normalized}");
8174 let candidates = self.type_candidates(visible_from, &qualified);
8175 if !candidates.is_empty() {
8176 return candidates;
8177 }
8178 }
8179 }
8180 self.type_candidates(visible_from, &normalized)
8181 }
8182
8183 fn resolve_unique_type_for_declaration(
8184 &self,
8185 visible_from: &ProjectFile,
8186 declaration: &CodeUnit,
8187 raw_name: &str,
8188 ) -> Option<CodeUnit> {
8189 unique_logical_type_candidate(self.type_candidates_for_declaration(
8190 visible_from,
8191 declaration,
8192 raw_name,
8193 ))
8194 }
8195
8196 fn resolve_defining_type_for_declaration(
8214 &self,
8215 analyzer: &CppGraphSource<'_>,
8216 visible_from: &ProjectFile,
8217 declaration: &CodeUnit,
8218 raw_name: &str,
8219 ) -> Option<CodeUnit> {
8220 let candidates = self.type_candidates_for_declaration(visible_from, declaration, raw_name);
8221 let logical = unique_logical_type_candidate(candidates.clone())?;
8222 let mut defining = candidates.into_iter().filter(|candidate| {
8223 candidate.is_class()
8224 && cpp_class_declaration_strength(analyzer, candidate)
8225 == CppClassDeclarationStrength::Full
8226 });
8227 match (defining.next(), defining.next()) {
8228 (Some(unique_definition), None) => Some(unique_definition.clone()),
8229 _ => Some(logical),
8230 }
8231 }
8232
8233 pub fn resolves_to_type(
8234 &self,
8235 analyzer: &CppGraphSource<'_>,
8236 file: &ProjectFile,
8237 raw_name: &str,
8238 target: &CodeUnit,
8239 ) -> bool {
8240 let Some(normalized) = normalize_reference_name(raw_name) else {
8241 return false;
8242 };
8243 let candidates = self.type_candidates(file, &normalized);
8244 if candidates.is_empty() {
8245 return self.parser_alias_resolves_to_type(file, raw_name, target);
8246 }
8247 let Some(resolved) =
8248 self.unique_type_candidate_preserving_target(analyzer, file, &candidates, target)
8249 else {
8250 return false;
8251 };
8252 same_symbol(&resolved, target) || same_visible_symbol(&resolved, target)
8253 }
8254
8255 pub fn alias_target(&self, alias: &CodeUnit) -> Option<CodeUnit> {
8256 let mut current = alias.clone();
8257 let mut visited = HashSet::default();
8258 while visited.insert(current.clone()) {
8259 let raw_target = cpp_canonical_alias_target_text(&self.cpp_source(), ¤t)?;
8260 let resolved =
8261 self.resolve_type_for_declaration(current.source(), ¤t, &raw_target)?;
8262 if resolved.is_class() {
8263 return Some(resolved);
8264 }
8265 if !declared_type_alias(&self.cpp_source(), &resolved) {
8266 return None;
8267 }
8268 current = resolved;
8269 }
8270 None
8271 }
8272
8273 pub fn same_logical_callable(
8288 &self,
8289 analyzer: &CppGraphSource<'_>,
8290 left: &CodeUnit,
8291 right: &CodeUnit,
8292 ) -> bool {
8293 if same_logical_symbol(left, right) {
8294 return true;
8295 }
8296 if left.kind() != right.kind()
8297 || !left.is_callable()
8298 || !right.is_callable()
8299 || left.fq_name() != right.fq_name()
8300 {
8301 return false;
8302 }
8303 if self.callable_is_template_declaration(analyzer, left)
8309 || self.callable_is_template_declaration(analyzer, right)
8310 {
8311 return false;
8312 }
8313 let (Some(left_comparable), Some(right_comparable)) = (
8314 self.callable_comparable(analyzer, left),
8315 self.callable_comparable(analyzer, right),
8316 ) else {
8317 return false;
8318 };
8319 if left_comparable.suffix != right_comparable.suffix
8324 || left_comparable.shapes.len() != right_comparable.shapes.len()
8325 {
8326 return false;
8327 }
8328 left_comparable
8329 .shapes
8330 .iter()
8331 .zip(right_comparable.shapes.iter())
8332 .all(|(left_slot, right_slot)| match (left_slot, right_slot) {
8333 (CppComparableSlot::Ellipsis, CppComparableSlot::Ellipsis) => true,
8334 (CppComparableSlot::Shape(left_shape), CppComparableSlot::Shape(right_shape)) => {
8335 self.comparable_shapes_agree(analyzer, left_shape, right_shape)
8336 }
8337 _ => false,
8341 })
8342 }
8343
8344 fn comparable_shapes_agree(
8350 &self,
8351 analyzer: &CppGraphSource<'_>,
8352 left: &CppComparableParameter,
8353 right: &CppComparableParameter,
8354 ) -> bool {
8355 let mut stack = vec![(left.root(), right.root())];
8356 while let Some((left_index, right_index)) = stack.pop() {
8357 match (left.node(left_index), right.node(right_index)) {
8358 (
8359 CppComparableNode::Named {
8360 name: left_name,
8361 primitive: left_primitive,
8362 konst: left_konst,
8363 volatil: left_volatil,
8364 },
8365 CppComparableNode::Named {
8366 name: right_name,
8367 primitive: right_primitive,
8368 konst: right_konst,
8369 volatil: right_volatil,
8370 },
8371 ) => {
8372 if left_konst != right_konst
8373 || left_volatil != right_volatil
8374 || left_primitive != right_primitive
8375 || !self.comparable_names_agree(
8376 analyzer,
8377 left_name,
8378 right_name,
8379 *left_primitive,
8380 )
8381 {
8382 return false;
8383 }
8384 }
8385 (
8386 CppComparableNode::Pointer {
8387 inner: left_inner,
8388 konst: left_konst,
8389 volatil: left_volatil,
8390 },
8391 CppComparableNode::Pointer {
8392 inner: right_inner,
8393 konst: right_konst,
8394 volatil: right_volatil,
8395 },
8396 ) => {
8397 if left_konst != right_konst || left_volatil != right_volatil {
8398 return false;
8399 }
8400 stack.push((*left_inner, *right_inner));
8401 }
8402 (
8403 CppComparableNode::Reference { inner: left_inner },
8404 CppComparableNode::Reference { inner: right_inner },
8405 )
8406 | (
8407 CppComparableNode::Array { inner: left_inner },
8408 CppComparableNode::Array { inner: right_inner },
8409 ) => stack.push((*left_inner, *right_inner)),
8410 (
8411 CppComparableNode::Generic {
8412 base: left_base,
8413 arguments: left_arguments,
8414 },
8415 CppComparableNode::Generic {
8416 base: right_base,
8417 arguments: right_arguments,
8418 },
8419 ) => {
8420 if left_arguments.len() != right_arguments.len() {
8421 return false;
8422 }
8423 stack.push((*left_base, *right_base));
8424 stack.extend(
8425 left_arguments.iter().zip(right_arguments.iter()).map(
8426 |(left_argument, right_argument)| (*left_argument, *right_argument),
8427 ),
8428 );
8429 }
8430 _ => return false,
8431 }
8432 }
8433 true
8434 }
8435
8436 fn comparable_names_agree(
8446 &self,
8447 analyzer: &CppGraphSource<'_>,
8448 left: &StructuredTypeName,
8449 right: &StructuredTypeName,
8450 primitive: bool,
8451 ) -> bool {
8452 if primitive {
8453 return left.path() == right.path();
8454 }
8455 match (
8456 self.comparable_name_terminal(analyzer, left),
8457 self.comparable_name_terminal(analyzer, right),
8458 ) {
8459 (Some(left_terminal), Some(right_terminal)) => {
8460 same_logical_symbol(&left_terminal, &right_terminal)
8461 }
8462 (None, None) => {
8463 left.path() == right.path() && left.is_absolute() == right.is_absolute()
8464 }
8465 _ => false,
8466 }
8467 }
8468
8469 fn comparable_name_terminal(
8479 &self,
8480 analyzer: &CppGraphSource<'_>,
8481 name: &StructuredTypeName,
8482 ) -> Option<CodeUnit> {
8483 let mut current = self.comparable_name_declaration(analyzer, name)?;
8484 let mut visited = HashSet::default();
8485 for _ in 0..MAX_COMPARABLE_ALIAS_HOPS {
8486 if !declared_type_alias(analyzer, ¤t) {
8493 return current.is_class().then_some(current);
8494 }
8495 if !visited.insert(current.clone()) {
8496 return None;
8497 }
8498 let facts = analyzer.declaration_source_properties(¤t)?;
8499 if facts.iter().any(|fact| fact.adds_indirection) {
8500 return None;
8501 }
8502 let raw_target = cpp_canonical_alias_target_text(analyzer, ¤t)?;
8503 current = self.comparable_alias_target(analyzer, ¤t, &raw_target)?;
8504 }
8505 None
8506 }
8507
8508 fn comparable_alias_target(
8519 &self,
8520 analyzer: &CppGraphSource<'_>,
8521 alias: &CodeUnit,
8522 raw_target: &str,
8523 ) -> Option<CodeUnit> {
8524 let absolute = raw_target.trim_start().starts_with("::");
8529 let normalized = normalize_reference_name(raw_target)?;
8530 let path = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8531 brokk_bifrost_core::analyzer::Language::Cpp,
8532 &normalized,
8533 );
8534 let lexical_scope = cpp_namespace_for(alias).map_or_else(Vec::new, |namespace| {
8535 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
8536 brokk_bifrost_core::analyzer::Language::Cpp,
8537 &namespace,
8538 )
8539 });
8540 let name = StructuredTypeName::new(path, lexical_scope, absolute)?;
8541 self.comparable_name_declaration(analyzer, &name)
8542 }
8543
8544 fn comparable_name_declaration(
8552 &self,
8553 analyzer: &CppGraphSource<'_>,
8554 name: &StructuredTypeName,
8555 ) -> Option<CodeUnit> {
8556 let mut cache = self
8563 .comparable_name_declarations
8564 .lock()
8565 .expect("C++ comparable name declaration cache poisoned");
8566 if let Some(cached) = cache.get(name) {
8567 return cached.clone();
8568 }
8569 let interner = segment_interner();
8570 let identifier = name.path().last()?;
8571 let candidates_by_identifier = self.cpp.visibility_identifier_candidates(identifier);
8572 let first_depth = if name.is_absolute() {
8573 0
8574 } else {
8575 name.lexical_scope().len()
8576 };
8577 let mut resolved = None;
8578 for depth in (0..=first_depth).rev() {
8579 let mut structured = FqName::new();
8580 for component in name.lexical_scope()[..depth].iter().chain(name.path()) {
8581 structured.push(interner.intern(component, SegmentKind::Unknown));
8582 }
8583 let mut candidates = candidates_by_identifier
8584 .iter()
8585 .filter(|unit| unit.fq().same_segment_texts(&structured))
8586 .filter(|unit| {
8587 unit.kind() == CodeUnitType::Class || declared_type_alias(analyzer, unit)
8588 })
8589 .cloned();
8590 let Some(first) = candidates.next() else {
8591 continue;
8592 };
8593 resolved = candidates
8594 .all(|unit| same_logical_symbol(&unit, &first))
8595 .then_some(first);
8596 break;
8597 }
8598 cache.insert(name.clone(), resolved.clone());
8599 resolved
8600 }
8601
8602 fn callable_comparable(
8608 &self,
8609 analyzer: &CppGraphSource<'_>,
8610 unit: &CodeUnit,
8611 ) -> Option<Arc<ExtractedComparable>> {
8612 if let Some(cached) = self
8613 .callable_comparables
8614 .lock()
8615 .expect("C++ callable comparable cache poisoned")
8616 .get(unit)
8617 .cloned()
8618 {
8619 return cached;
8620 }
8621 let extracted = self
8622 .extract_callable_comparable(analyzer, unit)
8623 .map(Arc::new);
8624 self.callable_comparables
8625 .lock()
8626 .expect("C++ callable comparable cache poisoned")
8627 .insert(unit.clone(), extracted.clone());
8628 extracted
8629 }
8630
8631 fn extract_callable_comparable(
8632 &self,
8633 analyzer: &CppGraphSource<'_>,
8634 unit: &CodeUnit,
8635 ) -> Option<ExtractedComparable> {
8636 let fact = analyzer
8637 .declaration_source_properties(unit)?
8638 .into_iter()
8639 .find(|fact| fact.callable_comparable_shapes.is_some())?;
8640 Some(ExtractedComparable {
8641 shapes: fact.callable_comparable_shapes?,
8642 suffix: fact.callable_identity_suffix?,
8643 })
8644 }
8645
8646 pub fn canonical_type_for_reference(
8647 &self,
8648 file: &ProjectFile,
8649 raw_name: &str,
8650 ) -> Option<CodeUnit> {
8651 let resolved = self.resolve_type(file, raw_name)?;
8652 self.alias_target(&resolved).or(Some(resolved))
8653 }
8654
8655 pub fn parser_alias_resolves_to_type(
8656 &self,
8657 file: &ProjectFile,
8658 raw_name: &str,
8659 target: &CodeUnit,
8660 ) -> bool {
8661 let Some(alias_name) = normalize_reference_name(raw_name) else {
8662 return false;
8663 };
8664 any_visible_source_file_matches(&self.visible_source_files_by_root, file, |source_file| {
8665 self.file_alias_matches(self.cpp, source_file, &alias_name, target)
8666 })
8667 }
8668
8669 fn file_alias_matches(
8670 &self,
8671 cpp: &dyn CppSource,
8672 file: &ProjectFile,
8673 alias_name: &str,
8674 target: &CodeUnit,
8675 ) -> bool {
8676 self.canonical_alias_cell(cpp, file).is_some_and(|cell| {
8677 cell.get()
8678 .expect("published canonical aliases")
8679 .iter()
8680 .any(|alias| alias.name == alias_name && alias_target_matches_target(alias, target))
8681 })
8682 }
8683
8684 fn canonical_alias_cell(
8685 &self,
8686 cpp: &dyn CppSource,
8687 file: &ProjectFile,
8688 ) -> Option<Arc<OnceLock<Box<[CppAlias]>>>> {
8689 let cell = {
8690 let mut cells = self.alias_cells.lock().expect("alias cell map lock");
8691 Arc::clone(
8692 cells
8693 .entry(file.clone())
8694 .or_insert_with(|| Arc::new(OnceLock::new())),
8695 )
8696 };
8697 if cell.get().is_none() {
8698 let aliases = aliases_from_canonical_source(cpp, self.token, file)?;
8699 cell.get_or_init(|| {
8700 self.parser_alias_source_parses
8701 .fetch_add(1, Ordering::Relaxed);
8702 #[cfg(any(test, feature = "test-support"))]
8703 {
8704 *self
8705 .alias_source_parse_counts
8706 .lock()
8707 .expect("alias source parse count lock")
8708 .entry(file.clone())
8709 .or_default() += 1;
8710 }
8711 aliases.into_boxed_slice()
8712 });
8713 }
8714 Some(cell)
8715 }
8716
8717 #[cfg(any(test, feature = "test-support"))]
8718 pub fn visible_source_files_for_test(&self, file: &ProjectFile) -> HashSet<ProjectFile> {
8719 self.visible_source_files_by_root
8720 .get(file)
8721 .cloned()
8722 .unwrap_or_else(|| HashSet::from_iter([file.clone()]))
8723 }
8724
8725 #[cfg(any(test, feature = "test-support"))]
8726 pub fn alias_source_parse_count_for_test(&self, file: &ProjectFile) -> usize {
8727 self.alias_source_parse_counts
8728 .lock()
8729 .expect("alias source parse count lock")
8730 .get(file)
8731 .copied()
8732 .unwrap_or(0)
8733 }
8734
8735 #[cfg(any(test, feature = "test-support"))]
8736 pub fn parser_alias_fallback_file_count_for_test(&self) -> usize {
8737 self.parser_alias_fallback_files.load(Ordering::Relaxed)
8738 }
8739
8740 pub fn resolve_named(
8741 &self,
8742 file: &ProjectFile,
8743 raw_name: &str,
8744 kind: TargetKind,
8745 ) -> Option<CodeUnit> {
8746 let normalized = normalize_reference_name(raw_name)?;
8747 self.named_candidates_for_normalized(file, &normalized, kind)
8748 .into_iter()
8749 .next()
8750 .cloned()
8751 }
8752
8753 pub fn contains_named_symbol(
8754 &self,
8755 file: &ProjectFile,
8756 raw_name: &str,
8757 kind: TargetKind,
8758 target: &CodeUnit,
8759 ) -> bool {
8760 let Some(normalized) = normalize_reference_name(raw_name) else {
8761 return false;
8762 };
8763 self.named_candidates_for_normalized(file, &normalized, kind)
8764 .into_iter()
8765 .any(|unit| {
8766 matches_kind_for_lookup(unit, kind)
8767 && reference_matches_unit(&normalized, unit)
8768 && same_visible_symbol(unit, target)
8769 })
8770 }
8771
8772 pub fn named_candidates(
8773 &self,
8774 file: &ProjectFile,
8775 raw_name: &str,
8776 kind: TargetKind,
8777 ) -> Vec<CodeUnit> {
8778 let Some(normalized) = normalize_reference_name(raw_name) else {
8779 return Vec::new();
8780 };
8781 self.named_candidates_for_normalized(file, &normalized, kind)
8782 .into_iter()
8783 .cloned()
8784 .collect()
8785 }
8786
8787 pub fn resolve_known_non_target(
8788 &self,
8789 file: &ProjectFile,
8790 raw_name: &str,
8791 kind: TargetKind,
8792 target: &CodeUnit,
8793 ) -> bool {
8794 let Some(normalized) = normalize_reference_name(raw_name) else {
8795 return false;
8796 };
8797 normalized.contains("::")
8798 && self
8799 .named_candidates_for_normalized(file, &normalized, kind)
8800 .into_iter()
8801 .any(|unit| {
8802 matches_kind_for_lookup(unit, kind)
8803 && reference_matches_unit(&normalized, unit)
8804 && !same_visible_symbol(unit, target)
8805 })
8806 }
8807
8808 pub fn resolve_call_return_binding(
8809 &self,
8810 analyzer: &CppGraphSource<'_>,
8811 file: &ProjectFile,
8812 raw_name: &str,
8813 arity: usize,
8814 lexical_namespace: Option<&str>,
8815 direct_type: Option<&CodeUnit>,
8816 ) -> Option<CppScanBinding> {
8817 let normalized = normalize_reference_name(raw_name)?;
8818 let mut candidates = Vec::new();
8819 for function in
8820 self.named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8821 {
8822 if cpp_callable_arity(analyzer, function).is_none_or(|known| known.accepts(arity))
8823 && !direct_type.is_some_and(|direct_type| {
8824 self.callable_is_constructor_declaration(analyzer, function)
8825 && type_owner_of(analyzer, function)
8826 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8827 })
8828 {
8829 candidates.push(function.clone());
8830 }
8831 }
8832 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8833 unanimous_return_binding(analyzer, self, file, &candidates)
8834 }
8835
8836 pub fn resolve_call_return_binding_without_arity(
8837 &self,
8838 analyzer: &CppGraphSource<'_>,
8839 file: &ProjectFile,
8840 raw_name: &str,
8841 lexical_namespace: Option<&str>,
8842 direct_type: Option<&CodeUnit>,
8843 ) -> (bool, Option<CppScanBinding>) {
8844 let Some(normalized) = normalize_reference_name(raw_name) else {
8845 return (false, None);
8846 };
8847 let mut candidates = self
8848 .named_candidates_for_normalized(file, &normalized, TargetKind::FreeFunction)
8849 .into_iter()
8850 .filter(|function| {
8851 function.is_function()
8852 && !direct_type.is_some_and(|direct_type| {
8853 self.callable_is_constructor_declaration(analyzer, function)
8854 && type_owner_of(analyzer, function)
8855 .is_some_and(|owner| same_visible_symbol(&owner, direct_type))
8856 })
8857 })
8858 .cloned()
8859 .collect::<Vec<_>>();
8860 candidates = nearest_namespace_candidates(candidates, &normalized, lexical_namespace);
8861 let has_candidates = !candidates.is_empty();
8862 (
8863 has_candidates,
8864 unanimous_return_binding(analyzer, self, file, &candidates),
8865 )
8866 }
8867
8868 pub fn visible_identifier_candidates<'b>(
8869 &'b self,
8870 file: &ProjectFile,
8871 identifier: &str,
8872 ) -> impl Iterator<Item = &'b CodeUnit> + 'b {
8873 self.visible_by_identifier
8874 .get(file)
8875 .and_then(|by_name| by_name.get(identifier))
8876 .into_iter()
8877 .flatten()
8878 }
8879
8880 pub fn type_reference_component_directly_names_target(
8886 &self,
8887 component: &str,
8888 target: &CodeUnit,
8889 ) -> bool {
8890 component == target.identifier()
8891 || self
8892 .cpp_template_metadata
8893 .get(target)
8894 .is_some_and(|metadata| component == metadata.primary_name)
8895 }
8896
8897 pub fn visible_type_reference_component_names_for_target(
8905 &self,
8906 analyzer: &CppGraphSource<'_>,
8907 file: &ProjectFile,
8908 target: &CodeUnit,
8909 ) -> HashSet<String> {
8910 let mut names = HashSet::from_iter([target.identifier().to_string()]);
8911 if let Some(metadata) = self.cpp_template_metadata.get(target) {
8912 names.insert(metadata.primary_name.clone());
8913 }
8914
8915 if let Some(by_identifier) = self.visible_by_identifier.get(file) {
8916 for (identifier, candidates) in by_identifier {
8917 if candidates.iter().any(|candidate| {
8918 (candidate.is_class()
8919 && (same_visible_symbol(candidate, target)
8920 || self.compatible_primary_template_redeclarations(candidate, target)))
8921 || (declared_type_alias(analyzer, candidate)
8922 && self.alias_candidate_structurally_reaches_target(
8923 analyzer, file, candidate, target,
8924 ))
8925 }) {
8926 names.insert(identifier.clone());
8927 }
8928 }
8929 }
8930
8931 names
8932 }
8933
8934 pub fn indexed_structural_class_scope(
8935 &self,
8936 file: &ProjectFile,
8937 class: Node<'_>,
8938 source: &str,
8939 ) -> Option<Vec<String>> {
8940 let key = (file.clone(), class.start_byte(), class.end_byte());
8941 if let Some(cached) = self
8942 .indexed_structural_class_scopes
8943 .lock()
8944 .expect("C++ indexed structural-class scope cache poisoned")
8945 .get(&key)
8946 .cloned()
8947 {
8948 return cached;
8949 }
8950 let resolved = (|| {
8951 let name = class.child_by_field_name("name")?;
8952 let identifier = if name.kind() == "template_type" {
8953 node_text(name.child_by_field_name("name")?, source).to_string()
8954 } else {
8955 let mut components = Vec::new();
8956 append_cpp_name_components(name, source, &mut components)?;
8957 components.last()?.clone()
8958 };
8959 let visible = self
8960 .visible_identifier_candidates(file, &identifier)
8961 .cloned()
8962 .collect::<Vec<_>>();
8963 let mut visible = visible;
8964 for candidate in
8965 self.visible_by_file
8966 .get(file)
8967 .into_iter()
8968 .flatten()
8969 .filter(|candidate| {
8970 self.cpp_template_metadata
8971 .get(candidate)
8972 .is_some_and(|metadata| metadata.primary_name == identifier)
8973 })
8974 {
8975 if !visible
8976 .iter()
8977 .any(|existing| same_logical_symbol(existing, candidate))
8978 {
8979 visible.push(candidate.clone());
8980 }
8981 }
8982 let cpp_source = self.cpp_source();
8985 let candidates = visible
8986 .iter()
8987 .filter(|candidate| {
8988 candidate.source() == file
8989 && candidate.is_class()
8990 && !declared_type_alias(&cpp_source, candidate)
8991 && self.cpp.ranges(candidate).iter().any(|range| {
8992 range.start_byte <= class.start_byte()
8993 && class.end_byte() <= range.end_byte
8994 })
8995 })
8996 .collect::<Vec<_>>();
8997 let owner = if name.kind() == "template_type" {
8998 let expected = normalize_cpp_whitespace(node_text(name, source));
8999 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
9000 let exact = candidates
9001 .iter()
9002 .copied()
9003 .filter(|candidate| {
9004 candidate
9005 .fq()
9006 .segments()
9007 .iter()
9008 .rev()
9009 .find_map(|&segment| {
9010 let (text, kind) = interner.resolve(segment);
9011 matches!(
9012 kind,
9013 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
9014 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
9015 )
9016 .then_some(text)
9017 })
9018 .is_some_and(|text| text == expected)
9019 })
9020 .collect::<Vec<_>>();
9021 unique_logical_type_candidate(exact)
9022 .or_else(|| unique_logical_type_candidate(candidates.clone()))?
9023 } else {
9024 unique_logical_type_candidate(candidates)?
9025 };
9026 Some(canonical_cpp_scope_components(&owner))
9027 })();
9028 self.indexed_structural_class_scopes
9029 .lock()
9030 .expect("C++ indexed structural-class scope cache poisoned")
9031 .insert(key, resolved.clone());
9032 resolved
9033 }
9034
9035 pub fn indexed_enclosing_owner_scope(
9036 &self,
9037 analyzer: &CppGraphSource<'_>,
9038 file: &ProjectFile,
9039 node: Node<'_>,
9040 ) -> Option<Vec<String>> {
9041 let anchor = std::iter::successors(Some(node), |current| current.parent())
9042 .find(|current| {
9043 matches!(
9044 current.kind(),
9045 "function_definition"
9046 | "class_specifier"
9047 | "struct_specifier"
9048 | "union_specifier"
9049 )
9050 })
9051 .unwrap_or(node);
9052 let key = (file.clone(), anchor.start_byte(), anchor.end_byte());
9053 if let Some(cached) = self
9054 .indexed_enclosing_owner_scopes
9055 .lock()
9056 .expect("C++ indexed enclosing-owner scope cache poisoned")
9057 .get(&key)
9058 .cloned()
9059 {
9060 return cached;
9061 }
9062 let resolved = (|| {
9063 let range = Range {
9064 start_byte: node.start_byte(),
9065 end_byte: node.end_byte(),
9066 start_line: node.start_position().row,
9067 end_line: node.end_position().row,
9068 };
9069 let start = analyzer.enclosing_code_unit(file, &range)?;
9070 let owner = brokk_bifrost_core::analyzer::usages::common::enclosing_owner_chain(
9071 start,
9072 |unit| self.cached_precise_parent_of(analyzer, unit),
9073 )
9074 .find(|unit| {
9075 unit.is_class()
9076 && !analyzer
9077 .type_alias_provider()
9078 .is_some_and(|provider| provider.is_type_alias(unit))
9079 })?;
9080 Some(canonical_cpp_scope_components(&owner))
9081 })();
9082 self.indexed_enclosing_owner_scopes
9083 .lock()
9084 .expect("C++ indexed enclosing-owner scope cache poisoned")
9085 .insert(key, resolved.clone());
9086 resolved
9087 }
9088
9089 fn cached_precise_parent_of(
9090 &self,
9091 analyzer: &CppGraphSource<'_>,
9092 code_unit: &CodeUnit,
9093 ) -> Option<CodeUnit> {
9094 if let Some(cached) = self
9095 .precise_parent_cache
9096 .lock()
9097 .expect("C++ precise-parent cache poisoned")
9098 .get(code_unit)
9099 .cloned()
9100 {
9101 return cached;
9102 }
9103 let resolved = precise_parent_resolution(analyzer, code_unit).map(|owner| owner.unit);
9104 self.precise_parent_cache
9105 .lock()
9106 .expect("C++ precise-parent cache poisoned")
9107 .insert(code_unit.clone(), resolved.clone());
9108 resolved
9109 }
9110
9111 pub fn callable_is_constructor_declaration(
9112 &self,
9113 analyzer: &CppGraphSource<'_>,
9114 candidate: &CodeUnit,
9115 ) -> bool {
9116 if !candidate.is_function() {
9117 return false;
9118 }
9119 if analyzer
9120 .declaration_source_properties(candidate)
9121 .is_some_and(|facts| facts.iter().any(|fact| fact.callable_is_constructor))
9122 {
9123 return true;
9124 }
9125 let metadata = analyzer.signature_metadata(candidate);
9126 !metadata.is_empty()
9127 && metadata
9128 .iter()
9129 .all(|signature| signature.return_type_text().is_none())
9130 }
9131
9132 pub fn callable_is_deduction_guide_declaration(
9140 &self,
9141 analyzer: &CppGraphSource<'_>,
9142 candidate: &CodeUnit,
9143 ) -> bool {
9144 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_deduction_guide)
9145 }
9146
9147 pub fn callable_is_template_declaration(
9151 &self,
9152 analyzer: &CppGraphSource<'_>,
9153 candidate: &CodeUnit,
9154 ) -> bool {
9155 callable_declaration_has_fact(analyzer, candidate, |fact| fact.callable_is_template)
9156 }
9157
9158 pub fn type_name_candidates<'b>(
9159 &'b self,
9160 file: &ProjectFile,
9161 normalized: &str,
9162 ) -> Vec<&'b CodeUnit> {
9163 self.candidate_units(file, normalized, TargetKind::Type)
9164 }
9165
9166 pub fn visible_members_for_owner_name<'b>(
9167 &'b self,
9168 file: &ProjectFile,
9169 owner: &CodeUnit,
9170 name: &str,
9171 ) -> Vec<&'b CodeUnit> {
9172 self.visible_identifier_candidates(file, name)
9173 .filter(|unit| {
9174 brokk_bifrost_core::analyzer::default_parent_fq_name(unit)
9178 .is_some_and(|parent| parent == owner.fq_name())
9179 })
9180 .collect()
9181 }
9182
9183 pub fn visible_member_for_owner_name(
9184 &self,
9185 file: &ProjectFile,
9186 owner: &CodeUnit,
9187 name: &str,
9188 ) -> VisibleMemberResolution {
9189 let candidates = self.visible_members_for_owner_name(file, owner, name);
9190 let mut callables = Vec::new();
9191 let mut non_callable = None;
9192 for candidate in candidates {
9193 if candidate.is_function() {
9194 callables.push(candidate.clone());
9195 } else if non_callable.is_none() {
9196 non_callable = Some(candidate.clone());
9197 }
9198 }
9199 match (callables.is_empty(), non_callable) {
9200 (false, None) => VisibleMemberResolution::Callable(callables),
9201 (true, Some(_)) => VisibleMemberResolution::NonCallable,
9202 (false, Some(_)) => VisibleMemberResolution::AmbiguousKind,
9203 (true, None) => VisibleMemberResolution::Missing,
9204 }
9205 }
9206
9207 fn field_declared_type_fact(
9208 &self,
9209 analyzer: &CppGraphSource<'_>,
9210 field: &CodeUnit,
9211 ) -> Option<DeclaredFieldTypeFact> {
9212 if let Some(cached) = self
9213 .field_type_facts
9214 .lock()
9215 .expect("C++ field type fact cache poisoned")
9216 .get(field)
9217 .cloned()
9218 {
9219 return cached;
9220 }
9221 let decoded = decode_field_declared_type_fact(analyzer, field);
9222 self.field_type_facts
9223 .lock()
9224 .expect("C++ field type fact cache poisoned")
9225 .insert(field.clone(), decoded.clone());
9226 decoded
9227 }
9228
9229 fn structured_alias_target(
9230 &self,
9231 analyzer: &CppGraphSource<'_>,
9232 unit: &CodeUnit,
9233 ) -> Option<StructuredAliasTarget> {
9234 if let Some(cached) = self
9235 .structured_alias_targets
9236 .lock()
9237 .expect("C++ structured alias target cache poisoned")
9238 .get(unit)
9239 .cloned()
9240 {
9241 return cached;
9242 }
9243 let decoded = decode_structured_alias_target(analyzer, unit);
9244 self.structured_alias_targets
9245 .lock()
9246 .expect("C++ structured alias target cache poisoned")
9247 .insert(unit.clone(), decoded.clone());
9248 decoded
9249 }
9250
9251 pub fn type_candidates<'b>(
9252 &'b self,
9253 file: &ProjectFile,
9254 normalized: &str,
9255 ) -> Vec<&'b CodeUnit> {
9256 let mut candidates = self
9257 .candidate_units(file, normalized, TargetKind::Type)
9258 .into_iter()
9259 .filter(|unit| {
9260 unit.kind() == CodeUnitType::Class || declared_type_alias(&self.cpp_source(), unit)
9261 })
9262 .collect::<Vec<_>>();
9263 dedup_unit_refs(&mut candidates);
9264 candidates
9265 }
9266
9267 pub fn named_candidates_for_normalized<'b>(
9268 &'b self,
9269 file: &ProjectFile,
9270 normalized: &str,
9271 kind: TargetKind,
9272 ) -> Vec<&'b CodeUnit> {
9273 let mut candidates = self
9274 .candidate_units(file, normalized, kind)
9275 .into_iter()
9276 .filter(|unit| {
9277 matches_kind_for_lookup(unit, kind) && reference_matches_unit(normalized, unit)
9278 })
9279 .collect::<Vec<_>>();
9280 dedup_unit_refs(&mut candidates);
9281 candidates
9282 }
9283
9284 pub fn candidate_units<'b>(
9285 &'b self,
9286 file: &ProjectFile,
9287 normalized: &str,
9288 kind: TargetKind,
9289 ) -> Vec<&'b CodeUnit> {
9290 if normalized.contains("::") {
9291 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
9300 brokk_bifrost_core::analyzer::Language::Cpp,
9301 normalized,
9302 );
9303 let Some(identifier) = parts.last() else {
9304 return Vec::new();
9305 };
9306 let fqns = cpp_reference_fqn_candidates_from_parts(&parts, kind);
9307 return self
9308 .visible_identifier_candidates(file, identifier)
9309 .filter(|unit| {
9310 #[cfg(any(test, feature = "test-support"))]
9311 self.qualified_candidate_inspections
9312 .fetch_add(1, Ordering::Relaxed);
9313 fqns.iter().any(|fqn| unit.fq_name() == *fqn)
9314 || canonical_cpp_name_matches(unit, normalized)
9315 })
9316 .collect();
9317 }
9318 self.visible_identifier_candidates(file, normalized)
9319 .collect()
9320 }
9321
9322 #[cfg(any(test, feature = "test-support"))]
9323 pub fn reset_qualified_candidate_inspections(&self) {
9324 self.qualified_candidate_inspections
9325 .store(0, Ordering::Relaxed);
9326 }
9327
9328 #[cfg(any(test, feature = "test-support"))]
9329 pub fn qualified_candidate_inspections(&self) -> usize {
9330 self.qualified_candidate_inspections.load(Ordering::Relaxed)
9331 }
9332
9333 #[cfg(any(test, feature = "test-support"))]
9334 pub fn visibility_identifier_lookup_count(&self) -> usize {
9335 self.visibility_identifier_lookup_count
9336 }
9337
9338 #[cfg(any(test, feature = "test-support"))]
9339 pub fn visibility_identifier_batch_count(&self) -> usize {
9340 self.visibility_identifier_batch_count
9341 }
9342
9343 #[cfg(any(test, feature = "test-support"))]
9344 pub fn reset_target_preserving_type_resolution_count(&self) {
9345 self.target_preserving_type_resolution_count
9346 .store(0, Ordering::Relaxed);
9347 }
9348
9349 #[cfg(any(test, feature = "test-support"))]
9350 pub fn target_preserving_type_resolution_count(&self) -> usize {
9351 self.target_preserving_type_resolution_count
9352 .load(Ordering::Relaxed)
9353 }
9354
9355 #[cfg(any(test, feature = "test-support"))]
9356 pub fn visible_parser_alias_name_set_build_count(&self) -> usize {
9357 self.visible_parser_alias_name_set_build_count
9358 .load(Ordering::Relaxed)
9359 }
9360}
9361
9362fn callable_declaration_has_fact(
9363 analyzer: &CppGraphSource<'_>,
9364 candidate: &CodeUnit,
9365 matches: impl Fn(&brokk_bifrost_core::analyzer::cpp_facts::CppDeclarationSourceFact) -> bool,
9366) -> bool {
9367 candidate.is_function()
9368 && analyzer
9369 .declaration_source_properties(candidate)
9370 .is_some_and(|facts| facts.iter().any(matches))
9371}
9372
9373#[derive(Default)]
9374struct IncludeGraph {
9375 targets_by_file: HashMap<ProjectFile, Vec<ProjectFile>>,
9376}
9377
9378impl IncludeGraph {
9379 fn extend_with<F>(
9380 &mut self,
9381 root: &ProjectFile,
9382 cancellation: Option<&CancellationToken>,
9383 targets_for: &mut F,
9384 ) where
9385 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9386 {
9387 let mut stack = vec![root.clone()];
9388 while let Some(file) = stack.pop() {
9389 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9390 break;
9391 }
9392 if self.targets_by_file.contains_key(&file) {
9393 continue;
9394 }
9395 let targets = targets_for(&file);
9396 stack.extend(targets.iter().cloned());
9397 self.targets_by_file.insert(file, targets);
9398 }
9399 }
9400
9401 fn files(&self) -> impl Iterator<Item = &ProjectFile> {
9402 self.targets_by_file.keys()
9403 }
9404
9405 fn targets(&self, file: &ProjectFile) -> &[ProjectFile] {
9406 self.targets_by_file
9407 .get(file)
9408 .map(Vec::as_slice)
9409 .unwrap_or_default()
9410 }
9411
9412 fn reachable_files(
9413 &self,
9414 root: &ProjectFile,
9415 cancellation: Option<&CancellationToken>,
9416 ) -> HashSet<ProjectFile> {
9417 let mut pending = vec![root.clone()];
9418 let mut visited = HashSet::default();
9419 while let Some(file) = pending.pop() {
9420 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9421 break;
9422 }
9423 if visited.insert(file.clone()) {
9424 pending.extend(self.targets(&file).iter().cloned());
9425 }
9426 }
9427 visited
9428 }
9429}
9430
9431fn build_bounded_visible_declarations(
9432 cpp: &dyn CppSource,
9433 token: QueryToken<'_>,
9434 analyzer: &CppGraphSource<'_>,
9435 roots: &HashSet<ProjectFile>,
9436 visible_sources: &HashMap<ProjectFile, HashSet<ProjectFile>>,
9437 cancellation: Option<&CancellationToken>,
9438 stats: &mut BoundedVisibilityStats,
9439) -> HashMap<ProjectFile, HashSet<CodeUnit>> {
9440 let mut candidates_by_identifier = HashMap::default();
9441 let mut declarations_by_source_and_reading: HashMap<
9442 (ProjectFile, bool),
9443 BoundedVisibilityDeclarations,
9444 > = HashMap::default();
9445 let mut dependency_names_by_unit: HashMap<CodeUnit, HashSet<String>> = HashMap::default();
9446 roots
9447 .iter()
9448 .map(|root| {
9449 let reading_is_c = analyzer.reference_uses_c_semantics(root);
9450 let root_declarations = declarations_by_source_and_reading
9451 .entry((root.clone(), reading_is_c))
9452 .or_insert_with(|| {
9453 bounded_visibility_declarations(cpp, analyzer, root, reading_is_c, stats)
9454 });
9455 let mut visible = root_declarations
9456 .all
9457 .iter()
9458 .cloned()
9459 .collect::<HashSet<_>>();
9460 let mut pending_names = HashSet::default();
9461 if let Some(prepared) = cpp.prepared_syntax(token, root) {
9462 let mut cursor = prepared.tree().walk();
9467 let mut pending_nodes = vec![prepared.tree().root_node()];
9468 while let Some(node) = pending_nodes.pop() {
9469 if matches!(
9470 node.kind(),
9471 "identifier"
9472 | "type_identifier"
9473 | "field_identifier"
9474 | "namespace_identifier"
9475 ) {
9476 pending_names.insert(node_text(node, prepared.source()).to_string());
9477 }
9478 if node.kind() == "preproc_arg" {
9479 for reference in
9480 object_macro_replacement_type_references(node, prepared.source())
9481 {
9482 pending_names.extend(reference.components);
9483 }
9484 }
9485 pending_nodes.extend(node.named_children(&mut cursor));
9486 }
9487 }
9488 stats.root_names += pending_names.len();
9489 let mut completed_names = HashSet::default();
9490 while !pending_names.is_empty() {
9491 stats.rounds += 1;
9492 let round_names = std::mem::take(&mut pending_names);
9493 let mut requested_names_by_source: HashMap<ProjectFile, HashSet<String>> =
9494 HashMap::default();
9495 let mut identifiers = Vec::new();
9496 for identifier in round_names {
9497 if !completed_names.insert(identifier.clone())
9498 || cancellation.is_some_and(CancellationToken::is_cancelled)
9499 {
9500 continue;
9501 }
9502 identifiers.push(identifier);
9503 }
9504 let missing_identifiers = identifiers
9505 .iter()
9506 .filter(|identifier| !candidates_by_identifier.contains_key(*identifier))
9507 .cloned()
9508 .collect::<HashSet<_>>();
9509 if !missing_identifiers.is_empty() {
9510 let lookup_started = Instant::now();
9511 let mut candidates = cpp
9512 .visibility_identifier_candidates_batch(&missing_identifiers, cancellation);
9513 stats.lookup_elapsed += lookup_started.elapsed();
9514 stats.identifier_lookups += missing_identifiers.len();
9515 stats.identifier_batches += 1;
9516 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9517 break;
9518 }
9519 for identifier in missing_identifiers {
9520 let units = candidates.remove(&identifier).unwrap_or_default();
9521 let candidate_count = units.len();
9522 let candidate_sources = units
9523 .into_iter()
9524 .map(|unit| unit.source().clone())
9525 .collect::<HashSet<_>>();
9526 candidates_by_identifier
9527 .insert(identifier, (candidate_sources, candidate_count));
9528 }
9529 }
9530 for identifier in identifiers {
9531 let (candidate_sources, candidate_count) = candidates_by_identifier
9532 .get(&identifier)
9533 .expect("every missing identifier was inserted after the batch lookup");
9534 stats.candidate_units += *candidate_count;
9535 for source in candidate_sources.iter().cloned() {
9536 if source != *root
9537 && visible_sources
9538 .get(root)
9539 .is_some_and(|files| files.contains(&source))
9540 {
9541 requested_names_by_source
9542 .entry(source)
9543 .or_default()
9544 .insert(identifier.clone());
9545 }
9546 }
9547 }
9548 stats.candidate_sources += requested_names_by_source.len();
9549 for (source, requested_names) in requested_names_by_source {
9550 let declarations = declarations_by_source_and_reading
9551 .entry((source.clone(), reading_is_c))
9552 .or_insert_with(|| {
9553 bounded_visibility_declarations(
9554 cpp,
9555 analyzer,
9556 &source,
9557 reading_is_c,
9558 stats,
9559 )
9560 });
9561 let mut selected = HashSet::default();
9562 for name in requested_names {
9563 if let Some(units) = declarations.by_identifier.get(&name) {
9564 selected.extend(units.iter().cloned());
9565 }
9566 }
9567 for unit in selected {
9568 stats.selected_units += 1;
9569 let dependency_names = dependency_names_by_unit
9570 .entry(unit.clone())
9571 .or_insert_with(|| {
9572 let facts_started = Instant::now();
9573 let mut dependency_names = HashSet::default();
9574 if let Some(facts) = analyzer.declaration_source_properties(&unit) {
9575 for fact in facts {
9576 for name in fact.dependency_type_names {
9577 stats.dependency_source_names += 1;
9578 #[cfg(any(test, feature = "test-support"))]
9579 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT
9580 .with(|count| count.set(count.get() + 1));
9581 dependency_names.insert(name);
9582 }
9583 }
9584 stats.dependency_source_elapsed += facts_started.elapsed();
9585 }
9586 dependency_names
9587 });
9588 for name in dependency_names.iter() {
9589 if !completed_names.contains(name) && pending_names.insert(name.clone())
9590 {
9591 stats.dependency_names += 1;
9592 }
9593 }
9594 visible.insert(unit);
9595 }
9596 }
9597 }
9598 (root.clone(), visible)
9599 })
9600 .collect()
9601}
9602
9603struct BoundedVisibilityDeclarations {
9604 all: Vec<CodeUnit>,
9605 by_identifier: HashMap<String, Vec<CodeUnit>>,
9606}
9607
9608fn bounded_visibility_declarations(
9609 cpp: &dyn CppSource,
9610 analyzer: &CppGraphSource<'_>,
9611 file: &ProjectFile,
9612 c_semantics: bool,
9613 stats: &mut BoundedVisibilityStats,
9614) -> BoundedVisibilityDeclarations {
9615 let declarations_started = Instant::now();
9616 let all = bounded_visibility_declarations_in_reading(analyzer, file, c_semantics)
9617 .into_iter()
9618 .collect::<Vec<_>>();
9619 stats.declaration_elapsed += declarations_started.elapsed();
9620 stats.declaration_reads += 1;
9621 stats.declaration_units += all.len();
9622
9623 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
9624 for unit in &all {
9625 by_identifier
9626 .entry(unit.identifier().to_string())
9627 .or_default()
9628 .push(unit.clone());
9629 if unit.is_class()
9630 && let Some(metadata) = cpp.template_metadata(unit)
9631 && metadata.primary_name != unit.identifier()
9632 {
9633 by_identifier
9634 .entry(metadata.primary_name)
9635 .or_default()
9636 .push(unit.clone());
9637 }
9638 }
9639 BoundedVisibilityDeclarations { all, by_identifier }
9640}
9641
9642#[derive(Default)]
9643struct BoundedVisibilityStats {
9644 rounds: usize,
9645 root_names: usize,
9646 identifier_lookups: usize,
9647 identifier_batches: usize,
9648 candidate_units: usize,
9649 candidate_sources: usize,
9650 declaration_reads: usize,
9651 declaration_units: usize,
9652 selected_units: usize,
9653 dependency_source_names: usize,
9654 dependency_names: usize,
9655 lookup_elapsed: Duration,
9656 declaration_elapsed: Duration,
9657 dependency_source_elapsed: Duration,
9658}
9659
9660fn bounded_visibility_declarations_in_reading(
9661 analyzer: &CppGraphSource<'_>,
9662 file: &ProjectFile,
9663 c_semantics: bool,
9664) -> BTreeSet<CodeUnit> {
9665 #[cfg(any(test, feature = "test-support"))]
9666 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(count.get() + 1));
9667 analyzer.declarations_in_reading(file, c_semantics)
9668}
9669
9670#[cfg(any(test, feature = "test-support"))]
9671pub fn reset_bounded_visibility_declaration_read_count_for_test() {
9672 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(|count| count.set(0));
9673}
9674
9675#[cfg(any(test, feature = "test-support"))]
9676pub fn bounded_visibility_declaration_read_count_for_test() -> usize {
9677 BOUNDED_VISIBILITY_DECLARATION_READ_COUNT.with(Cell::get)
9678}
9679
9680#[cfg(any(test, feature = "test-support"))]
9681pub fn reset_bounded_visibility_dependency_ast_node_count_for_test() {
9682 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(|count| count.set(0));
9683}
9684
9685#[cfg(any(test, feature = "test-support"))]
9686pub fn bounded_visibility_dependency_ast_node_count_for_test() -> usize {
9687 BOUNDED_VISIBILITY_DEPENDENCY_AST_NODE_COUNT.with(Cell::get)
9688}
9689
9690pub struct VisibilityData {
9691 pub visible_by_file: HashMap<ProjectFile, HashSet<CodeUnit>>,
9692 pub visible_source_files_by_root: HashMap<ProjectFile, HashSet<ProjectFile>>,
9693}
9694
9695pub fn build_visibility_data<F, R, D>(
9705 roots: &HashSet<ProjectFile>,
9706 cancellation: Option<&CancellationToken>,
9707 mut targets_for: F,
9708 mut reading_is_c_for: R,
9709 mut declarations_for: D,
9710) -> VisibilityData
9711where
9712 F: FnMut(&ProjectFile) -> Vec<ProjectFile>,
9713 R: FnMut(&ProjectFile) -> bool,
9714 D: FnMut(&ProjectFile, bool) -> BTreeSet<CodeUnit>,
9715{
9716 let mut include_graph = IncludeGraph::default();
9717 for file in roots {
9718 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9719 break;
9720 }
9721 include_graph.extend_with(file, cancellation, &mut targets_for);
9722 }
9723 let cpp_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = include_graph
9724 .files()
9725 .take_while(|_| !cancellation.is_some_and(CancellationToken::is_cancelled))
9726 .map(|file| (file.clone(), declarations_for(file, false)))
9727 .collect();
9728 let mut c_declarations_by_file: HashMap<ProjectFile, BTreeSet<CodeUnit>> = HashMap::default();
9729 let mut visible_by_file = HashMap::default();
9730 let mut visible_source_files_by_root = HashMap::default();
9731 for file in roots {
9732 if cancellation.is_some_and(CancellationToken::is_cancelled) {
9733 break;
9734 }
9735 let mut visited = HashSet::default();
9736 let mut visible = HashSet::default();
9737 let declarations_by_file = if reading_is_c_for(file) {
9738 for reached in cpp_declarations_by_file.keys() {
9739 if !c_declarations_by_file.contains_key(reached) {
9740 let declarations = declarations_for(reached, true);
9741 c_declarations_by_file.insert(reached.clone(), declarations);
9742 }
9743 }
9744 &c_declarations_by_file
9745 } else {
9746 &cpp_declarations_by_file
9747 };
9748 collect_visible_declarations(
9749 &include_graph,
9750 declarations_by_file,
9751 file,
9752 &mut visited,
9753 &mut visible,
9754 cancellation,
9755 );
9756 visible_by_file.insert(file.clone(), visible);
9757 visible_source_files_by_root.insert(file.clone(), visited);
9758 }
9759 VisibilityData {
9760 visible_by_file,
9761 visible_source_files_by_root,
9762 }
9763}
9764
9765#[derive(Default)]
9784struct OutOfLineOwnerBindingStats {
9785 unseen_owners: usize,
9786 definition_lookups: usize,
9787 admitted: usize,
9788}
9789
9790fn extend_with_out_of_line_owner_bindings(
9791 cpp: &dyn CppSource,
9792 visible_by_file: &mut HashMap<ProjectFile, HashSet<CodeUnit>>,
9793) -> OutOfLineOwnerBindingStats {
9794 let mut stats = OutOfLineOwnerBindingStats::default();
9795 for (file, visible) in visible_by_file.iter_mut() {
9796 let mut unseen_owners: HashSet<String> = visible
9800 .iter()
9801 .filter(|unit| unit.source() == file && (unit.is_function() || unit.is_field()))
9802 .filter_map(brokk_bifrost_core::analyzer::default_parent_fq_name)
9803 .collect();
9804 if unseen_owners.is_empty() {
9805 continue;
9806 }
9807 for unit in visible.iter().filter(|unit| unit.is_class()) {
9808 unseen_owners.remove(&unit.fq_name());
9809 }
9810 stats.unseen_owners += unseen_owners.len();
9811 stats.definition_lookups += unseen_owners.len();
9812 let admitted = unseen_owners
9813 .iter()
9814 .flat_map(|owner| cpp.definitions(owner))
9815 .filter(CodeUnit::is_class)
9816 .collect::<Vec<_>>();
9817 stats.admitted += admitted.len();
9818 visible.extend(admitted);
9819 }
9820 stats
9821}
9822
9823pub enum VisibleMemberResolution {
9824 Callable(Vec<CodeUnit>),
9825 NonCallable,
9826 AmbiguousKind,
9827 Missing,
9828}
9829
9830#[derive(Clone)]
9831pub enum EnclosingMemberOwnerResolution {
9832 Owner(CodeUnit),
9833 Ambiguous,
9834 Missing,
9835}
9836
9837pub fn resolve_declaring_member_owner(
9838 analyzer: &CppGraphSource<'_>,
9839 visibility: &VisibilityIndex<'_>,
9840 file: &ProjectFile,
9841 receiver_owner: &CodeUnit,
9842 member_name: &str,
9843) -> EnclosingMemberOwnerResolution {
9844 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
9845 return EnclosingMemberOwnerResolution::Missing;
9846 };
9847 let Some(receiver_owner) =
9848 visibility.canonical_visible_full_type_unit(analyzer, file, receiver_owner)
9849 else {
9850 return EnclosingMemberOwnerResolution::Ambiguous;
9851 };
9852 let resolve_level = |frontier: &[CodeUnit]| {
9853 let mut member_owners = Vec::new();
9854 for raw_owner in frontier {
9855 let Some(owner) =
9856 visibility.canonical_visible_full_type_unit(analyzer, file, raw_owner)
9857 else {
9858 return EnclosingMemberOwnerResolution::Ambiguous;
9859 };
9860 for member in visibility.visible_members_for_owner_name(file, &owner, member_name) {
9861 if !member.is_field() && !member.is_function() {
9866 continue;
9867 }
9868 let Some(member_owner) = type_owner_of(analyzer, member) else {
9869 return EnclosingMemberOwnerResolution::Ambiguous;
9870 };
9871 if !member_owners
9872 .iter()
9873 .any(|existing| same_visible_symbol(existing, &member_owner))
9874 {
9875 member_owners.push(member_owner);
9876 }
9877 }
9878 }
9879 match member_owners.len() {
9880 0 => EnclosingMemberOwnerResolution::Missing,
9881 1 => EnclosingMemberOwnerResolution::Owner(member_owners.pop().unwrap()),
9882 _ => EnclosingMemberOwnerResolution::Ambiguous,
9883 }
9884 };
9885 let direct = resolve_level(std::slice::from_ref(&receiver_owner));
9889 if !matches!(direct, EnclosingMemberOwnerResolution::Missing) {
9890 return direct;
9891 }
9892 let mut stack = hierarchy.get_direct_ancestors(&receiver_owner);
9893 let mut propagated_counts: HashMap<CodeUnit, u8> = HashMap::default();
9894 let mut path_matches = Vec::new();
9895 while let Some(raw_owner) = stack.pop() {
9896 let Some(owner) = visibility.canonical_visible_full_type_unit(analyzer, file, &raw_owner)
9897 else {
9898 return EnclosingMemberOwnerResolution::Ambiguous;
9899 };
9900 let propagated = propagated_counts.entry(owner.clone()).or_default();
9904 if *propagated == 2 {
9905 continue;
9906 }
9907 *propagated += 1;
9908 match resolve_level(std::slice::from_ref(&owner)) {
9909 EnclosingMemberOwnerResolution::Owner(owner) => {
9910 path_matches.push(owner);
9911 if path_matches.len() == 2 {
9912 return EnclosingMemberOwnerResolution::Ambiguous;
9913 }
9914 }
9915 EnclosingMemberOwnerResolution::Ambiguous => {
9916 return EnclosingMemberOwnerResolution::Ambiguous;
9917 }
9918 EnclosingMemberOwnerResolution::Missing => {
9919 stack.extend(hierarchy.get_direct_ancestors(&owner));
9920 }
9921 }
9922 }
9923 match path_matches.len() {
9924 0 => EnclosingMemberOwnerResolution::Missing,
9925 1 => EnclosingMemberOwnerResolution::Owner(path_matches.pop().unwrap()),
9926 _ => unreachable!("base-path matches are capped at one before returning"),
9927 }
9928}
9929
9930pub fn resolve_declaring_callable_owner(
9945 analyzer: &CppGraphSource<'_>,
9946 visibility: &VisibilityIndex<'_>,
9947 file: &ProjectFile,
9948 ordinary: EnclosingMemberOwnerResolution,
9949 member_name: &str,
9950 call_arity: usize,
9951) -> EnclosingMemberOwnerResolution {
9952 let EnclosingMemberOwnerResolution::Owner(ordinary_owner) = &ordinary else {
9953 return ordinary;
9954 };
9955 if visibility
9956 .visible_members_for_owner_name(file, ordinary_owner, member_name)
9957 .into_iter()
9958 .any(|unit| {
9959 unit.is_function()
9960 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(call_arity))
9961 })
9962 {
9963 return ordinary;
9964 }
9965
9966 let mut pending = match member_using_declaration_bases(
9967 analyzer,
9968 visibility,
9969 file,
9970 ordinary_owner,
9971 member_name,
9972 ) {
9973 Ok(bases) => bases,
9974 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
9975 };
9976 let mut visited = HashSet::default();
9977 let mut introduced_owners = Vec::new();
9978 while let Some(owner) = pending.pop() {
9979 if !visited.insert(owner.clone()) {
9980 continue;
9981 }
9982 let accepts_arity = visibility
9983 .visible_members_for_owner_name(file, &owner, member_name)
9984 .into_iter()
9985 .any(|unit| {
9986 unit.is_function()
9987 && cpp_callable_arity(analyzer, unit)
9988 .is_none_or(|known| known.accepts(call_arity))
9989 });
9990 if accepts_arity {
9991 if !introduced_owners
9992 .iter()
9993 .any(|existing| same_visible_symbol(existing, &owner))
9994 {
9995 introduced_owners.push(owner);
9996 }
9997 continue;
9998 }
9999 match member_using_declaration_bases(analyzer, visibility, file, &owner, member_name) {
10000 Ok(bases) => pending.extend(bases),
10001 Err(()) => return EnclosingMemberOwnerResolution::Ambiguous,
10002 }
10003 }
10004 match introduced_owners.as_slice() {
10005 [] => ordinary,
10006 [owner] => EnclosingMemberOwnerResolution::Owner(owner.clone()),
10007 _ => EnclosingMemberOwnerResolution::Ambiguous,
10008 }
10009}
10010
10011fn member_using_declaration_bases(
10012 analyzer: &CppGraphSource<'_>,
10013 visibility: &VisibilityIndex<'_>,
10014 file: &ProjectFile,
10015 owner: &CodeUnit,
10016 member_name: &str,
10017) -> Result<Vec<CodeUnit>, ()> {
10018 let Some(facts) = analyzer.declaration_source_properties(owner) else {
10019 return Err(());
10020 };
10021 let scopes: Vec<_> = facts
10022 .iter()
10023 .flat_map(|fact| &fact.member_usings)
10024 .filter(|using| using.member == member_name)
10025 .map(|using| using.scope.join("::"))
10026 .collect();
10027 if scopes.is_empty() {
10028 return Ok(Vec::new());
10029 }
10030 let Some(hierarchy) = analyzer.type_hierarchy_provider() else {
10031 return Ok(Vec::new());
10032 };
10033 let mut bases = Vec::new();
10034 for raw_ancestor in hierarchy.get_ancestors(owner) {
10035 let Some(ancestor) =
10036 visibility.canonical_visible_full_type_unit(analyzer, file, &raw_ancestor)
10037 else {
10038 return Err(());
10039 };
10040 let qualified = cpp_name_for(&ancestor);
10041 if scopes
10042 .iter()
10043 .any(|scope| cpp_qualified_name_has_scope_suffix(&qualified, scope))
10044 && !bases
10045 .iter()
10046 .any(|existing| same_visible_symbol(existing, &ancestor))
10047 {
10048 bases.push(ancestor);
10049 }
10050 }
10051 Ok(bases)
10052}
10053
10054pub fn lexical_component_tiers<'a>(
10055 components: &'a [String],
10056 global: bool,
10057 lexical_scope: &'a [String],
10058) -> impl Iterator<Item = Vec<String>> + 'a {
10059 let first_prefix_len = if global { 0 } else { lexical_scope.len() };
10060 (0..=first_prefix_len).rev().map(move |prefix_len| {
10061 let mut qualified = Vec::with_capacity(prefix_len + components.len());
10062 qualified.extend_from_slice(&lexical_scope[..prefix_len]);
10063 qualified.extend_from_slice(components);
10064 qualified
10065 })
10066}
10067
10068pub fn build_visible_identifier_index(
10069 analyzer: &CppGraphSource<'_>,
10070 visible_by_file: &HashMap<ProjectFile, HashSet<CodeUnit>>,
10071 visible_source_files_by_root: &HashMap<ProjectFile, HashSet<ProjectFile>>,
10072 global_field_internal_linkage: &mut HashMap<CodeUnit, bool>,
10073) -> HashMap<ProjectFile, HashMap<String, Vec<CodeUnit>>> {
10074 let mut out = HashMap::default();
10075 for (file, visible) in visible_by_file {
10076 let mut by_identifier: HashMap<String, Vec<CodeUnit>> = HashMap::default();
10077 for unit in visible {
10078 if unit.is_field()
10079 && !visible_source_files_by_root
10080 .get(file)
10081 .is_some_and(|sources| sources.contains(unit.source()))
10082 && cpp_global_field_has_internal_linkage_cached(
10083 analyzer,
10084 global_field_internal_linkage,
10085 unit,
10086 )
10087 {
10088 continue;
10089 }
10090 by_identifier
10091 .entry(unit.identifier().to_string())
10092 .or_default()
10093 .push(unit.clone());
10094 }
10095 for units in by_identifier.values_mut() {
10096 sort_lookup_units(units);
10097 units.dedup();
10098 }
10099 out.insert(file.clone(), by_identifier);
10100 }
10101 out
10102}
10103
10104fn sort_lookup_units(units: &mut [CodeUnit]) {
10105 units.sort_by(|left, right| {
10106 left.fq_name()
10107 .cmp(&right.fq_name())
10108 .then_with(|| left.signature().cmp(&right.signature()))
10109 .then_with(|| left.source().cmp(right.source()))
10110 .then_with(|| left.kind().cmp(&right.kind()))
10111 .then_with(|| {
10112 left.package_segment_count()
10113 .cmp(&right.package_segment_count())
10114 })
10115 .then_with(|| left.is_synthetic().cmp(&right.is_synthetic()))
10116 .then_with(|| stable_fq_name_cmp(left.fq(), right.fq()))
10117 });
10118}
10119
10120fn stable_fq_name_cmp(left: &FqName, right: &FqName) -> CmpOrdering {
10121 let interner = segment_interner();
10122 for (&left_id, &right_id) in left.segments().iter().zip(right.segments()) {
10123 let (left_text, left_kind) = interner.resolve(left_id);
10124 let (right_text, right_kind) = interner.resolve(right_id);
10125 let order = left_text
10126 .cmp(right_text)
10127 .then_with(|| segment_kind_order(left_kind).cmp(&segment_kind_order(right_kind)));
10128 if order != CmpOrdering::Equal {
10129 return order;
10130 }
10131 }
10132 left.len().cmp(&right.len())
10133}
10134
10135const fn segment_kind_order(kind: SegmentKind) -> u8 {
10136 match kind {
10137 SegmentKind::Path => 0,
10138 SegmentKind::Package => 1,
10139 SegmentKind::Type => 2,
10140 SegmentKind::Companion => 3,
10141 SegmentKind::Nested => 4,
10142 SegmentKind::Member => 5,
10143 SegmentKind::Unknown => 6,
10144 }
10145}
10146
10147fn dedup_unit_refs(units: &mut Vec<&CodeUnit>) {
10148 *units = brokk_bifrost_core::analyzer::usages::inverted_edges::dedup_preserving_order(
10149 std::mem::take(units),
10150 );
10151}
10152
10153pub fn cpp_reference_fqn_candidates(reference: &str, kind: TargetKind) -> Vec<String> {
10154 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10158 brokk_bifrost_core::analyzer::Language::Cpp,
10159 reference,
10160 );
10161 cpp_reference_fqn_candidates_from_parts(&parts, kind)
10162}
10163
10164fn cpp_reference_fqn_candidates_from_parts(parts: &[String], kind: TargetKind) -> Vec<String> {
10165 if parts.is_empty() {
10166 return Vec::new();
10167 }
10168
10169 let mut candidates = Vec::new();
10170 for package_len in 0..parts.len() {
10171 let package = parts[..package_len].join("::");
10172 let rest = &parts[package_len..];
10173 if rest.is_empty() {
10174 continue;
10175 }
10176 match kind {
10177 TargetKind::Type | TargetKind::Constructor => {
10178 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("$"));
10179 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10180 }
10181 TargetKind::FreeFunction
10182 | TargetKind::Method
10183 | TargetKind::GlobalField
10184 | TargetKind::MemberField
10185 | TargetKind::Macro => {
10186 push_cpp_fqn_candidate(&mut candidates, &package, &rest.join("."));
10187 if rest.len() > 1 {
10188 let owner = rest[..rest.len() - 1].join("$");
10189 let short = format!("{}.{}", owner, rest[rest.len() - 1]);
10190 push_cpp_fqn_candidate(&mut candidates, &package, &short);
10191 }
10192 }
10193 }
10194 }
10195 candidates
10196}
10197
10198fn push_cpp_fqn_candidate(out: &mut Vec<String>, package: &str, short: &str) {
10199 let fqn = if package.is_empty() {
10200 short.to_string()
10201 } else {
10202 format!("{package}.{short}")
10203 };
10204 if !out.contains(&fqn) {
10205 out.push(fqn);
10206 }
10207}
10208
10209pub fn infer_cpp_initializer_type(
10210 analyzer: &CppGraphSource<'_>,
10211 visibility: &VisibilityIndex<'_>,
10212 file: &ProjectFile,
10213 source: &str,
10214 node: Node<'_>,
10215) -> Option<CodeUnit> {
10216 infer_cpp_initializer_binding(analyzer, visibility, file, source, node, None)
10217 .and_then(|binding| binding.unit)
10218}
10219
10220pub fn infer_cpp_initializer_binding(
10221 analyzer: &CppGraphSource<'_>,
10222 visibility: &VisibilityIndex<'_>,
10223 file: &ProjectFile,
10224 source: &str,
10225 node: Node<'_>,
10226 receiver_resolver: Option<&ReceiverResolver<'_>>,
10227) -> Option<CppScanBinding> {
10228 match node.kind() {
10229 "new_expression" => {
10230 let type_node = cpp_new_expression_type_node(node)?;
10231 let type_text = node_text(type_node, source);
10232 let name = normalize_cpp_type_name(type_text);
10233 Some(CppScanBinding::from_type_name(
10234 name.clone(),
10235 visibility.resolve_type(file, &name),
10236 1,
10237 ))
10238 }
10239 "call_expression" => node.child_by_field_name("function").and_then(|function| {
10240 if function.kind() == "field_expression" {
10248 let arity = visibility.call_arity_evidence(file, node, source).exact()?;
10249 return resolve_field_method_call_return_binding(
10250 analyzer,
10251 visibility,
10252 file,
10253 source,
10254 function,
10255 arity,
10256 receiver_resolver,
10257 );
10258 }
10259 let function_text = node_text(function, source);
10260 let direct_type_binding = visibility
10261 .resolve_type(file, function_text)
10262 .map(|unit| CppScanBinding::from_unit(unit, 0));
10263 if function.kind() == "template_function" && direct_type_binding.is_some() {
10264 let lexical_namespace = enclosing_namespace_context(node, source);
10265 let arity = visibility.call_arity_evidence(file, node, source).exact();
10266 if let Some(arity) = arity
10267 && let Some(binding) = visibility.resolve_call_return_binding(
10268 analyzer,
10269 file,
10270 function_text,
10271 arity,
10272 lexical_namespace.as_deref(),
10273 direct_type_binding
10274 .as_ref()
10275 .and_then(|binding| binding.unit.as_ref()),
10276 )
10277 {
10278 return Some(binding);
10279 }
10280 let (has_callable, callable_binding) = visibility
10281 .resolve_call_return_binding_without_arity(
10282 analyzer,
10283 file,
10284 function_text,
10285 lexical_namespace.as_deref(),
10286 direct_type_binding
10287 .as_ref()
10288 .and_then(|binding| binding.unit.as_ref()),
10289 );
10290 if let Some(binding) = callable_binding {
10291 return Some(binding);
10292 }
10293 if has_callable {
10294 return None;
10295 }
10296 return direct_type_binding;
10297 }
10298 let arity = visibility.call_arity_evidence(file, node, source).exact();
10303 if let Some(arity) = arity {
10304 let direct_type_binding_for_call = direct_type_binding.clone();
10305 if let Some(binding) = resolve_static_method_call_return_binding(
10306 analyzer, visibility, file, source, function, arity,
10307 )
10308 .or_else(|| {
10309 visibility.resolve_call_return_binding(
10314 analyzer,
10315 file,
10316 function_text,
10317 arity,
10318 enclosing_namespace_context(node, source).as_deref(),
10319 direct_type_binding_for_call
10320 .as_ref()
10321 .and_then(|binding| binding.unit.as_ref()),
10322 )
10323 }) {
10324 return Some(binding);
10325 }
10326 }
10327 direct_type_binding
10328 }),
10329 _ => None,
10330 }
10331}
10332
10333pub fn cpp_new_expression_type_node(expression: Node<'_>) -> Option<Node<'_>> {
10339 debug_assert_eq!(expression.kind(), "new_expression");
10340 if let Some(type_node) = expression.child_by_field_name("type") {
10341 return Some(type_node);
10342 }
10343
10344 let mut pending = Vec::new();
10345 let mut cursor = expression.walk();
10346 pending.extend(
10347 expression
10348 .named_children(&mut cursor)
10349 .filter(|child| {
10350 !matches!(
10351 child.kind(),
10352 "argument_list" | "initializer_list" | "new_declarator"
10353 )
10354 })
10355 .collect::<Vec<_>>()
10356 .into_iter()
10357 .rev(),
10358 );
10359 while let Some(node) = pending.pop() {
10360 if is_cpp_type_specifier_node(node) {
10361 return Some(node);
10362 }
10363 if node.kind() == "ERROR" {
10364 let mut cursor = node.walk();
10365 pending.extend(
10366 node.named_children(&mut cursor)
10367 .collect::<Vec<_>>()
10368 .into_iter()
10369 .rev(),
10370 );
10371 }
10372 }
10373 None
10374}
10375
10376fn is_cpp_type_specifier_node(node: Node<'_>) -> bool {
10377 matches!(
10378 node.kind(),
10379 "class_specifier"
10380 | "decltype"
10381 | "dependent_type"
10382 | "enum_specifier"
10383 | "placeholder_type_specifier"
10384 | "primitive_type"
10385 | "qualified_identifier"
10386 | "sized_type_specifier"
10387 | "struct_specifier"
10388 | "template_type"
10389 | "type_identifier"
10390 | "union_specifier"
10391 )
10392}
10393
10394fn resolve_static_method_call_return_binding(
10395 analyzer: &CppGraphSource<'_>,
10396 visibility: &VisibilityIndex<'_>,
10397 file: &ProjectFile,
10398 source: &str,
10399 function: Node<'_>,
10400 arity: usize,
10401) -> Option<CppScanBinding> {
10402 if function.kind() != "qualified_identifier" {
10403 return None;
10404 }
10405 let qualified = normalize_cpp_reference_text(node_text(function, source));
10406 let parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
10413 brokk_bifrost_core::analyzer::Language::Cpp,
10414 &qualified,
10415 );
10416 let (owner_text, member_name) = match parts.split_last() {
10417 Some((member, owner_parts)) if !owner_parts.is_empty() => {
10418 (owner_parts.join("::"), member.clone())
10419 }
10420 _ => {
10421 let scope = function.child_by_field_name("scope")?;
10422 let name = function.child_by_field_name("name")?;
10423 (
10424 node_text(scope, source).to_string(),
10425 node_text(name, source).to_string(),
10426 )
10427 }
10428 };
10429 let owner = visibility.resolve_type(file, &owner_text)?;
10430 let candidates =
10431 callable_members_accepting_arity(analyzer, visibility, file, &owner, &member_name, arity);
10432 unanimous_return_binding(analyzer, visibility, file, &candidates)
10433}
10434
10435fn resolve_field_method_call_return_binding(
10436 analyzer: &CppGraphSource<'_>,
10437 visibility: &VisibilityIndex<'_>,
10438 file: &ProjectFile,
10439 source: &str,
10440 function: Node<'_>,
10441 arity: usize,
10442 receiver_resolver: Option<&ReceiverResolver<'_>>,
10443) -> Option<CppScanBinding> {
10444 debug_assert_eq!(
10445 function.kind(),
10446 "field_expression",
10447 "the member-call return binding answers only for a field-expression callee"
10448 );
10449 let receiver_resolver = receiver_resolver?;
10450 let field = function.child_by_field_name("field")?;
10451 let member_name = node_text(function_terminal_node(field), source);
10452 let receiver = function
10453 .child_by_field_name("argument")
10454 .or_else(|| function.named_child(0))?;
10455 let owners = receiver_resolver(receiver, source);
10456 let mut candidates = Vec::new();
10457 for owner in owners {
10458 let declaring_owner =
10459 match resolve_declaring_member_owner(analyzer, visibility, file, &owner, member_name) {
10460 EnclosingMemberOwnerResolution::Owner(owner) => owner,
10461 EnclosingMemberOwnerResolution::Missing => continue,
10462 EnclosingMemberOwnerResolution::Ambiguous => return None,
10463 };
10464 candidates.extend(callable_members_accepting_arity(
10465 analyzer,
10466 visibility,
10467 file,
10468 &declaring_owner,
10469 member_name,
10470 arity,
10471 ));
10472 }
10473 unanimous_return_binding(analyzer, visibility, file, &candidates)
10474}
10475
10476fn callable_members_accepting_arity(
10477 analyzer: &CppGraphSource<'_>,
10478 visibility: &VisibilityIndex<'_>,
10479 file: &ProjectFile,
10480 owner: &CodeUnit,
10481 member_name: &str,
10482 arity: usize,
10483) -> Vec<CodeUnit> {
10484 visibility
10485 .visible_members_for_owner_name(file, owner, member_name)
10486 .into_iter()
10487 .filter(|unit| {
10488 unit.is_function()
10489 && cpp_callable_arity(analyzer, unit).is_none_or(|known| known.accepts(arity))
10490 })
10491 .cloned()
10492 .collect()
10493}
10494
10495fn unanimous_return_binding(
10496 analyzer: &CppGraphSource<'_>,
10497 visibility: &VisibilityIndex<'_>,
10498 file: &ProjectFile,
10499 candidates: &[CodeUnit],
10500) -> Option<CppScanBinding> {
10501 let mut resolved_return: Option<CppScanBinding> = None;
10502 for function in candidates {
10503 let metadata = analyzer.signature_metadata(function);
10504 let return_types = if metadata.is_empty() {
10505 vec![cpp_function_return_type_text(analyzer, function)?]
10506 } else {
10507 metadata
10508 .iter()
10509 .map(|metadata| metadata.return_type_text().map(str::to_string))
10510 .collect::<Option<Vec<_>>>()?
10511 };
10512 for return_text in return_types {
10513 let indirection = crate::call_match::cpp_type_text_pointer_depth(&return_text);
10514 let name = normalize_cpp_type_name(&return_text);
10515 let binding = CppScanBinding::from_type_name(
10516 name.clone(),
10517 visibility
10518 .resolve_unique_canonical_type_for_declaration(analyzer, file, function, &name),
10519 indirection,
10520 );
10521 if let Some(existing) = resolved_return.as_ref()
10522 && (existing.indirection != binding.indirection
10523 || match (&existing.unit, &binding.unit) {
10524 (Some(left), Some(right)) => !same_visible_symbol(left, right),
10525 (None, None) => existing.type_name != binding.type_name,
10526 (Some(_), None) | (None, Some(_)) => true,
10527 })
10528 {
10529 return None;
10530 }
10531 resolved_return = Some(binding);
10532 }
10533 }
10534 resolved_return
10535}
10536
10537fn aliases_from_canonical_source(
10538 cpp: &dyn CppSource,
10539 token: QueryToken<'_>,
10540 file: &ProjectFile,
10541) -> Option<Vec<CppAlias>> {
10542 let source = cpp.declaration_source_facts(token, file)?;
10543 Some(
10544 source
10545 .facts
10546 .declarations
10547 .iter()
10548 .filter_map(|fact| {
10549 let alias = fact.file_scope_alias.as_ref()?;
10550 Some(CppAlias {
10551 name: alias.name.clone(),
10552 target: alias.target.clone(),
10553 namespace: alias.namespace.clone(),
10554 })
10555 })
10556 .collect(),
10557 )
10558}
10559
10560pub(crate) fn alias_has_visible_file_scope(node: Node<'_>) -> bool {
10561 let mut current = node.parent();
10562 while let Some(parent) = current {
10563 match parent.kind() {
10564 "translation_unit"
10565 | "namespace_definition"
10566 | "declaration_list"
10567 | "linkage_specification" => current = parent.parent(),
10568 "template_declaration" => current = parent.parent(),
10569 _ => return false,
10570 }
10571 }
10572 true
10573}
10574
10575pub fn collect_include_closure(
10576 analyzer: &CppGraphSource<'_>,
10577 include_targets: &IncludeTargetIndex,
10578 file: &ProjectFile,
10579 out: &mut HashSet<ProjectFile>,
10580 cancellation: Option<&CancellationToken>,
10581) {
10582 let mut stack = vec![file.clone()];
10583 while let Some(file) = stack.pop() {
10584 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10585 break;
10586 }
10587 if !out.insert(file.clone()) {
10588 continue;
10589 }
10590 for include in analyzer.canonical_include_paths(&file).unwrap_or_default() {
10591 for target in resolve_include_targets_with_index(&file, &include, include_targets) {
10592 stack.push(target);
10593 }
10594 }
10595 }
10596}
10597
10598fn collect_visible_declarations(
10599 include_graph: &IncludeGraph,
10600 declarations_by_file: &HashMap<ProjectFile, BTreeSet<CodeUnit>>,
10601 file: &ProjectFile,
10602 visited: &mut HashSet<ProjectFile>,
10603 out: &mut HashSet<CodeUnit>,
10604 cancellation: Option<&CancellationToken>,
10605) {
10606 let mut stack = vec![file.clone()];
10607 while let Some(file) = stack.pop() {
10608 if cancellation.is_some_and(CancellationToken::is_cancelled) {
10609 break;
10610 }
10611 if !visited.insert(file.clone()) {
10612 continue;
10613 }
10614 if let Some(declarations) = declarations_by_file.get(&file) {
10615 out.extend(declarations.iter().cloned());
10616 }
10617 stack.extend(include_graph.targets(&file).iter().cloned());
10618 }
10619}
10620
10621fn parse_macro_parameter_list<T>(
10622 replacement: &str,
10623 read: impl FnOnce(Node<'_>, &str) -> T,
10624) -> Option<T> {
10625 let source = format!("void __bifrost_macro_parameters({replacement});");
10626 let mut parser = Parser::new();
10627 parser
10628 .set_language(&tree_sitter_cpp::LANGUAGE.into())
10629 .ok()?;
10630 let tree = parser.parse(&source, None)?;
10631 let root = tree.root_node();
10632 if root.has_error() {
10633 return None;
10634 }
10635 let declaration = root.named_child(0)?;
10636 let declarator = declaration.child_by_field_name("declarator")?;
10637 let parameters = declarator.child_by_field_name("parameters")?;
10638 Some(read(parameters, &source))
10639}
10640
10641fn parse_macro_parameter_list_arity(replacement: &str) -> Option<CallableArity> {
10642 parse_macro_parameter_list(replacement, crate::declarations::cpp_callable_arity)
10643}
10644
10645pub fn cpp_macro_parameter_types(replacement: &str) -> Option<Vec<String>> {
10648 parse_macro_parameter_list(
10649 replacement,
10650 crate::declarations::cpp_callable_parameter_types,
10651 )
10652}
10653
10654pub fn cpp_callable_arity(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> Option<CallableArity> {
10655 brokk_bifrost_core::analyzer::model::first_callable_arity(analyzer.signature_metadata(unit))
10656}
10657
10658pub fn cpp_callable_parameter_types(
10659 analyzer: &CppGraphSource<'_>,
10660 unit: &CodeUnit,
10661) -> Option<Vec<String>> {
10662 brokk_bifrost_core::analyzer::model::first_callable_parameter_types(
10663 analyzer.signature_metadata(unit),
10664 )
10665}
10666
10667fn merge_compatible_callable_arities(
10668 left: CallableArity,
10669 right: CallableArity,
10670) -> Option<CallableArity> {
10671 let total = left.total();
10672 let left_repeated = left.accepts(total.saturating_add(1));
10673 let right_repeated = right.accepts(right.total().saturating_add(1));
10674 if total != right.total() || left_repeated != right_repeated {
10675 return None;
10676 }
10677 let required = (0..=total).find(|arity| left.accepts(*arity) || right.accepts(*arity))?;
10678 Some(CallableArity::new(required, total, left_repeated))
10679}
10680
10681fn collect_macro_events(
10682 cpp: &dyn CppSource,
10683 token: QueryToken<'_>,
10684 file: &ProjectFile,
10685) -> Vec<MacroEvent> {
10686 let Some(prepared) = cpp.prepared_syntax(token, file) else {
10687 return Vec::new();
10688 };
10689 let source = prepared.source();
10690 let mut events = Vec::new();
10691 let root = prepared.tree().root_node();
10692 let mut stack = vec![root];
10693 while let Some(node) = stack.pop() {
10694 match node.kind() {
10695 "preproc_def" | "preproc_function_def" => {
10696 let Some(name) = node.child_by_field_name("name") else {
10697 continue;
10698 };
10699 let name = node_text(name, source).to_string();
10700 events.push(MacroEvent::Define {
10701 name,
10702 binding: MacroBinding {
10703 source: file.clone(),
10704 declaration_byte: node.start_byte(),
10705 definition: VisibilityIndex::decode_macro_definition(node, source),
10706 exact: true,
10707 },
10708 byte: node.start_byte(),
10709 conditionals: owning_preprocessor_conditionals(root, node, source),
10710 });
10711 continue;
10712 }
10713 "preproc_include" => {
10714 let Some(path) = node.child_by_field_name("path") else {
10715 events.push(MacroEvent::Include {
10716 targets: Vec::new(),
10717 byte: node.start_byte(),
10718 conditionals: owning_preprocessor_conditionals(root, node, source),
10719 });
10720 continue;
10721 };
10722 let include = crate::imports::include_path_literal_value(path, source);
10723 let targets = include.map_or_else(Vec::new, |include| {
10724 resolve_include_targets_with_index(file, include, cpp.include_target_index())
10725 });
10726 if targets.is_empty()
10737 && include.is_some_and(|include| {
10738 !cpp.include_target_index().names_indexed_file(include)
10739 })
10740 {
10741 continue;
10742 }
10743 events.push(MacroEvent::Include {
10744 targets,
10745 byte: node.start_byte(),
10746 conditionals: owning_preprocessor_conditionals(root, node, source),
10747 });
10748 continue;
10749 }
10750 "preproc_call" => {
10751 let Some(directive) = node.child_by_field_name("directive") else {
10752 continue;
10753 };
10754 if node_text(directive, source) != "#undef" {
10755 continue;
10756 }
10757 let name = node
10758 .child_by_field_name("argument")
10759 .and_then(|argument| parse_preproc_identifier(node_text(argument, source)));
10760 if let Some(name) = name {
10761 events.push(MacroEvent::Undef {
10762 name,
10763 byte: node.start_byte(),
10764 conditionals: owning_preprocessor_conditionals(root, node, source),
10765 });
10766 } else {
10767 events.push(MacroEvent::Invalidate {
10768 byte: node.start_byte(),
10769 });
10770 }
10771 continue;
10772 }
10773 _ => {}
10774 }
10775 push_named_children_reversed(node, &mut stack);
10776 }
10777 events.sort_by_key(MacroEvent::byte);
10778 events
10779}
10780
10781fn find_include_activation(
10782 cpp: &dyn CppSource,
10783 token: QueryToken<'_>,
10784 file: &ProjectFile,
10785 prepared: &PreparedSyntaxTree,
10786 donor_source: &ProjectFile,
10787 language_guard_is_stable: &dyn Fn() -> bool,
10788) -> Option<usize> {
10789 let include_targets = cpp.include_target_index();
10790 let reference_is_c = file
10791 .rel_path()
10792 .extension()
10793 .and_then(|extension| extension.to_str())
10794 == Some("c");
10795 let mut direct_includes = Vec::new();
10796 let mut nodes = vec![prepared.tree().root_node()];
10797 while let Some(node) = nodes.pop() {
10800 if node.kind() == "preproc_include" {
10801 let Some(required_guards) = include_directive_guard_requirements(
10802 node,
10803 prepared.source(),
10804 reference_is_c,
10805 language_guard_is_stable,
10806 ) else {
10807 continue;
10808 };
10809 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
10810 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10811 if let Some(target) = unique_include_target(resolve_include_targets_with_index(
10812 file,
10813 include,
10814 include_targets,
10815 )) {
10816 direct_includes.push((node.end_byte(), target));
10817 }
10818 }
10819 }
10820 continue;
10821 }
10822 push_named_children_reversed(node, &mut nodes);
10823 }
10824 direct_includes.sort_by_key(|(activation, _)| *activation);
10825 let mut known_missing = HashSet::default();
10826 direct_includes
10827 .into_iter()
10828 .find(|(_, direct)| {
10829 unconditional_include_reaches(
10830 cpp,
10831 token,
10832 direct,
10833 donor_source,
10834 file,
10835 &mut known_missing,
10836 language_guard_is_stable,
10837 )
10838 })
10839 .map(|(activation, _)| activation)
10840}
10841
10842fn find_conditional_include_projection_index(
10843 cpp: &dyn CppSource,
10844 token: QueryToken<'_>,
10845 file: &ProjectFile,
10846 prepared: &PreparedSyntaxTree,
10847 language_guard_is_stable: &dyn Fn() -> bool,
10848 on_state: &dyn Fn(),
10849) -> ConditionalIncludeProjectionIndex {
10850 let reference_is_c = reference_uses_c_semantics(cpp, file);
10851 let include_targets = cpp.include_target_index();
10852 let mut projections_by_source: HashMap<ProjectFile, Vec<ConditionalIncludeProjection>> =
10853 HashMap::default();
10854 let mut pending = Vec::new();
10855 let mut nodes = vec![prepared.tree().root_node()];
10856 while let Some(node) = nodes.pop() {
10857 if node.kind() == "preproc_include" {
10858 let Some(required_guards) = include_directive_guard_requirements(
10859 node,
10860 prepared.source(),
10861 reference_is_c,
10862 language_guard_is_stable,
10863 ) else {
10864 continue;
10865 };
10866 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
10867 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10868 file,
10869 include,
10870 include_targets,
10871 )) else {
10872 continue;
10873 };
10874 pending.push((target, node.end_byte(), required_guards.clone()));
10875 }
10876 continue;
10877 }
10878 push_named_children_reversed(node, &mut nodes);
10879 }
10880
10881 let mut expanded: HashMap<(ProjectFile, usize), Vec<HashSet<PreprocessorGuard>>> =
10893 HashMap::default();
10894 while let Some((current_file, activation_byte, path)) = pending.pop() {
10895 let guard_sets = expanded
10896 .entry((current_file.clone(), activation_byte))
10897 .or_default();
10898 if guard_sets
10902 .iter()
10903 .any(|existing| existing.is_subset(&path.all))
10904 {
10905 continue;
10906 }
10907 let (evicted, kept): (Vec<_>, Vec<_>) = guard_sets
10908 .drain(..)
10909 .partition(|existing| path.all.is_subset(existing));
10910 *guard_sets = kept;
10911 guard_sets.push(path.all.clone());
10912 if !evicted.is_empty()
10913 && let Some(projections) = projections_by_source.get_mut(¤t_file)
10914 {
10915 projections.retain(|projection| {
10916 projection.activation_byte != activation_byte
10917 || !evicted.contains(&projection.required_guards)
10918 });
10919 }
10920 on_state();
10921
10922 projections_by_source
10925 .entry(current_file.clone())
10926 .or_default()
10927 .push(ConditionalIncludeProjection {
10928 activation_byte,
10929 required_guards: path.all.clone(),
10930 partial_guards: path.partial.clone(),
10931 });
10932
10933 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
10934 continue;
10935 };
10936 let mut nodes = vec![current_prepared.tree().root_node()];
10937 while let Some(node) = nodes.pop() {
10938 if node.kind() == "preproc_include" {
10939 let Some(include_guards) = include_directive_guard_requirements(
10940 node,
10941 current_prepared.source(),
10942 reference_is_c,
10943 language_guard_is_stable,
10944 ) else {
10945 continue;
10946 };
10947 let Some(reached) = path.merged(&include_guards) else {
10948 continue;
10949 };
10950 for (include, _) in
10951 include_path_from_node(node, current_prepared.source()).into_iter()
10952 {
10953 let Some(target) = unique_include_target(resolve_include_targets_with_index(
10954 ¤t_file,
10955 include,
10956 include_targets,
10957 )) else {
10958 continue;
10959 };
10960 pending.push((target, activation_byte, reached.clone()));
10961 }
10962 continue;
10963 }
10964 push_named_children_reversed(node, &mut nodes);
10965 }
10966 }
10967
10968 projections_by_source
10969 .into_iter()
10970 .map(|(source, mut projections)| {
10971 projections.sort_by_key(|projection| projection.activation_byte);
10972 (source, Arc::from(projections))
10973 })
10974 .collect()
10975}
10976
10977#[allow(clippy::too_many_arguments)]
10983fn find_conditional_include_projection_for_source(
10984 cpp: &dyn CppSource,
10985 token: QueryToken<'_>,
10986 file: &ProjectFile,
10987 prepared: &PreparedSyntaxTree,
10988 donor_source: &ProjectFile,
10989 admission: IncludePathAdmission,
10990 reference_guards: Option<&HashSet<PreprocessorGuard>>,
10991 reference_byte: usize,
10992 language_guard_is_stable: &dyn Fn() -> bool,
10993 on_state: &dyn Fn(),
10994) -> bool {
10995 let Some(reference_guards) = reference_guards else {
10996 return false;
10997 };
10998 let reference_is_c = reference_uses_c_semantics(cpp, file);
10999 let include_targets = cpp.include_target_index();
11000 let mut pending = Vec::new();
11001 let mut nodes = vec![prepared.tree().root_node()];
11002 while let Some(node) = nodes.pop() {
11003 if node.kind() == "preproc_include" {
11004 let Some(required_guards) = include_directive_guard_requirements(
11005 node,
11006 prepared.source(),
11007 reference_is_c,
11008 language_guard_is_stable,
11009 ) else {
11010 continue;
11011 };
11012 if node.end_byte() > reference_byte
11013 || !admission.admits(
11014 &required_guards.all,
11015 &required_guards.partial,
11016 Some(reference_guards),
11017 )
11018 {
11019 continue;
11020 }
11021 for (include, _) in include_path_from_node(node, prepared.source()).into_iter() {
11022 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11023 file,
11024 include,
11025 include_targets,
11026 )) else {
11027 continue;
11028 };
11029 if &target == donor_source {
11030 return true;
11031 }
11032 pending.push((target, required_guards.clone()));
11033 }
11034 continue;
11035 }
11036 push_named_children_reversed(node, &mut nodes);
11037 }
11038
11039 let mut expanded: HashMap<ProjectFile, Vec<HashSet<PreprocessorGuard>>> = HashMap::default();
11040 while let Some((current_file, path)) = pending.pop() {
11041 let guard_sets = expanded.entry(current_file.clone()).or_default();
11042 if guard_sets.contains(&path.all) {
11043 continue;
11044 }
11045 guard_sets.push(path.all.clone());
11046 on_state();
11047
11048 let Some(current_prepared) = cpp.prepared_syntax(token, ¤t_file) else {
11049 continue;
11050 };
11051 let mut nodes = vec![current_prepared.tree().root_node()];
11052 while let Some(node) = nodes.pop() {
11053 if node.kind() == "preproc_include" {
11054 let Some(include_guards) = include_directive_guard_requirements(
11055 node,
11056 current_prepared.source(),
11057 reference_is_c,
11058 language_guard_is_stable,
11059 ) else {
11060 continue;
11061 };
11062 let Some(reached) = path.merged(&include_guards) else {
11063 continue;
11064 };
11065 if !admission.admits(&reached.all, &reached.partial, Some(reference_guards)) {
11066 continue;
11067 }
11068 for (include, _) in
11069 include_path_from_node(node, current_prepared.source()).into_iter()
11070 {
11071 let Some(target) = unique_include_target(resolve_include_targets_with_index(
11072 ¤t_file,
11073 include,
11074 include_targets,
11075 )) else {
11076 continue;
11077 };
11078 if &target == donor_source {
11079 return true;
11080 }
11081 pending.push((target, reached.clone()));
11082 }
11083 continue;
11084 }
11085 push_named_children_reversed(node, &mut nodes);
11086 }
11087 }
11088 false
11089}
11090
11091pub fn cpp_include_closure_reaches(
11104 cpp: &dyn CppSource,
11105 token: QueryToken<'_>,
11106 translation_unit: &ProjectFile,
11107 header: &ProjectFile,
11108) -> bool {
11109 let language_guard_stable = OnceCell::new();
11110 unconditional_include_reaches(
11111 cpp,
11112 token,
11113 translation_unit,
11114 header,
11115 translation_unit,
11116 &mut HashSet::default(),
11117 &|| {
11118 *language_guard_stable.get_or_init(|| {
11119 let mut macro_events =
11120 |file: &ProjectFile| collect_macro_events(cpp, token, file).into_boxed_slice();
11121 include_language_guard_is_stable_with(
11122 cpp,
11123 token,
11124 translation_unit,
11125 &mut macro_events,
11126 &mut |_: &ProjectFile, prepared: &PreparedSyntaxTree| {
11127 collect_structured_include_facts(prepared)
11128 },
11129 )
11130 })
11131 },
11132 )
11133}
11134
11135fn unconditional_include_reaches(
11136 cpp: &dyn CppSource,
11137 token: QueryToken<'_>,
11138 first: &ProjectFile,
11139 donor_source: &ProjectFile,
11140 reference_file: &ProjectFile,
11141 known_missing: &mut HashSet<ProjectFile>,
11142 language_guard_is_stable: &dyn Fn() -> bool,
11143) -> bool {
11144 if first == donor_source {
11145 return true;
11146 }
11147 if known_missing.contains(first) {
11148 return false;
11149 }
11150 let reference_is_c = reference_file
11151 .rel_path()
11152 .extension()
11153 .and_then(|extension| extension.to_str())
11154 == Some("c");
11155 if let Some(reaches) =
11156 cpp.cached_unconditional_include_reachability(first, donor_source, reference_file)
11157 {
11158 return reaches;
11159 }
11160 let mut visited = HashSet::default();
11161 let mut files = vec![first.clone()];
11162 let include_targets = cpp.include_target_index();
11163 while let Some(file) = files.pop() {
11164 if file == *donor_source {
11165 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, true);
11166 return true;
11167 }
11168 if known_missing.contains(&file) || !visited.insert(file.clone()) {
11169 continue;
11170 }
11171 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11172 continue;
11173 };
11174 let mut cursor = prepared.tree().walk();
11176 let mut nodes = vec![prepared.tree().root_node()];
11177 while let Some(node) = nodes.pop() {
11178 if node.kind() == "preproc_include" {
11179 let Some(required_guards) = include_directive_guard_requirements(
11180 node,
11181 prepared.source(),
11182 reference_is_c,
11183 language_guard_is_stable,
11184 ) else {
11185 continue;
11186 };
11187 if required_guards.all.is_empty() && required_guards.partial.is_empty() {
11188 for (include, _) in include_path_from_node(node, prepared.source()).into_iter()
11189 {
11190 if let Some(target) = unique_include_target(
11191 resolve_include_targets_with_index(&file, include, include_targets),
11192 ) {
11193 files.push(target);
11194 }
11195 }
11196 }
11197 continue;
11198 }
11199 let first_pushed = nodes.len();
11200 nodes.extend(node.named_children(&mut cursor));
11201 nodes[first_pushed..].reverse();
11202 }
11203 }
11204 known_missing.extend(visited);
11205 cpp.cache_unconditional_include_reachability(first, donor_source, reference_file, false);
11206 false
11207}
11208
11209fn include_language_guard_is_stable_with<MacroEvents, IncludeFacts>(
11210 cpp: &dyn CppSource,
11211 token: QueryToken<'_>,
11212 file: &ProjectFile,
11213 mut macro_events: MacroEvents,
11214 mut include_facts: IncludeFacts,
11215) -> bool
11216where
11217 MacroEvents: FnMut(&ProjectFile) -> Box<[MacroEvent]>,
11218 IncludeFacts: FnMut(&ProjectFile, &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]>,
11219{
11220 if !crate::imports::is_cpp_translation_unit(file) {
11224 return false;
11225 }
11226 if !cpp.compile_contexts_for(file).is_empty()
11229 || cpp
11230 .reaching_translation_units(file)
11231 .iter()
11232 .any(|tu| !cpp.compile_contexts_for(tu).is_empty())
11233 {
11234 return false;
11235 }
11236 let mut pending = vec![file.clone()];
11237 let mut visited = HashSet::default();
11238 while let Some(file) = pending.pop() {
11239 if !visited.insert(file.clone()) {
11240 continue;
11241 }
11242 let Some(prepared) = cpp.prepared_syntax(token, &file) else {
11243 return false;
11244 };
11245 if macro_events(&file).iter().any(|event| match event {
11246 MacroEvent::Define { name, .. } | MacroEvent::Undef { name, .. } => {
11247 name == "__cplusplus"
11248 }
11249 MacroEvent::Invalidate { .. } => true,
11250 MacroEvent::Include { targets, .. } => targets.is_empty(),
11251 }) {
11252 return false;
11253 }
11254 for fact in include_facts(&file, prepared.as_ref()).iter() {
11257 let targets =
11258 resolve_include_targets_with_index(&file, &fact.path, cpp.include_target_index());
11259 if targets.is_empty() {
11260 return false;
11261 }
11262 pending.extend(targets);
11263 }
11264 }
11265 true
11266}
11267
11268fn declaration_guard_requirements(
11269 analyzer: &CppGraphSource<'_>,
11270 cpp: &dyn CppSource,
11271 candidate: &CodeUnit,
11272) -> Vec<(usize, HashSet<PreprocessorGuard>)> {
11273 declaration_guard_sites(analyzer, cpp, candidate, None)
11274 .into_iter()
11275 .map(|site| (site.byte, site.guards))
11276 .collect()
11277}
11278
11279struct DeclarationGuardSite {
11282 byte: usize,
11284 guards: HashSet<PreprocessorGuard>,
11286 completed_family_before_reference: bool,
11290}
11291
11292fn declaration_guard_sites(
11295 analyzer: &CppGraphSource<'_>,
11296 cpp: &dyn CppSource,
11297 candidate: &CodeUnit,
11298 reference_byte: Option<usize>,
11299) -> Vec<DeclarationGuardSite> {
11300 let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source()) else {
11301 return Vec::new();
11302 };
11303 let root = prepared.tree().root_node();
11304 analyzer
11305 .declaration_source_occurrences(candidate)
11306 .into_iter()
11307 .flatten()
11308 .filter_map(|occurrence| {
11309 let guards = cpp_guard_set_to_runtime(occurrence.fact.guard_requirements.as_ref()?)?;
11310 Some(DeclarationGuardSite {
11311 byte: occurrence.range.start_byte,
11312 guards,
11313 completed_family_before_reference: reference_byte.is_some_and(|reference_byte| {
11314 root.descendant_for_byte_range(
11315 occurrence.range.start_byte,
11316 occurrence.range.end_byte,
11317 )
11318 .is_some_and(|node| {
11319 declaration_follows_completed_family_before_reference(
11320 node,
11321 prepared.source(),
11322 reference_byte,
11323 )
11324 })
11325 }),
11326 })
11327 })
11328 .collect()
11329}
11330
11331fn declaration_follows_completed_family_before_reference(
11350 node: Node<'_>,
11351 source: &str,
11352 reference_byte: usize,
11353) -> bool {
11354 callable_guard_completion_byte(node, source).is_some_and(|end| end > 0 && end <= reference_byte)
11355}
11356
11357pub(crate) fn callable_guard_completion_byte(node: Node<'_>, source: &str) -> Option<usize> {
11360 let mut cache = PreprocessorContextCache::default();
11361 callable_guard_completion_byte_with_cache(node, source, &mut cache)
11362}
11363
11364pub(crate) fn callable_guard_completion_byte_with_cache(
11365 node: Node<'_>,
11366 source: &str,
11367 cache: &mut PreprocessorContextCache,
11368) -> Option<usize> {
11369 let mut completion_byte = 0;
11370 let mut ancestor = node.parent();
11371 while let Some(conditional) = ancestor {
11372 if matches!(
11373 conditional.kind(),
11374 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11375 ) && !is_file_covering_include_guard(conditional, source)
11376 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11377 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11378 {
11379 let family = preprocessor_conditional_family_root(conditional);
11380 if !preprocessor_conditional_family_has_terminal_else(family) {
11381 return None;
11382 }
11383 completion_byte = completion_byte.max(family.end_byte());
11384 }
11385 ancestor = conditional.parent();
11386 }
11387 Some(completion_byte)
11388}
11389
11390fn first_declaration_byte(analyzer: &CppGraphSource<'_>, candidate: &CodeUnit) -> Option<usize> {
11391 analyzer
11392 .ranges(candidate)
11393 .into_iter()
11394 .map(|range| range.start_byte)
11395 .min()
11396}
11397
11398fn context_fact_names(contexts: &[CppCompileContext]) -> Option<HashSet<String>> {
11404 let (first, rest) = contexts.split_first()?;
11405 Some(
11406 first
11407 .defined_macros
11408 .iter()
11409 .filter(|name| {
11410 rest.iter()
11411 .all(|context| context.defined_macros.contains(*name))
11412 })
11413 .cloned()
11414 .collect(),
11415 )
11416}
11417
11418pub fn guard_requirements_hold_at_reference(
11419 required: &HashSet<PreprocessorGuard>,
11420 reference: Option<&HashSet<PreprocessorGuard>>,
11421) -> bool {
11422 reference.is_some_and(|active| {
11423 required
11424 .iter()
11425 .all(|guard| preprocessor_guard_holds_at_reference(guard, active))
11426 })
11427}
11428
11429fn preprocessor_guard_holds_at_reference(
11430 required: &PreprocessorGuard,
11431 active: &HashSet<PreprocessorGuard>,
11432) -> bool {
11433 if active.contains(required) {
11434 return true;
11435 }
11436 let active_expression = BooleanGuardExpression::all(
11437 active
11438 .iter()
11439 .filter_map(PreprocessorGuard::as_boolean_expression),
11440 );
11441 required
11442 .as_boolean_expression()
11443 .is_some_and(|required| active_expression.implies(&required))
11444}
11445
11446fn guards_compatible_at_reference(
11451 declaration: &HashSet<PreprocessorGuard>,
11452 reference: Option<&HashSet<PreprocessorGuard>>,
11453) -> bool {
11454 reference.is_some_and(|active| merge_preprocessor_guards(declaration, active).is_some())
11455}
11456
11457pub fn preprocessor_conditional_family_range(
11466 root: Node<'_>,
11467 start_byte: usize,
11468 end_byte: usize,
11469) -> Option<(usize, usize)> {
11470 let node = root.descendant_for_byte_range(start_byte, end_byte)?;
11471 preprocessor_conditional_family_node(node)
11472 .map(|family| (family.start_byte(), family.end_byte()))
11473}
11474
11475pub(crate) fn preprocessor_conditional_family_node(node: Node<'_>) -> Option<Node<'_>> {
11476 let mut ancestor = Some(node);
11477 while let Some(current) = ancestor {
11478 if is_cpp_preprocessor_conditional(current)
11479 && preprocessor_conditional_contains_descendant(current, node)
11480 {
11481 return Some(preprocessor_conditional_family_root(current));
11482 }
11483 ancestor = current.parent();
11484 }
11485 None
11486}
11487
11488pub(crate) fn preprocessor_conditional_family_for_declaration_with_cache<'tree>(
11489 node: Node<'tree>,
11490 cache: &mut PreprocessorContextCache,
11491) -> Option<Node<'tree>> {
11492 let mut ancestor = node.parent();
11493 while let Some(current) = ancestor {
11494 if is_cpp_preprocessor_conditional(current)
11495 && preprocessor_conditional_contains_descendant_with_cache(current, node, cache)
11496 {
11497 let family = preprocessor_conditional_family_root(current);
11498 if preprocessor_conditional_family_has_terminal_else(family) {
11499 return Some(family);
11500 }
11501 }
11502 ancestor = current.parent();
11503 }
11504 None
11505}
11506
11507fn preprocessor_conditional_family_root(mut conditional: Node<'_>) -> Node<'_> {
11508 while let Some(parent) = conditional.parent() {
11509 let is_alternative = parent
11510 .child_by_field_name("alternative")
11511 .is_some_and(|alternative| {
11512 alternative.start_byte() == conditional.start_byte()
11513 && alternative.end_byte() == conditional.end_byte()
11514 });
11515 if !is_alternative {
11516 break;
11517 }
11518 conditional = parent;
11519 }
11520 conditional
11521}
11522
11523fn preprocessor_conditional_family_has_terminal_else(mut conditional: Node<'_>) -> bool {
11524 loop {
11525 let Some(alternative) = conditional.child_by_field_name("alternative") else {
11526 return false;
11527 };
11528 match alternative.kind() {
11529 "preproc_else" => return true,
11530 "preproc_elif" => conditional = alternative,
11531 _ => return false,
11532 }
11533 }
11534}
11535
11536fn include_directive_guard_requirements(
11549 node: Node<'_>,
11550 source: &str,
11551 reference_is_c: bool,
11552 language_guard_is_stable: &dyn Fn() -> bool,
11553) -> Option<PreprocessorGuardEnvironment> {
11554 let mut required = preprocessor_guard_environment_by_family(node, source)?;
11555 let excluded_by_language = required
11556 .all
11557 .iter()
11558 .any(|guard| preprocessor_guard_excludes_language(guard, reference_is_c));
11559 if excluded_by_language {
11560 return None;
11561 }
11562 let is_language_guard = |guard: &PreprocessorGuard| {
11563 matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
11564 if name == "__cplusplus")
11565 };
11566 if required.all.iter().any(is_language_guard) && language_guard_is_stable() {
11569 for guards in [&mut required.all, &mut required.partial] {
11570 guards.retain(|guard| !is_language_guard(guard));
11571 }
11572 }
11573 Some(required)
11574}
11575
11576fn preprocessor_guard_excludes_language(guard: &PreprocessorGuard, reference_is_c: bool) -> bool {
11577 match guard {
11578 PreprocessorGuard::Defined(name) => reference_is_c && name == "__cplusplus",
11579 PreprocessorGuard::Undefined(name) => !reference_is_c && name == "__cplusplus",
11580 _ => false,
11581 }
11582}
11583
11584pub fn preprocessor_guard_environment(
11585 node: Node<'_>,
11586 source: &str,
11587) -> Option<HashSet<PreprocessorGuard>> {
11588 let mut cache = PreprocessorContextCache::default();
11589 preprocessor_guard_environment_with_cache(node, source, &mut cache)
11590}
11591
11592pub(crate) fn preprocessor_guard_environment_with_cache(
11593 node: Node<'_>,
11594 source: &str,
11595 cache: &mut PreprocessorContextCache,
11596) -> Option<HashSet<PreprocessorGuard>> {
11597 preprocessor_guard_environment_by_family_with_cache(node, source, cache)
11598 .map(|environment| environment.all)
11599}
11600
11601#[derive(Clone)]
11612struct PreprocessorGuardEnvironment {
11613 all: HashSet<PreprocessorGuard>,
11614 partial: HashSet<PreprocessorGuard>,
11615}
11616
11617impl PreprocessorGuardEnvironment {
11618 fn merged(&self, other: &Self) -> Option<Self> {
11622 Some(Self {
11623 all: merge_preprocessor_guards(&self.all, &other.all)?,
11624 partial: self.partial.union(&other.partial).cloned().collect(),
11625 })
11626 }
11627}
11628
11629fn preprocessor_guard_environment_by_family(
11630 node: Node<'_>,
11631 source: &str,
11632) -> Option<PreprocessorGuardEnvironment> {
11633 let mut cache = PreprocessorContextCache::default();
11634 preprocessor_guard_environment_by_family_with_cache(node, source, &mut cache)
11635}
11636
11637fn preprocessor_guard_environment_by_family_with_cache(
11638 node: Node<'_>,
11639 source: &str,
11640 cache: &mut PreprocessorContextCache,
11641) -> Option<PreprocessorGuardEnvironment> {
11642 let mut all = HashSet::default();
11643 let mut partial = HashSet::default();
11644 let mut ancestor = node.parent();
11645 while let Some(conditional) = ancestor {
11646 if matches!(
11647 conditional.kind(),
11648 "preproc_if" | "preproc_ifdef" | "preproc_elif"
11649 ) && !is_file_covering_include_guard(conditional, source)
11650 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11651 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11652 {
11653 let guard = preprocessor_guard_for_descendant(conditional, node, source)?;
11654 match guard {
11655 PreprocessorGuard::Constant(true) => {
11656 ancestor = conditional.parent();
11657 continue;
11658 }
11659 PreprocessorGuard::Constant(false) => return None,
11660 _ => {}
11661 }
11662 if all.contains(&guard.negated()) {
11663 return None;
11664 }
11665 if !preprocessor_conditional_family_has_terminal_else(
11666 preprocessor_conditional_family_root(conditional),
11667 ) {
11668 partial.insert(guard.clone());
11669 }
11670 all.insert(guard);
11671 }
11672 ancestor = conditional.parent();
11673 }
11674 if let Some(guard) = fragmented_statement_preprocessor_guard(node, source) {
11675 match guard {
11676 PreprocessorGuard::Constant(true) => {}
11677 PreprocessorGuard::Constant(false) => return None,
11678 _ => {
11679 if all.contains(&guard.negated()) {
11680 return None;
11681 }
11682 partial.insert(guard.clone());
11686 all.insert(guard);
11687 }
11688 }
11689 }
11690 Some(PreprocessorGuardEnvironment { all, partial })
11691}
11692
11693pub(crate) fn callable_preprocessor_guard_requirements_with_cache(
11694 node: Node<'_>,
11695 source: &str,
11696 cache: &mut PreprocessorContextCache,
11697) -> Option<HashSet<PreprocessorGuard>> {
11698 let mut guards = HashSet::default();
11699 for guard in callable_preprocessor_ancestor_guards_with_cache(node, source, cache)? {
11700 match guard {
11701 PreprocessorGuard::Constant(true) => {}
11702 PreprocessorGuard::Constant(false) => return None,
11703 _ => {
11704 if guards.contains(&guard.negated()) {
11705 return None;
11706 }
11707 guards.insert(guard);
11708 }
11709 }
11710 }
11711 Some(guards)
11712}
11713
11714fn callable_preprocessor_ancestor_guards(
11715 node: Node<'_>,
11716 source: &str,
11717) -> Option<Vec<PreprocessorGuard>> {
11718 let mut cache = PreprocessorContextCache::default();
11719 callable_preprocessor_ancestor_guards_with_cache(node, source, &mut cache)
11720}
11721
11722fn callable_preprocessor_ancestor_guards_with_cache(
11723 node: Node<'_>,
11724 source: &str,
11725 cache: &mut PreprocessorContextCache,
11726) -> Option<Vec<PreprocessorGuard>> {
11727 let mut guards = Vec::new();
11728 let mut ancestor = node.parent();
11729 while let Some(conditional) = ancestor {
11730 if matches!(conditional.kind(), "preproc_if" | "preproc_ifdef")
11731 && !is_file_covering_include_guard(conditional, source)
11732 && !is_split_cpp_language_linkage_wrapper(conditional, node, source)
11733 && preprocessor_conditional_contains_descendant_with_cache(conditional, node, cache)
11734 {
11735 guards.push(preprocessor_guard_for_descendant(
11736 conditional,
11737 node,
11738 source,
11739 )?);
11740 }
11741 ancestor = conditional.parent();
11742 }
11743 Some(guards)
11744}
11745
11746fn fragmented_statement_preprocessor_guard(
11747 descendant: Node<'_>,
11748 source: &str,
11749) -> Option<PreprocessorGuard> {
11750 let mut ancestor = descendant.parent();
11756 while let Some(statement) = ancestor {
11757 if statement.kind() == "if_statement"
11758 && let (Some(consequence), Some(alternative)) = (
11759 statement.child_by_field_name("consequence"),
11760 statement.child_by_field_name("alternative"),
11761 )
11762 && alternative.start_byte() <= descendant.start_byte()
11763 && descendant.end_byte() <= alternative.end_byte()
11764 {
11765 let mut cursor = consequence.walk();
11766 let openers = consequence
11767 .named_children(&mut cursor)
11768 .filter(|child| {
11769 matches!(child.kind(), "preproc_if" | "preproc_ifdef")
11770 && child
11771 .child(child.child_count().saturating_sub(1))
11772 .is_some_and(|last| last.kind() == "#endif" && last.is_missing())
11773 })
11774 .collect::<Vec<_>>();
11775 if openers.len() != 1 {
11776 ancestor = statement.parent();
11777 continue;
11778 }
11779
11780 let mut terminators = Vec::new();
11781 let mut stack = vec![alternative];
11782 while let Some(node) = stack.pop() {
11783 if node.kind() == "preproc_call"
11784 && node.start_byte() >= descendant.end_byte()
11785 && node
11786 .child_by_field_name("directive")
11787 .is_some_and(|directive| node_text(directive, source).trim() == "#endif")
11788 {
11789 terminators.push(node);
11790 continue;
11791 }
11792 push_named_children_reversed(node, &mut stack);
11793 }
11794 if terminators.len() == 1 {
11795 return simple_preprocessor_guard(openers[0], source);
11796 }
11797 }
11798 ancestor = statement.parent();
11799 }
11800 None
11801}
11802
11803pub(crate) fn preprocessor_guard_for_descendant(
11804 conditional: Node<'_>,
11805 descendant: Node<'_>,
11806 source: &str,
11807) -> Option<PreprocessorGuard> {
11808 let mut guard = simple_preprocessor_guard(conditional, source)?;
11809 if conditional
11810 .child_by_field_name("alternative")
11811 .is_some_and(|alternative| {
11812 alternative.start_byte() <= descendant.start_byte()
11813 && descendant.end_byte() <= alternative.end_byte()
11814 })
11815 {
11816 let alternative = conditional.child_by_field_name("alternative")?;
11817 if !matches!(alternative.kind(), "preproc_else" | "preproc_elif") {
11821 return None;
11822 }
11823 guard = guard.negated();
11824 }
11825 Some(guard)
11826}
11827
11828pub(crate) fn preprocessor_conditional_contains_descendant(
11829 conditional: Node<'_>,
11830 descendant: Node<'_>,
11831) -> bool {
11832 cpp_displaced_preprocessor_boundary(conditional)
11833 .is_none_or(|boundary| descendant.end_byte() <= boundary.end_byte)
11834}
11835
11836fn preprocessor_conditional_contains_descendant_with_cache(
11837 conditional: Node<'_>,
11838 descendant: Node<'_>,
11839 cache: &mut PreprocessorContextCache,
11840) -> bool {
11841 cache
11842 .displaced_boundary_end(conditional)
11843 .is_none_or(|end_byte| descendant.end_byte() <= end_byte)
11844}
11845
11846pub fn merge_preprocessor_guards(
11847 left: &HashSet<PreprocessorGuard>,
11848 right: &HashSet<PreprocessorGuard>,
11849) -> Option<HashSet<PreprocessorGuard>> {
11850 let mut merged = left.clone();
11851 for guard in right {
11852 let boolean_negation = guard
11853 .as_boolean_expression()
11854 .map(|expression| expression.negated());
11855 if merged.contains(&guard.negated())
11856 || boolean_negation.is_some_and(|negated| {
11857 merged
11858 .iter()
11859 .filter_map(PreprocessorGuard::as_boolean_expression)
11860 .any(|existing| existing == negated)
11861 })
11862 {
11863 return None;
11864 }
11865 merged.insert(guard.clone());
11866 }
11867 Some(merged)
11868}
11869
11870fn simple_preprocessor_guard(conditional: Node<'_>, source: &str) -> Option<PreprocessorGuard> {
11871 if conditional.kind() == "preproc_ifdef" {
11872 let name = conditional.child_by_field_name("name")?;
11873 let name = node_text(name, source).to_string();
11874 return match conditional.child(0)?.kind() {
11875 "#ifdef" => Some(PreprocessorGuard::Defined(name)),
11876 "#ifndef" => Some(PreprocessorGuard::Undefined(name)),
11877 _ => None,
11878 };
11879 }
11880 let condition = conditional.child_by_field_name("condition")?;
11881 simple_preprocessor_expression_guard(condition, source).or_else(|| {
11882 Some(PreprocessorGuard::Expression(normalize_cpp_whitespace(
11883 node_text(condition, source),
11884 )))
11885 })
11886}
11887
11888fn simple_preprocessor_expression_guard(
11889 expression: Node<'_>,
11890 source: &str,
11891) -> Option<PreprocessorGuard> {
11892 match expression.kind() {
11893 "identifier" => Some(PreprocessorGuard::Boolean(BooleanGuardExpression::Truthy(
11894 node_text(expression, source).to_string(),
11895 ))),
11896 "number_literal" => match node_text(expression, source).trim() {
11897 "0" => Some(PreprocessorGuard::Constant(false)),
11898 "1" => Some(PreprocessorGuard::Constant(true)),
11899 _ => None,
11900 },
11901 "preproc_defined" => {
11902 let identifier = (0..expression.named_child_count())
11903 .filter_map(|index| expression.named_child(index))
11904 .find(|child| child.kind() == "identifier")?;
11905 Some(PreprocessorGuard::Defined(
11906 node_text(identifier, source).to_string(),
11907 ))
11908 }
11909 "unary_expression"
11910 if expression
11911 .child_by_field_name("operator")
11912 .is_some_and(|operator| operator.kind() == "!") =>
11913 {
11914 simple_preprocessor_expression_guard(
11915 expression.child_by_field_name("argument")?,
11916 source,
11917 )
11918 .map(|guard| guard.negated())
11919 }
11920 "parenthesized_expression" => (0..expression.named_child_count())
11921 .filter_map(|index| expression.named_child(index))
11922 .next()
11923 .and_then(|child| simple_preprocessor_expression_guard(child, source)),
11924 "binary_expression" => Some(PreprocessorGuard::Boolean(boolean_preprocessor_expression(
11925 expression, source,
11926 ))),
11927 _ => None,
11928 }
11929}
11930
11931fn boolean_preprocessor_expression(expression: Node<'_>, source: &str) -> BooleanGuardExpression {
11932 match expression.kind() {
11933 "number_literal" => match node_text(expression, source).trim() {
11934 "0" => BooleanGuardExpression::Constant(false),
11935 "1" => BooleanGuardExpression::Constant(true),
11936 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11937 expression, source,
11938 ))),
11939 },
11940 "identifier" => BooleanGuardExpression::Truthy(node_text(expression, source).to_string()),
11941 "preproc_defined" => {
11942 let identifier = (0..expression.named_child_count())
11943 .filter_map(|index| expression.named_child(index))
11944 .find(|child| child.kind() == "identifier");
11945 identifier.map_or_else(
11946 || {
11947 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11948 expression, source,
11949 )))
11950 },
11951 |identifier| {
11952 BooleanGuardExpression::Defined(node_text(identifier, source).to_string())
11953 },
11954 )
11955 }
11956 "unary_expression"
11957 if expression
11958 .child_by_field_name("operator")
11959 .is_some_and(|operator| operator.kind() == "!") =>
11960 {
11961 expression.child_by_field_name("argument").map_or_else(
11962 || {
11963 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11964 expression, source,
11965 )))
11966 },
11967 |argument| boolean_preprocessor_expression(argument, source).negated(),
11968 )
11969 }
11970 "parenthesized_expression" => (0..expression.named_child_count())
11971 .filter_map(|index| expression.named_child(index))
11972 .next()
11973 .map_or_else(
11974 || {
11975 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
11976 expression, source,
11977 )))
11978 },
11979 |child| boolean_preprocessor_expression(child, source),
11980 ),
11981 "binary_expression" => {
11982 let operands = || {
11983 Some((
11984 boolean_preprocessor_expression(
11985 expression.child_by_field_name("left")?,
11986 source,
11987 ),
11988 boolean_preprocessor_expression(
11989 expression.child_by_field_name("right")?,
11990 source,
11991 ),
11992 ))
11993 };
11994 match expression
11995 .child_by_field_name("operator")
11996 .map(|operator| operator.kind())
11997 {
11998 Some("&&") => operands().map_or_else(
11999 || {
12000 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12001 expression, source,
12002 )))
12003 },
12004 |(left, right)| BooleanGuardExpression::all([left, right]),
12005 ),
12006 Some("||") => operands().map_or_else(
12007 || {
12008 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12009 expression, source,
12010 )))
12011 },
12012 |(left, right)| BooleanGuardExpression::any([left, right]),
12013 ),
12014 _ => BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(
12015 expression, source,
12016 ))),
12017 }
12018 }
12019 _ => {
12020 BooleanGuardExpression::Opaque(normalize_cpp_whitespace(node_text(expression, source)))
12021 }
12022 }
12023}
12024
12025fn unique_include_target(mut targets: Vec<ProjectFile>) -> Option<ProjectFile> {
12026 if targets.len() == 1 {
12027 targets.pop()
12028 } else {
12029 None
12030 }
12031}
12032
12033fn callable_declaration_nodes<'tree>(
12034 analyzer: &CppGraphSource<'_>,
12035 prepared: &'tree PreparedSyntaxTree,
12036 candidate: &CodeUnit,
12037) -> Vec<Node<'tree>> {
12038 let root = prepared.tree().root_node();
12039 analyzer
12040 .ranges(candidate)
12041 .into_iter()
12042 .filter_map(|range| {
12043 let mut declaration =
12044 root.descendant_for_byte_range(range.start_byte, range.end_byte)?;
12045 while !matches!(
12058 declaration.kind(),
12059 "declaration" | "field_declaration" | "function_definition"
12060 ) && !crate::declarations::is_macro_wrapped_declaration_envelope(
12061 declaration,
12062 prepared.source(),
12063 ) {
12064 let Some(parent) = declaration.parent() else {
12065 break;
12066 };
12067 declaration = parent;
12068 }
12069 Some(declaration)
12070 })
12071 .collect()
12072}
12073
12074fn real_function_definition_ancestor<'tree>(
12075 node: Node<'tree>,
12076 source: &str,
12077) -> Option<Node<'tree>> {
12078 let mut ancestor = node.parent();
12079 while let Some(node) = ancestor {
12080 if node.kind() == "function_definition"
12081 && !is_recovered_declaration_scope_container(node, source)
12082 {
12083 return Some(node);
12084 }
12085 ancestor = node.parent();
12086 }
12087 None
12088}
12089
12090fn callable_declaration_activation_in_file(
12091 analyzer: &CppGraphSource<'_>,
12092 candidate: &CodeUnit,
12093 reference: &CallableReferenceContext<'_>,
12094) -> Option<usize> {
12095 analyzer
12096 .declaration_source_occurrences(candidate)?
12097 .into_iter()
12098 .filter_map(|occurrence| {
12099 let activation = occurrence.fact.callable_activation?;
12100 let guards = cpp_guard_set_to_runtime(occurrence.fact.callable_guards.as_ref()?)?;
12101 callable_guard_requirements_are_visible(
12102 &guards,
12103 occurrence.fact.callable_guard_completion_byte,
12104 reference,
12105 )
12106 .then_some(activation)
12107 })
12108 .min()
12109}
12110
12111fn callable_guard_requirements_are_visible(
12112 required: &HashSet<PreprocessorGuard>,
12113 completion_byte: Option<usize>,
12114 reference: &CallableReferenceContext<'_>,
12115) -> bool {
12116 if required.iter().all(|guard| match guard {
12117 PreprocessorGuard::Defined(name) if name == "__cplusplus" => !reference.is_c(),
12118 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => reference.is_c(),
12119 guard => reference
12120 .guards()
12121 .is_some_and(|active| preprocessor_guard_holds_at_reference(guard, active)),
12122 }) {
12123 return true;
12124 }
12125 if required.iter().any(|guard| match guard {
12126 PreprocessorGuard::Defined(name) if name == "__cplusplus" => reference.is_c(),
12127 PreprocessorGuard::Undefined(name) if name == "__cplusplus" => !reference.is_c(),
12128 _ => false,
12129 }) {
12130 return false;
12131 }
12132 let undecided: HashSet<_> = required
12133 .iter()
12134 .filter(|guard| {
12135 !matches!(guard, PreprocessorGuard::Defined(name) | PreprocessorGuard::Undefined(name)
12136 if name == "__cplusplus")
12137 })
12138 .cloned()
12139 .collect();
12140 reference.position.as_ref().is_some_and(|position| {
12141 completion_byte.is_some_and(|end| end > 0 && end <= position.byte)
12142 && guards_compatible_at_reference(&undecided, reference.guards())
12143 })
12144}
12145
12146struct CallableReferenceContext<'a> {
12152 file: &'a ProjectFile,
12153 position: Option<CallableReferencePosition<'a>>,
12154}
12155
12156struct CallableReferencePosition<'a> {
12160 prepared: &'a PreparedSyntaxTree,
12161 byte: usize,
12162 guards: &'a OnceCell<Option<HashSet<PreprocessorGuard>>>,
12163}
12164
12165impl CallableReferenceContext<'_> {
12166 fn is_c(&self) -> bool {
12167 self.file
12168 .rel_path()
12169 .extension()
12170 .and_then(|extension| extension.to_str())
12171 == Some("c")
12172 }
12173
12174 fn guards(&self) -> Option<&HashSet<PreprocessorGuard>> {
12175 let position = self.position.as_ref()?;
12176 position
12177 .guards
12178 .get_or_init(|| {
12179 position
12180 .prepared
12181 .tree()
12182 .root_node()
12183 .descendant_for_byte_range(position.byte, position.byte.saturating_add(1))
12184 .and_then(|node| {
12185 preprocessor_guard_environment(node, position.prepared.source())
12186 })
12187 })
12188 .as_ref()
12189 }
12190}
12191
12192fn callable_declaration_guard_requirements(
12204 node: Node<'_>,
12205 source: &str,
12206 reference: &CallableReferenceContext<'_>,
12207) -> Option<HashSet<PreprocessorGuard>> {
12208 let reference_is_c = reference.is_c();
12209 let mut required = HashSet::default();
12210 for guard in callable_preprocessor_ancestor_guards(node, source)? {
12211 match guard {
12212 PreprocessorGuard::Constant(true) => {}
12213 PreprocessorGuard::Constant(false) => return None,
12214 guard if preprocessor_guard_excludes_language(&guard, reference_is_c) => {
12215 return None;
12216 }
12217 guard => {
12218 required.insert(guard);
12219 }
12220 }
12221 }
12222 Some(required)
12223}
12224
12225fn callable_preprocessor_context_is_visible_for_reference(
12236 node: Node<'_>,
12237 source: &str,
12238 reference: &CallableReferenceContext<'_>,
12239) -> bool {
12240 let Some(required) = callable_declaration_guard_requirements(node, source, reference) else {
12241 return false;
12242 };
12243 if required.is_empty() || guard_requirements_hold_at_reference(&required, reference.guards()) {
12244 return true;
12245 }
12246 reference.position.as_ref().is_some_and(|position| {
12247 guards_compatible_at_reference(&required, reference.guards())
12248 && declaration_follows_completed_family_before_reference(node, source, position.byte)
12249 })
12250}
12251
12252fn flattened_macro_namespace_declaration_matches(
12253 analyzer: &CppGraphSource<'_>,
12254 _cpp: &dyn CppSource,
12255 reference_file: &ProjectFile,
12256 visible_declaration: &CodeUnit,
12257 qualified_candidate: &CodeUnit,
12258 reference_byte: usize,
12259) -> bool {
12260 if visible_declaration.kind() != qualified_candidate.kind()
12266 || visible_declaration.identifier() != qualified_candidate.identifier()
12267 || visible_declaration.signature() != qualified_candidate.signature()
12268 || !visible_declaration.package_name().is_empty()
12269 || qualified_candidate.package_name().is_empty()
12270 {
12271 return false;
12272 }
12273
12274 let Some(occurrences) = analyzer.declaration_source_occurrences(visible_declaration) else {
12275 return false;
12276 };
12277 occurrences.into_iter().any(|occurrence| {
12278 occurrence
12279 .displaced_namespace_closing_brace
12280 .is_some_and(|closing_brace| {
12281 visible_declaration.source() != reference_file || closing_brace < reference_byte
12282 })
12283 })
12284}
12285
12286pub(crate) fn flattened_macro_namespace_components(
12287 declaration: Node<'_>,
12288 source: &str,
12289) -> Option<Vec<String>> {
12290 flattened_macro_function_namespace_components(declaration, source)
12291 .or_else(|| flattened_macro_error_namespace_components(declaration, source))
12292}
12293
12294fn flattened_macro_function_namespace_components(
12295 declaration: Node<'_>,
12296 source: &str,
12297) -> Option<Vec<String>> {
12298 let body = declaration
12299 .parent()
12300 .filter(|parent| parent.kind() == "compound_statement")?;
12301 let function = body.parent()?;
12302 if function.child_by_field_name("body") != Some(body) {
12303 return None;
12304 }
12305 let namespace_name = recovered_macro_namespace_name(function, source)?;
12306 let mut components = enclosing_namespace_components(declaration, source)?;
12307 components.push(namespace_name);
12308 Some(components)
12309}
12310
12311fn recovered_macro_namespace_name(function: Node<'_>, source: &str) -> Option<String> {
12322 if function.kind() != "function_definition" || !function.has_error() {
12323 return None;
12324 }
12325 let body = function
12326 .child_by_field_name("body")
12327 .filter(|body| body.kind() == "compound_statement")?;
12328 let mut cursor = function.walk();
12329 let prefix = function
12330 .named_children(&mut cursor)
12331 .take_while(|child| child.start_byte() < body.start_byte())
12332 .filter(|child| child.kind() != "comment")
12333 .collect::<Vec<_>>();
12334 let begin_index = prefix.iter().rposition(|child| {
12335 flattened_macro_sentinel_name(*child, source)
12336 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12337 })?;
12338 let mut identifiers = Vec::new();
12339 let mut stack = prefix[begin_index + 1..]
12340 .iter()
12341 .rev()
12342 .copied()
12343 .collect::<Vec<_>>();
12344 while let Some(current) = stack.pop() {
12345 if let Some(identifier) = direct_cpp_identifier_name(current, source) {
12346 identifiers.push(identifier);
12347 continue;
12348 }
12349 let mut cursor = current.walk();
12350 let children = current.named_children(&mut cursor).collect::<Vec<_>>();
12351 stack.extend(children.into_iter().rev());
12352 }
12353 let [keyword, namespace_name] = identifiers.as_slice() else {
12354 return None;
12355 };
12356 if keyword != "namespace" || namespace_name.is_empty() || cpp_export_macro_token(namespace_name)
12357 {
12358 return None;
12359 }
12360 let mut next = function.next_named_sibling();
12361 let next = loop {
12362 let candidate = next?;
12363 next = candidate.next_named_sibling();
12364 if candidate.kind() != "comment" {
12365 break candidate;
12366 }
12367 };
12368 flattened_macro_sentinel_name(next, source)
12369 .is_some_and(|name| is_namespace_end_sentinel(&name))
12370 .then(|| namespace_name.clone())
12371}
12372
12373pub(crate) fn is_recovered_declaration_scope_container(node: Node<'_>, source: &str) -> bool {
12378 crate::declarations::is_recovered_exported_class_container(node, source)
12379 || crate::declarations::is_recovered_fragmented_partial_specialization_container(
12380 node, source,
12381 )
12382 || recovered_macro_namespace_name(node, source).is_some()
12383}
12384
12385fn flattened_macro_error_namespace_components(
12386 declaration: Node<'_>,
12387 source: &str,
12388) -> Option<Vec<String>> {
12389 let parent = declaration
12390 .parent()
12391 .filter(|parent| parent.kind() == "ERROR" && parent.has_error())?;
12392 let mut cursor = parent.walk();
12393 let siblings = parent.named_children(&mut cursor).collect::<Vec<_>>();
12394 let declaration_index = siblings
12395 .iter()
12396 .position(|candidate| same_node(*candidate, declaration))?;
12397 let begin_index = (0..declaration_index).rev().find(|index| {
12398 flattened_macro_sentinel_name(siblings[*index], source)
12399 .is_some_and(|name| is_namespace_begin_sentinel(&name))
12400 })?;
12401
12402 let significant = siblings[begin_index + 1..declaration_index]
12403 .iter()
12404 .copied()
12405 .filter(|node| node.kind() != "comment")
12406 .collect::<Vec<_>>();
12407 let [namespace_keyword, namespace_name, ..] = significant.as_slice() else {
12408 return None;
12409 };
12410 if direct_cpp_identifier_name(*namespace_keyword, source).as_deref() != Some("namespace") {
12411 return None;
12412 }
12413 let namespace_name = flattened_macro_namespace_name(*namespace_name, source)?;
12414 if significant[2..].iter().any(|node| {
12415 flattened_macro_sentinel_name(*node, source).is_some_and(|name| {
12416 is_namespace_begin_sentinel(&name) || is_namespace_end_sentinel(&name)
12417 })
12418 }) {
12419 return None;
12420 }
12421
12422 let mut saw_namespace_close = false;
12423 for sibling in siblings.iter().skip(declaration_index + 1).copied() {
12424 if sibling.kind() == "comment" {
12425 continue;
12426 }
12427 if !saw_namespace_close {
12428 if direct_unmatched_closing_brace(sibling) {
12429 saw_namespace_close = true;
12430 continue;
12431 }
12432 if flattened_macro_sentinel_name(sibling, source).is_some() {
12433 return None;
12434 }
12435 continue;
12436 }
12437 if !flattened_macro_sentinel_name(sibling, source)
12438 .is_some_and(|name| is_namespace_end_sentinel(&name))
12439 {
12440 return None;
12441 }
12442 let mut components = enclosing_namespace_components(declaration, source)?;
12443 components.push(namespace_name);
12444 return Some(components);
12445 }
12446 None
12447}
12448
12449fn flattened_macro_sentinel_name(node: Node<'_>, source: &str) -> Option<String> {
12450 let node = if node.kind() == "expression_statement" && node.named_child_count() == 1 {
12454 node.named_child(0)?
12455 } else {
12456 node
12457 };
12458 let candidate = direct_cpp_identifier_name(node, source).or_else(|| {
12459 node.child_by_field_name("type")
12460 .and_then(|type_node| direct_cpp_identifier_name(type_node, source))
12461 })?;
12462 (cpp_export_macro_token(&candidate)
12463 && (is_namespace_begin_sentinel(&candidate) || is_namespace_end_sentinel(&candidate)))
12464 .then_some(candidate)
12465}
12466
12467#[derive(Clone, Copy, PartialEq, Eq)]
12470enum NamespaceSentinel {
12471 Begin,
12472 End,
12473}
12474
12475fn namespace_sentinel(name: &str) -> Option<NamespaceSentinel> {
12476 if name.ends_with("NAMESPACE_BEGIN") || name.ends_with("BEGIN_NAMESPACE") {
12477 Some(NamespaceSentinel::Begin)
12478 } else if name.ends_with("NAMESPACE_END") || name.ends_with("END_NAMESPACE") {
12479 Some(NamespaceSentinel::End)
12480 } else {
12481 None
12482 }
12483}
12484
12485fn is_namespace_begin_sentinel(name: &str) -> bool {
12486 namespace_sentinel(name) == Some(NamespaceSentinel::Begin)
12487}
12488
12489fn is_namespace_end_sentinel(name: &str) -> bool {
12490 namespace_sentinel(name) == Some(NamespaceSentinel::End)
12491}
12492
12493fn flattened_macro_namespace_name(node: Node<'_>, source: &str) -> Option<String> {
12494 if node.kind() != "ERROR" || node.named_child_count() != 1 {
12495 return None;
12496 }
12497 let name = direct_cpp_identifier_name(node.named_child(0)?, source)?;
12498 (!cpp_export_macro_token(&name)).then_some(name)
12499}
12500
12501fn direct_cpp_identifier_name(node: Node<'_>, source: &str) -> Option<String> {
12502 if !matches!(
12503 node.kind(),
12504 "identifier" | "namespace_identifier" | "type_identifier"
12505 ) {
12506 return None;
12507 }
12508 let name = normalize_cpp_whitespace(node_text(node, source));
12509 (!name.is_empty()).then_some(name)
12510}
12511
12512fn guard_requirement_sets_match(
12513 left: &[(usize, HashSet<PreprocessorGuard>)],
12514 right: &[(usize, HashSet<PreprocessorGuard>)],
12515) -> bool {
12516 left.len() == right.len()
12517 && left.iter().all(|(_, left_guards)| {
12518 right
12519 .iter()
12520 .any(|(_, right_guards)| left_guards == right_guards)
12521 })
12522 && right.iter().all(|(_, right_guards)| {
12523 left.iter()
12524 .any(|(_, left_guards)| right_guards == left_guards)
12525 })
12526}
12527
12528pub(crate) fn direct_unmatched_closing_brace(node: Node<'_>) -> bool {
12529 node.kind() == "ERROR"
12530 && (0..node.child_count())
12531 .any(|index| node.child(index).is_some_and(|child| child.kind() == "}"))
12532}
12533
12534pub fn callable_preprocessor_context_is_visible(node: Node<'_>, source: &str) -> bool {
12535 let mut ancestor = node.parent();
12536 while let Some(parent) = ancestor {
12537 if is_cpp_preprocessor_conditional(parent)
12538 && !is_file_covering_include_guard(parent, source)
12539 && !is_split_cpp_language_linkage_wrapper(parent, node, source)
12540 {
12541 return false;
12542 }
12543 ancestor = parent.parent();
12544 }
12545 true
12546}
12547
12548fn is_split_cpp_language_linkage_wrapper(
12549 conditional: Node<'_>,
12550 descendant: Node<'_>,
12551 source: &str,
12552) -> bool {
12553 if conditional.child_by_field_name("alternative").is_some()
12554 || !matches!(
12555 simple_preprocessor_guard(conditional, source),
12556 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12557 )
12558 {
12559 return false;
12560 }
12561 let mut current = descendant.parent();
12562 let linkage = loop {
12563 let Some(node) = current else {
12564 return false;
12565 };
12566 if node == conditional {
12567 return false;
12568 }
12569 if node.kind() == "linkage_specification" {
12570 break node;
12571 }
12572 current = node.parent();
12573 };
12574 if linkage
12575 .child_by_field_name("value")
12576 .is_none_or(|value| node_text(value, source) != "\"C\"")
12577 {
12578 return false;
12579 }
12580 let Some(body) = linkage.child_by_field_name("body") else {
12581 return false;
12582 };
12583 let mut cursor = body.walk();
12588 let closes_opening_branch = body
12589 .named_children(&mut cursor)
12590 .take_while(|child| child.end_byte() <= descendant.start_byte())
12591 .any(|child| {
12592 child.kind() == "preproc_call"
12593 && child
12594 .child_by_field_name("directive")
12595 .is_some_and(|directive| node_text(directive, source) == "#endif")
12596 });
12597 let mut cursor = body.walk();
12598 let reopens_for_closing_brace = body
12599 .named_children(&mut cursor)
12600 .skip_while(|child| child.start_byte() < descendant.end_byte())
12601 .any(|child| {
12602 matches!(
12603 simple_preprocessor_guard(child, source),
12604 Some(PreprocessorGuard::Defined(name)) if name == "__cplusplus"
12605 ) && {
12606 let mut tokens = child.walk();
12607 child
12608 .children(&mut tokens)
12609 .any(|token| token.kind() == "#endif" && token.is_missing())
12610 }
12611 });
12612 closes_opening_branch && reopens_for_closing_brace
12613}
12614
12615pub fn call_arguments_node(node: Node<'_>) -> Option<Node<'_>> {
12619 node.child_by_field_name("arguments")
12620 .or_else(|| node.child_by_field_name("parameters"))
12621 .or_else(|| node.child_by_field_name("value"))
12622 .or_else(|| first_named_child_of_kind(node, "argument_list"))
12623 .or_else(|| first_named_child_of_kind(node, "initializer_list"))
12624}
12625
12626pub fn call_arity(node: Node<'_>) -> usize {
12627 call_arguments_node(node)
12628 .map(|args| argument_children(args).count())
12629 .unwrap_or(0)
12630}
12631
12632pub fn argument_children<'tree>(node: Node<'tree>) -> impl Iterator<Item = Node<'tree>> {
12633 let recovered_block_arguments = recovered_block_literal_arguments(node);
12634 (0..node.child_count())
12635 .filter_map(move |index| node.child(index))
12636 .filter(|child| child.is_named() && !child.is_extra())
12637 .flat_map(move |child| {
12638 if let Some((raw, left, right)) = recovered_block_arguments
12639 && child == raw
12640 {
12641 [Some(left), Some(right)]
12642 } else {
12643 [Some(child), None]
12644 }
12645 })
12646 .flatten()
12647}
12648
12649pub fn recovered_c_new_expression_arguments(
12658 node: Node<'_>,
12659 uses_c_semantics: bool,
12660) -> Option<[Node<'_>; 2]> {
12661 if !uses_c_semantics || node.kind() != "new_expression" {
12662 return None;
12663 }
12664 let parent = node.parent()?;
12665 if parent.kind() != "argument_list" {
12666 return None;
12667 }
12668 let keyword = node.child(0)?;
12669 let error = node.child(1)?;
12670 let trailing = node.child(2)?;
12671 if node.child(3).is_some()
12672 || keyword.kind() != "new"
12673 || keyword.is_named()
12674 || keyword.child_count() != 0
12675 || error.kind() != "ERROR"
12676 || !error.is_extra()
12677 || error.child_count() != 1
12678 || error.child(0).is_none_or(|comma| comma.kind() != ",")
12679 || node.child_by_field_name("type") != Some(trailing)
12680 || trailing.kind() != "type_identifier"
12681 {
12682 return None;
12683 }
12684 Some([keyword, trailing])
12685}
12686
12687pub fn recovered_c_new_expression_argument_at(
12690 mut node: Node<'_>,
12691 start_byte: usize,
12692 end_byte: usize,
12693 uses_c_semantics: bool,
12694) -> Option<Node<'_>> {
12695 if !uses_c_semantics {
12701 return None;
12702 }
12703 loop {
12704 if let Some(arguments) = recovered_c_new_expression_arguments(node, uses_c_semantics) {
12705 return arguments.into_iter().find(|argument| {
12706 argument.start_byte() <= start_byte && end_byte <= argument.end_byte()
12707 });
12708 }
12709 node = node.parent()?;
12710 }
12711}
12712
12713fn recovered_c_keyword_argument_count(
12714 file: &ProjectFile,
12715 call: Node<'_>,
12716 arguments: Node<'_>,
12717 source: &str,
12718) -> usize {
12719 if !is_c_source_file(file) || arguments.kind() != "argument_list" {
12725 return 0;
12726 }
12727 let mut ancestor = Some(call);
12728 let function = loop {
12729 let Some(current) = ancestor else {
12730 return recovered_c_keyword_argument_count_from_collapsed_function(call, arguments)
12731 .unwrap_or(0);
12732 };
12733 if current.kind() == "function_definition" {
12734 break current;
12735 }
12736 ancestor = current.parent();
12737 };
12738 let Some(parameters) = function
12739 .child_by_field_name("declarator")
12740 .and_then(|declarator| declarator.child_by_field_name("parameters"))
12741 else {
12742 return 0;
12743 };
12744 let displaced_parameter_keywords = (0..parameters.child_count())
12745 .filter_map(|index| parameters.child(index))
12746 .filter(|error| error.kind() == "ERROR")
12747 .filter_map(|error| {
12748 let parameter = error.prev_named_sibling()?;
12749 if parameter.kind() != "parameter_declaration"
12750 || parameter.end_byte() != error.start_byte()
12751 || extract_variable_name(parameter, source).is_some()
12752 {
12753 return None;
12754 }
12755 let mut children = (0..error.child_count())
12756 .filter_map(|index| error.child(index))
12757 .filter(|child| !child.is_extra() && !child.is_missing());
12758 let keyword = children.next()?;
12759 (children.next().is_none() && !keyword.is_named() && keyword.child_count() == 0)
12760 .then_some(keyword)
12761 })
12762 .collect::<Vec<_>>();
12763 if displaced_parameter_keywords.is_empty() {
12764 return 0;
12765 }
12766
12767 (0..arguments.child_count())
12768 .filter_map(|index| arguments.child(index))
12769 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12770 .filter(|error| {
12771 let mut children = (0..error.child_count())
12772 .filter_map(|index| error.child(index))
12773 .filter(|child| !child.is_extra() && !child.is_missing());
12774 let Some(comma) = children.next() else {
12775 return false;
12776 };
12777 let Some(keyword) = children.next() else {
12778 return false;
12779 };
12780 children.next().is_none()
12781 && comma.kind() == ","
12782 && !keyword.is_named()
12783 && keyword.child_count() == 0
12784 && displaced_parameter_keywords
12785 .iter()
12786 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12787 })
12788 .count()
12789}
12790
12791fn recovered_c_keyword_argument_count_from_collapsed_function(
12797 call: Node<'_>,
12798 arguments: Node<'_>,
12799) -> Option<usize> {
12800 let argument_keywords = children_iter(arguments)
12801 .filter(|error| error.kind() == "ERROR" && error.is_extra())
12802 .filter_map(|error| {
12803 let mut children =
12804 children_iter(error).filter(|child| !child.is_extra() && !child.is_missing());
12805 let comma = children.next()?;
12806 let keyword = children.next()?;
12807 (children.next().is_none()
12808 && comma.kind() == ","
12809 && !keyword.is_named()
12810 && keyword.child_count() == 0)
12811 .then_some(keyword)
12812 })
12813 .collect::<Vec<_>>();
12814 if argument_keywords.is_empty() {
12815 return Some(0);
12816 }
12817
12818 let mut ancestor = call.parent();
12823 let body = loop {
12824 let current = ancestor?;
12825 if matches!(current.kind(), "function_definition" | "lambda_expression") {
12826 return None;
12827 }
12828 if current.kind() == "compound_statement"
12829 && current
12830 .parent()
12831 .is_some_and(|parent| parent.kind() == "ERROR")
12832 {
12833 break current;
12834 }
12835 ancestor = current.parent();
12836 };
12837 let parameter_error = body.parent().filter(|parent| parent.kind() == "ERROR")?;
12838 let function_error = parameter_error
12839 .parent()
12840 .filter(|parent| parent.kind() == "ERROR")?;
12841 let function_children = children_iter(function_error).collect::<Vec<_>>();
12842 let parameter_error_index = function_children
12843 .iter()
12844 .position(|child| same_node(*child, parameter_error))?;
12845 let has_return_type = function_children[..parameter_error_index.saturating_sub(2)]
12848 .iter()
12849 .enumerate()
12850 .any(|(index, child)| {
12851 function_error.field_name_for_child(index as u32) == Some("type")
12852 && !child.is_extra()
12853 && !child.is_missing()
12854 && matches!(
12855 child.kind(),
12856 "primitive_type" | "sized_type_specifier" | "type_identifier"
12857 )
12858 });
12859 if parameter_error_index < 2
12860 || !has_return_type
12861 || function_children[parameter_error_index - 1].kind() != "("
12862 || function_children[parameter_error_index - 2].kind() != "identifier"
12863 {
12864 return Some(0);
12865 }
12866
12867 let parameter_children = children_iter(parameter_error).collect::<Vec<_>>();
12868 let body_index = parameter_children
12869 .iter()
12870 .position(|child| same_node(*child, body))?;
12871 let close_parenthesis_index = parameter_children[..body_index]
12872 .iter()
12873 .position(|child| child.kind() == ")")?;
12874 let parameter_keywords = parameter_children[..close_parenthesis_index]
12875 .iter()
12876 .enumerate()
12877 .filter(|(index, child)| {
12878 !child.is_named()
12879 && !child.is_extra()
12880 && !child.is_missing()
12881 && child.child_count() == 0
12882 && argument_keywords
12883 .iter()
12884 .any(|keyword| keyword.kind_id() == child.kind_id())
12885 && collapsed_c_parameter_name_position(¶meter_children, *index)
12886 })
12887 .map(|(_, child)| *child)
12888 .collect::<Vec<_>>();
12889 Some(
12890 argument_keywords
12891 .iter()
12892 .filter(|keyword| {
12893 parameter_keywords
12894 .iter()
12895 .any(|parameter| parameter.kind_id() == keyword.kind_id())
12896 })
12897 .count(),
12898 )
12899}
12900
12901fn collapsed_c_parameter_name_position(children: &[Node<'_>], keyword_index: usize) -> bool {
12906 let segment_start = children[..keyword_index]
12907 .iter()
12908 .rposition(|child| matches!(child.kind(), "(" | ","))
12909 .map_or(0, |index| index + 1);
12910 let mut saw_type = false;
12911 let mut saw_pointer = false;
12912 for child in &children[segment_start..keyword_index] {
12913 if child.kind() == "*" && !child.is_extra() && !child.is_missing() {
12914 saw_pointer = true;
12915 continue;
12916 }
12917 if saw_pointer {
12918 return false;
12919 }
12920 if !child.is_named() || child.is_extra() || child.is_missing() {
12921 return false;
12922 }
12923 if child.kind() == "type_qualifier" {
12924 continue;
12925 }
12926 if matches!(
12927 child.kind(),
12928 "class_specifier"
12929 | "dependent_type"
12930 | "enum_specifier"
12931 | "identifier"
12932 | "primitive_type"
12933 | "qualified_identifier"
12934 | "scoped_identifier"
12935 | "struct_specifier"
12936 | "template_type"
12937 | "type_identifier"
12938 | "union_specifier"
12939 ) {
12940 saw_type = true;
12941 continue;
12942 }
12943 return false;
12944 }
12945 saw_type && saw_pointer
12946}
12947
12948fn recovered_block_literal_arguments<'tree>(
12949 arguments: Node<'tree>,
12950) -> Option<(Node<'tree>, Node<'tree>, Node<'tree>)> {
12951 if arguments.kind() != "argument_list" {
12952 return None;
12953 }
12954 let mut raw_arguments = (0..arguments.child_count())
12955 .filter_map(|index| arguments.child(index))
12956 .filter(|child| child.is_named() && !child.is_extra());
12957 let raw = raw_arguments.next()?;
12958 if raw_arguments.next().is_some() || raw.kind() != "binary_expression" {
12959 return None;
12960 }
12961
12962 let left = raw.child_by_field_name("left")?;
12963 if left.is_missing() || left.start_byte() == left.end_byte() {
12964 return None;
12965 }
12966 let right = raw.child_by_field_name("right")?;
12967 if right.kind() != "compound_literal_expression"
12968 || right.is_missing()
12969 || right
12970 .child_by_field_name("type")
12971 .is_none_or(|node| node.kind() != "type_descriptor" || node.is_missing())
12972 || right
12973 .child_by_field_name("value")
12974 .is_none_or(|node| node.kind() != "initializer_list" || node.is_missing())
12975 {
12976 return None;
12977 }
12978 let has_intervening_error = (0..raw.child_count())
12979 .filter_map(|index| raw.child(index))
12980 .any(|child| {
12981 child.kind() == "ERROR"
12982 && !child.is_missing()
12983 && child.start_byte() >= left.end_byte()
12984 && child.end_byte() <= right.start_byte()
12985 });
12986 has_intervening_error.then_some((raw, left, right))
12987}
12988
12989pub fn constructor_type_node(node: Node<'_>) -> Option<Node<'_>> {
12990 match node.kind() {
12991 "new_expression" => node
12992 .child_by_field_name("type")
12993 .or_else(|| node.named_child(0)),
12994 "compound_literal_expression" => node.child_by_field_name("type"),
12995 "call_expression" => node.child_by_field_name("function"),
12996 _ => None,
12997 }
12998}
12999
13000pub fn cast_expression_type_node(node: Node<'_>) -> Option<Node<'_>> {
13006 if node.kind() != "cast_expression" {
13007 return None;
13008 }
13009 let descriptor = node.child_by_field_name("type")?;
13010 if descriptor.kind() == "type_descriptor" {
13011 descriptor.child_by_field_name("type")
13012 } else {
13013 Some(descriptor)
13014 }
13015}
13016
13017pub fn cpp_field_expression_receiver(field: Node<'_>) -> Option<Node<'_>> {
13032 debug_assert_eq!(field.kind(), "field_expression");
13033 let operator = field.child_by_field_name("operator")?;
13034 let mut cursor = field.walk();
13035 let receiver = field
13036 .named_children(&mut cursor)
13037 .filter(|child| child.end_byte() <= operator.start_byte())
13038 .last()?;
13039 if receiver.kind() != "ERROR" {
13040 return Some(receiver);
13041 }
13042 (receiver.named_child_count() == 1)
13043 .then(|| receiver.named_child(0))
13044 .flatten()
13045}
13046
13047pub fn field_initializer_constructs_target(
13048 node: Node<'_>,
13049 ctx: &ScanCtx<'_>,
13050 owner: &CodeUnit,
13051) -> bool {
13052 if first_named_child_of_kind(node, "qualified_identifier").is_some() {
13061 return qualified_base_initializer_constructs_target(node, ctx, owner);
13062 }
13063 let Some(name) = node
13064 .child_by_field_name("name")
13065 .or_else(|| first_named_child_of_kind(node, "field_identifier"))
13066 .or_else(|| first_named_child_of_kind(node, "qualified_identifier"))
13067 else {
13068 return false;
13069 };
13070 let field_name = node_text(name, ctx.source);
13071 ctx.visibility
13072 .visible_identifier_candidates(ctx.file, field_name)
13073 .filter(|unit| unit.is_field() && unit.identifier() == field_name)
13074 .any(|unit| field_declares_type(unit, ctx, owner) && field_holds_subobject(unit, ctx))
13075}
13076
13077fn field_holds_subobject(unit: &CodeUnit, ctx: &ScanCtx<'_>) -> bool {
13087 field_initializer_holds_subobject(&ctx.analyzer, ctx.visibility, unit)
13088}
13089
13090fn qualified_base_initializer_constructs_target(
13091 node: Node<'_>,
13092 ctx: &ScanCtx<'_>,
13093 owner: &CodeUnit,
13094) -> bool {
13095 let Some(qualified) = first_named_child_of_kind(node, "qualified_identifier") else {
13096 return false;
13097 };
13098 let Some(components) = cpp_type_name_components(qualified, ctx.source) else {
13099 return false;
13100 };
13101 let Some(lexical_scope) = enclosing_namespace_components(node, ctx.source) else {
13102 return false;
13103 };
13104 let resolves_target = |components: &[String]| {
13105 matches!(
13106 ctx.visibility.resolve_type_components_lexically_for_target(
13107 &ctx.analyzer,
13108 ctx.file,
13109 components,
13110 is_globally_qualified_cpp_name(qualified),
13111 &lexical_scope,
13112 owner,
13113 ),
13114 LexicalTypeResolution::Resolved { unit, .. }
13115 if same_visible_symbol(&unit, owner)
13116 )
13117 };
13118 if resolves_target(&components) {
13119 return true;
13120 }
13121
13122 components
13128 .last()
13129 .is_some_and(|terminal| terminal == owner.identifier())
13130 && resolves_target(&components[..components.len() - 1])
13131}
13132
13133fn field_declares_type(unit: &CodeUnit, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13134 ctx.visibility
13135 .field_declared_type_fact(&ctx.analyzer, unit)
13136 .is_some_and(|fact| {
13137 ctx.visibility
13138 .resolves_to_type(&ctx.analyzer, ctx.file, &fact.type_text, owner)
13139 || ctx.visibility.resolves_to_type(
13140 &ctx.analyzer,
13141 ctx.file,
13142 &normalize_field_type_text(&fact.type_text),
13143 owner,
13144 )
13145 })
13146}
13147
13148pub fn field_declared_binding(
13149 analyzer: &CppGraphSource<'_>,
13150 visibility: &VisibilityIndex<'_>,
13151 visible_from: &ProjectFile,
13152 field: &CodeUnit,
13153) -> Option<CppScanBinding> {
13154 let (normalized, resolved, indirection) =
13155 field_declared_type_binding(analyzer, visibility, visible_from, field)?;
13156 let resolved = resolved
13157 .or_else(|| anonymous_aggregate_field_owner(analyzer, visibility, visible_from, field));
13158 Some(CppScanBinding::from_type_name(
13159 normalized,
13160 resolved,
13161 indirection,
13162 ))
13163}
13164
13165pub fn field_initializer_holds_subobject(
13172 analyzer: &CppGraphSource<'_>,
13173 visibility: &VisibilityIndex<'_>,
13174 field: &CodeUnit,
13175) -> bool {
13176 visibility
13177 .field_declared_type_fact(analyzer, field)
13178 .is_none_or(|fact| !fact.binds_indirectly)
13179}
13180
13181fn anonymous_aggregate_field_owner(
13188 analyzer: &CppGraphSource<'_>,
13189 visibility: &VisibilityIndex<'_>,
13190 visible_from: &ProjectFile,
13191 field: &CodeUnit,
13192) -> Option<CodeUnit> {
13193 let owner = type_owner_of(analyzer, field)?;
13194 if !owner.is_class() {
13195 return None;
13196 }
13197 let declaration = analyzer.get_source(field, false)?;
13198 let mut parser = Parser::new();
13199 parser
13200 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13201 .ok()?;
13202 let tree = parser.parse(&declaration, None)?;
13203 let mut stack = vec![tree.root_node()];
13204 while let Some(node) = stack.pop() {
13205 if matches!(node.kind(), "declaration" | "field_declaration")
13206 && let Some(type_node) = node
13207 .child_by_field_name("type")
13208 .or_else(|| first_type_child(node))
13209 && matches!(type_node.kind(), "struct_specifier" | "union_specifier")
13210 && type_node.child_by_field_name("name").is_none()
13211 && declared_name_binding(node, type_node, field.identifier(), &declaration).is_some()
13212 {
13213 let matches = visibility
13214 .visible_members_for_owner_name(visible_from, &owner, field.identifier())
13215 .into_iter()
13216 .filter(|child| child.is_class() && child.identifier() == field.identifier())
13217 .collect::<Vec<_>>();
13218 return match matches.as_slice() {
13219 [child] => Some((*child).clone()),
13220 _ => None,
13221 };
13222 }
13223 let mut cursor = node.walk();
13224 stack.extend(node.named_children(&mut cursor));
13225 }
13226 None
13227}
13228
13229pub fn anonymous_aggregate_owner(
13236 analyzer: &CppGraphSource<'_>,
13237 file: &ProjectFile,
13238 node: Node<'_>,
13239) -> Option<CodeUnit> {
13240 if !matches!(node.kind(), "struct_specifier" | "union_specifier")
13241 || node.child_by_field_name("name").is_some()
13242 {
13243 return None;
13244 }
13245 let mut candidates = analyzer
13246 .declarations(file)
13247 .into_iter()
13248 .filter(|candidate| {
13249 candidate.is_class()
13250 && analyzer.ranges(candidate).into_iter().any(|range| {
13251 range.start_byte == node.start_byte() && range.end_byte == node.end_byte()
13252 })
13253 })
13254 .collect::<Vec<_>>();
13255 candidates.sort_by_key(|candidate| candidate.fq_name());
13256 candidates.dedup();
13257 match candidates.as_slice() {
13258 [candidate] => Some(candidate.clone()),
13259 _ => None,
13260 }
13261}
13262
13263fn logical_type_candidate(candidates: Vec<&CodeUnit>) -> Result<CodeUnit, TypeCandidateFailure> {
13265 let Some(first) = candidates.first() else {
13266 return Err(TypeCandidateFailure::Unresolvable);
13267 };
13268 if candidates
13269 .iter()
13270 .all(|candidate| candidate.kind() == first.kind() && candidate.fq_name() == first.fq_name())
13271 {
13272 Ok((*first).clone())
13273 } else {
13274 Err(TypeCandidateFailure::Ambiguous)
13275 }
13276}
13277
13278fn unique_logical_type_candidate(candidates: Vec<&CodeUnit>) -> Option<CodeUnit> {
13279 logical_type_candidate(candidates).ok()
13280}
13281
13282fn unique_type_candidate_preserving_alias(
13283 analyzer: &CppGraphSource<'_>,
13284 file: &ProjectFile,
13285 candidates: &[&CodeUnit],
13286) -> Option<CodeUnit> {
13287 let first = *candidates.first()?;
13288 if declared_type_alias(analyzer, first) {
13289 return candidates
13290 .iter()
13291 .all(|candidate| {
13292 declared_type_alias(analyzer, candidate)
13293 && candidate.kind() == first.kind()
13294 && candidate.fq_name() == first.fq_name()
13295 && candidate.source() == first.source()
13296 })
13297 .then(|| first.clone());
13298 }
13299 if analyzer.reference_uses_c_semantics(file)
13300 && first.is_class()
13301 && indexed_c_tag_kind(analyzer, first).is_some()
13302 {
13303 let mut full_source = None;
13304 let mut tag_kind = None;
13305 for candidate in candidates.iter().copied() {
13306 let candidate_tag_kind = indexed_c_tag_kind(analyzer, candidate)?;
13307 if tag_kind
13308 .replace(candidate_tag_kind)
13309 .is_some_and(|existing| existing != candidate_tag_kind)
13310 {
13311 return None;
13312 }
13313 if cpp_class_declaration_strength(analyzer, candidate)
13314 == CppClassDeclarationStrength::Full
13315 && full_source
13316 .replace(candidate.source())
13317 .is_some_and(|existing| existing != candidate.source())
13318 {
13319 return None;
13320 }
13321 }
13322 }
13323 candidates
13324 .iter()
13325 .all(|candidate| {
13326 !declared_type_alias(analyzer, candidate)
13327 && candidate.kind() == first.kind()
13328 && candidate.fq_name() == first.fq_name()
13329 })
13330 .then(|| first.clone())
13331}
13332
13333pub(crate) fn declared_type_alias(analyzer: &CppGraphSource<'_>, unit: &CodeUnit) -> bool {
13334 brokk_bifrost_core::analyzer::capabilities::provider_declares_type_alias(
13335 analyzer.type_alias_provider(),
13336 unit,
13337 )
13338}
13339
13340pub fn field_declared_type_binding(
13341 analyzer: &CppGraphSource<'_>,
13342 visibility: &VisibilityIndex<'_>,
13343 visible_from: &ProjectFile,
13344 field: &CodeUnit,
13345) -> Option<(String, Option<CodeUnit>, i32)> {
13346 let fact = visibility.field_declared_type_fact(analyzer, field)?;
13347 let normalized = normalize_field_type_text(&fact.type_text);
13348 let primary = visibility.resolve_unique_canonical_type_for_declaration(
13349 analyzer,
13350 visible_from,
13351 field,
13352 &normalized,
13353 );
13354 let resolved = match (primary, fact.template_arguments.as_deref()) {
13355 (Some(primary), Some(arguments)) => visibility
13356 .resolve_template_arguments(visible_from, primary, arguments)
13357 .ok(),
13358 (resolved, None) => resolved,
13359 (None, Some(_)) => None,
13360 };
13361 Some((normalized, resolved, fact.indirection))
13362}
13363
13364fn decode_field_declared_type_fact(
13365 analyzer: &CppGraphSource<'_>,
13366 field: &CodeUnit,
13367) -> Option<DeclaredFieldTypeFact> {
13368 if let Some(facts) = analyzer.declaration_source_properties(field)
13369 && let Some(first) = facts.first().and_then(|fact| fact.field_type.as_ref())
13370 && facts
13371 .iter()
13372 .all(|fact| fact.field_type.as_ref() == Some(first))
13373 {
13374 return Some(first.clone());
13375 }
13376 let Some(declaration) = analyzer.get_source(field, false) else {
13377 return decode_indexed_field_declared_type_fact(analyzer, field);
13378 };
13379 let mut parser = Parser::new();
13380 parser
13381 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13382 .ok()?;
13383 let contextual_declaration = format!("struct __bifrost_field_context {{ {declaration} }};");
13387 let contextual_tree = parser.parse(&contextual_declaration, None)?;
13388 let mut stack = vec![contextual_tree.root_node()];
13389 while let Some(node) = stack.pop() {
13390 if let Some(recovered) = recovered_pyobject_head_field(node, &contextual_declaration)
13391 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13392 {
13393 return Some(DeclaredFieldTypeFact {
13394 type_text: node_text(recovered.type_node, &contextual_declaration).to_string(),
13395 indirection: recovered.pointer_depth(),
13396 binds_indirectly: recovered.pointer_depth() > 0,
13397 template_arguments: None,
13398 });
13399 }
13400 if let Some(recovered) =
13401 recovered_function_like_field_declarator(node, &contextual_declaration)
13402 && node_text(recovered.name, &contextual_declaration) == field.identifier()
13403 {
13404 let type_node = node
13405 .child_by_field_name("type")
13406 .or_else(|| first_type_child(node))?;
13407 return Some(DeclaredFieldTypeFact {
13408 type_text: node_text(type_node, &contextual_declaration).to_string(),
13409 indirection: recovered.pointer_depth(),
13410 binds_indirectly: true,
13413 template_arguments: cpp_template_reference_arguments(
13414 type_node,
13415 &contextual_declaration,
13416 ),
13417 });
13418 }
13419 if let Some(fact) =
13420 decode_declared_field_type_node(node, field.identifier(), &contextual_declaration)
13421 {
13422 return Some(fact);
13423 }
13424 let mut cursor = node.walk();
13425 stack.extend(node.named_children(&mut cursor));
13426 }
13427 let tree = parser.parse(&declaration, None)?;
13428 let mut stack = vec![tree.root_node()];
13429 while let Some(node) = stack.pop() {
13430 if let Some(fact) = decode_declared_field_type_node(node, field.identifier(), &declaration)
13431 {
13432 return Some(fact);
13433 }
13434 let mut cursor = node.walk();
13435 stack.extend(node.named_children(&mut cursor));
13436 }
13437 None
13438}
13439
13440fn decode_indexed_field_declared_type_fact(
13445 analyzer: &CppGraphSource<'_>,
13446 field: &CodeUnit,
13447) -> Option<DeclaredFieldTypeFact> {
13448 let cpp = analyzer.cpp?;
13449 let prepared = cpp.prepared_syntax(analyzer.token, field.source())?;
13450 let source = prepared.source();
13451 let root = prepared.tree().root_node();
13452 for range in analyzer.ranges(field) {
13453 let end = range.start_byte.saturating_add(1).min(source.len());
13454 let mut current = root.descendant_for_byte_range(range.start_byte, end);
13455 while let Some(node) = current {
13456 if matches!(node.kind(), "declaration" | "field_declaration")
13457 && let Some(fact) =
13458 decode_declared_field_type_node(node, field.identifier(), source)
13459 {
13460 return Some(fact);
13461 }
13462 current = node.parent();
13463 }
13464 }
13465 None
13466}
13467
13468fn decode_declared_field_type_node(
13469 node: Node<'_>,
13470 field_name: &str,
13471 source: &str,
13472) -> Option<DeclaredFieldTypeFact> {
13473 if !matches!(node.kind(), "declaration" | "field_declaration") {
13474 return None;
13475 }
13476 let type_node = node
13477 .child_by_field_name("type")
13478 .or_else(|| first_type_child(node))?;
13479 cpp_declared_field_type_fact(node, type_node, field_name, source)
13480}
13481
13482pub(crate) fn cpp_declared_field_type_fact(
13483 node: Node<'_>,
13484 type_node: Node<'_>,
13485 field_name: &str,
13486 source: &str,
13487) -> Option<DeclaredFieldTypeFact> {
13488 let binding = declared_name_binding(node, type_node, field_name, source)?;
13489 let declared_type = if matches!(
13490 type_node.kind(),
13491 "class_specifier" | "struct_specifier" | "union_specifier"
13492 ) {
13493 type_node.child_by_field_name("name")
13494 } else {
13495 Some(type_node)
13496 };
13497 Some(DeclaredFieldTypeFact {
13498 type_text: declared_type.map_or_else(
13499 || field_name.to_string(),
13500 |declared_type| node_text(declared_type, source).to_string(),
13501 ),
13502 indirection: binding.pointer_depth,
13503 binds_indirectly: binding.indirect,
13504 template_arguments: declared_type
13505 .and_then(|declared_type| cpp_template_reference_arguments(declared_type, source)),
13506 })
13507}
13508
13509pub fn cpp_alias_declaration_target_text(declaration: &str) -> Option<String> {
13523 let mut parser = Parser::new();
13524 parser
13525 .set_language(&tree_sitter_cpp::LANGUAGE.into())
13526 .ok()?;
13527 let tree = parser.parse(declaration, None)?;
13528 let mut stack = vec![tree.root_node()];
13529 while let Some(node) = stack.pop() {
13530 let type_node = match node.kind() {
13531 "type_definition" => {
13532 let mut cursor = node.walk();
13533 if node
13534 .children_by_field_name("declarator", &mut cursor)
13535 .any(declarator_names_function_type)
13536 {
13537 return None;
13538 }
13539 node.child_by_field_name("type")?
13540 }
13541 "alias_declaration" => {
13542 let type_node = node.child_by_field_name("type")?;
13543 if type_node
13544 .child_by_field_name("declarator")
13545 .is_some_and(declarator_names_function_type)
13546 {
13547 return None;
13548 }
13549 type_node
13550 }
13551 _ => {
13552 let mut cursor = node.walk();
13553 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
13554 stack.extend(children.into_iter().rev());
13555 continue;
13556 }
13557 };
13558 return Some(node_text(type_node, declaration).to_string());
13559 }
13560 None
13561}
13562
13563pub(crate) fn declarator_names_function_type(declarator: Node<'_>) -> bool {
13569 let mut current = Some(declarator);
13570 while let Some(node) = current {
13571 match node.kind() {
13572 "function_declarator" | "abstract_function_declarator" => return true,
13573 "parenthesized_declarator" | "abstract_parenthesized_declarator" => {
13574 current = node.named_child(0);
13575 }
13576 _ => current = node.child_by_field_name("declarator"),
13577 }
13578 }
13579 false
13580}
13581
13582fn decode_structured_alias_target(
13583 analyzer: &CppGraphSource<'_>,
13584 unit: &CodeUnit,
13585) -> Option<StructuredAliasTarget> {
13586 let facts = analyzer.declaration_source_properties(unit)?;
13587 let first = facts.first()?.alias_target.as_ref()?;
13588 facts
13589 .iter()
13590 .all(|fact| fact.alias_target.as_ref() == Some(first))
13591 .then(|| first.clone())
13592}
13593
13594pub(crate) fn structured_alias_type_target(
13595 mut type_node: Node<'_>,
13596 source: &str,
13597) -> Option<StructuredAliasTarget> {
13598 while type_node.kind() == "type_descriptor" {
13599 type_node = type_node.child_by_field_name("type")?;
13600 }
13601 if type_node.kind() == "primitive_type" {
13602 return Some(StructuredAliasTarget::Builtin);
13603 }
13604 if matches!(
13605 type_node.kind(),
13606 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
13607 ) {
13608 type_node = type_node.child_by_field_name("name")?;
13609 }
13610 let global = type_node.child_by_field_name("scope").is_none()
13611 && type_node.child(0).is_some_and(|child| child.kind() == "::");
13612 let mut components = Vec::new();
13613 append_structured_type_components(type_node, source, &mut components)?;
13614 let arguments = cpp_template_reference_arguments(type_node, source);
13615 (!components.is_empty()).then_some(StructuredAliasTarget::Named {
13616 components,
13617 global,
13618 arguments,
13619 })
13620}
13621
13622fn append_structured_type_components(
13623 node: Node<'_>,
13624 source: &str,
13625 out: &mut Vec<String>,
13626) -> Option<()> {
13627 match node.kind() {
13628 "identifier" | "namespace_identifier" | "type_identifier" => {
13629 out.push(node_text(node, source).to_string());
13630 Some(())
13631 }
13632 "template_type" | "template_function" => {
13633 append_structured_type_components(node.child_by_field_name("name")?, source, out)
13634 }
13635 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
13636 let (scope, name) = qualified_name_parts(node)?;
13637 if let Some(scope) = scope {
13638 append_structured_type_components(scope, source, out)?;
13639 }
13640 append_structured_type_components(name, source, out)
13641 }
13642 _ => None,
13643 }
13644}
13645
13646pub(crate) struct DeclaredNameBinding {
13649 pub pointer_depth: i32,
13651 pub indirect: bool,
13655}
13656
13657pub(crate) fn declared_name_binding(
13658 declaration: Node<'_>,
13659 type_node: Node<'_>,
13660 field_name: &str,
13661 source: &str,
13662) -> Option<DeclaredNameBinding> {
13663 let mut stack = Vec::new();
13664 let mut cursor = declaration.walk();
13665 stack.extend(
13666 declaration
13667 .named_children(&mut cursor)
13668 .filter(|child| !same_node(*child, type_node)),
13669 );
13670 while let Some(node) = stack.pop() {
13671 if matches!(node.kind(), "identifier" | "field_identifier")
13672 && node_text(node, source) == field_name
13673 {
13674 let mut binding = DeclaredNameBinding {
13675 pointer_depth: 0,
13676 indirect: false,
13677 };
13678 let mut current = node.parent();
13679 while let Some(parent) = current {
13680 if same_node(parent, declaration) {
13681 return Some(binding);
13682 }
13683 match parent.kind() {
13684 "pointer_declarator" => {
13685 binding.pointer_depth += 1;
13686 binding.indirect = true;
13687 }
13688 "reference_declarator" => binding.indirect = true,
13689 _ => {}
13690 }
13691 current = parent.parent();
13692 }
13693 return None;
13694 }
13695 let mut cursor = node.walk();
13696 stack.extend(node.named_children(&mut cursor));
13697 }
13698 None
13699}
13700
13701fn normalize_field_type_text(type_text: &str) -> String {
13702 const FIELD_SPECIFIERS: [&str; 8] = [
13703 "extern ",
13704 "static ",
13705 "mutable ",
13706 "constexpr ",
13707 "constinit ",
13708 "inline ",
13709 "volatile ",
13710 "const ",
13711 ];
13712
13713 let mut normalized = normalize_type_text(type_text);
13714 loop {
13715 let Some(stripped) = FIELD_SPECIFIERS
13716 .iter()
13717 .find_map(|specifier| normalized.strip_prefix(specifier))
13718 else {
13719 return normalized;
13720 };
13721 normalized = normalize_type_text(stripped);
13722 }
13723}
13724
13725pub fn declaration_mentions_type(node: Node<'_>, ctx: &ScanCtx<'_>, owner: &CodeUnit) -> bool {
13726 let Some(type_node) = node.child_by_field_name("type") else {
13727 return false;
13728 };
13729 ctx.visibility.resolves_to_type(
13730 &ctx.analyzer,
13731 ctx.file,
13732 node_text(type_node, ctx.source),
13733 owner,
13734 )
13735}
13736
13737pub fn declaration_is_object_construction_candidate(node: Node<'_>, ctx: &ScanCtx<'_>) -> bool {
13738 !ctx.analyzer
13739 .declarations(ctx.file)
13740 .into_iter()
13741 .filter(|unit| unit.is_function())
13742 .any(|unit| {
13743 ctx.analyzer.ranges(&unit).iter().any(|range| {
13744 node.start_byte() <= range.start_byte && range.end_byte <= node.end_byte()
13745 })
13746 })
13747}
13748
13749pub enum DeclarationConstructorInitializer<'tree> {
13751 Arguments(Node<'tree>),
13754 Expression(Node<'tree>),
13757 Empty,
13759}
13760
13761pub fn declaration_constructor_initializer(
13762 node: Node<'_>,
13763) -> DeclarationConstructorInitializer<'_> {
13764 let mut cursor = node.walk();
13765 for child in node.named_children(&mut cursor) {
13766 if child.kind() == "init_declarator" {
13767 let Some(value) = child
13768 .child_by_field_name("value")
13769 .or_else(|| first_named_child_of_kind(child, "initializer_list"))
13770 .or_else(|| first_named_child_of_kind(child, "compound_literal_expression"))
13771 else {
13772 return DeclarationConstructorInitializer::Empty;
13773 };
13774 return match value.kind() {
13775 "argument_list" | "initializer_list" => {
13776 DeclarationConstructorInitializer::Arguments(value)
13777 }
13778 "compound_literal_expression" => call_arguments_node(value)
13779 .map_or(DeclarationConstructorInitializer::Empty, |arguments| {
13780 DeclarationConstructorInitializer::Arguments(arguments)
13781 }),
13782 _ => DeclarationConstructorInitializer::Expression(value),
13783 };
13784 }
13785 if let Some(declarator) = declaration_declarator(node, child) {
13786 return declarator_parameters(declarator)
13787 .map_or(DeclarationConstructorInitializer::Empty, |parameters| {
13788 DeclarationConstructorInitializer::Arguments(parameters)
13789 });
13790 }
13791 }
13792 DeclarationConstructorInitializer::Empty
13793}
13794
13795pub fn declaration_constructor_arity(node: Node<'_>, _ctx: &ScanCtx<'_>) -> usize {
13796 match declaration_constructor_initializer(node) {
13797 DeclarationConstructorInitializer::Arguments(arguments) => {
13798 argument_children(arguments).count()
13799 }
13800 DeclarationConstructorInitializer::Expression(_) => 1,
13801 DeclarationConstructorInitializer::Empty => 0,
13802 }
13803}
13804
13805fn declarator_parameters(node: Node<'_>) -> Option<Node<'_>> {
13809 let mut current = node;
13810 loop {
13811 if let Some(parameters) = current.child_by_field_name("parameters") {
13812 return Some(parameters);
13813 }
13814 current = current.child_by_field_name("declarator")?;
13815 }
13816}
13817
13818fn first_descendant_of_kind<'tree>(root: Node<'tree>, kind: &str) -> Option<Node<'tree>> {
13819 brokk_bifrost_core::analyzer::tree_walk::first_named_descendant_of_kind_inclusive(root, kind)
13820}
13821
13822fn argument_shape_may_change_arity(node: Node<'_>) -> bool {
13823 if node.kind() == "identifier" {
13824 return true;
13825 }
13826 if node.kind() == "parenthesized_expression" {
13827 return false;
13828 }
13829 if node.kind() == "call_expression" {
13830 return node
13831 .child_by_field_name("function")
13832 .is_some_and(|function| function.kind() == "identifier");
13833 }
13834 let mut stack = vec![node];
13835 while let Some(descendant) = stack.pop() {
13836 if descendant != node && descendant.kind() == "parenthesized_expression" {
13837 continue;
13838 }
13839 if descendant.kind() == "identifier" {
13840 return true;
13841 }
13842 if descendant.kind() == "call_expression" {
13843 if descendant
13844 .child_by_field_name("function")
13845 .is_some_and(|function| function.kind() == "identifier")
13846 {
13847 return true;
13848 }
13849 continue;
13850 }
13851 push_named_children_reversed(descendant, &mut stack);
13852 }
13853 false
13854}
13855
13856fn macro_expansion_shape_is_safe(
13857 node: Node<'_>,
13858 source: &str,
13859 parameters: &[String],
13860 environment: &MacroEnvironment,
13861) -> bool {
13862 if matches!(node.kind(), "identifier" | "parenthesized_expression") {
13863 return true;
13864 }
13865 if node.kind() == "call_expression" {
13866 let Some(function) = node.child_by_field_name("function") else {
13867 return true;
13868 };
13869 if function.kind() != "identifier" {
13870 return true;
13871 }
13872 let function_name = node_text(function, source);
13873 if parameters
13874 .iter()
13875 .any(|parameter| parameter == function_name)
13876 {
13877 return false;
13878 }
13879 if !environment.may_bind(function_name) {
13880 return true;
13881 }
13882 let Some(arguments) = node.child_by_field_name("arguments") else {
13883 return false;
13884 };
13885 return argument_children(arguments).all(|argument| {
13886 if argument.kind() == "identifier"
13887 && parameters
13888 .iter()
13889 .any(|parameter| parameter == node_text(argument, source))
13890 {
13891 return false;
13892 }
13893 macro_expansion_shape_is_safe(argument, source, parameters, environment)
13894 });
13895 }
13896 let mut stack = vec![node];
13897 while let Some(descendant) = stack.pop() {
13898 if descendant != node {
13899 if descendant.kind() == "parenthesized_expression" {
13900 continue;
13901 }
13902 if descendant.kind() == "call_expression" {
13903 let expands = descendant
13904 .child_by_field_name("function")
13905 .filter(|function| function.kind() == "identifier")
13906 .is_some_and(|function| environment.may_bind(node_text(function, source)));
13907 if expands {
13908 return false;
13909 }
13910 continue;
13911 }
13912 }
13913 if descendant.kind() == "identifier" {
13914 let identifier = node_text(descendant, source);
13915 if parameters.iter().any(|parameter| parameter == identifier)
13916 || environment.may_bind(identifier)
13917 {
13918 return false;
13919 }
13920 }
13921 push_named_children_reversed(descendant, &mut stack);
13922 }
13923 true
13924}
13925
13926fn collect_structured_include_facts(prepared: &PreparedSyntaxTree) -> Arc<[StructuredIncludeFact]> {
13927 let source = prepared.source();
13928 let mut facts = Vec::new();
13929 let mut nodes = vec![prepared.tree().root_node()];
13930 while let Some(node) = nodes.pop() {
13931 if node.kind() == "preproc_include" {
13932 let Some(path) = node
13933 .child_by_field_name("path")
13934 .and_then(|path| crate::imports::include_path_literal_value(path, source))
13935 .map(str::to_owned)
13936 else {
13937 continue;
13938 };
13939 facts.push(StructuredIncludeFact {
13940 start_byte: node.start_byte(),
13941 end_byte: node.end_byte(),
13942 path,
13943 });
13944 continue;
13945 }
13946 push_named_children_reversed(node, &mut nodes);
13947 }
13948 Arc::from(facts.into_boxed_slice())
13949}
13950
13951fn has_unresolved_include_visible_before_in_prepared(
13952 file: &ProjectFile,
13953 prepared: &PreparedSyntaxTree,
13954 include_targets: &IncludeTargetIndex,
13955 facts: &[StructuredIncludeFact],
13956 before_byte: usize,
13957) -> bool {
13958 let guards = OnceCell::new();
13959 let reference = CallableReferenceContext {
13960 file,
13961 position: Some(CallableReferencePosition {
13962 prepared,
13963 byte: before_byte,
13964 guards: &guards,
13965 }),
13966 };
13967 let root = prepared.tree().root_node();
13968 facts
13969 .iter()
13970 .filter(|fact| fact.end_byte <= before_byte)
13971 .any(|fact| {
13972 let node = root
13973 .descendant_for_byte_range(fact.start_byte, fact.end_byte)
13974 .expect("structured include fact range must be in prepared tree");
13975 assert_eq!(
13976 node.kind(),
13977 "preproc_include",
13978 "structured include fact range must identify its include node"
13979 );
13980 callable_preprocessor_context_is_visible_for_reference(
13981 node,
13982 prepared.source(),
13983 &reference,
13984 ) && resolve_include_targets_with_index(file, &fact.path, include_targets).is_empty()
13985 })
13986}
13987
13988fn has_preprocessor_conditional_ancestor(mut node: Node<'_>, source: &str) -> bool {
13989 let descendant = node;
13990 while let Some(parent) = node.parent() {
13991 if is_cpp_preprocessor_conditional(parent)
13992 && !is_file_covering_include_guard(parent, source)
13993 && preprocessor_conditional_contains_descendant(parent, descendant)
13994 {
13995 return true;
13996 }
13997 node = parent;
13998 }
13999 false
14000}
14001
14002fn owning_preprocessor_conditionals(
14014 root: Node<'_>,
14015 event: Node<'_>,
14016 source: &str,
14017) -> OwningPreprocessorConditionals {
14018 if !has_preprocessor_conditional_ancestor(event, source) {
14019 return OwningPreprocessorConditionals::default();
14020 }
14021 let start = event.start_byte();
14022 let descendant = root
14023 .descendant_for_byte_range(start, start.saturating_add(1).min(source.len()))
14024 .expect("a byte inside the parsed tree names a descendant");
14025 let mut owners = Vec::new();
14026 let mut current = descendant.parent();
14027 while let Some(conditional) = current {
14028 if is_cpp_preprocessor_conditional(conditional)
14029 && !is_file_covering_include_guard(conditional, source)
14030 && preprocessor_conditional_contains_descendant(conditional, descendant)
14031 {
14032 owners.push(conditional.start_byte());
14033 }
14034 current = conditional.parent();
14035 }
14036 owners.into_boxed_slice()
14037}
14038
14039fn is_file_covering_include_guard(node: Node<'_>, source: &str) -> bool {
14040 node.parent()
14041 .filter(|parent| parent.kind() == "translation_unit")
14042 .is_some_and(|root| top_level_canonical_include_guard_name(root, source).is_some())
14043 && is_canonical_include_guard(node, source)
14044}
14045
14046fn is_canonical_include_guard(node: Node<'_>, source: &str) -> bool {
14047 if node.kind() != "preproc_ifdef"
14048 || node
14049 .child(0)
14050 .is_none_or(|directive| directive.kind() != "#ifndef")
14051 || node.child_by_field_name("alternative").is_some()
14052 {
14053 return false;
14054 }
14055 let Some(guard_name) = node.child_by_field_name("name") else {
14056 return false;
14057 };
14058 let mut cursor = node.walk();
14059 node.named_children(&mut cursor)
14060 .find(|child| *child != guard_name && child.kind() != "comment")
14061 .filter(|child| child.kind() == "preproc_def")
14062 .and_then(|definition| definition.child_by_field_name("name"))
14063 .is_some_and(|defined_name| {
14064 node_text(defined_name, source) == node_text(guard_name, source)
14065 })
14066}
14067
14068fn top_level_canonical_include_guard_name(root: Node<'_>, source: &str) -> Option<String> {
14069 let mut guard = None;
14070 for child in named_children_iter(root) {
14071 if child.kind() == "comment" || is_pragma_once(child, source) {
14072 continue;
14073 }
14074 if guard.is_none() && is_canonical_include_guard(child, source) {
14075 guard = Some(child);
14076 } else {
14077 return None;
14078 }
14079 }
14080 guard
14081 .and_then(|guard: Node<'_>| guard.child_by_field_name("name"))
14082 .map(|name| node_text(name, source).to_string())
14083}
14084
14085fn top_level_macro_include_protection(root: Node<'_>, source: &str) -> MacroIncludeProtection {
14086 if (0..root.named_child_count())
14087 .filter_map(|index| root.named_child(index))
14088 .any(|child| is_pragma_once(child, source))
14089 {
14090 return MacroIncludeProtection::PragmaOnce;
14091 }
14092 top_level_canonical_include_guard_name(root, source)
14093 .map(MacroIncludeProtection::MacroGuard)
14094 .unwrap_or(MacroIncludeProtection::None)
14095}
14096
14097fn is_pragma_once(node: Node<'_>, source: &str) -> bool {
14098 node.kind() == "preproc_call"
14099 && node
14100 .child_by_field_name("directive")
14101 .is_some_and(|directive| node_text(directive, source) == "#pragma")
14102 && node
14103 .child_by_field_name("argument")
14104 .is_some_and(|argument| node_text(argument, source).trim() == "once")
14105}
14106
14107fn parse_preproc_identifier(argument: &str) -> Option<String> {
14108 let sentinel = format!("void __bifrost_undef() {{ {argument}; }}");
14109 let mut parser = Parser::new();
14110 parser
14111 .set_language(&tree_sitter_cpp::LANGUAGE.into())
14112 .ok()?;
14113 let tree = parser.parse(&sentinel, None)?;
14114 if tree.root_node().has_error() {
14115 return None;
14116 }
14117 let statement = first_descendant_of_kind(tree.root_node(), "expression_statement")?;
14118 let identifier = statement.named_child(0)?;
14119 (identifier.kind() == "identifier" && statement.named_child_count() == 1)
14120 .then(|| node_text(identifier, &sentinel).to_string())
14121}
14122
14123pub fn extract_variable_name(node: Node<'_>, source: &str) -> Option<String> {
14124 match node.kind() {
14125 "identifier" | "field_identifier" => {
14126 let name = node_text(node, source).trim();
14127 (!name.is_empty()).then(|| name.to_string())
14128 }
14129 "abstract_array_declarator"
14130 | "abstract_function_declarator"
14131 | "abstract_parenthesized_declarator"
14132 | "abstract_pointer_declarator"
14133 | "abstract_reference_declarator" => None,
14134 "function_declarator" => node
14135 .child_by_field_name("declarator")
14136 .or_else(|| node.child_by_field_name("name"))
14137 .and_then(|child| extract_variable_name(child, source)),
14138 _ => node
14139 .child_by_field_name("declarator")
14140 .or_else(|| node.child_by_field_name("name"))
14141 .or_else(|| node.named_child(node.named_child_count().saturating_sub(1)))
14142 .and_then(|child| extract_variable_name(child, source)),
14143 }
14144}
14145
14146pub fn is_c_source_file(file: &ProjectFile) -> bool {
14157 LanguageDialect::for_path(Language::Cpp, file.rel_path()) == LanguageDialect::CppC
14158}
14159
14160pub fn is_c_sizeof_expression_type_candidate(file: &ProjectFile, node: Node<'_>) -> bool {
14167 if !is_c_source_file(file) || node.kind() != "identifier" {
14168 return false;
14169 }
14170 let mut operand = node;
14171 while let Some(parent) = operand.parent().filter(|parent| {
14172 parent.kind() == "parenthesized_expression"
14173 && parent.named_child_count() == 1
14174 && parent.named_child(0) == Some(operand)
14175 }) {
14176 operand = parent;
14177 }
14178 operand.parent().is_some_and(|parent| {
14179 parent.kind() == "sizeof_expression" && parent.child_by_field_name("value") == Some(operand)
14180 })
14181}
14182
14183pub fn c_offsetof_member_parts(node: Node<'_>) -> Option<(Node<'_>, Node<'_>)> {
14192 if !is_c_offsetof_member_node(node) {
14193 return None;
14194 }
14195 let expression = node.parent()?;
14196 if expression.has_error() {
14197 return None;
14198 }
14199 let closing = expression.child(expression.child_count().saturating_sub(1))?;
14200 if closing.kind() != ")" || closing.is_missing() {
14201 return None;
14202 }
14203 let type_descriptor = expression.child_by_field_name("type")?;
14204 if type_descriptor.kind() != "type_descriptor"
14205 || type_descriptor.is_missing()
14206 || type_descriptor.has_error()
14207 {
14208 return None;
14209 }
14210 let type_specifier = type_descriptor.child_by_field_name("type")?;
14211 if type_specifier.is_missing() || type_specifier.has_error() {
14212 return None;
14213 }
14214 let type_reference = match type_specifier.kind() {
14215 "class_specifier" | "struct_specifier" | "union_specifier" => {
14216 type_specifier.child_by_field_name("name")?
14217 }
14218 _ => type_specifier,
14219 };
14220 (!type_reference.is_missing() && !type_reference.has_error()).then_some((type_reference, node))
14221}
14222
14223pub fn is_c_offsetof_member_node(node: Node<'_>) -> bool {
14228 node.kind() == "field_identifier"
14229 && node.parent().is_some_and(|parent| {
14230 parent.kind() == "offsetof_expression"
14231 && parent.child_by_field_name("member") == Some(node)
14232 })
14233}
14234
14235pub fn cpp_template_function_has_type_owner(node: Node<'_>) -> bool {
14239 if node.kind() != "template_function" {
14240 return false;
14241 }
14242 let Some(arguments) = node
14243 .parent()
14244 .filter(|parent| parent.kind() == "template_argument_list")
14245 else {
14246 return false;
14247 };
14248 let Some(owner) = arguments.parent().filter(|parent| {
14249 parent.kind() == "template_type"
14250 && parent.child_by_field_name("arguments") == Some(arguments)
14251 }) else {
14252 return false;
14253 };
14254
14255 let mut current = owner;
14256 while let Some(parent) = current.parent() {
14257 if parent.kind() == "type_descriptor" && parent.child_by_field_name("type") == Some(current)
14258 {
14259 return true;
14260 }
14261 if parent.kind() == "qualified_identifier"
14262 && parent.child_by_field_name("name") == Some(current)
14263 {
14264 current = parent;
14265 continue;
14266 }
14267 return false;
14268 }
14269 false
14270}
14271
14272pub fn is_type_shaped_template_argument_name(node: Node<'_>) -> bool {
14288 if node.kind() != "type_identifier" {
14289 return false;
14290 }
14291 let Some(descriptor) = node
14292 .parent()
14293 .filter(|parent| parent.kind() == "type_descriptor")
14294 else {
14295 return false;
14296 };
14297 if descriptor.child_by_field_name("type") != Some(node) {
14298 return false;
14299 }
14300 let Some(arguments) = descriptor
14301 .parent()
14302 .filter(|parent| parent.kind() == "template_argument_list")
14303 else {
14304 return false;
14305 };
14306 arguments.parent().is_some_and(|owner| {
14307 matches!(
14308 owner.kind(),
14309 "template_type" | "template_function" | "template_method"
14310 ) && owner.child_by_field_name("arguments") == Some(arguments)
14311 })
14312}
14313
14314pub fn reference_uses_c_semantics(cpp: &dyn CppSource, file: &ProjectFile) -> bool {
14327 is_c_source_file(file) || cpp.header_uses_c_semantics(file)
14328}
14329
14330pub fn is_declarator_node(node: Node<'_>) -> bool {
14331 matches!(
14332 node.kind(),
14333 "identifier"
14334 | "field_identifier"
14335 | "qualified_identifier"
14336 | "scoped_identifier"
14337 | "pointer_declarator"
14338 | "reference_declarator"
14339 | "array_declarator"
14340 | "parenthesized_declarator"
14341 | "function_declarator"
14342 )
14343}
14344
14345pub fn declaration_declarator<'tree>(
14354 declaration: Node<'tree>,
14355 child: Node<'tree>,
14356) -> Option<Node<'tree>> {
14357 if !matches!(
14358 declaration.kind(),
14359 "declaration"
14360 | "field_declaration"
14361 | "parameter_declaration"
14362 | "optional_parameter_declaration"
14363 | "function_definition"
14364 | "type_definition"
14365 | "alias_declaration"
14366 | "template_instantiation"
14367 ) {
14368 return None;
14369 }
14370 if declaration
14371 .child_by_field_name("type")
14372 .is_some_and(|type_node| same_node(type_node, child))
14373 {
14374 return None;
14375 }
14376 if child.kind() == "init_declarator" {
14377 return child.child_by_field_name("declarator");
14378 }
14379 let field = field_name_in_parent(declaration, child);
14380 if (is_declarator_node(child) && matches!(field, Some("declarator") | None))
14381 || (declaration.kind() == "type_definition"
14382 && child.kind() == "type_identifier"
14383 && matches!(field, Some("declarator") | None))
14384 {
14385 Some(child)
14386 } else {
14387 None
14388 }
14389}
14390
14391pub fn cpp_binding_parts<'tree>(
14393 node: Node<'tree>,
14394 source: &str,
14395) -> Option<(String, Node<'tree>, Option<Node<'tree>>)> {
14396 let declarator = node.child_by_field_name("declarator")?;
14397 let name = extract_variable_name(declarator, source)?;
14398 let type_node = node
14399 .child_by_field_name("type")
14400 .or_else(|| first_type_child(node));
14401 Some((name, declarator, type_node))
14402}
14403
14404#[derive(Clone, Debug, PartialEq, Eq)]
14407pub struct RecoveredNamespaceRegion {
14408 pub start: usize,
14410 pub end: usize,
14412 pub components: Vec<String>,
14415 pub component_ranges: Vec<Range>,
14426 pub component_name_ranges: Vec<Range>,
14432 pub component_closes: Vec<Option<Range>>,
14438}
14439
14440#[derive(Clone, Debug, Default)]
14505pub struct OrphanedNamespaceScopeIndex {
14506 regions: Vec<RecoveredNamespaceRegion>,
14507 brace_closes: HashMap<usize, Range>,
14510}
14511
14512impl OrphanedNamespaceScopeIndex {
14513 pub fn build(root: Node<'_>, source: &str) -> Self {
14514 if !root.has_error() {
14515 return Self::default();
14516 }
14517 #[derive(Clone, Copy)]
14523 struct OpenHead {
14524 head: Range,
14525 open: usize,
14526 }
14527 struct PendingRegion {
14530 start: usize,
14531 end: usize,
14532 components: Vec<String>,
14533 heads: Vec<OpenHead>,
14534 name_ranges: Vec<Range>,
14535 }
14536 struct Frame<'tree> {
14537 node: Node<'tree>,
14538 children: Vec<Node<'tree>>,
14539 next: usize,
14540 parsed_scope: Vec<String>,
14541 run: Option<PendingRegion>,
14542 }
14543 fn frame<'tree>(
14544 node: Node<'tree>,
14545 mut parsed_scope: Vec<String>,
14546 source: &str,
14547 ) -> Frame<'tree> {
14548 if node.kind() == "namespace_definition"
14549 && let Some(name) = node.child_by_field_name("name")
14550 {
14551 let mut components = Vec::new();
14552 if append_cpp_name_components(name, source, &mut components).is_some() {
14553 parsed_scope.extend(components);
14554 }
14555 }
14556 let mut cursor = node.walk();
14557 Frame {
14558 node,
14559 children: node.children(&mut cursor).collect(),
14560 next: 0,
14561 parsed_scope,
14562 run: None,
14563 }
14564 }
14565 let mut regions = Vec::new();
14566 let mut brace_closes = HashMap::default();
14567 let mut namespace_brace_proofs = Vec::new();
14574 let mut unpaired_closes: Vec<Range> = Vec::new();
14580 let mut open = Vec::new();
14584 let mut lexical_scope = Vec::new();
14585 let mut lexical_heads: Vec<OpenHead> = Vec::new();
14590 let mut lexical_name_ranges: Vec<Range> = Vec::new();
14593 let mut frames = vec![frame(root, Vec::new(), source)];
14594 while !frames.is_empty() {
14599 let parent_node = frames.len().checked_sub(2).map(|index| frames[index].node);
14600 let current = frames.last_mut().expect("frames is non-empty");
14601 if current.next == current.children.len() {
14602 regions.extend(frames.pop().expect("the frame just borrowed").run);
14603 continue;
14604 }
14605 let child = current.children[current.next];
14606 current.next += 1;
14607 match child.kind() {
14608 "{" if !child.is_missing() => {
14609 regions.extend(current.run.take());
14610 if let Some(namespace) = parent_node.filter(|parent| {
14611 parent.kind() == "namespace_definition"
14612 && parent.child_by_field_name("body") == Some(current.node)
14613 && child.start_byte() == current.node.start_byte()
14614 }) && let Some(proof) = namespace_body_brace_proof(current.node)
14615 {
14616 namespace_brace_proofs.push((child.start_byte(), namespace, proof));
14617 }
14618 let mut named = parent_node.and_then(|parent| {
14623 namespace_body_name_components(parent, current.node, source)
14624 });
14625 if named.is_none() {
14626 named = recovered_namespace_open_components(
14627 ¤t.children[..current.next - 1],
14628 source,
14629 );
14630 }
14631 open.push((child.start_byte(), lexical_scope.len()));
14632 if let Some((written_at, components)) = named {
14633 let head = OpenHead {
14634 head: Range {
14635 start_byte: written_at.start_byte(),
14636 end_byte: child.end_byte(),
14637 start_line: written_at.start_position().row + 1,
14638 end_line: child.end_position().row + 1,
14639 },
14640 open: child.start_byte(),
14641 };
14642 lexical_heads.extend(std::iter::repeat_n(head, components.len()));
14643 lexical_name_ranges
14644 .extend(components.iter().map(|(_, name_range)| *name_range));
14645 lexical_scope
14646 .extend(components.into_iter().map(|(component, _)| component));
14647 }
14648 continue;
14649 }
14650 "}" if !child.is_missing() => {
14651 regions.extend(current.run.take());
14652 let close = Range {
14653 start_byte: child.start_byte(),
14654 end_byte: child.end_byte(),
14655 start_line: child.start_position().row + 1,
14656 end_line: child.end_position().row + 1,
14657 };
14658 match open.pop() {
14659 Some((start, namespace_len)) => {
14660 debug_assert!(
14661 start < close.start_byte,
14662 "a close pairs with an open before it: {start} then {close:?}"
14663 );
14664 lexical_scope.truncate(namespace_len);
14665 lexical_heads.truncate(namespace_len);
14666 lexical_name_ranges.truncate(namespace_len);
14667 brace_closes.insert(start, close);
14668 }
14669 None => unpaired_closes.push(close),
14670 }
14671 continue;
14672 }
14673 _ => {}
14674 }
14675 if current.node.kind() != "namespace_definition"
14679 && lexical_scope != current.parsed_scope
14680 {
14681 match &mut current.run {
14682 Some(run) if run.components == lexical_scope => run.end = child.end_byte(),
14683 run => {
14684 regions.extend(run.take());
14685 *run = Some(PendingRegion {
14686 start: child.start_byte(),
14687 end: child.end_byte(),
14688 components: lexical_scope.clone(),
14689 heads: lexical_heads.clone(),
14690 name_ranges: lexical_name_ranges.clone(),
14691 });
14692 }
14693 }
14694 } else {
14695 regions.extend(current.run.take());
14696 }
14697 if child.has_error() {
14701 let parsed_scope = current.parsed_scope.clone();
14702 frames.push(frame(child, parsed_scope, source));
14703 }
14704 }
14705 if !unpaired_closes.is_empty() {
14714 brace_closes.clear();
14715 } else {
14716 let missing_namespaces = namespace_brace_proofs
14722 .iter()
14723 .filter_map(|(_, namespace, proof)| {
14724 matches!(proof, NamespaceBodyBraceProof::Missing).then_some(namespace.id())
14725 })
14726 .collect::<HashSet<_>>();
14727 for (open, namespace, proof) in namespace_brace_proofs {
14728 if !std::iter::successors(Some(namespace), |node| node.parent())
14729 .any(|ancestor| missing_namespaces.contains(&ancestor.id()))
14730 {
14731 continue;
14732 }
14733 match proof {
14734 NamespaceBodyBraceProof::Real(close) => {
14735 brace_closes.insert(open, close);
14736 }
14737 NamespaceBodyBraceProof::Missing => {
14738 brace_closes.remove(&open);
14739 }
14740 }
14741 }
14742 }
14743 let regions = regions
14748 .into_iter()
14749 .map(|region| {
14750 debug_assert_eq!(
14751 region.components.len(),
14752 region.heads.len(),
14753 "every recovered namespace level carries the head that writes it: {:?}",
14754 region.components
14755 );
14756 debug_assert_eq!(
14757 region.components.len(),
14758 region.name_ranges.len(),
14759 "every recovered namespace level carries the name token that writes it: {:?}",
14760 region.components
14761 );
14762 let component_closes: Vec<_> = region
14763 .heads
14764 .iter()
14765 .map(|level| brace_closes.get(&level.open).copied())
14766 .collect();
14767 let component_ranges = region
14768 .heads
14769 .iter()
14770 .zip(&component_closes)
14771 .map(|(level, close)| match close {
14772 Some(close) => Range {
14773 start_byte: level.head.start_byte,
14774 end_byte: close.end_byte,
14775 start_line: level.head.start_line,
14776 end_line: close.end_line,
14777 },
14778 None => level.head,
14779 })
14780 .collect();
14781 RecoveredNamespaceRegion {
14782 start: region.start,
14783 end: region.end,
14784 components: region.components,
14785 component_ranges,
14786 component_name_ranges: region.name_ranges,
14787 component_closes,
14788 }
14789 })
14790 .collect();
14791 Self {
14792 regions,
14793 brace_closes,
14794 }
14795 }
14796
14797 pub fn matching_close_brace(&self, open: usize) -> Option<Range> {
14804 self.brace_closes.get(&open).copied()
14805 }
14806
14807 pub fn is_empty(&self) -> bool {
14808 self.regions.is_empty()
14809 }
14810
14811 pub fn approximate_size(&self) -> usize {
14813 self.regions.iter().fold(
14814 self.brace_closes.len() * std::mem::size_of::<(usize, Range)>(),
14815 |total, region| {
14816 total
14817 .saturating_add(std::mem::size_of::<RecoveredNamespaceRegion>())
14818 .saturating_add(region.components.iter().map(String::len).sum::<usize>())
14819 .saturating_add(region.component_ranges.len() * std::mem::size_of::<Range>())
14820 .saturating_add(
14821 region.component_name_ranges.len() * std::mem::size_of::<Range>(),
14822 )
14823 .saturating_add(
14824 region.component_closes.len() * std::mem::size_of::<Option<Range>>(),
14825 )
14826 },
14827 )
14828 }
14829
14830 pub fn region_at(&self, byte: usize) -> Option<&RecoveredNamespaceRegion> {
14832 self.regions
14833 .iter()
14834 .filter(|region| region.start <= byte && byte < region.end)
14835 .min_by_key(|region| region.end - region.start)
14836 }
14837
14838 pub fn enclosing_namespace_components(&self, node: Node<'_>, source: &str) -> Vec<String> {
14842 self.enclosing_namespace_levels(node, source)
14843 .into_iter()
14844 .map(|(component, ..)| component)
14845 .collect()
14846 }
14847
14848 pub fn enclosing_namespace_levels(
14857 &self,
14858 node: Node<'_>,
14859 source: &str,
14860 ) -> Vec<(String, Range, Range)> {
14861 let mut parsed = Vec::new();
14862 let mut current = node.parent();
14863 while let Some(parent) = current {
14864 if parent.kind() == "namespace_definition"
14865 && let Some(name) = parent.child_by_field_name("name")
14866 && let Some(components) = cpp_name_component_ranges(name, source)
14867 {
14868 let range = node_range(parent);
14869 parsed.push((
14870 parent.start_byte(),
14871 components
14872 .into_iter()
14873 .map(|(component, name_range)| (component, range, name_range))
14874 .collect(),
14875 namespace_definition_has_real_braces(parent),
14876 ));
14877 }
14878 current = parent.parent();
14879 }
14880 parsed.reverse();
14881 self.splice_recovered_path(parsed, node.start_byte(), |region, active_components| {
14882 region
14883 .components
14884 .iter()
14885 .take(active_components)
14886 .cloned()
14887 .zip(
14888 region
14889 .component_ranges
14890 .iter()
14891 .take(active_components)
14892 .copied(),
14893 )
14894 .zip(
14895 region
14896 .component_name_ranges
14897 .iter()
14898 .take(active_components)
14899 .copied(),
14900 )
14901 .map(|((component, range), name_range)| (component, range, name_range))
14902 .collect()
14903 })
14904 }
14905
14906 pub fn restore_enclosing_namespaces(
14912 &self,
14913 parsed: Vec<(usize, Vec<String>, bool)>,
14914 node_start: usize,
14915 ) -> Vec<String> {
14916 self.splice_recovered_path(parsed, node_start, |region, active_components| {
14917 region
14918 .components
14919 .iter()
14920 .take(active_components)
14921 .cloned()
14922 .collect()
14923 })
14924 }
14925
14926 fn splice_recovered_path<T>(
14932 &self,
14933 parsed: Vec<(usize, Vec<T>, bool)>,
14934 node_start: usize,
14935 recovered: impl FnOnce(&RecoveredNamespaceRegion, usize) -> Vec<T>,
14936 ) -> Vec<T> {
14937 let Some(region) = self.region_at(node_start) else {
14938 return parsed
14939 .into_iter()
14940 .flat_map(|(_, levels, _)| levels)
14941 .collect();
14942 };
14943 debug_assert_eq!(
14944 region.components.len(),
14945 region.component_closes.len(),
14946 "every recovered namespace component carries its close proof: {region:?}"
14947 );
14948 debug_assert_eq!(
14949 region.components.len(),
14950 region.component_name_ranges.len(),
14951 "every recovered namespace component carries its name token: {region:?}"
14952 );
14953 if region.components.is_empty() {
14954 return parsed
14955 .into_iter()
14956 .filter(|(_, _, complete)| *complete)
14957 .flat_map(|(_, levels, _)| levels)
14958 .collect();
14959 }
14960 let active_components = region
14961 .component_closes
14962 .iter()
14963 .take_while(|close| {
14964 close
14965 .as_ref()
14966 .is_none_or(|close| node_start < close.end_byte)
14967 })
14968 .count();
14969 recovered(region, active_components)
14970 .into_iter()
14971 .chain(
14972 parsed
14973 .into_iter()
14974 .filter(|(start, _, _)| *start >= region.start)
14975 .flat_map(|(_, levels, _)| levels),
14976 )
14977 .collect()
14978 }
14979}
14980
14981pub(super) fn namespace_definition_has_real_braces(namespace: Node<'_>) -> bool {
14985 debug_assert_eq!(namespace.kind(), "namespace_definition");
14986 let Some(body) = namespace.child_by_field_name("body") else {
14987 return false;
14988 };
14989 let mut opening = false;
14990 let mut closing = false;
14991 let mut cursor = body.walk();
14992 for child in body.children(&mut cursor) {
14993 match child.kind() {
14994 "{" if !child.is_missing() => opening = true,
14995 "}" if !child.is_missing() => closing = true,
14996 _ => {}
14997 }
14998 }
14999 opening && closing
15000}
15001
15002fn recovered_namespace_open_components<'tree>(
15018 preceding: &[Node<'tree>],
15019 source: &str,
15020) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15021 let mut head = Vec::new();
15022 for &sibling in preceding.iter().rev() {
15023 if sibling.kind() != "comment" {
15024 head.push(sibling);
15025 if head.len() == 2 {
15026 break;
15027 }
15028 }
15029 }
15030 let [name, keyword] = head[..] else {
15031 return None;
15032 };
15033 if keyword.kind() != "namespace" {
15034 return None;
15035 }
15036 let components = cpp_name_component_ranges(name, source)?;
15037 (!components.is_empty()).then_some((keyword, components))
15038}
15039
15040fn namespace_body_name_components<'tree>(
15045 parent: Node<'tree>,
15046 body: Node<'tree>,
15047 source: &str,
15048) -> Option<(Node<'tree>, Vec<(String, Range)>)> {
15049 if parent.child_by_field_name("body") != Some(body) {
15050 return None;
15051 }
15052 let (written_at, name) = match (parent.kind(), body.kind()) {
15053 ("namespace_definition", "declaration_list") => {
15054 (parent, parent.child_by_field_name("name")?)
15055 }
15056 ("function_definition", "compound_statement") if parent.has_error() => {
15057 let mut cursor = parent.walk();
15061 let children = parent
15062 .children(&mut cursor)
15063 .filter(|child| child.kind() != "comment")
15064 .collect::<Vec<_>>();
15065 let [prefix, keyword, error, actual_body] = children.as_slice() else {
15066 return None;
15067 };
15068 if *actual_body != body
15069 || parent.child_by_field_name("type") != Some(*prefix)
15070 || parent.child_by_field_name("declarator") != Some(*keyword)
15071 || prefix.kind() != "type_identifier"
15072 || prefix.is_missing()
15073 || prefix.has_error()
15074 || prefix.child_count() != 0
15075 || keyword.kind() != "identifier"
15076 || keyword.is_missing()
15077 || keyword.has_error()
15078 || keyword.child_count() != 0
15079 || node_text(*keyword, source) != "namespace"
15080 || error.kind() != "ERROR"
15081 || error.is_missing()
15082 || body.is_missing()
15083 {
15084 return None;
15085 }
15086 let mut cursor = error.walk();
15087 let names = error
15088 .children(&mut cursor)
15089 .filter(|child| child.kind() != "comment")
15090 .collect::<Vec<_>>();
15091 let [name] = names.as_slice() else {
15092 return None;
15093 };
15094 if !matches!(name.kind(), "identifier" | "namespace_identifier")
15095 || name.is_missing()
15096 || name.has_error()
15097 || name.child_count() != 0
15098 {
15099 return None;
15100 }
15101 let open = body.child(0)?;
15102 if open.kind() != "{"
15103 || open.is_missing()
15104 || open.has_error()
15105 || open.start_byte() != body.start_byte()
15106 {
15107 return None;
15108 }
15109 (*keyword, *name)
15112 }
15113 _ => return None,
15114 };
15115 let components = cpp_name_component_ranges(name, source)?;
15116 (!components.is_empty()).then_some((written_at, components))
15117}
15118
15119#[derive(Clone, Copy)]
15120enum NamespaceBodyBraceProof {
15121 Real(Range),
15122 Missing,
15123}
15124
15125fn namespace_body_brace_proof(body: Node<'_>) -> Option<NamespaceBodyBraceProof> {
15131 if body.kind() != "declaration_list" {
15132 return None;
15133 }
15134 let mut cursor = body.walk();
15135 let children = body.children(&mut cursor).collect::<Vec<_>>();
15136 let close_index = children
15137 .iter()
15138 .rposition(|child| child.kind() == "}" && child.end_byte() == body.end_byte())?;
15139 let close = children[close_index];
15140 if close.is_missing() {
15141 return Some(NamespaceBodyBraceProof::Missing);
15142 }
15143 let namespace_terminator_sibling = body
15144 .parent()
15145 .filter(|namespace| namespace.kind() == "namespace_definition")
15146 .and_then(|namespace| namespace.next_sibling())
15147 .is_some_and(|sibling| {
15148 sibling.kind() == ";"
15149 || (sibling.kind() == "expression_statement"
15150 && sibling.child_count() == 1
15151 && sibling.child(0).is_some_and(|child| child.kind() == ";"))
15152 });
15153 if namespace_terminator_sibling
15158 || children
15159 .get(close_index + 1)
15160 .is_some_and(|sibling| sibling.kind() == ";")
15161 || children[..close_index]
15162 .iter()
15163 .rev()
15164 .find(|child| !child.is_extra() && child.kind() != "{")
15165 .is_some_and(|previous| {
15166 matches!(
15167 previous.kind(),
15168 "expression_statement" | "labeled_statement"
15169 )
15170 })
15171 {
15172 return None;
15173 }
15174 Some(NamespaceBodyBraceProof::Real(node_range(close)))
15175}
15176
15177#[derive(Clone, Copy, Debug, Eq, PartialEq)]
15178pub enum RecoveredDeclaratorTypeContext {
15179 Declaration,
15180 FunctionDefinition,
15181 Parameter,
15182}
15183
15184pub fn recovered_macro_decorated_declarator_type(
15199 node: Node<'_>,
15200) -> Option<RecoveredDeclaratorTypeContext> {
15201 recovered_macro_decorated_type_node(node).map(|(_, context)| context)
15202}
15203
15204pub fn recovered_macro_decorated_type_node(
15209 node: Node<'_>,
15210) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15211 if !matches!(node.kind(), "namespace_identifier" | "template_type") || node.is_missing() {
15212 return None;
15213 }
15214 let qualified = node.parent()?;
15215 if qualified.kind() != "qualified_identifier"
15216 || qualified.child_by_field_name("scope") != Some(node)
15217 || !(0..qualified.child_count())
15218 .filter_map(|index| qualified.child(index))
15219 .any(|child| child.kind() == "::" && child.is_missing())
15220 {
15221 return None;
15222 }
15223 if !concrete_recovered_declarator_name(qualified.child_by_field_name("name")?) {
15224 return None;
15225 }
15226
15227 let (declaration, context) = recovered_declarator_container(qualified)?;
15228 let type_node = declaration
15229 .child_by_field_name("type")
15230 .filter(|type_node| {
15231 *type_node != qualified
15232 && !type_node.is_missing()
15233 && type_node.start_byte() != type_node.end_byte()
15234 })?;
15235 Some((type_node, context))
15236}
15237
15238fn recovered_declarator_container(
15239 mut declarator: Node<'_>,
15240) -> Option<(Node<'_>, RecoveredDeclaratorTypeContext)> {
15241 loop {
15242 let parent = declarator.parent()?;
15243 if parent.kind() == "init_declarator" && has_field_child(parent, "declarator", declarator) {
15244 return Some((
15245 parent
15246 .parent()
15247 .filter(|declaration| declaration.kind() == "declaration")?,
15248 RecoveredDeclaratorTypeContext::Declaration,
15249 ));
15250 }
15251 if parent.kind() == "declaration" && has_field_child(parent, "declarator", declarator) {
15252 return Some((parent, RecoveredDeclaratorTypeContext::Declaration));
15253 }
15254 if parent.kind() == "function_definition"
15255 && has_field_child(parent, "declarator", declarator)
15256 {
15257 return Some((parent, RecoveredDeclaratorTypeContext::FunctionDefinition));
15258 }
15259 if matches!(
15265 parent.kind(),
15266 "parameter_declaration" | "optional_parameter_declaration"
15267 ) && has_field_child(parent, "declarator", declarator)
15268 {
15269 return Some((parent, RecoveredDeclaratorTypeContext::Parameter));
15270 }
15271 if !matches!(
15272 parent.kind(),
15273 "array_declarator"
15274 | "function_declarator"
15275 | "parenthesized_declarator"
15276 | "pointer_declarator"
15277 | "pointer_type_declarator"
15278 | "reference_declarator"
15279 ) || !has_field_child(parent, "declarator", declarator)
15280 {
15281 return None;
15282 }
15283 declarator = parent;
15284 }
15285}
15286
15287fn has_field_child(parent: Node<'_>, field: &str, target: Node<'_>) -> bool {
15288 brokk_bifrost_core::analyzer::structural::adapter_helpers::is_field_of(parent, target, field)
15289}
15290
15291fn concrete_recovered_declarator_name(mut node: Node<'_>) -> bool {
15292 loop {
15293 if node.is_missing() || node.start_byte() == node.end_byte() {
15294 return false;
15295 }
15296 match node.kind() {
15297 "identifier" | "field_identifier" | "type_identifier" | "operator_name" => {
15298 return true;
15299 }
15300 "array_declarator"
15301 | "function_declarator"
15302 | "parenthesized_declarator"
15303 | "pointer_declarator"
15304 | "pointer_type_declarator"
15305 | "reference_declarator" => {
15306 let Some(declarator) = node.child_by_field_name("declarator") else {
15307 return false;
15308 };
15309 node = declarator;
15310 }
15311 _ => return false,
15312 }
15313 }
15314}
15315
15316pub enum DesignatedInitializerOwner {
15318 Resolved(CodeUnit),
15319 Unresolved,
15320}
15321
15322enum InitializerOwnerStep {
15323 Field(String),
15324 AggregateWrapper,
15325}
15326
15327pub fn designated_initializer_owner(
15337 analyzer: &CppGraphSource<'_>,
15338 visibility: &VisibilityIndex<'_>,
15339 file: &ProjectFile,
15340 source: &str,
15341 node: Node<'_>,
15342) -> Option<DesignatedInitializerOwner> {
15343 if let Some(designator) = node
15344 .parent()
15345 .filter(|parent| parent.kind() == "field_designator")
15346 {
15347 let pair = designator.parent()?;
15348 if pair.kind() != "initializer_pair" {
15349 return None;
15350 }
15351 let mut cursor = pair.walk();
15352 let designators = pair
15353 .children_by_field_name("designator", &mut cursor)
15354 .collect::<Vec<_>>();
15355 let position = designators
15356 .iter()
15357 .position(|candidate| same_node(*candidate, designator))?;
15358 let initializer = pair.parent()?;
15359 if initializer.kind() != "initializer_list" {
15360 return None;
15361 }
15362 let mut owner = initializer_list_owner(analyzer, visibility, file, source, initializer);
15363 for prior in &designators[..position] {
15364 let field = prior
15365 .child_by_field_name("field")
15366 .or_else(|| first_named_child_of_kind(*prior, "field_identifier"))?;
15367 owner = owner.and_then(|owner| {
15368 initializer_field_owner(analyzer, visibility, file, owner, node_text(field, source))
15369 });
15370 }
15371 return Some(classified_designated_owner(owner));
15372 }
15373
15374 let init_declarator = node.parent()?;
15375 if init_declarator.child_by_field_name("declarator") != Some(node)
15376 || !crate::structural::is_recovered_designator_init_declarator(init_declarator)
15377 {
15378 return None;
15379 }
15380 Some(classified_designated_owner(declaration_owner(
15381 analyzer,
15382 visibility,
15383 file,
15384 source,
15385 init_declarator.parent()?,
15386 )))
15387}
15388
15389fn classified_designated_owner(owner: Option<CodeUnit>) -> DesignatedInitializerOwner {
15390 owner.map_or(
15391 DesignatedInitializerOwner::Unresolved,
15392 DesignatedInitializerOwner::Resolved,
15393 )
15394}
15395
15396fn initializer_list_owner(
15397 analyzer: &CppGraphSource<'_>,
15398 visibility: &VisibilityIndex<'_>,
15399 file: &ProjectFile,
15400 source: &str,
15401 initializer: Node<'_>,
15402) -> Option<CodeUnit> {
15403 let mut current = initializer;
15404 let mut steps = Vec::new();
15405 loop {
15406 let parent = current.parent()?;
15407 match parent.kind() {
15408 "initializer_pair" if parent.child_by_field_name("value") == Some(current) => {
15409 let designator = parent.child_by_field_name("designator")?;
15410 let step = designator
15411 .child_by_field_name("field")
15412 .or_else(|| first_named_child_of_kind(designator, "field_identifier"))
15413 .map(|field| InitializerOwnerStep::Field(node_text(field, source).to_string()))
15414 .unwrap_or(InitializerOwnerStep::AggregateWrapper);
15415 steps.push(step);
15416 current = parent.parent()?;
15417 }
15418 "initializer_list" => {
15419 current = parent;
15420 }
15421 "init_declarator" if parent.child_by_field_name("value") == Some(current) => {
15422 let declaration = parent.parent()?;
15423 let owner = declaration_owner(analyzer, visibility, file, source, declaration)?;
15424 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15425 }
15426 "compound_literal_expression"
15427 if parent.child_by_field_name("value") == Some(current) =>
15428 {
15429 let type_node = parent.child_by_field_name("type")?;
15430 let owner =
15431 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)?;
15432 return apply_initializer_owner_steps(analyzer, visibility, file, owner, steps);
15433 }
15434 "ERROR" => current = parent,
15435 _ => return None,
15436 }
15437 }
15438}
15439
15440fn apply_initializer_owner_steps(
15441 analyzer: &CppGraphSource<'_>,
15442 visibility: &VisibilityIndex<'_>,
15443 file: &ProjectFile,
15444 mut owner: CodeUnit,
15445 steps: Vec<InitializerOwnerStep>,
15446) -> Option<CodeUnit> {
15447 for step in steps.into_iter().rev() {
15448 if let InitializerOwnerStep::Field(field_name) = step {
15449 owner = initializer_field_owner(analyzer, visibility, file, owner, &field_name)?;
15450 }
15451 }
15452 Some(owner)
15453}
15454
15455fn initializer_field_owner(
15456 analyzer: &CppGraphSource<'_>,
15457 visibility: &VisibilityIndex<'_>,
15458 file: &ProjectFile,
15459 owner: CodeUnit,
15460 field_name: &str,
15461) -> Option<CodeUnit> {
15462 let fields = visibility
15463 .visible_members_for_owner_name(file, &owner, field_name)
15464 .into_iter()
15465 .filter(|field| field.is_field())
15466 .collect::<Vec<_>>();
15467 let field = match fields.as_slice() {
15468 [field] => *field,
15469 _ => return None,
15470 };
15471 field_declared_binding(analyzer, visibility, file, field)?.unit
15472}
15473
15474fn declaration_owner(
15475 analyzer: &CppGraphSource<'_>,
15476 visibility: &VisibilityIndex<'_>,
15477 file: &ProjectFile,
15478 source: &str,
15479 declaration: Node<'_>,
15480) -> Option<CodeUnit> {
15481 if !matches!(declaration.kind(), "declaration" | "field_declaration") {
15482 return None;
15483 }
15484 let type_node = declaration
15485 .child_by_field_name("type")
15486 .or_else(|| first_type_child(declaration))?;
15487 resolve_designated_owner_type(analyzer, visibility, file, source, type_node)
15488}
15489
15490fn resolve_designated_owner_type(
15491 analyzer: &CppGraphSource<'_>,
15492 visibility: &VisibilityIndex<'_>,
15493 file: &ProjectFile,
15494 source: &str,
15495 type_node: Node<'_>,
15496) -> Option<CodeUnit> {
15497 if let Some(owner) = anonymous_aggregate_owner(analyzer, file, type_node) {
15498 return Some(owner);
15499 }
15500 let type_name = normalize_type_text(node_text(type_node, source));
15501 visibility
15502 .resolve_type(file, &type_name)
15503 .filter(CodeUnit::is_class)
15504}
15505
15506pub fn first_type_child(node: Node<'_>) -> Option<Node<'_>> {
15507 let mut cursor = node.walk();
15508 node.named_children(&mut cursor).find(|child| {
15509 matches!(
15510 child.kind(),
15511 "type_identifier"
15512 | "primitive_type"
15513 | "qualified_identifier"
15514 | "scoped_type_identifier"
15515 | "struct_specifier"
15516 | "union_specifier"
15517 | "enum_specifier"
15518 )
15519 })
15520}
15521
15522pub fn constructor_style_local_declaration<T: Clone + Eq + Hash>(
15523 visibility: &VisibilityIndex<'_>,
15524 file: &ProjectFile,
15525 source: &str,
15526 declarator: Node<'_>,
15527 type_text: Option<&str>,
15528 bindings: &LocalInferenceEngine<T>,
15529) -> bool {
15530 if !has_ancestor_kind(declarator, "compound_statement") {
15531 return false;
15532 }
15533 if declarator
15534 .child_by_field_name("declarator")
15535 .is_none_or(|declarator| declarator.kind() != "identifier")
15536 {
15537 return false;
15538 }
15539 if !type_text
15540 .and_then(|text| visibility.resolve_type(file, text))
15541 .is_some_and(|unit| unit.is_class())
15542 {
15543 return false;
15544 }
15545 declarator
15546 .child_by_field_name("parameters")
15547 .is_some_and(|parameters| {
15548 constructor_parameters_look_like_expressions(parameters, source, bindings)
15549 })
15550}
15551
15552fn constructor_parameters_look_like_expressions<T: Clone + Eq + Hash>(
15553 parameters: Node<'_>,
15554 source: &str,
15555 bindings: &LocalInferenceEngine<T>,
15556) -> bool {
15557 let mut cursor = parameters.walk();
15558 parameters.named_children(&mut cursor).any(|parameter| {
15559 !matches!(
15560 parameter.kind(),
15561 "parameter_declaration" | "optional_parameter_declaration"
15562 ) || parameter_declaration_is_local_expression(parameter, source, bindings)
15563 })
15564}
15565
15566fn parameter_declaration_is_local_expression<T: Clone + Eq + Hash>(
15567 parameter: Node<'_>,
15568 source: &str,
15569 bindings: &LocalInferenceEngine<T>,
15570) -> bool {
15571 let text = node_text(parameter, source).trim();
15572 if text
15573 .chars()
15574 .all(|ch| ch == '_' || ch.is_ascii_alphanumeric())
15575 && bindings.is_shadowed(text)
15576 {
15577 return true;
15578 }
15579
15580 let Some(base) = parameter
15581 .child_by_field_name("type")
15582 .filter(|base| base.kind() == "type_identifier")
15583 else {
15584 return false;
15585 };
15586 let Some(subscript) = parameter
15587 .child_by_field_name("declarator")
15588 .filter(|declarator| declarator.kind() == "abstract_array_declarator")
15589 else {
15590 return false;
15591 };
15592 subscript.child_by_field_name("size").is_some()
15593 && bindings.is_shadowed(node_text(base, source).trim())
15594}
15595
15596pub fn is_declaration_name(node: Node<'_>) -> bool {
15597 let Some(parent) = node.parent() else {
15598 return false;
15599 };
15600 if parent
15601 .child_by_field_name("name")
15602 .is_some_and(|name| same_node(name, node))
15603 {
15604 if matches!(
15605 parent.kind(),
15606 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
15607 ) {
15608 return cpp_tag_specifier_declares_name(parent);
15609 }
15610 if matches!(
15611 parent.kind(),
15612 "namespace_definition"
15613 | "namespace_alias_definition"
15614 | "alias_declaration"
15615 | "enumerator"
15616 ) {
15617 return true;
15618 }
15619 }
15620
15621 let mut current = Some(parent);
15622 while let Some(ancestor) = current {
15623 let type_definition = ancestor.kind() == "type_definition";
15624 let mut child_cursor = ancestor.walk();
15625 if ancestor.named_children(&mut child_cursor).any(|child| {
15626 declaration_declarator(ancestor, child).is_some_and(|declarator| {
15627 declarator_name_path_contains(declarator, node, type_definition)
15628 })
15629 }) {
15630 return true;
15631 }
15632 if matches!(
15633 ancestor.kind(),
15634 "declaration"
15635 | "field_declaration"
15636 | "parameter_declaration"
15637 | "optional_parameter_declaration"
15638 | "function_definition"
15639 | "type_definition"
15640 | "alias_declaration"
15641 | "template_instantiation"
15642 | "class_specifier"
15643 | "struct_specifier"
15644 | "union_specifier"
15645 | "enum_specifier"
15646 ) {
15647 return false;
15648 }
15649 current = ancestor.parent();
15650 }
15651 false
15652}
15653
15654pub fn is_recovered_qualified_friend_class_type_reference(node: Node<'_>, source: &str) -> bool {
15663 if !matches!(
15664 node.kind(),
15665 "qualified_identifier" | "scoped_type_identifier"
15666 ) {
15667 return false;
15668 }
15669 let Some(declaration) = node
15670 .parent()
15671 .filter(|parent| parent.kind() == "declaration")
15672 else {
15673 return false;
15674 };
15675 if declaration.child_by_field_name("declarator") != Some(node)
15676 || !declaration
15677 .child_by_field_name("type")
15678 .is_some_and(|friend| {
15679 friend.kind() == "type_identifier" && node_text(friend, source) == "friend"
15680 })
15681 {
15682 return false;
15683 }
15684 let mut cursor = declaration.walk();
15685 let mut errors = declaration
15686 .named_children(&mut cursor)
15687 .filter(|child| child.kind() == "ERROR");
15688 let Some(error) = errors.next() else {
15689 return false;
15690 };
15691 errors.next().is_none()
15692 && error.named_child_count() == 1
15693 && error.named_child(0).is_some_and(|class| {
15694 class.kind() == "identifier" && node_text(class, source) == "class"
15695 })
15696}
15697
15698pub fn is_ordinary_macro_reference_node(node: Node<'_>) -> bool {
15699 if !matches!(node.kind(), "identifier" | "field_identifier") {
15700 return false;
15701 }
15702 if let Some(parent) = node.parent() {
15703 if parent.kind() == "call_expression"
15704 && parent.child_by_field_name("function") == Some(node)
15705 {
15706 return false;
15707 }
15708 if matches!(parent.kind(), "labeled_statement" | "goto_statement")
15709 && parent.child_by_field_name("label") == Some(node)
15710 {
15711 return false;
15712 }
15713 }
15714 if is_declaration_name(node) {
15715 return false;
15716 }
15717 let mut current = node.parent();
15718 while let Some(ancestor) = current {
15719 match ancestor.kind() {
15720 "preproc_ifdef" | "preproc_ifndef" => {
15721 if ancestor
15722 .child_by_field_name("name")
15723 .is_some_and(|name| node_range_contains(name, node))
15724 {
15725 return false;
15726 }
15727 }
15728 "preproc_if" | "preproc_elif" => {
15729 if ancestor
15730 .child_by_field_name("condition")
15731 .is_some_and(|condition| node_range_contains(condition, node))
15732 {
15733 return false;
15734 }
15735 }
15736 "preproc_else" => {}
15737 kind if kind.starts_with("preproc_") => return false,
15738 _ => {}
15739 }
15740 if matches!(
15741 ancestor.kind(),
15742 "translation_unit" | "function_definition" | "compound_statement"
15743 ) {
15744 break;
15745 }
15746 current = ancestor.parent();
15747 }
15748 true
15749}
15750
15751fn recovered_c_reference_node(
15752 visibility: &VisibilityIndex<'_>,
15753 file: &ProjectFile,
15754 node: Node<'_>,
15755 source: &str,
15756) -> bool {
15757 if node.start_byte() >= node.end_byte()
15758 || node.is_error()
15759 || node.is_missing()
15760 || !matches!(
15761 node.kind(),
15762 "identifier" | "field_identifier" | "type_identifier" | "namespace_identifier"
15763 )
15764 || recovered_c_macro_binding_role(node)
15765 || recovered_c_label_role(node)
15766 {
15767 return false;
15768 }
15769 let name = node_text(node, source);
15777 let recovered_function_call = recovered_c_function_call(visibility, file, node, name);
15778 let recovered_macro_call = recovered_c_function_declarator_invocation(node)
15779 && visibility.macro_name_may_be_bound_at(file, name, node.start_byte());
15780 let recovered_parenthesized_reference = recovered_c_parenthesized_declarator_reference(node);
15781 if is_declaration_name(node)
15782 && !recovered_c_explicit_assignment_callee(visibility, file, node, name)
15783 && !recovered_function_call
15784 && !recovered_macro_call
15785 && !recovered_parenthesized_reference
15786 {
15787 return false;
15788 }
15789
15790 if !name.is_empty() && visibility.macro_name_may_be_bound_at(file, name, node.start_byte()) {
15791 return true;
15792 }
15793 if recovered_c_explicit_assignment_callee(visibility, file, node, name) {
15794 return true;
15795 }
15796 if recovered_parenthesized_reference {
15797 return true;
15798 }
15799 if matches!(node.kind(), "type_identifier" | "namespace_identifier") {
15800 if recovered_function_call {
15801 return true;
15802 }
15803 return visibility
15804 .visible_identifier_candidates(file, name)
15805 .any(|candidate| candidate.is_class() || candidate.is_module());
15806 }
15807 let visible = visibility
15808 .visible_identifier_candidates(file, name)
15809 .next()
15810 .is_some();
15811 visible
15812 && (recovered_c_reference_anchor(node)
15813 || recovered_c_error_expression_leaf(node)
15814 || recovered_function_call)
15815}
15816
15817fn push_recovered_c_range(
15818 ranges: &mut Vec<Range>,
15819 seen: &mut HashSet<(usize, usize)>,
15820 start_byte: usize,
15821 end_byte: usize,
15822 node: Node<'_>,
15823 limit: usize,
15824) -> bool {
15825 if start_byte >= end_byte || !seen.insert((start_byte, end_byte)) {
15826 return true;
15827 }
15828 if ranges.len() >= limit {
15829 return false;
15830 }
15831 ranges.push(Range {
15832 start_byte,
15833 end_byte,
15834 start_line: node.start_position().row,
15835 end_line: node.end_position().row,
15836 });
15837 true
15838}
15839
15840fn recovered_c_error_expression_leaf(node: Node<'_>) -> bool {
15845 let mut current = node.parent();
15846 while let Some(parent) = current {
15847 if parent.is_error() {
15848 let Some(anchor) = parent.parent() else {
15849 return false;
15850 };
15851 return anchor.kind().ends_with("_expression")
15852 || matches!(
15853 anchor.kind(),
15854 "argument_list"
15855 | "return_statement"
15856 | "expression_statement"
15857 | "case_statement"
15858 | "initializer_list"
15859 | "field_designator"
15860 | "enumerator"
15861 );
15862 }
15863 if matches!(
15864 parent.kind(),
15865 "translation_unit" | "function_definition" | "compound_statement"
15866 ) {
15867 return false;
15868 }
15869 current = parent.parent();
15870 }
15871 false
15872}
15873
15874fn recovered_c_function_call(
15881 visibility: &VisibilityIndex<'_>,
15882 file: &ProjectFile,
15883 node: Node<'_>,
15884 name: &str,
15885) -> bool {
15886 if !matches!(
15887 node.kind(),
15888 "identifier" | "field_identifier" | "type_identifier"
15889 ) {
15890 return false;
15891 }
15892 let error_call_prefix = node.parent().is_some_and(|error| {
15897 error.is_error()
15898 && error
15899 .parent()
15900 .is_some_and(|parent| parent.kind() == "compound_statement")
15901 }) && node
15902 .prev_sibling()
15903 .is_none_or(|previous| previous.kind() == ";")
15904 && node.next_sibling().is_some_and(|open| {
15905 open.kind() == "("
15906 && open.next_named_sibling().is_some_and(|argument| {
15907 argument.kind() == "parameter_declaration" && !argument.has_error()
15908 })
15909 });
15910 (error_call_prefix || recovered_c_function_declarator_invocation(node))
15911 && visibility
15912 .visible_identifier_candidates(file, name)
15913 .any(CodeUnit::is_function)
15914}
15915
15916fn recovered_c_function_declarator_invocation(node: Node<'_>) -> bool {
15926 let mut function_declarator = if node.parent().is_some_and(|parent| {
15927 parent.kind() == "function_declarator"
15928 && parent.child_by_field_name("declarator") == Some(node)
15929 }) {
15930 node.parent().expect("checked function declarator parent")
15931 } else {
15932 let Some(parameter) = node.parent().filter(|parent| {
15933 parent.kind() == "parameter_declaration"
15934 && parent.child_by_field_name("type") == Some(node)
15935 }) else {
15936 return false;
15937 };
15938 if !parameter
15939 .child_by_field_name("declarator")
15940 .is_some_and(|declarator| declarator.kind() == "abstract_function_declarator")
15941 {
15942 return false;
15943 }
15944 let Some(parameters) = parameter
15945 .parent()
15946 .filter(|parent| parent.kind() == "parameter_list")
15947 else {
15948 return false;
15949 };
15950 let Some(function_declarator) = parameters
15951 .parent()
15952 .filter(|parent| parent.kind() == "function_declarator")
15953 else {
15954 return false;
15955 };
15956 function_declarator
15957 };
15958
15959 while let Some(parent) = function_declarator.parent().filter(|parent| {
15962 parent.kind() == "function_declarator"
15963 && parent.child_by_field_name("declarator") == Some(function_declarator)
15964 }) {
15965 function_declarator = parent;
15966 }
15967 let Some(mut current) = function_declarator
15968 .parent()
15969 .filter(|parent| parent.is_error())
15970 else {
15971 return false;
15972 };
15973 loop {
15974 let Some(parent) = current.parent() else {
15975 return false;
15976 };
15977 if matches!(
15978 parent.kind(),
15979 "translation_unit"
15980 | "compound_statement"
15981 | "preproc_if"
15982 | "preproc_ifdef"
15983 | "preproc_ifndef"
15984 | "preproc_else"
15985 | "preproc_elif"
15986 ) {
15987 return true;
15988 }
15989 if parent.kind() == "function_definition"
15990 && parent.child_by_field_name("declarator") == Some(current)
15991 && parent.named_child(0) == Some(current)
15992 && parent.child_by_field_name("body").is_some()
15993 {
15994 return true;
15995 }
15996 if parent.is_error()
15997 || matches!(
15998 parent.kind(),
15999 "parameter_declaration"
16000 | "parameter_list"
16001 | "function_declarator"
16002 | "abstract_function_declarator"
16003 | "parenthesized_declarator"
16004 )
16005 {
16006 current = parent;
16007 continue;
16008 }
16009 return false;
16010 }
16011}
16012
16013fn recovered_c_parenthesized_declarator_reference(node: Node<'_>) -> bool {
16019 let Some(error) = node.parent().filter(|parent| parent.is_error()) else {
16020 return false;
16021 };
16022 if error.named_child_count() != 1 || error.named_child(0) != Some(node) {
16023 return false;
16024 }
16025 let Some(declarator) = error
16026 .parent()
16027 .filter(|parent| parent.kind() == "parenthesized_declarator")
16028 else {
16029 return false;
16030 };
16031 let Some(declaration) = declarator
16032 .parent()
16033 .filter(|parent| parent.kind() == "declaration")
16034 else {
16035 return false;
16036 };
16037 if declaration.child_by_field_name("declarator") != Some(declarator) {
16038 return false;
16039 }
16040 let Some(type_node) = declaration.child_by_field_name("type") else {
16041 return false;
16042 };
16043 type_node.kind() == "dependent_type"
16044 && type_node
16045 .child(0)
16046 .is_some_and(|keyword| keyword.kind() == "typename")
16047}
16048
16049fn recovered_c_explicit_assignment_callee(
16050 visibility: &VisibilityIndex<'_>,
16051 file: &ProjectFile,
16052 node: Node<'_>,
16053 name: &str,
16054) -> bool {
16055 let mut current = node;
16056 let error = loop {
16057 let Some(parent) = current.parent() else {
16058 return false;
16059 };
16060 if parent.is_error() {
16061 break parent;
16062 }
16063 current = parent;
16064 };
16065 let mut cursor = error.walk();
16066 let explicit_recovery_precedes_callee = error
16067 .named_children(&mut cursor)
16068 .take_while(|child| child.start_byte() < node.start_byte())
16069 .any(|child| child.kind() == "explicit_function_specifier");
16070 if !explicit_recovery_precedes_callee {
16071 return false;
16072 }
16073 visibility
16074 .visible_identifier_candidates(file, name)
16075 .any(CodeUnit::is_function)
16076}
16077
16078fn recovered_c_macro_binding_role(mut node: Node<'_>) -> bool {
16079 while let Some(parent) = node.parent() {
16080 if matches!(
16081 parent.kind(),
16082 "preproc_def" | "preproc_function_def" | "preproc_params"
16083 ) {
16084 return true;
16085 }
16086 if parent.is_error()
16087 || matches!(
16088 parent.kind(),
16089 "translation_unit" | "function_definition" | "compound_statement"
16090 )
16091 {
16092 return false;
16093 }
16094 node = parent;
16095 }
16096 false
16097}
16098
16099fn recovered_c_label_role(node: Node<'_>) -> bool {
16100 node.parent().is_some_and(|parent| {
16101 matches!(parent.kind(), "labeled_statement" | "goto_statement")
16102 && parent.child_by_field_name("label") == Some(node)
16103 })
16104}
16105
16106fn recovered_c_reference_anchor(mut node: Node<'_>) -> bool {
16107 while let Some(parent) = node.parent() {
16108 if parent.is_error() {
16109 return false;
16110 }
16111 if parent.kind() == "optional_parameter_declaration"
16116 && parent
16117 .child_by_field_name("default_value")
16118 .is_some_and(|value| node_range_contains(value, node))
16119 {
16120 return true;
16121 }
16122 if parent.kind().ends_with("_expression")
16123 || matches!(
16124 parent.kind(),
16125 "argument_list"
16126 | "return_statement"
16127 | "expression_statement"
16128 | "case_statement"
16129 | "initializer_list"
16130 | "init_declarator"
16131 | "array_declarator"
16132 | "field_designator"
16133 | "enumerator"
16134 )
16135 {
16136 return true;
16137 }
16138 if matches!(
16139 parent.kind(),
16140 "translation_unit"
16141 | "function_definition"
16142 | "compound_statement"
16143 | "declaration"
16144 | "field_declaration"
16145 | "parameter_declaration"
16146 ) {
16147 return false;
16148 }
16149 node = parent;
16150 }
16151 false
16152}
16153
16154pub fn parameter_belongs_to_callable_scope(parameter: Node<'_>) -> bool {
16162 let mut current = parameter.parent();
16163 while let Some(ancestor) = current {
16164 if ancestor.kind() == "lambda_expression" {
16165 return ancestor
16166 .child_by_field_name("declarator")
16167 .is_some_and(|declarator| {
16168 declarator.start_byte() <= parameter.start_byte()
16169 && parameter.end_byte() <= declarator.end_byte()
16170 });
16171 }
16172 if ancestor.kind() == "function_definition" {
16173 return ancestor
16174 .child_by_field_name("declarator")
16175 .is_some_and(|declarator| {
16176 declarator.start_byte() <= parameter.start_byte()
16177 && parameter.end_byte() <= declarator.end_byte()
16178 });
16179 }
16180 current = ancestor.parent();
16181 }
16182 false
16183}
16184
16185pub fn is_parameter_type_reference(node: Node<'_>) -> bool {
16186 let mut current = node.parent();
16187 while let Some(ancestor) = current {
16188 if matches!(
16189 ancestor.kind(),
16190 "parameter_declaration" | "optional_parameter_declaration"
16191 ) {
16192 return crate::declarations::cpp_type_field_contains_node(ancestor, node);
16193 }
16194 if matches!(
16195 ancestor.kind(),
16196 "function_definition" | "lambda_expression" | "compound_statement"
16197 ) {
16198 return false;
16199 }
16200 current = ancestor.parent();
16201 }
16202 false
16203}
16204
16205pub fn cpp_tag_specifier_ancestors_before_type_reference_context(
16209 specifier: Node<'_>,
16210) -> impl Iterator<Item = Node<'_>> {
16211 std::iter::successors(specifier.parent(), |ancestor| ancestor.parent()).take_while(|ancestor| {
16212 !matches!(
16213 ancestor.kind(),
16214 "type_descriptor"
16215 | "parameter_declaration"
16216 | "optional_parameter_declaration"
16217 | "template_argument_list"
16218 | "cast_expression"
16219 )
16220 })
16221}
16222
16223pub fn cpp_tag_specifier_declares_name(specifier: Node<'_>) -> bool {
16224 if specifier.child_by_field_name("body").is_some() {
16225 return true;
16226 }
16227 for ancestor in cpp_tag_specifier_ancestors_before_type_reference_context(specifier) {
16228 match ancestor.kind() {
16229 "declaration" | "field_declaration" => {
16230 let mut cursor = ancestor.walk();
16231 return ancestor
16232 .children_by_field_name("declarator", &mut cursor)
16233 .next()
16234 .is_none();
16235 }
16236 "translation_unit" => return true,
16237 _ => {}
16238 }
16239 }
16240 false
16241}
16242
16243pub fn declarator_name_node(node: Node<'_>) -> Option<Node<'_>> {
16244 match node.kind() {
16245 "identifier"
16246 | "field_identifier"
16247 | "qualified_identifier"
16248 | "scoped_identifier"
16249 | "operator_name"
16250 | "destructor_name"
16251 | "literal_operator_name" => Some(node),
16252 "reference_declarator" | "parenthesized_declarator" => {
16253 node.named_child(0).and_then(declarator_name_node)
16254 }
16255 _ => node
16256 .child_by_field_name("declarator")
16257 .or_else(|| node.child_by_field_name("name"))
16258 .or_else(|| node.child_by_field_name("field"))
16259 .and_then(declarator_name_node),
16260 }
16261}
16262
16263pub fn declarator_path_contains_before_name(
16265 declarator: Node<'_>,
16266 name: Node<'_>,
16267 candidate: Node<'_>,
16268) -> bool {
16269 let mut current = Some(declarator);
16270 while let Some(node) = current {
16271 if same_node(node, candidate) {
16272 return true;
16273 }
16274 if same_node(node, name) {
16275 return false;
16276 }
16277 current = node
16278 .child_by_field_name("declarator")
16279 .or_else(|| node.child_by_field_name("name"))
16280 .or_else(|| node.child_by_field_name("field"));
16281 }
16282 false
16283}
16284
16285fn declarator_name_path_contains(
16286 declarator: Node<'_>,
16287 candidate: Node<'_>,
16288 allow_type_identifier: bool,
16289) -> bool {
16290 let Some(name) = declarator_name_leaf(declarator, allow_type_identifier) else {
16291 return false;
16292 };
16293 declarator_path_contains_before_name(declarator, name, candidate)
16294}
16295
16296fn declarator_name_leaf(node: Node<'_>, allow_type_identifier: bool) -> Option<Node<'_>> {
16297 match node.kind() {
16298 "identifier"
16299 | "field_identifier"
16300 | "operator_name"
16301 | "destructor_name"
16302 | "literal_operator_name" => Some(node),
16303 "type_identifier" if allow_type_identifier => Some(node),
16304 _ => node
16305 .child_by_field_name("declarator")
16306 .or_else(|| node.child_by_field_name("name"))
16307 .or_else(|| node.child_by_field_name("field"))
16308 .and_then(|child| declarator_name_leaf(child, allow_type_identifier)),
16309 }
16310}
16311
16312pub fn is_nested_type_node(node: Node<'_>) -> bool {
16315 node.parent().is_some_and(|parent| {
16316 matches!(
16317 parent.kind(),
16318 "qualified_identifier" | "scoped_type_identifier" | "template_type"
16319 )
16320 })
16321}
16322
16323pub struct OutOfLineMemberDefinitionOwners<'tree> {
16324 pub owners: Vec<(Node<'tree>, CodeUnit)>,
16325 innermost: Option<(Node<'tree>, CodeUnit)>,
16326}
16327
16328impl OutOfLineMemberDefinitionOwners<'_> {
16329 pub fn innermost(&self) -> Option<(Node<'_>, &CodeUnit)> {
16330 self.innermost.as_ref().map(|(node, owner)| (*node, owner))
16331 }
16332}
16333
16334pub struct QualifiedOwnerComponents<'tree> {
16335 pub nodes: Vec<Node<'tree>>,
16336 pub names: Vec<String>,
16337 pub global: bool,
16338}
16339
16340pub fn qualified_name_has_concrete_scope_separators(node: Node<'_>) -> bool {
16345 let mut stack = vec![node];
16346 let mut found_separator = false;
16347 while let Some(current) = stack.pop() {
16348 if !matches!(
16349 current.kind(),
16350 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16351 ) {
16352 continue;
16353 }
16354 let mut current_has_separator = false;
16355 for child in children_iter(current) {
16356 if child.kind() == "::" {
16357 if child.is_missing() {
16358 return false;
16359 }
16360 current_has_separator = true;
16361 found_separator = true;
16362 }
16363 }
16364 if !current_has_separator {
16365 return false;
16366 }
16367 for field in ["scope", "name"] {
16368 if let Some(child) = current.child_by_field_name(field)
16369 && matches!(
16370 child.kind(),
16371 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
16372 )
16373 {
16374 stack.push(child);
16375 }
16376 }
16377 }
16378 found_separator
16379}
16380
16381pub fn qualified_owner_components<'tree>(
16382 node: Node<'tree>,
16383 source: &str,
16384) -> Option<QualifiedOwnerComponents<'tree>> {
16385 if !qualified_name_has_concrete_scope_separators(node) {
16386 return None;
16387 }
16388 let mut nodes = cpp_name_component_nodes(node)?;
16389 nodes.pop()?;
16390 if nodes.is_empty() {
16391 return None;
16392 }
16393 let names = nodes
16394 .iter()
16395 .map(|component| node_text(*component, source).to_string())
16396 .collect();
16397 Some(QualifiedOwnerComponents {
16398 nodes,
16399 names,
16400 global: is_globally_qualified_cpp_name(node),
16401 })
16402}
16403
16404pub fn out_of_line_member_definition_owner<'tree>(
16405 analyzer: &CppGraphSource<'_>,
16406 visibility: &VisibilityIndex<'_>,
16407 file: &ProjectFile,
16408 source: &str,
16409 node: Node<'tree>,
16410) -> Option<OutOfLineMemberDefinitionOwners<'tree>> {
16411 if !matches!(node.kind(), "qualified_identifier" | "scoped_identifier")
16412 || !has_ancestor_kind(node, "function_definition")
16413 || !is_function_declarator_name_root(node)
16414 {
16415 return None;
16416 }
16417 let qualified = qualified_owner_components(node, source)?;
16418 let lexical_scope = enclosing_namespace_components(node, source)?;
16419 let mut owners = Vec::new();
16420 let mut innermost = None;
16421
16422 for component_count in 1..=qualified.names.len() {
16423 if let LexicalTypeResolution::Resolved { unit, .. } = visibility
16424 .resolve_type_components_lexically(
16425 analyzer,
16426 file,
16427 &qualified.names[..component_count],
16428 qualified.global,
16429 &lexical_scope,
16430 )
16431 && !owners
16432 .iter()
16433 .any(|(_, existing)| same_visible_symbol(existing, &unit))
16434 {
16435 if component_count == qualified.names.len() {
16436 innermost = Some((qualified.nodes[component_count - 1], unit.clone()));
16437 }
16438 owners.push((qualified.nodes[component_count - 1], unit));
16439 }
16440 }
16441
16442 if innermost.is_none() {
16452 let indexed_owner_components = visibility
16453 .indexed_enclosing_owner_scope(analyzer, file, node)
16454 .or_else(|| {
16455 if qualified.names.len() <= 1 {
16460 return None;
16461 }
16462 let range = Range {
16463 start_byte: node.start_byte(),
16464 end_byte: node.end_byte(),
16465 start_line: node.start_position().row,
16466 end_line: node.end_position().row,
16467 };
16468 let start = analyzer.enclosing_code_unit(file, &range)?;
16469 let mut components = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
16470 brokk_bifrost_core::analyzer::Language::Cpp,
16471 &cpp_name_for(&start),
16472 );
16473 components.pop();
16474 Some(components)
16475 });
16476 if let Some(indexed_owner_components) = indexed_owner_components
16477 && indexed_owner_components.len() > qualified.names.len()
16478 && indexed_owner_components.ends_with(&qualified.names)
16479 && indexed_namespace_path_is_recoverable(
16480 &lexical_scope,
16481 &indexed_owner_components,
16482 qualified.names.len(),
16483 )
16484 && (qualified.names.len() > 1 || !qualified.global)
16489 {
16490 let namespace_count = indexed_owner_components.len() - qualified.names.len();
16491 for component_count in 1..=qualified.names.len() {
16492 let expected = &indexed_owner_components[..namespace_count + component_count];
16493 let owner_node = qualified.nodes[component_count - 1];
16494 for owner in visibility
16495 .visible_identifier_candidates(file, &qualified.names[component_count - 1])
16496 .filter(|candidate| candidate.is_class())
16497 .filter(|candidate| {
16498 canonical_cpp_scope_components(candidate) == expected
16499 && visibility.external_type_candidate_visible_in_context(
16500 analyzer, file, candidate, node,
16501 )
16502 })
16503 {
16504 if component_count == qualified.names.len() && innermost.is_none() {
16505 innermost = Some((owner_node, owner.clone()));
16506 }
16507 if !owners
16508 .iter()
16509 .any(|(_, existing)| same_symbol(existing, owner))
16510 {
16511 owners.push((owner_node, owner.clone()));
16512 }
16513 }
16514 }
16515 }
16516 }
16517 (!owners.is_empty()).then_some(OutOfLineMemberDefinitionOwners { owners, innermost })
16518}
16519
16520fn is_function_declarator_name_root(node: Node<'_>) -> bool {
16521 let mut current = node;
16522 while let Some(parent) = current.parent() {
16523 if parent.kind() == "function_declarator" {
16524 return parent.child_by_field_name("declarator") == Some(current);
16525 }
16526 if matches!(
16527 parent.kind(),
16528 "pointer_declarator" | "reference_declarator" | "parenthesized_declarator"
16529 ) && parent.child_by_field_name("declarator") == Some(current)
16530 {
16531 current = parent;
16532 continue;
16533 }
16534 return false;
16535 }
16536 false
16537}
16538
16539pub fn append_cpp_name_components(
16540 node: Node<'_>,
16541 source: &str,
16542 out: &mut Vec<String>,
16543) -> Option<()> {
16544 out.extend(
16545 cpp_name_component_nodes(node)?
16546 .into_iter()
16547 .map(|component| node_text(component, source).to_string()),
16548 );
16549 Some(())
16550}
16551
16552fn cpp_name_component_ranges(node: Node<'_>, source: &str) -> Option<Vec<(String, Range)>> {
16555 cpp_name_component_nodes(node).map(|components| {
16556 components
16557 .into_iter()
16558 .map(|component| {
16559 (
16560 node_text(component, source).to_string(),
16561 node_range(component),
16562 )
16563 })
16564 .collect()
16565 })
16566}
16567
16568pub fn cpp_type_name_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
16569 let mut components = Vec::new();
16570 append_cpp_name_components(node, source, &mut components)?;
16571 Some(components)
16572}
16573
16574pub fn unique_macro_replacement_type_candidate(
16583 analyzer: &CppGraphSource<'_>,
16584 visibility: &VisibilityIndex<'_>,
16585 file: &ProjectFile,
16586 components: &[String],
16587) -> Option<CodeUnit> {
16588 let terminal = components.last()?;
16589 let mut candidates = Vec::new();
16590 for candidate in visibility
16591 .visible_identifier_candidates(file, terminal)
16592 .filter(|candidate| candidate.is_class() || declared_type_alias(analyzer, candidate))
16593 .filter(|candidate| canonical_cpp_scope_components(candidate).ends_with(components))
16594 {
16595 if !candidates
16596 .iter()
16597 .any(|existing| same_logical_symbol(existing, candidate))
16598 {
16599 candidates.push(candidate.clone());
16600 }
16601 }
16602 (candidates.len() == 1).then(|| candidates.remove(0))
16603}
16604
16605pub fn cpp_member_using_declaration_scopes(source: &str, member: &str) -> Vec<String> {
16613 let mut parser = Parser::new();
16614 if parser
16615 .set_language(&tree_sitter_cpp::LANGUAGE.into())
16616 .is_err()
16617 {
16618 return Vec::new();
16619 }
16620 let Some(tree) = parser.parse(source, None) else {
16621 return Vec::new();
16622 };
16623 let mut scopes = Vec::new();
16624 let mut pending = vec![tree.root_node()];
16625 while let Some(node) = pending.pop() {
16626 if node.kind() == "using_declaration" {
16627 let Some(imported) = node.named_child(0) else {
16628 continue;
16629 };
16630 let Some(mut components) = cpp_type_name_components(imported, source) else {
16631 continue;
16632 };
16633 if components.pop().as_deref() == Some(member) && !components.is_empty() {
16634 scopes.push(components.join("::"));
16635 }
16636 continue;
16637 }
16638 push_named_children_reversed(node, &mut pending);
16639 }
16640 scopes
16641}
16642
16643pub fn cpp_qualified_name_has_scope_suffix(qualified: &str, scope: &str) -> bool {
16647 qualified == scope
16648 || qualified
16649 .strip_suffix(scope)
16650 .is_some_and(|prefix| prefix.ends_with("::"))
16651}
16652
16653pub fn is_cpp_template_argument_type_leaf(node: Node<'_>) -> bool {
16658 let Some(type_descriptor) = node.parent() else {
16659 return false;
16660 };
16661 if type_descriptor.kind() != "type_descriptor"
16662 || type_descriptor.child_by_field_name("type") != Some(node)
16663 {
16664 return false;
16665 }
16666 let Some(arguments) = type_descriptor.parent() else {
16667 return false;
16668 };
16669 if arguments.kind() != "template_argument_list" {
16670 return false;
16671 }
16672 arguments.parent().is_some_and(|parent| {
16673 matches!(parent.kind(), "template_type" | "template_function")
16674 && parent.child_by_field_name("arguments") == Some(arguments)
16675 })
16676}
16677
16678pub fn cpp_template_reference_arguments(
16679 mut node: Node<'_>,
16680 source: &str,
16681) -> Option<Vec<CppTemplateExpression>> {
16682 loop {
16683 match node.kind() {
16684 "template_type" | "template_function" => {
16685 let arguments = node.child_by_field_name("arguments")?;
16686 let mut cursor = arguments.walk();
16687 return Some(
16688 arguments
16689 .named_children(&mut cursor)
16690 .filter(|argument| !argument.is_extra() && argument.kind() != "comment")
16691 .map(|argument| CppTemplateExpression {
16692 text: normalize_cpp_whitespace(node_text(argument, source)),
16693 term: cpp_template_term(
16695 argument,
16696 source,
16697 &[],
16698 &ParentIndex::unindexed(),
16699 ),
16700 })
16701 .collect(),
16702 );
16703 }
16704 "qualified_identifier" | "scoped_type_identifier" | "type_descriptor" => {
16705 node = node
16706 .child_by_field_name("name")
16707 .or_else(|| node.child_by_field_name("type"))?;
16708 }
16709 _ => return None,
16710 }
16711 }
16712}
16713
16714fn cpp_reconcile_primary_template_parameters(
16715 candidates: &[(&CodeUnit, &CppTemplateMetadata)],
16716 preferred: &CodeUnit,
16717) -> Option<Vec<CppTemplateParameterMetadata>> {
16718 let canonical = candidates
16719 .iter()
16720 .find_map(|(unit, metadata)| (*unit == preferred).then_some(*metadata))?;
16721 let mut merged = canonical
16722 .parameters
16723 .iter()
16724 .map(|parameter| CppTemplateParameterMetadata {
16725 name: parameter.name.clone(),
16726 kind: parameter.kind,
16727 variadic: parameter.variadic,
16728 default: None,
16729 })
16730 .collect::<Vec<_>>();
16731
16732 for (_, metadata) in candidates {
16733 if metadata.parameters.len() != merged.len() {
16734 return None;
16735 }
16736 let rename_bindings = metadata
16737 .parameters
16738 .iter()
16739 .zip(&merged)
16740 .map(|(parameter, canonical)| {
16741 (
16742 parameter.name.clone(),
16743 CppTemplateTerm::Parameter(canonical.name.clone()),
16744 )
16745 })
16746 .collect::<HashMap<_, _>>();
16747 for ((parameter, canonical), merged_parameter) in metadata
16748 .parameters
16749 .iter()
16750 .zip(&canonical.parameters)
16751 .zip(&mut merged)
16752 {
16753 if parameter.kind != canonical.kind || parameter.variadic != canonical.variadic {
16754 return None;
16755 }
16756 let Some(default) = ¶meter.default else {
16757 continue;
16758 };
16759 let normalized_term = cpp_substitute_template_term(&default.term, &rename_bindings)?;
16760 if let Some(existing) = &merged_parameter.default {
16761 if !cpp_template_terms_equal(&existing.term, &normalized_term) {
16762 return None;
16763 }
16764 } else {
16765 merged_parameter.default = Some(CppTemplateExpression {
16766 text: default.text.clone(),
16767 term: normalized_term,
16768 });
16769 }
16770 }
16771 }
16772 Some(merged)
16773}
16774
16775pub fn cpp_bind_template_arguments(
16776 parameters: &[CppTemplateParameterMetadata],
16777 explicit_arguments: &[CppTemplateExpression],
16778) -> Option<(Vec<CppTemplateExpression>, HashMap<String, CppTemplateTerm>)> {
16779 let variadic_index = parameters.iter().position(|parameter| parameter.variadic);
16780 if variadic_index.is_some_and(|index| {
16781 index + 1 != parameters.len()
16782 || parameters[index + 1..]
16783 .iter()
16784 .any(|parameter| parameter.variadic)
16785 }) {
16786 return None;
16787 }
16788 let fixed_count = variadic_index.unwrap_or(parameters.len());
16789 if variadic_index.is_none() && explicit_arguments.len() > fixed_count {
16790 return None;
16791 }
16792 let explicit_fixed_count = explicit_arguments.len().min(fixed_count);
16793 let mut expanded = explicit_arguments[..explicit_fixed_count]
16794 .iter()
16795 .map(cpp_clone_template_expression_iterative)
16796 .collect::<Vec<_>>();
16797 let mut bindings = HashMap::default();
16798 for (parameter, argument) in parameters[..explicit_fixed_count].iter().zip(&expanded) {
16799 bindings.insert(
16800 parameter.name.clone(),
16801 cpp_clone_template_term_iterative(&argument.term),
16802 );
16803 }
16804 for parameter in ¶meters[explicit_fixed_count..fixed_count] {
16805 let default = parameter.default.as_ref()?;
16806 let term = cpp_substitute_template_term(&default.term, &bindings)?;
16807 bindings.insert(parameter.name.clone(), term.clone());
16808 expanded.push(CppTemplateExpression {
16809 text: default.text.clone(),
16810 term,
16811 });
16812 }
16813 if let Some(index) = variadic_index {
16814 let packed_arguments = &explicit_arguments[explicit_fixed_count..];
16815 expanded.extend(
16816 packed_arguments
16817 .iter()
16818 .map(cpp_clone_template_expression_iterative),
16819 );
16820 bindings.insert(
16821 parameters[index].name.clone(),
16822 CppTemplateTerm::Node {
16823 kind: "parameter_pack".to_string(),
16824 children: packed_arguments
16825 .iter()
16826 .map(|argument| cpp_clone_template_term_iterative(&argument.term))
16827 .collect(),
16828 },
16829 );
16830 }
16831 Some((expanded, bindings))
16832}
16833
16834fn cpp_specialization_matches(
16835 metadata: &CppTemplateMetadata,
16836 arguments: &[CppTemplateExpression],
16837) -> bool {
16838 cpp_specialization_terms_match(
16839 &metadata.parameters,
16840 &metadata.specialization_arguments,
16841 arguments,
16842 )
16843}
16844
16845fn cpp_specialization_terms_match(
16846 parameters: &[CppTemplateParameterMetadata],
16847 patterns: &[CppTemplateExpression],
16848 arguments: &[CppTemplateExpression],
16849) -> bool {
16850 if patterns.len() != arguments.len() {
16851 return false;
16852 }
16853 let parameter_names = parameters
16854 .iter()
16855 .map(|parameter| parameter.name.as_str())
16856 .collect::<HashSet<_>>();
16857 let mut bindings: HashMap<String, CppTemplateTerm> = HashMap::default();
16858 patterns.iter().zip(arguments).all(|(pattern, argument)| {
16859 cpp_unify_template_term(
16860 &pattern.term,
16861 &argument.term,
16862 ¶meter_names,
16863 &mut bindings,
16864 )
16865 })
16866}
16867
16868fn cpp_specialization_more_specialized(
16869 candidate: &CppTemplateMetadata,
16870 other: &CppTemplateMetadata,
16871) -> bool {
16872 cpp_specialization_pattern_accepts(other, candidate)
16873 && !cpp_specialization_pattern_accepts(candidate, other)
16874}
16875
16876fn cpp_specialization_pattern_accepts(
16877 broader: &CppTemplateMetadata,
16878 narrower: &CppTemplateMetadata,
16879) -> bool {
16880 cpp_specialization_terms_match(
16881 &broader.parameters,
16882 &broader.specialization_arguments,
16883 &narrower.specialization_arguments,
16884 )
16885}
16886
16887pub fn cpp_substitute_template_term(
16888 term: &CppTemplateTerm,
16889 bindings: &HashMap<String, CppTemplateTerm>,
16890) -> Option<CppTemplateTerm> {
16891 enum Work<'a> {
16892 Visit(&'a CppTemplateTerm),
16893 Build { kind: String, child_count: usize },
16894 }
16895
16896 let mut work = vec![Work::Visit(term)];
16897 let mut substituted = Vec::new();
16898 while let Some(next) = work.pop() {
16899 match next {
16900 Work::Visit(CppTemplateTerm::Parameter(name)) => {
16901 substituted.push(cpp_clone_template_term_iterative(bindings.get(name)?));
16902 }
16903 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
16904 substituted.push(CppTemplateTerm::Atom {
16905 kind: kind.clone(),
16906 text: text.clone(),
16907 });
16908 }
16909 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
16910 work.push(Work::Build {
16911 kind: kind.clone(),
16912 child_count: children.len(),
16913 });
16914 work.extend(children.iter().rev().map(Work::Visit));
16915 }
16916 Work::Build { kind, child_count } => {
16917 let children = substituted.split_off(substituted.len() - child_count);
16918 substituted.push(CppTemplateTerm::Node { kind, children });
16919 }
16920 }
16921 }
16922 substituted.pop()
16923}
16924
16925pub fn cpp_substitute_template_arguments(
16926 arguments: &[CppTemplateExpression],
16927 bindings: &HashMap<String, CppTemplateTerm>,
16928) -> Option<Vec<CppTemplateExpression>> {
16929 let mut substituted = Vec::new();
16930 for argument in arguments {
16931 let CppTemplateTerm::Node { kind, children } = &argument.term else {
16932 substituted.push(CppTemplateExpression {
16933 text: argument.text.clone(),
16934 term: cpp_substitute_template_term(&argument.term, bindings)?,
16935 });
16936 continue;
16937 };
16938 if kind != "parameter_pack_expansion" {
16939 substituted.push(CppTemplateExpression {
16940 text: argument.text.clone(),
16941 term: cpp_substitute_template_term(&argument.term, bindings)?,
16942 });
16943 continue;
16944 }
16945 let [pattern, CppTemplateTerm::Atom { text: ellipsis, .. }] = children.as_slice() else {
16946 return None;
16947 };
16948 if ellipsis != "..." {
16949 return None;
16950 }
16951
16952 let mut pack_names = Vec::new();
16953 let mut work = vec![pattern];
16954 while let Some(term) = work.pop() {
16955 match term {
16956 CppTemplateTerm::Parameter(name)
16957 if matches!(
16958 bindings.get(name),
16959 Some(CppTemplateTerm::Node { kind, .. }) if kind == "parameter_pack"
16960 ) =>
16961 {
16962 if !pack_names.contains(name) {
16963 pack_names.push(name.clone());
16964 }
16965 }
16966 CppTemplateTerm::Node { children, .. } => work.extend(children),
16967 CppTemplateTerm::Parameter(_) | CppTemplateTerm::Atom { .. } => {}
16968 }
16969 }
16970 let first_pack = pack_names.first()?;
16971 let CppTemplateTerm::Node {
16972 children: first_elements,
16973 ..
16974 } = bindings.get(first_pack)?
16975 else {
16976 return None;
16977 };
16978 let pack_len = first_elements.len();
16979 for pack_name in &pack_names {
16980 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16981 return None;
16982 };
16983 if children.len() != pack_len {
16984 return None;
16985 }
16986 }
16987 for index in 0..pack_len {
16988 let mut element_bindings = bindings.clone();
16989 for pack_name in &pack_names {
16990 let CppTemplateTerm::Node { children, .. } = bindings.get(pack_name)? else {
16991 return None;
16992 };
16993 element_bindings.insert(
16994 pack_name.clone(),
16995 cpp_clone_template_term_iterative(&children[index]),
16996 );
16997 }
16998 substituted.push(CppTemplateExpression {
16999 text: argument.text.clone(),
17000 term: cpp_substitute_template_term(pattern, &element_bindings)?,
17001 });
17002 }
17003 }
17004 Some(substituted)
17005}
17006
17007fn cpp_clone_template_term_iterative(term: &CppTemplateTerm) -> CppTemplateTerm {
17008 enum Work<'a> {
17009 Visit(&'a CppTemplateTerm),
17010 Build { kind: String, child_count: usize },
17011 }
17012
17013 let mut work = vec![Work::Visit(term)];
17014 let mut cloned = Vec::new();
17015 while let Some(next) = work.pop() {
17016 match next {
17017 Work::Visit(CppTemplateTerm::Parameter(name)) => {
17018 cloned.push(CppTemplateTerm::Parameter(name.clone()));
17019 }
17020 Work::Visit(CppTemplateTerm::Atom { kind, text }) => {
17021 cloned.push(CppTemplateTerm::Atom {
17022 kind: kind.clone(),
17023 text: text.clone(),
17024 });
17025 }
17026 Work::Visit(CppTemplateTerm::Node { kind, children }) => {
17027 work.push(Work::Build {
17028 kind: kind.clone(),
17029 child_count: children.len(),
17030 });
17031 work.extend(children.iter().rev().map(Work::Visit));
17032 }
17033 Work::Build { kind, child_count } => {
17034 let children = cloned.split_off(cloned.len() - child_count);
17035 cloned.push(CppTemplateTerm::Node { kind, children });
17036 }
17037 }
17038 }
17039 cloned
17040 .pop()
17041 .expect("template term traversal emits one root")
17042}
17043
17044fn cpp_clone_template_expression_iterative(
17045 expression: &CppTemplateExpression,
17046) -> CppTemplateExpression {
17047 CppTemplateExpression {
17048 text: expression.text.clone(),
17049 term: cpp_clone_template_term_iterative(&expression.term),
17050 }
17051}
17052
17053pub fn cpp_unify_template_term(
17054 pattern: &CppTemplateTerm,
17055 argument: &CppTemplateTerm,
17056 parameters: &HashSet<&str>,
17057 bindings: &mut HashMap<String, CppTemplateTerm>,
17058) -> bool {
17059 let mut work = vec![(pattern, argument)];
17060 while let Some((pattern, argument)) = work.pop() {
17061 match pattern {
17062 CppTemplateTerm::Parameter(name) if parameters.contains(name.as_str()) => {
17063 if let Some(bound) = bindings.get(name) {
17064 if !cpp_template_terms_equal(bound, argument) {
17065 return false;
17066 }
17067 } else {
17068 bindings.insert(name.clone(), cpp_clone_template_term_iterative(argument));
17069 }
17070 }
17071 CppTemplateTerm::Atom {
17072 kind: pattern_kind,
17073 text: pattern_text,
17074 } => {
17075 if !matches!(
17076 argument,
17077 CppTemplateTerm::Atom { kind, text }
17078 if kind == pattern_kind && text == pattern_text
17079 ) {
17080 return false;
17081 }
17082 }
17083 CppTemplateTerm::Node {
17084 kind: pattern_kind,
17085 children: pattern_children,
17086 } => {
17087 let CppTemplateTerm::Node { kind, children } = argument else {
17088 return false;
17089 };
17090 if kind != pattern_kind || children.len() != pattern_children.len() {
17091 return false;
17092 }
17093 work.extend(pattern_children.iter().zip(children).rev());
17094 }
17095 CppTemplateTerm::Parameter(_) => return false,
17096 }
17097 }
17098 true
17099}
17100
17101fn cpp_template_terms_equal(left: &CppTemplateTerm, right: &CppTemplateTerm) -> bool {
17102 let mut work = vec![(left, right)];
17103 while let Some((left, right)) = work.pop() {
17104 match (left, right) {
17105 (CppTemplateTerm::Parameter(left), CppTemplateTerm::Parameter(right)) => {
17106 if left != right {
17107 return false;
17108 }
17109 }
17110 (
17111 CppTemplateTerm::Atom {
17112 kind: left_kind,
17113 text: left_text,
17114 },
17115 CppTemplateTerm::Atom {
17116 kind: right_kind,
17117 text: right_text,
17118 },
17119 ) => {
17120 if left_kind != right_kind || left_text != right_text {
17121 return false;
17122 }
17123 }
17124 (
17125 CppTemplateTerm::Node {
17126 kind: left_kind,
17127 children: left_children,
17128 },
17129 CppTemplateTerm::Node {
17130 kind: right_kind,
17131 children: right_children,
17132 },
17133 ) => {
17134 if left_kind != right_kind || left_children.len() != right_children.len() {
17135 return false;
17136 }
17137 work.extend(left_children.iter().zip(right_children).rev());
17138 }
17139 _ => return false,
17140 }
17141 }
17142 true
17143}
17144
17145pub fn cpp_name_component_nodes(node: Node<'_>) -> Option<Vec<Node<'_>>> {
17146 let mut components = Vec::new();
17147 let mut stack = vec![node];
17148 while let Some(current) = stack.pop() {
17149 match current.kind() {
17150 "identifier"
17151 | "field_identifier"
17152 | "namespace_identifier"
17153 | "type_identifier"
17154 | "operator_name"
17155 | "destructor_name" => components.push(current),
17156 "template_type" | "template_function" => {
17157 stack.push(current.child_by_field_name("name")?);
17158 }
17159 "dependent_name" => stack.push(current.named_child(0)?),
17160 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier" => {
17161 stack.push(current.child_by_field_name("name")?);
17162 if let Some(scope) = current.child_by_field_name("scope") {
17163 stack.push(scope);
17164 }
17165 }
17166 "nested_namespace_specifier" => {
17167 for index in (0..current.named_child_count()).rev() {
17168 stack.push(current.named_child(index)?);
17169 }
17170 }
17171 _ => return None,
17172 }
17173 }
17174 Some(components)
17175}
17176
17177pub fn is_globally_qualified_cpp_name(node: Node<'_>) -> bool {
17178 node.child_by_field_name("scope").is_none()
17179 && node.child(0).is_some_and(|child| child.kind() == "::")
17180}
17181
17182fn enclosing_namespace_components(node: Node<'_>, source: &str) -> Option<Vec<String>> {
17183 let mut namespaces = Vec::new();
17184 for parent in enclosing_cpp_namespace_nodes(node) {
17185 if let Some(name) = parent.child_by_field_name("name") {
17186 let mut components = Vec::new();
17187 append_cpp_name_components(name, source, &mut components)?;
17188 namespaces.push(components);
17189 }
17190 }
17191 Some(namespaces.into_iter().flatten().collect())
17192}
17193
17194fn indexed_namespace_path_is_recoverable(
17205 lexical_scope: &[String],
17206 indexed_owner_scope: &[String],
17207 explicit_owner_component_count: usize,
17208) -> bool {
17209 if lexical_scope.is_empty() {
17210 return explicit_owner_component_count > 1;
17211 }
17212 if lexical_scope.len() >= indexed_owner_scope.len() {
17213 return false;
17214 }
17215 super::cpp_namespace_path_is_ordered_subsequence(lexical_scope, indexed_owner_scope)
17216}
17217
17218pub use brokk_bifrost_core::analyzer::tree_walk::has_ancestor_kind;
17219
17220pub(crate) fn initialized_type_declaration_with_cast(node: Node<'_>) -> bool {
17226 let mut current = Some(node);
17227 while let Some(candidate) = current {
17228 if candidate.kind() == "declaration" {
17229 if !crate::declarations::cpp_type_field_contains_node(candidate, node) {
17230 return false;
17231 }
17232 let mut cursor = candidate.walk();
17233 return candidate.named_children(&mut cursor).any(|child| {
17234 child.kind() == "init_declarator"
17235 && child
17236 .child_by_field_name("value")
17237 .is_some_and(|value| value.kind() == "cast_expression")
17238 });
17239 }
17240 current = candidate.parent();
17241 }
17242 false
17243}
17244
17245#[derive(Clone, Copy, PartialEq, Eq)]
17246pub(crate) enum QualifiedAliasReferenceKind {
17247 Ordinary,
17248 ConstructorWithExpressionArgument,
17249 ExhaustiveTemplate,
17250}
17251
17252pub(crate) fn qualified_alias_reference_preserves_target(
17259 node: Node<'_>,
17260 target: &CodeUnit,
17261 analyzer: &CppGraphSource<'_>,
17262 visibility: &VisibilityIndex<'_>,
17263 file: &ProjectFile,
17264 source: &str,
17265) -> Option<QualifiedAliasReferenceKind> {
17266 if !matches!(
17267 node.kind(),
17268 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17269 ) {
17270 return None;
17271 }
17272 let components = cpp_type_name_components(node, source)?;
17273 let name = components.last()?;
17274 analyzer.type_alias_provider().and_then(|provider| {
17275 visibility
17276 .visible_identifier_candidates(file, name)
17277 .find_map(|candidate| {
17278 let proof = provider.is_type_alias(candidate)
17279 && canonical_cpp_scope_components(candidate) == components
17280 && visibility.external_type_candidate_visible_in_context(
17281 analyzer, file, candidate, node,
17282 )
17283 && match cpp_template_reference_arguments(node, source) {
17284 Some(arguments) => visibility.template_alias_arguments_preserve_target(
17285 analyzer, file, candidate, &arguments, target,
17286 ),
17287 None => visibility.structured_alias_primary_preserves_target(
17288 analyzer, file, candidate, target,
17289 ),
17290 };
17291 proof.then(|| {
17292 if cpp_template_reference_arguments(node, source).is_some()
17293 && visibility.is_exhaustive_same_fqn_type_declaration_family(
17294 analyzer, file, candidate,
17295 )
17296 {
17297 QualifiedAliasReferenceKind::ExhaustiveTemplate
17298 } else if qualified_alias_constructor_has_expression_argument(node)
17299 || qualified_alias_local_constructor_declaration(node)
17300 {
17301 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17302 } else {
17303 QualifiedAliasReferenceKind::Ordinary
17304 }
17305 })
17306 })
17307 })
17308}
17309
17310pub(crate) fn qualified_alias_reference_requires_terminal(
17311 reference: Option<QualifiedAliasReferenceKind>,
17312) -> bool {
17313 matches!(
17314 reference,
17315 Some(
17316 QualifiedAliasReferenceKind::ConstructorWithExpressionArgument
17317 | QualifiedAliasReferenceKind::ExhaustiveTemplate
17318 )
17319 )
17320}
17321
17322fn qualified_alias_constructor_has_expression_argument(node: Node<'_>) -> bool {
17323 let Some(declaration) = node.parent().filter(|parent| {
17324 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17325 }) else {
17326 return false;
17327 };
17328 let mut cursor = declaration.walk();
17329 declaration.named_children(&mut cursor).any(|child| {
17330 child.kind() == "init_declarator"
17331 && child
17332 .child_by_field_name("value")
17333 .filter(|value| value.kind() == "argument_list")
17334 .is_some_and(|arguments| {
17335 let mut cursor = arguments.walk();
17336 arguments.named_children(&mut cursor).any(|argument| {
17337 let is_parameter = matches!(
17338 argument.kind(),
17339 "parameter_declaration" | "optional_parameter_declaration"
17340 );
17341 if is_parameter {
17342 argument
17343 .child_by_field_name("type")
17344 .is_some_and(|type_node| {
17345 type_node.kind() == "type_identifier"
17346 && argument.child_by_field_name("declarator").is_none()
17347 })
17348 } else {
17349 !argument.kind().ends_with("_literal")
17350 && !matches!(argument.kind(), "true" | "false" | "nullptr")
17351 }
17352 })
17353 })
17354 })
17355}
17356
17357fn qualified_alias_local_constructor_declaration(node: Node<'_>) -> bool {
17362 let Some(declaration) = node.parent().filter(|parent| {
17363 parent.kind() == "declaration" && parent.child_by_field_name("type") == Some(node)
17364 }) else {
17365 return false;
17366 };
17367 if declaration
17368 .parent()
17369 .is_none_or(|parent| parent.kind() != "compound_statement")
17370 {
17371 return false;
17372 }
17373 let mut cursor = declaration.walk();
17374 declaration
17375 .named_children(&mut cursor)
17376 .any(|child| child.kind() == "function_declarator")
17377}
17378
17379pub fn function_terminal_node(mut node: Node<'_>) -> Node<'_> {
17385 loop {
17386 let next = match node.kind() {
17387 "qualified_identifier"
17388 | "scoped_identifier"
17389 | "template_method"
17390 | "template_function"
17391 | "template_type" => node.child_by_field_name("name"),
17392 "field_expression" => node.child_by_field_name("field"),
17393 _ => None,
17394 };
17395 let Some(next) = next else {
17396 return node;
17397 };
17398 node = next;
17399 }
17400}
17401
17402#[derive(Clone, Copy)]
17403pub struct RecoveredRelationalTemplateMemberCall<'tree> {
17404 pub receiver: Node<'tree>,
17405 pub member: Node<'tree>,
17406 pub arity: usize,
17407}
17408
17409pub fn recovered_relational_template_member_call(
17417 field: Node<'_>,
17418) -> Option<RecoveredRelationalTemplateMemberCall<'_>> {
17419 if field.kind() != "field_expression" {
17420 return None;
17421 }
17422 let receiver = field
17423 .child_by_field_name("argument")
17424 .or_else(|| field.child_by_field_name("object"))?;
17425 let member = field.child_by_field_name("field")?;
17426 let less = field.parent()?;
17427 if less.kind() != "binary_expression"
17428 || less.child_by_field_name("left") != Some(field)
17429 || less
17430 .child_by_field_name("operator")
17431 .is_none_or(|operator| operator.kind() != "<")
17432 || less.child_by_field_name("right").is_none()
17433 {
17434 return None;
17435 }
17436 let greater = less.parent()?;
17437 if greater.kind() != "binary_expression"
17438 || greater.child_by_field_name("left") != Some(less)
17439 || greater
17440 .child_by_field_name("operator")
17441 .is_none_or(|operator| operator.kind() != ">")
17442 {
17443 return None;
17444 }
17445 let arguments = greater.child_by_field_name("right")?;
17446 if arguments.kind() != "parenthesized_expression" {
17447 return None;
17448 }
17449 let arity = parenthesized_call_argument_arity(arguments)?;
17450 Some(RecoveredRelationalTemplateMemberCall {
17451 receiver,
17452 member,
17453 arity,
17454 })
17455}
17456
17457fn parenthesized_call_argument_arity(arguments: Node<'_>) -> Option<usize> {
17458 let expression = arguments.named_child(0)?;
17459 if expression.kind() != "comma_expression" {
17460 return Some(1);
17461 }
17462 let mut arity = 0usize;
17463 let mut stack = vec![expression];
17464 while let Some(node) = stack.pop() {
17465 if node.kind() == "comma_expression" {
17466 stack.push(node.child_by_field_name("right")?);
17467 stack.push(node.child_by_field_name("left")?);
17468 } else {
17469 arity += 1;
17470 }
17471 }
17472 Some(arity)
17473}
17474
17475pub fn is_call_callee_node(mut node: Node<'_>) -> bool {
17478 while let Some(parent) = node.parent() {
17479 match parent.kind() {
17480 "call_expression" => {
17481 return parent
17482 .child_by_field_name("function")
17483 .or_else(|| parent.named_child(0))
17484 == Some(node);
17485 }
17486 "qualified_identifier"
17487 | "scoped_identifier"
17488 | "template_function"
17489 | "template_type"
17490 | "field_expression" => node = parent,
17491 _ => return false,
17492 }
17493 }
17494 false
17495}
17496
17497pub fn type_reference_hit_node(mut node: Node<'_>) -> Node<'_> {
17501 loop {
17502 let Some(parent) = node.parent() else {
17503 return node;
17504 };
17505 if parent.kind() == "template_type"
17506 && parent
17507 .child_by_field_name("name")
17508 .is_some_and(|name| same_node(name, node))
17509 {
17510 node = parent;
17511 continue;
17512 }
17513 if matches!(
17514 parent.kind(),
17515 "qualified_identifier" | "scoped_identifier" | "scoped_type_identifier"
17516 ) && (parent
17517 .child_by_field_name("scope")
17518 .is_some_and(|scope| same_node(scope, node))
17519 || parent
17520 .child_by_field_name("name")
17521 .is_some_and(|name| same_node(name, node)))
17522 && parent.end_byte() == node.end_byte()
17523 {
17524 node = parent;
17525 continue;
17526 }
17527 return node;
17528 }
17529}
17530
17531pub fn normalize_type_text(value: &str) -> String {
17532 strip_tag_type_prefix(
17533 normalize_cpp_whitespace(value)
17534 .trim_start_matches("const ")
17535 .trim_end_matches('*')
17536 .trim_end_matches('&')
17537 .trim(),
17538 )
17539 .to_string()
17540}
17541
17542use crate::declarations::strip_tag_type_prefix;
17543
17544pub fn normalize_reference_name(value: &str) -> Option<String> {
17545 let normalized = normalize_cpp_reference_text(value);
17546 (!normalized.is_empty()).then_some(normalized)
17547}
17548
17549pub fn normalize_cpp_reference_text(value: &str) -> String {
17550 let mut text = normalize_cpp_whitespace(value)
17551 .trim_start_matches("new ")
17552 .trim()
17553 .to_string();
17554 if let Some(index) = text.find(['(', '{']) {
17555 text.truncate(index);
17556 }
17557 if let Some(index) = text.find('<') {
17558 text.truncate(index);
17559 }
17560 let normalized = text
17561 .trim()
17562 .trim_start_matches("const ")
17563 .trim_end_matches(|ch: char| ch == '*' || ch == '&' || ch.is_whitespace())
17564 .trim_matches(':')
17565 .trim();
17566 strip_tag_type_prefix(normalized).to_string()
17567}
17568
17569pub fn cpp_name_for(unit: &CodeUnit) -> String {
17570 let short = unit.short_name().replace(['.', '$'], "::");
17571 if unit.package_name().is_empty() {
17572 short
17573 } else {
17574 format!("{}::{}", unit.package_name(), short)
17575 }
17576}
17577
17578fn canonical_cpp_name_from_fq(unit: &CodeUnit) -> Option<String> {
17582 let fq = unit.fq();
17583 if fq.is_empty() {
17584 return None;
17585 }
17586 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17587 Some(
17588 fq.segments()
17589 .iter()
17590 .map(|&segment| interner.resolve(segment).0)
17591 .collect::<Vec<_>>()
17592 .join("::"),
17593 )
17594}
17595
17596fn canonical_cpp_name_matches(unit: &CodeUnit, expected: &str) -> bool {
17597 canonical_cpp_name_from_fq(unit).as_deref() == Some(expected)
17598 || unit.fq().is_empty() && cpp_name_for(unit) == expected
17599}
17600
17601pub fn canonical_cpp_scope_components(unit: &CodeUnit) -> Vec<String> {
17610 let fq = unit.fq();
17611 if !fq.is_empty() {
17612 let interner = brokk_bifrost_core::analyzer::fq_name::segment_interner();
17613 let scope = fq
17614 .segments()
17615 .iter()
17616 .filter_map(|&segment| {
17617 let (text, kind) = interner.resolve(segment);
17618 matches!(
17619 kind,
17620 brokk_bifrost_core::analyzer::fq_name::SegmentKind::Package
17621 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Type
17622 | brokk_bifrost_core::analyzer::fq_name::SegmentKind::Nested
17623 )
17624 .then(|| text.to_string())
17625 })
17626 .collect();
17627 return scope;
17628 }
17629 brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17630 brokk_bifrost_core::analyzer::Language::Cpp,
17631 &cpp_name_for(unit),
17632 )
17633}
17634
17635pub fn terminal_name(value: &str) -> &str {
17646 value
17647 .rsplit("::")
17648 .next()
17649 .unwrap_or(value)
17650 .rsplit(['.', '-', '>'])
17651 .next()
17652 .unwrap_or(value)
17653 .trim()
17654}
17655
17656pub fn name_matches_terminal(value: &str, expected: &str) -> bool {
17657 terminal_name(&normalize_cpp_reference_text(value)) == expected
17658}
17659
17660pub fn name_matches_callable(value: &str, expected: &str) -> bool {
17661 name_matches_terminal(value, expected)
17662 || expected.starts_with("operator")
17663 && terminal_name(&normalize_cpp_reference_text(value)) == "operator"
17664}
17665
17666pub fn name_mentions(value: &str, expected: &str) -> bool {
17667 normalize_cpp_reference_text(value)
17668 .split("::")
17669 .any(|part| part == expected)
17670}
17671
17672pub fn reference_matches_unit(reference: &str, unit: &CodeUnit) -> bool {
17673 let cpp_name = cpp_name_for(unit);
17674 if reference.contains("::") {
17675 return reference == cpp_name;
17676 }
17677 reference == cpp_name
17678 || terminal_name(reference) == unit.identifier()
17679 && (unit.package_name().is_empty() || reference == unit.identifier())
17680}
17681
17682pub fn matches_kind_for_lookup(unit: &CodeUnit, kind: TargetKind) -> bool {
17683 match kind {
17684 TargetKind::Type
17685 | TargetKind::Constructor
17686 | TargetKind::Method
17687 | TargetKind::MemberField => true,
17688 TargetKind::FreeFunction => unit.is_function(),
17689 TargetKind::GlobalField => unit.is_field(),
17690 TargetKind::Macro => unit.is_macro(),
17691 }
17692}
17693
17694fn alias_target_matches_target(alias: &CppAlias, target: &CodeUnit) -> bool {
17695 let normalized = normalize_cpp_reference_text(alias.target.trim().trim_end_matches(';'));
17696 let target_name = cpp_name_for(target);
17697 if normalized.contains("::") {
17698 return normalized == target_name;
17699 }
17700 if let Some(namespace) = alias.namespace.as_deref() {
17701 return namespace_prefixes(namespace)
17702 .into_iter()
17703 .any(|prefix| format!("{prefix}::{normalized}") == target_name);
17704 }
17705 target.package_name().is_empty() && normalized == target.identifier()
17706}
17707
17708pub fn cpp_function_return_type_text(
17711 analyzer: &CppGraphSource<'_>,
17712 function: &CodeUnit,
17713) -> Option<String> {
17714 let metadata = analyzer.signature_metadata(function);
17715 let first = metadata.first()?.return_type_text()?;
17716 metadata
17717 .iter()
17718 .all(|metadata| metadata.return_type_text() == Some(first))
17719 .then(|| first.to_owned())
17720}
17721
17722pub fn cpp_namespace_for(unit: &CodeUnit) -> Option<String> {
17723 cpp_name_for(unit).rsplit_once("::").map(|(namespace, _)| {
17733 namespace
17734 .strip_prefix("anonymous_namespace::")
17735 .unwrap_or(namespace)
17736 .to_string()
17737 })
17738}
17739
17740pub fn namespace_prefixes(namespace: &str) -> Vec<String> {
17742 let mut parts = brokk_bifrost_core::analyzer::symbol_path::parse_symbol_path(
17748 brokk_bifrost_core::analyzer::Language::Cpp,
17749 namespace,
17750 );
17751 let mut prefixes = Vec::new();
17752 while !parts.is_empty() {
17753 prefixes.push(parts.join("::"));
17754 parts.pop();
17755 }
17756 prefixes
17757}
17758
17759fn nearest_namespace_candidates(
17760 candidates: Vec<CodeUnit>,
17761 normalized: &str,
17762 lexical_namespace: Option<&str>,
17763) -> Vec<CodeUnit> {
17764 if normalized.contains("::") {
17765 return candidates;
17766 }
17767 if let Some(namespace) = lexical_namespace {
17768 for prefix in namespace_prefixes(namespace) {
17769 let scoped = candidates
17770 .iter()
17771 .filter(|function| cpp_namespace_for(function).as_deref() == Some(prefix.as_str()))
17772 .cloned()
17773 .collect::<Vec<_>>();
17774 if !scoped.is_empty() {
17775 return scoped;
17776 }
17777 }
17778 }
17779 candidates
17780 .into_iter()
17781 .filter(|function| cpp_namespace_for(function).is_none_or(|namespace| namespace.is_empty()))
17782 .collect()
17783}
17784
17785pub fn enclosing_namespace_context(node: Node<'_>, source: &str) -> Option<String> {
17786 let mut namespaces = Vec::new();
17787 for parent in enclosing_cpp_namespace_nodes(node) {
17788 if let Some(name) = parent.child_by_field_name("name") {
17789 let namespace = normalize_cpp_reference_text(node_text(name, source));
17790 if !namespace.is_empty() {
17791 namespaces.push(namespace);
17792 }
17793 }
17794 }
17795 if namespaces.is_empty() {
17796 None
17797 } else {
17798 Some(namespaces.join("::"))
17799 }
17800}
17801
17802pub fn type_owner_of(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
17806 type_owner_resolution(analyzer, code_unit).map(|owner| owner.unit)
17807}
17808
17809fn type_owner_resolution(
17810 analyzer: &CppGraphSource<'_>,
17811 code_unit: &CodeUnit,
17812) -> Option<ResolvedTypeOwner> {
17813 precise_parent_resolution(analyzer, code_unit).filter(|owner| !owner.unit.is_module())
17814}
17815
17816fn target_type_owner_resolution(
17817 analyzer: &CppGraphSource<'_>,
17818 code_unit: &CodeUnit,
17819) -> Option<ResolvedTypeOwner> {
17820 match type_owner_resolution(analyzer, code_unit) {
17821 Some(owner) if owner.unit.is_class() && !owner.is_forward_declaration => Some(owner),
17822 Some(_) | None => target_forward_owner_resolution(analyzer, code_unit),
17823 }
17824}
17825
17826fn target_forward_owner_resolution(
17838 analyzer: &CppGraphSource<'_>,
17839 code_unit: &CodeUnit,
17840) -> Option<ResolvedTypeOwner> {
17841 if !code_unit.is_function() {
17842 return None;
17843 }
17844 let owner_name = code_unit.fq().parent().filter(|owner| !owner.is_empty())?;
17850 let cpp = analyzer.cpp?;
17851 let mut visible_files = HashSet::default();
17852 collect_include_closure(
17853 analyzer,
17854 cpp.include_target_index(),
17855 code_unit.source(),
17856 &mut visible_files,
17857 None,
17858 );
17859 let candidates = analyzer.workspace_definitions().exact(&owner_name);
17860 let visible_candidates = candidates
17861 .iter()
17862 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
17863 .cloned()
17864 .collect::<Vec<_>>();
17865 match classify_direct_owner_candidates(analyzer, visible_candidates.into_iter()) {
17866 DirectOwnerResolution::UniqueFull(unit) => {
17867 return Some(ResolvedTypeOwner {
17868 unit,
17869 is_forward_declaration: false,
17870 });
17871 }
17872 DirectOwnerResolution::ForwardsOnly(forwards) => {
17873 return (forwards.len() == 1).then(|| ResolvedTypeOwner {
17874 unit: forwards.into_iter().next().unwrap(),
17875 is_forward_declaration: true,
17876 });
17877 }
17878 DirectOwnerResolution::Ambiguous => return None,
17879 DirectOwnerResolution::None => {}
17880 }
17881
17882 let candidates = candidates
17883 .into_iter()
17884 .filter(|candidate| candidate.is_class())
17885 .collect::<Vec<_>>();
17886 let (unit, is_forward_declaration) =
17887 match classify_direct_owner_candidates(analyzer, candidates.iter().cloned()) {
17888 DirectOwnerResolution::UniqueFull(unit) => (unit, false),
17889 DirectOwnerResolution::ForwardsOnly(forwards) => {
17890 (unique_logical_forward_owner(forwards)?, true)
17891 }
17892 DirectOwnerResolution::None | DirectOwnerResolution::Ambiguous => return None,
17893 };
17894 Some(ResolvedTypeOwner {
17895 unit,
17896 is_forward_declaration,
17897 })
17898}
17899
17900pub fn precise_parent_of(
17901 analyzer: &CppGraphSource<'_>,
17902 visibility: &VisibilityIndex<'_>,
17903 code_unit: &CodeUnit,
17904) -> Option<CodeUnit> {
17905 visibility.cached_precise_parent_of(analyzer, code_unit)
17906}
17907
17908fn precise_parent_resolution(
17909 analyzer: &CppGraphSource<'_>,
17910 code_unit: &CodeUnit,
17911) -> Option<ResolvedTypeOwner> {
17912 #[cfg(any(test, feature = "test-support"))]
17913 if let Some(cpp) = analyzer.cpp {
17914 cpp.record_cpp_parent_resolution_for_test();
17915 }
17916 if let Some(unit) = exact_structural_type_parent(analyzer, code_unit) {
17917 return Some(ResolvedTypeOwner {
17918 unit,
17919 is_forward_declaration: false,
17920 });
17921 }
17922 let fallback = analyzer.parent_of(code_unit);
17923 if !code_unit.owner_is_type_scope() {
17924 return fallback.map(|unit| ResolvedTypeOwner {
17925 unit,
17926 is_forward_declaration: false,
17927 });
17928 }
17929 let owner_fq = code_unit
17930 .fq()
17931 .parent()
17932 .expect("a unit with an owner identifier has a structured parent");
17933 let owner_candidates = analyzer.workspace_definitions().exact(&owner_fq);
17934 match same_source_owner(analyzer, code_unit, &owner_candidates) {
17935 DirectOwnerResolution::UniqueFull(owner) => {
17936 return Some(ResolvedTypeOwner {
17937 unit: owner,
17938 is_forward_declaration: false,
17939 });
17940 }
17941 DirectOwnerResolution::Ambiguous => return None,
17942 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {}
17943 }
17944 match directly_included_owner(analyzer, code_unit, &owner_candidates) {
17945 DirectOwnerResolution::UniqueFull(owner) => Some(ResolvedTypeOwner {
17946 unit: owner,
17947 is_forward_declaration: false,
17948 }),
17949 DirectOwnerResolution::Ambiguous => None,
17950 DirectOwnerResolution::ForwardsOnly(forwards) => {
17951 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17952 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17953 unit: owner,
17954 is_forward_declaration: false,
17955 }),
17956 FullOwnerResolution::None => {
17957 unique_logical_forward_owner(forwards).map(|unit| ResolvedTypeOwner {
17958 unit,
17959 is_forward_declaration: true,
17960 })
17961 }
17962 FullOwnerResolution::Ambiguous => None,
17963 }
17964 }
17965 DirectOwnerResolution::None => {
17966 match visible_full_cpp_owner(analyzer, code_unit, &owner_candidates) {
17967 FullOwnerResolution::Unique(owner) => Some(ResolvedTypeOwner {
17968 unit: owner,
17969 is_forward_declaration: false,
17970 }),
17971 FullOwnerResolution::Ambiguous => None,
17972 FullOwnerResolution::None => fallback
17973 .filter(|parent| {
17974 parent.source() == code_unit.source()
17975 && parent.fq() == &owner_fq
17976 && (!parent.is_class()
17977 || cpp_class_declaration_strength(analyzer, parent)
17978 == CppClassDeclarationStrength::Full)
17979 })
17980 .map(|unit| ResolvedTypeOwner {
17981 unit,
17982 is_forward_declaration: false,
17983 }),
17984 }
17985 }
17986 }
17987}
17988
17989fn exact_structural_type_parent(
17990 analyzer: &CppGraphSource<'_>,
17991 code_unit: &CodeUnit,
17992) -> Option<CodeUnit> {
17993 if !code_unit.is_function() && !code_unit.is_field() {
17994 return None;
17995 }
17996 let encoded_owner = code_unit.short_name().rsplit_once('.')?.0; let cpp = analyzer.cpp?;
17998 let parent = cpp.structural_parent_of(code_unit)?;
17999 (!parent.is_module()
18000 && parent.source() == code_unit.source()
18001 && parent.package_name() == code_unit.package_name()
18002 && parent.short_name() == encoded_owner)
18003 .then_some(parent)
18004}
18005
18006fn same_source_owner(
18007 analyzer: &CppGraphSource<'_>,
18008 code_unit: &CodeUnit,
18009 owner_candidates: &[CodeUnit],
18010) -> DirectOwnerResolution {
18011 let candidates = owner_candidates
18012 .iter()
18013 .filter(|candidate| candidate.is_class() && candidate.source() == code_unit.source())
18014 .cloned()
18015 .collect::<Vec<_>>();
18016 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18017 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18018}
18019
18020fn visible_full_cpp_owner(
18021 analyzer: &CppGraphSource<'_>,
18022 code_unit: &CodeUnit,
18023 owner_candidates: &[CodeUnit],
18024) -> FullOwnerResolution {
18025 let Some(cpp) = analyzer.cpp else {
18026 return FullOwnerResolution::None;
18027 };
18028 let mut visible_files = HashSet::default();
18029 collect_include_closure(
18030 analyzer,
18031 cpp.include_target_index(),
18032 code_unit.source(),
18033 &mut visible_files,
18034 None,
18035 );
18036 let candidates = owner_candidates
18037 .iter()
18038 .filter(|candidate| candidate.is_class() && visible_files.contains(candidate.source()))
18039 .cloned()
18040 .collect::<Vec<_>>();
18041 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18042 match collapse_owner_candidates(candidates.into_iter().map(|candidate| {
18043 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18044 (candidate, strength)
18045 })) {
18046 DirectOwnerResolution::UniqueFull(owner) => FullOwnerResolution::Unique(owner),
18047 DirectOwnerResolution::ForwardsOnly(_) | DirectOwnerResolution::None => {
18048 FullOwnerResolution::None
18049 }
18050 DirectOwnerResolution::Ambiguous => FullOwnerResolution::Ambiguous,
18051 }
18052}
18053
18054pub enum DirectOwnerResolution {
18055 None,
18056 ForwardsOnly(Vec<CodeUnit>),
18057 UniqueFull(CodeUnit),
18058 Ambiguous,
18059}
18060
18061enum FullOwnerResolution {
18062 None,
18063 Unique(CodeUnit),
18064 Ambiguous,
18065}
18066
18067fn directly_included_owner(
18068 analyzer: &CppGraphSource<'_>,
18069 code_unit: &CodeUnit,
18070 owner_candidates: &[CodeUnit],
18071) -> DirectOwnerResolution {
18072 let Some(cpp) = analyzer.cpp else {
18073 return DirectOwnerResolution::None;
18074 };
18075 let direct_includes: HashSet<ProjectFile> = analyzer
18076 .canonical_include_paths(code_unit.source())
18077 .unwrap_or_default()
18078 .into_iter()
18079 .flat_map(|include| {
18080 resolve_include_targets_with_index(
18081 code_unit.source(),
18082 &include,
18083 cpp.include_target_index(),
18084 )
18085 })
18086 .collect();
18087 let candidates = owner_candidates
18088 .iter()
18089 .filter(|candidate| candidate.is_class() && direct_includes.contains(candidate.source()))
18090 .cloned()
18091 .collect::<Vec<_>>();
18092 let candidates = prefer_member_declaring_owners(analyzer, code_unit, candidates);
18093 classify_direct_owner_candidates(analyzer, candidates.into_iter())
18094}
18095
18096fn prefer_member_declaring_owners(
18097 analyzer: &CppGraphSource<'_>,
18098 member: &CodeUnit,
18099 candidates: Vec<CodeUnit>,
18100) -> Vec<CodeUnit> {
18101 let matching = candidates
18102 .iter()
18103 .filter(|owner| owner_declares_member(analyzer, owner, member))
18104 .cloned()
18105 .collect::<Vec<_>>();
18106 if matching.is_empty() {
18107 candidates
18108 } else {
18109 matching
18110 }
18111}
18112
18113fn owner_declares_member(
18114 analyzer: &CppGraphSource<'_>,
18115 owner: &CodeUnit,
18116 member: &CodeUnit,
18117) -> bool {
18118 analyzer.direct_children(owner).into_iter().any(|child| {
18119 child.kind() == member.kind()
18120 && child.identifier() == member.identifier()
18121 && child.signature() == member.signature()
18122 })
18123}
18124
18125fn classify_direct_owner_candidates(
18126 analyzer: &CppGraphSource<'_>,
18127 candidates: impl Iterator<Item = CodeUnit>,
18128) -> DirectOwnerResolution {
18129 collapse_owner_candidates(candidates.map(|candidate| {
18130 let strength = cpp_class_declaration_strength(analyzer, &candidate);
18131 (candidate, strength)
18132 }))
18133}
18134
18135pub fn collapse_owner_candidates(
18136 candidates: impl Iterator<Item = (CodeUnit, CppClassDeclarationStrength)>,
18137) -> DirectOwnerResolution {
18138 let mut full_definition = None;
18139 let mut forwards = Vec::new();
18140 for (candidate, strength) in candidates {
18141 match strength {
18142 CppClassDeclarationStrength::Full if full_definition.is_some() => {
18143 return DirectOwnerResolution::Ambiguous;
18144 }
18145 CppClassDeclarationStrength::Full => full_definition = Some(candidate),
18146 CppClassDeclarationStrength::Forward => forwards.push(candidate),
18147 CppClassDeclarationStrength::Unknown => return DirectOwnerResolution::Ambiguous,
18148 }
18149 }
18150 if let Some(owner) = full_definition {
18151 DirectOwnerResolution::UniqueFull(owner)
18152 } else if !forwards.is_empty() {
18153 DirectOwnerResolution::ForwardsOnly(forwards)
18154 } else {
18155 DirectOwnerResolution::None
18156 }
18157}
18158
18159#[cfg(any(test, feature = "test-support"))]
18160pub fn unique_logical_forward_owner_for_test(forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18161 unique_logical_forward_owner(forwards)
18162}
18163
18164fn unique_logical_forward_owner(mut forwards: Vec<CodeUnit>) -> Option<CodeUnit> {
18165 let first = forwards.pop()?;
18166 forwards
18167 .iter()
18168 .all(|forward| same_logical_symbol(forward, &first))
18169 .then_some(first)
18170}
18171
18172pub fn cpp_class_declaration_strength(
18173 analyzer: &CppGraphSource<'_>,
18174 candidate: &CodeUnit,
18175) -> CppClassDeclarationStrength {
18176 let Some(cpp) = analyzer.cpp else {
18184 return uncached_cpp_class_declaration_strength(analyzer, candidate);
18185 };
18186 if let Some(strength) = cpp.cached_class_declaration_strength(candidate) {
18187 return strength;
18188 }
18189 let strength = uncached_cpp_class_declaration_strength(analyzer, candidate);
18190 cpp.cache_class_declaration_strength(candidate, strength);
18191 strength
18192}
18193
18194fn uncached_cpp_class_declaration_strength(
18195 analyzer: &CppGraphSource<'_>,
18196 candidate: &CodeUnit,
18197) -> CppClassDeclarationStrength {
18198 if let Some(facts) = analyzer.declaration_source_properties(candidate) {
18199 if facts
18200 .iter()
18201 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Full)
18202 {
18203 return CppClassDeclarationStrength::Full;
18204 }
18205 if facts
18206 .iter()
18207 .any(|fact| fact.class_strength == CppClassDeclarationStrength::Forward)
18208 {
18209 return CppClassDeclarationStrength::Forward;
18210 }
18211 }
18212 if let Some(cpp) = analyzer.cpp
18213 && let Some(prepared) = cpp.prepared_syntax(analyzer.token, candidate.source())
18214 {
18215 return cpp_class_declaration_strength_in_tree(
18216 analyzer,
18217 &cpp.recovered_export_class_index(analyzer.token, candidate.source()),
18218 candidate,
18219 prepared.source(),
18220 prepared.tree().root_node(),
18221 );
18222 }
18223 let Some(source) = analyzer.indexed_source(candidate.source()) else {
18224 return CppClassDeclarationStrength::Unknown;
18225 };
18226 let mut parser = Parser::new();
18227 if parser
18228 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18229 .is_err()
18230 {
18231 return CppClassDeclarationStrength::Unknown;
18232 }
18233 let Some(tree) = parser.parse(&source, None) else {
18234 return CppClassDeclarationStrength::Unknown;
18235 };
18236 let recovered_export_classes =
18239 CppRecoveredExportClassIndex::build(tree.root_node(), source.as_str());
18240 cpp_class_declaration_strength_in_tree(
18241 analyzer,
18242 &recovered_export_classes,
18243 candidate,
18244 &source,
18245 tree.root_node(),
18246 )
18247}
18248
18249fn cpp_class_declaration_strength_in_tree(
18250 analyzer: &CppGraphSource<'_>,
18251 recovered_export_classes: &CppRecoveredExportClassIndex,
18252 candidate: &CodeUnit,
18253 source: &str,
18254 root: Node<'_>,
18255) -> CppClassDeclarationStrength {
18256 let ranges = analyzer.ranges(candidate);
18257 let mut saw_forward = false;
18258 for range in ranges {
18259 match recovered_class_body_at(
18262 recovered_export_classes,
18263 root,
18264 source,
18265 candidate.identifier(),
18266 &range,
18267 ) {
18268 Some(true) => return CppClassDeclarationStrength::Full,
18269 Some(false) => {
18270 saw_forward = true;
18271 continue;
18272 }
18273 None => {}
18274 }
18275 let covers_range_start = |node: &Node<'_>| {
18282 node.start_byte() <= range.start_byte && node.end_byte() >= range.start_byte
18283 };
18284 let mut stack = Vec::new();
18285 if covers_range_start(&root) {
18286 stack.push(root);
18287 }
18288 while let Some(node) = stack.pop() {
18289 if node.start_byte() == range.start_byte
18290 && node.end_byte() == range.end_byte
18291 && matches!(
18292 node.kind(),
18293 "class_specifier" | "struct_specifier" | "union_specifier" | "enum_specifier"
18294 )
18295 {
18296 if cpp_class_node_has_body(node) {
18297 return CppClassDeclarationStrength::Full;
18298 }
18299 saw_forward = true;
18300 }
18301 let mut cursor = node.walk();
18302 stack.extend(node.named_children(&mut cursor).filter(covers_range_start));
18303 }
18304 }
18305 if saw_forward {
18306 CppClassDeclarationStrength::Forward
18307 } else {
18308 CppClassDeclarationStrength::Unknown
18309 }
18310}
18311
18312fn cpp_class_node_has_body(node: Node<'_>) -> bool {
18313 child_by_field_or_named_child_of_kinds(
18314 node,
18315 "body",
18316 &[
18317 "declaration_list",
18318 "field_declaration_list",
18319 "enumerator_list",
18320 ],
18321 )
18322 .is_some()
18323}
18324
18325#[derive(Clone, Copy, Debug, PartialEq, Eq)]
18326enum CppCTagKind {
18327 Struct,
18328 Union,
18329}
18330
18331fn indexed_c_tag_kind(analyzer: &CppGraphSource<'_>, code_unit: &CodeUnit) -> Option<CppCTagKind> {
18332 let declaration = analyzer.get_source(code_unit, false)?;
18333 let mut parser = Parser::new();
18334 parser
18335 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18336 .ok()?;
18337 let tree = parser.parse(&declaration, None)?;
18338 let mut stack = vec![tree.root_node()];
18339 while let Some(node) = stack.pop() {
18340 let kind = match node.kind() {
18341 "struct_specifier" => CppCTagKind::Struct,
18342 "union_specifier" => CppCTagKind::Union,
18343 _ => {
18344 let mut cursor = node.walk();
18345 stack.extend(node.named_children(&mut cursor));
18346 continue;
18347 }
18348 };
18349 if node
18350 .child_by_field_name("name")
18351 .is_some_and(|name| node_text(name, &declaration) == code_unit.identifier())
18352 {
18353 return Some(kind);
18354 }
18355 let mut cursor = node.walk();
18356 stack.extend(node.named_children(&mut cursor));
18357 }
18358 None
18359}
18360
18361pub fn visible_owner_from_member_name(ctx: &ScanCtx<'_>, code_unit: &CodeUnit) -> Option<CodeUnit> {
18362 if !code_unit.owner_is_type_scope() {
18363 return None;
18364 }
18365 let owner_fq = code_unit.fq().parent()?;
18366 ctx.analyzer
18367 .workspace_definitions()
18368 .exact(&owner_fq)
18369 .into_iter()
18370 .find(|candidate| candidate.is_class() && ctx.visibility.is_visible(ctx.file, candidate))
18371}
18372
18373pub fn same_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18374 same_logical_symbol(left, right) && left.source() == right.source()
18375}
18376
18377pub fn same_visible_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18378 same_symbol(left, right) || same_logical_symbol(left, right)
18379}
18380
18381pub fn same_visible_global_field_symbol(
18382 analyzer: &CppGraphSource<'_>,
18383 internal_linkage_cache: &mut HashMap<CodeUnit, bool>,
18384 left: &CodeUnit,
18385 right: &CodeUnit,
18386) -> bool {
18387 if same_symbol(left, right) {
18388 return true;
18389 }
18390 if !same_logical_symbol(left, right) {
18391 return false;
18392 }
18393 if cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, left)
18394 || cpp_global_field_has_internal_linkage_cached(analyzer, internal_linkage_cache, right)
18395 {
18396 left.source() == right.source()
18397 } else {
18398 true
18399 }
18400}
18401
18402fn cpp_global_field_has_internal_linkage_cached(
18403 analyzer: &CppGraphSource<'_>,
18404 cache: &mut HashMap<CodeUnit, bool>,
18405 candidate: &CodeUnit,
18406) -> bool {
18407 if let Some(internal) = cache.get(candidate) {
18408 return *internal;
18409 }
18410 #[cfg(any(test, feature = "test-support"))]
18411 note_cpp_global_field_internal_linkage_classification_for_test();
18412 let internal = cpp_global_field_has_internal_linkage(analyzer, candidate);
18413 cache.insert(candidate.clone(), internal);
18414 internal
18415}
18416
18417#[cfg(any(test, feature = "test-support"))]
18418thread_local! {
18419 static CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18420}
18421
18422#[cfg(any(test, feature = "test-support"))]
18423fn note_cpp_global_field_internal_linkage_classification_for_test() {
18424 CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST.with(|count| {
18425 count.set(count.get() + 1);
18426 });
18427}
18428
18429#[cfg(any(test, feature = "test-support"))]
18430pub fn with_cpp_global_field_internal_linkage_classification_counter_for_test<T>(
18431 body: impl FnOnce() -> T,
18432) -> (T, usize) {
18433 with_thread_local_counter_for_test(
18434 &CPP_GLOBAL_FIELD_INTERNAL_LINKAGE_CLASSIFICATIONS_FOR_TEST,
18435 body,
18436 )
18437}
18438
18439#[cfg(any(test, feature = "test-support"))]
18440fn with_thread_local_counter_for_test<T>(
18441 counter: &'static std::thread::LocalKey<Cell<usize>>,
18442 body: impl FnOnce() -> T,
18443) -> (T, usize) {
18444 counter.with(|count| {
18445 count.set(0);
18446 let result = body();
18447 let observed = count.get();
18448 count.set(0);
18449 (result, observed)
18450 })
18451}
18452
18453pub fn same_logical_symbol(left: &CodeUnit, right: &CodeUnit) -> bool {
18454 left.kind() == right.kind()
18455 && left.fq_name() == right.fq_name()
18456 && left.signature() == right.signature()
18457}
18458
18459pub fn select_alias_spelling_with_agreed_targets<'a, T: Eq>(
18462 candidates: &[&'a CodeUnit],
18463 mut structured_target: impl FnMut(&CodeUnit) -> Option<T>,
18464) -> Option<&'a CodeUnit> {
18465 let first = *candidates.first()?;
18466 if candidates.len() == 1 {
18467 return Some(first);
18468 }
18469 let first_target = structured_target(first)?;
18470 candidates
18471 .iter()
18472 .skip(1)
18473 .all(|candidate| structured_target(candidate).is_some_and(|target| target == first_target))
18474 .then_some(first)
18475}
18476
18477pub fn cpp_global_field_has_internal_linkage(
18478 analyzer: &CppGraphSource<'_>,
18479 candidate: &CodeUnit,
18480) -> bool {
18481 if !candidate.is_field() || candidate.short_name().contains('.') {
18482 return false;
18483 }
18484 let Some(local_linkage) = cpp_global_field_declaration_linkage(analyzer, candidate) else {
18485 return false;
18486 };
18487 match local_linkage {
18488 CppFieldLinkage::Internal => true,
18489 CppFieldLinkage::External => false,
18490 CppFieldLinkage::InternalUnlessExternalPeer => {
18491 !cpp_global_field_linkage_peers(analyzer, candidate)
18492 .filter_map(|peer| cpp_global_field_declaration_linkage(analyzer, &peer))
18493 .any(|linkage| matches!(linkage, CppFieldLinkage::External))
18494 }
18495 }
18496}
18497
18498fn cpp_global_field_linkage_peers<'a>(
18499 analyzer: &CppGraphSource<'a>,
18500 candidate: &'a CodeUnit,
18501) -> impl Iterator<Item = CodeUnit> + 'a {
18502 let name = candidate.fq().clone();
18503 analyzer
18504 .workspace_definitions()
18505 .exact(&name)
18506 .into_iter()
18507 .filter(move |peer| {
18508 if peer == candidate {
18509 return false;
18510 }
18511 #[cfg(any(test, feature = "test-support"))]
18512 note_cpp_global_field_linkage_peer_inspection_for_test();
18513 same_logical_symbol(peer, candidate)
18514 })
18515}
18516
18517#[cfg(any(test, feature = "test-support"))]
18518thread_local! {
18519 static CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST: Cell<usize> = const { Cell::new(0) };
18520}
18521
18522#[cfg(any(test, feature = "test-support"))]
18523fn note_cpp_global_field_linkage_peer_inspection_for_test() {
18524 CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST.with(|count| {
18525 count.set(count.get() + 1);
18526 });
18527}
18528
18529#[cfg(any(test, feature = "test-support"))]
18530pub fn with_cpp_global_field_linkage_peer_inspection_counter_for_test<T>(
18531 body: impl FnOnce() -> T,
18532) -> (T, usize) {
18533 with_thread_local_counter_for_test(&CPP_GLOBAL_FIELD_LINKAGE_PEER_INSPECTIONS_FOR_TEST, body)
18534}
18535
18536fn cpp_global_field_declaration_linkage(
18537 analyzer: &CppGraphSource<'_>,
18538 candidate: &CodeUnit,
18539) -> Option<CppFieldLinkage> {
18540 analyzer.cpp_field_linkage(candidate)
18541}
18542
18543pub fn cpp_canonical_alias_target_text(
18545 analyzer: &CppGraphSource<'_>,
18546 unit: &CodeUnit,
18547) -> Option<String> {
18548 let facts = analyzer.declaration_source_properties(unit)?;
18549 let first = facts.first()?.alias_target_text.as_ref()?;
18550 facts
18551 .iter()
18552 .all(|fact| fact.alias_target_text.as_ref() == Some(first))
18553 .then(|| first.clone())
18554}
18555
18556#[cfg(test)]
18557mod tests {
18558 #[test]
18559 #[hardwon("05fbd05c6bd7: C++ declaration recovery regressed for this structured syntax edge")]
18560 fn issue_3089_statement_formal_at_end_of_replacement() {
18561 let parameters = vec!["handle".to_owned(), "block".to_owned()];
18562 for replacement in [
18563 "do { header_event_t* event; if ((handle)->active) block } while (0)",
18564 "do { header_event_t* event; block } while (0)",
18565 ] {
18566 assert!(
18567 super::VisibilityIndex::parse_macro_replacement_body(replacement, ¶meters)
18568 .is_some(),
18569 "{replacement}"
18570 );
18571 }
18572 }
18573 use super::*;
18574 use brokk_bifrost_test_macros::hardwon;
18575
18576 #[test]
18577 #[hardwon("2e5169325a14: C++ declaration recovery regressed for this structured syntax edge")]
18578 fn c_sizeof_expression_type_candidate_is_structural_and_c_only() {
18579 let source = "int size(void) { return sizeof(((Payload))); }\n";
18580 let mut parser = Parser::new();
18581 parser
18582 .set_language(&tree_sitter_cpp::LANGUAGE.into())
18583 .expect("C++ grammar");
18584 let tree = parser.parse(source, None).expect("fixture tree");
18585 let start = source.find("Payload").expect("sizeof operand");
18586 let node = tree
18587 .root_node()
18588 .named_descendant_for_byte_range(start, start + "Payload".len())
18589 .expect("focused operand");
18590 let c_file = ProjectFile::new(std::env::temp_dir(), "issue.c");
18591 let cpp_file = ProjectFile::new(std::env::temp_dir(), "issue.cpp");
18592
18593 assert_eq!(node.kind(), "identifier");
18594 assert!(is_c_sizeof_expression_type_candidate(&c_file, node));
18595 assert!(!is_c_sizeof_expression_type_candidate(&cpp_file, node));
18596 }
18597
18598 fn parse_cpp(source: &str) -> Tree {
18599 crate::diagnostics::parse_cpp_tree(source)
18600 }
18601
18602 #[test]
18603 #[hardwon("#4271: source splitting included a new-array declarator in the allocation type")]
18604 fn cpp_new_expression_type_comes_from_the_structured_type_field() {
18605 let source = "auto value = new ns::Box<int> [ 3 ];";
18606 let tree = parse_cpp(source);
18607 let mut pending = vec![tree.root_node()];
18608 let allocation = loop {
18609 let node = pending.pop().expect("new expression in fixture");
18610 if node.kind() == "new_expression" {
18611 break node;
18612 }
18613 let mut cursor = node.walk();
18614 pending.extend(
18615 node.named_children(&mut cursor)
18616 .collect::<Vec<_>>()
18617 .into_iter()
18618 .rev(),
18619 );
18620 };
18621
18622 let type_node = cpp_new_expression_type_node(allocation).expect("structured type field");
18623 assert_eq!(allocation.child_by_field_name("type"), Some(type_node));
18624 assert_eq!(node_text(type_node, source), "ns::Box<int>");
18625 assert_eq!(
18626 allocation
18627 .child_by_field_name("declarator")
18628 .expect("array declarator")
18629 .kind(),
18630 "new_declarator"
18631 );
18632
18633 let old_split = normalize_cpp_whitespace(node_text(allocation, source));
18634 let old_split = old_split
18635 .strip_prefix("new ")
18636 .unwrap_or(old_split.as_str())
18637 .split(['(', '{'])
18638 .next()
18639 .expect("old split result");
18640 assert_ne!(old_split.trim(), node_text(type_node, source));
18641 }
18642
18643 fn named_node_at<'tree>(tree: &'tree Tree, source: &str, needle: &str) -> Node<'tree> {
18644 let start = source.find(needle).expect("fixture needle");
18645 tree.root_node()
18646 .named_descendant_for_byte_range(start, start + needle.len())
18647 .expect("node at needle")
18648 }
18649
18650 fn prepared_cpp(source: &str) -> PreparedSyntaxTree {
18651 let tree = crate::diagnostics::parse_cpp_tree(source);
18652 PreparedSyntaxTree::new(
18653 PreparedSyntaxSource::Exact(Arc::from(source)),
18654 tree,
18655 compute_line_starts(source),
18656 LanguageDialect::Standard(Language::Cpp),
18657 PreparedSourceOrigin::Disk,
18658 None,
18659 )
18660 }
18661
18662 fn unresolved_include_before(source: &str, reference: &str) -> bool {
18663 let file = ProjectFile::new(std::env::temp_dir(), "issue-3078.cpp");
18664 let prepared = prepared_cpp(source);
18665 let facts = collect_structured_include_facts(&prepared);
18666 let include_targets = IncludeTargetIndex::build([&file]);
18667 has_unresolved_include_visible_before_in_prepared(
18668 &file,
18669 &prepared,
18670 &include_targets,
18671 &facts,
18672 source.find(reference).expect("reference fixture"),
18673 )
18674 }
18675
18676 #[test]
18677 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18678 fn unresolved_include_before_reference_is_visible() {
18679 let source = "#include \"missing.h\"\nint use = Missing;\n";
18680 assert!(unresolved_include_before(source, "Missing"));
18681 }
18682
18683 #[test]
18684 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18685 fn unresolved_include_after_reference_is_not_visible() {
18686 let source = "int use = Missing;\n#include \"missing.h\"\n";
18687 assert!(!unresolved_include_before(source, "Missing"));
18688 }
18689
18690 #[test]
18691 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18692 fn unresolved_include_in_incompatible_sibling_branch_is_not_visible() {
18693 let source = "#if FEATURE\n#include \"missing.h\"\n#else\nint use = Missing;\n#endif\n";
18694 assert!(!unresolved_include_before(source, "Missing"));
18695 }
18696
18697 #[test]
18698 #[hardwon("5f8417432427: C++ declaration recovery regressed for this structured syntax edge")]
18699 fn unresolved_include_in_current_branch_is_visible() {
18700 let source =
18701 "#if FEATURE\n#include \"missing.h\"\nint use = Missing;\n#else\nint other;\n#endif\n";
18702 assert!(unresolved_include_before(source, "Missing"));
18703 }
18704
18705 const STOLEN_BRACE_CASCADE: &str = r#"namespace app {
18710namespace matchers {
18711 namespace detail {
18712 class API [[nodiscard]] First {
18713 public:
18714 int value() const { return count_ + 1; }
18715 private:
18716 int count_;
18717 };
18718 class API [[nodiscard]] Second {
18719 public:
18720 int value() const { return count_ + 2; }
18721 private:
18722 int count_;
18723 };
18724 } // namespace detail
18725
18726 template <typename T>
18727 void tail_function(MatcherBase<T> const& value);
18728
18729 class TailClass {};
18730} // namespace matchers
18731} // namespace app
18732
18733struct AfterAll {};
18734"#;
18735
18736 #[test]
18737 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18738 fn orphaned_namespace_scope_index_restores_a_stolen_brace_cascade() {
18739 let source = STOLEN_BRACE_CASCADE;
18740 let tree = parse_cpp(source);
18741 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18742
18743 let tail_class = named_node_at(&tree, source, "TailClass");
18744 assert!(
18745 !has_ancestor_kind(tail_class, "namespace_definition"),
18746 "the fixture must reproduce the recovery: the tail has no namespace ancestor"
18747 );
18748 let displaced = named_node_at(&tree, source, "Second");
18749 assert_eq!(
18750 enclosing_namespace_components(displaced, source),
18751 Some(vec!["app".to_string(), "matchers".to_string()]),
18752 "the fixture must displace the second class out of detail"
18753 );
18754
18755 let components = |needle: &str| {
18756 index.enclosing_namespace_components(named_node_at(&tree, source, needle), source)
18757 };
18758 assert_eq!(components("First"), ["app", "matchers", "detail"]);
18759 assert_eq!(components("Second"), ["app", "matchers", "detail"]);
18760 assert_eq!(components("MatcherBase<T>"), ["app", "matchers"]);
18761 assert_eq!(components("tail_function"), ["app", "matchers"]);
18762 assert_eq!(components("TailClass"), ["app", "matchers"]);
18763 assert!(components("AfterAll").is_empty());
18764 }
18765
18766 #[test]
18767 #[hardwon("c820d8c6a8e3: C++ declaration recovery regressed for this structured syntax edge")]
18768 fn orphaned_namespace_scope_index_is_empty_without_lost_scopes() {
18769 let clean = "namespace a { namespace b { class C {}; } class D {}; }\n";
18770 let tree = parse_cpp(clean);
18771 assert!(!tree.root_node().has_error());
18772 assert!(OrphanedNamespaceScopeIndex::build(tree.root_node(), clean).is_empty());
18773
18774 let damaged = "namespace a { namespace b { UNKNOWN_MACRO(x) } class C {}; }\n";
18777 let tree = parse_cpp(damaged);
18778 assert!(tree.root_node().has_error());
18779 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), damaged);
18780 assert_eq!(
18781 index.enclosing_namespace_components(named_node_at(&tree, damaged, "class C"), damaged),
18782 ["a"]
18783 );
18784 }
18785
18786 const COLLAPSED_NAMESPACE_HEAD: &str = r#"namespace app {
18793
18794 class Target {
18795 int value_;
18796 };
18797
18798 template <typename T>
18799 class Holder {
18800 public:
18801 explicit constexpr Holder( T lhs ): m_lhs( lhs ) {}
18802
18803#define HOLDER_DEFINE_OP( id, op ) \
18804 template <typename U> \
18805 constexpr friend auto operator op( Holder&& lhs, U&& rhs ) \
18806 -> std::enable_if_t<is_##id##_comparable<T, U>::value, Target> { \
18807 return Target{}; \
18808 }
18809
18810 HOLDER_DEFINE_OP( equal, == )
18811#undef HOLDER_DEFINE_OP
18812 T m_lhs;
18813 };
18814
18815 class Tail {};
18816}
18817"#;
18818
18819 #[test]
18820 #[hardwon("a438f125e172: C++ declaration recovery regressed for this structured syntax edge")]
18821 fn orphaned_namespace_scope_index_names_a_collapsed_namespace_head() {
18822 let source = COLLAPSED_NAMESPACE_HEAD;
18823 let tree = parse_cpp(source);
18824 let target = named_node_at(&tree, source, "class Target");
18825
18826 assert!(
18827 !has_ancestor_kind(target, "namespace_definition"),
18828 "the fixture must reproduce the collapse: the class has no namespace ancestor"
18829 );
18830 let head = target.parent().expect("the collapsed namespace envelope");
18831 assert_eq!(
18832 head.kind(),
18833 "ERROR",
18834 "the fixture must keep the namespace head in an ERROR node"
18835 );
18836
18837 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), source);
18838 assert_eq!(
18839 index.enclosing_namespace_components(target, source),
18840 ["app"]
18841 );
18842 assert_eq!(
18843 index.enclosing_namespace_levels(target, source),
18844 vec![(
18845 "app".to_string(),
18846 Range {
18847 start_byte: 0,
18848 end_byte: source.len() - 1,
18849 start_line: 1,
18850 end_line: 25,
18851 },
18852 Range {
18853 start_byte: 10,
18854 end_byte: 13,
18855 start_line: 1,
18856 end_line: 1,
18857 },
18858 )],
18859 "the recovered namespace owns its full construct, not its first member or just its header"
18860 );
18861 }
18862
18863 #[test]
18864 #[hardwon("#5868: a literal namespace head inside a damaged function wrapper lost its owner")]
18865 fn namespace_body_name_components_recovers_literal_function_head() {
18866 let source = "OPAQUE_PREFIX\nnamespace recovered\n{\nint member;\n}\nint member;\n";
18872 let tree = parse_cpp(source);
18873 let keyword_start = source.find("namespace recovered").unwrap();
18874 let keyword = tree
18875 .root_node()
18876 .named_descendant_for_byte_range(keyword_start, keyword_start + "namespace".len())
18877 .expect("literal keyword node");
18878 assert_eq!(keyword.kind(), "identifier");
18879 assert_eq!(keyword.child_count(), 0);
18880 assert!(!keyword.is_missing() && !keyword.has_error());
18881 assert_eq!(node_text(keyword, source), "namespace");
18882 let wrapper = keyword.parent().expect("actual wrapper");
18883 assert_eq!(wrapper.kind(), "function_definition");
18884 assert!(wrapper.has_error());
18885 let mut cursor = wrapper.walk();
18886 let children = wrapper
18887 .children(&mut cursor)
18888 .filter(|child| child.kind() != "comment")
18889 .collect::<Vec<_>>();
18890 assert_eq!(
18891 children.len(),
18892 4,
18893 "all named and unnamed children accounted: {children:?}"
18894 );
18895 let prefix = children[0];
18896 assert_eq!(prefix.kind(), "type_identifier");
18897 assert_eq!(prefix.child_count(), 0);
18898 assert!(!prefix.is_missing() && !prefix.has_error());
18899 assert_eq!(wrapper.child_by_field_name("type"), Some(prefix));
18900 assert_eq!(children[1], keyword);
18901 assert_eq!(wrapper.child_by_field_name("declarator"), Some(keyword));
18902 let error = children[2];
18903 assert_eq!(error.kind(), "ERROR");
18904 assert!(!error.is_missing());
18905 let mut cursor = error.walk();
18906 let error_children = error
18907 .children(&mut cursor)
18908 .filter(|child| child.kind() != "comment")
18909 .collect::<Vec<_>>();
18910 assert_eq!(
18911 error_children.len(),
18912 1,
18913 "exact literal-name error envelope: {error_children:?}"
18914 );
18915 let name = error_children[0];
18916 assert!(matches!(name.kind(), "identifier" | "namespace_identifier"));
18917 assert_eq!(name.child_count(), 0);
18918 assert!(!name.is_missing() && !name.has_error());
18919 assert_eq!(node_text(name, source), "recovered");
18920 let body = wrapper.child_by_field_name("body").expect("actual body");
18921 assert_eq!(children[3], body);
18922 assert_eq!(body.kind(), "compound_statement");
18923 assert!(!body.is_missing());
18924 let open = body.child(0).expect("actual first child");
18925 assert_eq!(open.kind(), "{");
18926 assert!(!open.is_missing() && !open.has_error());
18927 assert_eq!(open.start_byte(), body.start_byte());
18928 let (head, names) = namespace_body_name_components(wrapper, body, source)
18929 .expect("strict synthetic literal head recognized");
18930 assert_eq!(head, keyword);
18931 assert_eq!(
18932 names
18933 .iter()
18934 .map(|(name, _)| name.as_str())
18935 .collect::<Vec<_>>(),
18936 ["recovered"]
18937 );
18938 assert_eq!(
18939 (names[0].1.start_byte, names[0].1.end_byte),
18940 (name.start_byte(), name.end_byte())
18941 );
18942 let inside = named_node_at(&tree, source, "int member;");
18943 assert!(body.start_byte() < inside.start_byte() && inside.end_byte() < body.end_byte());
18944 }
18945
18946 #[test]
18947 #[hardwon("#5868: refusal controls must actually exercise the namespace body helper")]
18948 fn orphaned_namespace_scope_index_refuses_unproven_function_heads() {
18949 for refusal in [
18950 "int ordinary(int value) { return value; }",
18951 "OPAQUE_PREFIX namespace_ recovered { int member; }",
18952 "OPAQUE_PREFIX namespace recovered other { int member; }",
18953 "OPAQUE_PREFIX namespace recovered(int value) { return value; }",
18954 ] {
18955 let tree = parse_cpp(refusal);
18956 let mut stack = vec![tree.root_node()];
18957 let mut helper_calls = 0;
18958 while let Some(node) = stack.pop() {
18959 if node.kind() == "function_definition"
18960 && let Some(body) = node.child_by_field_name("body")
18961 {
18962 helper_calls += 1;
18963 assert_eq!(
18964 (node.start_byte(), node.end_byte()),
18965 (0, refusal.len()),
18966 "guard control must exercise its complete envelope: source={refusal}, node={node:?}"
18967 );
18968 assert!(
18969 namespace_body_name_components(node, body, refusal).is_none(),
18970 "refused shape acquired namespace: source={refusal}, node={node:?}, body={body:?}"
18971 );
18972 }
18973 let mut cursor = node.walk();
18974 stack.extend(node.named_children(&mut cursor));
18975 }
18976 assert_eq!(
18977 helper_calls,
18978 1,
18979 "guard control must reach exactly its actual function/body helper call: source={refusal}, tree={:?}",
18980 tree.root_node()
18981 );
18982 }
18983
18984 for absent in [
18987 "OPAQUE_PREFIX namespace recovered = other { int member; }",
18988 "OPAQUE_PREFIX namespace recovered int member;",
18989 ] {
18990 let tree = parse_cpp(absent);
18991 assert!(
18992 tree.root_node().has_error(),
18993 "expected malformed input: {absent}"
18994 );
18995 let mut stack = vec![tree.root_node()];
18996 let mut helper_candidates = 0;
18997 while let Some(node) = stack.pop() {
18998 if node.kind() == "function_definition"
18999 && node.child_by_field_name("body").is_some()
19000 {
19001 helper_candidates += 1;
19002 }
19003 let mut cursor = node.walk();
19004 stack.extend(node.named_children(&mut cursor));
19005 }
19006 assert_eq!(
19007 helper_candidates,
19008 0,
19009 "parser-absence control unexpectedly acquired a helper envelope: source={absent}, tree={:?}",
19010 tree.root_node()
19011 );
19012 }
19013 }
19014
19015 const MACRO_OPENED_SCOPE: &str = r#"#define APP_IF_ACTIVE if (active) {
19024
19025namespace app {
19026namespace detail {
19027
19028bool check(bool active) {
19029 APP_IF_ACTIVE
19030 return true;
19031 }
19032 return false;
19033}
19034
19035class Tail {};
19036
19037} // namespace detail
19038} // namespace app
19039"#;
19040
19041 const REFLECTION_CASE_LABEL_RECOVERY: &str = r#"namespace simdjson {
19048namespace compile_time {
19049namespace number_parsing {
19050consteval int leading_zeroes(uint64_t input_num, int last_bit = 0) {
19051 auto hex_to_u32 = [&] [[nodiscard]] () -> uint32_t {
19052 auto digit = [](uint8_t c) -> uint32_t {
19053 }
19054 }
19055 if (a) {
19056 while (a) {
19057 }
19058 }
19059 switch (c) {
19060 case '{': {
19061 }
19062 }
19063 }
19064 while (a) {
19065 cursor += field.second;
19066 if (a) {
19067 case '{': {
19068 if (a) {
19069 }
19070 }
19071 case 'n': {
19072 if (a) {
19073 }
19074 }
19075 }
19076 }
19077 if (a) {
19078 simdjson_consteval_error("Expected '}'");
19079 }
19080}
19081}
19082}
19083"#;
19084
19085 fn real_brace_token_counts(root: Node<'_>) -> (usize, usize) {
19087 let mut stack = vec![root];
19088 let mut counts = (0, 0);
19089 while let Some(node) = stack.pop() {
19090 match (node.kind(), node.is_missing()) {
19091 ("{", false) => counts.0 += 1,
19092 ("}", false) => counts.1 += 1,
19093 _ => {}
19094 }
19095 let mut cursor = node.walk();
19096 stack.extend(node.children(&mut cursor));
19097 }
19098 counts
19099 }
19100
19101 fn namespace_open_byte(source: &str, head: &str) -> usize {
19103 let at = source.find(head).expect("fixture namespace head");
19104 at + head.len() - 1
19105 }
19106
19107 fn assert_namespace_heads_have_no_matching_close(root: Node<'_>, source: &str, heads: &[&str]) {
19108 let index = OrphanedNamespaceScopeIndex::build(root, source);
19109 for head in heads {
19110 assert_eq!(
19111 index.matching_close_brace(namespace_open_byte(source, head)),
19112 None,
19113 "{head} must not claim a close belonging to an inner scope"
19114 );
19115 }
19116 }
19117
19118 #[test]
19119 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19120 fn orphaned_namespace_scope_index_pairs_nothing_when_a_scope_open_is_lost() {
19121 let source = MACRO_OPENED_SCOPE;
19122 let tree = parse_cpp(source);
19123 let root = tree.root_node();
19124 assert_eq!(
19125 real_brace_token_counts(root),
19126 (4, 5),
19127 "the fixture must reproduce the loss: one scope the source opens \
19128 has no `{{` token, so the stream holds an extra real close"
19129 );
19130
19131 assert_namespace_heads_have_no_matching_close(
19132 root,
19133 source,
19134 &["namespace app {", "namespace detail {"],
19135 );
19136 }
19137
19138 #[test]
19139 #[hardwon("5312adbcf9bf: C++ declaration recovery regressed for this structured syntax edge")]
19140 fn orphaned_namespace_scope_index_leaves_a_declaration_after_every_close_at_file_scope() {
19141 let source = format!("{MACRO_OPENED_SCOPE}struct AfterAll {{}};\n");
19145 let tree = parse_cpp(&source);
19146 let index = OrphanedNamespaceScopeIndex::build(tree.root_node(), &source);
19147 let after_all = named_node_at(&tree, &source, "struct AfterAll");
19148 assert!(
19149 index
19150 .enclosing_namespace_components(after_all, &source)
19151 .is_empty()
19152 );
19153 }
19154
19155 #[test]
19156 #[hardwon("1352f739be3d: C++ declaration recovery regressed for this structured syntax edge")]
19157 fn orphaned_namespace_scope_index_preserves_structured_reflection_closes() {
19158 let source = REFLECTION_CASE_LABEL_RECOVERY;
19159 let tree = parse_cpp(source);
19160 let root = tree.root_node();
19161 let mut stack = vec![root];
19162 let mut mis_lexed_opens = Vec::new();
19163 while let Some(node) = stack.pop() {
19164 if node.kind() == "character"
19165 && &source[node.start_byte()..node.end_byte()] == "{"
19166 && node
19167 .parent()
19168 .is_some_and(|parent| parent.kind() != "char_literal")
19169 {
19170 mis_lexed_opens.push(node.start_position().row + 1);
19171 }
19172 let mut cursor = node.walk();
19173 stack.extend(node.children(&mut cursor));
19174 }
19175 assert!(
19176 mis_lexed_opens.is_empty(),
19177 "ABI15 should keep each case-label `{{` inside its `char_literal`; \
19178 the old ABI14 mis-lex is no longer a valid recovery witness"
19179 );
19180 assert_eq!(
19181 real_brace_token_counts(root),
19182 (17, 17),
19183 "the improved parse retains balanced real brace tokens"
19184 );
19185 assert_eq!(root.kind(), "translation_unit");
19186 assert!(root.has_error(), "the malformed body remains incomplete");
19187 let index = OrphanedNamespaceScopeIndex::build(root, source);
19188 assert_eq!(
19189 index.matching_close_brace(namespace_open_byte(source, "namespace simdjson {")),
19190 None,
19191 "a missing outer close must not borrow an inner namespace's real close"
19192 );
19193 for (head, close) in [
19194 ("namespace compile_time {", source.len() - 2),
19195 ("namespace number_parsing {", source.len() - 4),
19196 ] {
19197 assert_eq!(
19198 index
19199 .matching_close_brace(namespace_open_byte(source, head))
19200 .map(|range| range.start_byte),
19201 Some(close),
19202 "{head} retains its own real close"
19203 );
19204 }
19205 }
19206
19207 #[test]
19208 #[hardwon("#2243: an empty namespace could hide a nested indexed owner suffix")]
19209 fn empty_parser_namespace_requires_a_nested_indexed_owner_suffix() {
19210 let indexed = ["cache", "Outer", "Inner"].map(str::to_string);
19211 assert!(indexed_namespace_path_is_recoverable(&[], &indexed, 2));
19212 assert!(!indexed_namespace_path_is_recoverable(&[], &indexed, 1));
19213 assert!(indexed_namespace_path_is_recoverable(
19214 &["cache".to_string()],
19215 &indexed,
19216 1,
19217 ));
19218 }
19219
19220 #[test]
19221 #[hardwon("#1876: lookup ordering was partial and changed with unrelated workspace content")]
19222 fn sort_lookup_units_totally_orders_every_identity_field() {
19223 let file = ProjectFile::new(std::env::temp_dir(), "issue_1876.cpp");
19224 let base = CodeUnit::with_signature(
19225 file.clone(),
19226 CodeUnitType::Function,
19227 "scope",
19228 "value",
19229 Some("()".to_string()),
19230 false,
19231 );
19232 let different_kind = CodeUnit::with_signature(
19233 file.clone(),
19234 CodeUnitType::Field,
19235 "scope",
19236 "value",
19237 Some("()".to_string()),
19238 false,
19239 );
19240 let synthetic = base.with_synthetic(true);
19241
19242 let interner = segment_interner();
19243 let mut member_fq = FqName::new();
19244 member_fq.push(interner.intern("scope", SegmentKind::Package));
19245 member_fq.push(interner.intern("value", SegmentKind::Member));
19246 let different_package_boundary = CodeUnit::from_fq(
19247 file.clone(),
19248 CodeUnitType::Function,
19249 member_fq,
19250 0,
19251 Some("()".to_string()),
19252 false,
19253 );
19254
19255 let mut unknown_fq = FqName::new();
19256 unknown_fq.push(interner.intern("scope", SegmentKind::Package));
19257 unknown_fq.push(interner.intern("value", SegmentKind::Unknown));
19258 let different_segment_kind = CodeUnit::from_fq(
19259 file,
19260 CodeUnitType::Function,
19261 unknown_fq,
19262 1,
19263 Some("()".to_string()),
19264 false,
19265 );
19266
19267 let input = vec![
19268 base,
19269 different_kind,
19270 synthetic,
19271 different_package_boundary,
19272 different_segment_kind,
19273 ];
19274 let mut expected = input.clone();
19275 sort_lookup_units(&mut expected);
19276 assert!(expected.windows(2).all(|pair| {
19277 let mut ordered = pair.to_vec();
19278 sort_lookup_units(&mut ordered);
19279 ordered == pair && pair[0] != pair[1]
19280 }));
19281
19282 let mut reversed = input.clone();
19283 reversed.reverse();
19284 sort_lookup_units(&mut reversed);
19285 assert_eq!(reversed, expected);
19286
19287 let mut rotated = input;
19288 rotated.rotate_left(2);
19289 sort_lookup_units(&mut rotated);
19290 assert_eq!(rotated, expected);
19291 }
19292
19293 #[test]
19294 #[hardwon("6ffe85773652: C++ declaration recovery regressed for this structured syntax edge")]
19295 fn structured_conditional_array_bound_preserves_the_real_guard() {
19296 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";
19297 let guarded = "#ifdef FEATURE_X\nvoid target(void);\n#endif\n";
19298 let parse = |source: &str| {
19299 let mut parser = Parser::new();
19300 parser
19301 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19302 .expect("C++ grammar");
19303 parser.parse(source, None).expect("fixture tree")
19304 };
19305
19306 let tree = parse(damaged);
19307 let root = tree.root_node();
19308 assert!(
19309 !root.has_error(),
19310 "the native conditional-array grammar now reads the original recovery witness cleanly: {}",
19311 root.to_sexp()
19312 );
19313 let target = damaged.find("target").expect("target byte");
19314 let declaration = root
19315 .descendant_for_byte_range(target, target + "target".len())
19316 .and_then(|mut node| {
19317 loop {
19318 if node.kind() == "declaration" {
19319 break Some(node);
19320 }
19321 node = node.parent()?;
19322 }
19323 })
19324 .expect("declaration after the displaced terminator");
19325 let outer = declaration
19326 .parent()
19327 .filter(|node| node.kind() == "preproc_ifdef")
19328 .expect("ordinary outer include guard");
19329 let bound = outer
19330 .named_children(&mut outer.walk())
19331 .filter(|node| node.kind() == "declaration")
19332 .filter_map(|node| node.child_by_field_name("declarator"))
19333 .find(|node| node.kind() == "array_declarator")
19334 .and_then(|node| node.child_by_field_name("size"))
19335 .expect("structured conditional array bound");
19336 assert_eq!(bound.kind(), "preproc_array_size");
19337 let conditional = bound
19338 .named_children(&mut bound.walk())
19339 .find(|node| node.kind() == "preproc_ifdef")
19340 .expect("array-size conditional");
19341 assert!(conditional.end_byte() <= declaration.start_byte());
19342 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19343 assert!(
19344 !std::iter::successors(Some(declaration), |node| node.parent())
19345 .any(|ancestor| same_node(ancestor, conditional)),
19346 "the declaration must not inherit the array-size feature guard"
19347 );
19348 assert!(cpp_displaced_preprocessor_terminator(outer).is_none());
19349 assert!(preprocessor_conditional_contains_descendant(
19350 outer,
19351 declaration
19352 ));
19353
19354 let tree = parse(guarded);
19355 let conditional = tree
19356 .root_node()
19357 .named_child(0)
19358 .filter(|node| node.kind() == "preproc_ifdef")
19359 .expect("ordinary conditional");
19360 let declaration = conditional
19361 .named_children(&mut conditional.walk())
19362 .find(|node| node.kind() == "declaration")
19363 .expect("guarded declaration");
19364 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19365 assert!(preprocessor_conditional_contains_descendant(
19366 conditional,
19367 declaration
19368 ));
19369
19370 let damaged_alternative = format!(
19371 "#ifndef NO_FEATURE\nvoid enabled(void) {{}}\n#else\nvoid disabled(void) {{\n{}\n}}\n#endif\n",
19372 "UNUSED(value)\n".repeat(64)
19373 );
19374 let tree = parse(&damaged_alternative);
19375 let conditional = tree
19376 .root_node()
19377 .named_child(0)
19378 .filter(|node| node.kind() == "preproc_ifdef")
19379 .expect("outer conditional with an alternative");
19380 assert!(conditional.has_error());
19381 assert!(conditional.child_by_field_name("alternative").is_some());
19382 assert!(
19383 conditional
19384 .child(conditional.child_count() - 1)
19385 .is_some_and(|child| child.kind() == "#endif" && !child.is_missing())
19386 );
19387 assert!(cpp_displaced_preprocessor_terminator(conditional).is_none());
19388
19389 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";
19390 let tree = parse(split_declaration);
19391 let root = tree.root_node();
19392 let conditional = root
19393 .named_children(&mut root.walk())
19394 .find(|node| node.kind() == "preproc_ifdef" && node.start_position().row == 3)
19395 .expect("split declaration conditional");
19396 let target = split_declaration
19397 .find("static int target")
19398 .expect("target byte");
19399 let boundary =
19400 cpp_displaced_preprocessor_boundary(conditional).expect("split declaration boundary");
19401 assert!(boundary.end_byte <= target, "{boundary:?}");
19402 assert_eq!(boundary.end_line, 9, "{boundary:?}");
19403 let target_node = root
19404 .descendant_for_byte_range(target, target + "static".len())
19405 .expect("target node");
19406 assert!(!preprocessor_conditional_contains_descendant(
19407 conditional,
19408 target_node
19409 ));
19410 }
19411
19412 #[test]
19413 #[hardwon("6ba7704de3ed: C++ declaration recovery regressed for this structured syntax edge")]
19414 fn fragmented_reference_guard_is_recovered() {
19415 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";
19416 let mut parser = Parser::new();
19417 parser
19418 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19419 .expect("C++ grammar");
19420 let tree = parser.parse(source, None).expect("fixture tree");
19421 let start = source.rfind("helper").expect("reference byte");
19422 let node = tree
19423 .root_node()
19424 .descendant_for_byte_range(start, start + "helper".len())
19425 .expect("reference node");
19426 let mut expected = HashSet::default();
19427 expected.insert(PreprocessorGuard::Boolean(BooleanGuardExpression::All(
19428 vec![
19429 BooleanGuardExpression::Truthy("HAVE_ONE".to_string()),
19430 BooleanGuardExpression::Truthy("HAVE_TWO".to_string()),
19431 ],
19432 )));
19433 assert_eq!(preprocessor_guard_environment(node, source), Some(expected));
19434 }
19435
19436 #[test]
19437 fn expression_defined_and_ifndef_guards_are_incompatible() {
19438 let source = "#if defined(WIN_MODE)\nint selected;\n#endif\n#ifndef WIN_MODE\nint rejected;\n#endif\n";
19439 let mut parser = Parser::new();
19440 parser
19441 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19442 .expect("C++ grammar");
19443 let tree = parser.parse(source, None).expect("fixture tree");
19444 let root = tree.root_node();
19445 let selected_start = source.find("selected").expect("selected declaration");
19446 let rejected_start = source.find("rejected").expect("rejected declaration");
19447 let selected = root
19448 .descendant_for_byte_range(selected_start, selected_start + "selected".len())
19449 .expect("selected node");
19450 let rejected = root
19451 .descendant_for_byte_range(rejected_start, rejected_start + "rejected".len())
19452 .expect("rejected node");
19453 let selected_guards =
19454 preprocessor_guard_environment(selected, source).expect("selected guards");
19455 let rejected_guards =
19456 preprocessor_guard_environment(rejected, source).expect("rejected guards");
19457
19458 assert!(
19459 merge_preprocessor_guards(&selected_guards, &rejected_guards).is_none(),
19460 "opposite spellings of one macro guard must contradict"
19461 );
19462 }
19463
19464 #[test]
19465 #[hardwon("02b3765c2a06: C++ declaration recovery regressed for this structured syntax edge")]
19466 fn split_language_linkage_wrapper_does_not_contradict_later_c_branch() {
19467 let source = r#"#ifdef _WIN32
19468#if defined(__cplusplus)
19469extern "C"
19470#endif
19471int platform_api(void);
19472#endif
19473
19474#ifdef _WIN32
19475static int entropy_target(void) { return 0; }
19476#else
19477#ifdef HAVE_COMMON_RANDOM
19478static int other_target(void) { return 0; }
19479#elif defined(HAVE_GETENTROPY)
19480static int entropy_target(void) { return 1; }
19481static int use_entropy(void) { return entropy_target(); }
19482#endif
19483#endif
19484"#;
19485 let mut parser = Parser::new();
19486 parser
19487 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19488 .expect("C++ grammar");
19489 let tree = parser.parse(source, None).expect("fixture tree");
19490 let start = source.rfind("entropy_target()").expect("reference");
19491 let node = tree
19492 .root_node()
19493 .descendant_for_byte_range(start, start + "entropy_target".len())
19494 .expect("reference node");
19495 let guards = preprocessor_guard_environment(node, source).expect("active C branch");
19496 assert!(
19497 guards.contains(&PreprocessorGuard::Undefined("_WIN32".to_string())),
19498 "{guards:#?}"
19499 );
19500 assert!(
19501 guards.contains(&PreprocessorGuard::Undefined(
19502 "HAVE_COMMON_RANDOM".to_string()
19503 )),
19504 "{guards:#?}"
19505 );
19506 assert!(
19507 guards.contains(&PreprocessorGuard::Defined("HAVE_GETENTROPY".to_string())),
19508 "{guards:#?}"
19509 );
19510 assert!(
19511 !guards.contains(&PreprocessorGuard::Defined("_WIN32".to_string())),
19512 "the malformed linkage wrapper must not impose its stale guard: {guards:#?}"
19513 );
19514 }
19515
19516 #[test]
19517 #[hardwon("1e74d25492c1: C++ declaration recovery regressed for this structured syntax edge")]
19518 fn ordinary_macro_role_distinguishes_conditional_body_from_directive_tokens() {
19519 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";
19520 let mut parser = Parser::new();
19521 parser
19522 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19523 .expect("C++ grammar");
19524 let tree = parser.parse(source, None).expect("fixture tree");
19525 let root = tree.root_node();
19526 let node_at = |text: &str, start: usize| {
19527 root.descendant_for_byte_range(start, start + text.len())
19528 .expect("token node")
19529 };
19530
19531 let key_start = source.find("case KEY").expect("case label") + "case ".len();
19532 let guard_start = source.find("ENABLE_KEYS").expect("guard name");
19533 assert!(is_ordinary_macro_reference_node(node_at("KEY", key_start)));
19534 assert!(!is_ordinary_macro_reference_node(node_at(
19535 "ENABLE_KEYS",
19536 guard_start,
19537 )));
19538 }
19539
19540 #[test]
19541 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19542 fn bare_macro_guard_is_implied_by_a_stronger_conjunction() {
19543 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";
19544 let mut parser = Parser::new();
19545 parser
19546 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19547 .expect("C++ grammar");
19548 let tree = parser.parse(source, None).expect("fixture tree");
19549 let root = tree.root_node();
19550 let definition_start = source.find("target(void)").expect("definition");
19551 let reference_start = source.rfind("target()").expect("reference");
19552 let definition = root
19553 .descendant_for_byte_range(definition_start, definition_start + "target".len())
19554 .expect("definition node");
19555 let reference = root
19556 .descendant_for_byte_range(reference_start, reference_start + "target".len())
19557 .expect("reference node");
19558 let required =
19559 preprocessor_guard_environment(definition, source).expect("definition guard");
19560 let active = preprocessor_guard_environment(reference, source).expect("reference guard");
19561 assert!(guard_requirements_hold_at_reference(
19562 &required,
19563 Some(&active)
19564 ));
19565 }
19566
19567 #[test]
19568 #[hardwon("31913fee42a0: C++ declaration recovery regressed for this structured syntax edge")]
19569 fn g_autoptr_assignment_shape_recovers_only_the_named_macro_declarator() {
19570 let source = "g_autoptr(FuChunkArray) self = make_array();";
19571 let mut parser = Parser::new();
19572 parser
19573 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19574 .expect("C++ grammar");
19575 let tree = parser.parse(source, None).expect("fixture tree");
19576 let statement = tree.root_node().named_child(0).expect("statement");
19577 let binding =
19578 recognized_c_macro_declarator_binding(statement, source).expect("g_autoptr binding");
19579 assert_eq!(binding.name, "self");
19580 assert_eq!(binding.type_name, "FuChunkArray");
19581 assert_eq!(binding.pointer_depth, 1);
19582
19583 let near_miss = "holder(FuChunkArray) self = make_array();";
19584 let tree = parser.parse(near_miss, None).expect("near-miss tree");
19585 let statement = tree.root_node().named_child(0).expect("statement");
19586 assert!(recognized_c_macro_declarator_binding(statement, near_miss).is_none());
19587 }
19588
19589 #[test]
19590 #[hardwon("dc1b8c5a6ee8: C++ declaration recovery regressed for this structured syntax edge")]
19591 fn boolean_guard_normalization_proves_equivalence_and_implication() {
19592 let windows = BooleanGuardExpression::Defined("WIN32".to_string());
19593 let cygwin = BooleanGuardExpression::Defined("CYGWIN".to_string());
19594 let negated_windows_branch =
19595 BooleanGuardExpression::all([windows.clone(), cygwin.negated()]).negated();
19596 let portable = BooleanGuardExpression::any([windows.negated(), cygwin]);
19597 assert_eq!(negated_windows_branch, portable);
19598
19599 let missing_a = BooleanGuardExpression::Undefined("A".to_string());
19600 let missing_b = BooleanGuardExpression::Undefined("B".to_string());
19601 let missing_c = BooleanGuardExpression::Undefined("C".to_string());
19602 let fallback_branch = BooleanGuardExpression::any([missing_a.clone(), missing_b.clone()]);
19603 let fallback_declaration = BooleanGuardExpression::any([missing_a, missing_b, missing_c]);
19604 assert!(fallback_branch.implies(&fallback_declaration));
19605 assert!(
19606 BooleanGuardExpression::Truthy("FEATURE".to_string())
19607 .implies(&BooleanGuardExpression::Defined("FEATURE".to_string()))
19608 );
19609 assert!(
19610 BooleanGuardExpression::Undefined("FEATURE".to_string())
19611 .implies(&BooleanGuardExpression::Falsy("FEATURE".to_string()))
19612 );
19613 assert!(
19614 !BooleanGuardExpression::Defined("FEATURE".to_string())
19615 .implies(&BooleanGuardExpression::Truthy("FEATURE".to_string()))
19616 );
19617 assert!(!fallback_declaration.implies(&fallback_branch));
19618 }
19619
19620 #[test]
19621 #[hardwon("15e5ef579cdd: C++ declaration recovery regressed for this structured syntax edge")]
19622 fn c_keyword_argument_recovery_requires_an_enclosing_displaced_parameter() {
19623 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";
19624 let mut parser = Parser::new();
19625 parser
19626 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19627 .expect("C++ grammar");
19628 let tree = parser.parse(source, None).expect("fixture tree");
19629 let root = tree.root_node();
19630 let call = |marker: &str| {
19631 let start = source.find(marker).expect("call marker");
19632 let mut node = root
19633 .descendant_for_byte_range(start, start + "helper".len())
19634 .expect("call name node");
19635 loop {
19636 if node.kind() == "call_expression" {
19637 break node;
19638 }
19639 node = node.parent().expect("call expression ancestor");
19640 }
19641 };
19642 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.c");
19643 let cpp_file = ProjectFile::new(std::env::temp_dir(), "keyword-argument.cpp");
19644 let keyword_call = call("helper(NULL, template); /* bound */");
19645 let keyword_arguments = keyword_call
19646 .child_by_field_name("arguments")
19647 .expect("keyword argument list");
19648 assert_eq!(
19649 recovered_c_keyword_argument_count(&c_file, keyword_call, keyword_arguments, source),
19650 1
19651 );
19652
19653 let nested_call = call("helper(NULL, template); /* nested bound */");
19654 let nested_arguments = nested_call
19655 .child_by_field_name("arguments")
19656 .expect("nested keyword argument list");
19657 assert_eq!(
19658 recovered_c_keyword_argument_count(&c_file, nested_call, nested_arguments, source),
19659 1
19660 );
19661
19662 let lambda_call = call("helper(NULL, template); }(); /* lambda scope */");
19663 let lambda_arguments = lambda_call
19664 .child_by_field_name("arguments")
19665 .expect("lambda keyword argument list");
19666 assert_eq!(
19667 recovered_c_keyword_argument_count(&c_file, lambda_call, lambda_arguments, source),
19668 0,
19669 "a lambda body must not borrow the enclosing collapsed C parameter"
19670 );
19671
19672 assert_eq!(
19673 recovered_c_keyword_argument_count(&cpp_file, keyword_call, keyword_arguments, source),
19674 0
19675 );
19676
19677 let unbound_call = call("helper(NULL, template); /* unbound */");
19678 let unbound_arguments = unbound_call
19679 .child_by_field_name("arguments")
19680 .expect("unbound argument list");
19681 assert_eq!(
19682 recovered_c_keyword_argument_count(&c_file, unbound_call, unbound_arguments, source),
19683 0
19684 );
19685 }
19686
19687 #[test]
19688 #[brokk_bifrost_test_macros::hardwon(
19689 "#4407: collapsed C keyword parameters need structural position proof"
19690 )]
19691 fn c_keyword_argument_recovery_rejects_non_parameter_keyword_positions() {
19692 let source = r#"static int helper(const char *left, wchar_t *right) { return 0; }
19693int type_keyword(template *value) {
19694 return helper(NULL, template); /* type */
19695}
19696int qualifier_keyword(const *template) {
19697 return helper(NULL, template); /* qualifier */
19698}
19699int annotation_keyword(wchar_t *value __attribute__((template))) {
19700 return helper(NULL, template); /* annotation */
19701}
19702int default_keyword(wchar_t *value = template) {
19703 return helper(NULL, template); /* default */
19704}
19705int nested_expression(wchar_t *value, (template)) {
19706 return helper(NULL, template); /* nested */
19707}
19708int malformed_head = (wchar_t *template) {
19709 return helper(NULL, template); /* malformed */
19710};
19711"#;
19712 let tree = parse_cpp(source);
19713 let root = tree.root_node();
19714 let call = |marker: &str| {
19715 let start = source.find(marker).expect("call marker");
19716 let mut node = root
19717 .descendant_for_byte_range(start, start + "helper".len())
19718 .expect("call name node");
19719 loop {
19720 if node.kind() == "call_expression" {
19721 break node;
19722 }
19723 node = node.parent().expect("call expression ancestor");
19724 }
19725 };
19726 let c_file = ProjectFile::new(std::env::temp_dir(), "keyword-near-miss.c");
19727 for marker in [
19728 "helper(NULL, template); /* type */",
19729 "helper(NULL, template); /* qualifier */",
19730 "helper(NULL, template); /* annotation */",
19731 "helper(NULL, template); /* default */",
19732 "helper(NULL, template); /* nested */",
19733 "helper(NULL, template); /* malformed */",
19734 ] {
19735 let keyword_call = call(marker);
19736 let keyword_arguments = keyword_call
19737 .child_by_field_name("arguments")
19738 .expect("keyword argument list");
19739 assert_eq!(
19740 recovered_c_keyword_argument_count(
19741 &c_file,
19742 keyword_call,
19743 keyword_arguments,
19744 source,
19745 ),
19746 0,
19747 "keyword in {marker} must not prove a parameter binding"
19748 );
19749 }
19750 }
19751
19752 #[test]
19753 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19754 fn c_function_declarator_recovery_accepts_invocations_not_binders() {
19755 let source = r#"#define MAKE(type) type *value
19756MAKE(int *);
19757typedef struct Item Item;
19758struct CPUX86State { struct { int ZMM_L(int); } xmm_regs[8]; };
19759void gen_op_movl(void *s, int first, int second) { }
19760const char *strZ(const char *value) { return value; }
19761int body(void *s) {
19762 MAKE(int *);
19763 gen_op_movl(s, offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)),
19764 offsetof(CPUX86State, xmm_regs[0].ZMM_L(0)));
19765 execvp(strZ(value), UNCONSTIFY(char **, args));
19766}
19767 #define DEV_CHECK_PRESENCE(TYPE, MEMBER, DEVTYPE, PROPERTY, VALUE) \
19768 if (!((TYPE)target)->MEMBER) { check(DEVTYPE, PROPERTY, VALUE); }
19769int recovered_deviation(struct Deviation *d, struct Target *target, void *ctx) {
19770 if (d->units) {
19771 switch (target->nodetype) {
19772 case 1:
19773 case 2:
19774 break;
19775 default:
19776 AMEND_WRONG_NODETYPE("deviation", "replace", "units");
19777 }
19778 DEV_CHECK_PRESENCE(struct Item *, units, "replacing", "units", d->units);
19779 lysdict_remove(ctx, ((struct Item *)target)->units);
19780 DUP_STRING_GOTO(ctx, d->units, ((struct Item *)target)->units, ret, cleanup);
19781 }
19782 return 0;
19783 }
19784STATIC EFI_STATUS Encode () { return 0; }
19785"#;
19786 let tree = parse_cpp(source);
19787 let top_macro_start = source.find("MAKE(int *);").expect("top macro");
19788 let top_macro = tree
19789 .root_node()
19790 .named_descendant_for_byte_range(top_macro_start, top_macro_start + 4)
19791 .expect("top macro node");
19792 let body_macro_start = source
19793 .match_indices("MAKE(int *);")
19794 .nth(1)
19795 .expect("body macro")
19796 .0;
19797 let body_macro = tree
19798 .root_node()
19799 .named_descendant_for_byte_range(body_macro_start, body_macro_start + 4)
19800 .expect("body macro node");
19801 let function_call_start = source
19802 .find("gen_op_movl(s, offsetof(CPUX86State")
19803 .expect("function call");
19804 let function_call = tree
19805 .root_node()
19806 .named_descendant_for_byte_range(function_call_start, function_call_start + 11)
19807 .expect("function call node");
19808 let strz_start = source.find("strZ(value)").expect("nested function call");
19809 let strz = tree
19810 .root_node()
19811 .named_descendant_for_byte_range(strz_start, strz_start + 4)
19812 .expect("nested function call node");
19813 let recovered_call_start = source.find("lysdict_remove(ctx").expect("recovered call");
19814 let recovered_call = tree
19815 .root_node()
19816 .named_descendant_for_byte_range(
19817 recovered_call_start,
19818 recovered_call_start + "lysdict_remove".len(),
19819 )
19820 .expect("recovered call node");
19821 let binder_start = source.find("Encode").expect("binder");
19822 let binder = tree
19823 .root_node()
19824 .named_descendant_for_byte_range(binder_start, binder_start + 6)
19825 .expect("binder node");
19826
19827 assert!(recovered_c_function_declarator_invocation(top_macro));
19828 assert!(recovered_c_function_declarator_invocation(body_macro));
19829 assert!(recovered_c_function_declarator_invocation(function_call));
19830 assert!(recovered_c_function_declarator_invocation(strz));
19831 assert!(recovered_c_function_declarator_invocation(recovered_call));
19832 assert!(!recovered_c_function_declarator_invocation(binder));
19833 }
19834
19835 #[test]
19836 #[hardwon("31982bd4d78a: C++ declaration recovery regressed for this structured syntax edge")]
19837 fn c_parenthesized_declarator_recovery_keeps_keyword_argument_and_rejects_siblings() {
19838 let source = r#"typedef int krb5_context;
19839int helper(int first, int second) { return first + second; }
19840static krb5_context ctx;
19841int main(int argc, char **argv) {
19842 int ccinitial;
19843 const char *collection_name, *typename;
19844 typename = helper(ctx, ccinitial);
19845 return 0;
19846}
19847"#;
19848 let tree = parse_cpp(source);
19849 let ctx = tree
19850 .root_node()
19851 .descendant_for_byte_range(
19852 source.find("ctx, ccinitial").expect("ctx argument"),
19853 source.find("ctx, ccinitial").expect("ctx argument") + 3,
19854 )
19855 .expect("ctx node");
19856 let ccinitial_start = source.find("ctx, ccinitial").expect("ctx argument") + 5;
19857 let ccinitial = tree
19858 .root_node()
19859 .descendant_for_byte_range(ccinitial_start, ccinitial_start + "ccinitial".len())
19860 .expect("sibling node");
19861 let typename = named_node_at(&tree, source, "typename = helper");
19862 let helper = named_node_at(&tree, source, "helper(ctx, ccinitial)");
19863
19864 assert_eq!(ctx.kind(), "identifier");
19865 assert!(recovered_c_parenthesized_declarator_reference(ctx));
19866 assert!(!recovered_c_parenthesized_declarator_reference(ccinitial));
19867 assert!(!recovered_c_parenthesized_declarator_reference(typename));
19868 assert!(!recovered_c_parenthesized_declarator_reference(helper));
19869 }
19870
19871 fn first_enum_flattened_namespace(source: &str) -> Option<Vec<String>> {
19872 let mut parser = Parser::new();
19873 parser
19874 .set_language(&tree_sitter_cpp::LANGUAGE.into())
19875 .expect("C++ grammar");
19876 let tree = parser.parse(source, None).expect("C++ fixture tree");
19877 let mut stack = vec![tree.root_node()];
19878 while let Some(node) = stack.pop() {
19879 if node.kind() == "enum_specifier" {
19880 return flattened_macro_namespace_components(node, source);
19881 }
19882 let mut cursor = node.walk();
19883 let children = node.named_children(&mut cursor).collect::<Vec<_>>();
19884 stack.extend(children.into_iter().rev());
19885 }
19886 None
19887 }
19888
19889 #[test]
19890 #[hardwon("0e6df36dfa5d: C++ declaration recovery regressed for this structured syntax edge")]
19891 fn flattened_namespace_scope_requires_a_complete_sentinel_envelope() {
19892 let complete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19893namespace detail
19894{
19895enum class value_t { null };
19896}
19897NLOHMANN_JSON_NAMESPACE_END
19898NLOHMANN_JSON_NAMESPACE_BEGIN
19899namespace next
19900{
19901struct next_type {};
19902}
19903NLOHMANN_JSON_NAMESPACE_END
19904"#;
19905 assert_eq!(
19906 first_enum_flattened_namespace(complete),
19907 Some(vec!["detail".to_string()])
19908 );
19909
19910 let stale_end = format!("NLOHMANN_JSON_NAMESPACE_END\n{complete}");
19911 assert_eq!(
19912 first_enum_flattened_namespace(&stale_end),
19913 Some(vec!["detail".to_string()]),
19914 "a stale end marker before the begin marker must not replace the intended namespace"
19915 );
19916
19917 let incomplete = r#"NLOHMANN_JSON_NAMESPACE_BEGIN
19918namespace detail
19919{
19920enum class value_t { null };
19921}
19922struct next_type {};
19923"#;
19924 assert_eq!(first_enum_flattened_namespace(incomplete), None);
19925 }
19926}
19927
19928#[cfg(test)]
19944mod lookup_order_properties {
19945 use super::*;
19946 use proptest::prelude::*;
19947
19948 const ATOMS: [&str; 9] = ["a", "b", "A", "a$b", "a$", "$a", "ab", "naïve", "識別子"];
19952 const REL_PATHS: [&str; 3] = ["a.cpp", "b.cpp", "sub/a.cpp"];
19953 const ROOT_NAMES: [&str; 2] = ["ws", "ws_much_longer_root_name"];
19957 const SIGNATURES: [Option<&str>; 3] = [None, Some("()"), Some("(int)")];
19958 const KINDS: [CodeUnitType; 6] = [
19959 CodeUnitType::Class,
19960 CodeUnitType::Function,
19961 CodeUnitType::Field,
19962 CodeUnitType::Module,
19963 CodeUnitType::Macro,
19964 CodeUnitType::FileScope,
19965 ];
19966
19967 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
19970 enum ProbedOrder {
19971 Before,
19972 Tied,
19973 After,
19974 Contradictory,
19977 }
19978
19979 impl ProbedOrder {
19980 fn mirror(self) -> Self {
19981 match self {
19982 ProbedOrder::Before => ProbedOrder::After,
19983 ProbedOrder::After => ProbedOrder::Before,
19984 other => other,
19985 }
19986 }
19987
19988 fn signum(self) -> i8 {
19990 match self {
19991 ProbedOrder::Before => -1,
19992 ProbedOrder::Tied => 0,
19993 ProbedOrder::After => 1,
19994 ProbedOrder::Contradictory => panic!("probed a non-dual comparator"),
19995 }
19996 }
19997 }
19998
19999 fn probe_order(left: &CodeUnit, right: &CodeUnit) -> ProbedOrder {
20007 if left == right {
20008 return ProbedOrder::Tied;
20011 }
20012 let mut forward = vec![left.clone(), right.clone()];
20013 sort_lookup_units(&mut forward);
20014 let mut backward = vec![right.clone(), left.clone()];
20015 sort_lookup_units(&mut backward);
20016 let left_first = backward[0] == *left;
20017 let right_first = forward[0] == *right;
20018 match (left_first, right_first) {
20019 (true, true) => ProbedOrder::Contradictory,
20020 (true, false) => ProbedOrder::Before,
20021 (false, true) => ProbedOrder::After,
20022 (false, false) => ProbedOrder::Tied,
20023 }
20024 }
20025
20026 fn code_unit_strategy() -> impl Strategy<Value = CodeUnit> {
20027 (
20028 0..ROOT_NAMES.len(),
20029 0..REL_PATHS.len(),
20030 0..KINDS.len(),
20031 prop::collection::vec((0..ATOMS.len(), 0..SegmentKind::ALL.len()), 1..=3),
20032 0..3usize,
20033 0..SIGNATURES.len(),
20034 any::<bool>(),
20035 )
20036 .prop_map(
20037 |(root, rel_path, kind, segments, package_prefix, signature, synthetic)| {
20038 let source = ProjectFile::new(
20039 std::env::temp_dir().join(ROOT_NAMES[root]),
20040 REL_PATHS[rel_path],
20041 );
20042 let interner = segment_interner();
20043 let mut fq = FqName::new();
20044 for (atom, segment_kind) in &segments {
20045 fq.push(interner.intern(ATOMS[*atom], SegmentKind::ALL[*segment_kind]));
20046 }
20047 let package_segment_count = package_prefix % fq.len();
20049 CodeUnit::from_fq(
20050 source,
20051 KINDS[kind],
20052 fq,
20053 package_segment_count,
20054 SIGNATURES[signature].map(str::to_string),
20055 synthetic,
20056 )
20057 },
20058 )
20059 }
20060
20061 proptest! {
20062 #![proptest_config(ProptestConfig::with_cases(256))]
20063
20064 #[test]
20067 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20068 fn lookup_order_is_reflexive_and_dual(
20069 left in code_unit_strategy(),
20070 right in code_unit_strategy(),
20071 ) {
20072 prop_assert_eq!(
20073 probe_order(&left, &left),
20074 ProbedOrder::Tied,
20075 "a unit must tie with itself: {:?}",
20076 left
20077 );
20078 let forward = probe_order(&left, &right);
20079 prop_assert_ne!(
20080 forward,
20081 ProbedOrder::Contradictory,
20082 "comparator put each of these strictly first: left={:?} right={:?}",
20083 left,
20084 right
20085 );
20086 prop_assert_eq!(
20087 probe_order(&right, &left),
20088 forward.mirror(),
20089 "compare(b, a) must reverse compare(a, b): left={:?} right={:?}",
20090 left,
20091 right
20092 );
20093 }
20094
20095 #[test]
20097 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20098 fn lookup_order_is_transitive(
20099 a in code_unit_strategy(),
20100 b in code_unit_strategy(),
20101 c in code_unit_strategy(),
20102 ) {
20103 let ab = probe_order(&a, &b);
20104 let bc = probe_order(&b, &c);
20105 let ac = probe_order(&a, &c);
20106 for (probed, pair) in [(ab, "a,b"), (bc, "b,c"), (ac, "a,c")] {
20107 prop_assert_ne!(
20108 probed,
20109 ProbedOrder::Contradictory,
20110 "comparator is not dual over {}: a={:?} b={:?} c={:?}",
20111 pair,
20112 a,
20113 b,
20114 c
20115 );
20116 }
20117 if ab.signum() <= 0 && bc.signum() <= 0 {
20118 prop_assert!(
20119 ac.signum() <= 0,
20120 "transitivity broken: a<=b ({:?}) and b<=c ({:?}) but a?c is {:?}; \
20121 a={:?} b={:?} c={:?}",
20122 ab,
20123 bc,
20124 ac,
20125 a,
20126 b,
20127 c
20128 );
20129 }
20130 }
20131
20132 #[test]
20135 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20136 fn lookup_order_separates_distinct_identities(
20137 left in code_unit_strategy(),
20138 right in code_unit_strategy(),
20139 ) {
20140 if probe_order(&left, &right) == ProbedOrder::Tied {
20141 prop_assert_eq!(
20142 &left,
20143 &right,
20144 "distinct identities tied, so their order is whatever order they \
20145 arrived in: left_segments={:?} right_segments={:?}",
20146 left.fq_segments_debug(),
20147 right.fq_segments_debug()
20148 );
20149 }
20150 }
20151
20152 #[test]
20155 #[brokk_bifrost_test_macros::hardwon("#1876: distinct C++ declaration identities could tie in lookup ordering and expose hash iteration order")]
20156 fn lookup_sort_is_permutation_invariant(
20157 units in prop::collection::vec(code_unit_strategy(), 1..=8),
20158 ) {
20159 let mut sorted = units.clone();
20160 sort_lookup_units(&mut sorted);
20161 for rotation in 0..units.len() {
20162 for reversed in [false, true] {
20163 let mut permuted = units.clone();
20164 permuted.rotate_left(rotation);
20165 if reversed {
20166 permuted.reverse();
20167 }
20168 sort_lookup_units(&mut permuted);
20169 prop_assert_eq!(
20170 &permuted,
20171 &sorted,
20172 "sorting a permutation gave a different list \
20173 (rotation={}, reversed={}): input={:?}",
20174 rotation,
20175 reversed,
20176 units
20177 );
20178 }
20179 }
20180 }
20181 }
20182}